Merge "Add requestSatelliteDisplayNameForSubscription in SatelliteManager" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index e184704..371177c 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -23,7 +23,7 @@
         "aconfig_mediacodec_flags_java_lib",
         "aconfig_settingslib_flags_java_lib",
         "aconfig_trade_in_mode_flags_java_lib",
-        "android-sdk-flags-java",
+        "adpf_flags_java_lib",
         "android.adaptiveauth.flags-aconfig-java",
         "android.app.appfunctions.flags-aconfig-java",
         "android.app.assist.flags-aconfig-java",
@@ -56,13 +56,13 @@
         "android.media.tv.flags-aconfig-java",
         "android.multiuser.flags-aconfig-java",
         "android.net.platform.flags-aconfig-java",
-        "android.net.vcn.flags-aconfig-java-export",
         "android.net.wifi.flags-aconfig-java",
         "android.nfc.flags-aconfig-java",
         "android.os.flags-aconfig-java",
         "android.os.vibrator.flags-aconfig-java",
         "android.permission.flags-aconfig-java",
         "android.provider.flags-aconfig-java",
+        "android.sdk.flags-aconfig-java",
         "android.security.flags-aconfig-java",
         "android.server.app.flags-aconfig-java",
         "android.service.autofill.flags-aconfig-java",
@@ -99,12 +99,14 @@
         "com.android.media.flags.editing-aconfig-java",
         "com.android.media.flags.performance-aconfig-java",
         "com.android.media.flags.projection-aconfig-java",
+        "com.android.net.http.flags-aconfig-exported-java",
         "com.android.net.thread.platform.flags-aconfig-java",
         "com.android.ranging.flags.ranging-aconfig-java-export",
         "com.android.server.contextualsearch.flags-java",
         "com.android.server.flags.services-aconfig-java",
         "com.android.text.flags-aconfig-java",
         "com.android.window.flags.window-aconfig-java",
+        "conscrypt_exported_aconfig_flags_lib",
         "device_policy_aconfig_flags_lib",
         "display_flags_lib",
         "dropbox_flags_lib",
@@ -194,6 +196,14 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Conscrypt
+java_aconfig_library {
+    name: "conscrypt_exported_aconfig_flags_lib",
+    aconfig_declarations: "conscrypt-aconfig-flags",
+    mode: "exported",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Telecom
 java_aconfig_library {
     name: "telecom_flags_core_java_lib",
@@ -328,6 +338,7 @@
     name: "android_nfc_flags_aconfig_c_lib",
     vendor_available: true,
     aconfig_declarations: "android.nfc.flags-aconfig",
+    min_sdk_version: "34",
     apex_available: [
         "//apex_available:platform",
         "com.android.nfcservices",
@@ -437,6 +448,8 @@
     min_sdk_version: "30",
     apex_available: [
         "//apex_available:platform",
+        "com.android.art",
+        "com.android.art.debug",
         "com.android.btservices",
         "com.android.mediaprovider",
         "com.android.permission",
@@ -652,6 +665,8 @@
     min_sdk_version: "30",
     apex_available: [
         "//apex_available:platform",
+        "com.android.art",
+        "com.android.art.debug",
         "com.android.permission",
     ],
 }
@@ -871,6 +886,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Adaptive Performance
+java_aconfig_library {
+    name: "adpf_flags_java_lib",
+    aconfig_declarations: "adpf_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Graphics
 java_aconfig_library {
     name: "hwui_flags_java_lib",
@@ -966,6 +988,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "android.app.flags-aconfig-java-host",
+    aconfig_declarations: "android.app.flags-aconfig",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Broadcast Radio
 aconfig_declarations {
     name: "android.hardware.radio.flags-aconfig",
@@ -1174,25 +1203,6 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
-// VCN
-// TODO:376339506 Move the VCN code, the flag declaration and
-// java_aconfig_library to framework-connectivity-b
-aconfig_declarations {
-    name: "android.net.vcn.flags-aconfig",
-    package: "android.net.vcn",
-    container: "com.android.tethering",
-    exportable: true,
-    srcs: ["core/java/android/net/vcn/*.aconfig"],
-}
-
-java_aconfig_library {
-    name: "android.net.vcn.flags-aconfig-java-export",
-    aconfig_declarations: "android.net.vcn.flags-aconfig",
-    mode: "exported",
-    min_sdk_version: "35",
-    defaults: ["framework-minus-apex-aconfig-java-defaults"],
-}
-
 // DevicePolicy
 aconfig_declarations {
     name: "device_policy_aconfig_flags",
@@ -1210,6 +1220,17 @@
 }
 
 java_aconfig_library {
+    name: "device_policy_aconfig_flags_java_export",
+    aconfig_declarations: "device_policy_aconfig_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+    min_sdk_version: "30",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.permission",
+    ],
+}
+
+java_aconfig_library {
     name: "device_policy_aconfig_flags_lib_host",
     aconfig_declarations: "device_policy_aconfig_flags",
     host_supported: true,
diff --git a/Android.bp b/Android.bp
index 424a4a71..a525583b8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -83,11 +83,11 @@
         ":framework-telecomm-sources",
         ":framework-telephony-common-sources",
         ":framework-telephony-sources",
-        ":framework-vcn-util-sources",
         ":framework-wifi-annotations",
         ":framework-wifi-non-updatable-sources",
         ":PacProcessor-aidl-sources",
         ":ProxyHandler-aidl-sources",
+        ":vcn-utils-platform-sources",
         ":net-utils-framework-common-srcs",
 
         // AIDL from frameworks/base/native/
@@ -313,9 +313,9 @@
             ":framework-telecomm-sources",
             ":framework-telephony-common-sources",
             ":framework-telephony-sources",
-            ":framework-vcn-util-sources",
             ":framework-wifi-annotations",
             ":framework-wifi-non-updatable-sources",
+            ":vcn-utils-platform-sources",
             ":PacProcessor-aidl-sources",
             ":ProxyHandler-aidl-sources",
             ":net-utils-framework-common-srcs",
@@ -371,6 +371,7 @@
         "view-inspector-annotation-processor",
         "staledataclass-annotation-processor",
         "error_prone_android_framework",
+        "systemfeatures-metadata-processor",
     ],
     // Exports needed for staledataclass-annotation-processor, see b/139342589.
     javacflags: [
@@ -597,7 +598,7 @@
     srcs: [
         "core/java/com/android/internal/util/HexDump.java",
         "core/java/com/android/internal/util/WakeupMessage.java",
-        "services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java",
+        "packages/Vcn/framework-b/src/android/net/vcn/util/PersistableBundleUtils.java",
         "telephony/java/android/telephony/Annotation.java",
     ],
 }
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index a92a543..d83109a 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -18,7 +18,7 @@
                tests/
                tools/
 bpfmt = -d
-ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI,libs/WindowManager/Shell/src/com/android/wm/shell/freeform
+ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI,libs/WindowManager/Shell/src/com/android/wm/shell/freeform,libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode
 
 [Hook Scripts]
 checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
diff --git a/android-sdk-flags/Android.bp b/android-sdk-flags/Android.bp
index 79a0b9a..d1df2ca 100644
--- a/android-sdk-flags/Android.bp
+++ b/android-sdk-flags/Android.bp
@@ -17,14 +17,21 @@
 }
 
 aconfig_declarations {
-    name: "android-sdk-flags",
+    name: "android.sdk.flags-aconfig",
     package: "android.sdk",
     container: "system",
     srcs: ["flags.aconfig"],
 }
 
 java_aconfig_library {
-    name: "android-sdk-flags-java",
-    aconfig_declarations: "android-sdk-flags",
+    name: "android.sdk.flags-aconfig-java",
+    aconfig_declarations: "android.sdk.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+java_aconfig_library {
+    name: "android.sdk.flags-aconfig-java-host",
+    aconfig_declarations: "android.sdk.flags-aconfig",
+    host_supported: true,
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index 47a85498f..63624d8 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -29,6 +29,7 @@
    namespace: "backstage_power"
    description: "Detect, report and take action on jobs that maybe abandoned by the app without calling jobFinished."
    bug: "372529068"
+   is_exported: true
 }
 
 flag {
@@ -36,6 +37,7 @@
     namespace: "backstage_power"
     description: "Ignore the important_while_foreground flag and change the related APIs to be not effective"
     bug: "374175032"
+    is_exported: true
 }
 
 flag {
diff --git a/apex/jobscheduler/service/aconfig/app_idle.aconfig b/apex/jobscheduler/service/aconfig/app_idle.aconfig
index f079c02..74d2a59 100644
--- a/apex/jobscheduler/service/aconfig/app_idle.aconfig
+++ b/apex/jobscheduler/service/aconfig/app_idle.aconfig
@@ -21,3 +21,10 @@
        purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "adjust_default_bucket_elevation_params"
+    namespace: "backstage_power"
+    description: "Adjust the default bucket evaluation parameters"
+    bug: "379909479"
+}
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 963307b..a5a08fb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -573,6 +573,7 @@
                         case Constants.KEY_MIN_LINEAR_BACKOFF_TIME_MS:
                         case Constants.KEY_MIN_EXP_BACKOFF_TIME_MS:
                         case Constants.KEY_SYSTEM_STOP_TO_FAILURE_RATIO:
+                        case Constants.KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF:
                             mConstants.updateBackoffConstantsLocked();
                             break;
                         case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
@@ -679,6 +680,8 @@
         private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms";
         private static final String KEY_SYSTEM_STOP_TO_FAILURE_RATIO =
                 "system_stop_to_failure_ratio";
+        private static final String KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF =
+                "abandoned_job_timeouts_before_aggressive_backoff";
         private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
         private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
         private static final String KEY_CONN_USE_CELL_SIGNAL_STRENGTH =
@@ -750,6 +753,7 @@
         private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
         private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
         private static final int DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO = 3;
+        private static final int DEFAULT_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF = 3;
         private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
         private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
         private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true;
@@ -845,7 +849,12 @@
          * incremental failure in the backoff policy calculation.
          */
         int SYSTEM_STOP_TO_FAILURE_RATIO = DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO;
-
+        /**
+         * Number of consecutive timeouts by abandoned jobs before we change to aggressive backoff
+         * policy.
+         */
+        int ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF =
+                DEFAULT_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF;
         /**
          * The fraction of a job's running window that must pass before we
          * consider running it when the network is congested.
@@ -1078,6 +1087,10 @@
             SYSTEM_STOP_TO_FAILURE_RATIO = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_SYSTEM_STOP_TO_FAILURE_RATIO,
                     DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO);
+            ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF = DeviceConfig.getInt(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF,
+                    DEFAULT_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF);
         }
 
         // TODO(141645789): move into ConnectivityController.CcConfig
@@ -1287,6 +1300,8 @@
             pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println();
             pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
             pw.print(KEY_SYSTEM_STOP_TO_FAILURE_RATIO, SYSTEM_STOP_TO_FAILURE_RATIO).println();
+            pw.print(KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF,
+                    ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF).println();
             pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
             pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
             pw.print(KEY_CONN_USE_CELL_SIGNAL_STRENGTH, CONN_USE_CELL_SIGNAL_STRENGTH).println();
@@ -2997,6 +3012,7 @@
 
         final long initialBackoffMillis = job.getInitialBackoffMillis();
         int numFailures = failureToReschedule.getNumFailures();
+        int numAbandonedFailures = failureToReschedule.getNumAbandonedFailures();
         int numSystemStops = failureToReschedule.getNumSystemStops();
         // We should back off slowly if JobScheduler keeps stopping the job,
         // but back off immediately if the issue appeared to be the app's fault
@@ -3006,9 +3022,19 @@
                 || internalStopReason == JobParameters.INTERNAL_STOP_REASON_ANR
                 || stopReason == JobParameters.STOP_REASON_USER) {
             numFailures++;
+        } else if (android.app.job.Flags.handleAbandonedJobs()
+                && internalStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED) {
+            numAbandonedFailures++;
+            numFailures++;
         } else {
             numSystemStops++;
         }
+
+        int backoffPolicy = job.getBackoffPolicy();
+        if (shouldUseAggressiveBackoff(numAbandonedFailures)) {
+            backoffPolicy = JobInfo.BACKOFF_POLICY_EXPONENTIAL;
+        }
+
         final int backoffAttempts =
                 numFailures + numSystemStops / mConstants.SYSTEM_STOP_TO_FAILURE_RATIO;
         final long earliestRuntimeMs;
@@ -3017,7 +3043,7 @@
             earliestRuntimeMs = JobStatus.NO_EARLIEST_RUNTIME;
         } else {
             long delayMillis;
-            switch (job.getBackoffPolicy()) {
+            switch (backoffPolicy) {
                 case JobInfo.BACKOFF_POLICY_LINEAR: {
                     long backoff = initialBackoffMillis;
                     if (backoff < mConstants.MIN_LINEAR_BACKOFF_TIME_MS) {
@@ -3046,7 +3072,7 @@
         }
         JobStatus newJob = new JobStatus(failureToReschedule,
                 earliestRuntimeMs,
-                JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops,
+                JobStatus.NO_LATEST_RUNTIME, numFailures, numAbandonedFailures, numSystemStops,
                 failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis(),
                 failureToReschedule.getCumulativeExecutionTimeMs());
         if (stopReason == JobParameters.STOP_REASON_USER) {
@@ -3069,6 +3095,20 @@
     }
 
     /**
+     * Returns {@code true} if the given number of abandoned failures indicates that JobScheduler
+     * should use an aggressive backoff policy.
+     *
+     * @param numAbandonedFailures The number of abandoned failures.
+     * @return {@code true} if the given number of abandoned failures indicates that JobScheduler
+     *     should use an aggressive backoff policy.
+     */
+    public boolean shouldUseAggressiveBackoff(int numAbandonedFailures) {
+        return android.app.job.Flags.handleAbandonedJobs()
+                && numAbandonedFailures
+                        > mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF;
+    }
+
+    /**
      * Maximum time buffer in which JobScheduler will try to optimize periodic job scheduling. This
      * does not cause a job's period to be larger than requested (eg: if the requested period is
      * shorter than this buffer). This is used to put a limit on when JobScheduler will intervene
@@ -3147,6 +3187,7 @@
             return new JobStatus(periodicToReschedule,
                     elapsedNow + period - flex, elapsedNow + period,
                     0 /* numFailures */, 0 /* numSystemStops */,
+                    0 /* numAbandonedFailures */,
                     sSystemClock.millis() /* lastSuccessfulRunTime */,
                     periodicToReschedule.getLastFailedRunTime(),
                     0 /* Reset cumulativeExecutionTime because of successful execution */);
@@ -3163,6 +3204,7 @@
         return new JobStatus(periodicToReschedule,
                 newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
                 0 /* numFailures */, 0 /* numSystemStops */,
+                0 /* numAbandonedFailures */,
                 sSystemClock.millis() /* lastSuccessfulRunTime */,
                 periodicToReschedule.getLastFailedRunTime(),
                 0 /* Reset cumulativeExecutionTime because of successful execution */);
@@ -3171,6 +3213,10 @@
     @VisibleForTesting
     void maybeProcessBuggyJob(@NonNull JobStatus jobStatus, int debugStopReason) {
         boolean jobTimedOut = debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT;
+        if (android.app.job.Flags.handleAbandonedJobs()) {
+            jobTimedOut |= (debugStopReason
+                == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+        }
         // If madeActive = 0, the job never actually started.
         if (!jobTimedOut && jobStatus.madeActive > 0) {
             final long executionDurationMs = sUptimeMillisClock.millis() - jobStatus.madeActive;
@@ -3252,9 +3298,12 @@
         // we stop it.
         final JobStatus rescheduledJob = needsReschedule
                 ? getRescheduleJobForFailureLocked(jobStatus, stopReason, debugStopReason) : null;
+        final boolean isStopReasonAbandoned = android.app.job.Flags.handleAbandonedJobs()
+                && (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
         if (rescheduledJob != null
                 && !rescheduledJob.shouldTreatAsUserInitiatedJob()
                 && (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT
+                || isStopReasonAbandoned
                 || debugStopReason == JobParameters.INTERNAL_STOP_REASON_PREEMPT)) {
             rescheduledJob.disallowRunInBatterySaverAndDoze();
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index d8934d8..dfb3681 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -269,7 +269,9 @@
                         convertRtcBoundsToElapsed(utcTimes, elapsedNow);
                 JobStatus newJob = new JobStatus(job,
                         elapsedRuntimes.first, elapsedRuntimes.second,
-                        0, 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime(),
+                        0 /* numFailures */, 0 /* numAbandonedFailures */,
+                        0 /* numSystemStops */, job.getLastSuccessfulRunTime(),
+                        job.getLastFailedRunTime(),
                         job.getCumulativeExecutionTimeMs());
                 newJob.prepareLocked();
                 toAdd.add(newJob);
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 a3eaefd..5a33aa0 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
@@ -316,6 +316,12 @@
     private final int numFailures;
 
     /**
+     * How many times this job has stopped due to {@link
+     * JobParameters#STOP_REASON_TIMEOUT_ABANDONED}.
+     */
+    private final int mNumAbandonedFailures;
+
+    /**
      * The number of times JobScheduler has forced this job to stop due to reasons mostly outside
      * of the app's control.
      */
@@ -605,6 +611,8 @@
      * @param tag A string associated with the job for debugging/logging purposes.
      * @param numFailures Count of how many times this job has requested a reschedule because
      *     its work was not yet finished.
+     * @param mNumAbandonedFailures Count of how many times this job has requested a reschedule
+     *     because it was abandoned.
      * @param numSystemStops Count of how many times JobScheduler has forced this job to stop due to
      *     factors mostly out of the app's control.
      * @param earliestRunTimeElapsedMillis Milestone: earliest point in time at which the job
@@ -617,7 +625,7 @@
      */
     private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
             int sourceUserId, int standbyBucket, @Nullable String namespace, String tag,
-            int numFailures, int numSystemStops,
+            int numFailures, int mNumAbandonedFailures, int numSystemStops,
             long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
             long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs,
             int internalFlags,
@@ -677,6 +685,7 @@
         this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
         this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
         this.numFailures = numFailures;
+        this.mNumAbandonedFailures = mNumAbandonedFailures;
         mNumSystemStops = numSystemStops;
 
         int requiredConstraints = job.getConstraintFlags();
@@ -750,7 +759,8 @@
         this(jobStatus.getJob(), jobStatus.getUid(),
                 jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
                 jobStatus.getStandbyBucket(), jobStatus.getNamespace(),
-                jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getNumSystemStops(),
+                jobStatus.getSourceTag(), jobStatus.getNumFailures(),
+                jobStatus.getNumAbandonedFailures(), jobStatus.getNumSystemStops(),
                 jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
                 jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
                 jobStatus.getCumulativeExecutionTimeMs(),
@@ -787,6 +797,7 @@
         this(job, callingUid, sourcePkgName, sourceUserId,
                 standbyBucket, namespace,
                 sourceTag, /* numFailures */ 0, /* numSystemStops */ 0,
+                /* mNumAbandonedFailures */ 0,
                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
                 lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTimeMs,
                 innerFlags, dynamicConstraints);
@@ -806,13 +817,15 @@
     /** Create a new job to be rescheduled with the provided parameters. */
     public JobStatus(JobStatus rescheduling,
             long newEarliestRuntimeElapsedMillis,
-            long newLatestRuntimeElapsedMillis, int numFailures, int numSystemStops,
+            long newLatestRuntimeElapsedMillis, int numFailures,
+            int mNumAbandonedFailures, int numSystemStops,
             long lastSuccessfulRunTime, long lastFailedRunTime,
             long cumulativeExecutionTimeMs) {
         this(rescheduling.job, rescheduling.getUid(),
                 rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
                 rescheduling.getStandbyBucket(), rescheduling.getNamespace(),
-                rescheduling.getSourceTag(), numFailures, numSystemStops,
+                rescheduling.getSourceTag(), numFailures,
+                mNumAbandonedFailures, numSystemStops,
                 newEarliestRuntimeElapsedMillis,
                 newLatestRuntimeElapsedMillis,
                 lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTimeMs,
@@ -851,7 +864,8 @@
         int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
                 sourceUserId, elapsedNow);
         return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
-                standbyBucket, namespace, tag, /* numFailures */ 0, /* numSystemStops */ 0,
+                standbyBucket, namespace, tag, /* numFailures */ 0,
+                /* mNumAbandonedFailures */ 0, /* numSystemStops */ 0,
                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
                 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
                 /* cumulativeExecutionTime */ 0,
@@ -1146,6 +1160,13 @@
     }
 
     /**
+     * Returns the number of times the job stopped previously for STOP_REASON_TIMEOUT_ABANDONED.
+     */
+    public int getNumAbandonedFailures() {
+        return mNumAbandonedFailures;
+    }
+
+    /**
      * Returns the number of times the system stopped a previous execution of this job for reasons
      * that were likely outside the app's control.
      */
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index c9d3407..9871d71 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -337,11 +337,11 @@
      */
     long[] mAppStandbyElapsedThresholds = DEFAULT_ELAPSED_TIME_THRESHOLDS;
     /** Minimum time a strong usage event should keep the bucket elevated. */
-    long mStrongUsageTimeoutMillis = ConstantsObserver.DEFAULT_STRONG_USAGE_TIMEOUT;
+    long mStrongUsageTimeoutMillis = ConstantsObserver.DEFAULT_LEGACY_STRONG_USAGE_TIMEOUT;
     /** Minimum time a notification seen event should keep the bucket elevated. */
     long mNotificationSeenTimeoutMillis = ConstantsObserver.DEFAULT_NOTIFICATION_TIMEOUT;
     /** Minimum time a slice pinned event should keep the bucket elevated. */
-    long mSlicePinnedTimeoutMillis = ConstantsObserver.DEFAULT_SLICE_PINNED_TIMEOUT;
+    long mSlicePinnedTimeoutMillis = ConstantsObserver.DEFAULT_LEGACY_SLICE_PINNED_TIMEOUT;
     /** The standby bucket that an app will be promoted on a notification-seen event */
     int mNotificationSeenPromotedBucket =
             ConstantsObserver.DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET;
@@ -362,7 +362,9 @@
     /** Maximum time to wait for a prediction before using simple timeouts to downgrade buckets. */
     long mPredictionTimeoutMillis = DEFAULT_PREDICTION_TIMEOUT;
     /** Maximum time a sync adapter associated with a CP should keep the buckets elevated. */
-    long mSyncAdapterTimeoutMillis = ConstantsObserver.DEFAULT_SYNC_ADAPTER_TIMEOUT;
+    long mSyncAdapterTimeoutMillis = ConstantsObserver.DEFAULT_LEGACY_SYNC_ADAPTER_TIMEOUT;
+    /** The bucket that an app will be promoted on a sync adapter associated with a CP */
+    int mSyncAdapaterPromotedBucket = STANDBY_BUCKET_ACTIVE;
     /**
      * Maximum time an exempted sync should keep the buckets elevated, when sync is scheduled in
      * non-doze
@@ -751,7 +753,7 @@
                         userId);
                 synchronized (mAppIdleLock) {
                     reportNoninteractiveUsageCrossUserLocked(packageName, userId,
-                            STANDBY_BUCKET_ACTIVE, REASON_SUB_USAGE_SYNC_ADAPTER,
+                            mSyncAdapaterPromotedBucket, REASON_SUB_USAGE_SYNC_ADAPTER,
                             elapsedRealtime, mSyncAdapterTimeoutMillis, linkedProfiles);
                 }
             }
@@ -2446,6 +2448,8 @@
         pw.println("Flags: ");
         pw.println("    " + Flags.FLAG_AVOID_IDLE_CHECK
                 + ": " + Flags.avoidIdleCheck());
+        pw.println("    " + Flags.FLAG_ADJUST_DEFAULT_BUCKET_ELEVATION_PARAMS
+                + ": " + Flags.adjustDefaultBucketElevationParams());
         pw.println();
 
         synchronized (mCarrierPrivilegedLock) {
@@ -2481,6 +2485,9 @@
         pw.print("  mSyncAdapterTimeoutMillis=");
         TimeUtils.formatDuration(mSyncAdapterTimeoutMillis, pw);
         pw.println();
+        pw.print("  mSyncAdapaterPromotedBucket=");
+        pw.print(standbyBucketToString(mSyncAdapaterPromotedBucket));
+        pw.println();
         pw.print("  mSystemInteractionTimeoutMillis=");
         TimeUtils.formatDuration(mSystemInteractionTimeoutMillis, pw);
         pw.println();
@@ -3059,12 +3066,18 @@
 
         public static final long DEFAULT_CHECK_IDLE_INTERVAL_MS =
                 COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR;
-        public static final long DEFAULT_STRONG_USAGE_TIMEOUT =
+        public static final long DEFAULT_LEGACY_STRONG_USAGE_TIMEOUT =
                 COMPRESS_TIME ? ONE_MINUTE : 1 * ONE_HOUR;
+
+        public static final long DEFAULT_CURRENT_STRONG_USAGE_TIMEOUT =
+                COMPRESS_TIME ? ONE_MINUTE : 5 * ONE_MINUTE;
         public static final long DEFAULT_NOTIFICATION_TIMEOUT =
                 COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR;
-        public static final long DEFAULT_SLICE_PINNED_TIMEOUT =
+        public static final long DEFAULT_LEGACY_SLICE_PINNED_TIMEOUT =
                 COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR;
+
+        public static final long DEFAULT_CURRENT_SLICE_PINNED_TIMEOUT =
+                COMPRESS_TIME ? 12 * ONE_MINUTE : 2 * ONE_HOUR;
         public static final int DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET =
                 STANDBY_BUCKET_WORKING_SET;
         public static final boolean DEFAULT_RETAIN_NOTIFICATION_SEEN_IMPACT_FOR_PRE_T_APPS = false;
@@ -3073,8 +3086,11 @@
                 COMPRESS_TIME ? 2 * ONE_MINUTE : 2 * ONE_HOUR;
         public static final long DEFAULT_SYSTEM_INTERACTION_TIMEOUT =
                 COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE;
-        public static final long DEFAULT_SYNC_ADAPTER_TIMEOUT =
+        public static final long DEFAULT_LEGACY_SYNC_ADAPTER_TIMEOUT =
                 COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE;
+
+        public static final long DEFAULT_CURRENT_SYNC_ADAPTER_TIMEOUT =
+                COMPRESS_TIME ? ONE_MINUTE : 2 * ONE_HOUR;
         public static final long DEFAULT_EXEMPTED_SYNC_SCHEDULED_NON_DOZE_TIMEOUT =
                 COMPRESS_TIME ? (ONE_MINUTE / 2) : 10 * ONE_MINUTE;
         public static final long DEFAULT_EXEMPTED_SYNC_SCHEDULED_DOZE_TIMEOUT =
@@ -3117,6 +3133,9 @@
             cr.registerContentObserver(Global.getUriFor(Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED),
                     false, this);
             mInjector.registerDeviceConfigPropertiesChangedListener(this);
+
+            processDefaultConstants();
+
             // Load all the constants.
             // postOneTimeCheckIdleStates() doesn't need to be called on boot.
             processProperties(mInjector.getDeviceConfigProperties());
@@ -3135,6 +3154,17 @@
             postOneTimeCheckIdleStates();
         }
 
+        private void processDefaultConstants() {
+            if (!Flags.adjustDefaultBucketElevationParams()) {
+                return;
+            }
+
+            mSlicePinnedTimeoutMillis = DEFAULT_CURRENT_SLICE_PINNED_TIMEOUT;
+            mSyncAdapterTimeoutMillis = DEFAULT_CURRENT_SYNC_ADAPTER_TIMEOUT;
+            mSyncAdapaterPromotedBucket = STANDBY_BUCKET_WORKING_SET;
+            mStrongUsageTimeoutMillis = DEFAULT_CURRENT_STRONG_USAGE_TIMEOUT;
+        }
+
         private void processProperties(DeviceConfig.Properties properties) {
             boolean timeThresholdsUpdated = false;
             synchronized (mAppIdleLock) {
@@ -3182,11 +3212,16 @@
                         case KEY_SLICE_PINNED_HOLD_DURATION:
                             mSlicePinnedTimeoutMillis = properties.getLong(
                                     KEY_SLICE_PINNED_HOLD_DURATION,
-                                    DEFAULT_SLICE_PINNED_TIMEOUT);
+                                    Flags.adjustDefaultBucketElevationParams()
+                                            ? DEFAULT_CURRENT_SLICE_PINNED_TIMEOUT
+                                            : DEFAULT_LEGACY_SLICE_PINNED_TIMEOUT);
                             break;
                         case KEY_STRONG_USAGE_HOLD_DURATION:
                             mStrongUsageTimeoutMillis = properties.getLong(
-                                    KEY_STRONG_USAGE_HOLD_DURATION, DEFAULT_STRONG_USAGE_TIMEOUT);
+                                    KEY_STRONG_USAGE_HOLD_DURATION,
+                                    Flags.adjustDefaultBucketElevationParams()
+                                            ? DEFAULT_CURRENT_STRONG_USAGE_TIMEOUT
+                                            : DEFAULT_LEGACY_STRONG_USAGE_TIMEOUT);
                             break;
                         case KEY_PREDICTION_TIMEOUT:
                             mPredictionTimeoutMillis = properties.getLong(
@@ -3203,7 +3238,10 @@
                             break;
                         case KEY_SYNC_ADAPTER_HOLD_DURATION:
                             mSyncAdapterTimeoutMillis = properties.getLong(
-                                    KEY_SYNC_ADAPTER_HOLD_DURATION, DEFAULT_SYNC_ADAPTER_TIMEOUT);
+                                    KEY_SYNC_ADAPTER_HOLD_DURATION,
+                                    Flags.adjustDefaultBucketElevationParams()
+                                            ? DEFAULT_CURRENT_SYNC_ADAPTER_TIMEOUT
+                                            : DEFAULT_LEGACY_SYNC_ADAPTER_TIMEOUT);
                             break;
                         case KEY_EXEMPTED_SYNC_SCHEDULED_DOZE_HOLD_DURATION:
                             mExemptedSyncScheduledDozeTimeoutMillis = properties.getLong(
diff --git a/boot/boot-image-profile-extra.txt b/boot/boot-image-profile-extra.txt
index e31eb3a..fd51f9c 100644
--- a/boot/boot-image-profile-extra.txt
+++ b/boot/boot-image-profile-extra.txt
@@ -23,4 +23,25 @@
 # For now, compile all methods in MessageQueue to avoid performance cliffs for
 # flagged/evolving hot code paths. See: b/338098106
 HSPLandroid/os/MessageQueue;->*
-HSPLandroid/os/MessageQueue$*;->*
+HSPLandroid/os/MessageQueue$FileDescriptorRecord;->*
+HSPLandroid/os/MessageQueue$IdleHandler;->*
+HSPLandroid/os/MessageQueue$MessageCompare;->*
+HSPLandroid/os/MessageQueue$MatchAllFutureMessages;->*
+HSPLandroid/os/MessageQueue$MatchAllMessages;->*
+HSPLandroid/os/MessageQueue$MatchBarrierToken;->*
+HSPLandroid/os/MessageQueue$MatchDeliverableMessages;->*
+HSPLandroid/os/MessageQueue$MatchHandler;->*
+HSPLandroid/os/MessageQueue$MatchHandlerAndObject;->*
+HSPLandroid/os/MessageQueue$MatchHandlerAndObjectEquals;->*
+HSPLandroid/os/MessageQueue$MatchHandlerRunnableAndObject;->*
+HSPLandroid/os/MessageQueue$MatchHandlerRunnableAndObjectEquals;->*
+HSPLandroid/os/MessageQueue$MatchHandlerWhatAndObject;->*
+HSPLandroid/os/MessageQueue$MatchHandlerWhatAndObjectEquals;->*
+HSPLandroid/os/MessageQueue$MessageCounts;->*
+HSPLandroid/os/MessageQueue$StackNode;->*
+HSPLandroid/os/MessageQueue$MessageNode;->*
+HSPLandroid/os/MessageQueue$OnFileDescriptorEventListener$Events;->*
+HSPLandroid/os/MessageQueue$OnFileDescriptorEventListener;->*
+HSPLandroid/os/MessageQueue$StackNodeType;->*
+HSPLandroid/os/MessageQueue$StateNode;->*
+HSPLandroid/os/MessageQueue$TimedParkStateNode;->*
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 58763a7..d9ff190 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -165,6 +165,7 @@
     ],
     host_supported: true,
     test_suites: ["general-tests"],
+    require_root: true,
     srcs: [
         "tests/BinaryStreamVisitorTests.cpp",
         "tests/CommandLineOptionsTests.cpp",
diff --git a/cmds/idmap2/idmap2/CreateMultiple.cpp b/cmds/idmap2/idmap2/CreateMultiple.cpp
index 68800cd..2608c69 100644
--- a/cmds/idmap2/idmap2/CreateMultiple.cpp
+++ b/cmds/idmap2/idmap2/CreateMultiple.cpp
@@ -99,7 +99,7 @@
 
   std::vector<std::string> idmap_paths;
   for (const std::string& overlay_apk_path : overlay_apk_paths) {
-    const std::string idmap_path = Idmap::CanonicalIdmapPathFor(idmap_dir, overlay_apk_path);
+    std::string idmap_path = Idmap::CanonicalIdmapPathFor(idmap_dir, overlay_apk_path);
     const uid_t uid = getuid();
     if (!UidHasWriteAccessToPath(uid, idmap_path)) {
       LOG(WARNING) << "uid " << uid << "does not have write access to " << idmap_path.c_str();
@@ -111,7 +111,7 @@
                 !ignore_overlayable)) {
       const auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
       if (!overlay) {
-        LOG(WARNING) << "failed to load apk " << overlay_apk_path.c_str();
+        LOG(WARNING) << "failed to load apk " << overlay_apk_path;
         continue;
       }
 
@@ -138,7 +138,7 @@
       }
     }
 
-    idmap_paths.emplace_back(idmap_path);
+    idmap_paths.emplace_back(std::move(idmap_path));
   }
 
   for (const std::string& idmap_path : idmap_paths) {
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
index 57ae354..f22a481 100644
--- a/cmds/idmap2/libidmap2/ResourceContainer.cpp
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -22,6 +22,7 @@
 #include <utility>
 #include <vector>
 
+#include "android-base/scopeguard.h"
 #include "androidfw/ApkAssets.h"
 #include "androidfw/AssetManager.h"
 #include "androidfw/Util.h"
@@ -269,27 +270,40 @@
   std::unique_ptr<AssetManager2> am;
   ZipAssetsProvider* zip_assets;
 
-  static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip,
+  static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider>&& zip,
                                      package_property_t flags) {
     ResState state;
     state.zip_assets = zip.get();
     if ((state.apk_assets = ApkAssets::Load(std::move(zip), flags)) == nullptr) {
-      return Error("failed to load apk asset");
+      return Error("failed to load apk asset for '%s'",
+                   state.zip_assets->GetDebugName().c_str());
     }
 
+    // Make sure we put ZipAssetsProvider where we took it if initialization fails, so the
+    // original object stays valid for any next call it may get.
+    auto scoped_restore_zip_assets = android::base::ScopeGuard([&zip, &state]() {
+      zip = std::unique_ptr<ZipAssetsProvider>(
+          static_cast<ZipAssetsProvider*>(
+              std::move(const_cast<ApkAssets&>(*state.apk_assets)).TakeAssetsProvider().release()));
+    });
+
     if ((state.arsc = state.apk_assets->GetLoadedArsc()) == nullptr) {
-      return Error("failed to retrieve loaded arsc");
+      return Error("failed to retrieve loaded arsc for '%s'",
+                   state.zip_assets->GetDebugName().c_str());
     }
 
     if ((state.package = GetPackageAtIndex0(state.arsc)) == nullptr) {
-      return Error("failed to retrieve loaded package at index 0");
+      return Error("failed to retrieve loaded package at index 0 for '%s'",
+                   state.zip_assets->GetDebugName().c_str());
     }
 
     state.am = std::make_unique<AssetManager2>();
     if (!state.am->SetApkAssets({state.apk_assets}, false)) {
-      return Error("failed to create asset manager");
+      return Error("failed to create asset manager for '%s'",
+                   state.zip_assets->GetDebugName().c_str());
     }
 
+    scoped_restore_zip_assets.Disable();
     return state;
   }
 };
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 1b656e8..7093614 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -214,6 +214,20 @@
   ASSERT_EQ(idmap->GetHeader()->GetOverlayName(), TestConstants::OVERLAY_NAME_ALL_POLICIES);
 }
 
+TEST(IdmapTests, TargetContainerWorksAfterError) {
+  auto target = TargetResourceContainer::FromPath(GetTestDataPath() + "/target/target-bad.apk");
+  ASSERT_TRUE(target);
+
+  auto crc = target->get()->GetCrc();
+  ASSERT_TRUE(crc);
+
+  // This call tries to construct the full ApkAssets state, and fails.
+  ASSERT_FALSE(target->get()->DefinesOverlayable());
+  auto crc2 = target->get()->GetCrc();
+  ASSERT_TRUE(crc2);
+  EXPECT_EQ(*crc, *crc2);
+}
+
 TEST(IdmapTests, CreateIdmapDataFromApkAssets) {
   std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
   std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
diff --git a/cmds/idmap2/tests/data/target/target-bad.apk b/cmds/idmap2/tests/data/target/target-bad.apk
new file mode 100644
index 0000000..fd86782
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/target-bad.apk
Binary files differ
diff --git a/core/api/current.txt b/core/api/current.txt
index a42a8de..f3d19ca 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -247,6 +247,7 @@
     field public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE";
     field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
     field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
+    field @FlaggedApi("android.media.tv.flags.media_quality_fw") public static final String READ_COLOR_ZONES = "android.permission.READ_COLOR_ZONES";
     field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
     field @FlaggedApi("com.android.server.feature.flags.enable_read_dropbox_permission") public static final String READ_DROPBOX_DATA = "android.permission.READ_DROPBOX_DATA";
     field public static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE";
@@ -1203,6 +1204,7 @@
     field public static final int minResizeHeight = 16843670; // 0x1010396
     field public static final int minResizeWidth = 16843669; // 0x1010395
     field public static final int minSdkVersion = 16843276; // 0x101020c
+    field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int minSdkVersionFull;
     field public static final int minWidth = 16843071; // 0x101013f
     field public static final int minimumHorizontalAngle = 16843901; // 0x101047d
     field public static final int minimumVerticalAngle = 16843902; // 0x101047e
@@ -1502,6 +1504,7 @@
     field public static final int shadowRadius = 16843108; // 0x1010164
     field public static final int shape = 16843162; // 0x101019a
     field public static final int shareInterpolator = 16843195; // 0x10101bb
+    field @FlaggedApi("android.nfc.nfc_associated_role_services") public static final int shareRolePriority;
     field @Deprecated public static final int sharedUserId = 16842763; // 0x101000b
     field @Deprecated public static final int sharedUserLabel = 16843361; // 0x1010261
     field public static final int sharedUserMaxSdkVersion = 16844365; // 0x101064d
@@ -2185,6 +2188,18 @@
   public static final class R.dimen {
     ctor public R.dimen();
     field public static final int app_icon_size = 17104896; // 0x1050000
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveDefaultEffectDamping;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveDefaultSpatialDamping;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveFastEffectDamping;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveFastSpatialDamping;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveSlowEffectDamping;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveSlowSpatialDamping;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardDefaultEffectDamping;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardDefaultSpatialDamping;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastEffectDamping;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastSpatialDamping;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowEffectDamping;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowSpatialDamping;
     field public static final int dialog_min_width_major = 17104899; // 0x1050003
     field public static final int dialog_min_width_minor = 17104900; // 0x1050004
     field public static final int notification_large_icon_height = 17104902; // 0x1050006
@@ -2451,6 +2466,7 @@
     field public static final int primary = 16908300; // 0x102000c
     field public static final int progress = 16908301; // 0x102000d
     field public static final int redo = 16908339; // 0x1020033
+    field @FlaggedApi("android.appwidget.flags.engagement_metrics") public static final int remoteViewsMetricsId;
     field public static final int replaceText = 16908340; // 0x1020034
     field public static final int secondaryProgress = 16908303; // 0x102000f
     field public static final int selectAll = 16908319; // 0x102001f
@@ -2480,6 +2496,18 @@
     ctor public R.integer();
     field public static final int config_longAnimTime = 17694722; // 0x10e0002
     field public static final int config_mediumAnimTime = 17694721; // 0x10e0001
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveDefaultEffectStiffness;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveDefaultSpatialStiffness;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveFastEffectStiffness;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveFastSpatialStiffness;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveSlowEffectStiffness;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionExpressiveSlowSpatialStiffness;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardDefaultEffectStiffness;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardDefaultSpatialStiffness;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastEffectStiffness;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastSpatialStiffness;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowEffectStiffness;
+    field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowSpatialStiffness;
     field public static final int config_shortAnimTime = 17694720; // 0x10e0000
     field @Deprecated public static final int status_bar_notification_info_maxnum = 17694723; // 0x10e0003
   }
@@ -8914,6 +8942,7 @@
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.assist.AssistContent> CREATOR;
     field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXTRA_APP_FUNCTION_DATA = "android.app.assist.extra.APP_FUNCTION_DATA";
+    field @FlaggedApi("com.android.window.flags.enable_desktop_windowing_app_to_web_education") public static final String EXTRA_SESSION_TRANSFER_WEB_URI = "android.app.assist.extra.SESSION_TRANSFER_WEB_URI";
   }
 
   public class AssistStructure implements android.os.Parcelable {
@@ -9892,6 +9921,8 @@
     field public static final String ACTION_APPWIDGET_PICK = "android.appwidget.action.APPWIDGET_PICK";
     field public static final String ACTION_APPWIDGET_RESTORED = "android.appwidget.action.APPWIDGET_RESTORED";
     field public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE";
+    field @FlaggedApi("android.appwidget.flags.engagement_metrics") public static final String EVENT_CATEGORY_APPWIDGET = "android.appwidget";
+    field @FlaggedApi("android.appwidget.flags.engagement_metrics") public static final String EVENT_TYPE_WIDGET_INTERACTION = "widget_interaction";
     field public static final String EXTRA_APPWIDGET_ID = "appWidgetId";
     field public static final String EXTRA_APPWIDGET_IDS = "appWidgetIds";
     field public static final String EXTRA_APPWIDGET_OLD_IDS = "appWidgetOldIds";
@@ -9901,6 +9932,10 @@
     field public static final String EXTRA_APPWIDGET_PROVIDER_PROFILE = "appWidgetProviderProfile";
     field public static final String EXTRA_CUSTOM_EXTRAS = "customExtras";
     field public static final String EXTRA_CUSTOM_INFO = "customInfo";
+    field @FlaggedApi("android.appwidget.flags.engagement_metrics") public static final String EXTRA_EVENT_CLICKED_VIEWS = "android.appwidget.extra.EVENT_CLICKED_VIEWS";
+    field @FlaggedApi("android.appwidget.flags.engagement_metrics") public static final String EXTRA_EVENT_DURATION_MS = "android.appwidget.extra.EVENT_DURATION_MS";
+    field @FlaggedApi("android.appwidget.flags.engagement_metrics") public static final String EXTRA_EVENT_POSITION_RECT = "android.appwidget.extra.EVENT_POSITION_RECT";
+    field @FlaggedApi("android.appwidget.flags.engagement_metrics") public static final String EXTRA_EVENT_SCROLLED_VIEWS = "android.appwidget.extra.EVENT_SCROLLED_VIEWS";
     field public static final String EXTRA_HOST_ID = "hostId";
     field public static final int INVALID_APPWIDGET_ID = 0; // 0x0
     field public static final String META_DATA_APPWIDGET_PROVIDER = "android.appwidget.provider";
@@ -9990,12 +10025,12 @@
     method public int describeContents();
     method @Nullable public android.companion.AssociatedDevice getAssociatedDevice();
     method @FlaggedApi("android.companion.association_device_icon") @Nullable public android.graphics.drawable.Icon getDeviceIcon();
+    method @FlaggedApi("android.companion.association_tag") @Nullable public android.companion.DeviceId getDeviceId();
     method @Nullable public android.net.MacAddress getDeviceMacAddress();
     method @Nullable public String getDeviceProfile();
     method @Nullable public CharSequence getDisplayName();
     method public int getId();
     method public int getSystemDataSyncFlags();
-    method @FlaggedApi("android.companion.association_tag") @Nullable public String getTag();
     method public boolean isSelfManaged();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR;
@@ -10068,7 +10103,6 @@
     method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void attachSystemDataTransport(int, @NonNull java.io.InputStream, @NonNull java.io.OutputStream) throws android.companion.DeviceNotAssociatedException;
     method @Nullable public android.content.IntentSender buildAssociationCancellationIntent();
     method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException;
-    method @FlaggedApi("android.companion.association_tag") public void clearAssociationTag(int);
     method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
     method public void disableSystemDataSyncForTypes(int, int);
     method @Deprecated public void disassociate(@NonNull String);
@@ -10080,11 +10114,11 @@
     method @FlaggedApi("android.companion.perm_sync_user_consent") public boolean isPermissionTransferUserConsented(int);
     method @FlaggedApi("android.companion.unpair_associated_device") @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeBond(int);
     method public void requestNotificationAccess(android.content.ComponentName);
-    method @FlaggedApi("android.companion.association_tag") public void setAssociationTag(int, @NonNull String);
-    method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
+    method @FlaggedApi("android.companion.association_tag") public void setDeviceId(int, @Nullable android.companion.DeviceId);
+    method @Deprecated @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
     method @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull android.companion.ObservingDevicePresenceRequest);
     method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
-    method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
+    method @Deprecated @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
     method @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull android.companion.ObservingDevicePresenceRequest);
     field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
     field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
@@ -10112,9 +10146,9 @@
     method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String);
-    method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
+    method @Deprecated @FlaggedApi("android.companion.device_presence") @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
     method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
-    method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
+    method @Deprecated @FlaggedApi("android.companion.device_presence") @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
     method @FlaggedApi("android.companion.device_presence") @MainThread public void onDevicePresenceEvent(@NonNull android.companion.DevicePresenceEvent);
     field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
   }
@@ -10125,6 +10159,21 @@
   public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable {
   }
 
+  @FlaggedApi("android.companion.association_tag") public final class DeviceId implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public String getCustomId();
+    method @Nullable public android.net.MacAddress getMacAddress();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.DeviceId> CREATOR;
+  }
+
+  public static final class DeviceId.Builder {
+    ctor public DeviceId.Builder();
+    method @NonNull public android.companion.DeviceId build();
+    method @NonNull public android.companion.DeviceId.Builder setCustomId(@Nullable String);
+    method @NonNull public android.companion.DeviceId.Builder setMacAddress(@Nullable android.net.MacAddress);
+  }
+
   public class DeviceNotAssociatedException extends java.lang.RuntimeException {
   }
 
@@ -13217,8 +13266,8 @@
   public abstract class PackageManager {
     ctor @Deprecated public PackageManager();
     method @Deprecated public abstract void addPackageToPreferred(@NonNull String);
-    method public abstract boolean addPermission(@NonNull android.content.pm.PermissionInfo);
-    method public abstract boolean addPermissionAsync(@NonNull android.content.pm.PermissionInfo);
+    method @Deprecated @FlaggedApi("android.permission.flags.permission_tree_apis_deprecated") public abstract boolean addPermission(@NonNull android.content.pm.PermissionInfo);
+    method @Deprecated @FlaggedApi("android.permission.flags.permission_tree_apis_deprecated") public abstract boolean addPermissionAsync(@NonNull android.content.pm.PermissionInfo);
     method @Deprecated public abstract void addPreferredActivity(@NonNull android.content.IntentFilter, int, @Nullable android.content.ComponentName[], @NonNull android.content.ComponentName);
     method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean addWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
     method public boolean canPackageQuery(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -13359,7 +13408,7 @@
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryServiceProperty(@NonNull String);
     method public void relinquishUpdateOwnership(@NonNull String);
     method @Deprecated public abstract void removePackageFromPreferred(@NonNull String);
-    method public abstract void removePermission(@NonNull String);
+    method @Deprecated @FlaggedApi("android.permission.flags.permission_tree_apis_deprecated") public abstract void removePermission(@NonNull String);
     method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean removeWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
     method public void requestChecksums(@NonNull String, boolean, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int);
@@ -17034,7 +17083,7 @@
     method public void arcTo(@NonNull android.graphics.RectF, float, float);
     method public void arcTo(float, float, float, float, float, float, boolean);
     method public void close();
-    method @Deprecated public void computeBounds(@NonNull android.graphics.RectF, boolean);
+    method public void computeBounds(@NonNull android.graphics.RectF, boolean);
     method @FlaggedApi("com.android.graphics.flags.exact_compute_bounds") public void computeBounds(@NonNull android.graphics.RectF);
     method public void conicTo(float, float, float, float, float);
     method public void cubicTo(float, float, float, float, float, float);
@@ -18954,6 +19003,7 @@
     method @FlaggedApi("android.hardware.flags.luts_api") @NonNull public int[] getSamplingKeys();
     method @FlaggedApi("android.hardware.flags.luts_api") public int getSize();
     field @FlaggedApi("android.hardware.flags.luts_api") public static final int ONE_DIMENSION = 1; // 0x1
+    field @FlaggedApi("android.hardware.flags.luts_api") public static final int SAMPLING_KEY_CIE_Y = 2; // 0x2
     field @FlaggedApi("android.hardware.flags.luts_api") public static final int SAMPLING_KEY_MAX_RGB = 1; // 0x1
     field @FlaggedApi("android.hardware.flags.luts_api") public static final int SAMPLING_KEY_RGB = 0; // 0x0
     field @FlaggedApi("android.hardware.flags.luts_api") public static final int THREE_DIMENSION = 3; // 0x3
@@ -20758,7 +20808,9 @@
 
   public final class VirtualDisplayConfig implements android.os.Parcelable {
     method public int describeContents();
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @FloatRange(from=0.0f, to=1.0f) public float getDefaultBrightness();
     method public int getDensityDpi();
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @FloatRange(from=0.0f, to=1.0f) public float getDimBrightness();
     method @NonNull public java.util.Set<java.lang.String> getDisplayCategories();
     method public int getFlags();
     method public int getHeight();
@@ -20770,10 +20822,17 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.display.VirtualDisplayConfig> CREATOR;
   }
 
+  @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public static interface VirtualDisplayConfig.BrightnessListener {
+    method public void onBrightnessChanged(@FloatRange(from=0.0f, to=1.0f) float);
+  }
+
   public static final class VirtualDisplayConfig.Builder {
     ctor public VirtualDisplayConfig.Builder(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int);
     method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder addDisplayCategory(@NonNull String);
     method @NonNull public android.hardware.display.VirtualDisplayConfig build();
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setBrightnessListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.display.VirtualDisplayConfig.BrightnessListener);
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDefaultBrightness(@FloatRange(from=0.0f, to=1.0f) float);
+    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDimBrightness(@FloatRange(from=0.0f, to=1.0f) float);
     method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCategories(@NonNull java.util.Set<java.lang.String>);
     method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setFlags(int);
     method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setRequestedRefreshRate(@FloatRange(from=0.0f) float);
@@ -24843,7 +24902,9 @@
     method @Nullable public android.net.Uri getIconUri();
     method @NonNull public String getId();
     method @NonNull public CharSequence getName();
+    method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public java.util.Set<java.lang.String> getRequiredPermissions();
     method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getSuitabilityStatus();
+    method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public int getSupportedRoutingTypes();
     method public int getType();
     method public int getVolume();
     method public int getVolumeHandling();
@@ -24859,6 +24920,8 @@
     field public static final String FEATURE_REMOTE_AUDIO_PLAYBACK = "android.media.route.feature.REMOTE_AUDIO_PLAYBACK";
     field public static final String FEATURE_REMOTE_PLAYBACK = "android.media.route.feature.REMOTE_PLAYBACK";
     field public static final String FEATURE_REMOTE_VIDEO_PLAYBACK = "android.media.route.feature.REMOTE_VIDEO_PLAYBACK";
+    field @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final int FLAG_ROUTING_TYPE_REMOTE = 4; // 0x4
+    field @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public static final int FLAG_ROUTING_TYPE_SYSTEM_AUDIO = 1; // 0x1
     field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
     field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
     field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2; // 0x2
@@ -24908,7 +24971,9 @@
     method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
     method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
     method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
+    method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public android.media.MediaRoute2Info.Builder setRequiredPermissions(@NonNull java.util.Set<java.lang.String>);
     method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.MediaRoute2Info.Builder setSuitabilityStatus(int);
+    method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") @NonNull public android.media.MediaRoute2Info.Builder setSupportedRoutingTypes(int);
     method @NonNull public android.media.MediaRoute2Info.Builder setType(int);
     method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityPublic();
     method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityRestricted(@NonNull java.util.Set<java.lang.String>);
@@ -27096,10 +27161,10 @@
     method public int describeContents();
     method public int getColorFormat();
     method public int getCompressAlgorithm();
-    method @IntRange(from=0) public int getHorizontalZonesNumber();
+    method @IntRange(from=0, to=128) public int getHorizontalZonesNumber();
     method @NonNull public String getPackageName();
     method public int getSource();
-    method @IntRange(from=0) public int getVerticalZonesNumber();
+    method @IntRange(from=0, to=80) public int getVerticalZonesNumber();
     method @NonNull public int[] getZonesColors();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.quality.AmbientBacklightMetadata> CREATOR;
@@ -29827,6 +29892,7 @@
   public class X509TrustManagerExtensions {
     ctor public X509TrustManagerExtensions(javax.net.ssl.X509TrustManager) throws java.lang.IllegalArgumentException;
     method public java.util.List<java.security.cert.X509Certificate> checkServerTrusted(java.security.cert.X509Certificate[], String, String) throws java.security.cert.CertificateException;
+    method @FlaggedApi("android.net.platform.flags.x509_extensions_certificate_transparency") @NonNull public java.util.List<java.security.cert.X509Certificate> checkServerTrusted(@NonNull java.security.cert.X509Certificate[], @Nullable byte[], @Nullable byte[], @NonNull String, @NonNull String) throws java.security.cert.CertificateException;
     method public boolean isSameTrustConfiguration(String, String);
     method public boolean isUserAddedCertificate(java.security.cert.X509Certificate);
   }
@@ -30069,128 +30135,6 @@
 
 }
 
-package android.net.vcn {
-
-  public final class VcnCellUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
-    method public int getCbs();
-    method public int getDun();
-    method public int getIms();
-    method public int getInternet();
-    method public int getMms();
-    method @NonNull public java.util.Set<java.lang.String> getOperatorPlmnIds();
-    method public int getOpportunistic();
-    method public int getRcs();
-    method public int getRoaming();
-    method @NonNull public java.util.Set<java.lang.Integer> getSimSpecificCarrierIds();
-  }
-
-  public static final class VcnCellUnderlyingNetworkTemplate.Builder {
-    ctor public VcnCellUnderlyingNetworkTemplate.Builder();
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate build();
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setCbs(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setDun(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setIms(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setInternet(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMetered(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMms(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOperatorPlmnIds(@NonNull java.util.Set<java.lang.String>);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOpportunistic(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRcs(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRoaming(int);
-    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setSimSpecificCarrierIds(@NonNull java.util.Set<java.lang.Integer>);
-  }
-
-  public final class VcnConfig implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
-    method @NonNull public java.util.Set<java.lang.Integer> getRestrictedUnderlyingNetworkTransports();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR;
-  }
-
-  public static final class VcnConfig.Builder {
-    ctor public VcnConfig.Builder(@NonNull android.content.Context);
-    method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig);
-    method @NonNull public android.net.vcn.VcnConfig build();
-    method @NonNull public android.net.vcn.VcnConfig.Builder setRestrictedUnderlyingNetworkTransports(@NonNull java.util.Set<java.lang.Integer>);
-  }
-
-  public final class VcnGatewayConnectionConfig {
-    method @NonNull public int[] getExposedCapabilities();
-    method @NonNull public String getGatewayConnectionName();
-    method @IntRange(from=0x500) public int getMaxMtu();
-    method public int getMinUdpPort4500NatTimeoutSeconds();
-    method @NonNull public long[] getRetryIntervalsMillis();
-    method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
-    method public boolean hasGatewayOption(int);
-    method @FlaggedApi("android.net.vcn.safe_mode_config") public boolean isSafeModeEnabled();
-    field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET = -1; // 0xffffffff
-    field public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; // 0x0
-  }
-
-  public static final class VcnGatewayConnectionConfig.Builder {
-    ctor public VcnGatewayConnectionConfig.Builder(@NonNull String, @NonNull android.net.ipsec.ike.IkeTunnelConnectionParams);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addGatewayOption(int);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeGatewayOption(int);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]);
-    method @FlaggedApi("android.net.vcn.safe_mode_config") @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setSafeModeEnabled(boolean);
-    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>);
-  }
-
-  public class VcnManager {
-    method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException;
-    method @NonNull public java.util.List<android.os.ParcelUuid> getConfiguredSubscriptionGroups();
-    method public void registerVcnStatusCallback(@NonNull android.os.ParcelUuid, @NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnStatusCallback);
-    method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException;
-    method public void unregisterVcnStatusCallback(@NonNull android.net.vcn.VcnManager.VcnStatusCallback);
-    field public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; // 0x1
-    field public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; // 0x0
-    field public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; // 0x2
-    field public static final int VCN_STATUS_CODE_ACTIVE = 2; // 0x2
-    field public static final int VCN_STATUS_CODE_INACTIVE = 1; // 0x1
-    field public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; // 0x0
-    field public static final int VCN_STATUS_CODE_SAFE_MODE = 3; // 0x3
-  }
-
-  public abstract static class VcnManager.VcnStatusCallback {
-    ctor public VcnManager.VcnStatusCallback();
-    method public abstract void onGatewayConnectionError(@NonNull String, int, @Nullable Throwable);
-    method public abstract void onStatusChanged(int);
-  }
-
-  public abstract class VcnUnderlyingNetworkTemplate {
-    method public int getMetered();
-    method public int getMinEntryDownstreamBandwidthKbps();
-    method public int getMinEntryUpstreamBandwidthKbps();
-    method public int getMinExitDownstreamBandwidthKbps();
-    method public int getMinExitUpstreamBandwidthKbps();
-    field public static final int MATCH_ANY = 0; // 0x0
-    field public static final int MATCH_FORBIDDEN = 2; // 0x2
-    field public static final int MATCH_REQUIRED = 1; // 0x1
-  }
-
-  public final class VcnWifiUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
-    method @NonNull public java.util.Set<java.lang.String> getSsids();
-  }
-
-  public static final class VcnWifiUnderlyingNetworkTemplate.Builder {
-    ctor public VcnWifiUnderlyingNetworkTemplate.Builder();
-    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate build();
-    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMetered(int);
-    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
-    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
-    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setSsids(@NonNull java.util.Set<java.lang.String>);
-  }
-
-}
-
 package android.opengl {
 
   public class EGL14 {
@@ -33603,7 +33547,10 @@
   @FlaggedApi("android.os.cpu_gpu_headrooms") public final class CpuHeadroomParams {
     ctor public CpuHeadroomParams();
     method public int getCalculationType();
+    method @IntRange(from=0x32, to=0x2710) public long getCalculationWindowMillis();
     method public void setCalculationType(int);
+    method public void setCalculationWindowMillis(@IntRange(from=0x32, to=0x2710) int);
+    method public void setTids(@NonNull int...);
     field public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1
     field public static final int CPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0
   }
@@ -33858,7 +33805,9 @@
   @FlaggedApi("android.os.cpu_gpu_headrooms") public final class GpuHeadroomParams {
     ctor public GpuHeadroomParams();
     method public int getCalculationType();
+    method @IntRange(from=0x32, to=0x2710) public int getCalculationWindowMillis();
     method public void setCalculationType(int);
+    method public void setCalculationWindowMillis(@IntRange(from=0x32, to=0x2710) int);
     field public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1
     field public static final int GPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0
   }
@@ -40862,13 +40811,14 @@
 
   public abstract class AutofillService extends android.app.Service {
     ctor public AutofillService();
-    method @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory();
+    method @Deprecated @FlaggedApi("android.service.autofill.autofill_session_destroyed") @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory();
     method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConnected();
     method public void onDisconnected();
     method public abstract void onFillRequest(@NonNull android.service.autofill.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
     method public abstract void onSaveRequest(@NonNull android.service.autofill.SaveRequest, @NonNull android.service.autofill.SaveCallback);
     method public void onSavedDatasetsInfoRequest(@NonNull android.service.autofill.SavedDatasetsInfoCallback);
+    method @FlaggedApi("android.service.autofill.autofill_session_destroyed") public void onSessionDestroyed(@Nullable android.service.autofill.FillEventHistory);
     field public static final String EXTRA_FILL_RESPONSE = "android.service.autofill.extra.FILL_RESPONSE";
     field public static final String SERVICE_INTERFACE = "android.service.autofill.AutofillService";
     field public static final String SERVICE_META_DATA = "android.autofill";
@@ -51928,7 +51878,7 @@
     method public int getState();
     method @FlaggedApi("com.android.server.display.feature.flags.enable_get_suggested_frame_rate") public float getSuggestedFrameRate(int);
     method public android.view.Display.Mode[] getSupportedModes();
-    method @Deprecated public float[] getSupportedRefreshRates();
+    method @FlaggedApi("com.android.server.display.feature.flags.enable_get_supported_refresh_rates") @NonNull public float[] getSupportedRefreshRates();
     method @Deprecated public int getWidth();
     method @FlaggedApi("com.android.server.display.feature.flags.enable_has_arr_support") public boolean hasArrSupport();
     method public boolean isHdr();
@@ -53447,6 +53397,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.view.Surface> CREATOR;
     field public static final int FRAME_RATE_COMPATIBILITY_DEFAULT = 0; // 0x0
     field public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1; // 0x1
+    field @FlaggedApi("com.android.graphics.surfaceflinger.flags.arr_setframerate_gte_enum") public static final int FRAME_RATE_COMPATIBILITY_GTE = 2; // 0x2
     field public static final int ROTATION_0 = 0; // 0x0
     field public static final int ROTATION_180 = 2; // 0x2
     field public static final int ROTATION_270 = 3; // 0x3
@@ -56486,6 +56437,7 @@
     method public float getMin();
     method public int getType();
     method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.RangeInfo obtain(int, float, float, float);
+    field @FlaggedApi("android.view.accessibility.indeterminate_range_info") @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.RangeInfo INDETERMINATE;
     field public static final int RANGE_TYPE_FLOAT = 1; // 0x1
     field @FlaggedApi("android.view.accessibility.indeterminate_range_info") public static final int RANGE_TYPE_INDETERMINATE = 3; // 0x3
     field public static final int RANGE_TYPE_INT = 0; // 0x0
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 1a949d84..bca2ce2 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -100,6 +100,7 @@
     method @NonNull public android.content.Context createContextForSdkInSandbox(@NonNull android.content.pm.ApplicationInfo, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.os.IBinder getProcessToken();
     method @NonNull public android.os.UserHandle getUser();
+    field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence_module") public static final int BIND_FOREGROUND_SERVICE = 67108864; // 0x4000000
     field public static final String PAC_PROXY_SERVICE = "pac_proxy";
     field public static final String TEST_NETWORK_SERVICE = "test_network";
     field @FlaggedApi("android.os.mainline_vcn_platform_api") public static final String VCN_MANAGEMENT_SERVICE = "vcn_management";
@@ -129,6 +130,7 @@
 
   public abstract class PackageManager {
     method @NonNull public String getSdkSandboxPackageName();
+    method @FlaggedApi("android.content.pm.cloud_compilation_pm") @NonNull public static android.content.pm.SigningInfo getVerifiedSigningInfo(@NonNull String, int) throws android.content.pm.SigningInfoException;
     method @RequiresPermission(android.Manifest.permission.MAKE_UID_VISIBLE) public void makeUidVisible(int, int);
     field public static final String EXTRA_VERIFICATION_ROOT_HASH = "android.content.pm.extra.VERIFICATION_ROOT_HASH";
     field public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 67108864; // 0x4000000
@@ -139,6 +141,18 @@
     method @NonNull public String getPackageName();
   }
 
+  public final class SigningInfo implements android.os.Parcelable {
+    method @FlaggedApi("android.content.pm.cloud_compilation_pm") public boolean signersMatchExactly(@NonNull android.content.pm.SigningInfo);
+    field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_JAR = 1; // 0x1
+    field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_SIGNING_BLOCK_V2 = 2; // 0x2
+    field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_SIGNING_BLOCK_V3 = 3; // 0x3
+    field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_SIGNING_BLOCK_V4 = 4; // 0x4
+  }
+
+  @FlaggedApi("android.content.pm.cloud_compilation_pm") public class SigningInfoException extends java.lang.Exception {
+    method @FlaggedApi("android.content.pm.cloud_compilation_pm") public int getCode();
+  }
+
 }
 
 package android.hardware.usb {
@@ -251,10 +265,6 @@
 
 package android.net {
 
-  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class ConnectivityFrameworkInitializerBaklava {
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static void registerServiceWrappers();
-  }
-
   public class LocalSocket implements java.io.Closeable {
     ctor public LocalSocket(@NonNull java.io.FileDescriptor);
   }
@@ -314,25 +324,6 @@
 
 }
 
-package android.net.vcn {
-
-  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class VcnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int describeContents();
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public long getApplicableRedactions();
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int getMinUdpPort4500NatTimeoutSeconds();
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.TransportInfo makeCopy(long);
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnTransportInfo> CREATOR;
-  }
-
-  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final class VcnTransportInfo.Builder {
-    ctor @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public VcnTransportInfo.Builder();
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo build();
-    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
-  }
-
-}
-
 package android.net.wifi {
 
   public final class WifiMigration {
@@ -491,6 +482,10 @@
     field public static final long TRACE_TAG_NETWORK = 2097152L; // 0x200000L
   }
 
+  public class UpdateEngine {
+    method @FlaggedApi("android.os.update_engine_api") public void triggerPostinstall(@NonNull String);
+  }
+
 }
 
 package android.os.storage {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d971f8b..a593970 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -34,6 +34,7 @@
     field public static final String ACCESS_VIBRATOR_STATE = "android.permission.ACCESS_VIBRATOR_STATE";
     field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
     field public static final String ADD_ALWAYS_UNLOCKED_DISPLAY = "android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY";
+    field @FlaggedApi("android.companion.virtualdevice.flags.enable_limited_vdm_role") public static final String ADD_MIRROR_DISPLAY = "android.permission.ADD_MIRROR_DISPLAY";
     field public static final String ADD_TRUSTED_DISPLAY = "android.permission.ADD_TRUSTED_DISPLAY";
     field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY";
     field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
@@ -77,6 +78,7 @@
     field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_INTELLIGENCE_SERVICE = "android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE";
     field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE = "android.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE";
     field public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE";
+    field @FlaggedApi("android.location.flags.population_density_provider") public static final String BIND_POPULATION_DENSITY_PROVIDER_SERVICE = "android.permission.BIND_POPULATION_DENSITY_PROVIDER_SERVICE";
     field public static final String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE";
     field public static final String BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE = "android.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE";
     field public static final String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE";
@@ -515,6 +517,7 @@
     field public static final int config_defaultCallScreening = 17039398; // 0x1040026
     field public static final int config_defaultDialer = 17039395; // 0x1040023
     field public static final int config_defaultNotes = 17039429; // 0x1040045
+    field @FlaggedApi("android.permission.flags.cross_user_role_platform_api_enabled") public static final int config_defaultReservedForTestingProfileGroupExclusivity;
     field @FlaggedApi("android.permission.flags.retail_demo_role_enabled") public static final int config_defaultRetailDemo = 17039432; // 0x1040048
     field public static final int config_defaultSms = 17039396; // 0x1040024
     field @FlaggedApi("android.permission.flags.wallet_role_enabled") public static final int config_defaultWallet = 17039433; // 0x1040049
@@ -901,7 +904,7 @@
   public static final class AppOpsManager.OpEventProxyInfo implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public String getAttributionTag();
-    method @FlaggedApi("android.permission.flags.device_id_in_op_proxy_info_enabled") @Nullable public String getDeviceId();
+    method @FlaggedApi("android.permission.flags.device_id_in_op_proxy_info_enabled") @NonNull public String getDeviceId();
     method @Nullable public String getPackageName();
     method @IntRange(from=0) public int getUid();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -1288,6 +1291,7 @@
     method @FlaggedApi("android.app.live_wallpaper_content_handling") @Nullable @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.app.wallpaper.WallpaperInstance getWallpaperInstance(int);
     method public void setDisplayOffset(android.os.IBinder, int, int);
     method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setStreamWithCrops(@NonNull java.io.InputStream, @NonNull android.util.SparseArray<android.graphics.Rect>, boolean, int) throws java.io.IOException;
+    method @FlaggedApi("android.app.live_wallpaper_content_handling") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setStreamWithDescription(@NonNull java.io.InputStream, @NonNull android.app.wallpaper.WallpaperDescription, boolean, int) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName);
     method @FlaggedApi("android.app.live_wallpaper_content_handling") @RequiresPermission(allOf={android.Manifest.permission.SET_WALLPAPER_COMPONENT, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public boolean setWallpaperComponentWithDescription(@NonNull android.app.wallpaper.WallpaperDescription, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponentWithFlags(@NonNull android.content.ComponentName, int);
@@ -3299,6 +3303,14 @@
 
 }
 
+package android.app.wallpaper {
+
+  @FlaggedApi("android.app.live_wallpaper_content_handling") public final class WallpaperDescription implements android.os.Parcelable {
+    method @NonNull public android.util.SparseArray<android.graphics.Rect> getCropHints();
+  }
+
+}
+
 package android.app.wallpapereffectsgeneration {
 
   public final class CameraAttributes implements android.os.Parcelable {
@@ -3542,6 +3554,7 @@
   public static interface VirtualDeviceManager.ActivityListener {
     method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onActivityLaunchBlocked(int, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle, @Nullable android.content.IntentSender);
     method public void onDisplayEmpty(int);
+    method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onSecureWindowHidden(int);
     method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onSecureWindowShown(int, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
     method @Deprecated public void onTopActivityChanged(int, @NonNull android.content.ComponentName);
     method public default void onTopActivityChanged(int, @NonNull android.content.ComponentName, int);
@@ -3939,6 +3952,7 @@
     field public static final String WIFI_NL80211_SERVICE = "wifinl80211";
     field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
     field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
+    field @FlaggedApi("android.net.wifi.flags.usd") public static final String WIFI_USD_SERVICE = "wifi_usd";
   }
 
   public final class ContextParams {
@@ -4249,6 +4263,7 @@
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
     field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
     field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
+    field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final String ACTION_INSTALL_DEPENDENCY = "android.content.pm.action.INSTALL_DEPENDENCY";
     field public static final int DATA_LOADER_TYPE_INCREMENTAL = 2; // 0x2
     field public static final int DATA_LOADER_TYPE_NONE = 0; // 0x0
     field public static final int DATA_LOADER_TYPE_STREAMING = 1; // 0x1
@@ -5207,6 +5222,11 @@
     method @NonNull public java.util.Collection<android.hardware.contexthub.HubServiceInfo> getServiceInfoCollection();
     method @Nullable public String getTag();
     method public int getVersion();
+    field public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4; // 0x4
+    field public static final int REASON_ENDPOINT_INVALID = 5; // 0x5
+    field public static final int REASON_ENDPOINT_STOPPED = 6; // 0x6
+    field public static final int REASON_FAILURE = 0; // 0x0
+    field public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3; // 0x3
   }
 
   public static final class HubEndpoint.Builder {
@@ -5244,9 +5264,9 @@
   }
 
   @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable {
-    method public void close();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void close();
     method @Nullable public android.hardware.contexthub.HubServiceInfo getServiceInfo();
-    method @NonNull public android.hardware.location.ContextHubTransaction<java.lang.Void> sendMessage(@NonNull android.hardware.contexthub.HubMessage);
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> sendMessage(@NonNull android.hardware.contexthub.HubMessage);
   }
 
   @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSessionResult {
@@ -5273,9 +5293,8 @@
   }
 
   @FlaggedApi("android.chre.flags.offload_api") public final class HubServiceInfo implements android.os.Parcelable {
-    ctor public HubServiceInfo(@NonNull String, int, int, int, @NonNull android.os.ParcelableHolder);
+    ctor public HubServiceInfo(@NonNull String, int, int, int);
     method public int describeContents();
-    method @NonNull public android.os.ParcelableHolder getExtendedInfo();
     method public int getFormat();
     method public int getMajorVersion();
     method public int getMinorVersion();
@@ -5290,16 +5309,17 @@
   public static final class HubServiceInfo.Builder {
     ctor public HubServiceInfo.Builder(@NonNull String, int, int, int);
     method @NonNull public android.hardware.contexthub.HubServiceInfo build();
-    method @NonNull public android.hardware.contexthub.HubServiceInfo.Builder setExtendedInfo(@Nullable android.os.Parcelable);
+  }
+
+  @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointDiscoveryCallback {
+    method public void onEndpointsStarted(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>);
+    method public void onEndpointsStopped(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>, int);
   }
 
   @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
     method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
     method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable android.hardware.contexthub.HubServiceInfo);
     method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
-    field public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4; // 0x4
-    field public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3; // 0x3
-    field public static final int REASON_UNSPECIFIED = 0; // 0x0
   }
 
   @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointMessageCallback {
@@ -5447,19 +5467,13 @@
     field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400
   }
 
-  public abstract static class VirtualDisplay.Callback {
-    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public void onRequestedBrightnessChanged(@FloatRange(from=0.0f, to=1.0f) float);
-  }
-
   public final class VirtualDisplayConfig implements android.os.Parcelable {
-    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @FloatRange(from=0.0f, to=1.0f) public float getDefaultBrightness();
     method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @Nullable public android.view.DisplayCutout getDisplayCutout();
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") public boolean isHomeSupported();
     method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") public boolean isIgnoreActivitySizeRestrictions();
   }
 
   public static final class VirtualDisplayConfig.Builder {
-    method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDefaultBrightness(@FloatRange(from=0.0f, to=1.0f) float);
     method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setHomeSupported(boolean);
     method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setIgnoreActivitySizeRestrictions(boolean);
@@ -6315,11 +6329,16 @@
     method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
     method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
     method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
+    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(long, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback);
+    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(long, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback, @NonNull java.util.concurrent.Executor);
+    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull String, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback);
+    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull String, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback, @NonNull java.util.concurrent.Executor);
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessage(int, int, @NonNull android.hardware.location.ContextHubMessage);
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int unloadNanoApp(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
     method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
     method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
+    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback);
     field public static final int AUTHORIZATION_DENIED = 0; // 0x0
     field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1
     field public static final int AUTHORIZATION_GRANTED = 2; // 0x2
@@ -7183,8 +7202,8 @@
     method public int getAudioCapabilities();
     method @NonNull public byte[] getData();
     method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra> getKeyphrases();
-    method public boolean isAllowMultipleTriggers();
     method public boolean isCaptureRequested();
+    method public boolean isMultipleTriggersAllowed();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
   }
@@ -7192,11 +7211,11 @@
   public static final class SoundTrigger.RecognitionConfig.Builder {
     ctor public SoundTrigger.RecognitionConfig.Builder();
     method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig build();
-    method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAllowMultipleTriggers(boolean);
     method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAudioCapabilities(int);
     method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setCaptureRequested(boolean);
     method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@NonNull byte[]);
     method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setKeyphrases(@NonNull java.util.Collection<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+    method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setMultipleTriggersAllowed(boolean);
   }
 
   public static class SoundTrigger.RecognitionEvent {
@@ -7624,9 +7643,11 @@
     method public boolean isActive();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isMuted();
     method public boolean isSpatialized();
-    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8
+    field @Deprecated @FlaggedApi("android.media.audio.muted_by_port_volume_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_CLIENT_VOLUME = 16; // 0x10
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1
+    field @FlaggedApi("android.media.audio.muted_by_port_volume_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_OP_CONTROL_AUDIO = 128; // 0x80
+    field @FlaggedApi("android.media.audio.muted_by_port_volume_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_OP_PLAY_AUDIO = 8; // 0x8
     field @FlaggedApi("android.media.audio.muted_by_port_volume_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_PORT_VOLUME = 64; // 0x40
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_MUTED = 4; // 0x4
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_VOLUME = 2; // 0x2
@@ -7764,7 +7785,7 @@
   }
 
   public final class MediaCas implements java.lang.AutoCloseable {
-    method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceHolderRetain(boolean);
+    method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceOwnershipRetention(boolean);
     method @FlaggedApi("android.media.tv.flags.mediacas_update_client_profile_priority") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean updateResourcePriority(int, int);
   }
 
@@ -8115,6 +8136,8 @@
     method @NonNull public java.util.List<android.media.quality.SoundProfile> getSoundProfilesByPackage(@NonNull String);
     method public void setAutoPictureQualityEnabled(boolean);
     method public void setAutoSoundQualityEnabled(boolean);
+    method public boolean setDefaultPictureProfile(@Nullable String);
+    method public boolean setDefaultSoundProfile(@Nullable String);
     method public void setPictureProfileAllowList(@NonNull java.util.List<java.lang.String>);
     method public void setSoundProfileAllowList(@NonNull java.util.List<java.lang.String>);
     method public void setSuperResolutionEnabled(boolean);
@@ -8677,8 +8700,8 @@
     method public int setLnaEnabled(boolean);
     method public int setMaxNumberOfFrontends(int, @IntRange(from=0) int);
     method public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener);
-    method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceHolderRetain(boolean);
     method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener);
+    method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceOwnershipRetention(boolean);
     method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
     method public int transferOwner(@NonNull android.media.tv.tuner.Tuner);
     method public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
@@ -10603,28 +10626,6 @@
 
 }
 
-package android.net.vcn {
-
-  public class VcnManager {
-    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void addVcnNetworkPolicyChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener);
-    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.vcn.VcnNetworkPolicyResult applyVcnNetworkPolicy(@NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties);
-    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void removeVcnNetworkPolicyChangeListener(@NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener);
-  }
-
-  public static interface VcnManager.VcnNetworkPolicyChangeListener {
-    method public void onPolicyChanged();
-  }
-
-  public final class VcnNetworkPolicyResult implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
-    method public boolean isTeardownRequested();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnNetworkPolicyResult> CREATOR;
-  }
-
-}
-
 package android.net.wifi {
 
   public final class WifiKeystore {
@@ -11110,6 +11111,7 @@
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicAidGroup(@NonNull android.nfc.cardemulation.AidGroup);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setOffHostSecureElement(@NonNull String);
     method @FlaggedApi("android.nfc.nfc_observe_mode") public void setShouldDefaultToObserveMode(boolean);
+    method @FlaggedApi("android.nfc.nfc_associated_role_services") public boolean shareRolePriority();
     method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean shouldDefaultToObserveMode();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int);
     field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.ApduServiceInfo> CREATOR;
@@ -12865,6 +12867,30 @@
 
 package android.security.intrusiondetection {
 
+  @FlaggedApi("android.security.afl_api") public final class IntrusionDetectionEvent implements android.os.Parcelable {
+    ctor public IntrusionDetectionEvent(@NonNull android.app.admin.SecurityLog.SecurityEvent);
+    ctor public IntrusionDetectionEvent(@NonNull android.app.admin.DnsEvent);
+    ctor public IntrusionDetectionEvent(@NonNull android.app.admin.ConnectEvent);
+    method @FlaggedApi("android.security.afl_api") public int describeContents();
+    method @NonNull public android.app.admin.ConnectEvent getConnectEvent();
+    method @NonNull public android.app.admin.DnsEvent getDnsEvent();
+    method @NonNull public android.app.admin.SecurityLog.SecurityEvent getSecurityEvent();
+    method @NonNull public int getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.security.intrusiondetection.IntrusionDetectionEvent> CREATOR;
+    field public static final int NETWORK_EVENT_CONNECT = 2; // 0x2
+    field public static final int NETWORK_EVENT_DNS = 1; // 0x1
+    field public static final int SECURITY_EVENT = 0; // 0x0
+  }
+
+  @FlaggedApi("android.security.afl_api") public class IntrusionDetectionEventTransport {
+    ctor public IntrusionDetectionEventTransport();
+    method public boolean addData(@NonNull java.util.List<android.security.intrusiondetection.IntrusionDetectionEvent>);
+    method @NonNull public android.os.IBinder getBinder();
+    method public boolean initialize();
+    method public boolean release();
+  }
+
   @FlaggedApi("android.security.afl_api") public class IntrusionDetectionManager {
     method @RequiresPermission(android.Manifest.permission.READ_INTRUSION_DETECTION_STATE) public void addStateCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_INTRUSION_DETECTION_STATE) public void disable(@NonNull java.util.concurrent.Executor, @NonNull android.security.intrusiondetection.IntrusionDetectionManager.CommandCallback);
@@ -16055,6 +16081,10 @@
     field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CALL_FORWARDING_INDICATOR_CHANGED = 4; // 0x4
     field public static final int EVENT_CALL_STATE_CHANGED = 6; // 0x6
     field public static final int EVENT_CARRIER_NETWORK_CHANGED = 17; // 0x11
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED = 44; // 0x2c
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED = 43; // 0x2b
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED = 42; // 0x2a
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED = 45; // 0x2d
     field @FlaggedApi("com.android.internal.telephony.flags.cellular_identifier_disclosure_indications") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED = 47; // 0x2f
     field @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public static final int EVENT_CELL_INFO_CHANGED = 11; // 0xb
     field @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public static final int EVENT_CELL_LOCATION_CHANGED = 5; // 0x5
@@ -16099,6 +16129,13 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallStatesChanged(@NonNull java.util.List<android.telephony.CallState>);
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static interface TelephonyCallback.CarrierRoamingNtnModeListener {
+    method public default void onCarrierRoamingNtnAvailableServicesChanged(@NonNull int[]);
+    method public default void onCarrierRoamingNtnEligibleStateChanged(boolean);
+    method public void onCarrierRoamingNtnModeChanged(boolean);
+    method public default void onCarrierRoamingNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrength);
+  }
+
   @FlaggedApi("com.android.internal.telephony.flags.cellular_identifier_disclosure_indications") public static interface TelephonyCallback.CellularIdentifierDisclosedListener {
     method public void onCellularIdentifierDisclosedChanged(@NonNull android.telephony.CellularIdentifierDisclosure);
   }
@@ -16224,6 +16261,7 @@
     method @FlaggedApi("android.permission.flags.get_emergency_role_holder_api_enabled") @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getEmergencyAssistancePackageName();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
+    method @FlaggedApi("com.android.internal.telephony.flags.get_group_id_level2") @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getGroupIdLevel2();
     method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @NonNull @RequiresPermission(value=android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, conditional=true) public java.util.List<java.lang.String> getImsPcscfAddresses();
     method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @Nullable @RequiresPermission(android.Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER) public String getImsPrivateUserIdentity();
     method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @NonNull @RequiresPermission(value=android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, conditional=true) public java.util.List<android.net.Uri> getImsPublicUserIdentities();
@@ -18701,6 +18739,14 @@
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR;
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class EarfcnRange implements android.os.Parcelable {
+    method public int describeContents();
+    method @IntRange(from=0, to=65535) public int getEndEarfcn();
+    method @IntRange(from=0, to=65535) public int getStartEarfcn();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.EarfcnRange> CREATOR;
+  }
+
   @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public class EnableRequestAttributes {
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isDemoMode();
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isEmergencyMode();
@@ -18739,6 +18785,14 @@
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.PointingInfo> CREATOR;
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatelliteAccessConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.telephony.satellite.SatelliteInfo> getSatelliteInfos();
+    method @NonNull public java.util.List<java.lang.Integer> getTagIds();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteAccessConfiguration> CREATOR;
+  }
+
   @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteCapabilities implements android.os.Parcelable {
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public java.util.Map<java.lang.Integer,android.telephony.satellite.AntennaPosition> getAntennaPositionMap();
@@ -18753,6 +18807,11 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteCapabilitiesChanged(@NonNull android.telephony.satellite.SatelliteCapabilities);
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public interface SatelliteCommunicationAllowedStateCallback {
+    method public default void onSatelliteAccessConfigurationChanged(@Nullable android.telephony.satellite.SatelliteAccessConfiguration);
+    method public void onSatelliteCommunicationAllowedStateChanged(boolean);
+  }
+
   @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteDatagram implements android.os.Parcelable {
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public byte[] getSatelliteDatagram();
@@ -18764,18 +18823,32 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>);
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatelliteInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.lang.Integer> getBands();
+    method @NonNull public java.util.List<android.telephony.satellite.EarfcnRange> getEarfcnRanges();
+    method @NonNull public java.util.UUID getSatelliteId();
+    method @NonNull public android.telephony.satellite.SatellitePosition getSatellitePosition();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteInfo> CREATOR;
+  }
+
   @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public final class SatelliteManager {
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getAttachRestrictionReasonsForCarrier(int);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.List<java.lang.String> getSatellitePlmnsForCarrier(int);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForCommunicationAllowedStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCommunicationAllowedStateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForIncomingDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteModemStateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSupportedStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteSupportedStateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
@@ -18788,16 +18861,21 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteSubscriberProvisionStatus(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.telephony.satellite.SatelliteSubscriberProvisionStatus>,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForCapabilitiesChanged(@NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForCommunicationAllowedStateChanged(@NonNull android.telephony.satellite.SatelliteCommunicationAllowedStateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForIncomingDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForModemStateChanged(@NonNull android.telephony.satellite.SatelliteModemStateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSupportedStateChanged(@NonNull android.telephony.satellite.SatelliteSupportedStateCallback);
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String ACTION_SATELLITE_START_NON_EMERGENCY_SESSION = "android.telephony.satellite.action.SATELLITE_START_NON_EMERGENCY_SESSION";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED = "android.telephony.satellite.action.SATELLITE_SUBSCRIBER_ID_LIST_CHANGED";
     field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS = 7; // 0x7
     field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3; // 0x3
     field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5; // 0x5
@@ -18816,6 +18894,7 @@
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_UNKNOWN = 0; // 0x0
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS = 1; // 0x1
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911 = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT = "android.telephony.METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT";
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; // 0x3
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; // 0x1
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
@@ -18882,18 +18961,100 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getErrorCode();
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatelliteModemEnableRequestAttributes implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.telephony.satellite.SatelliteSubscriptionInfo getSatelliteSubscriptionInfo();
+    method public boolean isDemoMode();
+    method public boolean isEmergencyMode();
+    method public boolean isEnabled();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteModemEnableRequestAttributes> CREATOR;
+  }
+
   @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteModemStateCallback {
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteModemStateChanged(int);
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatellitePosition implements android.os.Parcelable {
+    method public int describeContents();
+    method public double getAltitudeKm();
+    method public double getLongitudeDegrees();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatellitePosition> CREATOR;
+  }
+
   @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteProvisionStateCallback {
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteProvisionStateChanged(boolean);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public default void onSatelliteSubscriptionProvisionStateChanged(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberProvisionStatus>);
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatelliteSubscriberInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getCarrierId();
+    method @NonNull public String getNiddApn();
+    method public int getSubId();
+    method @NonNull public String getSubscriberId();
+    method public int getSubscriberIdType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteSubscriberInfo> CREATOR;
+    field public static final int ICCID = 0; // 0x0
+    field public static final int IMSI_MSISDN = 1; // 0x1
+  }
+
+  public static final class SatelliteSubscriberInfo.Builder {
+    ctor public SatelliteSubscriberInfo.Builder();
+    method @NonNull public android.telephony.satellite.SatelliteSubscriberInfo build();
+    method @NonNull public android.telephony.satellite.SatelliteSubscriberInfo.Builder setCarrierId(int);
+    method @NonNull public android.telephony.satellite.SatelliteSubscriberInfo.Builder setNiddApn(@NonNull String);
+    method @NonNull public android.telephony.satellite.SatelliteSubscriberInfo.Builder setSubId(int);
+    method @NonNull public android.telephony.satellite.SatelliteSubscriberInfo.Builder setSubscriberId(@NonNull String);
+    method @NonNull public android.telephony.satellite.SatelliteSubscriberInfo.Builder setSubscriberIdType(int);
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatelliteSubscriberProvisionStatus implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.telephony.satellite.SatelliteSubscriberInfo getSatelliteSubscriberInfo();
+    method public boolean isProvisioned();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteSubscriberProvisionStatus> CREATOR;
+  }
+
+  public static final class SatelliteSubscriberProvisionStatus.Builder {
+    ctor public SatelliteSubscriberProvisionStatus.Builder();
+    method @NonNull public android.telephony.satellite.SatelliteSubscriberProvisionStatus build();
+    method @NonNull public android.telephony.satellite.SatelliteSubscriberProvisionStatus.Builder setProvisioned(boolean);
+    method @NonNull public android.telephony.satellite.SatelliteSubscriberProvisionStatus.Builder setSatelliteSubscriberInfo(@NonNull android.telephony.satellite.SatelliteSubscriberInfo);
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatelliteSubscriptionInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getIccId();
+    method @NonNull public String getNiddApn();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteSubscriptionInfo> CREATOR;
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public interface SatelliteSupportedStateCallback {
+    method public void onSatelliteSupportedStateChanged(boolean);
   }
 
   @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteTransmissionUpdateCallback {
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onReceiveDatagramStateChanged(int, int, int);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatellitePositionChanged(@NonNull android.telephony.satellite.PointingInfo);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public default void onSendDatagramRequested(int);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSendDatagramStateChanged(int, int, int);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public default void onSendDatagramStateChanged(int, int, int, int);
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SystemSelectionSpecifier implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public int[] getBands();
+    method @NonNull public int[] getEarfcns();
+    method @NonNull public String getMccMnc();
+    method @NonNull public java.util.List<android.telephony.satellite.SatelliteInfo> getSatelliteInfos();
+    method @NonNull public int[] getTagIds();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SystemSelectionSpecifier> CREATOR;
   }
 
 }
@@ -19001,8 +19162,10 @@
 
   public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable {
     method public final long getUserActivityTimeout();
+    method @FlaggedApi("com.android.hardware.input.override_power_key_behavior_in_focused_window") @RequiresPermission(android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) public boolean isReceivePowerKeyDoublePressEnabled();
     method public boolean isSystemApplicationOverlay();
     method @FlaggedApi("android.companion.virtualdevice.flags.status_bar_and_insets") public void setInsetsParams(@NonNull java.util.List<android.view.WindowManager.InsetsParams>);
+    method @FlaggedApi("com.android.hardware.input.override_power_key_behavior_in_focused_window") @RequiresPermission(android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) public void setReceivePowerKeyDoublePressEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY) public void setSystemApplicationOverlay(boolean);
     method public final void setUserActivityTimeout(long);
     field @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 524288; // 0x80000
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1a97f7b..5442ab7 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -541,6 +541,7 @@
     method @Nullable public android.graphics.Rect peekBitmapDimensions();
     method @Nullable public android.graphics.Rect peekBitmapDimensions(int);
     method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setBitmapWithCrops(@Nullable android.graphics.Bitmap, @NonNull java.util.Map<android.graphics.Point,android.graphics.Rect>, boolean, int) throws java.io.IOException;
+    method @FlaggedApi("android.app.live_wallpaper_content_handling") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setBitmapWithDescription(@Nullable android.graphics.Bitmap, @NonNull android.app.wallpaper.WallpaperDescription, boolean, int) throws java.io.IOException;
     method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setStreamWithCrops(@NonNull java.io.InputStream, @NonNull java.util.Map<android.graphics.Point,android.graphics.Rect>, boolean, int) throws java.io.IOException;
     method public void setWallpaperZoomOut(@NonNull android.os.IBinder, float);
     method public boolean shouldEnableWideColorGamut();
@@ -907,6 +908,15 @@
 
 }
 
+package android.app.wallpaper {
+
+  public static final class WallpaperDescription.Builder {
+    method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setCropHints(@NonNull java.util.Map<android.graphics.Point,android.graphics.Rect>);
+    method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setCropHints(@NonNull android.util.SparseArray<android.graphics.Rect>);
+  }
+
+}
+
 package android.appwidget {
 
   public class AppWidgetManager {
@@ -923,6 +933,7 @@
     method @NonNull public android.companion.AssociationInfo build();
     method @NonNull public android.companion.AssociationInfo.Builder setAssociatedDevice(@Nullable android.companion.AssociatedDevice);
     method @FlaggedApi("android.companion.association_device_icon") @NonNull public android.companion.AssociationInfo.Builder setDeviceIcon(@Nullable android.graphics.drawable.Icon);
+    method @FlaggedApi("android.companion.association_tag") @NonNull public android.companion.AssociationInfo.Builder setDeviceId(@Nullable android.companion.DeviceId);
     method @NonNull public android.companion.AssociationInfo.Builder setDeviceMacAddress(@Nullable android.net.MacAddress);
     method @NonNull public android.companion.AssociationInfo.Builder setDeviceProfile(@Nullable String);
     method @NonNull public android.companion.AssociationInfo.Builder setDisplayName(@Nullable CharSequence);
@@ -931,7 +942,6 @@
     method @NonNull public android.companion.AssociationInfo.Builder setRevoked(boolean);
     method @NonNull public android.companion.AssociationInfo.Builder setSelfManaged(boolean);
     method @NonNull public android.companion.AssociationInfo.Builder setSystemDataSyncFlags(int);
-    method @FlaggedApi("android.companion.association_tag") @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String);
     method @NonNull public android.companion.AssociationInfo.Builder setTimeApproved(long);
   }
 
@@ -3730,6 +3740,7 @@
     method @NonNull public android.view.Display.Mode getDefaultMode();
     method public int getRemoveMode();
     method @NonNull public int[] getReportedHdrTypes();
+    method @NonNull public float[] getSupportedRefreshRatesLegacy();
     method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut();
     method @Nullable public android.view.Display.Mode getSystemPreferredDisplayMode();
     method public int getType();
@@ -3752,6 +3763,7 @@
 
   public static final class Display.Mode implements android.os.Parcelable {
     ctor public Display.Mode(int, int, float);
+    method public float getVsyncRate();
     method public boolean isSynthetic();
     method public boolean matches(int, int, float);
   }
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index d1eb8e8..4bf87f91 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -141,6 +141,14 @@
     }
 
     /**
+     * @see #sPostNotifyEndListenerEnabled
+     * @hide
+     */
+    public static boolean isPostNotifyEndListenerEnabled() {
+        return sPostNotifyEndListenerEnabled;
+    }
+
+    /**
      * Starts this animation. If the animation has a nonzero startDelay, the animation will start
      * running after that delay elapses. A non-delayed animation will have its initial
      * value(s) set immediately, followed by calls to
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index d9f8d33..eccb6ff 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -1164,6 +1164,19 @@
             @UserIdInt int userId, int notificationId);
 
     /**
+     * Notifies that a media service associated with a media session has transitioned to a
+     * "user-engaged" state. Upon receiving this notification, service will transition to the
+     * foreground state. It should only be called by
+     * {@link com.android.server.media.MediaSessionService}
+     *
+     * @param packageName The package name of the app running the media service.
+     * @param userId The user ID associated with the service.
+     * @param notificationId The ID of the media notification associated with the service.
+     */
+    public abstract void notifyActiveMediaForegroundService(@NonNull String packageName,
+            @UserIdInt int userId, int notificationId);
+
+    /**
      * Same as {@link android.app.IActivityManager#startProfile(int userId)}, but it would succeed
      * even if the profile is disabled - it should only be called by
      * {@link com.android.server.devicepolicy.DevicePolicyManagerService} when starting a profile
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index cb7b115..5d0b355 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5159,7 +5159,7 @@
                 try {
                     if (doRebind) {
                         ActivityManager.getService().unbindFinished(
-                                data.token, data.intent, doRebind);
+                                data.token, data.intent);
                     } else {
                         ActivityManager.getService().serviceDoneExecuting(
                                 data.token, SERVICE_DONE_EXECUTING_UNBIND, 0, 0, data.intent);
@@ -6995,21 +6995,44 @@
 
     final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
         if (start) {
-            try {
-                switch (profileType) {
-                    default:
+            switch (profileType) {
+                case ProfilerInfo.PROFILE_TYPE_LOW_OVERHEAD:
+                    if (!com.android.art.flags.Flags.alwaysEnableProfileCode()) {
+                        Slog.w(TAG, "Low overhead tracing feature is not enabled");
+                        break;
+                    }
+                    VMDebug.startLowOverheadTrace();
+                    break;
+                default:
+                    try {
                         mProfiler.setProfiler(profilerInfo);
                         mProfiler.startProfiling();
                         break;
-                }
-            } catch (RuntimeException e) {
-                Slog.w(TAG, "Profiling failed on path " + profilerInfo.profileFile
-                        + " -- can the process access this path?");
-            } finally {
-                profilerInfo.closeFd();
+                    } catch (RuntimeException e) {
+                        Slog.w(TAG, "Profiling failed on path " + profilerInfo.profileFile
+                                + " -- can the process access this path?");
+                    } finally {
+                        profilerInfo.closeFd();
+                    }
             }
         } else {
             switch (profileType) {
+                case ProfilerInfo.PROFILE_TYPE_LOW_OVERHEAD:
+                    if (!com.android.art.flags.Flags.alwaysEnableProfileCode()) {
+                        if (profilerInfo != null) {
+                            profilerInfo.closeFd();
+                        }
+                        Slog.w(TAG, "Low overhead tracing feature is not enabled");
+                        break;
+                    }
+                    if (profilerInfo != null) {
+                        FileDescriptor fd = profilerInfo.profileFd.getFileDescriptor();
+                        VMDebug.TraceDestination dst =
+                                VMDebug.TraceDestination.fromFileDescriptor(fd);
+                        VMDebug.dumpLowOverheadTrace(dst);
+                    }
+                    VMDebug.stopLowOverheadTrace();
+                    break;
                 default:
                     mProfiler.stopProfiling();
                     break;
@@ -8843,10 +8866,10 @@
         // Call per-process mainline module initialization.
         initializeMainlineModules();
 
-        Process.setArgV0("<pre-initialized>");
-
         Looper.prepareMainLooper();
 
+        Process.setArgV0("<pre-initialized>");
+
         // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
         // It will be in the format "seq=114"
         long startSeq = 0;
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 009cd72..61b5687 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -69,6 +70,14 @@
     public int topActivityLetterboxAppWidth = PROPERTY_VALUE_UNSET;
 
     /**
+     * Contains the top activity bounds when the activity is letterboxed.
+     * It's {@code null} if there's no top activity in the task or it's not letterboxed.
+     */
+    // TODO(b/379824541) Remove duplicate information.
+    @Nullable
+    public Rect topActivityLetterboxBounds;
+
+    /**
      * Stores camera-related app compat information about a particular Task.
      */
     public CameraCompatTaskInfo cameraCompatTaskInfo = CameraCompatTaskInfo.create();
@@ -378,6 +387,7 @@
         topActivityLetterboxHeight = source.readInt();
         topActivityLetterboxAppWidth = source.readInt();
         topActivityLetterboxAppHeight = source.readInt();
+        topActivityLetterboxBounds = source.readTypedObject(Rect.CREATOR);
         cameraCompatTaskInfo = source.readTypedObject(CameraCompatTaskInfo.CREATOR);
     }
 
@@ -393,6 +403,7 @@
         dest.writeInt(topActivityLetterboxHeight);
         dest.writeInt(topActivityLetterboxAppWidth);
         dest.writeInt(topActivityLetterboxAppHeight);
+        dest.writeTypedObject(topActivityLetterboxBounds, flags);
         dest.writeTypedObject(cameraCompatTaskInfo, flags);
     }
 
@@ -415,6 +426,7 @@
                 + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled()
                 + " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled()
                 + " hasMinAspectRatioOverride=" + hasMinAspectRatioOverride()
+                + " topActivityLetterboxBounds=" + topActivityLetterboxBounds
                 + " cameraCompatTaskInfo=" + cameraCompatTaskInfo.toString()
                 + "}";
     }
diff --git a/core/java/android/app/AppOpsManager.aidl b/core/java/android/app/AppOpsManager.aidl
index b4dee2e..56ed290 100644
--- a/core/java/android/app/AppOpsManager.aidl
+++ b/core/java/android/app/AppOpsManager.aidl
@@ -19,6 +19,7 @@
 parcelable AppOpsManager.PackageOps;
 parcelable AppOpsManager.NoteOpEventProxyInfo;
 parcelable AppOpsManager.NoteOpEvent;
+parcelable AppOpsManager.NotedOp;
 parcelable AppOpsManager.OpFeatureEntry;
 parcelable AppOpsManager.OpEntry;
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 3476578..c789e28 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -262,6 +262,24 @@
 
     private static final Object sLock = new Object();
 
+    // A map that records noted times for each op.
+    private final ArrayMap<NotedOp, Integer> mPendingNotedOps = new ArrayMap<>();
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+    private static final int NOTE_OP_BATCHING_DELAY_MILLIS = 1000;
+
+    private boolean isNoteOpBatchingSupported() {
+        // If noteOp is called from system server no IPC is made, hence we don't need batching.
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            return false;
+        }
+        return Flags.noteOpBatchingEnabled();
+    }
+
+    private final Object mBatchedNoteOpLock = new Object();
+    @GuardedBy("mBatchedNoteOpLock")
+    private boolean mIsBatchedNoteOpCallScheduled = false;
+
     /** Current {@link OnOpNotedCallback}. Change via {@link #setOnOpNotedCallback} */
     @GuardedBy("sLock")
     private static @Nullable OnOpNotedCallback sOnOpNotedCallback;
@@ -3586,7 +3604,7 @@
         /** Attribution tag of the proxy that noted the op */
         private @Nullable String mAttributionTag;
         /** Persistent device Id of the proxy that noted the op */
-        private @Nullable String mDeviceId;
+        private @NonNull String mDeviceId;
 
         /**
          * Reinit existing object with new state.
@@ -3599,7 +3617,7 @@
          * @hide
          */
         public void reinit(@IntRange(from = 0) int uid, @Nullable String packageName,
-                @Nullable String attributionTag, @Nullable String deviceId) {
+                @Nullable String attributionTag, @NonNull String deviceId) {
             mUid = Preconditions.checkArgumentNonnegative(uid);
             mPackageName = packageName;
             mAttributionTag = attributionTag;
@@ -3662,7 +3680,8 @@
                     "from", 0);
             this.mPackageName = packageName;
             this.mAttributionTag = attributionTag;
-            this.mDeviceId = deviceId;
+            this.mDeviceId = deviceId == null ? VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
+                    : deviceId;
         }
         /**
          * Copy constructor
@@ -3705,7 +3724,7 @@
          * 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; }
+        public @NonNull String getDeviceId() { return mDeviceId; }
 
         @Override
         @DataClass.Generated.Member
@@ -3716,12 +3735,12 @@
             byte flg = 0;
             if (mPackageName != null) flg |= 0x2;
             if (mAttributionTag != null) flg |= 0x4;
-            if (mDeviceId != null) flg |= 0x8;
+            flg |= 0x8;
             dest.writeByte(flg);
             dest.writeInt(mUid);
             if (mPackageName != null) dest.writeString(mPackageName);
             if (mAttributionTag != null) dest.writeString(mAttributionTag);
-            if (mDeviceId != null) dest.writeString(mDeviceId);
+            dest.writeString(mDeviceId);
         }
 
         @Override
@@ -3739,7 +3758,8 @@
             int uid = in.readInt();
             String packageName = (flg & 0x2) == 0 ? null : in.readString();
             String attributionTag = (flg & 0x4) == 0 ? null : in.readString();
-            String deviceId = (flg & 0x8) == 0 ? null : in.readString();
+            String deviceId = (flg & 0x8) == 0 ? VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
+                    : in.readString();
             this.mUid = uid;
             com.android.internal.util.AnnotationValidations.validate(
                     IntRange.class, null, mUid,
@@ -7464,6 +7484,135 @@
     }
 
     /**
+     * A NotedOp is an app op grouped in noteOp API and sent to the system server in a batch
+     *
+     * @hide
+     */
+    public static final class NotedOp implements Parcelable {
+        private final @IntRange(from = 0, to = _NUM_OP - 1) int mOp;
+        private final @IntRange(from = 0) int mUid;
+        private final @Nullable String mPackageName;
+        private final @Nullable String mAttributionTag;
+        private final int mVirtualDeviceId;
+        private final @Nullable String mMessage;
+        private final boolean mShouldCollectAsyncNotedOp;
+        private final boolean mShouldCollectMessage;
+
+        public NotedOp(int op, int uid, @Nullable String packageName,
+                @Nullable String attributionTag, int virtualDeviceId, @Nullable String message,
+                boolean shouldCollectAsyncNotedOp, boolean shouldCollectMessage) {
+            mOp = op;
+            mUid = uid;
+            mPackageName = packageName;
+            mAttributionTag = attributionTag;
+            mVirtualDeviceId = virtualDeviceId;
+            mMessage = message;
+            mShouldCollectAsyncNotedOp = shouldCollectAsyncNotedOp;
+            mShouldCollectMessage = shouldCollectMessage;
+        }
+
+        NotedOp(Parcel source) {
+            mOp = source.readInt();
+            mUid = source.readInt();
+            mPackageName = source.readString();
+            mAttributionTag = source.readString();
+            mVirtualDeviceId = source.readInt();
+            mMessage = source.readString();
+            mShouldCollectAsyncNotedOp = source.readBoolean();
+            mShouldCollectMessage = source.readBoolean();
+        }
+
+        public int getOp() {
+            return mOp;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public @Nullable String getPackageName() {
+            return mPackageName;
+        }
+
+        public @Nullable String getAttributionTag() {
+            return mAttributionTag;
+        }
+
+        public int getVirtualDeviceId() {
+            return mVirtualDeviceId;
+        }
+
+        public @Nullable String getMessage() {
+            return mMessage;
+        }
+
+        public boolean getShouldCollectAsyncNotedOp() {
+            return mShouldCollectAsyncNotedOp;
+        }
+
+        public boolean getShouldCollectMessage() {
+            return mShouldCollectMessage;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(mOp);
+            dest.writeInt(mUid);
+            dest.writeString(mPackageName);
+            dest.writeString(mAttributionTag);
+            dest.writeInt(mVirtualDeviceId);
+            dest.writeString(mMessage);
+            dest.writeBoolean(mShouldCollectAsyncNotedOp);
+            dest.writeBoolean(mShouldCollectMessage);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            NotedOp that = (NotedOp) o;
+            return mOp == that.mOp && mUid == that.mUid && Objects.equals(mPackageName,
+                    that.mPackageName) && Objects.equals(mAttributionTag, that.mAttributionTag)
+                    && mVirtualDeviceId == that.mVirtualDeviceId && Objects.equals(mMessage,
+                    that.mMessage) && Objects.equals(mShouldCollectAsyncNotedOp,
+                    that.mShouldCollectAsyncNotedOp) && Objects.equals(mShouldCollectMessage,
+                    that.mShouldCollectMessage);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mOp, mUid, mPackageName, mAttributionTag, mVirtualDeviceId,
+                    mMessage, mShouldCollectAsyncNotedOp, mShouldCollectMessage);
+        }
+
+        @Override
+        public String toString() {
+            return "NotedOp{" + "mOp=" + mOp + ", mUid=" + mUid + ", mPackageName=" + mPackageName
+                    + ", mAttributionTag=" + mAttributionTag + ", mVirtualDeviceId="
+                    + mVirtualDeviceId + ", mMessage=" + mMessage + ", mShouldCollectAsyncNotedOp="
+                    + mShouldCollectAsyncNotedOp + ", mShouldCollectMessage="
+                    + mShouldCollectMessage + "}";
+        }
+
+
+        public static final @NonNull Creator<NotedOp> CREATOR =
+                new Creator<>() {
+                    @Override public NotedOp createFromParcel(Parcel source) {
+                        return new NotedOp(source);
+                    }
+
+                    @Override public NotedOp[] newArray(int size) {
+                        return new NotedOp[size];
+                    }
+                };
+    }
+
+    /**
      * Computes the sum of the counts for the given flags in between the begin and
      * end UID states.
      *
@@ -7977,6 +8126,9 @@
     AppOpsManager(Context context, IAppOpsService service) {
         mContext = context;
         mService = service;
+        mHandlerThread = new HandlerThread("AppOpsManager");
+        mHandlerThread.start();
+        mHandler = mHandlerThread.getThreadHandler();
 
         if (mContext != null) {
             final PackageManager pm = mContext.getPackageManager();
@@ -9313,15 +9465,74 @@
                 }
             }
 
-            SyncNotedAppOp syncOp;
-            if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
-                syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
-                        collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
-            } else {
-                syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag,
-                    virtualDeviceId, collectionMode == COLLECT_ASYNC, message,
-                    shouldCollectMessage);
+            SyncNotedAppOp syncOp = null;
+            boolean skipBinderCall = false;
+            if (isNoteOpBatchingSupported()) {
+                int mode = sAppOpModeCache.query(
+                        new AppOpModeQuery(op, uid, packageName, virtualDeviceId, attributionTag,
+                                "noteOpNoThrow"));
+                // For FOREGROUND mode, we still need to make a binder call to the system service
+                // to translate it to ALLOWED or IGNORED. So no batching is needed.
+                if (mode != MODE_FOREGROUND) {
+                    synchronized (mBatchedNoteOpLock) {
+                        NotedOp notedOp = new NotedOp(op, uid, packageName, attributionTag,
+                                virtualDeviceId, message, collectionMode == COLLECT_ASYNC,
+                                shouldCollectMessage);
+
+                        // Batch same noteOp calls and send them with their counters to the system
+                        // service asynchronously. The time window for batching is specified in
+                        // NOTE_OP_BATCHING_DELAY_MILLIS. Always allow the first noteOp call to go
+                        // through binder API. Accumulate subsequent same noteOp calls during the
+                        // time window in mPendingNotedOps.
+                        if (!mPendingNotedOps.containsKey(notedOp)) {
+                            mPendingNotedOps.put(notedOp, 0);
+                        } else {
+                            skipBinderCall = true;
+                            mPendingNotedOps.merge(notedOp, 1, Integer::sum);
+                        }
+
+                        if (!mIsBatchedNoteOpCallScheduled) {
+                            mHandler.postDelayed(() -> {
+                                ArrayMap<NotedOp, Integer> pendingNotedOpsCopy;
+                                synchronized(mBatchedNoteOpLock) {
+                                    mIsBatchedNoteOpCallScheduled = false;
+                                    pendingNotedOpsCopy =
+                                            new ArrayMap<NotedOp, Integer>(mPendingNotedOps);
+                                    mPendingNotedOps.clear();
+                                }
+                                for (int i = pendingNotedOpsCopy.size() - 1; i >= 0; i--) {
+                                    if (pendingNotedOpsCopy.valueAt(i) == 0) {
+                                        pendingNotedOpsCopy.removeAt(i);
+                                    }
+                                }
+                                if (!pendingNotedOpsCopy.isEmpty()) {
+                                    try {
+                                        mService.noteOperationsInBatch(pendingNotedOpsCopy);
+                                    } catch (RemoteException e) {
+                                        throw e.rethrowFromSystemServer();
+                                    }
+                                }
+                            }, NOTE_OP_BATCHING_DELAY_MILLIS);
+
+                            mIsBatchedNoteOpCallScheduled = true;
+                        }
+                    }
+
+                    syncOp = new SyncNotedAppOp(mode, op, attributionTag, packageName);
+                }
             }
+
+            if (!skipBinderCall) {
+                if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+                    syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
+                            collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
+                } else {
+                    syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag,
+                            virtualDeviceId, collectionMode == COLLECT_ASYNC, message,
+                            shouldCollectMessage);
+                }
+            }
+
             if (syncOp.getOpMode() == MODE_ALLOWED) {
                 if (collectionMode == COLLECT_SELF) {
                     collectNotedOpForSelf(syncOp);
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index b21defb..8b7ea0f 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -29,7 +29,7 @@
 import com.android.internal.util.function.DodecFunction;
 import com.android.internal.util.function.HexConsumer;
 import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.OctFunction;
+import com.android.internal.util.function.NonaFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.UndecFunction;
 
@@ -86,9 +86,9 @@
          */
         SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
                 @Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
-                @Nullable String message, boolean shouldCollectMessage,
-                @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String,
-                        Boolean, SyncNotedAppOp> superImpl);
+                @Nullable String message, boolean shouldCollectMessage, int notedCount,
+                @NonNull NonaFunction<Integer, Integer, String, String, Integer, Boolean, String,
+                        Boolean, Integer, SyncNotedAppOp> superImpl);
 
         /**
          * Allows overriding note proxy operation behavior.
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 7e0a9b6..da33847 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -130,7 +130,6 @@
 import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.Immutable;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.pm.RoSystemFeatures;
@@ -799,7 +798,7 @@
     private final static PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>
             mHasSystemFeatureCache = new PropertyInvalidatedCache<>(
                 new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
-                .api(HAS_SYSTEM_FEATURE_API).maxEntries(256).isolateUids(false),
+                .api(HAS_SYSTEM_FEATURE_API).maxEntries(SDK_FEATURE_COUNT).isolateUids(false),
                 HAS_SYSTEM_FEATURE_API, null) {
 
                 @Override
@@ -1020,6 +1019,33 @@
         }
     }
 
+    @Override
+    public void setPageSizeAppCompatFlagsSettingsOverride(String packageName, boolean enabled) {
+        try {
+            mPM.setPageSizeAppCompatFlagsSettingsOverride(packageName, enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean isPageSizeCompatEnabled(String packageName) {
+        try {
+            return mPM.isPageSizeCompatEnabled(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public String getPageSizeCompatWarningMessage(String packageName) {
+        try {
+            return mPM.getPageSizeCompatWarningMessage(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private static List<byte[]> encodeCertificates(List<Certificate> certs) throws
             CertificateEncodingException {
         if (certs == null) {
diff --git a/core/java/android/app/BroadcastStickyCache.java b/core/java/android/app/BroadcastStickyCache.java
new file mode 100644
index 0000000..fe2e107
--- /dev/null
+++ b/core/java/android/app/BroadcastStickyCache.java
@@ -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 android.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context.RegisterReceiverFlags;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
+import android.net.TetheringManager;
+import android.net.nsd.NsdManager;
+import android.net.wifi.WifiManager;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.IpcDataCache;
+import android.os.IpcDataCache.Config;
+import android.os.UpdateLock;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.view.WindowManagerPolicyConstants;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+/** @hide */
+public class BroadcastStickyCache {
+
+    @VisibleForTesting
+    public static final String[] STICKY_BROADCAST_ACTIONS = {
+            AudioManager.ACTION_HDMI_AUDIO_PLUG,
+            AudioManager.ACTION_HEADSET_PLUG,
+            AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED,
+            AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED,
+            AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+            AudioManager.RINGER_MODE_CHANGED_ACTION,
+            ConnectivityManager.CONNECTIVITY_ACTION,
+            Intent.ACTION_BATTERY_CHANGED,
+            Intent.ACTION_DEVICE_STORAGE_FULL,
+            Intent.ACTION_DEVICE_STORAGE_LOW,
+            Intent.ACTION_SIM_STATE_CHANGED,
+            NsdManager.ACTION_NSD_STATE_CHANGED,
+            TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED,
+            TetheringManager.ACTION_TETHER_STATE_CHANGED,
+            UpdateLock.UPDATE_LOCK_CHANGED,
+            UsbManager.ACTION_USB_STATE,
+            WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED,
+            WifiManager.NETWORK_STATE_CHANGED_ACTION,
+            WifiManager.SUPPLICANT_STATE_CHANGED_ACTION,
+            WifiManager.WIFI_STATE_CHANGED_ACTION,
+            WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
+            WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED,
+            "android.net.conn.INET_CONDITION_ACTION" // ConnectivityManager.INET_CONDITION_ACTION
+    };
+
+    @VisibleForTesting
+    public static final ArrayMap<String, String> sActionApiNameMap = new ArrayMap<>();
+
+    private static final ArrayMap<String, IpcDataCache.Config> sActionConfigMap = new ArrayMap<>();
+
+    private static final ArrayMap<StickyBroadcastFilter, IpcDataCache<Void, Intent>>
+            sFilterCacheMap = new ArrayMap<>();
+
+    static {
+        sActionApiNameMap.put(AudioManager.ACTION_HDMI_AUDIO_PLUG, "hdmi_audio_plug");
+        sActionApiNameMap.put(AudioManager.ACTION_HEADSET_PLUG, "headset_plug");
+        sActionApiNameMap.put(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED,
+                "sco_audio_state_changed");
+        sActionApiNameMap.put(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED,
+                "action_sco_audio_state_updated");
+        sActionApiNameMap.put(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+                "internal_ringer_mode_changed_action");
+        sActionApiNameMap.put(AudioManager.RINGER_MODE_CHANGED_ACTION,
+                "ringer_mode_changed");
+        sActionApiNameMap.put(ConnectivityManager.CONNECTIVITY_ACTION,
+                "connectivity_change");
+        sActionApiNameMap.put(Intent.ACTION_BATTERY_CHANGED, "battery_changed");
+        sActionApiNameMap.put(Intent.ACTION_DEVICE_STORAGE_FULL, "device_storage_full");
+        sActionApiNameMap.put(Intent.ACTION_DEVICE_STORAGE_LOW, "device_storage_low");
+        sActionApiNameMap.put(Intent.ACTION_SIM_STATE_CHANGED, "sim_state_changed");
+        sActionApiNameMap.put(NsdManager.ACTION_NSD_STATE_CHANGED, "nsd_state_changed");
+        sActionApiNameMap.put(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED,
+                "service_providers_updated");
+        sActionApiNameMap.put(TetheringManager.ACTION_TETHER_STATE_CHANGED,
+                "tether_state_changed");
+        sActionApiNameMap.put(UpdateLock.UPDATE_LOCK_CHANGED, "update_lock_changed");
+        sActionApiNameMap.put(UsbManager.ACTION_USB_STATE, "usb_state");
+        sActionApiNameMap.put(WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED,
+                "wifi_scan_availability_changed");
+        sActionApiNameMap.put(WifiManager.NETWORK_STATE_CHANGED_ACTION,
+                "network_state_change");
+        sActionApiNameMap.put(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION,
+                "supplicant_state_change");
+        sActionApiNameMap.put(WifiManager.WIFI_STATE_CHANGED_ACTION, "wifi_state_changed");
+        sActionApiNameMap.put(
+                WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION, "wifi_p2p_state_changed");
+        sActionApiNameMap.put(
+                WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED, "hdmi_plugged");
+        sActionApiNameMap.put(
+                "android.net.conn.INET_CONDITION_ACTION", "inet_condition_action");
+    }
+
+    /**
+     * Checks whether we can use caching for the given filter.
+     */
+    public static boolean useCache(@Nullable IntentFilter filter) {
+        return Flags.useStickyBcastCache()
+                && filter != null
+                && filter.safeCountActions() == 1
+                && ArrayUtils.contains(STICKY_BROADCAST_ACTIONS, filter.getAction(0));
+    }
+
+    public static void invalidateCache(@NonNull String action) {
+        if (!Flags.useStickyBcastCache()
+                || !ArrayUtils.contains(STICKY_BROADCAST_ACTIONS, action)) {
+            return;
+        }
+        IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+                sActionApiNameMap.get(action));
+    }
+
+    public static void invalidateAllCaches() {
+        for (int i = sActionApiNameMap.size() - 1; i >= 0; i--) {
+            IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+                    sActionApiNameMap.valueAt(i));
+        }
+    }
+
+    /**
+     * Returns the cached {@link Intent} based on the filter, if exits otherwise
+     * fetches the value from the service.
+     */
+    @Nullable
+    public static Intent getIntent(
+            @NonNull IApplicationThread applicationThread,
+            @NonNull String mBasePackageName,
+            @Nullable String attributionTag,
+            @NonNull IntentFilter filter,
+            @Nullable String broadcastPermission,
+            @UserIdInt int userId,
+            @RegisterReceiverFlags int flags) {
+        IpcDataCache<Void, Intent> intentDataCache = findIpcDataCache(filter);
+
+        if (intentDataCache == null) {
+            final String action = filter.getAction(0);
+            final StickyBroadcastFilter stickyBroadcastFilter =
+                    new StickyBroadcastFilter(filter, action);
+            final Config config = getConfig(action);
+
+            intentDataCache =
+                    new IpcDataCache<>(config,
+                            (query) -> ActivityManager.getService().registerReceiverWithFeature(
+                                    applicationThread,
+                                    mBasePackageName,
+                                    attributionTag,
+                                    /* receiverId= */ "null",
+                                    /* receiver= */ null,
+                                    filter,
+                                    broadcastPermission,
+                                    userId,
+                                    flags));
+            sFilterCacheMap.put(stickyBroadcastFilter, intentDataCache);
+        }
+        return intentDataCache.query(null);
+    }
+
+    @VisibleForTesting
+    public static void clearCacheForTest() {
+        sFilterCacheMap.clear();
+    }
+
+    @Nullable
+    private static IpcDataCache<Void, Intent> findIpcDataCache(
+            @NonNull IntentFilter filter) {
+        for (int i = sFilterCacheMap.size() - 1; i >= 0; i--) {
+            StickyBroadcastFilter existingFilter = sFilterCacheMap.keyAt(i);
+            if (filter.getAction(0).equals(existingFilter.action())
+                    && IntentFilter.filterEquals(existingFilter.filter(), filter)) {
+                return sFilterCacheMap.valueAt(i);
+            }
+        }
+        return null;
+    }
+
+    @NonNull
+    private static IpcDataCache.Config getConfig(@NonNull String action) {
+        if (!sActionConfigMap.containsKey(action)) {
+            // We only need 1 entry per cache but just to be on the safer side we are choosing 32
+            // although we don't expect more than 1.
+            sActionConfigMap.put(action,
+                    new Config(32, IpcDataCache.MODULE_SYSTEM, sActionApiNameMap.get(action)));
+        }
+
+        return sActionConfigMap.get(action);
+    }
+
+    @VisibleForTesting
+    private record StickyBroadcastFilter(@NonNull IntentFilter filter, @NonNull String action) {
+    }
+}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index cd56957..dcbdc23 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1922,10 +1922,23 @@
             }
         }
         try {
-            final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
-                    mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
-                    AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
-                    flags);
+            final Intent intent;
+            if (receiver == null && BroadcastStickyCache.useCache(filter)) {
+                intent = BroadcastStickyCache.getIntent(
+                        mMainThread.getApplicationThread(),
+                        mBasePackageName,
+                        getAttributionTag(),
+                        filter,
+                        broadcastPermission,
+                        userId,
+                        flags);
+            } else {
+                intent = ActivityManager.getService().registerReceiverWithFeature(
+                        mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
+                        AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission,
+                        userId, flags);
+            }
+
             if (intent != null) {
                 intent.setExtrasClassLoader(getClassLoader());
                 // TODO: determine at registration time if caller is
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 0668958..ad01ad5 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -321,7 +321,7 @@
     oneway void removeContentProvider(in IBinder connection, boolean stable);
     @UnsupportedAppUsage
     void setRequestedOrientation(in IBinder token, int requestedOrientation);
-    void unbindFinished(in IBinder token, in Intent service, boolean doRebind);
+    void unbindFinished(in IBinder token, in Intent service);
     @UnsupportedAppUsage
     void setProcessImportant(in IBinder token, int pid, boolean isForeground, String reason);
     void setServiceForeground(in ComponentName className, in IBinder token,
@@ -1028,4 +1028,14 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DEVICE_POWER)")
     void noteAppRestrictionEnabled(in String packageName, int uid, int restrictionType,
             boolean enabled, int reason, in String subReason, int source, long threshold);
+
+    /**
+     * Creates and returns a new IntentCreatorToken that keeps the creatorUid and refreshes key
+     * fields of the intent passed in.
+     *
+     * @param intent The intent with key fields out of sync of the IntentCreatorToken it contains.
+     * @hide
+     */
+    @EnforcePermission("INTERACT_ACROSS_USERS_FULL")
+    IBinder refreshIntentCreatorToken(in Intent intent);
 }
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index f2228f9..4a9a286 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -178,6 +178,11 @@
     void onRecentTaskListFrozenChanged(boolean frozen);
 
     /**
+     *  Called when a task is removed from the recent tasks list as a result of adding a new task.
+     */
+    void onRecentTaskRemovedForAddTask(int taskId);
+
+    /**
      * Called when a task gets or loses focus.
      *
      * @param taskId id of the task.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index b84c91b..a4d8a5c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -441,8 +441,8 @@
 
     /**
      * A large-format version of {@link #contentView}, giving the Notification an
-     * opportunity to show more detail. The system UI may choose to show this
-     * instead of the normal content view at its discretion.
+     * opportunity to show more detail when expanded. The system UI may choose
+     * to show this instead of the normal content view at its discretion.
      *
      * As of N, this field may be null. The expanded notification view is determined by the
      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
@@ -814,21 +814,21 @@
         if (Flags.notificationsRedesignTemplates()) {
             return switch (layoutId) {
                 case R.layout.notification_2025_template_collapsed_base,
+                     R.layout.notification_2025_template_expanded_base,
                      R.layout.notification_2025_template_heads_up_base,
                      R.layout.notification_2025_template_header,
-                     R.layout.notification_template_material_big_base,
-                     R.layout.notification_template_material_big_picture,
-                     R.layout.notification_template_material_big_text,
-                     R.layout.notification_template_material_inbox,
-                     R.layout.notification_template_material_messaging,
-                     R.layout.notification_template_material_big_messaging,
-                     R.layout.notification_template_material_conversation,
-                     R.layout.notification_template_material_media,
-                     R.layout.notification_template_material_big_media,
-                     R.layout.notification_template_material_call,
-                     R.layout.notification_template_material_big_call,
-                     R.layout.notification_template_header -> true;
-                case R.layout.notification_template_material_progress -> Flags.apiRichOngoing();
+                     R.layout.notification_2025_template_conversation,
+                     R.layout.notification_2025_template_collapsed_call,
+                     R.layout.notification_2025_template_expanded_call,
+                     R.layout.notification_2025_template_collapsed_messaging,
+                     R.layout.notification_2025_template_expanded_messaging,
+                     R.layout.notification_2025_template_collapsed_media,
+                     R.layout.notification_2025_template_expanded_media,
+                     R.layout.notification_2025_template_expanded_big_picture,
+                     R.layout.notification_2025_template_expanded_big_text,
+                     R.layout.notification_2025_template_expanded_inbox -> true;
+                case R.layout.notification_2025_template_expanded_progress
+                        -> Flags.apiRichOngoing();
                 default -> false;
             };
         }
@@ -1337,7 +1337,7 @@
     public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
 
     /**
-     * {@link #extras} key: this is the longer text shown in the big form of a
+     * {@link #extras} key: this is the longer text shown in the expanded form of a
      * {@link BigTextStyle} notification, as supplied to
      * {@link BigTextStyle#bigText(CharSequence)}.
      */
@@ -3263,6 +3263,7 @@
     public boolean hasPromotableCharacteristics() {
         return isColorizedRequested()
                 && hasTitle()
+                && !isGroupSummary()
                 && !containsCustomViews()
                 && hasPromotableStyle();
     }
@@ -5918,12 +5919,12 @@
 
         private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
                 TemplateBindResult result) {
-            p.headerless(resId == getBaseLayoutResource()
+            p.headerless(resId == getCollapsedBaseLayoutResource()
                     || resId == getHeadsUpBaseLayoutResource()
                     || resId == getCompactHeadsUpBaseLayoutResource()
                     || resId == getMessagingCompactHeadsUpLayoutResource()
-                    || resId == getMessagingLayoutResource()
-                    || resId == R.layout.notification_template_material_media);
+                    || resId == getCollapsedMessagingLayoutResource()
+                    || resId == getCollapsedMediaLayoutResource());
             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
 
             resetStandardTemplate(contentView);
@@ -5963,7 +5964,7 @@
 
         private static void setHeaderlessVerticalMargins(RemoteViews contentView,
                 StandardTemplateParams p, boolean hasSecondLine) {
-            if (!p.mHeaderless) {
+            if (Flags.notificationsRedesignTemplates() || !p.mHeaderless) {
                 return;
             }
             int marginDimen = hasSecondLine
@@ -6377,7 +6378,7 @@
             boolean hideSnoozeButton = mN.isFgsOrUij()
                     || mN.fullScreenIntent != null
                     || isBackgroundColorized(p)
-                    || p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG;
+                    || p.mViewType != StandardTemplateParams.VIEW_TYPE_EXPANDED;
             big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton);
             if (hideSnoozeButton) {
                 // Only hide; NotificationContentView will show it when it adds the click listener
@@ -6444,10 +6445,13 @@
                 // Clear view padding to allow buttons to start on the left edge.
                 // This must be done before 'setEmphasizedMode' which sets top/bottom margins.
                 big.setViewPadding(R.id.actions, 0, 0, 0, 0);
-                // Add an optional indent that will make buttons start at the correct column when
-                // there is enough space to do so (and fall back to the left edge if not).
-                big.setInt(R.id.actions, "setCollapsibleIndentDimen",
-                        R.dimen.call_notification_collapsible_indent);
+                if (!Flags.notificationsRedesignTemplates()) {
+                    // Add an optional indent that will make buttons start at the correct column
+                    // when there is enough space to do so (and fall back to the left edge if not).
+                    // This is handled directly in NotificationActionListLayout in the new design.
+                    big.setInt(R.id.actions, "setCollapsibleIndentDimen",
+                            R.dimen.call_notification_collapsible_indent);
+                }
                 if (evenlyDividedCallStyleActionLayout()) {
                     if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
                         Log.d(TAG, "setting evenly divided mode on action list");
@@ -6582,19 +6586,21 @@
                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
                     .fillTextsFrom(this);
             TemplateBindResult result = new TemplateBindResult();
-            RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result);
+            RemoteViews standard = applyStandardTemplate(getCollapsedBaseLayoutResource(),
+                    p, result);
             buildCustomContentIntoTemplate(mContext, standard, customContent,
                     p, result);
             return standard;
         }
 
-        private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) {
+        private RemoteViews minimallyDecoratedExpandedContentView(
+                @NonNull RemoteViews customContent) {
             StandardTemplateParams p = mParams.reset()
-                    .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+                    .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
                     .decorationType(StandardTemplateParams.DECORATION_MINIMAL)
                     .fillTextsFrom(this);
             TemplateBindResult result = new TemplateBindResult();
-            RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
+            RemoteViews standard = applyStandardTemplateWithActions(getExpandedBaseLayoutResource(),
                     p, result);
             buildCustomContentIntoTemplate(mContext, standard, customContent,
                     p, result);
@@ -6640,7 +6646,7 @@
             StandardTemplateParams p = mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
                     .fillTextsFrom(this);
-            return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */);
+            return applyStandardTemplate(getCollapsedBaseLayoutResource(), p, null /* result */);
         }
 
         private boolean useExistingRemoteView(RemoteViews customContent) {
@@ -6678,24 +6684,29 @@
          */
         @Deprecated
         public RemoteViews createBigContentView() {
+            return createExpandedContentView();
+        }
+
+        private RemoteViews createExpandedContentView() {
             RemoteViews result = null;
             if (useExistingRemoteView(mN.bigContentView)) {
                 return fullyCustomViewRequiresDecoration(false /* fromStyle */)
-                        ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView;
+                        ? minimallyDecoratedExpandedContentView(mN.bigContentView)
+                        : mN.bigContentView;
             }
             if (mStyle != null) {
-                result = mStyle.makeBigContentView();
+                result = mStyle.makeExpandedContentView();
                 if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) {
-                    result = minimallyDecoratedBigContentView(result);
+                    result = minimallyDecoratedExpandedContentView(result);
                 }
             }
             if (result == null) {
-                if (bigContentViewRequired()) {
+                if (expandedContentViewRequired()) {
                     StandardTemplateParams p = mParams.reset()
-                            .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+                            .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
                             .allowTextWithProgress(true)
                             .fillTextsFrom(this);
-                    result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p,
+                    result = applyStandardTemplateWithActions(getExpandedBaseLayoutResource(), p,
                             null /* result */);
                 }
             }
@@ -6709,7 +6720,7 @@
         // apps can detect the change, it's most likely that the changes will simply result in
         // visual regressions.
         @SuppressWarnings("AndroidFrameworkCompatChange")
-        private boolean bigContentViewRequired() {
+        private boolean expandedContentViewRequired() {
             if (Flags.notificationExpansionOptional()) {
                 // Notifications without a bigContentView, style, or actions do not need to expand
                 boolean exempt = mN.bigContentView == null
@@ -6756,19 +6767,6 @@
         }
 
         /**
-         * Construct a RemoteViews for the ambient version of the notification.
-         *
-         * @hide
-         */
-        public RemoteViews makeAmbientNotification() {
-            RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */);
-            if (headsUpContentView != null) {
-                return headsUpContentView;
-            }
-            return createContentView();
-        }
-
-        /**
          * Adapt the Notification header if this view is used as an expanded view.
          *
          * @hide
@@ -7520,7 +7518,7 @@
         }
 
         @UnsupportedAppUsage
-        private int getBaseLayoutResource() {
+        private int getCollapsedBaseLayoutResource() {
             if (Flags.notificationsRedesignTemplates()) {
                 return R.layout.notification_2025_template_collapsed_base;
             } else {
@@ -7544,36 +7542,100 @@
             return R.layout.notification_template_material_messaging_compact_heads_up;
         }
 
-        private int getBigBaseLayoutResource() {
-            return R.layout.notification_template_material_big_base;
+        private int getExpandedBaseLayoutResource() {
+            if (Flags.notificationsRedesignTemplates()) {
+                return R.layout.notification_2025_template_expanded_base;
+            } else {
+                return R.layout.notification_template_material_big_base;
+            }
         }
 
         private int getBigPictureLayoutResource() {
-            return R.layout.notification_template_material_big_picture;
+            if (Flags.notificationsRedesignTemplates()) {
+                return R.layout.notification_2025_template_expanded_big_picture;
+            } else {
+                return R.layout.notification_template_material_big_picture;
+            }
         }
 
         private int getBigTextLayoutResource() {
-            return R.layout.notification_template_material_big_text;
+            if (Flags.notificationsRedesignTemplates()) {
+                return R.layout.notification_2025_template_expanded_big_text;
+            } else {
+                return R.layout.notification_template_material_big_text;
+            }
         }
 
         private int getInboxLayoutResource() {
-            return R.layout.notification_template_material_inbox;
+            if (Flags.notificationsRedesignTemplates()) {
+                return R.layout.notification_2025_template_expanded_inbox;
+            } else {
+                return R.layout.notification_template_material_inbox;
+            }
         }
 
-        private int getMessagingLayoutResource() {
-            return R.layout.notification_template_material_messaging;
+        private int getCollapsedMessagingLayoutResource() {
+            if (Flags.notificationsRedesignTemplates()) {
+                return R.layout.notification_2025_template_collapsed_messaging;
+            } else {
+                return R.layout.notification_template_material_messaging;
+            }
         }
 
-        private int getBigMessagingLayoutResource() {
-            return R.layout.notification_template_material_big_messaging;
+        private int getExpandedMessagingLayoutResource() {
+            if (Flags.notificationsRedesignTemplates()) {
+                return R.layout.notification_2025_template_expanded_messaging;
+            } else {
+                return R.layout.notification_template_material_big_messaging;
+            }
+        }
+
+        private int getCollapsedMediaLayoutResource() {
+            if (Flags.notificationsRedesignTemplates()) {
+                return R.layout.notification_2025_template_collapsed_media;
+            } else {
+                return R.layout.notification_template_material_media;
+            }
+        }
+
+        private int getExpandedMediaLayoutResource() {
+            if (Flags.notificationsRedesignTemplates()) {
+                return R.layout.notification_2025_template_expanded_media;
+            } else {
+                return R.layout.notification_template_material_big_media;
+            }
         }
 
         private int getConversationLayoutResource() {
-            return R.layout.notification_template_material_conversation;
+            if (Flags.notificationsRedesignTemplates()) {
+                return R.layout.notification_2025_template_conversation;
+            } else {
+                return R.layout.notification_template_material_conversation;
+            }
+        }
+
+        private int getCollapsedCallLayoutResource() {
+            if (Flags.notificationsRedesignTemplates()) {
+                return R.layout.notification_2025_template_collapsed_call;
+            } else {
+                return R.layout.notification_template_material_call;
+            }
+        }
+
+        private int getExpandedCallLayoutResource() {
+            if (Flags.notificationsRedesignTemplates()) {
+                return R.layout.notification_2025_template_expanded_call;
+            } else {
+                return R.layout.notification_template_material_big_call;
+            }
         }
 
         private int getProgressLayoutResource() {
-            return R.layout.notification_template_material_progress;
+            if (Flags.notificationsRedesignTemplates()) {
+                return R.layout.notification_2025_template_expanded_progress;
+            } else {
+                return R.layout.notification_template_material_progress;
+            }
         }
 
         private int getActionLayoutResource() {
@@ -8033,7 +8095,7 @@
         protected Builder mBuilder;
 
         /**
-         * Overrides ContentTitle in the big form of the template.
+         * Overrides ContentTitle in the expanded form of the template.
          * This defaults to the value passed to setContentTitle().
          */
         protected void internalSetBigContentTitle(CharSequence title) {
@@ -8041,7 +8103,7 @@
         }
 
         /**
-         * Set the first line of text after the detail section in the big form of the template.
+         * Set the first line of text after the detail section in the expanded form of the template.
          */
         protected void internalSetSummaryText(CharSequence cs) {
             mSummaryText = cs;
@@ -8104,10 +8166,10 @@
         }
 
         /**
-         * Construct a Style-specific RemoteViews for the final big notification layout.
+         * Construct a Style-specific RemoteViews for the final expanded notification layout.
          * @hide
          */
-        public RemoteViews makeBigContentView() {
+        public RemoteViews makeExpandedContentView() {
             return null;
         }
 
@@ -8271,7 +8333,7 @@
         }
 
         /**
-         * Overrides ContentTitle in the big form of the template.
+         * Overrides ContentTitle in the expanded form of the template.
          * This defaults to the value passed to setContentTitle().
          */
         @NonNull
@@ -8281,7 +8343,7 @@
         }
 
         /**
-         * Set the first line of text after the detail section in the big form of the template.
+         * Set the first line of text after the detail section in the expanded form of the template.
          */
         @NonNull
         public BigPictureStyle setSummaryText(@Nullable CharSequence cs) {
@@ -8340,7 +8402,7 @@
         }
 
         /**
-         * Override the large icon when the big notification is shown.
+         * Override the large icon when the expanded notification is shown.
          */
         @NonNull
         public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) {
@@ -8348,7 +8410,7 @@
         }
 
         /**
-         * Override the large icon when the big notification is shown.
+         * Override the large icon when the expanded notification is shown.
          */
         @NonNull
         public BigPictureStyle bigLargeIcon(@Nullable Icon icon) {
@@ -8412,7 +8474,7 @@
                     .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
                     .fillTextsFrom(mBuilder)
                     .promotedPicture(mPictureIcon);
-            return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
+            return getStandardView(mBuilder.getCollapsedBaseLayoutResource(), p, null /* result */);
         }
 
         /**
@@ -8434,7 +8496,7 @@
         /**
          * @hide
          */
-        public RemoteViews makeBigContentView() {
+        public RemoteViews makeExpandedContentView() {
             // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
             // This covers the following cases:
             //   1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
@@ -8453,7 +8515,7 @@
             }
 
             StandardTemplateParams p = mBuilder.mParams.reset()
-                    .viewType(StandardTemplateParams.VIEW_TYPE_BIG).fillTextsFrom(mBuilder);
+                    .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED).fillTextsFrom(mBuilder);
             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(),
                     p, null /* result */);
             if (mSummaryTextSet) {
@@ -8600,7 +8662,7 @@
         }
 
         /**
-         * Overrides ContentTitle in the big form of the template.
+         * Overrides ContentTitle in the expanded form of the template.
          * This defaults to the value passed to setContentTitle().
          */
         public BigTextStyle setBigContentTitle(CharSequence title) {
@@ -8609,7 +8671,7 @@
         }
 
         /**
-         * Set the first line of text after the detail section in the big form of the template.
+         * Set the first line of text after the detail section in the expanded form of the template.
          */
         public BigTextStyle setSummaryText(CharSequence cs) {
             internalSetSummaryText(safeCharSequence(cs));
@@ -8617,7 +8679,7 @@
         }
 
         /**
-         * Provide the longer text to be displayed in the big form of the
+         * Provide the longer text to be displayed in the expanded form of the
          * template in place of the content text.
          */
         public BigTextStyle bigText(CharSequence cs) {
@@ -8661,7 +8723,7 @@
             if (increasedHeight) {
                 ArrayList<Action> originalActions = mBuilder.mActions;
                 mBuilder.mActions = new ArrayList<>();
-                RemoteViews remoteViews = makeBigContentView();
+                RemoteViews remoteViews = makeExpandedContentView();
                 mBuilder.mActions = originalActions;
                 return remoteViews;
             }
@@ -8675,7 +8737,7 @@
         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
             if (increasedHeight && mBuilder.mActions.size() > 0) {
                 // TODO(b/163626038): pass VIEW_TYPE_HEADS_UP?
-                return makeBigContentView();
+                return makeExpandedContentView();
             }
             return super.makeHeadsUpContentView(increasedHeight);
         }
@@ -8683,9 +8745,9 @@
         /**
          * @hide
          */
-        public RemoteViews makeBigContentView() {
+        public RemoteViews makeExpandedContentView() {
             StandardTemplateParams p = mBuilder.mParams.reset()
-                    .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+                    .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
                     .allowTextWithProgress(true)
                     .textViewId(R.id.big_text)
                     .fillTextsFrom(mBuilder);
@@ -9357,20 +9419,20 @@
          * @hide
          */
         @Override
-        public RemoteViews makeBigContentView() {
-            return makeMessagingView(StandardTemplateParams.VIEW_TYPE_BIG);
+        public RemoteViews makeExpandedContentView() {
+            return makeMessagingView(StandardTemplateParams.VIEW_TYPE_EXPANDED);
         }
 
         /**
          * Create a messaging layout.
          *
-         * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_BIG,
+         * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_EXPANDEDIG,
          *                VIEW_TYPE_HEADS_UP
          * @return the created remoteView.
          */
         @NonNull
         private RemoteViews makeMessagingView(int viewType) {
-            boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_BIG;
+            boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_EXPANDED;
             boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL;
             boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
             boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
@@ -9414,8 +9476,8 @@
                     isConversationLayout
                             ? mBuilder.getConversationLayoutResource()
                             : isCollapsed
-                                    ? mBuilder.getMessagingLayoutResource()
-                                    : mBuilder.getBigMessagingLayoutResource(),
+                                    ? mBuilder.getCollapsedMessagingLayoutResource()
+                                    : mBuilder.getExpandedMessagingLayoutResource(),
                     p,
                     bindResult);
             if (isConversationLayout) {
@@ -10038,7 +10100,7 @@
         }
 
         /**
-         * Overrides ContentTitle in the big form of the template.
+         * Overrides ContentTitle in the expanded form of the template.
          * This defaults to the value passed to setContentTitle().
          */
         public InboxStyle setBigContentTitle(CharSequence title) {
@@ -10047,7 +10109,7 @@
         }
 
         /**
-         * Set the first line of text after the detail section in the big form of the template.
+         * Set the first line of text after the detail section in the expanded form of the template.
          */
         public InboxStyle setSummaryText(CharSequence cs) {
             internalSetSummaryText(safeCharSequence(cs));
@@ -10095,9 +10157,9 @@
         /**
          * @hide
          */
-        public RemoteViews makeBigContentView() {
+        public RemoteViews makeExpandedContentView() {
             StandardTemplateParams p = mBuilder.mParams.reset()
-                    .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+                    .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
                     .fillTextsFrom(mBuilder).text(null);
             TemplateBindResult result = new TemplateBindResult();
             RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result);
@@ -10352,8 +10414,8 @@
          * @hide
          */
         @Override
-        public RemoteViews makeBigContentView() {
-            return makeMediaBigContentView(null /* customContent */);
+        public RemoteViews makeExpandedContentView() {
+            return makeMediaExpandedContentView(null /* customContent */);
         }
 
         /**
@@ -10468,7 +10530,7 @@
                     .fillTextsFrom(mBuilder);
             TemplateBindResult result = new TemplateBindResult();
             RemoteViews template = mBuilder.applyStandardTemplate(
-                    R.layout.notification_template_material_media, p,
+                    mBuilder.getCollapsedMediaLayoutResource(), p,
                     null /* result */);
 
             for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
@@ -10489,15 +10551,15 @@
         }
 
         /** @hide */
-        protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) {
+        protected RemoteViews makeMediaExpandedContentView(@Nullable RemoteViews customContent) {
             final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
             StandardTemplateParams p = mBuilder.mParams.reset()
-                    .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+                    .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
                     .hideProgress(true)
                     .fillTextsFrom(mBuilder);
             TemplateBindResult result = new TemplateBindResult();
             RemoteViews template = mBuilder.applyStandardTemplate(
-                    R.layout.notification_template_material_big_media, p , result);
+                    mBuilder.getExpandedMediaLayoutResource(), p , result);
 
             for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
                 if (i < actionCount) {
@@ -10798,8 +10860,8 @@
         /**
          * @hide
          */
-        public RemoteViews makeBigContentView() {
-            return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG);
+        public RemoteViews makeExpandedContentView() {
+            return makeCallLayout(StandardTemplateParams.VIEW_TYPE_EXPANDED);
         }
 
         @NonNull
@@ -10920,10 +10982,10 @@
             final RemoteViews contentView;
             if (isCollapsed) {
                 contentView = mBuilder.applyStandardTemplate(
-                        R.layout.notification_template_material_call, p, null /* result */);
+                        mBuilder.getCollapsedCallLayoutResource(), p, null /* result */);
             } else {
                 contentView = mBuilder.applyStandardTemplateWithActions(
-                        R.layout.notification_template_material_big_call, p, null /* result */);
+                    mBuilder.getExpandedCallLayoutResource(), p, null /* result */);
             }
 
             // Bind some extra conversation-specific header fields.
@@ -11545,7 +11607,7 @@
                     .hideProgress(true)
                     .fillTextsFrom(mBuilder);
 
-            return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
+            return getStandardView(mBuilder.getCollapsedBaseLayoutResource(), p, null /* result */);
         }
         /**
          * @hide
@@ -11563,9 +11625,9 @@
          * @hide
          */
         @Override
-        public RemoteViews makeBigContentView() {
+        public RemoteViews makeExpandedContentView() {
             StandardTemplateParams p = mBuilder.mParams.reset()
-                    .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+                    .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
                     .allowTextWithProgress(true)
                     .hideProgress(true)
                     .fillTextsFrom(mBuilder);
@@ -12009,8 +12071,8 @@
          * @hide
          */
         @Override
-        public RemoteViews makeBigContentView() {
-            return makeDecoratedBigContentView();
+        public RemoteViews makeExpandedContentView() {
+            return makeDecoratedExpandedContentView();
         }
 
         /**
@@ -12053,13 +12115,13 @@
                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
                     .fillTextsFrom(mBuilder);
             RemoteViews remoteViews = mBuilder.applyStandardTemplate(
-                    mBuilder.getBaseLayoutResource(), p, result);
+                    mBuilder.getCollapsedBaseLayoutResource(), p, result);
             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent,
                     p, result);
             return remoteViews;
         }
 
-        private RemoteViews makeDecoratedBigContentView() {
+        private RemoteViews makeDecoratedExpandedContentView() {
             RemoteViews bigContentView = mBuilder.mN.bigContentView == null
                     ? mBuilder.mN.contentView
                     : mBuilder.mN.bigContentView;
@@ -12068,11 +12130,11 @@
             }
             TemplateBindResult result = new TemplateBindResult();
             StandardTemplateParams p = mBuilder.mParams.reset()
-                    .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+                    .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
                     .decorationType(StandardTemplateParams.DECORATION_PARTIAL)
                     .fillTextsFrom(mBuilder);
             RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
-                    mBuilder.getBigBaseLayoutResource(), p, result);
+                    mBuilder.getExpandedBaseLayoutResource(), p, result);
             buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView,
                     p, result);
             return remoteViews;
@@ -12145,11 +12207,11 @@
          * @hide
          */
         @Override
-        public RemoteViews makeBigContentView() {
+        public RemoteViews makeExpandedContentView() {
             RemoteViews customContent = mBuilder.mN.bigContentView != null
                     ? mBuilder.mN.bigContentView
                     : mBuilder.mN.contentView;
-            return makeMediaBigContentView(customContent);
+            return makeMediaExpandedContentView(customContent);
         }
 
         /**
@@ -12160,7 +12222,7 @@
             RemoteViews customContent = mBuilder.mN.headsUpContentView != null
                     ? mBuilder.mN.headsUpContentView
                     : mBuilder.mN.contentView;
-            return makeMediaBigContentView(customContent);
+            return makeMediaExpandedContentView(customContent);
         }
 
         /**
@@ -14486,7 +14548,7 @@
 
         public static int VIEW_TYPE_UNSPECIFIED = 0;
         public static int VIEW_TYPE_NORMAL = 1;
-        public static int VIEW_TYPE_BIG = 2;
+        public static int VIEW_TYPE_EXPANDED = 2;
         public static int VIEW_TYPE_HEADS_UP = 3;
         public static int VIEW_TYPE_MINIMIZED = 4;    // header only for minimized state
         public static int VIEW_TYPE_PUBLIC = 5;       // header only for automatic public version
@@ -14774,12 +14836,23 @@
                 } else {
                     mBackgroundColor = rawColor;
                 }
-                mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
-                        ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode),
-                        mBackgroundColor, 4.5);
-                mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
-                        ContrastColorUtil.resolveSecondaryColor(ctx, mBackgroundColor, nightMode),
-                        mBackgroundColor, 4.5);
+                if (Flags.uiRichOngoing()) {
+                    boolean isBgDark = Notification.Builder.isColorDark(mBackgroundColor);
+                    int onSurfaceColorExtreme = isBgDark ? Color.WHITE : Color.BLACK;
+                    mPrimaryTextColor = ContrastColorUtil.ensureContrast(
+                            ColorUtils.blendARGB(mBackgroundColor, onSurfaceColorExtreme, 0.9f),
+                            mBackgroundColor, isBgDark, 4.5);
+                    mSecondaryTextColor = ContrastColorUtil.ensureContrast(
+                            ColorUtils.blendARGB(mBackgroundColor, onSurfaceColorExtreme, 0.8f),
+                            mBackgroundColor, isBgDark, 4.5);
+                } else {
+                    mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
+                            ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode),
+                            mBackgroundColor, 4.5);
+                    mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
+                            ContrastColorUtil.resolveSecondaryColor(ctx,
+                                    mBackgroundColor, nightMode), mBackgroundColor, 4.5);
+                }
                 mContrastColor = mPrimaryTextColor;
                 mPrimaryAccentColor = mPrimaryTextColor;
                 mSecondaryAccentColor = mSecondaryTextColor;
diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java
index bcae22a..0348b6d 100644
--- a/core/java/android/app/ProfilerInfo.java
+++ b/core/java/android/app/ProfilerInfo.java
@@ -32,6 +32,12 @@
  * {@hide}
  */
 public class ProfilerInfo implements Parcelable {
+    // Regular profiling which provides different modes of profiling at some performance cost.
+    public static final int PROFILE_TYPE_REGULAR = 0;
+
+    // Low overhead profiling that captures a simple sliding window of past events.
+    public static final int PROFILE_TYPE_LOW_OVERHEAD = 1;
+
     // Version of the profiler output
     public static final int OUTPUT_VERSION_DEFAULT = 1;
     // CLOCK_TYPE_DEFAULT chooses the default used by ART. ART uses CLOCK_TYPE_DUAL by default (see
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index e218418..3973c58 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -61,6 +61,8 @@
 import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 
 /**
@@ -680,12 +682,17 @@
         @GuardedBy("mLock")
         private boolean mTestMode = false;
 
-        /**
-         * The local value of the handler, used during testing but also used directly by the
-         * NonceLocal handler.
-         */
+        // This is the local value of the nonce, as last set by the NonceHandler.  It is always
+        // updated by the setNonce() operation.  The getNonce() operation returns this value in
+        // NonceLocal handlers and handlers in test mode.
         @GuardedBy("mLock")
-        protected long mTestNonce = NONCE_UNSET;
+        protected long mShadowNonce = NONCE_UNSET;
+
+        // A list of watchers to be notified of changes.  This is null until at least one watcher
+        // registers.  Checking for null is meant to be the fastest way the handler can determine
+        // that there are no watchers to be notified.
+        @GuardedBy("mLock")
+        private ArrayList<Semaphore> mWatchers;
 
         /**
          * The methods to get and set a nonce from whatever storage is being used.  mLock may be
@@ -701,27 +708,60 @@
 
         /**
          * Get a nonce from storage.  If the handler is in test mode, the nonce is returned from
-         * the local mTestNonce.
+         * the local mShadowNonce.
          */
         long getNonce() {
             synchronized (mLock) {
-                if (mTestMode) return mTestNonce;
+                if (mTestMode) return mShadowNonce;
             }
             return getNonceInternal();
         }
 
         /**
-         * Write a nonce to storage.  If the handler is in test mode, the nonce is written to the
-         * local mTestNonce and storage is not affected.
+         * Write a nonce to storage.  The nonce is always written to the local mShadowNonce.  If
+         * the handler is not in test mode the nonce is also written to storage.
          */
         void setNonce(long val) {
             synchronized (mLock) {
-                if (mTestMode) {
-                    mTestNonce = val;
-                    return;
+                mShadowNonce = val;
+                if (!mTestMode) {
+                    setNonceInternal(val);
+                }
+                wakeAllWatchersLocked();
+            }
+        }
+
+        @GuardedBy("mLock")
+        private void wakeAllWatchersLocked() {
+            if (mWatchers != null) {
+                for (int i = 0; i < mWatchers.size(); i++) {
+                    mWatchers.get(i).release();
                 }
             }
-            setNonceInternal(val);
+        }
+
+        /**
+         * Register a watcher to be notified when a nonce changes.  There is no check for
+         * duplicates.  In general, this method is called only from {@link NonceWatcher}.
+         */
+        void registerWatcher(Semaphore s) {
+            synchronized (mLock) {
+                if (mWatchers == null) {
+                    mWatchers = new ArrayList<>();
+                }
+                mWatchers.add(s);
+            }
+        }
+
+        /**
+         * Unregister a watcher.  Nothing happens if the watcher is not registered.
+         */
+        void unregisterWatcher(Semaphore s) {
+            synchronized (mLock) {
+                if (mWatchers != null) {
+                    mWatchers.remove(s);
+                }
+            }
         }
 
         /**
@@ -854,7 +894,7 @@
         void setTestMode(boolean mode) {
             synchronized (mLock) {
                 mTestMode = mode;
-                mTestNonce = NONCE_UNSET;
+                mShadowNonce = NONCE_UNSET;
             }
         }
 
@@ -1028,7 +1068,7 @@
     /**
      * SystemProperties and shared storage are protected and cannot be written by random
      * processes.  So, for testing purposes, the NonceLocal handler stores the nonce locally.  The
-     * NonceLocal uses the mTestNonce in the superclass, regardless of test mode.
+     * NonceLocal uses the mShadowNonce in the superclass, regardless of test mode.
      */
     private static class NonceLocal extends NonceHandler {
         // The saved nonce.
@@ -1040,16 +1080,130 @@
 
         @Override
         long getNonceInternal() {
-            return mTestNonce;
+            return mShadowNonce;
         }
 
         @Override
         void setNonceInternal(long value) {
-            mTestNonce = value;
+            mShadowNonce = value;
         }
     }
 
     /**
+     * A NonceWatcher lets an external client test if a nonce value has changed from the last time
+     * the watcher was checked.
+     * @hide
+     */
+    public static class NonceWatcher implements AutoCloseable {
+        // The handler for the key.
+        private final NonceHandler mHandler;
+
+        // The last-seen value.  This is initialized to "unset".
+        private long mLastSeen = NONCE_UNSET;
+
+        // The semaphore that the watcher waits on.  A permit is released every time the nonce
+        // changes.  Permits are acquired in the wait method.
+        private final Semaphore mSem = new Semaphore(0);
+
+        /**
+         * Create a watcher for a handler.  The last-seen value is not set here and will be
+         * "unset".  Therefore, a call to isChanged() will return true if the nonce has ever been
+         * set, no matter when the watcher is first created.  Clients may want to flush that
+         * change by calling isChanged() immediately after constructing the object.
+         */
+        private NonceWatcher(@NonNull NonceHandler handler) {
+            mHandler = handler;
+            mHandler.registerWatcher(mSem);
+        }
+
+        /**
+         * Unregister to be notified when a nonce changes.  NonceHandler allows a call to
+         * unregisterWatcher with a semaphore that is not registered, so there is no check inside
+         * this method to guard against multiple closures.
+         */
+        @Override
+        public void close() {
+            mHandler.unregisterWatcher(mSem);
+        }
+
+        /**
+         * Return the last seen value of the nonce.  This does not update that value.  Only
+         * {@link #isChanged()} updates the value.
+         */
+        public long lastSeen() {
+            return mLastSeen;
+        }
+
+        /**
+         * Return true if the nonce has changed from the last time isChanged() was called.  The
+         * method is not thread safe.
+         * @hide
+         */
+        public boolean isChanged() {
+            long current = mHandler.getNonce();
+            if (current != mLastSeen) {
+                mLastSeen = current;
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Wait for the nonce value to change.  It is not guaranteed that the nonce has changed when
+         * this returns: clients must confirm with {@link #isChanged}. The wait operation is only
+         * effective in a process that writes the nonces.  The function returns the number of times
+         * the nonce had changed since the last call to the method.
+         * @hide
+         */
+        public int waitForChange() throws InterruptedException {
+            mSem.acquire(1);
+            return 1 + mSem.drainPermits();
+        }
+
+        /**
+         * Wait for the nonce value to change.  It is not guaranteed that the nonce has changed when
+         * this returns: clients must confirm with {@link #isChanged}. The wait operation is only
+         * effective in a process that writes the nonces.  The function returns the number of times
+         * the nonce changed since the last call to the method.  A return value of zero means the
+         * timeout expired.  Beware that a timeout of 0 means the function will not wait at all.
+         * @hide
+         */
+        public int waitForChange(long timeout, TimeUnit timeUnit) throws InterruptedException {
+            if (mSem.tryAcquire(1, timeout, timeUnit)) {
+                return 1 + mSem.drainPermits();
+            } else {
+                return 0;
+            }
+        }
+
+        /**
+         * Wake the watcher by releasing the semaphore.  This can be used to wake clients that are
+         * blocked in {@link #waitForChange} without affecting the underlying nonce.
+         * @hide
+         */
+        public void wakeUp() {
+            mSem.release();
+        }
+    }
+
+    /**
+     * Return a NonceWatcher for the cache.
+     * @hide
+     */
+    public NonceWatcher getNonceWatcher() {
+        return new NonceWatcher(mNonce);
+    }
+
+    /**
+     * Return a NonceWatcher for the given property.  If a handler does not exist for the
+     * property, one is created.  This throws if the property name is not a valid cache key.
+     * @hide
+     */
+    public static NonceWatcher getNonceWatcher(@NonNull String propertyName) {
+        return new NonceWatcher(getNonceHandler(propertyName));
+    }
+
+    /**
      * Complete key prefixes.
      */
     private static final String PREFIX_TEST = CACHE_KEY_PREFIX + "." + MODULE_TEST + ".";
@@ -1663,6 +1817,26 @@
     }
 
     /**
+     * Non-static version of corkInvalidations() for situations in which the cache instance is
+     * available.  This is slightly faster than than the static versions because it does not have
+     * to look up the NonceHandler for a given property name.
+     * @hide
+     */
+    public void corkInvalidations() {
+        mNonce.cork();
+    }
+
+    /**
+     * Non-static version of uncorkInvalidations() for situations in which the cache instance is
+     * available.  This is slightly faster than than the static versions because it does not have
+     * to look up the NonceHandler for a given property name.
+     * @hide
+     */
+    public void uncorkInvalidations() {
+        mNonce.uncork();
+    }
+
+    /**
      * Invalidate caches in all processes that are keyed for the module and api.
      * @hide
      */
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 5ed1f4e..637187e 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -177,6 +177,10 @@
         {
             "file_patterns": ["(/|^)AppOpsManager.java"],
             "name": "CtsAppOpsTestCases"
+        },
+        {
+            "file_patterns": ["(/|^)BroadcastStickyCache.java"],
+            "name": "BroadcastUnitTests"
         }
     ]
 }
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 36f61fd..b9b582a 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -198,6 +198,10 @@
     }
 
     @Override
+    public void onRecentTaskRemovedForAddTask(int taskId) {
+    }
+
+    @Override
     public void onTaskFocusChanged(int taskId, boolean focused) {
     }
 
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index abb2dd4..89e25e7 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2092,7 +2092,7 @@
 
     /**
      * Returns the description of the designated wallpaper. Returns null if the lock screen
-     * wallpaper is requested lock screen wallpaper is not set.
+     * wallpaper is requested and lock screen wallpaper is not set.
 
      * @param which Specifies wallpaper to request (home or lock).
      * @throws IllegalArgumentException if {@code which} is not exactly one of
@@ -2491,6 +2491,27 @@
         return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);
     }
 
+    /**
+     * Version of setBitmap that allows specification of wallpaper metadata including how the
+     * wallpaper will be positioned for different display sizes.
+     *
+     * @param fullImage   A bitmap that will supply the wallpaper imagery.
+     * @param description Wallpaper metadata including desired cropping
+     * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper
+     *                    image for restore to a future device; {@code false} otherwise.
+     * @param which       Flags indicating which wallpaper(s) to configure with the new imagery.
+     * @hide
+     */
+    @FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+    public int setBitmapWithDescription(@Nullable Bitmap fullImage,
+            @NonNull WallpaperDescription description, boolean allowBackup,
+            @SetWallpaperFlags int which) throws IOException {
+        return setBitmapWithCrops(fullImage, description.getCropHints(), allowBackup, which,
+                mContext.getUserId());
+    }
+
     private final void validateRect(Rect rect) {
         if (rect != null && rect.isEmpty()) {
             throw new IllegalArgumentException("visibleCrop rectangle must be valid and non-empty");
@@ -2700,6 +2721,27 @@
     }
 
     /**
+     * Version of setStream that allows specification of wallpaper metadata including how the
+     * wallpaper will be positioned for different display sizes.
+     *
+     * @param bitmapData  A stream containing the raw data to install as a wallpaper. This
+     *                    data can be in any format handled by {@link BitmapRegionDecoder}.
+     * @param description Wallpaper metadata including desired cropping
+     * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper
+     *                    image for restore to a future device; {@code false} otherwise.
+     * @param which       Flags indicating which wallpaper(s) to configure with the new imagery.
+     * @hide
+     */
+    @FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
+    public int setStreamWithDescription(@NonNull InputStream bitmapData,
+            @NonNull WallpaperDescription description, boolean allowBackup,
+            @SetWallpaperFlags int which) throws IOException {
+        return setStreamWithCrops(bitmapData, description.getCropHints(), allowBackup, which);
+    }
+
+    /**
      * Return whether any users are currently set to use the wallpaper
      * with the given resource ID.  That is, their wallpaper has been
      * set through {@link #setResource(int)} with the same resource id.
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 1f31ab5..44940ae 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -164,4 +164,5 @@
      name: "app_start_info_component"
      description: "Control ApplicationStartInfo component field and API"
      bug: "362537357"
+     is_exported: true
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 42fa9e7..8372078 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4177,6 +4177,35 @@
         }
     }
 
+
+    /**
+     * Similar to the public variant of {@link #setMtePolicy} but for use by the system.
+     *
+     * <p>Called by a system service only, meaning that the caller's UID must be equal to
+     * {@link Process#SYSTEM_UID}.
+     *
+     * @throws SecurityException if caller is not permitted to set Mte policy
+     * @throws UnsupportedOperationException if the device does not support MTE
+     * @param systemEntity  The service entity that adds the restriction. A user restriction set by
+     *                       a service entity can only be cleared by the same entity. This can be
+     *                       just the calling package name, or any string of the caller's choice
+     *                       can be used.
+     * @param policy the MTE policy to be set
+     * @hide
+     */
+    public void setMtePolicy(@NonNull String systemEntity, @MtePolicy int policy) {
+        throwIfParentInstance("setMtePolicyForUser");
+        if (mService != null) {
+            try {
+                mService.setMtePolicyBySystem(systemEntity, policy);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+
+
     /**
      * Called by a device owner, profile owner of an organization-owned device to
      * get the Memory Tagging Extension (MTE) policy
@@ -12457,6 +12486,36 @@
     }
 
     /**
+     * Returns the {@link EnforcingAdmin} who have set this policy.
+     *
+     * <p>Important: this API is a temporary solution, hence should be kept hidden. That is because
+     * the string argument can't define policies with arguments.
+     *
+     * <p>Note that for {@link #POLICY_SUSPEND_PACKAGES} it returns the PO or DO to keep the
+     * behavior the same as before the bug fix for b/192245204.
+     *
+     * <p>This API is only callable by the system UID
+     *
+     * @param userId     The user for whom to retrieve the information.
+     * @param identifier The policy enforced by admins. It could be any user restriction or
+     *                   policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+     *                   {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}. This also works
+     *                   for {@link DevicePolicyIdentifiers#MEMORY_TAGGING_POLICY}.
+     *
+     * @hide
+     */
+    public @Nullable EnforcingAdmin getEnforcingAdmin(int userId, String identifier) {
+        if (mService != null) {
+            try {
+                return mService.getEnforcingAdmin(userId, identifier);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns the list of {@link EnforcingAdmin}s who have set this restriction.
      *
      * <p>Note that for {@link #POLICY_SUSPEND_PACKAGES} it returns the PO or DO to keep the
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d048b53..03a9f99 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -279,8 +279,9 @@
     boolean isNotificationListenerServicePermitted(in String packageName, int userId);
 
     Intent createAdminSupportIntent(in String restriction);
-    Bundle getEnforcingAdminAndUserDetails(int userId,String restriction);
-    List<EnforcingAdmin> getEnforcingAdminsForRestriction(int userId,String restriction);
+    Bundle getEnforcingAdminAndUserDetails(int userId, String restriction);
+    EnforcingAdmin getEnforcingAdmin(int userId, String identifier);
+    List<EnforcingAdmin> getEnforcingAdminsForRestriction(int userId, String restriction);
     boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden, boolean parent);
     boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean parent);
 
@@ -617,6 +618,7 @@
     int[] getApplicationExemptions(String packageName);
 
     void setMtePolicy(int flag, String callerPackageName);
+    void setMtePolicyBySystem(in String systemEntity, int policy);
     int getMtePolicy(String callerPackageName);
 
     void setManagedSubscriptionsPolicy(in ManagedSubscriptionsPolicy policy);
diff --git a/core/java/android/app/admin/UnknownAuthority.java b/core/java/android/app/admin/UnknownAuthority.java
index fdad898..bebffde 100644
--- a/core/java/android/app/admin/UnknownAuthority.java
+++ b/core/java/android/app/admin/UnknownAuthority.java
@@ -22,6 +22,8 @@
 import android.annotation.TestApi;
 import android.os.Parcel;
 
+import java.util.Objects;
+
 /**
  * Class used to identify a default value for the authority of the {@link EnforcingAdmin} setting
  * a policy, meaning it is not one of the other known subclasses of {@link Authority}, this would be
@@ -31,6 +33,7 @@
  */
 @SystemApi
 public final class UnknownAuthority extends Authority {
+    private final String mName;
 
     /**
      * Object representing an unknown authority.
@@ -45,22 +48,40 @@
      * Creates an authority that represents an admin that can set a policy but
      * doesn't have a known authority (e.g. a system components).
      */
-    public UnknownAuthority() {}
+    public UnknownAuthority() {
+        mName = null;
+    }
+
+    /** @hide */
+    public UnknownAuthority(String name) {
+        mName = name;
+    }
+
+    private UnknownAuthority(Parcel source) {
+        this(source.readString8());
+    }
+
+    /** @hide */
+    public String getName() {
+        return mName;
+    }
 
     @Override
     public String toString() {
-        return "DefaultAuthority {}";
+        return "DefaultAuthority {" + mName + "}";
     }
 
     @Override
     public boolean equals(@Nullable Object o) {
         if (this == o) return true;
-        return o != null && getClass() == o.getClass();
+        if (o == null || getClass() != o.getClass()) return false;
+        UnknownAuthority other = (UnknownAuthority) o;
+        return Objects.equals(mName, other.mName);
     }
 
     @Override
     public int hashCode() {
-        return 0;
+        return Objects.hashCode(mName);
     }
 
     @Override
@@ -69,14 +90,16 @@
     }
 
     @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {}
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mName);
+    }
 
     @NonNull
     public static final Creator<UnknownAuthority> CREATOR =
             new Creator<UnknownAuthority>() {
                 @Override
                 public UnknownAuthority createFromParcel(Parcel source) {
-                    return UNKNOWN_AUTHORITY;
+                    return new UnknownAuthority(source);
                 }
 
                 @Override
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 581efa5..361ba73 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -286,6 +286,16 @@
 }
 
 flag {
+    name: "unsuspend_not_suspended"
+    namespace: "enterprise"
+    description: "When admin unsuspends packages, pass previously not suspended packages to PM too"
+    bug: "378766314"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "backup_connected_apps_settings"
     namespace: "enterprise"
     description: "backup and restore connected work and personal apps user settings across devices"
@@ -367,6 +377,7 @@
     namespace: "enterprise"
     description: "API that removes a given managed profile."
     bug: "372652841"
+    is_exported: true
 }
 
 flag {
@@ -387,9 +398,11 @@
 
 flag {
   name: "split_create_managed_profile_enabled"
+  is_exported: true
   namespace: "enterprise"
   description: "Split up existing create and provision managed profile API."
   bug: "375382324"
+  is_exported: true
 }
 
 flag {
diff --git a/core/java/android/app/appfunctions/AppFunctionException.java b/core/java/android/app/appfunctions/AppFunctionException.java
index cbd1d93..c8d80d3 100644
--- a/core/java/android/app/appfunctions/AppFunctionException.java
+++ b/core/java/android/app/appfunctions/AppFunctionException.java
@@ -29,7 +29,14 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
-/** Represents an app function related errors. */
+/**
+ * Represents an app function related error.
+ *
+ * <p>This exception may include an {@link AppFunctionException#getExtras() Bundle}
+ * containing additional error-specific metadata.
+ *
+ * <p>The AppFunction SDK can expose structured APIs by packing and unpacking this Bundle.
+ */
 @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
 public final class AppFunctionException extends Exception implements Parcelable {
     /**
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
index 1557815..bdc6ce5 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
@@ -27,7 +27,16 @@
 
 import java.util.Objects;
 
-/** A request to execute an app function. */
+/**
+ * A request to execute an app function.
+ *
+ * <p>The {@link ExecuteAppFunctionRequest#getParameters()} contains the parameters for the function
+ * to be executed in a GenericDocument. Structured classes defined in the AppFunction SDK can be
+ * converted into GenericDocuments.
+ *
+ * <p>The {@link ExecuteAppFunctionRequest#getExtras()} provides any extra metadata for the request.
+ * Structured APIs can be exposed in the SDK by packing and unpacking this Bundle.
+ */
 @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
 public final class ExecuteAppFunctionRequest implements Parcelable {
     @NonNull
@@ -127,6 +136,16 @@
         return mExtras;
     }
 
+    /**
+     * Returns the size of the request in bytes.
+     *
+     * @hide
+     */
+    public int getRequestDataSize() {
+        return mTargetPackageName.getBytes().length + mFunctionIdentifier.getBytes().length
+                + mParameters.getDataSize() + mExtras.getSize();
+    }
+
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString8(mTargetPackageName);
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index acad43b..618cc1c 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -27,7 +27,16 @@
 
 import java.util.Objects;
 
-/** The response to an app function execution. */
+/**
+ * The response to an app function execution.
+ *
+ * <p>The {@link ExecuteAppFunctionResponse#getResultDocument()} contains the function's return
+ * value as a GenericDocument. This can be converted back into a structured class using the
+ * AppFunction SDK.
+ *
+ * <p>The {@link ExecuteAppFunctionResponse#getExtras()} provides any extra metadata returned by the
+ * function. The AppFunction SDK can expose structured APIs by packing and unpacking this Bundle.
+ */
 @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
 public final class ExecuteAppFunctionResponse implements Parcelable {
     @NonNull
@@ -126,6 +135,15 @@
         return mExtras;
     }
 
+    /**
+     * Returns the size of the response in bytes.
+     *
+     * @hide
+     */
+    public int getResponseDataSize() {
+        return mResultDocumentWrapper.getDataSize() + mExtras.getSize();
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/app/appfunctions/GenericDocumentWrapper.java b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
index 541ca74..02133b4 100644
--- a/core/java/android/app/appfunctions/GenericDocumentWrapper.java
+++ b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
@@ -50,6 +50,10 @@
     @Nullable
     private Parcel mParcel;
 
+    @GuardedBy("mLock")
+    @Nullable
+    private Integer mDataSize;
+
     private final Object mLock = new Object();
 
     public static final Creator<GenericDocumentWrapper> CREATOR =
@@ -75,11 +79,13 @@
     public GenericDocumentWrapper(@NonNull GenericDocument genericDocument) {
         mGenericDocument = Objects.requireNonNull(genericDocument);
         mParcel = null;
+        mDataSize = null;
     }
 
     public GenericDocumentWrapper(@NonNull Parcel parcel) {
         mGenericDocument = null;
         mParcel = Objects.requireNonNull(parcel);
+        mDataSize = mParcel.dataSize();
     }
 
     /** Returns the wrapped {@link android.app.appsearch.GenericDocument} */
@@ -109,6 +115,21 @@
         }
     }
 
+    /** Returns the size of the parcelled document. */
+
+    int getDataSize() {
+        synchronized (mLock) {
+            if (mDataSize != null) {
+                return mDataSize;
+            }
+            Parcel tempParcel = Parcel.obtain();
+            writeToParcel(tempParcel, 0);
+            mDataSize = tempParcel.dataSize();
+            tempParcel.recycle();
+            return mDataSize;
+        }
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/app/assist/AssistContent.java b/core/java/android/app/assist/AssistContent.java
index 43a46ba..3e3ca24 100644
--- a/core/java/android/app/assist/AssistContent.java
+++ b/core/java/android/app/assist/AssistContent.java
@@ -30,6 +30,31 @@
     public static final String EXTRA_APP_FUNCTION_DATA =
             "android.app.assist.extra.APP_FUNCTION_DATA";
 
+    /**
+     * This extra can be optionally supplied in the {@link #getExtras} bundle to provide a
+     * {@link Uri} which will be utilized when transitioning a user's session to another surface.
+     *
+     * <p>If provided, instead of using the URI provided in {@link #setWebUri}, the
+     * "Open in browser" feature will use this URI to transition the current session from one
+     * surface to the other. Apps may choose to encode session or user information into this
+     * URI in order to provide a better session transfer experience.
+     *
+     * <p>Unlike {@link #setWebUri}, this URI will not be used for features where the user might
+     * accidentally share it with another user. However, developers should not encode
+     * authentication credentials into this URI, because it will be surfaced in the browser URL
+     * bar and may be copied and shared from there.
+     *
+     * <p>When providing this extra, developers should still continue to provide
+     * {@link #setWebUri} for backwards compatibility with features such as
+     * <a href="https://developer.android.com/guide/components/activities/recents#url-sharing">
+     * recents URL sharing</a> which do not benefit from a session-transfer web URI.
+     *
+     * @see android.app.Activity#requestOpenInBrowserEducation()
+     */
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION)
+    public static final String EXTRA_SESSION_TRANSFER_WEB_URI =
+            "android.app.assist.extra.SESSION_TRANSFER_WEB_URI";
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private boolean mIsAppProvidedIntent = false;
     private boolean mIsAppProvidedWebUri = false;
diff --git a/core/java/android/app/background_install_control_manager.aconfig b/core/java/android/app/background_install_control_manager.aconfig
index d29c5b5..39f10dc 100644
--- a/core/java/android/app/background_install_control_manager.aconfig
+++ b/core/java/android/app/background_install_control_manager.aconfig
@@ -9,3 +9,10 @@
      is_fixed_read_only: true
      bug: "287507984"
 }
+
+flag {
+    name: "background_install_control_callback_api"
+    namespace: "preload_safety"
+    description: "Feature flag to enable the use of push API in background install control service"
+    bug: "369382811"
+}
\ No newline at end of file
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index 5e09517..529b59a 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -6,6 +6,7 @@
   namespace: "machine_learning"
   description: "Flag to enable the service"
   bug: "309689654"
+  is_exported: true
 }
 flag {
   name: "enable_token_refresh"
diff --git a/core/java/android/app/jank/flags.aconfig b/core/java/android/app/jank/flags.aconfig
index 5657f7e..a62df1b 100644
--- a/core/java/android/app/jank/flags.aconfig
+++ b/core/java/android/app/jank/flags.aconfig
@@ -6,6 +6,7 @@
   namespace: "system_performance"
   description: "Control the API portion of Detailed Application Jank Metrics"
   bug: "366264614"
+  is_exported: true
 }
 
 flag {
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index d5e696d..d4f82f6 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -16,3 +16,11 @@
   description: "Flag to enable the SupervisionService on Wear devices"
   bug: "373358935"
 }
+
+flag {
+  name: "enable_sync_with_dpm"
+  is_exported: true
+  namespace: "supervision"
+  description: "Flag that enables supervision when the supervision app is the profile owner"
+  bug: "377261590"
+}
diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java
index 4a142bb..ca2d9e6 100644
--- a/core/java/android/app/wallpaper/WallpaperDescription.java
+++ b/core/java/android/app/wallpaper/WallpaperDescription.java
@@ -19,8 +19,15 @@
 import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
 
 import android.annotation.FlaggedApi;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.app.WallpaperManager.ScreenOrientation;
 import android.content.ComponentName;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -29,6 +36,8 @@
 import android.text.Spanned;
 import android.text.SpannedString;
 import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -43,6 +52,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -71,12 +81,15 @@
     @Nullable private final Uri mContextUri;
     @Nullable private final CharSequence mContextDescription;
     @NonNull private final PersistableBundle mContent;
+    @NonNull private final SparseArray<Rect> mCropHints;
+    private final float mSampleSize;
 
     private WallpaperDescription(@Nullable ComponentName component,
             @Nullable String id, @Nullable Uri thumbnail, @Nullable CharSequence title,
             @Nullable List<CharSequence> description, @Nullable Uri contextUri,
             @Nullable CharSequence contextDescription,
-            @Nullable PersistableBundle content) {
+            @Nullable PersistableBundle content, @NonNull SparseArray<Rect> cropHints,
+            float sampleSize) {
         this.mComponent = component;
         this.mId = id;
         this.mThumbnail = thumbnail;
@@ -85,6 +98,8 @@
         this.mContextUri = contextUri;
         this.mContextDescription = contextDescription;
         this.mContent = (content != null) ? content : new PersistableBundle();
+        this.mCropHints = cropHints;
+        this.mSampleSize = sampleSize;
     }
 
     /** @return the component for this wallpaper, or {@code null} for a static wallpaper */
@@ -134,6 +149,25 @@
         return mContent;
     }
 
+    /**
+     * @return the cropping for the current image as described in
+     * {@link Builder#setCropHints(SparseArray)}
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public SparseArray<Rect> getCropHints() {
+        return mCropHints;
+    }
+
+    /**
+     * @return the subsamling size as described in {@link Builder#setSampleSize(float)}.
+     * @hide
+     */
+    public float getSampleSize() {
+        return mSampleSize;
+    }
+
     ////// Comparison overrides
 
     @Override
@@ -163,9 +197,23 @@
         if (mContextDescription != null) {
             out.attribute(null, "contextdescription", toHtml(mContextDescription));
         }
+
+        for (Pair<Integer, String> pair : screenDimensionPairs()) {
+            @ScreenOrientation int orientation = pair.first;
+            String attrName = pair.second;
+            Rect cropHint = mCropHints.get(orientation);
+            if (cropHint == null) continue;
+            out.attributeInt(null, "cropLeft" + attrName, cropHint.left);
+            out.attributeInt(null, "cropTop" + attrName, cropHint.top);
+            out.attributeInt(null, "cropRight" + attrName, cropHint.right);
+            out.attributeInt(null, "cropBottom" + attrName, cropHint.bottom);
+        }
+        out.attributeFloat(null, "sampleSize", mSampleSize);
+
         out.startTag(null, XML_TAG_DESCRIPTION);
         for (CharSequence s : mDescription) out.attribute(null, "descriptionline", toHtml(s));
         out.endTag(null, XML_TAG_DESCRIPTION);
+
         try {
             out.startTag(null, XML_TAG_CONTENT);
             mContent.saveToXml(out);
@@ -194,6 +242,19 @@
         CharSequence contextDescription = fromHtml(
                 in.getAttributeValue(null, "contextdescription"));
 
+        SparseArray<Rect> cropHints = new SparseArray<>();
+        screenDimensionPairs().forEach(pair -> {
+            @ScreenOrientation int orientation = pair.first;
+            String attrName = pair.second;
+            Rect crop = new Rect(
+                    in.getAttributeInt(null, "cropLeft" + attrName, 0),
+                    in.getAttributeInt(null, "cropTop" + attrName, 0),
+                    in.getAttributeInt(null, "cropRight" + attrName, 0),
+                    in.getAttributeInt(null, "cropBottom" + attrName, 0));
+            if (!crop.isEmpty()) cropHints.put(orientation, crop);
+        });
+        float sampleSize = in.getAttributeFloat(null, "sampleSize", 1f);
+
         List<CharSequence> description = new ArrayList<>();
         PersistableBundle content = null;
         int type;
@@ -213,7 +274,7 @@
         }
 
         return new WallpaperDescription(componentName, id, thumbnail, title, description,
-                contextUri, contextDescription, content);
+                contextUri, contextDescription, content, cropHints, sampleSize);
     }
 
     private static String toHtml(@NonNull CharSequence c) {
@@ -253,6 +314,13 @@
         mContextUri = Uri.CREATOR.createFromParcel(in);
         mContextDescription = in.readCharSequence();
         mContent = PersistableBundle.CREATOR.createFromParcel(in);
+        mCropHints = new SparseArray<>();
+        screenDimensionPairs().forEach(pair -> {
+            int orientation = pair.first;
+            Rect crop = in.readTypedObject(Rect.CREATOR);
+            if (crop != null) mCropHints.put(orientation, crop);
+        });
+        mSampleSize = in.readFloat();
     }
 
     @NonNull
@@ -283,6 +351,11 @@
         Uri.writeToParcel(dest, mContextUri);
         dest.writeCharSequence(mContextDescription);
         dest.writePersistableBundle(mContent);
+        screenDimensionPairs().forEach(pair -> {
+            int orientation = pair.first;
+            dest.writeTypedObject(mCropHints.get(orientation), flags);
+        });
+        dest.writeFloat(mSampleSize);
     }
 
     ////// Builder
@@ -293,9 +366,17 @@
      */
     @NonNull
     public Builder toBuilder() {
-        return new Builder().setComponent(mComponent).setId(mId).setThumbnail(mThumbnail).setTitle(
-                mTitle).setDescription(mDescription).setContextUri(
-                mContextUri).setContextDescription(mContextDescription).setContent(mContent);
+        return new Builder()
+                .setComponent(mComponent)
+                .setId(mId)
+                .setThumbnail(mThumbnail)
+                .setTitle(mTitle)
+                .setDescription(mDescription)
+                .setContextUri(mContextUri)
+                .setContextDescription(mContextDescription)
+                .setContent(mContent)
+                .setCropHints(mCropHints)
+                .setSampleSize(mSampleSize);
     }
 
     /** Builder for the immutable {@link WallpaperDescription} class */
@@ -308,6 +389,9 @@
         @Nullable private Uri mContextUri;
         @Nullable private CharSequence mContextDescription;
         @NonNull private PersistableBundle mContent = new PersistableBundle();
+        @NonNull
+        private SparseArray<Rect> mCropHints = new SparseArray<>();
+        private float mSampleSize = 1f;
 
         /** Creates a new, empty {@link Builder}. */
         public Builder() {}
@@ -416,11 +500,69 @@
             return this;
         }
 
+        /**
+         * Defines which part of the source wallpaper image is in the stored crop file.
+         *
+         * @param cropHints map from screen dimensions to a sub-region of the image to display
+         *                  for those dimensions. The {@code Rect} sub-region may have a larger
+         *                  width/height ratio than the screen dimensions to apply a horizontal
+         *                  parallax effect. If the map is empty or some entries are missing, the
+         *                  system will apply a default strategy to position the wallpaper for
+         *                  any unspecified screen dimensions.
+         * @hide
+         */
+        @NonNull
+        @TestApi
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setCropHints(@NonNull Map<Point, Rect> cropHints) {
+            mCropHints = new SparseArray<>();
+            cropHints.forEach(
+                    (point, rect) -> mCropHints.put(WallpaperManager.getOrientation(point), rect));
+            return this;
+        }
+
+        /**
+         * Defines which part of the source wallpaper image is in the stored crop file.
+         *
+         * @param cropHints map from {@link ScreenOrientation} to a sub-region of the image to
+         *                  display for that screen orientation.
+         * @hide
+         */
+        @NonNull
+        @TestApi
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setCropHints(@NonNull SparseArray<Rect> cropHints) {
+            mCropHints = cropHints;
+            return this;
+        }
+
+        /**
+         * How much the crop is sub-sampled. A value > 1 means that the image quality was reduced.
+         * This is the ratio between the cropHint height and the actual stored crop file height.
+         * height.
+         *
+         * @param sampleSize Sub-sampling value
+         * @hide
+         */
+        @NonNull
+        public Builder setSampleSize(float sampleSize) {
+            mSampleSize = sampleSize;
+            return this;
+        }
+
         /** Creates and returns the {@link WallpaperDescription} represented by this builder. */
         @NonNull
         public WallpaperDescription build() {
             return new WallpaperDescription(mComponent, mId, mThumbnail, mTitle, mDescription,
-                    mContextUri, mContextDescription, mContent);
+                    mContextUri, mContextDescription, mContent, mCropHints, mSampleSize);
         }
     }
+
+    private static List<Pair<Integer, String>> screenDimensionPairs() {
+        return List.of(
+                new Pair<>(WallpaperManager.ORIENTATION_PORTRAIT, "Portrait"),
+                new Pair<>(WallpaperManager.ORIENTATION_LANDSCAPE, "Landscape"),
+                new Pair<>(WallpaperManager.ORIENTATION_SQUARE_PORTRAIT, "SquarePortrait"),
+                new Pair<>(WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE, "SquareLandscape"));
+    }
 }
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 8f20ea0..40de298 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -31,6 +31,7 @@
 import android.annotation.UserIdInt;
 import android.app.IServiceConnection;
 import android.app.PendingIntent;
+import android.app.usage.UsageStatsManager;
 import android.appwidget.flags.Flags;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -41,12 +42,14 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -485,6 +488,67 @@
     public static final String ACTION_APPWIDGET_HOST_RESTORED
             = "android.appwidget.action.APPWIDGET_HOST_RESTORED";
 
+    /**
+     * This is the value of {@link UsageStatsManager.EXTRA_EVENT_ACTION} in the event bundle for
+     * widget user interaction events.
+     *
+     * A single widget interaction event describes what user interactions happened during a single
+     * impression of the widget.
+     */
+    @FlaggedApi(Flags.FLAG_ENGAGEMENT_METRICS)
+    public static final String EVENT_TYPE_WIDGET_INTERACTION = "widget_interaction";
+
+    /**
+     * This is the value of {@link UsageStatsManager.EXTRA_EVENT_CATEGORY} in the event bundle for
+     * widget user interaction events.
+     */
+    @FlaggedApi(Flags.FLAG_ENGAGEMENT_METRICS)
+    public static final String EVENT_CATEGORY_APPWIDGET = "android.appwidget";
+
+    /**
+     * This bundle extra describes which views have been clicked during a single impression of the
+     * widget. It is an integer array of view IDs of the clicked views.
+     *
+     * Widget providers may set a different ID for event purposes by setting the
+     * {@link android.R.id.remoteViewsMetricsId} int tag on the view.
+     *
+     * @see android.views.RemoteViews.setIntTag
+     */
+    @FlaggedApi(Flags.FLAG_ENGAGEMENT_METRICS)
+    public static final String EXTRA_EVENT_CLICKED_VIEWS =
+            "android.appwidget.extra.EVENT_CLICKED_VIEWS";
+
+    /**
+     * This bundle extra describes which views have been scrolled during a single impression of the
+     * widget. It is an integer array of view IDs of the scrolled views.
+     *
+     * Widget providers may set a different ID for event purposes by setting the
+     * {@link android.R.id.remoteViewsMetricsId} int tag on the view.
+     *
+     * @see android.views.RemoteViews.setIntTag
+     */
+    @FlaggedApi(Flags.FLAG_ENGAGEMENT_METRICS)
+    public static final String EXTRA_EVENT_SCROLLED_VIEWS =
+            "android.appwidget.extra.EVENT_SCROLLED_VIEWS";
+
+    /**
+     * This bundle extra contains a long that represents the duration of time in milliseconds
+     * during which the widget was visible.
+     */
+    @FlaggedApi(Flags.FLAG_ENGAGEMENT_METRICS)
+    public static final String EXTRA_EVENT_DURATION_MS =
+            "android.appwidget.extra.EVENT_DURATION_MS";
+
+    /**
+     * This bundle extra contains an integer array with 4 elements that describe the left, top,
+     * right, and bottom coordinates of the widget at the end of the interaction event.
+     *
+     * This Rect indicates the current position and size of the widget.
+     */
+    @FlaggedApi(Flags.FLAG_ENGAGEMENT_METRICS)
+    public static final String EXTRA_EVENT_POSITION_RECT =
+            "android.appwidget.extra.EVENT_POSITION_RECT";
+
     private static final String TAG = "AppWidgetManager";
 
     private static Executor sUpdateExecutor;
@@ -1516,6 +1580,39 @@
         }
     }
 
+    /**
+     * Create a {@link PersistableBundle} that represents a single widget interaction event.
+     *
+     * @param appWidgetId App Widget ID of the widget.
+     * @param durationMs Duration of the impression in milliseconds
+     * @param position Current position of the widget.
+     * @param clickedIds IDs of views clicked during this event.
+     * @param scrolledIds IDs of views scrolled during this event.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ENGAGEMENT_METRICS)
+    @NonNull
+    public static PersistableBundle createWidgetInteractionEvent(int appWidgetId, long durationMs,
+            @Nullable Rect position, @Nullable int[] clickedIds, @Nullable int[] scrolledIds) {
+        PersistableBundle extras = new PersistableBundle();
+        extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, EVENT_TYPE_WIDGET_INTERACTION);
+        extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, EVENT_CATEGORY_APPWIDGET);
+        extras.putInt(EXTRA_APPWIDGET_ID, appWidgetId);
+        extras.putLong(EXTRA_EVENT_DURATION_MS, durationMs);
+        if (position != null) {
+            extras.putIntArray(EXTRA_EVENT_POSITION_RECT,
+                    new int[]{position.left, position.top, position.right, position.bottom});
+        }
+        if (clickedIds != null && clickedIds.length > 0) {
+            extras.putIntArray(EXTRA_EVENT_CLICKED_VIEWS, clickedIds);
+        }
+        if (scrolledIds != null && scrolledIds.length > 0) {
+            extras.putIntArray(EXTRA_EVENT_SCROLLED_VIEWS, scrolledIds);
+        }
+        return extras;
+    }
+
 
     @UiThread
     private static @NonNull Executor createUpdateExecutorIfNull() {
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 4499a72..9914ba2 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -105,3 +105,11 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "engagement_metrics"
+  namespace: "app_widgets"
+  description: "Enable collection of widget engagement metrics"
+  bug: "364655296"
+  is_exported: true
+}
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 1249734..2f16115 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -54,8 +54,6 @@
     @NonNull
     private final String mPackageName;
     @Nullable
-    private final String mTag;
-    @Nullable
     private final MacAddress mDeviceMacAddress;
     @Nullable
     private final CharSequence mDisplayName;
@@ -85,6 +83,8 @@
      */
     private final long mLastTimeConnectedMs;
     private final int mSystemDataSyncFlags;
+    @Nullable
+    private final DeviceId mDeviceId;
 
     /**
      * A device icon displayed on a selfManaged association dialog.
@@ -97,11 +97,11 @@
      * @hide
      */
     public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName,
-            @Nullable String tag, @Nullable MacAddress macAddress,
-            @Nullable CharSequence displayName, @Nullable String deviceProfile,
-            @Nullable AssociatedDevice associatedDevice, boolean selfManaged,
-            boolean notifyOnDeviceNearby, boolean revoked, boolean pending, long timeApprovedMs,
-            long lastTimeConnectedMs, int systemDataSyncFlags, @Nullable Icon deviceIcon) {
+            @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
+            @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
+            boolean selfManaged, boolean notifyOnDeviceNearby, boolean revoked, boolean pending,
+            long timeApprovedMs, long lastTimeConnectedMs, int systemDataSyncFlags,
+            @Nullable Icon deviceIcon, @Nullable DeviceId deviceId) {
         if (id <= 0) {
             throw new IllegalArgumentException("Association ID should be greater than 0");
         }
@@ -115,7 +115,6 @@
         mPackageName = packageName;
         mDeviceMacAddress = macAddress;
         mDisplayName = displayName;
-        mTag = tag;
         mDeviceProfile = deviceProfile;
         mAssociatedDevice = associatedDevice;
         mSelfManaged = selfManaged;
@@ -126,6 +125,7 @@
         mLastTimeConnectedMs = lastTimeConnectedMs;
         mSystemDataSyncFlags = systemDataSyncFlags;
         mDeviceIcon = deviceIcon;
+        mDeviceId = deviceId;
     }
 
     /**
@@ -155,13 +155,13 @@
     }
 
     /**
-     * @return the tag of this association.
-     * @see CompanionDeviceManager#setAssociationTag(int, String)
+     * @return the {@link DeviceId} of this association.
+     * @see CompanionDeviceManager#setDeviceId(int, DeviceId)
      */
     @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
     @Nullable
-    public String getTag() {
-        return mTag;
+    public DeviceId getDeviceId() {
+        return mDeviceId;
     }
 
     /**
@@ -355,7 +355,6 @@
                 + "mId=" + mId
                 + ", mUserId=" + mUserId
                 + ", mPackageName='" + mPackageName + '\''
-                + ", mTag='" + mTag + '\''
                 + ", mDeviceMacAddress=" + mDeviceMacAddress
                 + ", mDisplayName='" + mDisplayName + '\''
                 + ", mDeviceProfile='" + mDeviceProfile + '\''
@@ -369,6 +368,7 @@
                     mLastTimeConnectedMs == Long.MAX_VALUE
                         ? LAST_TIME_CONNECTED_NONE : new Date(mLastTimeConnectedMs))
                 + ", mSystemDataSyncFlags=" + mSystemDataSyncFlags
+                + ", mDeviceId='" + mDeviceId
                 + '}';
     }
 
@@ -386,21 +386,22 @@
                 && mTimeApprovedMs == that.mTimeApprovedMs
                 && mLastTimeConnectedMs == that.mLastTimeConnectedMs
                 && Objects.equals(mPackageName, that.mPackageName)
-                && Objects.equals(mTag, that.mTag)
                 && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
                 && Objects.equals(mDisplayName, that.mDisplayName)
                 && Objects.equals(mDeviceProfile, that.mDeviceProfile)
                 && Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
                 && mSystemDataSyncFlags == that.mSystemDataSyncFlags
                 && (mDeviceIcon == null ? that.mDeviceIcon == null
-                : mDeviceIcon.sameAs(that.mDeviceIcon));
+                : mDeviceIcon.sameAs(that.mDeviceIcon))
+                && Objects.equals(mDeviceId, that.mDeviceId);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mId, mUserId, mPackageName, mTag, mDeviceMacAddress, mDisplayName,
+        return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
                 mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked,
-                mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags, mDeviceIcon);
+                mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags, mDeviceIcon,
+                mDeviceId);
     }
 
     @Override
@@ -413,7 +414,6 @@
         dest.writeInt(mId);
         dest.writeInt(mUserId);
         dest.writeString(mPackageName);
-        dest.writeString(mTag);
         dest.writeTypedObject(mDeviceMacAddress, 0);
         dest.writeCharSequence(mDisplayName);
         dest.writeString(mDeviceProfile);
@@ -431,13 +431,19 @@
         } else {
             dest.writeInt(0);
         }
+
+        if (Flags.associationTag() && mDeviceId != null) {
+            dest.writeInt(1);
+            dest.writeTypedObject(mDeviceId, flags);
+        } else {
+            dest.writeInt(0);
+        }
     }
 
     private AssociationInfo(@NonNull Parcel in) {
         mId = in.readInt();
         mUserId = in.readInt();
         mPackageName = in.readString();
-        mTag = in.readString();
         mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR);
         mDisplayName = in.readCharSequence();
         mDeviceProfile = in.readString();
@@ -454,6 +460,12 @@
         } else {
             mDeviceIcon = null;
         }
+        int deviceId = in.readInt();
+        if (Flags.associationTag() && deviceId == 1) {
+            mDeviceId = in.readTypedObject(DeviceId.CREATOR);
+        } else {
+            mDeviceId = null;
+        }
     }
 
     @NonNull
@@ -481,7 +493,6 @@
         private final int mId;
         private final int mUserId;
         private final String mPackageName;
-        private String mTag;
         private MacAddress mDeviceMacAddress;
         private CharSequence mDisplayName;
         private String mDeviceProfile;
@@ -494,6 +505,7 @@
         private long mLastTimeConnectedMs;
         private int mSystemDataSyncFlags;
         private Icon mDeviceIcon;
+        private DeviceId mDeviceId;
 
         /** @hide */
         @TestApi
@@ -509,7 +521,6 @@
             mId = info.mId;
             mUserId = info.mUserId;
             mPackageName = info.mPackageName;
-            mTag = info.mTag;
             mDeviceMacAddress = info.mDeviceMacAddress;
             mDisplayName = info.mDisplayName;
             mDeviceProfile = info.mDeviceProfile;
@@ -522,6 +533,7 @@
             mLastTimeConnectedMs = info.mLastTimeConnectedMs;
             mSystemDataSyncFlags = info.mSystemDataSyncFlags;
             mDeviceIcon = info.mDeviceIcon;
+            mDeviceId = info.mDeviceId;
         }
 
         /**
@@ -534,7 +546,6 @@
             mId = id;
             mUserId = userId;
             mPackageName = packageName;
-            mTag = info.mTag;
             mDeviceMacAddress = info.mDeviceMacAddress;
             mDisplayName = info.mDisplayName;
             mDeviceProfile = info.mDeviceProfile;
@@ -547,14 +558,15 @@
             mLastTimeConnectedMs = info.mLastTimeConnectedMs;
             mSystemDataSyncFlags = info.mSystemDataSyncFlags;
             mDeviceIcon = info.mDeviceIcon;
+            mDeviceId = info.mDeviceId;
         }
 
         /** @hide */
         @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
         @TestApi
         @NonNull
-        public Builder setTag(@Nullable String tag) {
-            mTag = tag;
+        public Builder setDeviceId(@Nullable DeviceId deviceId) {
+            mDeviceId = deviceId;
             return this;
         }
 
@@ -684,7 +696,6 @@
                     mId,
                     mUserId,
                     mPackageName,
-                    mTag,
                     mDeviceMacAddress,
                     mDisplayName,
                     mDeviceProfile,
@@ -696,7 +707,8 @@
                     mTimeApprovedMs,
                     mLastTimeConnectedMs,
                     mSystemDataSyncFlags,
-                    mDeviceIcon
+                    mDeviceIcon,
+                    mDeviceId
             );
         }
     }
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 4472c3d..a96ba11 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -278,12 +278,6 @@
     public static final int MESSAGE_ONEWAY_TO_WEARABLE = 0x43847987; // +TOW
 
     /**
-     * The length limit of Association tag.
-     * @hide
-     */
-    private static final int ASSOCIATION_TAG_LENGTH_LIMIT = 1024;
-
-    /**
      * Callback for applications to receive updates about and the outcome of
      * {@link AssociationRequest} issued via {@code associate()} call.
      *
@@ -1229,7 +1223,6 @@
         }
     }
 
-    // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
     /**
      * Register to receive callbacks whenever the associated device comes in and out of range.
      *
@@ -1261,7 +1254,12 @@
      *
      * @throws DeviceNotAssociatedException if the given device was not previously associated
      * with this app.
+     *
+     * @deprecated use {@link #startObservingDevicePresence(ObservingDevicePresenceRequest)}
+     * instead.
      */
+    @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
     public void startObservingDevicePresence(@NonNull String deviceAddress)
             throws DeviceNotAssociatedException {
@@ -1288,7 +1286,7 @@
                             callingUid, callingPid);
         }
     }
-    // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
+
     /**
      * Unregister for receiving callbacks whenever the associated device comes in and out of range.
      *
@@ -1305,7 +1303,12 @@
      *
      * @throws DeviceNotAssociatedException if the given device was not previously associated
      * with this app.
+     *
+     * @deprecated use {@link #stopObservingDevicePresence(ObservingDevicePresenceRequest)}
+     * instead.
      */
+    @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
     public void stopObservingDevicePresence(@NonNull String deviceAddress)
             throws DeviceNotAssociatedException {
@@ -1771,57 +1774,25 @@
     }
 
     /**
-     * Sets the {@link AssociationInfo#getTag() tag} for this association.
+     * Sets the {@link DeviceId deviceId} for this association.
      *
-     * <p>The length of the tag must be at most 1024 characters to save disk space.
-     *
-     * <p>This allows to store useful information about the associated devices.
+     * <p>This device id helps the system uniquely identify your device for efficient device
+     * management and prevents duplicate entries.
      *
      * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association
-     *                          of the companion device recorded by CompanionDeviceManager
-     * @param tag the tag of this association
+     *                          of the companion device recorded by CompanionDeviceManager.
+     * @param deviceId to be used as device identifier to represent the associated device.
      */
     @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
     @UserHandleAware
-    public void setAssociationTag(int associationId, @NonNull String tag) {
-        if (mService == null) {
-            Log.w(TAG, "CompanionDeviceManager service is not available.");
-            return;
-        }
-
-        Objects.requireNonNull(tag, "tag cannot be null");
-
-        if (tag.length() > ASSOCIATION_TAG_LENGTH_LIMIT) {
-            throw new IllegalArgumentException("Length of the tag must be at most"
-                    + ASSOCIATION_TAG_LENGTH_LIMIT + " characters");
-        }
-
-        try {
-            mService.setAssociationTag(associationId, tag);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Clears the {@link AssociationInfo#getTag() tag} for this association.
-     *
-     * <p>The tag will be set to null for this association when calling this API.
-     *
-     * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association
-     *                          of the companion device recorded by CompanionDeviceManager
-     * @see CompanionDeviceManager#setAssociationTag(int, String)
-     */
-    @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
-    @UserHandleAware
-    public void clearAssociationTag(int associationId) {
+    public void setDeviceId(int associationId, @Nullable DeviceId deviceId) {
         if (mService == null) {
             Log.w(TAG, "CompanionDeviceManager service is not available.");
             return;
         }
 
         try {
-            mService.clearAssociationTag(associationId);
+            mService.setDeviceId(associationId, deviceId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 5ad2348..316d129 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -247,12 +247,14 @@
                 .detachSystemDataTransport(associationId);
     }
 
-    // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
     /**
-     * Called by system whenever a device associated with this app is connected.
+     * Called by the system when an associated device is nearby or connected.
      *
      * @param associationInfo A record for the companion device.
+     * @deprecated use {@link #onDevicePresenceEvent(DevicePresenceEvent)}} instead.
      */
+    @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+    @Deprecated
     @MainThread
     public void onDeviceAppeared(@NonNull AssociationInfo associationInfo) {
         if (!associationInfo.isSelfManaged()) {
@@ -260,12 +262,14 @@
         }
     }
 
-    // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
     /**
-     * Called by system whenever a device associated with this app is disconnected.
+     * Called by the system when an associated device is out of range or disconnected.
      *
      * @param associationInfo A record for the companion device.
+     * @deprecated use {@link #onDevicePresenceEvent(DevicePresenceEvent)}} instead.
      */
+    @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+    @Deprecated
     @MainThread
     public void onDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
         if (!associationInfo.isSelfManaged()) {
@@ -274,7 +278,7 @@
     }
 
     /**
-     * Called by the system during device events.
+     * Called by the system when an associated device's presence state changes.
      *
      * @see CompanionDeviceManager#startObservingDevicePresence(ObservingDevicePresenceRequest)
      */
diff --git a/core/java/android/companion/DeviceId.aidl b/core/java/android/companion/DeviceId.aidl
new file mode 100644
index 0000000..d60d5f4
--- /dev/null
+++ b/core/java/android/companion/DeviceId.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.companion;
+
+parcelable DeviceId;
\ No newline at end of file
diff --git a/core/java/android/companion/DeviceId.java b/core/java/android/companion/DeviceId.java
new file mode 100644
index 0000000..f66a1ae
--- /dev/null
+++ b/core/java/android/companion/DeviceId.java
@@ -0,0 +1,210 @@
+/*
+ * 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.companion;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.MacAddress;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.OneTimeUseBuilder;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ *  A device id represents a device identifier managed by the companion app.
+ */
+@FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
+public final class DeviceId implements Parcelable {
+    /**
+     * The length limit of custom id.
+     */
+    private static final int CUSTOM_ID_LENGTH_LIMIT = 1024;
+
+    private final String mCustomId;
+    private final MacAddress mMacAddress;
+
+    /**
+     * @hide
+     */
+    public DeviceId(@Nullable String customId, @Nullable MacAddress macAddress) {
+        mCustomId = customId;
+        mMacAddress = macAddress;
+    }
+
+    /**
+     * Returns true if two Device ids are represent the same device. False otherwise.
+     * @hide
+     */
+    public boolean isSameDevice(@Nullable DeviceId other) {
+        if (other == null) {
+            return false;
+        }
+
+        if (this.mCustomId != null && other.mCustomId != null) {
+            return this.mCustomId.equals(other.mCustomId);
+        }
+        if (this.mMacAddress != null && other.mMacAddress != null) {
+            return this.mMacAddress.equals(other.mMacAddress);
+        }
+
+        return false;
+    }
+
+    /** @hide */
+    @Nullable
+    public String getMacAddressAsString() {
+        return mMacAddress != null ? mMacAddress.toString().toUpperCase(Locale.US) : null;
+    }
+
+    /**
+     * @return the custom id that managed by the companion app.
+     */
+    @Nullable
+    public String getCustomId() {
+        return mCustomId;
+    }
+
+    /**
+     * @return the mac address that managed by the companion app.
+     */
+    @Nullable
+    public MacAddress getMacAddress() {
+        return mMacAddress;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        if (mCustomId != null) {
+            dest.writeInt(1);
+            dest.writeString8(mCustomId);
+        } else {
+            dest.writeInt(0);
+        }
+        dest.writeTypedObject(mMacAddress, 0);
+
+    }
+
+    private DeviceId(@NonNull Parcel in) {
+        int flg = in.readInt();
+        if (flg == 1) {
+            mCustomId = in.readString8();
+        } else {
+            mCustomId = null;
+        }
+        mMacAddress = in.readTypedObject(MacAddress.CREATOR);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<DeviceId> CREATOR =
+            new Parcelable.Creator<DeviceId>() {
+                @Override
+                public DeviceId[] newArray(int size) {
+                    return new DeviceId[size];
+                }
+
+                @Override
+                public DeviceId createFromParcel(@android.annotation.NonNull Parcel in) {
+                    return new DeviceId(in);
+                }
+            };
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mCustomId, mMacAddress);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (!(o instanceof DeviceId that)) return false;
+
+        return Objects.equals(mCustomId, that.mCustomId)
+                && Objects.equals(mMacAddress, that.mMacAddress);
+    }
+
+    @Override
+    public String toString() {
+        return "DeviceId{"
+                + "," + "mCustomId= " + mCustomId
+                + "," + "mMacAddress= " + mMacAddress
+                + "}";
+    }
+
+    /**
+     * A builder for {@link DeviceId}
+     */
+    public static final class Builder extends OneTimeUseBuilder<DeviceId> {
+        private String mCustomId;
+        private MacAddress mMacAddress;
+
+        public Builder() {}
+
+        /**
+         * Sets the custom device id. This id is used by the Companion app to
+         * identify a specific device.
+         *
+         * @param customId the custom device id
+         * @throws IllegalArgumentException length of the custom id must more than 1024
+         * characters to save disk space.
+         */
+        @NonNull
+        public Builder setCustomId(@Nullable String customId) {
+            checkNotUsed();
+            if (customId != null
+                    && customId.length() > CUSTOM_ID_LENGTH_LIMIT) {
+                throw new IllegalArgumentException("Length of the custom id must be at most "
+                        + CUSTOM_ID_LENGTH_LIMIT + " characters");
+            }
+            this.mCustomId = customId;
+            return this;
+        }
+
+        /**
+         * Sets the mac address. This mac address is used by the Companion app to
+         * identify a specific device.
+         *
+         * @param macAddress the remote device mac address
+         * @throws IllegalArgumentException length of the custom id must more than 1024
+         * characters to save disk space.
+         */
+        @NonNull
+        public Builder setMacAddress(@Nullable MacAddress macAddress) {
+            checkNotUsed();
+            mMacAddress = macAddress;
+            return this;
+        }
+
+        @NonNull
+        @Override
+        public DeviceId build() {
+            markUsed();
+            if (mCustomId == null && mMacAddress == null) {
+                throw new IllegalArgumentException("At least one device id property must be"
+                        + "non-null to build a DeviceId.");
+            }
+            return new DeviceId(mCustomId, mMacAddress);
+        }
+    }
+}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index de3ddec..a2b7dd9 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -28,6 +28,7 @@
 import android.companion.datatransfer.PermissionSyncRequest;
 import android.content.ComponentName;
 import android.os.ParcelUuid;
+import android.companion.DeviceId;
 
 
 /**
@@ -134,9 +135,7 @@
     @EnforcePermission("MANAGE_COMPANION_DEVICES")
     void enableSecureTransport(boolean enabled);
 
-    void setAssociationTag(int associationId, String tag);
-
-    void clearAssociationTag(int associationId);
+    void setDeviceId(int associationId, in DeviceId deviceId);
 
     byte[] getBackupPayload(int userId);
 
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 2539a12..2b9f700 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -46,6 +46,7 @@
     namespace: "companion"
     description: "Unpair with an associated bluetooth device"
     bug: "322237619"
+    is_exported: true
 }
 
 flag {
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index d3a1c25..f8ac27d 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -96,6 +96,7 @@
     boolean canCreateMirrorDisplays();
 
     /*
+    /*
      * Turns off all trusted non-mirror displays of the virtual device.
      */
     void goToSleep();
diff --git a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
index 767f52a..448793d 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
@@ -63,4 +63,11 @@
      * @param user The user associated with the activity.
      */
     void onSecureWindowShown(int displayId, in ComponentName componentName, in UserHandle user);
+
+    /**
+     * Called when a secure surface is no longer shown on the device.
+     *
+     * @param displayId The display ID on which the secure surface was shown.
+     */
+    void onSecureWindowHidden(int displayId);
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index d63a443..42c7441 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -166,6 +166,20 @@
                         Binder.restoreCallingIdentity(token);
                     }
                 }
+
+                @Override
+                public void onSecureWindowHidden(int displayId) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        synchronized (mActivityListenersLock) {
+                            for (int i = 0; i < mActivityListeners.size(); i++) {
+                                mActivityListeners.valueAt(i).onSecureWindowHidden(displayId);
+                            }
+                        }
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
             };
 
     private final IVirtualDeviceSoundEffectListener mSoundEffectListener =
@@ -617,6 +631,10 @@
             mExecutor.execute(() ->
                     mActivityListener.onSecureWindowShown(displayId, componentName, user));
         }
+
+        public void onSecureWindowHidden(int displayId) {
+            mExecutor.execute(() -> mActivityListener.onSecureWindowHidden(displayId));
+        }
     }
 
     /**
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 6ea7834..b3f09a9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -1288,6 +1288,17 @@
         @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
         default void onSecureWindowShown(int displayId, @NonNull ComponentName componentName,
                 @NonNull UserHandle user) {}
+
+        /**
+         * Called when a window with a secure surface is no longer shown on the device.
+         *
+         * @param displayId The display ID on which the window was shown before.
+         *
+         * @see Display#FLAG_SECURE
+         * @see WindowManager.LayoutParams#FLAG_SECURE
+         */
+        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
+        default void onSecureWindowHidden(int displayId) {}
     }
 
     /**
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index cc57dc0..e271cf4 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -946,6 +946,34 @@
     }
 
     /**
+     * Make a clone of ClipData that only contains URIs. This reduces the size of data transfer over
+     * IPC and only retains important information for the purpose of verifying creator token of an
+     * Intent.
+     * @return a copy of ClipData with only URIs remained.
+     * @hide
+     */
+    public ClipData cloneOnlyUriItems() {
+        ArrayList<Item> items = null;
+        final int N = mItems.size();
+        for (int i = 0; i < N; i++) {
+            Item item = mItems.get(i);
+            if (item.getUri() != null) {
+                if (items == null) {
+                    items = new ArrayList<>(N);
+                }
+                items.add(new Item(item.getUri()));
+            } else if (item.getIntent() != null) {
+                if (items == null) {
+                    items = new ArrayList<>(N);
+                }
+                items.add(new Item(item.getIntent().cloneForCreatorToken()));
+            }
+        }
+        if (items == null || items.isEmpty()) return null;
+        return new ClipData(new ClipDescription("", new String[0]), items);
+    }
+
+    /**
      * Create a new ClipData holding data of the type
      * {@link ClipDescription#MIMETYPE_TEXT_PLAIN}.
      *
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index acad92c9..6ec6a62 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -18,6 +18,7 @@
 
 import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
 import static android.content.flags.Flags.FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS;
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
 import static android.security.Flags.FLAG_SECURE_LOCKDOWN;
 
 import android.annotation.AttrRes;
@@ -640,12 +641,15 @@
     public static final int BIND_FOREGROUND_SERVICE_WHILE_AWAKE = 0x02000000;
 
     /**
-     * @hide Flag for {@link #bindService}: For only the case where the binding
+     * Flag for {@link #bindService}: For only the case where the binding
      * is coming from the system, set the process state to BOUND_FOREGROUND_SERVICE
      * instead of the normal maximum of IMPORTANT_FOREGROUND.  That is, this is
      * saying that the process shouldn't participate in the normal power reduction
      * modes (removing network access etc).
+     * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE)
     public static final int BIND_FOREGROUND_SERVICE = 0x04000000;
 
     /**
@@ -4250,6 +4254,7 @@
             //@hide: WIFI_RTT_SERVICE,
             //@hide: ETHERNET_SERVICE,
             WIFI_RTT_RANGING_SERVICE,
+            WIFI_USD_SERVICE,
             NSD_SERVICE,
             AUDIO_SERVICE,
             AUDIO_DEVICE_VOLUME_SERVICE,
@@ -5096,6 +5101,19 @@
      */
     public static final String WIFI_RTT_RANGING_SERVICE = "wifirtt";
 
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a {@link
+     * android.net.wifi.usd.UsdManager} for Unsynchronized Service Discovery (USD) operation.
+     *
+     * @see #getSystemService(String)
+     * @see android.net.wifi.usd.UsdManager
+     * @hide
+     */
+    @FlaggedApi(android.net.wifi.flags.Flags.FLAG_USD)
+    @SystemApi
+    public static final String WIFI_USD_SERVICE = "wifi_usd";
+
     /**
      * Use with {@link #getSystemService(String)} to retrieve a {@link
      * android.net.lowpan.LowpanManager} for handling management of
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 3d2d487..a577ec0 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6187,7 +6187,8 @@
      * {@link #EXTRA_CHOOSER_MODIFY_SHARE_ACTION},
      * {@link #EXTRA_METADATA_TEXT},
      * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER},
-     * {@link #EXTRA_CHOOSER_RESULT_INTENT_SENDER}.
+     * {@link #EXTRA_CHOOSER_RESULT_INTENT_SENDER},
+     * {@link #EXTRA_EXCLUDE_COMPONENTS}.
      * </p>
      */
     public static final String EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI =
@@ -7720,6 +7721,7 @@
     @IntDef(flag = true, prefix = { "EXTENDED_FLAG_" }, value = {
             EXTENDED_FLAG_FILTER_MISMATCH,
             EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN,
+            EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ExtendedFlags {}
@@ -7740,6 +7742,13 @@
      */
     public static final int EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN = 1 << 1;
 
+    /**
+     * This flag indicates this intent called {@link #collectExtraIntentKeys()}.
+     *
+     * @hide
+     */
+    public static final int EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED = 1 << 2;
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // toUri() and parseUri() options.
@@ -7962,6 +7971,24 @@
     }
 
     /**
+     * Make a copy of all members important to identify an intent with its creator token.
+     * @hide
+     */
+    public @NonNull Intent cloneForCreatorToken() {
+        Intent clone = new Intent()
+                .setAction(this.mAction)
+                .setDataAndType(this.mData, this.mType)
+                .setPackage(this.mPackage)
+                .setComponent(this.mComponent)
+                .setFlags(this.mFlags & IMMUTABLE_FLAGS);
+        if (this.mClipData != null) {
+            clone.setClipData(this.mClipData.cloneOnlyUriItems());
+        }
+        clone.mCreatorTokenInfo = this.mCreatorTokenInfo;
+        return clone;
+    }
+
+    /**
      * Create an intent with a given action.  All other fields (data, type,
      * class) are null.  Note that the action <em>must</em> be in a
      * namespace because Intents are used globally in the system -- for
@@ -11676,7 +11703,7 @@
                 Log.w(TAG, "Failure filling in extras", e);
             }
         }
-        mCreatorTokenInfo = other.mCreatorTokenInfo;
+        fillInCreatorTokenInfo(other.mCreatorTokenInfo, changes);
         if (mayHaveCopiedUris && mContentUserHint == UserHandle.USER_CURRENT
                 && other.mContentUserHint != UserHandle.USER_CURRENT) {
             mContentUserHint = other.mContentUserHint;
@@ -11684,6 +11711,45 @@
         return changes;
     }
 
+    // keep original creator token and merge nested intent keys.
+    private void fillInCreatorTokenInfo(CreatorTokenInfo otherCreatorTokenInfo, int changes) {
+        if (otherCreatorTokenInfo != null && otherCreatorTokenInfo.mNestedIntentKeys != null) {
+            if (mCreatorTokenInfo == null) {
+                mCreatorTokenInfo = new CreatorTokenInfo();
+            }
+            ArraySet<NestedIntentKey> otherNestedIntentKeys =
+                    otherCreatorTokenInfo.mNestedIntentKeys;
+            if (mCreatorTokenInfo.mNestedIntentKeys == null) {
+                mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>(otherNestedIntentKeys);
+            } else {
+                ArraySet<NestedIntentKey> otherKeys;
+                if ((changes & FILL_IN_CLIP_DATA) == 0) {
+                    // If clip data is Not filled in from other, do not merge clip data keys.
+                    otherKeys = new ArraySet<>();
+                    int N = otherNestedIntentKeys.size();
+                    for (int i = 0; i < N; i++) {
+                        NestedIntentKey key = otherNestedIntentKeys.valueAt(i);
+                        if (key.mType != NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA) {
+                            otherKeys.add(key);
+                        }
+                    }
+                } else {
+                    // If clip data is filled in from other, remove clip data keys from this
+                    // creatorTokenInfo and then merge every key from the others.
+                    int N = mCreatorTokenInfo.mNestedIntentKeys.size();
+                    for (int i = N - 1; i >= 0; i--) {
+                        NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i);
+                        if (key.mType == NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA) {
+                            mCreatorTokenInfo.mNestedIntentKeys.removeAt(i);
+                        }
+                    }
+                    otherKeys = otherNestedIntentKeys;
+                }
+                mCreatorTokenInfo.mNestedIntentKeys.addAll(otherKeys);
+            }
+        }
+    }
+
     /**
      * Merge the extras data in this intent with that of other supplied intent using the
      * strategy specified using {@code extrasMerger}.
@@ -12220,6 +12286,7 @@
         private IBinder mCreatorToken;
         // Stores all extra keys whose values are intents for a top level intent.
         private ArraySet<NestedIntentKey> mNestedIntentKeys;
+
     }
 
     /**
@@ -12328,7 +12395,8 @@
     }
 
     private void collectNestedIntentKeysRecur(Set<Intent> visited) {
-        if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
+        addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
+        if (mExtras != null && !mExtras.isEmpty()) {
             for (String key : mExtras.keySet()) {
                 Object value = mExtras.get(key);
 
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index cccfdb0..9478422 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1449,6 +1449,97 @@
         }
     }
 
+    /**
+     * Use this to report any errors during alignment checks
+     *
+     * @hide
+     */
+    public static final int PAGE_SIZE_APP_COMPAT_FLAG_ERROR = -1;
+
+    /**
+     * Initial value for mPageSizeAppCompatFlags
+     *
+     * @hide
+     */
+    public static final int PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED = 0;
+
+    /**
+     * if set, extract libs forcefully for 16 KB device and show warning dialog.
+     *
+     * @hide
+     */
+    public static final int PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED = 1 << 1;
+
+    /**
+     * if set, load 4 KB aligned ELFs on 16 KB device in compat mode and show warning dialog.
+     *
+     * @hide
+     */
+    public static final int PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED = 1 << 2;
+
+    /**
+     * Run in 16 KB app compat mode. This flag will be set explicitly through settings. If set, 16
+     * KB app compat warning dialogs will still show up.
+     *
+     * @hide
+     */
+    public static final int PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED = 1 << 3;
+
+    /**
+     * Disable 16 KB app compat mode through settings. It should only affect ELF loading as app is
+     * already installed.
+     *
+     * @hide
+     */
+    public static final int PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED = 1 << 4;
+
+    /**
+     * Run in 16 KB app compat mode. This flag will be set explicitly through manifest. If set, hide
+     * the 16 KB app compat warning dialogs. This has the highest priority to enable compat mode.
+     *
+     * @hide
+     */
+    public static final int PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED = 1 << 5;
+
+    /**
+     * Disable 16 KB app compat mode. This has the highest priority to disable compat mode.
+     *
+     * @hide
+     */
+    public static final int PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED = 1 << 6;
+
+    /**
+     * Max value for page size app compat
+     *
+     * @hide
+     */
+    public static final int PAGE_SIZE_APP_COMPAT_FLAG_MAX = 1 << 7;
+
+    /**
+     * 16 KB app compat status for the app. App can have native shared libs which are not page
+     * aligned, LOAD segments inside the shared libs have to be page aligned. Apps can specify the
+     * override in manifest file as well.
+     */
+    private @PageSizeAppCompatFlags int mPageSizeAppCompatFlags =
+            ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+
+    /** {@hide} */
+    @IntDef(
+            prefix = {"PAGE_SIZE_APP_COMPAT_FLAG_"},
+            value = {
+                PAGE_SIZE_APP_COMPAT_FLAG_ERROR,
+                PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED,
+                PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED,
+                PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED,
+                PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED,
+                PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED,
+                PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED,
+                PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED,
+                PAGE_SIZE_APP_COMPAT_FLAG_MAX,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PageSizeAppCompatFlags {}
+
     /** @hide */
     public String classLoaderName;
 
@@ -1777,7 +1868,7 @@
             pw.println(prefix + "enableOnBackInvokedCallback=" + isOnBackInvokedCallbackEnabled());
             pw.println(prefix + "allowCrossUidActivitySwitchFromBelow="
                     + allowCrossUidActivitySwitchFromBelow);
-
+            pw.println(prefix + "mPageSizeAppCompatFlags=" + mPageSizeAppCompatFlags);
         }
         pw.println(prefix + "createTimestamp=" + createTimestamp);
         if (mKnownActivityEmbeddingCerts != null) {
@@ -1897,6 +1988,10 @@
             }
             proto.write(ApplicationInfoProto.Detail.ALLOW_CROSS_UID_ACTIVITY_SWITCH_FROM_BELOW,
                     allowCrossUidActivitySwitchFromBelow);
+
+            proto.write(ApplicationInfoProto.Detail.ENABLE_PAGE_SIZE_APP_COMPAT,
+                        mPageSizeAppCompatFlags);
+
             proto.end(detailToken);
         }
         if (!ArrayUtils.isEmpty(mKnownActivityEmbeddingCerts)) {
@@ -2024,6 +2119,7 @@
         localeConfigRes = orig.localeConfigRes;
         allowCrossUidActivitySwitchFromBelow = orig.allowCrossUidActivitySwitchFromBelow;
         createTimestamp = SystemClock.uptimeMillis();
+        mPageSizeAppCompatFlags = orig.mPageSizeAppCompatFlags;
     }
 
     public String toString() {
@@ -2128,6 +2224,7 @@
         }
         dest.writeInt(localeConfigRes);
         dest.writeInt(allowCrossUidActivitySwitchFromBelow ? 1 : 0);
+        dest.writeInt(mPageSizeAppCompatFlags);
 
         sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
     }
@@ -2228,6 +2325,7 @@
         }
         localeConfigRes = source.readInt();
         allowCrossUidActivitySwitchFromBelow = source.readInt() != 0;
+        mPageSizeAppCompatFlags = source.readInt();
 
         mKnownActivityEmbeddingCerts = sForStringSet.unparcel(source);
         if (mKnownActivityEmbeddingCerts.isEmpty()) {
@@ -2765,6 +2863,11 @@
         requestRawExternalStorageAccess = value;
     }
 
+    /** {@hide} */
+    public void setPageSizeAppCompatFlags(@PageSizeAppCompatFlags int value) {
+        mPageSizeAppCompatFlags |= value;
+    }
+
     /**
      * Replaces {@link #mAppClassNamesByProcess}. This takes over the ownership of the passed map.
      * Do not modify the argument at the callsite.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 5d4babb..9f898b8 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -848,4 +848,12 @@
     int getAppMetadataSource(String packageName, int userId);
 
     ComponentName getDomainVerificationAgent(int userId);
+
+    void setPageSizeAppCompatFlagsSettingsOverride(in String packageName, boolean enabled);
+
+    boolean isPageSizeCompatEnabled(in String packageName);
+
+    String getPageSizeCompatWarningMessage(in String packageName);
+
+    List<String> getAllApexDirectories();
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index cb665cd..cd62573 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -218,6 +218,17 @@
             "android.content.pm.action.CONFIRM_PRE_APPROVAL";
 
     /**
+     * Intent action to be sent to the implementer of
+     * {@link android.content.pm.dependencyinstaller.DependencyInstallerService}.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+    @SystemApi
+    public static final String ACTION_INSTALL_DEPENDENCY =
+            "android.content.pm.action.INSTALL_DEPENDENCY";
+
+    /**
      * An integer session ID that an operation is working with.
      *
      * @see Intent#getIntExtra(String, int)
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index d2b43b9..a06eb1c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import static android.content.pm.SigningInfo.AppSigningSchemeVersion;
 import static android.media.audio.Flags.FLAG_FEATURE_SPATIAL_AUDIO_HEADTRACKING_LOW_LATENCY;
 
 import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES;
@@ -59,6 +60,8 @@
 import android.content.IntentSender;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.dex.ArtManager;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -94,6 +97,7 @@
 import android.telephony.ims.SipDelegateManager;
 import android.util.AndroidException;
 import android.util.Log;
+import android.util.apk.ApkSignatureVerifier;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.pm.parsing.PackageInfoCommonUtils;
@@ -804,7 +808,6 @@
         @Deprecated
         private void __metadata() {}
 
-
         //@formatter:on
         // End of generated code
 
@@ -3148,6 +3151,16 @@
     public static final long MAXIMUM_VERIFICATION_TIMEOUT = 60*60*1000;
 
     /**
+     * As the generated feature count is useful for classes that may not be compiled in the same
+     * annotation processing unit as PackageManager, we redeclare it here for visibility.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static final int SDK_FEATURE_COUNT =
+            com.android.internal.pm.SystemFeaturesMetadata.SDK_FEATURE_COUNT;
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device's
      * audio pipeline is low-latency, more suitable for audio applications sensitive to delays or
      * lag in sound input or output.
@@ -6729,6 +6742,11 @@
      * If the given permission already exists, the info you supply here
      * will be used to update it.
      *
+     * @deprecated Support for dynamic permissions is going to be removed, and apps that use dynamic
+     * permissions should declare their permissions statically inside their app manifest instead.
+     * This method will become a no-op in a future Android release and eventually be removed from
+     * the SDK.
+     *
      * @param info Description of the permission to be added.
      *
      * @return Returns true if a new permission was created, false if an
@@ -6739,7 +6757,9 @@
      *
      * @see #removePermission(String)
      */
-    //@Deprecated
+    @SuppressWarnings("HiddenAbstractMethod")
+    @FlaggedApi(android.permission.flags.Flags.FLAG_PERMISSION_TREE_APIS_DEPRECATED)
+    @Deprecated
     public abstract boolean addPermission(@NonNull PermissionInfo info);
 
     /**
@@ -6748,8 +6768,15 @@
      * allowing it to return quicker and batch a series of adds at the
      * expense of no guarantee the added permission will be retained if
      * the device is rebooted before it is written.
+     *
+     * @deprecated Support for dynamic permissions is going to be removed, and apps that use dynamic
+     * permissions should declare their permissions statically inside their app manifest instead.
+     * This method will become a no-op in a future Android release and eventually be removed from
+     * the SDK.
      */
-    //@Deprecated
+    @SuppressWarnings("HiddenAbstractMethod")
+    @FlaggedApi(android.permission.flags.Flags.FLAG_PERMISSION_TREE_APIS_DEPRECATED)
+    @Deprecated
     public abstract boolean addPermissionAsync(@NonNull PermissionInfo info);
 
     /**
@@ -6758,6 +6785,11 @@
      * -- you are only allowed to remove permissions that you are allowed
      * to add.
      *
+     * @deprecated Support for dynamic permissions is going to be removed, and apps that use dynamic
+     * permissions should declare their permissions statically inside their app manifest instead.
+     * This method will become a no-op in a future Android release and eventually be removed from
+     * the SDK.
+     *
      * @param permName The name of the permission to remove.
      *
      * @throws SecurityException if you are not allowed to remove the
@@ -6765,7 +6797,9 @@
      *
      * @see #addPermission(PermissionInfo)
      */
-    //@Deprecated
+    @SuppressWarnings("HiddenAbstractMethod")
+    @FlaggedApi(android.permission.flags.Flags.FLAG_PERMISSION_TREE_APIS_DEPRECATED)
+    @Deprecated
     public abstract void removePermission(@NonNull String permName);
 
     /**
@@ -10987,6 +11021,41 @@
     }
 
     /**
+     * Set the page compat mode override for given package
+     *
+     * @hide
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB)
+    public void setPageSizeAppCompatFlagsSettingsOverride(@NonNull String packageName,
+            boolean enabled) {
+        throw new UnsupportedOperationException(
+                "setPageSizeAppCompatFlagsSettingsOverride not implemented in subclass");
+    }
+
+    /**
+     * Check whether page size app compat mode is enabled for given package
+     *
+     * @hide
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB)
+    public boolean isPageSizeCompatEnabled(@NonNull String packageName) {
+        throw new UnsupportedOperationException(
+                "isPageSizeCompatEnabled not implemented in subclass");
+    }
+
+    /**
+     * Get the page size app compat warning dialog to show at app launch time
+     *
+     * @hide
+     */
+    @Nullable
+    @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB)
+    public String getPageSizeCompatWarningMessage(@NonNull String packageName) {
+        throw new UnsupportedOperationException(
+                "getPageSizeCompatWarningMessage not implemented in subclass");
+    }
+
+     /**
      * Returns the harmful app warning string for the given app, or null if there is none set.
      *
      * @param packageName The full name of the desired package.
@@ -11932,4 +12001,31 @@
         throw new UnsupportedOperationException(
                 "parseServiceMetadata not implemented in subclass");
     }
+
+    /**
+     * Verifies and returns the
+     * <a href="https://source.android.com/docs/security/features/apksigning">app signing</a>
+     * information of the file at the given path. This operation takes a few milliseconds.
+     *
+     * Unlike {@link #getPackageArchiveInfo(String, PackageInfoFlags)} with {@link
+     * #GET_SIGNING_CERTIFICATES}, this method does not require the file to be a package archive
+     * file.
+     *
+     * @throws SigningInfoException if the verification fails
+     *
+     * @hide
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_CLOUD_COMPILATION_PM)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static @NonNull SigningInfo getVerifiedSigningInfo(@NonNull String path,
+            @AppSigningSchemeVersion int minAppSigningSchemeVersion) throws SigningInfoException {
+        ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+        ParseResult<SigningDetails> result =
+                ApkSignatureVerifier.verify(input, path, minAppSigningSchemeVersion);
+        if (result.isError()) {
+            throw new SigningInfoException(
+                    result.getErrorCode(), result.getErrorMessage(), result.getException());
+        }
+        return new SigningInfo(result.getResult());
+    }
 }
diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java
index 23daaf2..e4fbd1f 100644
--- a/core/java/android/content/pm/SigningInfo.java
+++ b/core/java/android/content/pm/SigningInfo.java
@@ -16,14 +16,20 @@
 
 package android.content.pm;
 
+import static android.content.pm.SigningDetails.SignatureSchemeVersion;
+
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.content.pm.SigningDetails.SignatureSchemeVersion;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArraySet;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.security.PublicKey;
 import java.util.Collection;
 
@@ -31,6 +37,55 @@
  * Information pertaining to the signing certificates used to sign a package.
  */
 public final class SigningInfo implements Parcelable {
+    /**
+     * JAR signing (v1 scheme).
+     * See https://source.android.com/docs/security/features/apksigning#v1.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int VERSION_JAR = SignatureSchemeVersion.JAR;
+
+    /**
+     * APK signature scheme v2.
+     * See https://source.android.com/docs/security/features/apksigning/v2.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int VERSION_SIGNING_BLOCK_V2 = SignatureSchemeVersion.SIGNING_BLOCK_V2;
+
+    /**
+     * APK signature scheme v3.
+     * See https://source.android.com/docs/security/features/apksigning/v3.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int VERSION_SIGNING_BLOCK_V3 = SignatureSchemeVersion.SIGNING_BLOCK_V3;
+
+    /**
+     * APK signature scheme v4.
+     * See https://source.android.com/docs/security/features/apksigning/v4.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int VERSION_SIGNING_BLOCK_V4 = SignatureSchemeVersion.SIGNING_BLOCK_V4;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"VERSION_"}, value = {
+            VERSION_JAR,
+            VERSION_SIGNING_BLOCK_V2,
+            VERSION_SIGNING_BLOCK_V3,
+            VERSION_SIGNING_BLOCK_V4,
+    })
+    public @interface AppSigningSchemeVersion {}
 
     @NonNull
     private final SigningDetails mSigningDetails;
@@ -198,6 +253,17 @@
         return mSigningDetails;
     }
 
+    /**
+     * Returns true if the signing certificates in this and other match exactly.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public boolean signersMatchExactly(@NonNull SigningInfo other) {
+        return mSigningDetails.signaturesMatchExactly(other.mSigningDetails);
+    }
+
     public static final @android.annotation.NonNull Parcelable.Creator<SigningInfo> CREATOR =
             new Parcelable.Creator<SigningInfo>() {
         @Override
diff --git a/core/java/android/content/pm/SigningInfoException.java b/core/java/android/content/pm/SigningInfoException.java
new file mode 100644
index 0000000..a81e07e
--- /dev/null
+++ b/core/java/android/content/pm/SigningInfoException.java
@@ -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 android.content.pm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+/**
+ * Indicates an error when verifying the
+ * <a href="https://source.android.com/docs/security/features/apksigning">app signing</a>
+ * information.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class SigningInfoException extends Exception {
+    private final int mCode;
+
+    /** @hide */
+    public SigningInfoException(int code, @NonNull String message, @Nullable Throwable cause) {
+        super(message, cause);
+        mCode = code;
+    }
+
+    /**
+     * Returns a code representing the cause, in one of the installation parse return codes in
+     * {@link PackageManager}.
+     */
+    @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+    public int getCode() {
+        return mCode;
+    }
+}
diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
index ba089f7..35e5c44 100644
--- a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
+++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java
@@ -53,7 +53,14 @@
      * Callback to indicate that all the requested dependencies have been resolved and their
      * sessions created. See {@link  DependencyInstallerService#onDependenciesRequired}.
      *
+     * The system will wait for the sessions to be installed before resuming the original session
+     * which requested dependency installation.
+     *
+     * If any of the session fails to install, the system may fail the original session. The caller
+     * is expected to handle clean up of any other pending sessions remanining.
+     *
      * @param sessionIds the install session IDs for all requested dependencies
+     * @throws IllegalArgumentException if session id doesn't exist or has already failed.
      */
     public void onAllDependenciesResolved(@NonNull int[] sessionIds) {
         try {
diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
index 92d1d9e..e4cf55d 100644
--- a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
+++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl
@@ -24,7 +24,7 @@
 *
 * {@hide}
 */
-oneway interface IDependencyInstallerCallback {
+interface IDependencyInstallerCallback {
     /**
      * Callback to indicate that all the requested dependencies have been resolved and have been
      * committed for installation. See {@link  DependencyInstallerService#onDependenciesRequired}.
@@ -38,4 +38,4 @@
      * and any associated sessions have been abandoned.
      */
     void onFailureToResolveAllDependencies();
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index fbe581c..dfeee2a 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -341,6 +341,7 @@
     description: "Feature flag to introduce a new way to change the launcher badging."
     bug: "364760703"
     is_fixed_read_only: true
+    is_exported: true
 }
 
 flag {
@@ -353,13 +354,6 @@
 }
 
 flag {
-    name: "support_minor_versions_in_minsdkversion"
-    namespace: "package_manager_service"
-    description: "Block app installations that specify an incompatible minor SDK version"
-    bug: "377302905"
-}
-
-flag {
     name: "app_compat_option_16kb"
     is_exported: true
     namespace: "devoptions_settings"
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 813208d..f29e2e8 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -341,6 +341,39 @@
   is_fixed_read_only: true
 }
 
+flag {
+    name: "cache_user_start_realtime_read_only"
+    namespace: "multiuser"
+    description: "Cache getUserStartRealtime to avoid unnecessary binder calls"
+    bug: "350416205"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+  }
+  is_fixed_read_only: true
+}
+
+flag {
+    name: "cache_user_unlock_realtime_read_only"
+    namespace: "multiuser"
+    description: "Cache getUserUnlockRealtime to avoid unnecessary binder calls"
+    bug: "350421407"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+  }
+  is_fixed_read_only: true
+}
+
+flag {
+    name: "cache_user_restrictions_read_only"
+    namespace: "multiuser"
+    description: "Cache hasUserRestriction to avoid unnecessary binder calls"
+    bug: "350419621"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+  }
+  is_fixed_read_only: true
+}
+
 # This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile.
 flag {
     name: "enable_private_space_features"
@@ -560,4 +593,5 @@
     namespace: "profile_experiences"
     description: "Add support for LauncherUserInfo configs"
     bug: "346553745"
+    is_exported: true
 }
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 7254203..18a45d8d 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -595,9 +595,6 @@
                                     /*allowDuplicates=*/ true);
                             break;
                         case TAG_USES_STATIC_LIBRARY:
-                            if (!android.content.pm.Flags.sdkDependencyInstaller()) {
-                                break;
-                            }
                             String usesStaticLibName = parser.getAttributeValue(
                                     ANDROID_RES_NAMESPACE, "name");
                             long usesStaticLibVersion = parser.getAttributeIntValue(
@@ -666,7 +663,7 @@
                                     SharedLibraryInfo.TYPE_SDK_PACKAGE));
                             break;
                         case TAG_STATIC_LIBRARY:
-                            isSdkLibrary = true;
+                            isStaticLibrary = true;
                             // Mirrors ParsingPackageUtils#parseStaticLibrary until lite and full
                             // parsing are combined
                             String staticLibName = parser.getAttributeValue(
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index 153dd9a..e30f871 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -330,6 +330,36 @@
     }
 
     /**
+     * Check if a package is compatible with this platform with regards to its
+     * its minSdkVersionFull.
+     *
+     * @param minSdkVersionFullString    A string representation of a major.minor version,
+     *                                   e.g. "12.34"
+     * @param platformMinSdkVersionFull The major and minor version of the platform, i.e. the value
+     *                                  of Build.VERSION.SDK_INT_FULL
+     * @param input                     A ParseInput object to report success or failure
+     */
+    public static ParseResult<Void> verifyMinSdkVersionFull(@NonNull String minSdkVersionFullString,
+            int platformMinSdkVersionFull, @NonNull ParseInput input) {
+        int minSdkVersionFull;
+        try {
+            minSdkVersionFull = Build.parseFullVersion(minSdkVersionFullString);
+        } catch (IllegalStateException e) {
+            return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                    e.getMessage());
+        }
+        if (minSdkVersionFull <= platformMinSdkVersionFull) {
+            return input.success(null);
+        }
+        return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
+                "Requires newer sdk version "
+                + Build.fullVersionToString(minSdkVersionFull)
+                + " (current version is "
+                + Build.fullVersionToString(platformMinSdkVersionFull)
+                + ")");
+    }
+
+    /**
      * Computes the targetSdkVersion to use at runtime. If the package is not compatible with this
      * platform, populates {@code outError[0]} with an error message.
      * <p>
diff --git a/core/java/android/content/pm/xr.aconfig b/core/java/android/content/pm/xr.aconfig
index 61835c1..a26f48e 100644
--- a/core/java/android/content/pm/xr.aconfig
+++ b/core/java/android/content/pm/xr.aconfig
@@ -6,4 +6,5 @@
     name: "xr_manifest_entries"
     description: "Adds manifest entries used by Android XR"
     bug: "364416355"
+    is_exported: true
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/DisplayLuts.java b/core/java/android/hardware/DisplayLuts.java
index 6343ba1..0abb30f 100644
--- a/core/java/android/hardware/DisplayLuts.java
+++ b/core/java/android/hardware/DisplayLuts.java
@@ -177,6 +177,8 @@
                     return "SAMPLING_KEY_RGB";
                 case LutProperties.SAMPLING_KEY_MAX_RGB:
                     return "SAMPLING_KEY_MAX_RGB";
+                case LutProperties.SAMPLING_KEY_CIE_Y:
+                    return "SAMPLING_KEY_CIE_Y";
                 default:
                     return "";
             }
diff --git a/core/java/android/hardware/LutProperties.java b/core/java/android/hardware/LutProperties.java
index bf40a41..abb303a 100644
--- a/core/java/android/hardware/LutProperties.java
+++ b/core/java/android/hardware/LutProperties.java
@@ -44,7 +44,8 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"SAMPLING_KEY_"}, value = {
         SAMPLING_KEY_RGB,
-        SAMPLING_KEY_MAX_RGB
+        SAMPLING_KEY_MAX_RGB,
+        SAMPLING_KEY_CIE_Y
     })
     public @interface SamplingKey {
     }
@@ -57,6 +58,10 @@
     @FlaggedApi(Flags.FLAG_LUTS_API)
     public static final int SAMPLING_KEY_MAX_RGB = 1;
 
+    /** use y of CIE XYZ as the gain value of a lut */
+    @FlaggedApi(Flags.FLAG_LUTS_API)
+    public static final int SAMPLING_KEY_CIE_Y = 2;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 7a23033..73b6417 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -53,6 +53,7 @@
   namespace: "biometrics_framework"
   description: "This flag is for API changes related to Identity Check"
   bug: "373424727"
+  is_exported: true
 }
 
 flag {
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 3cf508a..58fe477 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -1066,6 +1066,7 @@
                     case ImageFormat.YUV_420_888:
                     case ImageFormat.JPEG:
                     case ImageFormat.JPEG_R:
+                    case ImageFormat.DEPTH_JPEG:
                     case ImageFormat.YCBCR_P010:
                         break;
                     default:
@@ -1096,9 +1097,10 @@
                     // processed YUV_420 buffers.
                     return getSupportedSizes(
                             extenders.second.getSupportedPostviewResolutions(sz), format);
-                }  else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
-                    // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic
-                    // extension case
+                }  else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010 ||
+                        (Flags.depthJpegExtensions() && (format == ImageFormat.DEPTH_JPEG))) {
+                    // DepthJpeg/Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the
+                    // basic extension case
                     return new ArrayList<>();
                 } else {
                     throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1194,8 +1196,8 @@
      *
      * <p>Device-specific extensions currently support at most three
      * multi-frame capture surface formats. ImageFormat.JPEG will be supported by all
-     * extensions while ImageFormat.YUV_420_888, ImageFormat.JPEG_R, or ImageFormat.YCBCR_P010
-     * may or may not be supported.</p>
+     * extensions while ImageFormat.YUV_420_888, ImageFormat.JPEG_R, ImageFormat.YCBCR_P010 or
+     * ImageFormat.DEPTH_JPEG may or may not be supported.</p>
      *
      * @param extension the extension type
      * @param format    device-specific extension output format
@@ -1203,7 +1205,8 @@
      * supported.
      * @throws IllegalArgumentException in case of format different from ImageFormat.JPEG,
      *                                  ImageFormat.YUV_420_888, ImageFormat.JPEG_R,
-     *                                  ImageFormat.YCBCR_P010; or unsupported extension.
+     *                                  ImageFormat.DEPTH_JPEG, ImageFormat.YCBCR_P010; or
+     *                                  unsupported extension.
      */
     public @NonNull
     List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
@@ -1227,6 +1230,7 @@
                         case ImageFormat.YUV_420_888:
                         case ImageFormat.JPEG:
                         case ImageFormat.JPEG_R:
+                        case ImageFormat.DEPTH_JPEG:
                         case ImageFormat.YCBCR_P010:
                             break;
                         default:
@@ -1260,8 +1264,9 @@
                         } else {
                             return generateSupportedSizes(null, format, streamMap);
                         }
-                    } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
-                        // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the
+                    } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010 ||
+                            (Flags.depthJpegExtensions() && (format == ImageFormat.DEPTH_JPEG))) {
+                        // DepthJpeg/Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the
                         // basic extension case
                         return new ArrayList<>();
                     } else {
@@ -1292,7 +1297,8 @@
      * or null if no capture latency info can be provided
      * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG},
      *                                  {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R}
-     *                                  {@link ImageFormat#YCBCR_P010};
+     *                                  {@link ImageFormat#YCBCR_P010},
+     *                                  {@link ImageFormat#DEPTH_JPEG};
      *                                  or unsupported extension.
      */
     public @Nullable Range<Long> getEstimatedCaptureLatencyRangeMillis(@Extension int extension,
@@ -1301,6 +1307,7 @@
             case ImageFormat.YUV_420_888:
             case ImageFormat.JPEG:
             case ImageFormat.JPEG_R:
+            case ImageFormat.DEPTH_JPEG:
             case ImageFormat.YCBCR_P010:
                 //No op
                 break;
@@ -1349,8 +1356,9 @@
                     // specific and cannot be estimated accurately enough.
                     return  null;
                 }
-                if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
-                    // JpegR/UltraHDR + YCBCR_P010 is not supported for basic extensions
+                if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010 ||
+                        (Flags.depthJpegExtensions() && (format == ImageFormat.DEPTH_JPEG))) {
+                    // DepthJpeg/JpegR/UltraHDR + YCBCR_P010 is not supported for basic extensions
                     return null;
                 }
 
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 266efb7..aba2345 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1699,17 +1699,18 @@
         }
 
         if (context != null) {
-            final ActivityManager activityManager =
-                    context.getSystemService(ActivityManager.class);
-            for (ActivityManager.AppTask appTask : activityManager.getAppTasks()) {
-                final TaskInfo taskInfo = appTask.getTaskInfo();
-                final int freeformCameraCompatMode =
-                        taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode;
-                if (freeformCameraCompatMode != 0
-                        && taskInfo.topActivity != null
-                        && taskInfo.topActivity.getPackageName().equals(packageName)) {
-                    // WindowManager has requested rotation override.
-                    return getRotationOverrideForCompatFreeform(freeformCameraCompatMode);
+            final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
+            if (activityManager != null) {
+                for (ActivityManager.AppTask appTask : activityManager.getAppTasks()) {
+                    final TaskInfo taskInfo = appTask.getTaskInfo();
+                    final int freeformCameraCompatMode = taskInfo.appCompatTaskInfo
+                            .cameraCompatTaskInfo.freeformCameraCompatMode;
+                    if (freeformCameraCompatMode != 0
+                            && taskInfo.topActivity != null
+                            && taskInfo.topActivity.getPackageName().equals(packageName)) {
+                        // WindowManager has requested rotation override.
+                        return getRotationOverrideForCompatFreeform(freeformCameraCompatMode);
+                    }
                 }
             }
         }
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
index 7e42f43..29eaab8 100644
--- a/core/java/android/hardware/camera2/TotalCaptureResult.java
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -86,7 +86,8 @@
         mPhysicalCaptureResults = new HashMap<String, TotalCaptureResult>();
         for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) {
             TotalCaptureResult physicalResult = new TotalCaptureResult(
-                    onePhysicalResult.getCameraId(), onePhysicalResult.getCameraMetadata(),
+                    onePhysicalResult.getCameraId(),
+                    onePhysicalResult.getCameraMetadata(),
                     parent, extras, /*partials*/null, sessionId, new PhysicalCaptureResultInfo[0]);
             mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(),
                     physicalResult);
@@ -115,7 +116,8 @@
         mPhysicalCaptureResults = new HashMap<String, TotalCaptureResult>();
         for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) {
             TotalCaptureResult physicalResult = new TotalCaptureResult(
-                    onePhysicalResult.getCameraId(), onePhysicalResult.getCameraMetadata(),
+                    onePhysicalResult.getCameraId(),
+                    onePhysicalResult.getCameraMetadata(),
                     parent, requestId, frameNumber, /*partials*/null, sessionId,
                     new PhysicalCaptureResultInfo[0]);
             mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(),
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index fc03b51..d511e9f 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -186,12 +186,12 @@
 
         HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>();
 
-        IntArray supportedCaptureOutputFormats =
-                new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
-        supportedCaptureOutputFormats.addAll(
-                CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
-        supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
-        for (int format : supportedCaptureOutputFormats.toArray()) {
+        Integer[] supportedCaptureOutputFormats =
+                new Integer[CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.size()];
+        supportedCaptureOutputFormats =
+                CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.toArray(
+                        supportedCaptureOutputFormats);
+        for (int format : supportedCaptureOutputFormats) {
             List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
                     config.getExtension(), format);
             if (supportedSizes != null) {
@@ -230,7 +230,7 @@
             Size burstCaptureSurfaceSize =
                     new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight);
             HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>();
-            for (int format : supportedCaptureOutputFormats.toArray()) {
+            for (int format : supportedCaptureOutputFormats) {
                 List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes(
                         config.getExtension(), burstCaptureSurfaceSize, format);
                 if (supportedSizesPostview != null) {
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index ea70abb..34c0f7b 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -34,6 +34,7 @@
 import android.hardware.camera2.CameraExtensionCharacteristics;
 import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CameraMetadataInfo;
 import android.hardware.camera2.CameraOfflineSession;
 import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
@@ -59,6 +60,8 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.os.SystemClock;
@@ -129,6 +132,8 @@
     final Object mInterfaceLock = new Object(); // access from this class and Session only!
     private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
 
+    private long mFMQReader; // native fmq reader ptr
+
     private final StateCallback mDeviceCallback;
     private volatile StateCallbackKK mSessionStateCallback;
     private final Executor mDeviceExecutor;
@@ -476,6 +481,9 @@
             if (mInError) return;
 
             mRemoteDevice = new ICameraDeviceUserWrapper(remoteDevice);
+            Parcel resultParcel = Parcel.obtain();
+            mRemoteDevice.getCaptureResultMetadataQueue().writeToParcel(resultParcel, 0);
+            mFMQReader = nativeCreateFMQReader(resultParcel);
 
             IBinder remoteDeviceBinder = remoteDevice.asBinder();
             // For legacy camera device, remoteDevice is in the same process, and
@@ -1682,6 +1690,7 @@
             if (mRemoteDevice != null || mInError) {
                 mDeviceExecutor.execute(mCallOnClosed);
             }
+            nativeClose(mFMQReader);
 
             mRemoteDevice = null;
         }
@@ -2416,27 +2425,61 @@
                 }
             }
         }
+        private PhysicalCaptureResultInfo[] readMetadata(
+            PhysicalCaptureResultInfo[] srcPhysicalResults) {
+            PhysicalCaptureResultInfo[] retVal =
+                    new PhysicalCaptureResultInfo[srcPhysicalResults.length];
+            int i = 0;
+            long fmqSize = 0;
+            for (PhysicalCaptureResultInfo srcPhysicalResult : srcPhysicalResults) {
+                CameraMetadataNative physicalCameraMetadata = null;
+                if (srcPhysicalResult.getCameraMetadataInfo().getTag() ==
+                        CameraMetadataInfo.fmqSize) {
+                    fmqSize = srcPhysicalResult.getCameraMetadataInfo().getFmqSize();
+                    physicalCameraMetadata =
+                            new CameraMetadataNative(nativeReadResultMetadata(mFMQReader, fmqSize));
+                } else {
+                    physicalCameraMetadata = srcPhysicalResult.getCameraMetadata();
+                }
+                PhysicalCaptureResultInfo physicalResultInfo =
+                        new PhysicalCaptureResultInfo(
+                                srcPhysicalResult.getCameraId(), physicalCameraMetadata);
+                retVal[i] = physicalResultInfo;
+                i++;
+            }
+           return retVal;
+        }
 
         @Override
-        public void onResultReceived(CameraMetadataNative result,
+        public void onResultReceived(CameraMetadataInfo resultInfo,
                 CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])
                 throws RemoteException {
             int requestId = resultExtras.getRequestId();
             long frameNumber = resultExtras.getFrameNumber();
-
-            if (DEBUG) {
-                Log.v(TAG, "Received result frame " + frameNumber + " for id "
-                        + requestId);
-            }
-
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
-
+                PhysicalCaptureResultInfo savedPhysicalResults[] = physicalResults;
+                CameraMetadataNative result;
+                if (resultInfo.getTag() == CameraMetadataInfo.fmqSize) {
+                    CameraMetadataNative fmqMetadata =
+                            new CameraMetadataNative(
+                                    nativeReadResultMetadata(mFMQReader, resultInfo.getFmqSize()));
+                    result = fmqMetadata;
+                } else {
+                    result = resultInfo.getMetadata();
+                }
+                physicalResults = readMetadata(savedPhysicalResults);
+                if (DEBUG) {
+                    Log.v(TAG, "Received result frame " + frameNumber + " for id "
+                            + requestId);
+                }
 
                 // Redirect device callback to the offline session in case we are in the middle
                 // of an offline switch
                 if (mOfflineSessionImpl != null) {
-                    mOfflineSessionImpl.getCallbacks().onResultReceived(result, resultExtras,
+                    CameraMetadataInfo resultInfoOffline = CameraMetadataInfo.metadata(result);
+                    mOfflineSessionImpl.getCallbacks().onResultReceived(resultInfoOffline,
+                            resultExtras,
                             physicalResults);
                     return;
                 }
@@ -2824,6 +2867,11 @@
         }
     }
 
+    private static native long nativeCreateFMQReader(Parcel resultQueue);
+    //TODO: Investigate adding FastNative b/62791857
+    private static native long nativeReadResultMetadata(long ptr, long metadataSize);
+    private static native void nativeClose(long ptr);
+
     @Override
     public @CAMERA_AUDIO_RESTRICTION int getCameraAudioRestriction() throws CameraAccessException {
         synchronized(mInterfaceLock) {
@@ -2870,4 +2918,4 @@
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index ce1609d..ed73e62 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -186,12 +186,12 @@
         }
 
         HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>();
-        IntArray supportedCaptureOutputFormats =
-                new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
-        supportedCaptureOutputFormats.addAll(
-                CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
-        supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
-        for (int format : supportedCaptureOutputFormats.toArray()) {
+        Integer[] supportedCaptureOutputFormats =
+                new Integer[CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.size()];
+        supportedCaptureOutputFormats =
+                CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.toArray(
+                        supportedCaptureOutputFormats);
+        for (int format : supportedCaptureOutputFormats) {
             List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
                     config.getExtension(), format);
             if (supportedSizes != null) {
@@ -223,7 +223,7 @@
             Size burstCaptureSurfaceSize =
                     new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight);
             HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>();
-            for (int format : supportedCaptureOutputFormats.toArray()) {
+            for (int format : supportedCaptureOutputFormats) {
                 List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes(
                         config.getExtension(), burstCaptureSurfaceSize, format);
                 if (supportedSizesPostview != null) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index f91d277..212c909 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -32,11 +32,14 @@
 import android.util.IntArray;
 import android.util.Log;
 import android.util.Size;
+import android.util.SparseIntArray;
 import android.view.Surface;
 
 import com.android.internal.camera.flags.Flags;
 
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executor;
@@ -48,11 +51,16 @@
     public final static int JPEG_DEFAULT_QUALITY = 100;
     public final static int JPEG_DEFAULT_ROTATION = 0;
 
-    public static final int[] SUPPORTED_CAPTURE_OUTPUT_FORMATS = {
-            CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT,
-            ImageFormat.JPEG,
-            ImageFormat.JPEG_R
-    };
+    public static HashSet<Integer> SUPPORTED_CAPTURE_OUTPUT_FORMATS = new HashSet<>();
+
+    static {
+        SUPPORTED_CAPTURE_OUTPUT_FORMATS.addAll(Arrays.asList(
+                CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT, ImageFormat.JPEG,
+                ImageFormat.YCBCR_P010, ImageFormat.JPEG_R ));
+        if (Flags.depthJpegExtensions()) {
+            SUPPORTED_CAPTURE_OUTPUT_FORMATS.add(ImageFormat.DEPTH_JPEG);
+        }
+    }
 
     public static class SurfaceInfo {
         public int mWidth = 0;
@@ -101,6 +109,13 @@
             surfaceInfo.mFormat = ImageFormat.JPEG_R;
             return surfaceInfo;
         }
+        if (Flags.depthJpegExtensions()) {
+            if ((nativeFormat == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB)
+                    && (dataspace == StreamConfigurationMap.HAL_DATASPACE_DYNAMIC_DEPTH)) {
+                surfaceInfo.mFormat = ImageFormat.DEPTH_JPEG;
+                return surfaceInfo;
+            }
+        }
 
         return surfaceInfo;
     }
@@ -125,14 +140,14 @@
     public static Surface getBurstCaptureSurface(
             @NonNull List<OutputConfiguration> outputConfigs,
             @NonNull HashMap<Integer, List<Size>> supportedCaptureSizes) {
-        IntArray supportedCaptureOutputFormats =
-                new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
-        supportedCaptureOutputFormats.addAll(
-                CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
-        supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+        Integer[] supportedCaptureOutputFormats =
+                new Integer[CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.size()];
+        supportedCaptureOutputFormats =
+                CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.toArray(
+                        supportedCaptureOutputFormats);
         for (OutputConfiguration config : outputConfigs) {
             SurfaceInfo surfaceInfo = querySurface(config.getSurface());
-            for (int supportedFormat : supportedCaptureOutputFormats.toArray()) {
+            for (int supportedFormat : supportedCaptureOutputFormats) {
                 if (surfaceInfo.mFormat == supportedFormat) {
                     Size captureSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
                     if (supportedCaptureSizes.containsKey(supportedFormat)) {
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index c0a5928..d7b6f11 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -391,6 +391,18 @@
     }
 
     /**
+     * Take ownership of native metadata
+     */
+    public CameraMetadataNative(long metadataPtr) {
+        super();
+        mMetadataPtr = metadataPtr;
+        if (mMetadataPtr == 0) {
+            throw new OutOfMemoryError("Failed to allocate native CameraMetadata");
+        }
+        updateNativeAllocation();
+    }
+
+    /**
      * Move the contents from {@code other} into a new camera metadata instance.</p>
      *
      * <p>After this call, {@code other} will become empty.</p>
diff --git a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
index 1769c46..e660d6a 100644
--- a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
@@ -28,6 +28,7 @@
 import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CameraMetadataInfo;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.ICameraOfflineSession;
 import android.hardware.camera2.TotalCaptureResult;
@@ -291,10 +292,10 @@
         }
 
         @Override
-        public void onResultReceived(CameraMetadataNative result,
+        public void onResultReceived(CameraMetadataInfo resultInfo,
                 CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])
                 throws RemoteException {
-
+            CameraMetadataNative result = resultInfo.getMetadata();
             int requestId = resultExtras.getRequestId();
             long frameNumber = resultExtras.getFrameNumber();
 
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index 831c75ec..a79e084 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -26,6 +26,7 @@
 import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.utils.ExceptionUtils;
 import android.hardware.camera2.utils.SubmitInfo;
+import android.hardware.common.fmq.MQDescriptor;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -313,4 +314,15 @@
             throw ExceptionUtils.throwAsPublicException(e);
         }
     }
-}
+
+    public MQDescriptor<Byte, Byte> getCaptureResultMetadataQueue() throws CameraAccessException {
+        try {
+            return mRemoteDevice.getCaptureResultMetadataQueue();
+        } catch (ServiceSpecificException e) {
+            throw ExceptionUtils.throwAsPublicException(e);
+        } catch (RemoteException e) {
+            throw ExceptionUtils.throwAsPublicException(e);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java b/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java
index 09619d0..77296d1 100644
--- a/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java
+++ b/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.camera2.impl;
 
+import android.hardware.camera2.CameraMetadataInfo;
 import android.hardware.camera2.impl.CameraMetadataNative;
 
 import android.os.Parcel;
@@ -25,7 +26,7 @@
  */
 public class PhysicalCaptureResultInfo implements Parcelable {
     private String cameraId;
-    private CameraMetadataNative cameraMetadata;
+    private CameraMetadataInfo cameraMetadataInfo;
 
     public static final @android.annotation.NonNull Parcelable.Creator<PhysicalCaptureResultInfo> CREATOR =
             new Parcelable.Creator<PhysicalCaptureResultInfo>() {
@@ -46,7 +47,7 @@
 
     public PhysicalCaptureResultInfo(String cameraId, CameraMetadataNative cameraMetadata) {
         this.cameraId = cameraId;
-        this.cameraMetadata = cameraMetadata;
+        this.cameraMetadataInfo = CameraMetadataInfo.metadata(cameraMetadata);
     }
 
     @Override
@@ -57,13 +58,12 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(cameraId);
-        cameraMetadata.writeToParcel(dest, flags);
+        cameraMetadataInfo.writeToParcel(dest, flags);
     }
 
     public void readFromParcel(Parcel in) {
         cameraId = in.readString();
-        cameraMetadata = new CameraMetadataNative();
-        cameraMetadata.readFromParcel(in);
+        cameraMetadataInfo = CameraMetadataInfo.CREATOR.createFromParcel(in);
     }
 
     public String getCameraId() {
@@ -71,6 +71,11 @@
     }
 
     public CameraMetadataNative getCameraMetadata() {
-        return cameraMetadata;
+        return cameraMetadataInfo.getMetadata();
     }
-}
+
+    public CameraMetadataInfo getCameraMetadataInfo() {
+        return cameraMetadataInfo;
+    }
+
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
index 078b4d4..7efdd6d 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -18,6 +18,7 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -31,6 +32,8 @@
 
 import androidx.annotation.GuardedBy;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -48,6 +51,46 @@
 public class HubEndpoint {
     private static final String TAG = "HubEndpoint";
 
+    /**
+     * Constants describing the outcome of operations through HubEndpoints (like opening/closing of
+     * sessions or stopping of endpoints).
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"REASON_"},
+            value = {
+                REASON_FAILURE,
+                REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED,
+                REASON_CLOSE_ENDPOINT_SESSION_REQUESTED,
+                REASON_ENDPOINT_INVALID,
+                REASON_ENDPOINT_STOPPED,
+            })
+    public @interface Reason {}
+
+    /** Unclassified failure */
+    public static final int REASON_FAILURE = 0;
+
+    // The values 1 and 2 are reserved at the Context Hub HAL but not exposed to apps.
+
+    /** The peer rejected the request to open this endpoint session. */
+    public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3;
+
+    /** The peer closed this endpoint session. */
+    public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4;
+
+    /** The peer endpoint is invalid. */
+    public static final int REASON_ENDPOINT_INVALID = 5;
+
+    /**
+     * The endpoint is now stopped. The app should retrieve the endpoint info using {@link
+     * android.hardware.location.ContextHubManager#findEndpoints} or register updates through
+     * {@link android.hardware.location.ContextHubManager#registerEndpointDiscoveryCallback}
+     * to get notified if the endpoint restarts.
+     */
+    public static final int REASON_ENDPOINT_STOPPED = 6;
+
     private final Object mLock = new Object();
     private final HubEndpointInfo mPendingHubEndpointInfo;
     @Nullable private final IHubEndpointLifecycleCallback mLifecycleCallback;
@@ -173,9 +216,7 @@
 
                     try {
                         mServiceToken.closeSession(
-                                sessionId,
-                                IHubEndpointLifecycleCallback
-                                        .REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
+                                sessionId, REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
                     } catch (RemoteException e) {
                         e.rethrowFromSystemServer();
                     }
@@ -396,9 +437,7 @@
 
         try {
             // Oneway notification to system service
-            serviceToken.closeSession(
-                    session.getId(),
-                    IHubEndpointLifecycleCallback.REASON_CLOSE_ENDPOINT_SESSION_REQUESTED);
+            serviceToken.closeSession(session.getId(), REASON_CLOSE_ENDPOINT_SESSION_REQUESTED);
         } catch (RemoteException e) {
             Log.e(TAG, "closeSession: failed to close session " + session, e);
             e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java
index cf952cb..b3d65c1 100644
--- a/core/java/android/hardware/contexthub/HubEndpointSession.java
+++ b/core/java/android/hardware/contexthub/HubEndpointSession.java
@@ -19,6 +19,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.chre.flags.Flags;
 import android.hardware.location.ContextHubTransaction;
@@ -70,6 +71,7 @@
      *     receiving the response for the message.
      */
     @NonNull
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     public ContextHubTransaction<Void> sendMessage(@NonNull HubMessage message) {
         if (mIsClosed.get()) {
             throw new IllegalStateException("Session is already closed.");
@@ -120,6 +122,7 @@
      * <p>When this function is invoked, the messaging associated with this session is invalidated.
      * All futures messages targeted for this client are dropped.
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     public void close() {
         if (!mIsClosed.getAndSet(true)) {
             mCloseGuard.close();
diff --git a/core/java/android/hardware/contexthub/HubServiceInfo.java b/core/java/android/hardware/contexthub/HubServiceInfo.java
index c7fe77c..a1c52fb 100644
--- a/core/java/android/hardware/contexthub/HubServiceInfo.java
+++ b/core/java/android/hardware/contexthub/HubServiceInfo.java
@@ -17,12 +17,10 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
-import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.chre.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.ParcelableHolder;
 
 import androidx.annotation.NonNull;
 
@@ -76,15 +74,12 @@
     private final int mMajorVersion;
     private final int mMinorVersion;
 
-    @NonNull private final ParcelableHolder mExtendedInfo;
-
     /** @hide */
     public HubServiceInfo(android.hardware.contexthub.Service service) {
         mServiceDescriptor = service.serviceDescriptor;
         mFormat = service.format;
         mMajorVersion = service.majorVersion;
         mMinorVersion = service.minorVersion;
-        mExtendedInfo = service.extendedInfo;
     }
 
     private HubServiceInfo(Parcel in) {
@@ -92,20 +87,17 @@
         mFormat = in.readInt();
         mMajorVersion = in.readInt();
         mMinorVersion = in.readInt();
-        mExtendedInfo = ParcelableHolder.CREATOR.createFromParcel(in);
     }
 
     public HubServiceInfo(
             @NonNull String serviceDescriptor,
             @ServiceFormat int format,
             int majorVersion,
-            int minorVersion,
-            @NonNull ParcelableHolder extendedInfo) {
+            int minorVersion) {
         mServiceDescriptor = serviceDescriptor;
         mFormat = format;
         mMajorVersion = majorVersion;
         mMinorVersion = minorVersion;
-        mExtendedInfo = extendedInfo;
     }
 
     /** Get the unique identifier of this service. See {@link Builder} for more information. */
@@ -134,17 +126,10 @@
         return mMinorVersion;
     }
 
-    /** Get the {@link ParcelableHolder} for the extended information about the service. */
-    @NonNull
-    public ParcelableHolder getExtendedInfo() {
-        return mExtendedInfo;
-    }
-
     /** Parcel implementation details */
     @Override
     public int describeContents() {
-        // Passthrough describeContents flags for mExtendedInfo because we don't have FD otherwise.
-        return mExtendedInfo.describeContents();
+        return 0;
     }
 
     /** Parcel implementation details */
@@ -154,7 +139,6 @@
         dest.writeInt(mFormat);
         dest.writeInt(mMajorVersion);
         dest.writeInt(mMinorVersion);
-        mExtendedInfo.writeToParcel(dest, flags);
     }
 
     /** Builder for a {@link HubServiceInfo} object. */
@@ -165,9 +149,6 @@
         private final int mMajorVersion;
         private final int mMinorVersion;
 
-        private final ParcelableHolder mExtendedInfo =
-                new ParcelableHolder(Parcelable.PARCELABLE_STABILITY_VINTF);
-
         /**
          * Create a builder for {@link HubServiceInfo} with a service descriptor.
          *
@@ -220,20 +201,6 @@
         }
 
         /**
-         * Set the extended information of this service.
-         *
-         * @param extendedInfo Parcelable with extended information about this service. The
-         *     parcelable needs to have at least VINTF stability. Null can be used to clear a
-         *     previously set value.
-         * @throws android.os.BadParcelableException if the parcelable cannot be used.
-         */
-        @NonNull
-        public Builder setExtendedInfo(@Nullable Parcelable extendedInfo) {
-            mExtendedInfo.setParcelable(extendedInfo);
-            return this;
-        }
-
-        /**
          * Build the {@link HubServiceInfo} object.
          *
          * @throws IllegalStateException if the Builder is missing required info.
@@ -244,7 +211,7 @@
                 throw new IllegalStateException("Major and minor version must be set.");
             }
             return new HubServiceInfo(
-                    mServiceDescriptor, mFormat, mMajorVersion, mMinorVersion, mExtendedInfo);
+                    mServiceDescriptor, mFormat, mMajorVersion, mMinorVersion);
         }
     }
 
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpointDiscoveryCallback.aidl b/core/java/android/hardware/contexthub/IContextHubEndpointDiscoveryCallback.aidl
new file mode 100644
index 0000000..245be93
--- /dev/null
+++ b/core/java/android/hardware/contexthub/IContextHubEndpointDiscoveryCallback.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright 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.contexthub;
+
+import android.hardware.contexthub.HubEndpointInfo;
+
+/**
+ * @hide
+ */
+oneway interface IContextHubEndpointDiscoveryCallback {
+    /**
+     * Called when endpoint(s) start.
+     * @param hubEndpointInfoList The list of endpoints that started.
+     */
+    void onEndpointsStarted(in HubEndpointInfo[] hubEndpointInfoList);
+
+    /**
+     * Called when endpoint(s) stopped.
+     * @param hubEndpointInfoList The list of endpoints that started.
+     * @param reason The reason why the endpoints stopped.
+     */
+    void onEndpointsStopped(in HubEndpointInfo[] hubEndpointInfoList, int reason);
+}
diff --git a/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java b/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java
new file mode 100644
index 0000000..a61a7eb
--- /dev/null
+++ b/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java
@@ -0,0 +1,49 @@
+/*
+ * 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.contexthub;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+
+import java.util.List;
+
+/**
+ * Interface for listening to updates about endpoint availability.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public interface IHubEndpointDiscoveryCallback {
+    /**
+     * Called when a list of hub endpoints have started.
+     *
+     * @param discoveryInfoList The list containing hub discovery information.
+     */
+    void onEndpointsStarted(@NonNull List<HubDiscoveryInfo> discoveryInfoList);
+
+    /**
+     * Called when a list of hub endpoints have stopped.
+     *
+     * @param discoveryInfoList The list containing hub discovery information.
+     * @param reason The reason the endpoints stopped.
+     */
+    void onEndpointsStopped(
+            @NonNull List<HubDiscoveryInfo> discoveryInfoList, @HubEndpoint.Reason int reason);
+}
diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
index 4688439..fe449bb 100644
--- a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
+++ b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
@@ -17,15 +17,11 @@
 package android.hardware.contexthub;
 
 import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.chre.flags.Flags;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
 /**
  * Interface for listening to lifecycle events of a hub endpoint.
  *
@@ -34,24 +30,6 @@
 @SystemApi
 @FlaggedApi(Flags.FLAG_OFFLOAD_API)
 public interface IHubEndpointLifecycleCallback {
-    /** Unknown reason. */
-    int REASON_UNSPECIFIED = 0;
-
-    /** The peer rejected the request to open this endpoint session. */
-    int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3;
-
-    /** The peer closed this endpoint session. */
-    int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4;
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-        REASON_UNSPECIFIED,
-        REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED,
-        REASON_CLOSE_ENDPOINT_SESSION_REQUESTED,
-    })
-    @interface EndpointLifecycleReason {}
-
     /**
      * Called when an endpoint is requesting a session be opened with another endpoint.
      *
@@ -78,5 +56,5 @@
      *     used.
      * @param reason The reason why this session was closed.
      */
-    void onSessionClosed(@NonNull HubEndpointSession session, @EndpointLifecycleReason int reason);
+    void onSessionClosed(@NonNull HubEndpointSession session, @HubEndpoint.Reason int reason);
 }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 25327a9..7054c37 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -24,6 +24,7 @@
 import static com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
@@ -70,6 +71,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 
@@ -1841,6 +1843,30 @@
     }
 
     /**
+     * Register a listener to receive display topology updates.
+     * @param executor The executor specifying the thread on which the callbacks will be invoked
+     * @param listener The listener
+     *
+     * @hide
+     */
+    @RequiresPermission(MANAGE_DISPLAYS)
+    public void registerTopologyListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<DisplayTopology> listener) {
+        mGlobal.registerTopologyListener(executor, listener, ActivityThread.currentPackageName());
+    }
+
+    /**
+     * Unregister a display topology listener.
+     * @param listener The listener to unregister
+     *
+     * @hide
+     */
+    @RequiresPermission(MANAGE_DISPLAYS)
+    public void unregisterTopologyListener(@NonNull Consumer<DisplayTopology> listener) {
+        mGlobal.unregisterTopologyListener(listener);
+    }
+
+    /**
      * Listens for changes in available display devices.
      */
     public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 1e66bee..ffa5460 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -23,6 +23,7 @@
 import static android.view.Display.HdrCapabilities.HdrType;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
@@ -73,6 +74,7 @@
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
 
 /**
  * Manager communication with the display manager service on behalf of
@@ -126,7 +128,7 @@
     public static final int EVENT_DISPLAY_REFRESH_RATE_CHANGED = 8;
     public static final int EVENT_DISPLAY_STATE_CHANGED = 9;
 
-    @LongDef(prefix = {"INTERNAL_EVENT_DISPLAY"}, flag = true, value = {
+    @LongDef(prefix = {"INTERNAL_EVENT_FLAG_"}, flag = true, value = {
             INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
             INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
             INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
@@ -134,7 +136,8 @@
             INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
             INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
             INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
-            INTERNAL_EVENT_FLAG_DISPLAY_STATE
+            INTERNAL_EVENT_FLAG_DISPLAY_STATE,
+            INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface InternalEventFlag {}
@@ -147,6 +150,7 @@
     public static final long INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 5;
     public static final long INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE = 1L << 6;
     public static final long INTERNAL_EVENT_FLAG_DISPLAY_STATE = 1L << 7;
+    public static final long INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED = 1L << 8;
 
     @UnsupportedAppUsage
     private static DisplayManagerGlobal sInstance;
@@ -164,6 +168,9 @@
     private final CopyOnWriteArrayList<DisplayListenerDelegate> mDisplayListeners =
             new CopyOnWriteArrayList<>();
 
+    private final CopyOnWriteArrayList<DisplayTopologyListenerDelegate> mTopologyListeners =
+            new CopyOnWriteArrayList<>();
+
     private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<>();
     private final ColorSpace mWideColorSpace;
     private final OverlayProperties mOverlayProperties;
@@ -457,6 +464,18 @@
         }
     }
 
+    private void maybeLogAllTopologyListeners() {
+        if (!extraLogging()) {
+            return;
+        }
+        Slog.i(TAG, "Currently registered display topology listeners:");
+        int i = 0;
+        for (DisplayTopologyListenerDelegate d : mTopologyListeners) {
+            Slog.i(TAG, i + ": " + d);
+            i++;
+        }
+    }
+
     /**
      * Called when there is a display-related window configuration change. Reroutes the event from
      * WindowManager to make sure the {@link Display} fields are up-to-date in the last callback.
@@ -502,9 +521,22 @@
                     | INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
                     | INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
         }
+        if (!mTopologyListeners.isEmpty()) {
+            mask |= INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED;
+        }
         return mask;
     }
 
+    private DisplayTopologyListenerDelegate findTopologyListenerLocked(
+            @NonNull Consumer<DisplayTopology> listener) {
+        for (DisplayTopologyListenerDelegate delegate : mTopologyListeners) {
+            if (delegate.mListener == listener) {
+                return delegate;
+            }
+        }
+        return null;
+    }
+
     private void registerCallbackIfNeededLocked() {
         if (mCallback == null) {
             mCallback = new DisplayManagerCallback();
@@ -1316,6 +1348,9 @@
      */
     @RequiresPermission(MANAGE_DISPLAYS)
     public void setDisplayTopology(DisplayTopology topology) {
+        if (topology == null) {
+            throw new IllegalArgumentException("Topology must not be null");
+        }
         try {
             mDm.setDisplayTopology(topology);
         } catch (RemoteException ex) {
@@ -1323,6 +1358,57 @@
         }
     }
 
+    /**
+     * @see DisplayManager#registerTopologyListener
+     */
+    @RequiresPermission(MANAGE_DISPLAYS)
+    public void registerTopologyListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<DisplayTopology> listener, String packageName) {
+        if (!Flags.displayTopology()) {
+            return;
+        }
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+        if (extraLogging()) {
+            Slog.i(TAG, "Registering display topology listener: packageName=" + packageName);
+        }
+        synchronized (mLock) {
+            DisplayTopologyListenerDelegate delegate = findTopologyListenerLocked(listener);
+            if (delegate == null) {
+                mTopologyListeners.add(new DisplayTopologyListenerDelegate(listener, executor,
+                        packageName));
+                registerCallbackIfNeededLocked();
+                updateCallbackIfNeededLocked();
+            }
+            maybeLogAllTopologyListeners();
+        }
+    }
+
+    /**
+     * @see DisplayManager#unregisterTopologyListener
+     */
+    @RequiresPermission(MANAGE_DISPLAYS)
+    public void unregisterTopologyListener(@NonNull Consumer<DisplayTopology> listener) {
+        if (!Flags.displayTopology()) {
+            return;
+        }
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+        if (extraLogging()) {
+            Slog.i(TAG, "Unregistering display topology listener: " + listener);
+        }
+        synchronized (mLock) {
+            DisplayTopologyListenerDelegate delegate = findTopologyListenerLocked(listener);
+            if (delegate != null) {
+                mTopologyListeners.remove(delegate);
+                updateCallbackIfNeededLocked();
+            }
+        }
+        maybeLogAllTopologyListeners();
+    }
+
     private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
         @Override
         public void onDisplayEvent(int displayId, @DisplayEvent int event) {
@@ -1332,6 +1418,16 @@
             }
             handleDisplayEvent(displayId, event, false /* forceUpdate */);
         }
+
+        @Override
+        public void onTopologyChanged(DisplayTopology topology) {
+            if (DEBUG) {
+                Log.d(TAG, "onTopologyChanged: " + topology);
+            }
+            for (DisplayTopologyListenerDelegate listener : mTopologyListeners) {
+                listener.onTopologyChanged(topology);
+            }
+        }
     }
 
     private static final class DisplayListenerDelegate {
@@ -1507,12 +1603,30 @@
                 mExecutor.execute(mCallback::onStopped);
             }
         }
+    }
 
-        @Override // Binder call
-        public void onRequestedBrightnessChanged(float brightness) {
-            if (mCallback != null) {
-                mExecutor.execute(() -> mCallback.onRequestedBrightnessChanged(brightness));
+    private static final class DisplayTopologyListenerDelegate {
+        private final Consumer<DisplayTopology> mListener;
+        private final Executor mExecutor;
+        private final String mPackageName;
+
+        DisplayTopologyListenerDelegate(@NonNull Consumer<DisplayTopology> listener,
+                @NonNull @CallbackExecutor Executor executor, String packageName) {
+            mExecutor = executor;
+            mListener = listener;
+            mPackageName = packageName;
+        }
+
+        @Override
+        public String toString() {
+            return "DisplayTopologyListener {packageName=" + mPackageName + "}";
+        }
+
+        void onTopologyChanged(DisplayTopology topology) {
+            if (extraLogging()) {
+                Slog.i(TAG, "Sending topology update: " + topology);
             }
+            mExecutor.execute(() -> mListener.accept(topology));
         }
     }
 
diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
index f00c3a5..54d0dd0 100644
--- a/core/java/android/hardware/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -28,6 +28,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.IndentingPrintWriter;
+import android.util.MathUtils;
 import android.util.Pair;
 import android.util.Slog;
 import android.view.Display;
@@ -283,6 +284,154 @@
         normalize();
     }
 
+    /**
+     * Clamp offsets and remove any overlaps between displays.
+     */
+    public void normalize() {
+        if (mRoot == null) {
+            return;
+        }
+        clampOffsets(mRoot);
+
+        Map<TreeNode, RectF> bounds = new HashMap<>();
+        Map<TreeNode, Integer> depths = new HashMap<>();
+        Map<TreeNode, TreeNode> parents = new HashMap<>();
+        getInfo(bounds, depths, parents, mRoot, /* x= */ 0, /* y= */ 0, /* depth= */ 0);
+
+        // Sort the displays first by their depth in the tree, then by the distance of their top
+        // left point from the root display's origin (0, 0). This way we process the displays
+        // starting at the root and we push out a display if necessary.
+        Comparator<TreeNode> comparator = (d1, d2) -> {
+            if (d1 == d2) {
+                return 0;
+            }
+
+            int compareDepths = Integer.compare(depths.get(d1), depths.get(d2));
+            if (compareDepths != 0) {
+                return compareDepths;
+            }
+
+            RectF bounds1 = bounds.get(d1);
+            RectF bounds2 = bounds.get(d2);
+            return Double.compare(Math.hypot(bounds1.left, bounds1.top),
+                    Math.hypot(bounds2.left, bounds2.top));
+        };
+        List<TreeNode> displays = new ArrayList<>(bounds.keySet());
+        displays.sort(comparator);
+
+        for (int i = 1; i < displays.size(); i++) {
+            TreeNode targetDisplay = displays.get(i);
+            TreeNode lastIntersectingSourceDisplay = null;
+            float lastOffsetX = 0;
+            float lastOffsetY = 0;
+
+            for (int j = 0; j < i; j++) {
+                TreeNode sourceDisplay = displays.get(j);
+                RectF sourceBounds = bounds.get(sourceDisplay);
+                RectF targetBounds = bounds.get(targetDisplay);
+
+                if (!RectF.intersects(sourceBounds, targetBounds)) {
+                    continue;
+                }
+
+                // Find the offset by which to move the display. Pick the smaller one among the x
+                // and y axes.
+                float offsetX = targetBounds.left >= 0
+                        ? sourceBounds.right - targetBounds.left
+                        : sourceBounds.left - targetBounds.right;
+                float offsetY = targetBounds.top >= 0
+                        ? sourceBounds.bottom - targetBounds.top
+                        : sourceBounds.top - targetBounds.bottom;
+                if (Math.abs(offsetX) <= Math.abs(offsetY)) {
+                    targetBounds.left += offsetX;
+                    targetBounds.right += offsetX;
+                    // We need to also update the offset in the tree
+                    if (targetDisplay.mPosition == POSITION_TOP
+                            || targetDisplay.mPosition == POSITION_BOTTOM) {
+                        targetDisplay.mOffset += offsetX;
+                    }
+                    offsetY = 0;
+                } else {
+                    targetBounds.top += offsetY;
+                    targetBounds.bottom += offsetY;
+                    // We need to also update the offset in the tree
+                    if (targetDisplay.mPosition == POSITION_LEFT
+                            || targetDisplay.mPosition == POSITION_RIGHT) {
+                        targetDisplay.mOffset += offsetY;
+                    }
+                    offsetX = 0;
+                }
+
+                lastIntersectingSourceDisplay = sourceDisplay;
+                lastOffsetX = offsetX;
+                lastOffsetY = offsetY;
+            }
+
+            // Now re-parent the target display to the last intersecting source display if it no
+            // longer touches its parent.
+            if (lastIntersectingSourceDisplay == null) {
+                // There was no overlap.
+                continue;
+            }
+            TreeNode parent = parents.get(targetDisplay);
+            if (parent == lastIntersectingSourceDisplay) {
+                // The displays are moved in such a way that they're adjacent to the intersecting
+                // display. If the last intersecting display happens to be the parent then we
+                // already know that the display is adjacent to its parent.
+                continue;
+            }
+
+            RectF childBounds = bounds.get(targetDisplay);
+            RectF parentBounds = bounds.get(parent);
+            // Check that the edges are on the same line
+            boolean areTouching = switch (targetDisplay.mPosition) {
+                case POSITION_LEFT -> floatEquals(parentBounds.left, childBounds.right);
+                case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left);
+                case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom);
+                case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top);
+                default -> throw new IllegalStateException(
+                        "Unexpected value: " + targetDisplay.mPosition);
+            };
+            // Check that the offset is within bounds
+            areTouching &= switch (targetDisplay.mPosition) {
+                case POSITION_LEFT, POSITION_RIGHT ->
+                        childBounds.bottom + EPSILON >= parentBounds.top
+                                && childBounds.top <= parentBounds.bottom + EPSILON;
+                case POSITION_TOP, POSITION_BOTTOM ->
+                        childBounds.right + EPSILON >= parentBounds.left
+                                && childBounds.left <= parentBounds.right + EPSILON;
+                default -> throw new IllegalStateException(
+                        "Unexpected value: " + targetDisplay.mPosition);
+            };
+
+            if (!areTouching) {
+                // Re-parent the display.
+                parent.mChildren.remove(targetDisplay);
+                RectF lastIntersectingSourceDisplayBounds =
+                        bounds.get(lastIntersectingSourceDisplay);
+                lastIntersectingSourceDisplay.mChildren.add(targetDisplay);
+
+                if (lastOffsetX != 0) {
+                    targetDisplay.mPosition = lastOffsetX > 0 ? POSITION_RIGHT : POSITION_LEFT;
+                    targetDisplay.mOffset =
+                            childBounds.top - lastIntersectingSourceDisplayBounds.top;
+                } else if (lastOffsetY != 0) {
+                    targetDisplay.mPosition = lastOffsetY > 0 ? POSITION_BOTTOM : POSITION_TOP;
+                    targetDisplay.mOffset =
+                            childBounds.left - lastIntersectingSourceDisplayBounds.left;
+                }
+            }
+        }
+    }
+
+    /**
+     * @return A deep copy of the topology that will not be modified by the system.
+     */
+    public DisplayTopology copy() {
+        TreeNode rootCopy = mRoot == null ? null : mRoot.copy();
+        return new DisplayTopology(rootCopy, mPrimaryDisplayId);
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -434,145 +583,6 @@
     }
 
     /**
-     * Update the topology to remove any overlaps between displays.
-     */
-    @VisibleForTesting
-    public void normalize() {
-        if (mRoot == null) {
-            return;
-        }
-        Map<TreeNode, RectF> bounds = new HashMap<>();
-        Map<TreeNode, Integer> depths = new HashMap<>();
-        Map<TreeNode, TreeNode> parents = new HashMap<>();
-        getInfo(bounds, depths, parents, mRoot, /* x= */ 0, /* y= */ 0, /* depth= */ 0);
-
-        // Sort the displays first by their depth in the tree, then by the distance of their top
-        // left point from the root display's origin (0, 0). This way we process the displays
-        // starting at the root and we push out a display if necessary.
-        Comparator<TreeNode> comparator = (d1, d2) -> {
-            if (d1 == d2) {
-                return 0;
-            }
-
-            int compareDepths = Integer.compare(depths.get(d1), depths.get(d2));
-            if (compareDepths != 0) {
-                return compareDepths;
-            }
-
-            RectF bounds1 = bounds.get(d1);
-            RectF bounds2 = bounds.get(d2);
-            return Double.compare(Math.hypot(bounds1.left, bounds1.top),
-                    Math.hypot(bounds2.left, bounds2.top));
-        };
-        List<TreeNode> displays = new ArrayList<>(bounds.keySet());
-        displays.sort(comparator);
-
-        for (int i = 1; i < displays.size(); i++) {
-            TreeNode targetDisplay = displays.get(i);
-            TreeNode lastIntersectingSourceDisplay = null;
-            float lastOffsetX = 0;
-            float lastOffsetY = 0;
-
-            for (int j = 0; j < i; j++) {
-                TreeNode sourceDisplay = displays.get(j);
-                RectF sourceBounds = bounds.get(sourceDisplay);
-                RectF targetBounds = bounds.get(targetDisplay);
-
-                if (!RectF.intersects(sourceBounds, targetBounds)) {
-                    continue;
-                }
-
-                // Find the offset by which to move the display. Pick the smaller one among the x
-                // and y axes.
-                float offsetX = targetBounds.left >= 0
-                        ? sourceBounds.right - targetBounds.left
-                        : sourceBounds.left - targetBounds.right;
-                float offsetY = targetBounds.top >= 0
-                        ? sourceBounds.bottom - targetBounds.top
-                        : sourceBounds.top - targetBounds.bottom;
-                if (Math.abs(offsetX) <= Math.abs(offsetY)) {
-                    targetBounds.left += offsetX;
-                    targetBounds.right += offsetX;
-                    // We need to also update the offset in the tree
-                    if (targetDisplay.mPosition == POSITION_TOP
-                            || targetDisplay.mPosition == POSITION_BOTTOM) {
-                        targetDisplay.mOffset += offsetX;
-                    }
-                    offsetY = 0;
-                } else {
-                    targetBounds.top += offsetY;
-                    targetBounds.bottom += offsetY;
-                    // We need to also update the offset in the tree
-                    if (targetDisplay.mPosition == POSITION_LEFT
-                            || targetDisplay.mPosition == POSITION_RIGHT) {
-                        targetDisplay.mOffset += offsetY;
-                    }
-                    offsetX = 0;
-                }
-
-                lastIntersectingSourceDisplay = sourceDisplay;
-                lastOffsetX = offsetX;
-                lastOffsetY = offsetY;
-            }
-
-            // Now re-parent the target display to the last intersecting source display if it no
-            // longer touches its parent.
-            if (lastIntersectingSourceDisplay == null) {
-                // There was no overlap.
-                continue;
-            }
-            TreeNode parent = parents.get(targetDisplay);
-            if (parent == lastIntersectingSourceDisplay) {
-                // The displays are moved in such a way that they're adjacent to the intersecting
-                // display. If the last intersecting display happens to be the parent then we
-                // already know that the display is adjacent to its parent.
-                continue;
-            }
-
-            RectF childBounds = bounds.get(targetDisplay);
-            RectF parentBounds = bounds.get(parent);
-            // Check that the edges are on the same line
-            boolean areTouching = switch (targetDisplay.mPosition) {
-                case POSITION_LEFT -> floatEquals(parentBounds.left, childBounds.right);
-                case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left);
-                case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom);
-                case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top);
-                default -> throw new IllegalStateException(
-                        "Unexpected value: " + targetDisplay.mPosition);
-            };
-            // Check that the offset is within bounds
-            areTouching &= switch (targetDisplay.mPosition) {
-                case POSITION_LEFT, POSITION_RIGHT ->
-                        childBounds.bottom + EPSILON >= parentBounds.top
-                                && childBounds.top <= parentBounds.bottom + EPSILON;
-                case POSITION_TOP, POSITION_BOTTOM ->
-                        childBounds.right + EPSILON >= parentBounds.left
-                                && childBounds.left <= parentBounds.right + EPSILON;
-                default -> throw new IllegalStateException(
-                        "Unexpected value: " + targetDisplay.mPosition);
-            };
-
-            if (!areTouching) {
-                // Re-parent the display.
-                parent.mChildren.remove(targetDisplay);
-                RectF lastIntersectingSourceDisplayBounds =
-                        bounds.get(lastIntersectingSourceDisplay);
-                lastIntersectingSourceDisplay.mChildren.add(targetDisplay);
-
-                if (lastOffsetX != 0) {
-                    targetDisplay.mPosition = lastOffsetX > 0 ? POSITION_RIGHT : POSITION_LEFT;
-                    targetDisplay.mOffset =
-                            childBounds.top - lastIntersectingSourceDisplayBounds.top;
-                } else if (lastOffsetY != 0) {
-                    targetDisplay.mPosition = lastOffsetY > 0 ? POSITION_BOTTOM : POSITION_TOP;
-                    targetDisplay.mOffset =
-                            childBounds.left - lastIntersectingSourceDisplayBounds.left;
-                }
-            }
-        }
-    }
-
-    /**
      * Tests whether two brightness float values are within a small enough tolerance
      * of each other.
      * @param a first float to compare
@@ -597,6 +607,24 @@
         return found;
     }
 
+    /**
+     * Ensure that the offsets of all displays within the given tree are within bounds.
+     * @param display The starting node
+     */
+    private void clampOffsets(TreeNode display) {
+        if (display == null) {
+            return;
+        }
+        for (TreeNode child : display.mChildren) {
+            if (child.mPosition == POSITION_LEFT || child.mPosition == POSITION_RIGHT) {
+                child.mOffset = MathUtils.constrain(child.mOffset, -child.mHeight, display.mHeight);
+            } else if (child.mPosition == POSITION_TOP || child.mPosition == POSITION_BOTTOM) {
+                child.mOffset = MathUtils.constrain(child.mOffset, -child.mWidth, display.mWidth);
+            }
+            clampOffsets(child);
+        }
+    }
+
     public static final class TreeNode implements Parcelable {
         public static final int POSITION_LEFT = 0;
         public static final int POSITION_TOP = 1;
@@ -694,6 +722,17 @@
             return Collections.unmodifiableList(mChildren);
         }
 
+        /**
+         * @return A deep copy of the node that will not be modified by the system.
+         */
+        public TreeNode copy() {
+            TreeNode copy = new TreeNode(mDisplayId, mWidth, mHeight, mPosition, mOffset);
+            for (TreeNode child : mChildren) {
+                copy.mChildren.add(child.copy());
+            }
+            return copy;
+        }
+
         @Override
         public String toString() {
             return "Display {id=" + mDisplayId + ", width=" + mWidth + ", height=" + mHeight
diff --git a/core/java/android/hardware/display/IBrightnessListener.aidl b/core/java/android/hardware/display/IBrightnessListener.aidl
new file mode 100644
index 0000000..f5d3743
--- /dev/null
+++ b/core/java/android/hardware/display/IBrightnessListener.aidl
@@ -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 android.hardware.display;
+
+/**
+ * Interface for notifying the display owner about brightness changes.
+ *
+ * @hide
+ */
+oneway interface IBrightnessListener {
+    /**
+     * Called when the display's brightness has changed.
+     *
+     * @param brightness the new brightness of the display. Value of {@code 0.0} indicates the
+     *   minimum supported brightness and value of {@code 1.0} indicates the maximum supported
+     *   brightness.
+     */
+    void onBrightnessChanged(float brightness);
+}
diff --git a/core/java/android/hardware/display/IDisplayManagerCallback.aidl b/core/java/android/hardware/display/IDisplayManagerCallback.aidl
index c50e3fb..d05a1b8 100644
--- a/core/java/android/hardware/display/IDisplayManagerCallback.aidl
+++ b/core/java/android/hardware/display/IDisplayManagerCallback.aidl
@@ -16,7 +16,10 @@
 
 package android.hardware.display;
 
+import android.hardware.display.DisplayTopology;
+
 /** @hide */
 interface IDisplayManagerCallback {
     oneway void onDisplayEvent(int displayId, int event);
+    oneway void onTopologyChanged(in DisplayTopology topology);
 }
diff --git a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl
index 9cc0364..c3490d1 100644
--- a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl
+++ b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl
@@ -38,9 +38,4 @@
      * of the application to release() the virtual display.
      */
     void onStopped();
-
-    /**
-     * Called when the virtual display's requested brightness has changed.
-     */
-    void onRequestedBrightnessChanged(float brightness);
 }
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 3b573ea..32b6405 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -16,8 +16,6 @@
 package android.hardware.display;
 
 import android.annotation.FlaggedApi;
-import android.annotation.FloatRange;
-import android.annotation.SystemApi;
 import android.view.Display;
 import android.view.Surface;
 
@@ -166,25 +164,5 @@
          * of the application to release() the virtual display.
          */
         public void onStopped() { }
-
-        /**
-         * Called when the requested brightness of the display has changed.
-         *
-         * <p>The system may adjust the display's brightness based on user or app activity. This
-         * callback will only be invoked if the display has an explicitly specified default
-         * brightness value.</p>
-         *
-         * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of
-         * {@code 1.0} indicates the maximum supported brightness.</p>
-         *
-         * @see android.view.View#setKeepScreenOn(boolean)
-         * @see android.view.WindowManager.LayoutParams#screenBrightness
-         * @see VirtualDisplayConfig.Builder#setDefaultBrightness(float)
-         * @hide
-         */
-        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
-        @SystemApi
-        public void onRequestedBrightnessChanged(
-                @FloatRange(from = 0.0f, to = 1.0f) float brightness) {}
     }
 }
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 57d9d28..7257055 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -18,11 +18,13 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.hardware.display.DisplayManager.VirtualDisplayFlag;
 import android.media.projection.MediaProjection;
@@ -38,6 +40,7 @@
 import java.util.Collections;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * Holds configuration used to create {@link VirtualDisplay} instances.
@@ -63,6 +66,8 @@
     private final DisplayCutout mDisplayCutout;
     private final boolean mIgnoreActivitySizeRestrictions;
     private final float mDefaultBrightness;
+    private final float mDimBrightness;
+    private final IBrightnessListener mBrightnessListener;
 
     private VirtualDisplayConfig(
             @NonNull String name,
@@ -79,7 +84,9 @@
             boolean isHomeSupported,
             @Nullable DisplayCutout displayCutout,
             boolean ignoreActivitySizeRestrictions,
-            @FloatRange(from = 0.0f, to = 1.0f) float defaultBrightness) {
+            @FloatRange(from = 0.0f, to = 1.0f) float defaultBrightness,
+            @FloatRange(from = 0.0f, to = 1.0f) float dimBrightness,
+            IBrightnessListener brightnessListener) {
         mName = name;
         mWidth = width;
         mHeight = height;
@@ -95,6 +102,8 @@
         mDisplayCutout = displayCutout;
         mIgnoreActivitySizeRestrictions = ignoreActivitySizeRestrictions;
         mDefaultBrightness = defaultBrightness;
+        mDimBrightness = dimBrightness;
+        mBrightnessListener = brightnessListener;
     }
 
     /**
@@ -167,14 +176,33 @@
      * indicates the maximum supported brightness.</p>
      *
      * @see Builder#setDefaultBrightness(float)
-     * @hide
      */
     @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
-    @SystemApi
     public @FloatRange(from = 0.0f, to = 1.0f) float getDefaultBrightness() {
         return mDefaultBrightness;
     }
 
+    /**
+     * Returns the dim brightness of the display.
+     *
+     * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of {@code 1.0}
+     * indicates the maximum supported brightness.</p>
+     *
+     * @see Builder#setDimBrightness(float)
+     */
+    @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    public @FloatRange(from = 0.0f, to = 1.0f) float getDimBrightness() {
+        return mDimBrightness;
+    }
+
+    /**
+     * Returns the listener to get notified about changes in the display brightness.
+     * @hide
+     */
+    @Nullable
+    public IBrightnessListener getBrightnessListener() {
+        return mBrightnessListener;
+    }
 
     /**
      * Returns the unique identifier for the display. Shouldn't be displayed to the user.
@@ -266,6 +294,8 @@
         DisplayCutout.ParcelableWrapper.writeCutoutToParcel(mDisplayCutout, dest, flags);
         dest.writeBoolean(mIgnoreActivitySizeRestrictions);
         dest.writeFloat(mDefaultBrightness);
+        dest.writeFloat(mDimBrightness);
+        dest.writeStrongBinder(mBrightnessListener != null ? mBrightnessListener.asBinder() : null);
     }
 
     @Override
@@ -294,7 +324,9 @@
                 && mIsHomeSupported == that.mIsHomeSupported
                 && mIgnoreActivitySizeRestrictions == that.mIgnoreActivitySizeRestrictions
                 && Objects.equals(mDisplayCutout, that.mDisplayCutout)
-                && mDefaultBrightness == that.mDefaultBrightness;
+                && mDefaultBrightness == that.mDefaultBrightness
+                && mDimBrightness == that.mDimBrightness
+                && Objects.equals(mBrightnessListener, that.mBrightnessListener);
     }
 
     @Override
@@ -303,7 +335,8 @@
                 mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId,
                 mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories,
                 mRequestedRefreshRate, mIsHomeSupported, mDisplayCutout,
-                mIgnoreActivitySizeRestrictions, mDefaultBrightness);
+                mIgnoreActivitySizeRestrictions, mDefaultBrightness, mDimBrightness,
+                mBrightnessListener);
         return hashCode;
     }
 
@@ -326,6 +359,7 @@
                 + " mDisplayCutout=" + mDisplayCutout
                 + " mIgnoreActivitySizeRestrictions=" + mIgnoreActivitySizeRestrictions
                 + " mDefaultBrightness=" + mDefaultBrightness
+                + " mDimBrightness=" + mDimBrightness
                 + ")";
     }
 
@@ -345,6 +379,43 @@
         mDisplayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(in);
         mIgnoreActivitySizeRestrictions = in.readBoolean();
         mDefaultBrightness = in.readFloat();
+        mDimBrightness = in.readFloat();
+        mBrightnessListener = IBrightnessListener.Stub.asInterface(in.readStrongBinder());
+    }
+
+    /**
+     * Listener for display brightness changes.
+     */
+    @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    public interface BrightnessListener {
+
+        /**
+         * Called when the display's brightness has changed.
+         *
+         * @param brightness the new brightness of the display. Value of {@code 0.0} indicates the
+         *   minimum supported brightness and value of {@code 1.0} indicates the maximum supported
+         *   brightness.
+         */
+        void onBrightnessChanged(@FloatRange(from = 0.0f, to = 1.0f) float brightness);
+    }
+
+    private static class BrightnessListenerDelegate extends IBrightnessListener.Stub {
+
+        @NonNull
+        private final Executor mExecutor;
+        @NonNull
+        private final BrightnessListener mListener;
+
+        BrightnessListenerDelegate(@NonNull @CallbackExecutor Executor executor,
+                @NonNull BrightnessListener listener) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        @Override
+        public void onBrightnessChanged(float brightness) {
+            mExecutor.execute(() -> mListener.onBrightnessChanged(brightness));
+        }
     }
 
     @NonNull
@@ -380,6 +451,8 @@
         private DisplayCutout mDisplayCutout = null;
         private boolean mIgnoreActivitySizeRestrictions = false;
         private float mDefaultBrightness = 0.0f;
+        private float mDimBrightness = PowerManager.BRIGHTNESS_INVALID;
+        private IBrightnessListener mBrightnessListener = null;
 
         /**
          * Creates a new Builder.
@@ -575,24 +648,21 @@
          * Sets the default brightness of the display.
          *
          * <p>The system will use this brightness value whenever the display should be bright, i.e.
-         * it is powered on and not dimmed due to user activity or app activity.</p>
+         * it is powered on and not modified due to user activity or app activity.</p>
          *
          * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of
          * {@code 1.0} indicates the maximum supported brightness.</p>
          *
          * <p>If unset, defaults to {@code 0.0}</p>
          *
+         * @throws IllegalArgumentException if the brightness is outside the valid range [0.0, 1.0]
          * @see android.view.View#setKeepScreenOn(boolean)
-         * @see Builder#setDefaultBrightness(float)
-         * @see VirtualDisplay.Callback#onRequestedBrightnessChanged(float)
-         * @hide
+         * @see #setBrightnessListener(Executor, BrightnessListener)
          */
         @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
-        @SystemApi
         @NonNull
         public Builder setDefaultBrightness(@FloatRange(from = 0.0f, to = 1.0f) float brightness) {
-            if (brightness < PowerManager.BRIGHTNESS_MIN
-                    || brightness > PowerManager.BRIGHTNESS_MAX) {
+            if (!isValidBrightness(brightness)) {
                 throw new IllegalArgumentException(
                         "Virtual display default brightness must be in range [0.0, 1.0]");
             }
@@ -601,10 +671,65 @@
         }
 
         /**
+         * Sets the dim brightness of the display.
+         *
+         * <p>The system will use this brightness value whenever the display should be dim, i.e.
+         * it is powered on and dimmed due to user activity or app activity.</p>
+         *
+         * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of
+         * {@code 1.0} indicates the maximum supported brightness.</p>
+         *
+         * <p>If set, the default brightness must also be set to a value greater or equal to the
+         * dim brightness. If unset, defaults to the system default.</p>
+         *
+         * @throws IllegalArgumentException if the brightness is outside the valid range [0.0, 1.0]
+         * @see Builder#setDefaultBrightness(float)
+         * @see #setBrightnessListener(Executor, BrightnessListener)
+         */
+        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+        @NonNull
+        public Builder setDimBrightness(@FloatRange(from = 0.0f, to = 1.0f) float brightness) {
+            if (!isValidBrightness(brightness)) {
+                throw new IllegalArgumentException(
+                        "Virtual display dim brightness must be in range [0.0, 1.0]");
+            }
+            mDimBrightness = brightness;
+            return this;
+        }
+
+        /**
+         * Sets the listener to get notified about changes in the display brightness.
+         *
+         * @param executor The executor where the callback is executed on.
+         * @param listener The listener to get notified when the display brightness has changed.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder") // The hidden getter returns the AIDL object
+        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+        @NonNull
+        public Builder setBrightnessListener(@NonNull @CallbackExecutor Executor executor,
+                @NonNull BrightnessListener listener) {
+            mBrightnessListener = new BrightnessListenerDelegate(
+                    Objects.requireNonNull(executor), Objects.requireNonNull(listener));
+            return this;
+        }
+
+        private boolean isValidBrightness(float brightness) {
+            return !Float.isNaN(brightness) && PowerManager.BRIGHTNESS_MIN <= brightness
+                    && brightness <= PowerManager.BRIGHTNESS_MAX;
+        }
+
+        /**
          * Builds the {@link VirtualDisplayConfig} instance.
+         *
+         * @throws IllegalArgumentException if the dim brightness is set to a value greater than
+         *   the default brightness.
          */
         @NonNull
         public VirtualDisplayConfig build() {
+            if (isValidBrightness(mDimBrightness) && mDimBrightness > mDefaultBrightness) {
+                throw new IllegalArgumentException(
+                        "The dim brightness must not be greater than the default brightness");
+            }
             return new VirtualDisplayConfig(
                     mName,
                     mWidth,
@@ -620,7 +745,9 @@
                     mIsHomeSupported,
                     mDisplayCutout,
                     mIgnoreActivitySizeRestrictions,
-                    mDefaultBrightness);
+                    mDefaultBrightness,
+                    mDimBrightness,
+                    mBrightnessListener);
         }
     }
 }
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 114459e..3f9317a 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -27,6 +27,7 @@
 import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
 import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling;
 import static com.android.hardware.input.Flags.mouseSwapPrimaryButton;
+import static com.android.hardware.input.Flags.touchpadSystemGestureDisable;
 import static com.android.hardware.input.Flags.touchpadTapDragging;
 import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut;
 import static com.android.hardware.input.Flags.touchpadVisualizer;
@@ -374,6 +375,15 @@
     }
 
     /**
+     * Returns true if the feature flag for disabling system gestures on touchpads is enabled.
+     *
+     * @hide
+     */
+    public static boolean isTouchpadSystemGestureDisableFeatureFlagEnabled() {
+        return touchpadSystemGestureDisable();
+    }
+
+    /**
      * Returns true if the feature flag for touchpad visualizer is enabled.
      *
      * @hide
@@ -530,6 +540,40 @@
     }
 
     /**
+     * Returns true if system gestures (three- and four-finger swipes) should be enabled for
+     * touchpads.
+     *
+     * @param context The application context.
+     * @return Whether system gestures on touchpads are enabled
+     *
+     * @hide
+     */
+    public static boolean useTouchpadSystemGestures(@NonNull Context context) {
+        if (!isTouchpadSystemGestureDisableFeatureFlagEnabled()) {
+            return true;
+        }
+        return Settings.System.getIntForUser(context.getContentResolver(),
+                Settings.System.TOUCHPAD_SYSTEM_GESTURES, 1, UserHandle.USER_CURRENT) == 1;
+    }
+
+    /**
+     * Sets whether system gestures are enabled for touchpads.
+     *
+     * @param context The application context.
+     * @param enabled True to enable system gestures.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+    public static void setTouchpadSystemGesturesEnabled(@NonNull Context context, boolean enabled) {
+        if (!isTouchpadSystemGestureDisableFeatureFlagEnabled()) {
+            return;
+        }
+        Settings.System.putIntForUser(context.getContentResolver(),
+                Settings.System.TOUCHPAD_SYSTEM_GESTURES, enabled ? 1 : 0, UserHandle.USER_CURRENT);
+    }
+
+    /**
      * Whether a pointer icon will be shown over the location of a stylus pointer.
      *
      * @hide
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 0c89059..ebb6172 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -149,6 +149,13 @@
 }
 
 flag {
+    name: "touchpad_system_gesture_disable"
+    namespace: "input"
+    description: "Adds an accessibility setting to disable system navigation gestures (3- and 4-finger swipes) on touchpads"
+    bug: "353947750"
+}
+
+flag {
     name: "enable_customizable_input_gestures"
     namespace: "input"
     description: "Enables keyboard shortcut customization support"
@@ -160,6 +167,7 @@
     namespace: "input"
     description: "Enables new 25Q2 keycodes"
     bug: "365920375"
+    is_exported: true
 }
 
 flag {
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 426cd69f..3a74130 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -38,6 +38,8 @@
 import android.hardware.contexthub.HubEndpoint;
 import android.hardware.contexthub.HubEndpointInfo;
 import android.hardware.contexthub.HubServiceInfo;
+import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
+import android.hardware.contexthub.IHubEndpointDiscoveryCallback;
 import android.hardware.contexthub.IHubEndpointLifecycleCallback;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -49,7 +51,9 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 
 /**
@@ -202,6 +206,10 @@
     private Callback mCallback;
     private Handler mCallbackHandler;
 
+    /** A map of endpoint discovery callbacks currently registered */
+    private Map<IHubEndpointDiscoveryCallback, IContextHubEndpointDiscoveryCallback>
+            mDiscoveryCallbacks = new ConcurrentHashMap<>();
+
     /**
      * @deprecated Use {@code mCallback} instead.
      */
@@ -694,8 +702,6 @@
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @NonNull
     public List<HubDiscoveryInfo> findEndpoints(long endpointId) {
-        // TODO(b/379323274): Consider improving these getters to avoid racing with nano app load
-        //  timing.
         try {
             List<HubEndpointInfo> endpointInfos = mService.findEndpoints(endpointId);
             List<HubDiscoveryInfo> results = new ArrayList<>(endpointInfos.size());
@@ -720,8 +726,6 @@
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @NonNull
     public List<HubDiscoveryInfo> findEndpoints(@NonNull String serviceDescriptor) {
-        // TODO(b/379323274): Consider improving these getters to avoid racing with nano app load
-        //  timing.
         if (serviceDescriptor.isBlank()) {
             throw new IllegalArgumentException("Invalid service descriptor: " + serviceDescriptor);
         }
@@ -744,6 +748,188 @@
     }
 
     /**
+     * Creates an interface to invoke endpoint discovery callbacks to send down to the service.
+     *
+     * @param callback the callback to invoke at the client process
+     * @param executor the executor to invoke callbacks for this client
+     * @return the callback interface
+     */
+    private IContextHubEndpointDiscoveryCallback createDiscoveryCallback(
+            IHubEndpointDiscoveryCallback callback,
+            Executor executor,
+            @Nullable String serviceDescriptor) {
+        return new IContextHubEndpointDiscoveryCallback.Stub() {
+            @Override
+            public void onEndpointsStarted(HubEndpointInfo[] hubEndpointInfoList) {
+                if (hubEndpointInfoList.length == 0) {
+                    Log.w(TAG, "onEndpointsStarted: received empty discovery list");
+                    return;
+                }
+                executor.execute(
+                        () -> {
+                            // TODO(b/380293951): Refactor
+                            List<HubDiscoveryInfo> discoveryList =
+                                    new ArrayList<>(hubEndpointInfoList.length);
+                            for (HubEndpointInfo info : hubEndpointInfoList) {
+                                if (serviceDescriptor != null) {
+                                    for (HubServiceInfo sInfo : info.getServiceInfoCollection()) {
+                                        if (sInfo.getServiceDescriptor()
+                                                .equals(serviceDescriptor)) {
+                                            discoveryList.add(new HubDiscoveryInfo(info, sInfo));
+                                        }
+                                    }
+                                } else {
+                                    discoveryList.add(new HubDiscoveryInfo(info));
+                                }
+                            }
+                            if (discoveryList.isEmpty()) {
+                                Log.w(TAG, "onEndpointsStarted: no matching service descriptor");
+                            } else {
+                                callback.onEndpointsStarted(discoveryList);
+                            }
+                        });
+            }
+
+            @Override
+            public void onEndpointsStopped(HubEndpointInfo[] hubEndpointInfoList, int reason) {
+                if (hubEndpointInfoList.length == 0) {
+                    Log.w(TAG, "onEndpointsStopped: received empty discovery list");
+                    return;
+                }
+                executor.execute(
+                        () -> {
+                            List<HubDiscoveryInfo> discoveryList =
+                                    new ArrayList<>(hubEndpointInfoList.length);
+                            for (HubEndpointInfo info : hubEndpointInfoList) {
+                                if (serviceDescriptor != null) {
+                                    for (HubServiceInfo sInfo : info.getServiceInfoCollection()) {
+                                        if (sInfo.getServiceDescriptor()
+                                                .equals(serviceDescriptor)) {
+                                            discoveryList.add(new HubDiscoveryInfo(info, sInfo));
+                                        }
+                                    }
+                                } else {
+                                    discoveryList.add(new HubDiscoveryInfo(info));
+                                }
+                            }
+                            if (discoveryList.isEmpty()) {
+                                Log.w(TAG, "onEndpointsStopped: no matching service descriptor");
+                            } else {
+                                callback.onEndpointsStopped(discoveryList, reason);
+                            }
+                        });
+            }
+        };
+    }
+
+    /**
+     * Equivalent to {@link #registerEndpointDiscoveryCallback(long, IHubEndpointDiscoveryCallback,
+     * Executor)} with the default executor in the main thread.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+    public void registerEndpointDiscoveryCallback(
+            long endpointId, @NonNull IHubEndpointDiscoveryCallback callback) {
+        registerEndpointDiscoveryCallback(
+                endpointId, callback, new HandlerExecutor(Handler.getMain()));
+    }
+
+    /**
+     * Registers a callback to be notified when the hub endpoint with the corresponding endpoint ID
+     * has started or stopped.
+     *
+     * @param endpointId The identifier of the hub endpoint.
+     * @param callback The callback to be invoked.
+     * @param executor The executor to invoke the callback on.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+    public void registerEndpointDiscoveryCallback(
+            long endpointId,
+            @NonNull IHubEndpointDiscoveryCallback callback,
+            @NonNull Executor executor) {
+        Objects.requireNonNull(callback, "callback cannot be null");
+        Objects.requireNonNull(executor, "executor cannot be null");
+        IContextHubEndpointDiscoveryCallback iCallback =
+                createDiscoveryCallback(callback, executor, null);
+        try {
+            mService.registerEndpointDiscoveryCallbackId(endpointId, iCallback);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        mDiscoveryCallbacks.put(callback, iCallback);
+    }
+
+    /**
+     * Equivalent to {@link #registerEndpointDiscoveryCallback(String,
+     * IHubEndpointDiscoveryCallback, Executor)} with the default executor in the main thread.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+    public void registerEndpointDiscoveryCallback(
+            @NonNull String serviceDescriptor, @NonNull IHubEndpointDiscoveryCallback callback) {
+        registerEndpointDiscoveryCallback(
+                serviceDescriptor, callback, new HandlerExecutor(Handler.getMain()));
+    }
+
+    /**
+     * Registers a callback to be notified when the hub endpoint with the corresponding service
+     * descriptor has started or stopped.
+     *
+     * @param serviceDescriptor The service descriptor of the hub endpoint.
+     * @param callback The callback to be invoked.
+     * @param executor The executor to invoke the callback on.
+     * @throws IllegalArgumentException if the serviceDescriptor is empty.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+    public void registerEndpointDiscoveryCallback(
+            @NonNull String serviceDescriptor,
+            @NonNull IHubEndpointDiscoveryCallback callback,
+            @NonNull Executor executor) {
+        Objects.requireNonNull(serviceDescriptor, "serviceDescriptor cannot be null");
+        Objects.requireNonNull(callback, "callback cannot be null");
+        Objects.requireNonNull(executor, "executor cannot be null");
+        if (serviceDescriptor.isBlank()) {
+            throw new IllegalArgumentException("Invalid service descriptor: " + serviceDescriptor);
+        }
+
+        IContextHubEndpointDiscoveryCallback iCallback =
+                createDiscoveryCallback(callback, executor, serviceDescriptor);
+        try {
+            mService.registerEndpointDiscoveryCallbackDescriptor(serviceDescriptor, iCallback);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        mDiscoveryCallbacks.put(callback, iCallback);
+    }
+
+    /**
+     * Unregisters a previously registered endpoint discovery callback.
+     *
+     * @param callback The callback previously registered.
+     * @throws IllegalArgumentException If the callback was not previously registered.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+    public void unregisterEndpointDiscoveryCallback(
+            @NonNull IHubEndpointDiscoveryCallback callback) {
+        Objects.requireNonNull(callback, "callback cannot be null");
+        IContextHubEndpointDiscoveryCallback iCallback = mDiscoveryCallbacks.remove(callback);
+        if (iCallback == null) {
+            throw new IllegalArgumentException("Callback not previously registered");
+        }
+
+        try {
+            mService.unregisterEndpointDiscoveryCallback(iCallback);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Set a callback to receive messages from the context hub
      *
      * @param callback Callback object
@@ -1081,6 +1267,9 @@
      * registration succeeds, the endpoint can receive notifications through the provided callback.
      *
      * @param hubEndpoint {@link HubEndpoint} object created by {@link HubEndpoint.Builder}
+     * @throws IllegalStateException if the registration failed, for example if too many endpoints
+     *     are registered at the service
+     * @throws UnsupportedOperationException if endpoint registration is not supported
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index f9f41244..f14aadc 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -21,6 +21,7 @@
 import android.hardware.contexthub.HubEndpointInfo;
 import android.hardware.contexthub.IContextHubEndpoint;
 import android.hardware.contexthub.IContextHubEndpointCallback;
+import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubMessage;
 import android.hardware.location.HubInfo;
@@ -137,4 +138,16 @@
     // Register an endpoint with the context hub
     @EnforcePermission("ACCESS_CONTEXT_HUB")
     IContextHubEndpoint registerEndpoint(in HubEndpointInfo pendingEndpointInfo, in IContextHubEndpointCallback callback);
+
+    // Register an endpoint discovery callback (id)
+    @EnforcePermission("ACCESS_CONTEXT_HUB")
+    void registerEndpointDiscoveryCallbackId(long endpointId, in IContextHubEndpointDiscoveryCallback callback);
+
+    // Register an endpoint discovery callback (descriptor)
+    @EnforcePermission("ACCESS_CONTEXT_HUB")
+    void registerEndpointDiscoveryCallbackDescriptor(String serviceDescriptor, in IContextHubEndpointDiscoveryCallback callback);
+
+    // Unregister an endpoint with the context hub
+    @EnforcePermission("ACCESS_CONTEXT_HUB")
+    void unregisterEndpointDiscoveryCallback(in IContextHubEndpointDiscoveryCallback callback);
 }
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 2ba1078..73c8e3e 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -157,7 +157,7 @@
             SoundTrigger.RecognitionConfig apiConfig) {
         RecognitionConfig aidlConfig = new RecognitionConfig();
         aidlConfig.captureRequested = apiConfig.isCaptureRequested();
-        // apiConfig.isAllowMultipleTriggers() is ignored by the lower layers.
+        // apiConfig.isMultipleTriggersAllowed() is ignored by the lower layers.
         aidlConfig.phraseRecognitionExtras =
                 new PhraseRecognitionExtra[apiConfig.getKeyphrases().size()];
         for (int i = 0; i < apiConfig.getKeyphrases().size(); ++i) {
@@ -178,7 +178,7 @@
         }
         return new SoundTrigger.RecognitionConfig.Builder()
             .setCaptureRequested(aidlConfig.captureRequested)
-            .setAllowMultipleTriggers(false)
+            .setMultipleTriggersAllowed(false)
             .setKeyphrases(keyphrases)
             .setData(Arrays.copyOf(aidlConfig.data, aidlConfig.data.length))
             .setAudioCapabilities(aidl2apiAudioCapabilities(aidlConfig.audioCapabilities))
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 7745b03..7c4ddc6 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1518,7 +1518,7 @@
     @FlaggedApi(android.media.soundtrigger.Flags.FLAG_MANAGER_API)
     public static final class RecognitionConfig implements Parcelable {
         private final boolean mCaptureRequested;
-        private final boolean mAllowMultipleTriggers;
+        private final boolean mMultipleTriggersAllowed;
         private final KeyphraseRecognitionExtra mKeyphrases[];
         private final byte[] mData;
         private final @ModuleProperties.AudioCapabilities int mAudioCapabilities;
@@ -1529,7 +1529,7 @@
          * {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)}
          *
          * @param captureRequested Whether the DSP should capture the trigger sound.
-         * @param allowMultipleTriggers Whether the service should restart listening after the DSP
+         * @param multipleTriggersAllowed Whether the service should restart listening after the DSP
          *                              triggers.
          * @param keyphrases List of keyphrases in the sound model.
          * @param data Opaque data for use by system applications who know about voice engine
@@ -1537,11 +1537,11 @@
          * @param audioCapabilities Bit field encoding of the AudioCapabilities. See
          *                          {@link ModuleProperties.AudioCapabilities} for details.
          */
-        private RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+        private RecognitionConfig(boolean captureRequested, boolean multipleTriggersAllowed,
                 @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
                 @ModuleProperties.AudioCapabilities int audioCapabilities) {
             this.mCaptureRequested = captureRequested;
-            this.mAllowMultipleTriggers = allowMultipleTriggers;
+            this.mMultipleTriggersAllowed = multipleTriggersAllowed;
             this.mKeyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
             this.mData = data != null ? data : new byte[0];
             this.mAudioCapabilities = audioCapabilities;
@@ -1553,7 +1553,7 @@
          *
          * @deprecated Use {@link Builder} instead.
          * @param captureRequested Whether the DSP should capture the trigger sound.
-         * @param allowMultipleTriggers Whether the service should restart listening after the DSP
+         * @param multipleTriggersAllowed Whether the service should restart listening after the DSP
          *                              triggers.
          * @param keyphrases List of keyphrases in the sound model.
          * @param data Opaque data for use by system applications.
@@ -1563,9 +1563,9 @@
         @UnsupportedAppUsage
         @Deprecated
         @TestApi
-        public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+        public RecognitionConfig(boolean captureRequested, boolean multipleTriggersAllowed,
                 @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
-            this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
+            this(captureRequested, multipleTriggersAllowed, keyphrases, data, 0);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1593,8 +1593,8 @@
          * <p><b>Note:</b> This config flag is currently used at the service layer rather than by
          * the DSP.
          */
-        public boolean isAllowMultipleTriggers() {
-            return mAllowMultipleTriggers;
+        public boolean isMultipleTriggersAllowed() {
+            return mMultipleTriggersAllowed;
         }
 
         /**
@@ -1627,19 +1627,19 @@
 
         private static RecognitionConfig fromParcel(Parcel in) {
             boolean captureRequested = in.readBoolean();
-            boolean allowMultipleTriggers = in.readBoolean();
+            boolean multipleTriggersAllowed = in.readBoolean();
             KeyphraseRecognitionExtra[] keyphrases =
                     in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
             byte[] data = in.createByteArray();
             int audioCapabilities = in.readInt();
-            return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data,
-                    audioCapabilities);
+            return new RecognitionConfig(captureRequested, multipleTriggersAllowed, keyphrases,
+                    data, audioCapabilities);
         }
 
         @Override
         public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeBoolean(mCaptureRequested);
-            dest.writeBoolean(mAllowMultipleTriggers);
+            dest.writeBoolean(mMultipleTriggersAllowed);
             dest.writeTypedArray(mKeyphrases, flags);
             dest.writeByteArray(mData);
             dest.writeInt(mAudioCapabilities);
@@ -1653,7 +1653,7 @@
         @Override
         public String toString() {
             return "RecognitionConfig [captureRequested=" + mCaptureRequested
-                    + ", allowMultipleTriggers=" + mAllowMultipleTriggers + ", keyphrases="
+                    + ", multipleTriggersAllowed=" + mMultipleTriggersAllowed + ", keyphrases="
                     + Arrays.toString(mKeyphrases) + ", data=" + Arrays.toString(mData)
                     + ", audioCapabilities=" + Integer.toHexString(mAudioCapabilities) + "]";
         }
@@ -1670,7 +1670,7 @@
             if (mCaptureRequested != other.mCaptureRequested) {
                 return false;
             }
-            if (mAllowMultipleTriggers != other.mAllowMultipleTriggers) {
+            if (mMultipleTriggersAllowed != other.mMultipleTriggersAllowed) {
                 return false;
             }
             if (!Arrays.equals(mKeyphrases, other.mKeyphrases)) {
@@ -1690,7 +1690,7 @@
             final int prime = 31;
             int result = 1;
             result = prime * result + (mCaptureRequested ? 1 : 0);
-            result = prime * result + (mAllowMultipleTriggers ? 1 : 0);
+            result = prime * result + (mMultipleTriggersAllowed ? 1 : 0);
             result = prime * result + Arrays.hashCode(mKeyphrases);
             result = prime * result + Arrays.hashCode(mData);
             result = prime * result + mAudioCapabilities;
@@ -1702,7 +1702,7 @@
          */
         public static final class Builder {
             private boolean mCaptureRequested;
-            private boolean mAllowMultipleTriggers;
+            private boolean mMultipleTriggersAllowed;
             @Nullable private KeyphraseRecognitionExtra[] mKeyphrases;
             @Nullable private byte[] mData;
             private @ModuleProperties.AudioCapabilities int mAudioCapabilities;
@@ -1725,12 +1725,12 @@
 
             /**
              * Sets allow multiple triggers state.
-             * @param allowMultipleTriggers Whether the service should restart listening after the
+             * @param multipleTriggersAllowed Whether the service should restart listening after the
              *                              DSP triggers.
              * @return the same Builder instance.
              */
-            public @NonNull Builder setAllowMultipleTriggers(boolean allowMultipleTriggers) {
-                mAllowMultipleTriggers = allowMultipleTriggers;
+            public @NonNull Builder setMultipleTriggersAllowed(boolean multipleTriggersAllowed) {
+                mMultipleTriggersAllowed = multipleTriggersAllowed;
                 return this;
             }
 
@@ -1779,7 +1779,7 @@
             public @NonNull RecognitionConfig build() {
                 RecognitionConfig config = new RecognitionConfig(
                         /* captureRequested= */ mCaptureRequested,
-                        /* allowMultipleTriggers= */ mAllowMultipleTriggers,
+                        /* multipleTriggersAllowed= */ mMultipleTriggersAllowed,
                         /* keyphrases= */ mKeyphrases,
                         /* data= */ mData,
                         /* audioCapabilities= */ mAudioCapabilities);
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 b719a7c..9403f78 100644
--- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -30,6 +30,7 @@
     namespace: "usb"
     description: "Feature flag to enable exposing usb speed system api"
     bug: "373653182"
+    is_exported: true
 }
 
 flag {
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 4bde8e2..5f3c15d 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1199,6 +1199,11 @@
                     // when the stylus is not down.
                     mPrivOps.setHandwritingSurfaceNotTouchable(true);
                     break;
+                case MotionEvent.ACTION_OUTSIDE:
+                    // TODO(b/350047836): determine if there is use-case for simultaneous touch
+                    //  and stylus handwriting and we shouldn't finish for that.
+                    finishStylusHandwriting();
+                    break;
             }
         }
 
@@ -3207,6 +3212,7 @@
             Log.d(TAG, "Setting new handwriting region for stylus handwriting "
                     + handwritingRegion + " from last " + mLastHandwritingRegion);
         }
+        mPrivOps.setHandwritingTouchableRegion(handwritingRegion);
         mLastHandwritingRegion = handwritingRegion;
     }
 
diff --git a/core/java/android/net/LocalSocket.java b/core/java/android/net/LocalSocket.java
index a86396c..0fedf8e 100644
--- a/core/java/android/net/LocalSocket.java
+++ b/core/java/android/net/LocalSocket.java
@@ -65,7 +65,7 @@
     }
 
     /**
-     * Creates a AF_LOCAL/UNIX domain stream socket with given socket type
+     * Creates a AF_LOCAL/UNIX domain socket with the given socket type.
      *
      * @param sockType either {@link #SOCKET_DGRAM}, {@link #SOCKET_STREAM}
      * or {@link #SOCKET_SEQPACKET}
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index f7dc790..95b5f69 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -27,4 +27,13 @@
   metadata {
     purpose: PURPOSE_BUGFIX
   }
+  is_exported: true
+}
+
+flag {
+  name: "x509_extensions_certificate_transparency"
+  is_exported: true
+  namespace: "network_security"
+  description: "Flag to use checkServerTrusted to verify SCTs in OCSP and TLS Data"
+  bug: "319829948"
 }
diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java
index 280dad0..b44f75a 100644
--- a/core/java/android/net/http/X509TrustManagerExtensions.java
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -16,6 +16,13 @@
 
 package android.net.http;
 
+import static com.android.org.conscrypt.flags.Flags.certificateTransparencyCheckservertrustedApi;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.platform.flags.Flags;
 import android.security.net.config.UserCertificateSource;
 
 import com.android.org.conscrypt.TrustManagerImpl;
@@ -24,6 +31,7 @@
 import java.lang.reflect.Method;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
+import java.util.Collections;
 import java.util.List;
 
 import javax.net.ssl.X509TrustManager;
@@ -31,9 +39,9 @@
 /**
  * X509TrustManager wrapper exposing Android-added features.
  * <p>
- * The checkServerTrusted method allows callers to perform additional
- * verification of certificate chains after they have been successfully verified
- * by the platform.
+ * The checkServerTrusted methods allow callers to provide some additional
+ * context for the verification. This is particularly useful when an SSLEngine
+ * or SSLSocket is not available.
  * </p>
  */
 public class X509TrustManagerExtensions {
@@ -42,6 +50,7 @@
     // Methods to use when mDelegate is not a TrustManagerImpl and duck typing is being used.
     private final X509TrustManager mTrustManager;
     private final Method mCheckServerTrusted;
+    private final Method mCheckServerTrustedOcspAndTlsData;
     private final Method mIsSameTrustConfiguration;
 
     /**
@@ -55,6 +64,7 @@
             mDelegate = (TrustManagerImpl) tm;
             mTrustManager = null;
             mCheckServerTrusted = null;
+            mCheckServerTrustedOcspAndTlsData = null;
             mIsSameTrustConfiguration = null;
             return;
         }
@@ -69,8 +79,19 @@
                     String.class);
         } catch (NoSuchMethodException e) {
             throw new IllegalArgumentException("Required method"
-                    + " checkServerTrusted(X509Certificate[], String, String, String) missing");
+                    + " checkServerTrusted(X509Certificate[], String, String) missing");
         }
+        // Check that the OCSP and TlsData aware checkServerTrusted is present.
+        Method checkServerTrustedOcspAndTlsData = null;
+        try {
+            checkServerTrustedOcspAndTlsData = tm.getClass().getMethod("checkServerTrusted",
+                    X509Certificate[].class,
+                    Byte[].class,
+                    Byte[].class,
+                    String.class,
+                    String.class);
+        } catch (ReflectiveOperationException ignored) { }
+        mCheckServerTrustedOcspAndTlsData = checkServerTrustedOcspAndTlsData;
         // Get the option isSameTrustConfiguration method.
         Method isSameTrustConfiguration = null;
         try {
@@ -115,6 +136,65 @@
     }
 
     /**
+     * Verifies the given certificate chain.
+     *
+     * <p>See {@link X509TrustManager#checkServerTrusted(X509Certificate[], String)} for a
+     * description of the chain and authType parameters. The final parameter, host, should be the
+     * hostname of the server.</p>
+     *
+     * <p>ocspData and tlsSctData may be provided to verify any Signed Certificate Timestamp (SCT)
+     * attached to the connection. These are ASN.1 octet strings (SignedCertificateTimestampList)
+     * as described in RFC 6962, Section 3.3. Note that SCTs embedded in the certificate chain
+     * will automatically be processed.
+     * </p>
+     *
+     * @throws CertificateException if the chain does not verify correctly.
+     * @throws IllegalArgumentException if the TrustManager is not compatible.
+     * @return the properly ordered chain used for verification as a list of X509Certificates.
+     */
+    @FlaggedApi(Flags.FLAG_X509_EXTENSIONS_CERTIFICATE_TRANSPARENCY)
+    @NonNull
+    public List<X509Certificate> checkServerTrusted(
+            @SuppressLint("ArrayReturn") @NonNull X509Certificate[] chain,
+            @Nullable byte[] ocspData,
+            @Nullable byte[] tlsSctData,
+            @NonNull String authType,
+            @NonNull String host) throws CertificateException {
+        List<X509Certificate> result;
+        if (mDelegate != null) {
+            if (certificateTransparencyCheckservertrustedApi()) {
+                result = mDelegate.checkServerTrusted(chain, ocspData, tlsSctData, authType, host);
+                return result == null ? Collections.emptyList() : result;
+            } else {
+                // The conscrypt mainline module does not have the required method.
+                throw new IllegalArgumentException("Required method"
+                    + " checkServerTrusted(X509Certificate[], byte[], byte[], String, String)"
+                    + " not available in TrustManagerImpl");
+            }
+        }
+        if (mCheckServerTrustedOcspAndTlsData == null) {
+            throw new IllegalArgumentException("Required method"
+                    + " checkServerTrusted(X509Certificate[], byte[], byte[], String, String)"
+                    + " missing");
+        }
+        try {
+            result = (List<X509Certificate>) mCheckServerTrustedOcspAndTlsData.invoke(mTrustManager,
+                    ocspData, tlsSctData, chain, authType, host);
+            return result == null ? Collections.emptyList() : result;
+        } catch (IllegalAccessException e) {
+            throw new CertificateException("Failed to call checkServerTrusted", e);
+        } catch (InvocationTargetException e) {
+            if (e.getCause() instanceof CertificateException) {
+                throw (CertificateException) e.getCause();
+            }
+            if (e.getCause() instanceof RuntimeException) {
+                throw (RuntimeException) e.getCause();
+            }
+            throw new CertificateException("checkServerTrusted failed", e.getCause());
+        }
+    }
+
+    /**
      * Checks whether a CA certificate is added by an user.
      *
      * <p>Since {@link X509TrustManager#checkServerTrusted} may allow its parameter {@code chain} to
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
deleted file mode 100644
index c0398ce..0000000
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
+++ /dev/null
@@ -1,740 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.net.vcn;
-
-import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.getMatchCriteriaString;
-
-import static com.android.internal.annotations.VisibleForTesting.Visibility;
-import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
-import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
-import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
-import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.net.NetworkCapabilities;
-import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria;
-import android.os.PersistableBundle;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
-import com.android.server.vcn.util.PersistableBundleUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * This class represents a configuration for a network template class of underlying cellular
- * networks.
- *
- * <p>See {@link VcnUnderlyingNetworkTemplate}
- */
-public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate {
-    private static final String ALLOWED_NETWORK_PLMN_IDS_KEY = "mAllowedNetworkPlmnIds";
-    @NonNull private final Set<String> mAllowedNetworkPlmnIds;
-    private static final String ALLOWED_SPECIFIC_CARRIER_IDS_KEY = "mAllowedSpecificCarrierIds";
-    @NonNull private final Set<Integer> mAllowedSpecificCarrierIds;
-
-    private static final String ROAMING_MATCH_KEY = "mRoamingMatchCriteria";
-    private static final int DEFAULT_ROAMING_MATCH_CRITERIA = MATCH_ANY;
-    private final int mRoamingMatchCriteria;
-
-    private static final String OPPORTUNISTIC_MATCH_KEY = "mOpportunisticMatchCriteria";
-    private static final int DEFAULT_OPPORTUNISTIC_MATCH_CRITERIA = MATCH_ANY;
-    private final int mOpportunisticMatchCriteria;
-
-    private static final String CAPABILITIES_MATCH_CRITERIA_KEY = "mCapabilitiesMatchCriteria";
-    @NonNull private final Map<Integer, Integer> mCapabilitiesMatchCriteria;
-
-    private static final Map<Integer, Integer> CAPABILITIES_MATCH_CRITERIA_DEFAULT;
-
-    static {
-        Map<Integer, Integer> capsMatchCriteria = new HashMap<>();
-        capsMatchCriteria.put(NET_CAPABILITY_CBS, MATCH_ANY);
-        capsMatchCriteria.put(NET_CAPABILITY_DUN, MATCH_ANY);
-        capsMatchCriteria.put(NET_CAPABILITY_IMS, MATCH_ANY);
-        capsMatchCriteria.put(NET_CAPABILITY_INTERNET, MATCH_REQUIRED);
-        capsMatchCriteria.put(NET_CAPABILITY_MMS, MATCH_ANY);
-        capsMatchCriteria.put(NET_CAPABILITY_RCS, MATCH_ANY);
-
-        CAPABILITIES_MATCH_CRITERIA_DEFAULT = Collections.unmodifiableMap(capsMatchCriteria);
-    }
-
-    private VcnCellUnderlyingNetworkTemplate(
-            int meteredMatchCriteria,
-            int minEntryUpstreamBandwidthKbps,
-            int minExitUpstreamBandwidthKbps,
-            int minEntryDownstreamBandwidthKbps,
-            int minExitDownstreamBandwidthKbps,
-            Set<String> allowedNetworkPlmnIds,
-            Set<Integer> allowedSpecificCarrierIds,
-            int roamingMatchCriteria,
-            int opportunisticMatchCriteria,
-            Map<Integer, Integer> capabilitiesMatchCriteria) {
-        super(
-                NETWORK_PRIORITY_TYPE_CELL,
-                meteredMatchCriteria,
-                minEntryUpstreamBandwidthKbps,
-                minExitUpstreamBandwidthKbps,
-                minEntryDownstreamBandwidthKbps,
-                minExitDownstreamBandwidthKbps);
-        mAllowedNetworkPlmnIds = new ArraySet<>(allowedNetworkPlmnIds);
-        mAllowedSpecificCarrierIds = new ArraySet<>(allowedSpecificCarrierIds);
-        mRoamingMatchCriteria = roamingMatchCriteria;
-        mOpportunisticMatchCriteria = opportunisticMatchCriteria;
-        mCapabilitiesMatchCriteria = new HashMap<>(capabilitiesMatchCriteria);
-
-        validate();
-    }
-
-    /** @hide */
-    @Override
-    protected void validate() {
-        super.validate();
-        validatePlmnIds(mAllowedNetworkPlmnIds);
-        validateCapabilitiesMatchCriteria(mCapabilitiesMatchCriteria);
-        Objects.requireNonNull(mAllowedSpecificCarrierIds, "matchingCarrierIds is null");
-        validateMatchCriteria(mRoamingMatchCriteria, "mRoamingMatchCriteria");
-        validateMatchCriteria(mOpportunisticMatchCriteria, "mOpportunisticMatchCriteria");
-    }
-
-    private static void validatePlmnIds(Set<String> matchingOperatorPlmnIds) {
-        Objects.requireNonNull(matchingOperatorPlmnIds, "matchingOperatorPlmnIds is null");
-
-        // A valid PLMN is a concatenation of MNC and MCC, and thus consists of 5 or 6 decimal
-        // digits.
-        for (String id : matchingOperatorPlmnIds) {
-            if ((id.length() == 5 || id.length() == 6) && id.matches("[0-9]+")) {
-                continue;
-            } else {
-                throw new IllegalArgumentException("Found invalid PLMN ID: " + id);
-            }
-        }
-    }
-
-    private static void validateCapabilitiesMatchCriteria(
-            Map<Integer, Integer> capabilitiesMatchCriteria) {
-        Objects.requireNonNull(capabilitiesMatchCriteria, "capabilitiesMatchCriteria is null");
-
-        boolean requiredCapabilityFound = false;
-        for (Map.Entry<Integer, Integer> entry : capabilitiesMatchCriteria.entrySet()) {
-            final int capability = entry.getKey();
-            final int matchCriteria = entry.getValue();
-
-            Preconditions.checkArgument(
-                    CAPABILITIES_MATCH_CRITERIA_DEFAULT.containsKey(capability),
-                    "NetworkCapability " + capability + "out of range");
-            validateMatchCriteria(matchCriteria, "capability " + capability);
-
-            requiredCapabilityFound |= (matchCriteria == MATCH_REQUIRED);
-        }
-
-        if (!requiredCapabilityFound) {
-            throw new IllegalArgumentException("No required capabilities found");
-        }
-    }
-
-    /** @hide */
-    @NonNull
-    @VisibleForTesting(visibility = Visibility.PROTECTED)
-    public static VcnCellUnderlyingNetworkTemplate fromPersistableBundle(
-            @NonNull PersistableBundle in) {
-        Objects.requireNonNull(in, "PersistableBundle is null");
-
-        final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY);
-
-        final int minEntryUpstreamBandwidthKbps =
-                in.getInt(MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
-        final int minExitUpstreamBandwidthKbps =
-                in.getInt(MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
-        final int minEntryDownstreamBandwidthKbps =
-                in.getInt(MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
-        final int minExitDownstreamBandwidthKbps =
-                in.getInt(MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
-
-        final PersistableBundle plmnIdsBundle =
-                in.getPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY);
-        Objects.requireNonNull(plmnIdsBundle, "plmnIdsBundle is null");
-        final Set<String> allowedNetworkPlmnIds =
-                new ArraySet<String>(
-                        PersistableBundleUtils.toList(plmnIdsBundle, STRING_DESERIALIZER));
-
-        final PersistableBundle specificCarrierIdsBundle =
-                in.getPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY);
-        Objects.requireNonNull(specificCarrierIdsBundle, "specificCarrierIdsBundle is null");
-        final Set<Integer> allowedSpecificCarrierIds =
-                new ArraySet<Integer>(
-                        PersistableBundleUtils.toList(
-                                specificCarrierIdsBundle, INTEGER_DESERIALIZER));
-
-        final PersistableBundle capabilitiesMatchCriteriaBundle =
-                in.getPersistableBundle(CAPABILITIES_MATCH_CRITERIA_KEY);
-        final Map<Integer, Integer> capabilitiesMatchCriteria;
-        if (capabilitiesMatchCriteriaBundle == null) {
-            capabilitiesMatchCriteria = CAPABILITIES_MATCH_CRITERIA_DEFAULT;
-        } else {
-            capabilitiesMatchCriteria =
-                    PersistableBundleUtils.toMap(
-                            capabilitiesMatchCriteriaBundle,
-                            INTEGER_DESERIALIZER,
-                            INTEGER_DESERIALIZER);
-        }
-
-        final int roamingMatchCriteria = in.getInt(ROAMING_MATCH_KEY);
-        final int opportunisticMatchCriteria = in.getInt(OPPORTUNISTIC_MATCH_KEY);
-
-        return new VcnCellUnderlyingNetworkTemplate(
-                meteredMatchCriteria,
-                minEntryUpstreamBandwidthKbps,
-                minExitUpstreamBandwidthKbps,
-                minEntryDownstreamBandwidthKbps,
-                minExitDownstreamBandwidthKbps,
-                allowedNetworkPlmnIds,
-                allowedSpecificCarrierIds,
-                roamingMatchCriteria,
-                opportunisticMatchCriteria,
-                capabilitiesMatchCriteria);
-    }
-
-    /** @hide */
-    @Override
-    @NonNull
-    @VisibleForTesting(visibility = Visibility.PROTECTED)
-    public PersistableBundle toPersistableBundle() {
-        final PersistableBundle result = super.toPersistableBundle();
-
-        final PersistableBundle plmnIdsBundle =
-                PersistableBundleUtils.fromList(
-                        new ArrayList<>(mAllowedNetworkPlmnIds), STRING_SERIALIZER);
-        result.putPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY, plmnIdsBundle);
-
-        final PersistableBundle specificCarrierIdsBundle =
-                PersistableBundleUtils.fromList(
-                        new ArrayList<>(mAllowedSpecificCarrierIds), INTEGER_SERIALIZER);
-        result.putPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY, specificCarrierIdsBundle);
-
-        final PersistableBundle capabilitiesMatchCriteriaBundle =
-                PersistableBundleUtils.fromMap(
-                        mCapabilitiesMatchCriteria, INTEGER_SERIALIZER, INTEGER_SERIALIZER);
-        result.putPersistableBundle(
-                CAPABILITIES_MATCH_CRITERIA_KEY, capabilitiesMatchCriteriaBundle);
-
-        result.putInt(ROAMING_MATCH_KEY, mRoamingMatchCriteria);
-        result.putInt(OPPORTUNISTIC_MATCH_KEY, mOpportunisticMatchCriteria);
-
-        return result;
-    }
-
-    /**
-     * Retrieve the matching operator PLMN IDs, or an empty set if any PLMN ID is acceptable.
-     *
-     * @see Builder#setOperatorPlmnIds(Set)
-     */
-    @NonNull
-    public Set<String> getOperatorPlmnIds() {
-        return Collections.unmodifiableSet(mAllowedNetworkPlmnIds);
-    }
-
-    /**
-     * Retrieve the matching sim specific carrier IDs, or an empty set if any sim specific carrier
-     * ID is acceptable.
-     *
-     * @see Builder#setSimSpecificCarrierIds(Set)
-     */
-    @NonNull
-    public Set<Integer> getSimSpecificCarrierIds() {
-        return Collections.unmodifiableSet(mAllowedSpecificCarrierIds);
-    }
-
-    /**
-     * Return the matching criteria for roaming networks.
-     *
-     * @see Builder#setRoaming(int)
-     */
-    @MatchCriteria
-    public int getRoaming() {
-        return mRoamingMatchCriteria;
-    }
-
-    /**
-     * Return the matching criteria for opportunistic cellular subscriptions.
-     *
-     * @see Builder#setOpportunistic(int)
-     */
-    @MatchCriteria
-    public int getOpportunistic() {
-        return mOpportunisticMatchCriteria;
-    }
-
-    /**
-     * Returns the matching criteria for CBS networks.
-     *
-     * @see Builder#setCbs(int)
-     */
-    @MatchCriteria
-    public int getCbs() {
-        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_CBS);
-    }
-
-    /**
-     * Returns the matching criteria for DUN networks.
-     *
-     * @see Builder#setDun(int)
-     */
-    @MatchCriteria
-    public int getDun() {
-        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_DUN);
-    }
-    /**
-     * Returns the matching criteria for IMS networks.
-     *
-     * @see Builder#setIms(int)
-     */
-    @MatchCriteria
-    public int getIms() {
-        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_IMS);
-    }
-    /**
-     * Returns the matching criteria for INTERNET networks.
-     *
-     * @see Builder#setInternet(int)
-     */
-    @MatchCriteria
-    public int getInternet() {
-        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_INTERNET);
-    }
-    /**
-     * Returns the matching criteria for MMS networks.
-     *
-     * @see Builder#setMms(int)
-     */
-    @MatchCriteria
-    public int getMms() {
-        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_MMS);
-    }
-
-    /**
-     * Returns the matching criteria for RCS networks.
-     *
-     * @see Builder#setRcs(int)
-     */
-    @MatchCriteria
-    public int getRcs() {
-        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_RCS);
-    }
-
-    /** @hide */
-    @Override
-    public Map<Integer, Integer> getCapabilitiesMatchCriteria() {
-        return Collections.unmodifiableMap(new HashMap<>(mCapabilitiesMatchCriteria));
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(
-                super.hashCode(),
-                mAllowedNetworkPlmnIds,
-                mAllowedSpecificCarrierIds,
-                mCapabilitiesMatchCriteria,
-                mRoamingMatchCriteria,
-                mOpportunisticMatchCriteria);
-    }
-
-    @Override
-    public boolean equals(@Nullable Object other) {
-        if (!super.equals(other)) {
-            return false;
-        }
-
-        if (!(other instanceof VcnCellUnderlyingNetworkTemplate)) {
-            return false;
-        }
-
-        final VcnCellUnderlyingNetworkTemplate rhs = (VcnCellUnderlyingNetworkTemplate) other;
-        return Objects.equals(mAllowedNetworkPlmnIds, rhs.mAllowedNetworkPlmnIds)
-                && Objects.equals(mAllowedSpecificCarrierIds, rhs.mAllowedSpecificCarrierIds)
-                && Objects.equals(mCapabilitiesMatchCriteria, rhs.mCapabilitiesMatchCriteria)
-                && mRoamingMatchCriteria == rhs.mRoamingMatchCriteria
-                && mOpportunisticMatchCriteria == rhs.mOpportunisticMatchCriteria;
-    }
-
-    /** @hide */
-    @Override
-    void dumpTransportSpecificFields(IndentingPrintWriter pw) {
-        if (!mAllowedNetworkPlmnIds.isEmpty()) {
-            pw.println("mAllowedNetworkPlmnIds: " + mAllowedNetworkPlmnIds);
-        }
-        if (!mAllowedNetworkPlmnIds.isEmpty()) {
-            pw.println("mAllowedSpecificCarrierIds: " + mAllowedSpecificCarrierIds);
-        }
-        pw.println("mCapabilitiesMatchCriteria: " + mCapabilitiesMatchCriteria);
-        if (mRoamingMatchCriteria != DEFAULT_ROAMING_MATCH_CRITERIA) {
-            pw.println("mRoamingMatchCriteria: " + getMatchCriteriaString(mRoamingMatchCriteria));
-        }
-        if (mOpportunisticMatchCriteria != DEFAULT_OPPORTUNISTIC_MATCH_CRITERIA) {
-            pw.println(
-                    "mOpportunisticMatchCriteria: "
-                            + getMatchCriteriaString(mOpportunisticMatchCriteria));
-        }
-    }
-
-    /** This class is used to incrementally build VcnCellUnderlyingNetworkTemplate objects. */
-    public static final class Builder {
-        private int mMeteredMatchCriteria = DEFAULT_METERED_MATCH_CRITERIA;
-
-        @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
-        @NonNull private final Set<Integer> mAllowedSpecificCarrierIds = new ArraySet<>();
-        @NonNull private final Map<Integer, Integer> mCapabilitiesMatchCriteria = new HashMap<>();
-
-        private int mRoamingMatchCriteria = DEFAULT_ROAMING_MATCH_CRITERIA;
-        private int mOpportunisticMatchCriteria = DEFAULT_OPPORTUNISTIC_MATCH_CRITERIA;
-
-        private int mMinEntryUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
-        private int mMinExitUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
-        private int mMinEntryDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
-        private int mMinExitDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
-
-        /** Construct a Builder object. */
-        public Builder() {
-            mCapabilitiesMatchCriteria.putAll(CAPABILITIES_MATCH_CRITERIA_DEFAULT);
-        }
-
-        /**
-         * Set the matching criteria for metered networks.
-         *
-         * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one
-         * without NET_CAPABILITY_NOT_METERED). A template where setMetered(MATCH_FORBIDDEN) will
-         * only match a network that is not metered (one with NET_CAPABILITY_NOT_METERED).
-         *
-         * @param matchCriteria the matching criteria for metered networks. Defaults to {@link
-         *     #MATCH_ANY}.
-         * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED
-         */
-        // The matching getter is defined in the super class. Please see {@link
-        // VcnUnderlyingNetworkTemplate#getMetered()}
-        @SuppressLint("MissingGetterMatchingBuilder")
-        @NonNull
-        public Builder setMetered(@MatchCriteria int matchCriteria) {
-            validateMatchCriteria(matchCriteria, "setMetered");
-
-            mMeteredMatchCriteria = matchCriteria;
-            return this;
-        }
-
-        /**
-         * Set operator PLMN IDs with which a network can match this template.
-         *
-         * <p>This is used to distinguish cases where roaming agreements may dictate a different
-         * priority from a partner's networks.
-         *
-         * @param operatorPlmnIds the matching operator PLMN IDs in String. Network with one of the
-         *     matching PLMN IDs can match this template. If the set is empty, any PLMN ID will
-         *     match. The default is an empty set. A valid PLMN is a concatenation of MNC and MCC,
-         *     and thus consists of 5 or 6 decimal digits.
-         * @see SubscriptionInfo#getMccString()
-         * @see SubscriptionInfo#getMncString()
-         */
-        @NonNull
-        public Builder setOperatorPlmnIds(@NonNull Set<String> operatorPlmnIds) {
-            validatePlmnIds(operatorPlmnIds);
-
-            mAllowedNetworkPlmnIds.clear();
-            mAllowedNetworkPlmnIds.addAll(operatorPlmnIds);
-            return this;
-        }
-
-        /**
-         * Set sim specific carrier IDs with which a network can match this template.
-         *
-         * @param simSpecificCarrierIds the matching sim specific carrier IDs. Network with one of
-         *     the sim specific carrier IDs can match this template. If the set is empty, any
-         *     carrier ID will match. The default is an empty set.
-         * @see TelephonyManager#getSimSpecificCarrierId()
-         */
-        @NonNull
-        public Builder setSimSpecificCarrierIds(@NonNull Set<Integer> simSpecificCarrierIds) {
-            Objects.requireNonNull(simSpecificCarrierIds, "simSpecificCarrierIds is null");
-
-            mAllowedSpecificCarrierIds.clear();
-            mAllowedSpecificCarrierIds.addAll(simSpecificCarrierIds);
-            return this;
-        }
-
-        /**
-         * Set the matching criteria for roaming networks.
-         *
-         * <p>A template where setRoaming(MATCH_REQUIRED) will only match roaming networks (one
-         * without NET_CAPABILITY_NOT_ROAMING). A template where setRoaming(MATCH_FORBIDDEN) will
-         * only match a network that is not roaming (one with NET_CAPABILITY_NOT_ROAMING).
-         *
-         * @param matchCriteria the matching criteria for roaming networks. Defaults to {@link
-         *     #MATCH_ANY}.
-         * @see NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING
-         */
-        @NonNull
-        public Builder setRoaming(@MatchCriteria int matchCriteria) {
-            validateMatchCriteria(matchCriteria, "setRoaming");
-
-            mRoamingMatchCriteria = matchCriteria;
-            return this;
-        }
-
-        /**
-         * Set the matching criteria for opportunistic cellular subscriptions.
-         *
-         * @param matchCriteria the matching criteria for opportunistic cellular subscriptions.
-         *     Defaults to {@link #MATCH_ANY}.
-         * @see SubscriptionManager#setOpportunistic(boolean, int)
-         */
-        @NonNull
-        public Builder setOpportunistic(@MatchCriteria int matchCriteria) {
-            validateMatchCriteria(matchCriteria, "setOpportunistic");
-
-            mOpportunisticMatchCriteria = matchCriteria;
-            return this;
-        }
-
-        /**
-         * Set the minimum upstream bandwidths that this template will match.
-         *
-         * <p>This template will not match a network that does not provide at least the bandwidth
-         * passed as the entry bandwidth, except in the case that the network is selected as the VCN
-         * Gateway Connection's underlying network, where it will continue to match until the
-         * bandwidth drops under the exit bandwidth.
-         *
-         * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
-         * invalid case where a network fulfills the entry criteria, but at the same time fails the
-         * exit criteria.
-         *
-         * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
-         * {@link NetworkCapabilities}. The provided estimates will be used without modification.
-         *
-         * @param minEntryUpstreamBandwidthKbps the minimum accepted upstream bandwidth for networks
-         *     that ARE NOT the already-selected underlying network, or {@code 0} to disable this
-         *     requirement. Disabled by default.
-         * @param minExitUpstreamBandwidthKbps the minimum accepted upstream bandwidth for a network
-         *     that IS the already-selected underlying network, or {@code 0} to disable this
-         *     requirement. Disabled by default.
-         * @return this {@link Builder} instance, for chaining
-         */
-        @NonNull
-        // The getter for the two integers are separated, and in the superclass. Please see {@link
-        // VcnUnderlyingNetworkTemplate#getMinEntryUpstreamBandwidthKbps()} and {@link
-        // VcnUnderlyingNetworkTemplate#getMinExitUpstreamBandwidthKbps()}
-        @SuppressLint("MissingGetterMatchingBuilder")
-        public Builder setMinUpstreamBandwidthKbps(
-                int minEntryUpstreamBandwidthKbps, int minExitUpstreamBandwidthKbps) {
-            validateMinBandwidthKbps(minEntryUpstreamBandwidthKbps, minExitUpstreamBandwidthKbps);
-
-            mMinEntryUpstreamBandwidthKbps = minEntryUpstreamBandwidthKbps;
-            mMinExitUpstreamBandwidthKbps = minExitUpstreamBandwidthKbps;
-
-            return this;
-        }
-
-        /**
-         * Set the minimum upstream bandwidths that this template will match.
-         *
-         * <p>This template will not match a network that does not provide at least the bandwidth
-         * passed as the entry bandwidth, except in the case that the network is selected as the VCN
-         * Gateway Connection's underlying network, where it will continue to match until the
-         * bandwidth drops under the exit bandwidth.
-         *
-         * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
-         * invalid case where a network fulfills the entry criteria, but at the same time fails the
-         * exit criteria.
-         *
-         * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
-         * {@link NetworkCapabilities}. The provided estimates will be used without modification.
-         *
-         * @param minEntryDownstreamBandwidthKbps the minimum accepted downstream bandwidth for
-         *     networks that ARE NOT the already-selected underlying network, or {@code 0} to
-         *     disable this requirement. Disabled by default.
-         * @param minExitDownstreamBandwidthKbps the minimum accepted downstream bandwidth for a
-         *     network that IS the already-selected underlying network, or {@code 0} to disable this
-         *     requirement. Disabled by default.
-         * @return this {@link Builder} instance, for chaining
-         */
-        @NonNull
-        // The getter for the two integers are separated, and in the superclass. Please see {@link
-        // VcnUnderlyingNetworkTemplate#getMinEntryDownstreamBandwidthKbps()} and {@link
-        // VcnUnderlyingNetworkTemplate#getMinExitDownstreamBandwidthKbps()}
-        @SuppressLint("MissingGetterMatchingBuilder")
-        public Builder setMinDownstreamBandwidthKbps(
-                int minEntryDownstreamBandwidthKbps, int minExitDownstreamBandwidthKbps) {
-            validateMinBandwidthKbps(
-                    minEntryDownstreamBandwidthKbps, minExitDownstreamBandwidthKbps);
-
-            mMinEntryDownstreamBandwidthKbps = minEntryDownstreamBandwidthKbps;
-            mMinExitDownstreamBandwidthKbps = minExitDownstreamBandwidthKbps;
-
-            return this;
-        }
-
-        /**
-         * Sets the matching criteria for CBS networks.
-         *
-         * <p>A template where {@code setCbs(MATCH_REQUIRED)} is called will only match CBS networks
-         * (ones with NET_CAPABILITY_CBS). A template where {@code setCbs(MATCH_FORBIDDEN)} is
-         * called will only match networks that do not support CBS (ones without
-         * NET_CAPABILITY_CBS).
-         *
-         * @param matchCriteria the matching criteria for CBS networks. Defaults to {@link
-         *     #MATCH_ANY}.
-         * @see NetworkCapabilities#NET_CAPABILITY_CBS
-         */
-        @NonNull
-        public Builder setCbs(@MatchCriteria int matchCriteria) {
-            validateMatchCriteria(matchCriteria, "setCbs");
-
-            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_CBS, matchCriteria);
-            return this;
-        }
-
-        /**
-         * Sets the matching criteria for DUN networks.
-         *
-         * <p>A template where {@code setDun(MATCH_REQUIRED)} is called will only match DUN networks
-         * (ones with NET_CAPABILITY_DUN). A template where {@code setDun(MATCH_FORBIDDEN)} is
-         * called will only match networks that do not support DUN (ones without
-         * NET_CAPABILITY_DUN).
-         *
-         * @param matchCriteria the matching criteria for DUN networks. Defaults to {@link
-         *     #MATCH_ANY}.
-         * @see NetworkCapabilities#NET_CAPABILITY_DUN
-         */
-        @NonNull
-        public Builder setDun(@MatchCriteria int matchCriteria) {
-            validateMatchCriteria(matchCriteria, "setDun");
-
-            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_DUN, matchCriteria);
-            return this;
-        }
-
-        /**
-         * Sets the matching criteria for IMS networks.
-         *
-         * <p>A template where {@code setIms(MATCH_REQUIRED)} is called will only match IMS networks
-         * (ones with NET_CAPABILITY_IMS). A template where {@code setIms(MATCH_FORBIDDEN)} is
-         * called will only match networks that do not support IMS (ones without
-         * NET_CAPABILITY_IMS).
-         *
-         * @param matchCriteria the matching criteria for IMS networks. Defaults to {@link
-         *     #MATCH_ANY}.
-         * @see NetworkCapabilities#NET_CAPABILITY_IMS
-         */
-        @NonNull
-        public Builder setIms(@MatchCriteria int matchCriteria) {
-            validateMatchCriteria(matchCriteria, "setIms");
-
-            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_IMS, matchCriteria);
-            return this;
-        }
-
-        /**
-         * Sets the matching criteria for INTERNET networks.
-         *
-         * <p>A template where {@code setInternet(MATCH_REQUIRED)} is called will only match
-         * INTERNET networks (ones with NET_CAPABILITY_INTERNET). A template where {@code
-         * setInternet(MATCH_FORBIDDEN)} is called will only match networks that do not support
-         * INTERNET (ones without NET_CAPABILITY_INTERNET).
-         *
-         * @param matchCriteria the matching criteria for INTERNET networks. Defaults to {@link
-         *     #MATCH_REQUIRED}.
-         * @see NetworkCapabilities#NET_CAPABILITY_INTERNET
-         */
-        @NonNull
-        public Builder setInternet(@MatchCriteria int matchCriteria) {
-            validateMatchCriteria(matchCriteria, "setInternet");
-
-            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_INTERNET, matchCriteria);
-            return this;
-        }
-
-        /**
-         * Sets the matching criteria for MMS networks.
-         *
-         * <p>A template where {@code setMms(MATCH_REQUIRED)} is called will only match MMS networks
-         * (ones with NET_CAPABILITY_MMS). A template where {@code setMms(MATCH_FORBIDDEN)} is
-         * called will only match networks that do not support MMS (ones without
-         * NET_CAPABILITY_MMS).
-         *
-         * @param matchCriteria the matching criteria for MMS networks. Defaults to {@link
-         *     #MATCH_ANY}.
-         * @see NetworkCapabilities#NET_CAPABILITY_MMS
-         */
-        @NonNull
-        public Builder setMms(@MatchCriteria int matchCriteria) {
-            validateMatchCriteria(matchCriteria, "setMms");
-
-            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_MMS, matchCriteria);
-            return this;
-        }
-
-        /**
-         * Sets the matching criteria for RCS networks.
-         *
-         * <p>A template where {@code setRcs(MATCH_REQUIRED)} is called will only match RCS networks
-         * (ones with NET_CAPABILITY_RCS). A template where {@code setRcs(MATCH_FORBIDDEN)} is
-         * called will only match networks that do not support RCS (ones without
-         * NET_CAPABILITY_RCS).
-         *
-         * @param matchCriteria the matching criteria for RCS networks. Defaults to {@link
-         *     #MATCH_ANY}.
-         * @see NetworkCapabilities#NET_CAPABILITY_RCS
-         */
-        @NonNull
-        public Builder setRcs(@MatchCriteria int matchCriteria) {
-            validateMatchCriteria(matchCriteria, "setRcs");
-
-            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_RCS, matchCriteria);
-            return this;
-        }
-
-        /** Build the VcnCellUnderlyingNetworkTemplate. */
-        @NonNull
-        public VcnCellUnderlyingNetworkTemplate build() {
-            return new VcnCellUnderlyingNetworkTemplate(
-                    mMeteredMatchCriteria,
-                    mMinEntryUpstreamBandwidthKbps,
-                    mMinExitUpstreamBandwidthKbps,
-                    mMinEntryDownstreamBandwidthKbps,
-                    mMinExitDownstreamBandwidthKbps,
-                    mAllowedNetworkPlmnIds,
-                    mAllowedSpecificCarrierIds,
-                    mRoamingMatchCriteria,
-                    mOpportunisticMatchCriteria,
-                    mCapabilitiesMatchCriteria);
-        }
-    }
-}
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
deleted file mode 100644
index a27e923..0000000
--- a/core/java/android/net/vcn/VcnConfig.java
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.net.vcn;
-
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_TEST;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-
-import static com.android.internal.annotations.VisibleForTesting.Visibility;
-import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
-import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.PersistableBundle;
-import android.util.ArraySet;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
-import com.android.server.vcn.util.PersistableBundleUtils;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * This class represents a configuration for a Virtual Carrier Network.
- *
- * <p>Each {@link VcnGatewayConnectionConfig} instance added represents a connection that will be
- * brought up on demand based on active {@link NetworkRequest}(s).
- *
- * @see VcnManager for more information on the Virtual Carrier Network feature
- */
-public final class VcnConfig implements Parcelable {
-    @NonNull private static final String TAG = VcnConfig.class.getSimpleName();
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            prefix = {"TRANSPORT_"},
-            value = {
-                NetworkCapabilities.TRANSPORT_CELLULAR,
-                NetworkCapabilities.TRANSPORT_WIFI,
-            })
-    @Target({ElementType.TYPE_USE})
-    public @interface VcnUnderlyingNetworkTransport {}
-
-    private static final Set<Integer> ALLOWED_TRANSPORTS = new ArraySet<>();
-
-    static {
-        ALLOWED_TRANSPORTS.add(TRANSPORT_WIFI);
-        ALLOWED_TRANSPORTS.add(TRANSPORT_CELLULAR);
-        ALLOWED_TRANSPORTS.add(TRANSPORT_TEST);
-    }
-
-    private static final String PACKAGE_NAME_KEY = "mPackageName";
-    @NonNull private final String mPackageName;
-
-    private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs";
-    @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs;
-
-    private static final Set<Integer> RESTRICTED_TRANSPORTS_DEFAULT =
-            Collections.singleton(TRANSPORT_WIFI);
-    private static final String RESTRICTED_TRANSPORTS_KEY = "mRestrictedTransports";
-    @NonNull private final Set<Integer> mRestrictedTransports;
-
-    private static final String IS_TEST_MODE_PROFILE_KEY = "mIsTestModeProfile";
-    private final boolean mIsTestModeProfile;
-
-    private VcnConfig(
-            @NonNull String packageName,
-            @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs,
-            @NonNull Set<Integer> restrictedTransports,
-            boolean isTestModeProfile) {
-        mPackageName = packageName;
-        mGatewayConnectionConfigs =
-                Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs));
-        mRestrictedTransports = Collections.unmodifiableSet(new ArraySet<>(restrictedTransports));
-        mIsTestModeProfile = isTestModeProfile;
-
-        validate();
-    }
-
-    /**
-     * Deserializes a VcnConfig from a PersistableBundle.
-     *
-     * @hide
-     */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public VcnConfig(@NonNull PersistableBundle in) {
-        mPackageName = in.getString(PACKAGE_NAME_KEY);
-
-        final PersistableBundle gatewayConnectionConfigsBundle =
-                in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY);
-        mGatewayConnectionConfigs =
-                new ArraySet<>(
-                        PersistableBundleUtils.toList(
-                                gatewayConnectionConfigsBundle, VcnGatewayConnectionConfig::new));
-
-        final PersistableBundle restrictedTransportsBundle =
-                in.getPersistableBundle(RESTRICTED_TRANSPORTS_KEY);
-        if (restrictedTransportsBundle == null) {
-            // RESTRICTED_TRANSPORTS_KEY was added in U and does not exist in VcnConfigs created in
-            // older platforms
-            mRestrictedTransports = RESTRICTED_TRANSPORTS_DEFAULT;
-        } else {
-            mRestrictedTransports =
-                    new ArraySet<Integer>(
-                            PersistableBundleUtils.toList(
-                                    restrictedTransportsBundle, INTEGER_DESERIALIZER));
-        }
-
-        mIsTestModeProfile = in.getBoolean(IS_TEST_MODE_PROFILE_KEY);
-
-        validate();
-    }
-
-    private void validate() {
-        Objects.requireNonNull(mPackageName, "packageName was null");
-        Preconditions.checkCollectionNotEmpty(
-                mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty");
-
-        final Iterator<Integer> iterator = mRestrictedTransports.iterator();
-        while (iterator.hasNext()) {
-            final int transport = iterator.next();
-            if (!ALLOWED_TRANSPORTS.contains(transport)) {
-                iterator.remove();
-                Log.w(
-                        TAG,
-                        "Found invalid transport "
-                                + transport
-                                + " which might be from a new version of VcnConfig");
-            }
-
-            if (transport == TRANSPORT_TEST && !mIsTestModeProfile) {
-                throw new IllegalArgumentException(
-                        "Found TRANSPORT_TEST in a non-test-mode profile");
-            }
-        }
-    }
-
-    /**
-     * Retrieve the package name of the provisioning app.
-     *
-     * @hide
-     */
-    @NonNull
-    public String getProvisioningPackageName() {
-        return mPackageName;
-    }
-
-    /** Retrieves the set of configured GatewayConnection(s). */
-    @NonNull
-    public Set<VcnGatewayConnectionConfig> getGatewayConnectionConfigs() {
-        return Collections.unmodifiableSet(mGatewayConnectionConfigs);
-    }
-
-    /**
-     * Retrieve the transports that will be restricted by the VCN.
-     *
-     * @see Builder#setRestrictedUnderlyingNetworkTransports(Set)
-     */
-    @NonNull
-    public Set<@VcnUnderlyingNetworkTransport Integer> getRestrictedUnderlyingNetworkTransports() {
-        return Collections.unmodifiableSet(mRestrictedTransports);
-    }
-
-    /**
-     * Returns whether or not this VcnConfig is restricted to test networks.
-     *
-     * @hide
-     */
-    public boolean isTestModeProfile() {
-        return mIsTestModeProfile;
-    }
-
-    /**
-     * Serializes this object to a PersistableBundle.
-     *
-     * @hide
-     */
-    @NonNull
-    public PersistableBundle toPersistableBundle() {
-        final PersistableBundle result = new PersistableBundle();
-
-        result.putString(PACKAGE_NAME_KEY, mPackageName);
-
-        final PersistableBundle gatewayConnectionConfigsBundle =
-                PersistableBundleUtils.fromList(
-                        new ArrayList<>(mGatewayConnectionConfigs),
-                        VcnGatewayConnectionConfig::toPersistableBundle);
-        result.putPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY, gatewayConnectionConfigsBundle);
-
-        final PersistableBundle restrictedTransportsBundle =
-                PersistableBundleUtils.fromList(
-                        new ArrayList<>(mRestrictedTransports), INTEGER_SERIALIZER);
-        result.putPersistableBundle(RESTRICTED_TRANSPORTS_KEY, restrictedTransportsBundle);
-
-        result.putBoolean(IS_TEST_MODE_PROFILE_KEY, mIsTestModeProfile);
-
-        return result;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(
-                mPackageName, mGatewayConnectionConfigs, mRestrictedTransports, mIsTestModeProfile);
-    }
-
-    @Override
-    public boolean equals(@Nullable Object other) {
-        if (!(other instanceof VcnConfig)) {
-            return false;
-        }
-
-        final VcnConfig rhs = (VcnConfig) other;
-        return mPackageName.equals(rhs.mPackageName)
-                && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs)
-                && mRestrictedTransports.equals(rhs.mRestrictedTransports)
-                && mIsTestModeProfile == rhs.mIsTestModeProfile;
-    }
-
-    // Parcelable methods
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel out, int flags) {
-        out.writeParcelable(toPersistableBundle(), flags);
-    }
-
-    @NonNull
-    public static final Parcelable.Creator<VcnConfig> CREATOR =
-            new Parcelable.Creator<VcnConfig>() {
-                @NonNull
-                public VcnConfig createFromParcel(Parcel in) {
-                    return new VcnConfig((PersistableBundle) in.readParcelable(null, android.os.PersistableBundle.class));
-                }
-
-                @NonNull
-                public VcnConfig[] newArray(int size) {
-                    return new VcnConfig[size];
-                }
-            };
-
-    /** This class is used to incrementally build {@link VcnConfig} objects. */
-    public static final class Builder {
-        @NonNull private final String mPackageName;
-
-        @NonNull
-        private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>();
-
-        @NonNull private final Set<Integer> mRestrictedTransports = new ArraySet<>();
-
-        private boolean mIsTestModeProfile = false;
-
-        public Builder(@NonNull Context context) {
-            Objects.requireNonNull(context, "context was null");
-
-            mPackageName = context.getOpPackageName();
-            mRestrictedTransports.addAll(RESTRICTED_TRANSPORTS_DEFAULT);
-        }
-
-        /**
-         * Adds a configuration for an individual gateway connection.
-         *
-         * @param gatewayConnectionConfig the configuration for an individual gateway connection
-         * @return this {@link Builder} instance, for chaining
-         * @throws IllegalArgumentException if a VcnGatewayConnectionConfig has already been set for
-         *     this {@link VcnConfig} with the same GatewayConnection name (as returned via {@link
-         *     VcnGatewayConnectionConfig#getGatewayConnectionName()}).
-         */
-        @NonNull
-        public Builder addGatewayConnectionConfig(
-                @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) {
-            Objects.requireNonNull(gatewayConnectionConfig, "gatewayConnectionConfig was null");
-
-            for (final VcnGatewayConnectionConfig vcnGatewayConnectionConfig :
-                    mGatewayConnectionConfigs) {
-                if (vcnGatewayConnectionConfig
-                        .getGatewayConnectionName()
-                        .equals(gatewayConnectionConfig.getGatewayConnectionName())) {
-                    throw new IllegalArgumentException(
-                            "GatewayConnection for specified name already exists");
-                }
-            }
-
-            mGatewayConnectionConfigs.add(gatewayConnectionConfig);
-            return this;
-        }
-
-        private void validateRestrictedTransportsOrThrow(Set<Integer> restrictedTransports) {
-            Objects.requireNonNull(restrictedTransports, "transports was null");
-
-            for (int transport : restrictedTransports) {
-                if (!ALLOWED_TRANSPORTS.contains(transport)) {
-                    throw new IllegalArgumentException("Invalid transport " + transport);
-                }
-            }
-        }
-
-        /**
-         * Sets transports that will be restricted by the VCN.
-         *
-         * <p>In general, apps will not be able to bind to, or use a restricted network. In other
-         * words, unless the network type is marked restricted, any app can opt to use underlying
-         * networks, instead of through the VCN.
-         *
-         * @param transports transports that will be restricted by VCN. Networks that include any of
-         *     the transports will be marked as restricted. {@link
-         *     NetworkCapabilities#TRANSPORT_WIFI} is marked restricted by default.
-         * @return this {@link Builder} instance, for chaining
-         * @throws IllegalArgumentException if the input contains unsupported transport types.
-         * @see NetworkCapabilities#NET_CAPABILITY_NOT_RESTRICTED
-         */
-        @NonNull
-        public Builder setRestrictedUnderlyingNetworkTransports(
-                @NonNull Set<@VcnUnderlyingNetworkTransport Integer> transports) {
-            validateRestrictedTransportsOrThrow(transports);
-
-            mRestrictedTransports.clear();
-            mRestrictedTransports.addAll(transports);
-            return this;
-        }
-
-        /**
-         * Restricts this VcnConfig to matching with test networks (only).
-         *
-         * <p>This method is for testing only, and must not be used by apps. Calling {@link
-         * VcnManager#setVcnConfig(ParcelUuid, VcnConfig)} with a VcnConfig where test-network usage
-         * is enabled will require the MANAGE_TEST_NETWORKS permission.
-         *
-         * @return this {@link Builder} instance, for chaining
-         * @hide
-         */
-        @NonNull
-        public Builder setIsTestModeProfile() {
-            mIsTestModeProfile = true;
-            return this;
-        }
-
-        /**
-         * Builds and validates the VcnConfig.
-         *
-         * @return an immutable VcnConfig instance
-         */
-        @NonNull
-        public VcnConfig build() {
-            return new VcnConfig(
-                    mPackageName,
-                    mGatewayConnectionConfigs,
-                    mRestrictedTransports,
-                    mIsTestModeProfile);
-        }
-    }
-}
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
deleted file mode 100644
index 3219ce8..0000000
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ /dev/null
@@ -1,870 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.net.vcn;
-
-import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
-import static android.net.vcn.Flags.FLAG_MAINLINE_VCN_MODULE_API;
-import static android.net.vcn.Flags.FLAG_SAFE_MODE_CONFIG;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
-
-import static com.android.internal.annotations.VisibleForTesting.Visibility;
-
-import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.ipsec.ike.IkeTunnelConnectionParams;
-import android.net.vcn.persistablebundleutils.TunnelConnectionParamsUtils;
-import android.os.PersistableBundle;
-import android.util.ArraySet;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
-import com.android.server.vcn.util.PersistableBundleUtils;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.concurrent.TimeUnit;
-
-/**
- * This class represents a configuration for a connection to a Virtual Carrier Network gateway.
- *
- * <p>Each VcnGatewayConnectionConfig represents a single logical connection to a carrier gateway,
- * and may provide one or more telephony services (as represented by network capabilities). Each
- * gateway is expected to provide mobility for a given session as the device roams across {@link
- * Network}s.
- *
- * <p>A VCN connection based on this configuration will be brought up dynamically based on device
- * settings, and filed NetworkRequests. Underlying Networks must provide INTERNET connectivity, and
- * must be part of the subscription group under which this configuration is registered (see {@link
- * VcnManager#setVcnConfig}).
- *
- * <p>As an abstraction of a cellular network, services that can be provided by a VCN network are
- * limited to services provided by cellular networks:
- *
- * <ul>
- *   <li>{@link NetworkCapabilities#NET_CAPABILITY_MMS}
- *   <li>{@link NetworkCapabilities#NET_CAPABILITY_SUPL}
- *   <li>{@link NetworkCapabilities#NET_CAPABILITY_DUN}
- *   <li>{@link NetworkCapabilities#NET_CAPABILITY_FOTA}
- *   <li>{@link NetworkCapabilities#NET_CAPABILITY_IMS}
- *   <li>{@link NetworkCapabilities#NET_CAPABILITY_CBS}
- *   <li>{@link NetworkCapabilities#NET_CAPABILITY_IA}
- *   <li>{@link NetworkCapabilities#NET_CAPABILITY_RCS}
- *   <li>{@link NetworkCapabilities#NET_CAPABILITY_XCAP}
- *   <li>{@link NetworkCapabilities#NET_CAPABILITY_EIMS}
- *   <li>{@link NetworkCapabilities#NET_CAPABILITY_INTERNET}
- *   <li>{@link NetworkCapabilities#NET_CAPABILITY_MCX}
- * </ul>
- */
-public final class VcnGatewayConnectionConfig {
-    /**
-     * Minimum NAT timeout not set.
-     *
-     * <p>When the timeout is not set, the device will automatically choose a keepalive interval and
-     * may reduce the keepalive frequency for power-optimization.
-     */
-    @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
-    // This constant does not represent a minimum value. It indicates the value is not configured.
-    @SuppressLint("MinMaxConstant")
-    public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET = -1;
-
-    /** @hide */
-    public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS = 120;
-
-    // TODO: Use MIN_MTU_V6 once it is public, @hide
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int MIN_MTU_V6 = 1280;
-
-    /**
-     * The set of allowed capabilities for exposed capabilities.
-     *
-     * @hide
-     */
-    public static final Set<Integer> ALLOWED_CAPABILITIES;
-
-    static {
-        Set<Integer> allowedCaps = new ArraySet<>();
-        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_MMS);
-        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_SUPL);
-        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_DUN);
-        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_FOTA);
-        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_IMS);
-        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_CBS);
-        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_IA);
-        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_RCS);
-        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_XCAP);
-        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_EIMS);
-        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_MCX);
-
-        ALLOWED_CAPABILITIES = Collections.unmodifiableSet(allowedCaps);
-    }
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            prefix = {"NET_CAPABILITY_"},
-            value = {
-                NetworkCapabilities.NET_CAPABILITY_MMS,
-                NetworkCapabilities.NET_CAPABILITY_SUPL,
-                NetworkCapabilities.NET_CAPABILITY_DUN,
-                NetworkCapabilities.NET_CAPABILITY_FOTA,
-                NetworkCapabilities.NET_CAPABILITY_IMS,
-                NetworkCapabilities.NET_CAPABILITY_CBS,
-                NetworkCapabilities.NET_CAPABILITY_IA,
-                NetworkCapabilities.NET_CAPABILITY_RCS,
-                NetworkCapabilities.NET_CAPABILITY_XCAP,
-                NetworkCapabilities.NET_CAPABILITY_EIMS,
-                NetworkCapabilities.NET_CAPABILITY_INTERNET,
-                NetworkCapabilities.NET_CAPABILITY_MCX,
-            })
-    public @interface VcnSupportedCapability {}
-
-    /**
-     * Perform mobility update to attempt recovery from suspected data stalls.
-     *
-     * <p>If set, the gateway connection will monitor the data stall detection of the VCN network.
-     * When there is a suspected data stall, the gateway connection will attempt recovery by
-     * performing a mobility update on the underlying IKE session.
-     */
-    public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0;
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            prefix = {"VCN_GATEWAY_OPTION_"},
-            value = {
-                VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY,
-            })
-    public @interface VcnGatewayOption {}
-
-    private static final Set<Integer> ALLOWED_GATEWAY_OPTIONS = new ArraySet<>();
-
-    static {
-        ALLOWED_GATEWAY_OPTIONS.add(VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY);
-    }
-
-    private static final int DEFAULT_MAX_MTU = 1500;
-
-    /**
-     * The maximum number of retry intervals that may be specified.
-     *
-     * <p>Limited to ensure an upper bound on config sizes.
-     */
-    private static final int MAX_RETRY_INTERVAL_COUNT = 10;
-
-    /**
-     * The minimum allowable repeating retry interval
-     *
-     * <p>To ensure the device is not constantly being woken up, this retry interval MUST be greater
-     * than this value.
-     *
-     * @see {@link Builder#setRetryIntervalsMillis()}
-     */
-    private static final long MINIMUM_REPEATING_RETRY_INTERVAL_MS = TimeUnit.MINUTES.toMillis(15);
-
-    private static final long[] DEFAULT_RETRY_INTERVALS_MS =
-            new long[] {
-                TimeUnit.SECONDS.toMillis(1),
-                TimeUnit.SECONDS.toMillis(2),
-                TimeUnit.SECONDS.toMillis(5),
-                TimeUnit.SECONDS.toMillis(30),
-                TimeUnit.MINUTES.toMillis(1),
-                TimeUnit.MINUTES.toMillis(5),
-                TimeUnit.MINUTES.toMillis(15)
-            };
-
-    /** @hide */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static final List<VcnUnderlyingNetworkTemplate> DEFAULT_UNDERLYING_NETWORK_TEMPLATES =
-            new ArrayList<>();
-
-    static {
-        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
-                new VcnCellUnderlyingNetworkTemplate.Builder()
-                        .setOpportunistic(MATCH_REQUIRED)
-                        .build());
-
-        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
-                new VcnWifiUnderlyingNetworkTemplate.Builder()
-                        .build());
-
-        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
-                new VcnCellUnderlyingNetworkTemplate.Builder()
-                        .build());
-    }
-
-    private static final String GATEWAY_CONNECTION_NAME_KEY = "mGatewayConnectionName";
-    @NonNull private final String mGatewayConnectionName;
-
-    private static final String TUNNEL_CONNECTION_PARAMS_KEY = "mTunnelConnectionParams";
-    @NonNull private IkeTunnelConnectionParams mTunnelConnectionParams;
-
-    private static final String EXPOSED_CAPABILITIES_KEY = "mExposedCapabilities";
-    @NonNull private final SortedSet<Integer> mExposedCapabilities;
-
-    /** @hide */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static final String UNDERLYING_NETWORK_TEMPLATES_KEY = "mUnderlyingNetworkTemplates";
-
-    @NonNull private final List<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkTemplates;
-
-    private static final String MAX_MTU_KEY = "mMaxMtu";
-    private final int mMaxMtu;
-
-    private static final String RETRY_INTERVAL_MS_KEY = "mRetryIntervalsMs";
-    @NonNull private final long[] mRetryIntervalsMs;
-
-    private static final String MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS_KEY =
-            "mMinUdpPort4500NatTimeoutSeconds";
-    private final int mMinUdpPort4500NatTimeoutSeconds;
-
-    private static final String IS_SAFE_MODE_DISABLED_KEY = "mIsSafeModeDisabled";
-    private final boolean mIsSafeModeDisabled;
-
-    private static final String GATEWAY_OPTIONS_KEY = "mGatewayOptions";
-    @NonNull private final Set<Integer> mGatewayOptions;
-
-    /** Builds a VcnGatewayConnectionConfig with the specified parameters. */
-    private VcnGatewayConnectionConfig(
-            @NonNull String gatewayConnectionName,
-            @NonNull IkeTunnelConnectionParams tunnelConnectionParams,
-            @NonNull Set<Integer> exposedCapabilities,
-            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            @NonNull long[] retryIntervalsMs,
-            @IntRange(from = MIN_MTU_V6) int maxMtu,
-            @NonNull int minUdpPort4500NatTimeoutSeconds,
-            boolean isSafeModeDisabled,
-            @NonNull Set<Integer> gatewayOptions) {
-        mGatewayConnectionName = gatewayConnectionName;
-        mTunnelConnectionParams = tunnelConnectionParams;
-        mExposedCapabilities = new TreeSet(exposedCapabilities);
-        mRetryIntervalsMs = retryIntervalsMs;
-        mMaxMtu = maxMtu;
-        mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds;
-        mGatewayOptions = Collections.unmodifiableSet(new ArraySet(gatewayOptions));
-        mIsSafeModeDisabled = isSafeModeDisabled;
-
-        mUnderlyingNetworkTemplates = new ArrayList<>(underlyingNetworkTemplates);
-        if (mUnderlyingNetworkTemplates.isEmpty()) {
-            mUnderlyingNetworkTemplates.addAll(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
-        }
-
-        validate();
-    }
-
-    // Null check MUST be done for all new fields added to VcnGatewayConnectionConfig, to avoid
-    // crashes when parsing PersistableBundle built on old platforms.
-    /** @hide */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public VcnGatewayConnectionConfig(@NonNull PersistableBundle in) {
-        final PersistableBundle tunnelConnectionParamsBundle =
-                in.getPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY);
-        Objects.requireNonNull(
-                tunnelConnectionParamsBundle, "tunnelConnectionParamsBundle was null");
-
-        final PersistableBundle exposedCapsBundle =
-                in.getPersistableBundle(EXPOSED_CAPABILITIES_KEY);
-        mGatewayConnectionName = in.getString(GATEWAY_CONNECTION_NAME_KEY);
-        mTunnelConnectionParams =
-                TunnelConnectionParamsUtils.fromPersistableBundle(tunnelConnectionParamsBundle);
-        mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
-                exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
-
-        final PersistableBundle networkTemplatesBundle =
-                in.getPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY);
-
-        if (networkTemplatesBundle == null) {
-            // UNDERLYING_NETWORK_TEMPLATES_KEY was added in Android T. Thus
-            // VcnGatewayConnectionConfig created on old platforms will not have this data and will
-            // be assigned with the default value
-            mUnderlyingNetworkTemplates = new ArrayList<>(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
-        } else {
-            mUnderlyingNetworkTemplates =
-                    PersistableBundleUtils.toList(
-                            networkTemplatesBundle,
-                            VcnUnderlyingNetworkTemplate::fromPersistableBundle);
-        }
-
-        final PersistableBundle gatewayOptionsBundle = in.getPersistableBundle(GATEWAY_OPTIONS_KEY);
-
-        if (gatewayOptionsBundle == null) {
-            // GATEWAY_OPTIONS_KEY was added in Android U. Thus VcnGatewayConnectionConfig created
-            // on old platforms will not have this data and will be assigned with the default value
-            mGatewayOptions = Collections.emptySet();
-        } else {
-            mGatewayOptions =
-                    new ArraySet<>(
-                            PersistableBundleUtils.toList(
-                                    gatewayOptionsBundle,
-                                    PersistableBundleUtils.INTEGER_DESERIALIZER));
-        }
-
-        mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY);
-        mMaxMtu = in.getInt(MAX_MTU_KEY);
-        mMinUdpPort4500NatTimeoutSeconds =
-                in.getInt(
-                        MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS_KEY,
-                        MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET);
-        mIsSafeModeDisabled = in.getBoolean(IS_SAFE_MODE_DISABLED_KEY);
-
-        validate();
-    }
-
-    private void validate() {
-        Objects.requireNonNull(mGatewayConnectionName, "gatewayConnectionName was null");
-        Objects.requireNonNull(mTunnelConnectionParams, "tunnel connection parameter was null");
-
-        Preconditions.checkArgument(
-                mExposedCapabilities != null && !mExposedCapabilities.isEmpty(),
-                "exposedCapsBundle was null or empty");
-        for (Integer cap : getAllExposedCapabilities()) {
-            checkValidCapability(cap);
-        }
-
-        validateNetworkTemplateList(mUnderlyingNetworkTemplates);
-        Objects.requireNonNull(mRetryIntervalsMs, "retryIntervalsMs was null");
-        validateRetryInterval(mRetryIntervalsMs);
-
-        Preconditions.checkArgument(
-                mMaxMtu >= MIN_MTU_V6, "maxMtu must be at least IPv6 min MTU (1280)");
-
-        Preconditions.checkArgument(
-                mMinUdpPort4500NatTimeoutSeconds == MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET
-                        || mMinUdpPort4500NatTimeoutSeconds
-                                >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
-                "minUdpPort4500NatTimeoutSeconds must be at least 120s");
-
-        for (int option : mGatewayOptions) {
-            validateGatewayOption(option);
-        }
-    }
-
-    private static void checkValidCapability(int capability) {
-        Preconditions.checkArgument(
-                ALLOWED_CAPABILITIES.contains(capability),
-                "NetworkCapability " + capability + "out of range");
-    }
-
-    private static void validateRetryInterval(@Nullable long[] retryIntervalsMs) {
-        Preconditions.checkArgument(
-                retryIntervalsMs != null
-                        && retryIntervalsMs.length > 0
-                        && retryIntervalsMs.length <= MAX_RETRY_INTERVAL_COUNT,
-                "retryIntervalsMs was null, empty or exceed max interval count");
-
-        final long repeatingInterval = retryIntervalsMs[retryIntervalsMs.length - 1];
-        if (repeatingInterval < MINIMUM_REPEATING_RETRY_INTERVAL_MS) {
-            throw new IllegalArgumentException(
-                    "Repeating retry interval was too short, must be a minimum of 15 minutes: "
-                            + repeatingInterval);
-        }
-    }
-
-    private static void validateNetworkTemplateList(
-            List<VcnUnderlyingNetworkTemplate> networkPriorityRules) {
-        Objects.requireNonNull(networkPriorityRules, "networkPriorityRules is null");
-
-        Set<VcnUnderlyingNetworkTemplate> existingRules = new ArraySet<>();
-        for (VcnUnderlyingNetworkTemplate rule : networkPriorityRules) {
-            Objects.requireNonNull(rule, "Found null value VcnUnderlyingNetworkTemplate");
-            if (!existingRules.add(rule)) {
-                throw new IllegalArgumentException("Found duplicate VcnUnderlyingNetworkTemplate");
-            }
-        }
-    }
-
-    private static void validateGatewayOption(int option) {
-        if (!ALLOWED_GATEWAY_OPTIONS.contains(option)) {
-            throw new IllegalArgumentException("Invalid vcn gateway option: " + option);
-        }
-    }
-
-    /**
-     * Returns the configured Gateway Connection name.
-     *
-     * <p>This name is used by the configuring apps to distinguish between
-     * VcnGatewayConnectionConfigs configured on a single {@link VcnConfig}. This will be used as
-     * the identifier in VcnStatusCallback invocations.
-     *
-     * @see VcnManager.VcnStatusCallback#onGatewayConnectionError
-     */
-    @NonNull
-    public String getGatewayConnectionName() {
-        return mGatewayConnectionName;
-    }
-
-    /**
-     * Returns tunnel connection parameters.
-     *
-     * @hide
-     */
-    @NonNull
-    public IkeTunnelConnectionParams getTunnelConnectionParams() {
-        return mTunnelConnectionParams;
-    }
-
-    /**
-     * Returns all exposed capabilities.
-     *
-     * <p>The returned integer-value capabilities will not contain duplicates, and will be sorted in
-     * ascending numerical order.
-     *
-     * @see Builder#addExposedCapability(int)
-     * @see Builder#removeExposedCapability(int)
-     */
-    @NonNull
-    public int[] getExposedCapabilities() {
-        // Sorted set guarantees ordering
-        final int[] caps = new int[mExposedCapabilities.size()];
-
-        int i = 0;
-        for (int c : mExposedCapabilities) {
-            caps[i++] = c;
-        }
-
-        return caps;
-    }
-
-    /**
-     * Returns all exposed capabilities.
-     *
-     * <p>Left to prevent the need to make major changes while changes are actively in flight.
-     *
-     * @deprecated use getExposedCapabilities() instead
-     * @hide
-     */
-    @Deprecated
-    @NonNull
-    public Set<Integer> getAllExposedCapabilities() {
-        return Collections.unmodifiableSet(mExposedCapabilities);
-    }
-
-    /**
-     * Retrieve the VcnUnderlyingNetworkTemplate list, or a default list if it is not configured.
-     *
-     * @see Builder#setVcnUnderlyingNetworkPriorities(List)
-     */
-    @NonNull
-    public List<VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities() {
-        return new ArrayList<>(mUnderlyingNetworkTemplates);
-    }
-
-    /**
-     * Retrieves the configured retry intervals.
-     *
-     * @see Builder#setRetryIntervalsMillis(long[])
-     */
-    @NonNull
-    public long[] getRetryIntervalsMillis() {
-        return Arrays.copyOf(mRetryIntervalsMs, mRetryIntervalsMs.length);
-    }
-
-    /**
-     * Retrieves the maximum MTU allowed for this Gateway Connection.
-     *
-     * @see Builder#setMaxMtu(int)
-     */
-    @IntRange(from = MIN_MTU_V6)
-    public int getMaxMtu() {
-        return mMaxMtu;
-    }
-
-    /**
-     * Retrieves the maximum supported IKEv2/IPsec NATT keepalive timeout.
-     *
-     * @see Builder#setMinUdpPort4500NatTimeoutSeconds(int)
-     */
-    public int getMinUdpPort4500NatTimeoutSeconds() {
-        return mMinUdpPort4500NatTimeoutSeconds;
-    }
-
-    /**
-     * Check whether safe mode is enabled
-     *
-     * @see Builder#setSafeModeEnabled(boolean)
-     */
-    @FlaggedApi(FLAG_SAFE_MODE_CONFIG)
-    public boolean isSafeModeEnabled() {
-        return !mIsSafeModeDisabled;
-    }
-
-    /**
-     * Checks if the given VCN gateway option is enabled.
-     *
-     * @param option the option to check.
-     * @throws IllegalArgumentException if the provided option is invalid.
-     * @see Builder#addGatewayOption(int)
-     * @see Builder#removeGatewayOption(int)
-     */
-    public boolean hasGatewayOption(@VcnGatewayOption int option) {
-        validateGatewayOption(option);
-        return mGatewayOptions.contains(option);
-    }
-
-    /**
-     * Converts this config to a PersistableBundle.
-     *
-     * @hide
-     */
-    @NonNull
-    @VisibleForTesting(visibility = Visibility.PROTECTED)
-    public PersistableBundle toPersistableBundle() {
-        final PersistableBundle result = new PersistableBundle();
-
-        final PersistableBundle tunnelConnectionParamsBundle =
-                TunnelConnectionParamsUtils.toPersistableBundle(mTunnelConnectionParams);
-        final PersistableBundle exposedCapsBundle =
-                PersistableBundleUtils.fromList(
-                        new ArrayList<>(mExposedCapabilities),
-                        PersistableBundleUtils.INTEGER_SERIALIZER);
-        final PersistableBundle networkTemplatesBundle =
-                PersistableBundleUtils.fromList(
-                        mUnderlyingNetworkTemplates,
-                        VcnUnderlyingNetworkTemplate::toPersistableBundle);
-        final PersistableBundle gatewayOptionsBundle =
-                PersistableBundleUtils.fromList(
-                        new ArrayList<>(mGatewayOptions),
-                        PersistableBundleUtils.INTEGER_SERIALIZER);
-
-        result.putString(GATEWAY_CONNECTION_NAME_KEY, mGatewayConnectionName);
-        result.putPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY, tunnelConnectionParamsBundle);
-        result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle);
-        result.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, networkTemplatesBundle);
-        result.putPersistableBundle(GATEWAY_OPTIONS_KEY, gatewayOptionsBundle);
-        result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs);
-        result.putInt(MAX_MTU_KEY, mMaxMtu);
-        result.putInt(MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS_KEY, mMinUdpPort4500NatTimeoutSeconds);
-        result.putBoolean(IS_SAFE_MODE_DISABLED_KEY, mIsSafeModeDisabled);
-
-        return result;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(
-                mGatewayConnectionName,
-                mTunnelConnectionParams,
-                mExposedCapabilities,
-                mUnderlyingNetworkTemplates,
-                Arrays.hashCode(mRetryIntervalsMs),
-                mMaxMtu,
-                mMinUdpPort4500NatTimeoutSeconds,
-                mIsSafeModeDisabled,
-                mGatewayOptions);
-    }
-
-    @Override
-    public boolean equals(@Nullable Object other) {
-        if (!(other instanceof VcnGatewayConnectionConfig)) {
-            return false;
-        }
-
-        final VcnGatewayConnectionConfig rhs = (VcnGatewayConnectionConfig) other;
-        return mGatewayConnectionName.equals(rhs.mGatewayConnectionName)
-                && mTunnelConnectionParams.equals(rhs.mTunnelConnectionParams)
-                && mExposedCapabilities.equals(rhs.mExposedCapabilities)
-                && mUnderlyingNetworkTemplates.equals(rhs.mUnderlyingNetworkTemplates)
-                && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs)
-                && mMaxMtu == rhs.mMaxMtu
-                && mMinUdpPort4500NatTimeoutSeconds == rhs.mMinUdpPort4500NatTimeoutSeconds
-                && mIsSafeModeDisabled == rhs.mIsSafeModeDisabled
-                && mGatewayOptions.equals(rhs.mGatewayOptions);
-    }
-
-    /**
-     * This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects.
-     */
-    public static final class Builder {
-        @NonNull private final String mGatewayConnectionName;
-        @NonNull private final IkeTunnelConnectionParams mTunnelConnectionParams;
-        @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet();
-
-        @NonNull
-        private final List<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkTemplates =
-                new ArrayList<>(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
-
-        @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
-        private int mMaxMtu = DEFAULT_MAX_MTU;
-        private int mMinUdpPort4500NatTimeoutSeconds = MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET;
-        private boolean mIsSafeModeDisabled = false;
-
-        @NonNull private final Set<Integer> mGatewayOptions = new ArraySet<>();
-
-        // TODO: (b/175829816) Consider VCN-exposed capabilities that may be transport dependent.
-        //       Consider the case where the VCN might only expose MMS on WiFi, but defer to MMS
-        //       when on Cell.
-
-        /**
-         * Construct a Builder object.
-         *
-         * @param gatewayConnectionName the String GatewayConnection name for this
-         *     VcnGatewayConnectionConfig. Each VcnGatewayConnectionConfig within a {@link
-         *     VcnConfig} must be given a unique name. This name is used by the caller to
-         *     distinguish between VcnGatewayConnectionConfigs configured on a single {@link
-         *     VcnConfig}. This will be used as the identifier in VcnStatusCallback invocations.
-         * @param tunnelConnectionParams the IKE tunnel connection configuration
-         * @throws IllegalArgumentException if the provided IkeTunnelConnectionParams is not
-         *     configured to support MOBIKE
-         * @see IkeTunnelConnectionParams
-         * @see VcnManager.VcnStatusCallback#onGatewayConnectionError
-         */
-        public Builder(
-                @NonNull String gatewayConnectionName,
-                @NonNull IkeTunnelConnectionParams tunnelConnectionParams) {
-            Objects.requireNonNull(gatewayConnectionName, "gatewayConnectionName was null");
-            Objects.requireNonNull(tunnelConnectionParams, "tunnelConnectionParams was null");
-            if (!tunnelConnectionParams.getIkeSessionParams().hasIkeOption(IKE_OPTION_MOBIKE)) {
-                throw new IllegalArgumentException(
-                        "MOBIKE must be configured for the provided IkeSessionParams");
-            }
-
-            mGatewayConnectionName = gatewayConnectionName;
-            mTunnelConnectionParams = tunnelConnectionParams;
-        }
-
-        /**
-         * Add a capability that this VCN Gateway Connection will support.
-         *
-         * @param exposedCapability the app-facing capability to be exposed by this VCN Gateway
-         *     Connection (i.e., the capabilities that this VCN Gateway Connection will support).
-         * @return this {@link Builder} instance, for chaining
-         * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
-         *     Connection
-         */
-        @NonNull
-        public Builder addExposedCapability(@VcnSupportedCapability int exposedCapability) {
-            checkValidCapability(exposedCapability);
-
-            mExposedCapabilities.add(exposedCapability);
-            return this;
-        }
-
-        /**
-         * Remove a capability that this VCN Gateway Connection will support.
-         *
-         * @param exposedCapability the app-facing capability to not be exposed by this VCN Gateway
-         *     Connection (i.e., the capabilities that this VCN Gateway Connection will support)
-         * @return this {@link Builder} instance, for chaining
-         * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
-         *     Connection
-         */
-        @NonNull
-        @SuppressLint("BuilderSetStyle") // For consistency with NetCaps.Builder add/removeCap
-        public Builder removeExposedCapability(@VcnSupportedCapability int exposedCapability) {
-            checkValidCapability(exposedCapability);
-
-            mExposedCapabilities.remove(exposedCapability);
-            return this;
-        }
-
-        /**
-         * Set the list of templates to match underlying networks against, in high-to-low priority
-         * order.
-         *
-         * <p>To select the VCN underlying network, the VCN connection will go through all the
-         * network candidates and return a network matching the highest priority rule.
-         *
-         * <p>If multiple networks match the same rule, the VCN will prefer an already-selected
-         * network as opposed to a new/unselected network. However, if both are new/unselected
-         * networks, a network will be chosen arbitrarily amongst the networks matching the highest
-         * priority rule.
-         *
-         * <p>If all networks fail to match the rules provided, a carrier-owned underlying network
-         * will still be selected (if available, at random if necessary).
-         *
-         * @param underlyingNetworkTemplates a list of unique VcnUnderlyingNetworkTemplates that are
-         *     ordered from most to least preferred, or an empty list to use the default
-         *     prioritization. The default network prioritization order is Opportunistic cellular,
-         *     Carrier WiFi and then Macro cellular.
-         * @return this {@link Builder} instance, for chaining
-         */
-        @NonNull
-        public Builder setVcnUnderlyingNetworkPriorities(
-                @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) {
-            validateNetworkTemplateList(underlyingNetworkTemplates);
-
-            mUnderlyingNetworkTemplates.clear();
-
-            if (underlyingNetworkTemplates.isEmpty()) {
-                mUnderlyingNetworkTemplates.addAll(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
-            } else {
-                mUnderlyingNetworkTemplates.addAll(underlyingNetworkTemplates);
-            }
-
-            return this;
-        }
-
-        /**
-         * Set the retry interval between VCN establishment attempts upon successive failures.
-         *
-         * <p>The last retry interval will be repeated until safe mode is entered, or a connection
-         * is successfully established, at which point the retry timers will be reset. For power
-         * reasons, the last (repeated) retry interval MUST be at least 15 minutes.
-         *
-         * <p>Retry intervals MAY be subject to system power saving modes. That is to say that if
-         * the system enters a power saving mode, the retry may not occur until the device leaves
-         * the specified power saving mode. Intervals are sequential, and intervals will NOT be
-         * skipped if system power saving results in delaying retries (even if it exceed multiple
-         * retry intervals).
-         *
-         * <p>Each Gateway Connection will retry according to the retry intervals configured, but if
-         * safe mode is enabled, all Gateway Connection(s) will be disabled.
-         *
-         * @param retryIntervalsMs an array of between 1 and 10 millisecond intervals after which
-         *     the VCN will attempt to retry a session initiation. The last (repeating) retry
-         *     interval must be at least 15 minutes. Defaults to: {@code [1s, 2s, 5s, 30s, 1m, 5m,
-         *     15m]}
-         * @return this {@link Builder} instance, for chaining
-         * @see VcnManager for additional discussion on fail-safe mode
-         */
-        @NonNull
-        public Builder setRetryIntervalsMillis(@NonNull long[] retryIntervalsMs) {
-            validateRetryInterval(retryIntervalsMs);
-
-            mRetryIntervalsMs = retryIntervalsMs;
-            return this;
-        }
-
-        /**
-         * Sets the maximum MTU allowed for this VCN Gateway Connection.
-         *
-         * <p>This MTU is applied to the VCN Gateway Connection exposed Networks, and represents the
-         * MTU of the virtualized network.
-         *
-         * <p>The system may reduce the MTU below the maximum specified based on signals such as the
-         * MTU of the underlying networks (and adjusted for Gateway Connection overhead).
-         *
-         * @param maxMtu the maximum MTU allowed for this Gateway Connection. Must be greater than
-         *     the IPv6 minimum MTU of 1280. Defaults to 1500.
-         * @return this {@link Builder} instance, for chaining
-         */
-        @NonNull
-        public Builder setMaxMtu(@IntRange(from = MIN_MTU_V6) int maxMtu) {
-            Preconditions.checkArgument(
-                    maxMtu >= MIN_MTU_V6, "maxMtu must be at least IPv6 min MTU (1280)");
-
-            mMaxMtu = maxMtu;
-            return this;
-        }
-
-        /**
-         * Sets the maximum supported IKEv2/IPsec NATT keepalive timeout.
-         *
-         * <p>This is used as a power-optimization hint for other IKEv2/IPsec use cases (e.g. VPNs,
-         * or IWLAN) to reduce the necessary keepalive frequency, thus conserving power and data.
-         *
-         * @param minUdpPort4500NatTimeoutSeconds the maximum keepalive timeout supported by the VCN
-         *     Gateway Connection, generally the minimum duration a NAT mapping is cached on the VCN
-         *     Gateway; or {@link MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET} to clear this value.
-         * @return this {@link Builder} instance, for chaining
-         */
-        @NonNull
-        public Builder setMinUdpPort4500NatTimeoutSeconds(
-                @IntRange(from = MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS)
-                        int minUdpPort4500NatTimeoutSeconds) {
-            Preconditions.checkArgument(
-                    minUdpPort4500NatTimeoutSeconds == MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET
-                            || minUdpPort4500NatTimeoutSeconds
-                                    >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
-                    "Timeout must be at least 120s or MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET");
-
-            mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds;
-            return this;
-        }
-
-        /**
-         * Enables the specified VCN gateway option.
-         *
-         * @param option the option to be enabled
-         * @return this {@link Builder} instance, for chaining
-         * @throws IllegalArgumentException if the provided option is invalid
-         */
-        @NonNull
-        public Builder addGatewayOption(@VcnGatewayOption int option) {
-            validateGatewayOption(option);
-            mGatewayOptions.add(option);
-            return this;
-        }
-
-        /**
-         * Resets (disables) the specified VCN gateway option.
-         *
-         * @param option the option to be disabled
-         * @return this {@link Builder} instance, for chaining
-         * @throws IllegalArgumentException if the provided option is invalid
-         */
-        @NonNull
-        public Builder removeGatewayOption(@VcnGatewayOption int option) {
-            validateGatewayOption(option);
-            mGatewayOptions.remove(option);
-            return this;
-        }
-
-        /**
-         * Enable/disable safe mode
-         *
-         * <p>If a VCN fails to provide connectivity within a system-provided timeout, it will enter
-         * safe mode. In safe mode, the VCN Network will be torn down and the system will restore
-         * connectivity by allowing underlying cellular or WiFi networks to be used as default. At
-         * the same time, VCN will continue to retry until it succeeds.
-         *
-         * <p>When safe mode is disabled and VCN connection fails to provide connectivity, end users
-         * might not have connectivity, and may not have access to carrier-owned underlying
-         * networks.
-         *
-         * @param enabled whether safe mode should be enabled. Defaults to {@code true}
-         */
-        @FlaggedApi(FLAG_SAFE_MODE_CONFIG)
-        @NonNull
-        public Builder setSafeModeEnabled(boolean enabled) {
-            mIsSafeModeDisabled = !enabled;
-            return this;
-        }
-
-        /**
-         * Builds and validates the VcnGatewayConnectionConfig.
-         *
-         * @return an immutable VcnGatewayConnectionConfig instance
-         */
-        @NonNull
-        public VcnGatewayConnectionConfig build() {
-            return new VcnGatewayConnectionConfig(
-                    mGatewayConnectionName,
-                    mTunnelConnectionParams,
-                    mExposedCapabilities,
-                    mUnderlyingNetworkTemplates,
-                    mRetryIntervalsMs,
-                    mMaxMtu,
-                    mMinUdpPort4500NatTimeoutSeconds,
-                    mIsSafeModeDisabled,
-                    mGatewayOptions);
-        }
-    }
-}
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
deleted file mode 100644
index f275714..0000000
--- a/core/java/android/net/vcn/VcnManager.java
+++ /dev/null
@@ -1,772 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.net.vcn;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.RequiresFeature;
-import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
-import android.annotation.SystemService;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
-import android.os.ParcelUuid;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.net.module.util.BinderUtils;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executor;
-
-/**
- * VcnManager publishes APIs for applications to configure and manage Virtual Carrier Networks.
- *
- * <p>A VCN creates a virtualization layer to allow carriers to aggregate heterogeneous physical
- * networks, unifying them as a single carrier network. This enables infrastructure flexibility on
- * the part of carriers without impacting user connectivity, abstracting the physical network
- * technologies as an implementation detail of their public network.
- *
- * <p>Each VCN virtualizes a carrier's network by building tunnels to a carrier's core network over
- * carrier-managed physical links and supports a IP mobility layer to ensure seamless transitions
- * between the underlying networks. Each VCN is configured based on a Subscription Group (see {@link
- * android.telephony.SubscriptionManager}) and aggregates all networks that are brought up based on
- * a profile or suggestion in the specified Subscription Group.
- *
- * <p>The VCN can be configured to expose one or more {@link android.net.Network}(s), each with
- * different capabilities, allowing for APN virtualization.
- *
- * <p>If a tunnel fails to connect, or otherwise encounters a fatal error, the VCN will attempt to
- * reestablish the connection. If the tunnel still has not reconnected after a system-determined
- * timeout, the VCN Safe Mode (see below) will be entered.
- *
- * <p>The VCN Safe Mode ensures that users (and carriers) have a fallback to restore system
- * connectivity to update profiles, diagnose issues, contact support, or perform other remediation
- * tasks. In Safe Mode, the system will allow underlying cellular networks to be used as default.
- * Additionally, during Safe Mode, the VCN will continue to retry the connections, and will
- * automatically exit Safe Mode if all active tunnels connect successfully.
- *
- * <p>Apps targeting Android 15 or newer should check the existence of {@link
- * PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION} before querying the service. If the feature is
- * absent, {@link Context#getSystemService} may return null.
- */
-@SystemService(VcnManager.VCN_MANAGEMENT_SERVICE_STRING)
-@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
-public class VcnManager {
-    @NonNull private static final String TAG = VcnManager.class.getSimpleName();
-
-    // TODO: b/366598445: Expose and use Context.VCN_MANAGEMENT_SERVICE
-    /** @hide */
-    public static final String VCN_MANAGEMENT_SERVICE_STRING = "vcn_management";
-
-    /**
-     * Key for WiFi entry RSSI thresholds
-     *
-     * <p>The VCN will only migrate to a Carrier WiFi network that has a signal strength greater
-     * than, or equal to this threshold.
-     *
-     * @hide
-     */
-    @NonNull
-    public static final String VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY =
-            "vcn_network_selection_wifi_entry_rssi_threshold";
-
-    /**
-     * Key for WiFi entry RSSI thresholds
-     *
-     * <p>If the VCN's selected Carrier WiFi network has a signal strength less than this threshold,
-     * the VCN will attempt to migrate away from the Carrier WiFi network.
-     *
-     * @hide
-     */
-    @NonNull
-    public static final String VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY =
-            "vcn_network_selection_wifi_exit_rssi_threshold";
-
-    /**
-     * Key for the interval to poll IpSecTransformState for packet loss monitoring
-     *
-     * @hide
-     */
-    @NonNull
-    public static final String VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY =
-            "vcn_network_selection_poll_ipsec_state_interval_seconds";
-
-    /**
-     * Key for the threshold of IPSec packet loss rate
-     *
-     * @hide
-     */
-    @NonNull
-    public static final String VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY =
-            "vcn_network_selection_ipsec_packet_loss_percent_threshold";
-
-    /**
-     * Key for detecting unusually large increases in IPsec packet sequence numbers.
-     *
-     * <p>If the sequence number increases by more than this value within a second, it may indicate
-     * an intentional leap on the server's downlink. To avoid false positives, the packet loss
-     * detector will suppress loss reporting.
-     *
-     * <p>By default, there's no maximum limit enforced, prioritizing detection of lossy networks.
-     * To reduce false positives, consider setting an appropriate maximum threshold.
-     *
-     * @hide
-     */
-    @NonNull
-    public static final String VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY =
-            "vcn_network_selection_max_seq_num_increase_per_second";
-
-    /**
-     * Key for the list of timeouts in minute to stop penalizing an underlying network candidate
-     *
-     * @hide
-     */
-    @NonNull
-    public static final String VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY =
-            "vcn_network_selection_penalty_timeout_minutes_list";
-
-    // TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz
-
-    /**
-     * Key for transports that need to be marked as restricted by the VCN
-     *
-     * <p>Defaults to TRANSPORT_WIFI if the config does not exist
-     *
-     * @hide
-     */
-    public static final String VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY =
-            "vcn_restricted_transports";
-
-    /**
-     * Key for number of seconds to wait before entering safe mode
-     *
-     * <p>A VcnGatewayConnection will enter safe mode when it takes over the configured timeout to
-     * enter {@link ConnectedState}.
-     *
-     * <p>Defaults to 30, unless overridden by carrier config
-     *
-     * @hide
-     */
-    @NonNull
-    public static final String VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY =
-            "vcn_safe_mode_timeout_seconds_key";
-
-    /**
-     * Key for maximum number of parallel SAs for tunnel aggregation
-     *
-     * <p>If set to a value > 1, multiple tunnels will be set up, and inbound traffic will be
-     * aggregated over the various tunnels.
-     *
-     * <p>Defaults to 1, unless overridden by carrier config
-     *
-     * @hide
-     */
-    @NonNull
-    public static final String VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY =
-            "vcn_tunnel_aggregation_sa_count_max";
-
-    /** List of Carrier Config options to extract from Carrier Config bundles. @hide */
-    @NonNull
-    public static final String[] VCN_RELATED_CARRIER_CONFIG_KEYS =
-            new String[] {
-                VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
-                VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
-                VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
-                VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
-                VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
-                VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
-                VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
-                VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY,
-                VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY,
-            };
-
-    private static final Map<
-                    VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder>
-            REGISTERED_POLICY_LISTENERS = new ConcurrentHashMap<>();
-
-    @NonNull private final Context mContext;
-    @NonNull private final IVcnManagementService mService;
-
-    /**
-     * Construct an instance of VcnManager within an application context.
-     *
-     * @param ctx the application context for this manager
-     * @param service the VcnManagementService binder backing this manager
-     *
-     * @hide
-     */
-    public VcnManager(@NonNull Context ctx, @NonNull IVcnManagementService service) {
-        mContext = requireNonNull(ctx, "missing context");
-        mService = requireNonNull(service, "missing service");
-    }
-
-    /**
-     * Get all currently registered VcnNetworkPolicyChangeListeners for testing purposes.
-     *
-     * @hide
-     */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    @NonNull
-    public static Map<VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder>
-            getAllPolicyListeners() {
-        return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS);
-    }
-
-    /**
-     * Sets the VCN configuration for a given subscription group.
-     *
-     * <p>An app that has carrier privileges for any of the subscriptions in the given group may set
-     * a VCN configuration. If a configuration already exists for the given subscription group, it
-     * will be overridden. Any active VCN(s) may be forced to restart to use the new configuration.
-     *
-     * <p>This API is ONLY permitted for callers running as the primary user.
-     *
-     * @param subscriptionGroup the subscription group that the configuration should be applied to
-     * @param config the configuration parameters for the VCN
-     * @throws SecurityException if the caller does not have carrier privileges for the provided
-     *     subscriptionGroup, or is not running as the primary user
-     * @throws IOException if the configuration failed to be saved and persisted to disk. This may
-     *     occur due to temporary disk errors, or more permanent conditions such as a full disk.
-     */
-    @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant
-    public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config)
-            throws IOException {
-        requireNonNull(subscriptionGroup, "subscriptionGroup was null");
-        requireNonNull(config, "config was null");
-
-        try {
-            mService.setVcnConfig(subscriptionGroup, config, mContext.getOpPackageName());
-        } catch (ServiceSpecificException e) {
-            throw new IOException(e);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Clears the VCN configuration for a given subscription group.
-     *
-     * <p>An app that has carrier privileges for any of the subscriptions in the given group may
-     * clear a VCN configuration. This API is ONLY permitted for callers running as the primary
-     * user. Any active VCN associated with this configuration will be torn down.
-     *
-     * @param subscriptionGroup the subscription group that the configuration should be applied to
-     * @throws SecurityException if the caller does not have carrier privileges, is not the owner of
-     *     the associated configuration, or is not running as the primary user
-     * @throws IOException if the configuration failed to be cleared from disk. This may occur due
-     *     to temporary disk errors, or more permanent conditions such as a full disk.
-     */
-    @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant
-    public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) throws IOException {
-        requireNonNull(subscriptionGroup, "subscriptionGroup was null");
-
-        try {
-            mService.clearVcnConfig(subscriptionGroup, mContext.getOpPackageName());
-        } catch (ServiceSpecificException e) {
-            throw new IOException(e);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Retrieves the list of Subscription Groups for which a VCN Configuration has been set.
-     *
-     * <p>The returned list will include only subscription groups for which an associated {@link
-     * VcnConfig} exists, and the app is either:
-     *
-     * <ul>
-     *   <li>Carrier privileged for that subscription group, or
-     *   <li>Is the provisioning package of the config
-     * </ul>
-     *
-     * @throws SecurityException if the caller is not running as the primary user
-     */
-    @NonNull
-    public List<ParcelUuid> getConfiguredSubscriptionGroups() {
-        try {
-            return mService.getConfiguredSubscriptionGroups(mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    // TODO(b/180537630): remove all VcnUnderlyingNetworkPolicyListener refs once Telephony is using
-    // the new VcnNetworkPolicyChangeListener API
-    /**
-     * VcnUnderlyingNetworkPolicyListener is the interface through which internal system components
-     * can register to receive updates for VCN-underlying Network policies from the System Server.
-     *
-     * @hide
-     */
-    public interface VcnUnderlyingNetworkPolicyListener extends VcnNetworkPolicyChangeListener {}
-
-    /**
-     * Add a listener for VCN-underlying network policy updates.
-     *
-     * @param executor the Executor that will be used for invoking all calls to the specified
-     *     Listener
-     * @param listener the VcnUnderlyingNetworkPolicyListener to be added
-     * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
-     * @throws IllegalStateException if the specified VcnUnderlyingNetworkPolicyListener is already
-     *     registered
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
-    public void addVcnUnderlyingNetworkPolicyListener(
-            @NonNull Executor executor, @NonNull VcnUnderlyingNetworkPolicyListener listener) {
-        addVcnNetworkPolicyChangeListener(executor, listener);
-    }
-
-    /**
-     * Remove the specified VcnUnderlyingNetworkPolicyListener from VcnManager.
-     *
-     * <p>If the specified listener is not currently registered, this is a no-op.
-     *
-     * @param listener the VcnUnderlyingNetworkPolicyListener that will be removed
-     * @hide
-     */
-    public void removeVcnUnderlyingNetworkPolicyListener(
-            @NonNull VcnUnderlyingNetworkPolicyListener listener) {
-        removeVcnNetworkPolicyChangeListener(listener);
-    }
-
-    /**
-     * Queries the underlying network policy for a network with the given parameters.
-     *
-     * <p>Prior to a new NetworkAgent being registered, or upon notification that Carrier VCN policy
-     * may have changed via {@link VcnUnderlyingNetworkPolicyListener#onPolicyChanged()}, a Network
-     * Provider MUST poll for the updated Network policy based on that Network's capabilities and
-     * properties.
-     *
-     * @param networkCapabilities the NetworkCapabilities to be used in determining the Network
-     *     policy for this Network.
-     * @param linkProperties the LinkProperties to be used in determining the Network policy for
-     *     this Network.
-     * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
-     * @return the VcnUnderlyingNetworkPolicy to be used for this Network.
-     * @hide
-     */
-    @NonNull
-    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
-    public VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(
-            @NonNull NetworkCapabilities networkCapabilities,
-            @NonNull LinkProperties linkProperties) {
-        requireNonNull(networkCapabilities, "networkCapabilities must not be null");
-        requireNonNull(linkProperties, "linkProperties must not be null");
-
-        try {
-            return mService.getUnderlyingNetworkPolicy(networkCapabilities, linkProperties);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * VcnNetworkPolicyChangeListener is the interface through which internal system components
-     * (e.g. Network Factories) can register to receive updates for VCN-underlying Network policies
-     * from the System Server.
-     *
-     * <p>Any Network Factory that brings up Networks capable of being VCN-underlying Networks
-     * should register a VcnNetworkPolicyChangeListener. VcnManager will then use this listener to
-     * notify the registrant when VCN Network policies change. Upon receiving this signal, the
-     * listener must check {@link VcnManager} for the current Network policy result for each of its
-     * Networks via {@link #applyVcnNetworkPolicy(NetworkCapabilities, LinkProperties)}.
-     *
-     * @hide
-     */
-    @SystemApi
-    public interface VcnNetworkPolicyChangeListener {
-        /**
-         * Notifies the implementation that the VCN's underlying Network policy has changed.
-         *
-         * <p>After receiving this callback, implementations should get the current {@link
-         * VcnNetworkPolicyResult} via {@link #applyVcnNetworkPolicy(NetworkCapabilities,
-         * LinkProperties)}.
-         */
-        void onPolicyChanged();
-    }
-
-    /**
-     * Add a listener for VCN-underlying Network policy updates.
-     *
-     * <p>A {@link VcnNetworkPolicyChangeListener} is eligible to begin receiving callbacks once it
-     * is registered. No callbacks are guaranteed upon registration.
-     *
-     * @param executor the Executor that will be used for invoking all calls to the specified
-     *     Listener
-     * @param listener the VcnNetworkPolicyChangeListener to be added
-     * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
-     * @throws IllegalStateException if the specified VcnNetworkPolicyChangeListener is already
-     *     registered
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
-    public void addVcnNetworkPolicyChangeListener(
-            @NonNull Executor executor, @NonNull VcnNetworkPolicyChangeListener listener) {
-        requireNonNull(executor, "executor must not be null");
-        requireNonNull(listener, "listener must not be null");
-
-        VcnUnderlyingNetworkPolicyListenerBinder binder =
-                new VcnUnderlyingNetworkPolicyListenerBinder(executor, listener);
-        if (REGISTERED_POLICY_LISTENERS.putIfAbsent(listener, binder) != null) {
-            throw new IllegalStateException("listener is already registered with VcnManager");
-        }
-
-        try {
-            mService.addVcnUnderlyingNetworkPolicyListener(binder);
-        } catch (RemoteException e) {
-            REGISTERED_POLICY_LISTENERS.remove(listener);
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Remove the specified VcnNetworkPolicyChangeListener from VcnManager.
-     *
-     * <p>If the specified listener is not currently registered, this is a no-op.
-     *
-     * @param listener the VcnNetworkPolicyChangeListener that will be removed
-     * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
-    public void removeVcnNetworkPolicyChangeListener(
-            @NonNull VcnNetworkPolicyChangeListener listener) {
-        requireNonNull(listener, "listener must not be null");
-
-        VcnUnderlyingNetworkPolicyListenerBinder binder =
-                REGISTERED_POLICY_LISTENERS.remove(listener);
-        if (binder == null) {
-            return;
-        }
-
-        try {
-            mService.removeVcnUnderlyingNetworkPolicyListener(binder);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Applies the network policy for a {@link android.net.Network} with the given parameters.
-     *
-     * <p>Prior to a new NetworkAgent being registered, or upon notification that Carrier VCN policy
-     * may have changed via {@link VcnNetworkPolicyChangeListener#onPolicyChanged()}, a Network
-     * Provider MUST poll for the updated Network policy based on that Network's capabilities and
-     * properties.
-     *
-     * @param networkCapabilities the NetworkCapabilities to be used in determining the Network
-     *     policy result for this Network.
-     * @param linkProperties the LinkProperties to be used in determining the Network policy result
-     *     for this Network.
-     * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
-     * @return the {@link VcnNetworkPolicyResult} to be used for this Network.
-     * @hide
-     */
-    @NonNull
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
-    public VcnNetworkPolicyResult applyVcnNetworkPolicy(
-            @NonNull NetworkCapabilities networkCapabilities,
-            @NonNull LinkProperties linkProperties) {
-        requireNonNull(networkCapabilities, "networkCapabilities must not be null");
-        requireNonNull(linkProperties, "linkProperties must not be null");
-
-        final VcnUnderlyingNetworkPolicy policy =
-                getUnderlyingNetworkPolicy(networkCapabilities, linkProperties);
-        return new VcnNetworkPolicyResult(
-                policy.isTeardownRequested(), policy.getMergedNetworkCapabilities());
-    }
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-        VCN_STATUS_CODE_NOT_CONFIGURED,
-        VCN_STATUS_CODE_INACTIVE,
-        VCN_STATUS_CODE_ACTIVE,
-        VCN_STATUS_CODE_SAFE_MODE
-    })
-    public @interface VcnStatusCode {}
-
-    /**
-     * Value indicating that the VCN for the subscription group is not configured, or that the
-     * callback is not privileged for the subscription group.
-     */
-    public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0;
-
-    /**
-     * Value indicating that the VCN for the subscription group is inactive.
-     *
-     * <p>A VCN is inactive if a {@link VcnConfig} is present for the subscription group, but the
-     * provisioning package is not privileged.
-     */
-    public static final int VCN_STATUS_CODE_INACTIVE = 1;
-
-    /**
-     * Value indicating that the VCN for the subscription group is active.
-     *
-     * <p>A VCN is active if a {@link VcnConfig} is present for the subscription, the provisioning
-     * package is privileged, and the VCN is not in Safe Mode. In other words, a VCN is considered
-     * active while it is connecting, fully connected, and disconnecting.
-     */
-    public static final int VCN_STATUS_CODE_ACTIVE = 2;
-
-    /**
-     * Value indicating that the VCN for the subscription group is in Safe Mode.
-     *
-     * <p>A VCN will be put into Safe Mode if any of the gateway connections were unable to
-     * establish a connection within a system-determined timeout (while underlying networks were
-     * available).
-     */
-    public static final int VCN_STATUS_CODE_SAFE_MODE = 3;
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-        VCN_ERROR_CODE_INTERNAL_ERROR,
-        VCN_ERROR_CODE_CONFIG_ERROR,
-        VCN_ERROR_CODE_NETWORK_ERROR
-    })
-    public @interface VcnErrorCode {}
-
-    /**
-     * Value indicating that an internal failure occurred in this Gateway Connection.
-     */
-    public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0;
-
-    /**
-     * Value indicating that an error with this Gateway Connection's configuration occurred.
-     *
-     * <p>For example, this error code will be returned after authentication failures.
-     */
-    public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1;
-
-    /**
-     * Value indicating that a Network error occurred with this Gateway Connection.
-     *
-     * <p>For example, this error code will be returned if an underlying {@link android.net.Network}
-     * for this Gateway Connection is lost, or if an error occurs while resolving the connection
-     * endpoint address.
-     */
-    public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2;
-
-    /**
-     * VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs.
-     *
-     * <p>VcnStatusCallbacks may be registered before {@link VcnConfig}s are provided for a
-     * subscription group.
-     */
-    public abstract static class VcnStatusCallback {
-        private VcnStatusCallbackBinder mCbBinder;
-
-        /**
-         * Invoked when status of the VCN for this callback's subscription group changes.
-         *
-         * @param statusCode the code for the status change encountered by this {@link
-         *     VcnStatusCallback}'s subscription group. This value will be one of VCN_STATUS_CODE_*.
-         */
-        public abstract void onStatusChanged(@VcnStatusCode int statusCode);
-
-        /**
-         * Invoked when a VCN Gateway Connection corresponding to this callback's subscription group
-         * encounters an error.
-         *
-         * @param gatewayConnectionName the String GatewayConnection name for the GatewayConnection
-         *     encountering an error. This will match the name for exactly one {@link
-         *     VcnGatewayConnectionConfig} for the {@link VcnConfig} configured for this callback's
-         *     subscription group
-         * @param errorCode the code to indicate the error that occurred. This value will be one of
-         *     VCN_ERROR_CODE_*.
-         * @param detail Throwable to provide additional information about the error, or {@code
-         *     null} if none
-         */
-        public abstract void onGatewayConnectionError(
-                @NonNull String gatewayConnectionName,
-                @VcnErrorCode int errorCode,
-                @Nullable Throwable detail);
-    }
-
-    /**
-     * Registers the given callback to receive status updates for the specified subscription.
-     *
-     * <p>Callbacks can be registered for a subscription before {@link VcnConfig}s are set for it.
-     *
-     * <p>A {@link VcnStatusCallback} may only be registered for one subscription at a time. {@link
-     * VcnStatusCallback}s may be reused once unregistered.
-     *
-     * <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier
-     * privileges for the specified subscription at the time of invocation.
-     *
-     * <p>A {@link VcnStatusCallback} is eligible to begin receiving callbacks once it is registered
-     * and there is a VCN active for its specified subscription group (this may happen after the
-     * callback is registered).
-     *
-     * <p>{@link VcnStatusCallback#onStatusChanged(int)} will be invoked on registration with the
-     * current status for the specified subscription group's VCN. If the registrant is not
-     * privileged for this subscription group, {@link #VCN_STATUS_CODE_NOT_CONFIGURED} will be
-     * returned.
-     *
-     * @param subscriptionGroup The subscription group to match for callbacks
-     * @param executor The {@link Executor} to be used for invoking callbacks
-     * @param callback The VcnStatusCallback to be registered
-     * @throws IllegalStateException if callback is currently registered with VcnManager
-     */
-    public void registerVcnStatusCallback(
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull Executor executor,
-            @NonNull VcnStatusCallback callback) {
-        requireNonNull(subscriptionGroup, "subscriptionGroup must not be null");
-        requireNonNull(executor, "executor must not be null");
-        requireNonNull(callback, "callback must not be null");
-
-        synchronized (callback) {
-            if (callback.mCbBinder != null) {
-                throw new IllegalStateException("callback is already registered with VcnManager");
-            }
-            callback.mCbBinder = new VcnStatusCallbackBinder(executor, callback);
-
-            try {
-                mService.registerVcnStatusCallback(
-                        subscriptionGroup, callback.mCbBinder, mContext.getOpPackageName());
-            } catch (RemoteException e) {
-                callback.mCbBinder = null;
-                throw e.rethrowFromSystemServer();
-            }
-        }
-    }
-
-    /**
-     * Unregisters the given callback.
-     *
-     * <p>Once unregistered, the callback will stop receiving status updates for the subscription it
-     * was registered with.
-     *
-     * @param callback The callback to be unregistered
-     */
-    public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) {
-        requireNonNull(callback, "callback must not be null");
-
-        synchronized (callback) {
-            if (callback.mCbBinder == null) {
-                // no Binder attached to this callback, so it's not currently registered
-                return;
-            }
-
-            try {
-                mService.unregisterVcnStatusCallback(callback.mCbBinder);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            } finally {
-                callback.mCbBinder = null;
-            }
-        }
-    }
-
-    /**
-     * Binder wrapper for added VcnNetworkPolicyChangeListeners to receive signals from System
-     * Server.
-     *
-     * @hide
-     */
-    private static class VcnUnderlyingNetworkPolicyListenerBinder
-            extends IVcnUnderlyingNetworkPolicyListener.Stub {
-        @NonNull private final Executor mExecutor;
-        @NonNull private final VcnNetworkPolicyChangeListener mListener;
-
-        private VcnUnderlyingNetworkPolicyListenerBinder(
-                Executor executor, VcnNetworkPolicyChangeListener listener) {
-            mExecutor = executor;
-            mListener = listener;
-        }
-
-        @Override
-        public void onPolicyChanged() {
-            BinderUtils.withCleanCallingIdentity(
-                    () -> mExecutor.execute(() -> mListener.onPolicyChanged()));
-        }
-    }
-
-    /**
-     * Binder wrapper for VcnStatusCallbacks to receive signals from VcnManagementService.
-     *
-     * @hide
-     */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class VcnStatusCallbackBinder extends IVcnStatusCallback.Stub {
-        @NonNull private final Executor mExecutor;
-        @NonNull private final VcnStatusCallback mCallback;
-
-        public VcnStatusCallbackBinder(
-                @NonNull Executor executor, @NonNull VcnStatusCallback callback) {
-            mExecutor = executor;
-            mCallback = callback;
-        }
-
-        @Override
-        public void onVcnStatusChanged(@VcnStatusCode int statusCode) {
-            BinderUtils.withCleanCallingIdentity(
-                    () -> mExecutor.execute(() -> mCallback.onStatusChanged(statusCode)));
-        }
-
-        // TODO(b/180521637): use ServiceSpecificException for safer Exception 'parceling'
-        @Override
-        public void onGatewayConnectionError(
-                @NonNull String gatewayConnectionName,
-                @VcnErrorCode int errorCode,
-                @Nullable String exceptionClass,
-                @Nullable String exceptionMessage) {
-            final Throwable cause = createThrowableByClassName(exceptionClass, exceptionMessage);
-
-            BinderUtils.withCleanCallingIdentity(
-                    () ->
-                            mExecutor.execute(
-                                    () ->
-                                            mCallback.onGatewayConnectionError(
-                                                    gatewayConnectionName, errorCode, cause)));
-        }
-
-        private static Throwable createThrowableByClassName(
-                @Nullable String className, @Nullable String message) {
-            if (className == null) {
-                return null;
-            }
-
-            try {
-                Class<?> c = Class.forName(className);
-                return (Throwable) c.getConstructor(String.class).newInstance(message);
-            } catch (ReflectiveOperationException | ClassCastException e) {
-                return new RuntimeException(className + ": " + message);
-            }
-        }
-    }
-}
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
deleted file mode 100644
index c7b2f18..0000000
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.net.vcn;
-
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
-
-import static com.android.internal.annotations.VisibleForTesting.Visibility;
-import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
-import static com.android.server.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.net.NetworkCapabilities;
-import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria;
-import android.os.PersistableBundle;
-import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.vcn.util.PersistableBundleUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * This class represents a configuration for a network template class of underlying Carrier WiFi
- * networks.
- *
- * <p>See {@link VcnUnderlyingNetworkTemplate}
- */
-public final class VcnWifiUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate {
-    private static final String SSIDS_KEY = "mSsids";
-    @Nullable private final Set<String> mSsids;
-
-    private VcnWifiUnderlyingNetworkTemplate(
-            int meteredMatchCriteria,
-            int minEntryUpstreamBandwidthKbps,
-            int minExitUpstreamBandwidthKbps,
-            int minEntryDownstreamBandwidthKbps,
-            int minExitDownstreamBandwidthKbps,
-            Set<String> ssids) {
-        super(
-                NETWORK_PRIORITY_TYPE_WIFI,
-                meteredMatchCriteria,
-                minEntryUpstreamBandwidthKbps,
-                minExitUpstreamBandwidthKbps,
-                minEntryDownstreamBandwidthKbps,
-                minExitDownstreamBandwidthKbps);
-        mSsids = new ArraySet<>(ssids);
-
-        validate();
-    }
-
-    /** @hide */
-    @Override
-    protected void validate() {
-        super.validate();
-        validateSsids(mSsids);
-    }
-
-    private static void validateSsids(Set<String> ssids) {
-        Objects.requireNonNull(ssids, "ssids is null");
-
-        for (String ssid : ssids) {
-            Objects.requireNonNull(ssid, "found null value ssid");
-        }
-    }
-
-    /** @hide */
-    @NonNull
-    @VisibleForTesting(visibility = Visibility.PROTECTED)
-    public static VcnWifiUnderlyingNetworkTemplate fromPersistableBundle(
-            @NonNull PersistableBundle in) {
-        Objects.requireNonNull(in, "PersistableBundle is null");
-
-        final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY);
-
-        final int minEntryUpstreamBandwidthKbps =
-                in.getInt(MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
-        final int minExitUpstreamBandwidthKbps =
-                in.getInt(MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
-        final int minEntryDownstreamBandwidthKbps =
-                in.getInt(MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
-        final int minExitDownstreamBandwidthKbps =
-                in.getInt(MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
-
-        final PersistableBundle ssidsBundle = in.getPersistableBundle(SSIDS_KEY);
-        Objects.requireNonNull(ssidsBundle, "ssidsBundle is null");
-        final Set<String> ssids =
-                new ArraySet<String>(
-                        PersistableBundleUtils.toList(ssidsBundle, STRING_DESERIALIZER));
-        return new VcnWifiUnderlyingNetworkTemplate(
-                meteredMatchCriteria,
-                minEntryUpstreamBandwidthKbps,
-                minExitUpstreamBandwidthKbps,
-                minEntryDownstreamBandwidthKbps,
-                minExitDownstreamBandwidthKbps,
-                ssids);
-    }
-
-    /** @hide */
-    @Override
-    @NonNull
-    @VisibleForTesting(visibility = Visibility.PROTECTED)
-    public PersistableBundle toPersistableBundle() {
-        final PersistableBundle result = super.toPersistableBundle();
-
-        final PersistableBundle ssidsBundle =
-                PersistableBundleUtils.fromList(new ArrayList<>(mSsids), STRING_SERIALIZER);
-        result.putPersistableBundle(SSIDS_KEY, ssidsBundle);
-
-        return result;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(super.hashCode(), mSsids);
-    }
-
-    @Override
-    public boolean equals(@Nullable Object other) {
-        if (!super.equals(other)) {
-            return false;
-        }
-
-        if (!(other instanceof VcnWifiUnderlyingNetworkTemplate)) {
-            return false;
-        }
-
-        final VcnWifiUnderlyingNetworkTemplate rhs = (VcnWifiUnderlyingNetworkTemplate) other;
-        return mSsids.equals(rhs.mSsids);
-    }
-
-    /** @hide */
-    @Override
-    void dumpTransportSpecificFields(IndentingPrintWriter pw) {
-        if (!mSsids.isEmpty()) {
-            pw.println("mSsids: " + mSsids);
-        }
-    }
-
-    /**
-     * Retrieve the matching SSIDs, or an empty set if any SSID is acceptable.
-     *
-     * @see Builder#setSsids(Set)
-     */
-    @NonNull
-    public Set<String> getSsids() {
-        return Collections.unmodifiableSet(mSsids);
-    }
-
-    /** @hide */
-    @Override
-    public Map<Integer, Integer> getCapabilitiesMatchCriteria() {
-        return Collections.singletonMap(NET_CAPABILITY_INTERNET, MATCH_REQUIRED);
-    }
-
-    /** This class is used to incrementally build VcnWifiUnderlyingNetworkTemplate objects. */
-    public static final class Builder {
-        private int mMeteredMatchCriteria = MATCH_ANY;
-        @NonNull private final Set<String> mSsids = new ArraySet<>();
-
-        private int mMinEntryUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
-        private int mMinExitUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
-        private int mMinEntryDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
-        private int mMinExitDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
-
-        /** Construct a Builder object. */
-        public Builder() {}
-
-        /**
-         * Set the matching criteria for metered networks.
-         *
-         * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one
-         * without NET_CAPABILITY_NOT_METERED). A template where setMetered(MATCH_FORBIDDEN) will
-         * only match a network that is not metered (one with NET_CAPABILITY_NOT_METERED).
-         *
-         * @param matchCriteria the matching criteria for metered networks. Defaults to {@link
-         *     #MATCH_ANY}.
-         * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED
-         */
-        // The matching getter is defined in the super class. Please see {@link
-        // VcnUnderlyingNetworkTemplate#getMetered()}
-        @SuppressLint("MissingGetterMatchingBuilder")
-        @NonNull
-        public Builder setMetered(@MatchCriteria int matchCriteria) {
-            validateMatchCriteria(matchCriteria, "setMetered");
-
-            mMeteredMatchCriteria = matchCriteria;
-            return this;
-        }
-
-        /**
-         * Set the SSIDs with which a network can match this priority rule.
-         *
-         * @param ssids the matching SSIDs. Network with one of the matching SSIDs can match this
-         *     priority rule. If the set is empty, any SSID will match. The default is an empty set.
-         */
-        @NonNull
-        public Builder setSsids(@NonNull Set<String> ssids) {
-            validateSsids(ssids);
-
-            mSsids.clear();
-            mSsids.addAll(ssids);
-            return this;
-        }
-
-        /**
-         * Set the minimum upstream bandwidths that this template will match.
-         *
-         * <p>This template will not match a network that does not provide at least the bandwidth
-         * passed as the entry bandwidth, except in the case that the network is selected as the VCN
-         * Gateway Connection's underlying network, where it will continue to match until the
-         * bandwidth drops under the exit bandwidth.
-         *
-         * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
-         * invalid case where a network fulfills the entry criteria, but at the same time fails the
-         * exit criteria.
-         *
-         * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
-         * {@link NetworkCapabilities}. The provided estimates will be used without modification.
-         *
-         * @param minEntryUpstreamBandwidthKbps the minimum accepted upstream bandwidth for networks
-         *     that ARE NOT the already-selected underlying network, or {@code 0} to disable this
-         *     requirement. Disabled by default.
-         * @param minExitUpstreamBandwidthKbps the minimum accepted upstream bandwidth for a network
-         *     that IS the already-selected underlying network, or {@code 0} to disable this
-         *     requirement. Disabled by default.
-         * @return this {@link Builder} instance, for chaining
-         */
-        @NonNull
-        // The getter for the two integers are separated, and in the superclass. Please see {@link
-        // VcnUnderlyingNetworkTemplate#getMinEntryUpstreamBandwidthKbps()} and {@link
-        // VcnUnderlyingNetworkTemplate#getMinExitUpstreamBandwidthKbps()}
-        @SuppressLint("MissingGetterMatchingBuilder")
-        public Builder setMinUpstreamBandwidthKbps(
-                int minEntryUpstreamBandwidthKbps, int minExitUpstreamBandwidthKbps) {
-            validateMinBandwidthKbps(minEntryUpstreamBandwidthKbps, minExitUpstreamBandwidthKbps);
-
-            mMinEntryUpstreamBandwidthKbps = minEntryUpstreamBandwidthKbps;
-            mMinExitUpstreamBandwidthKbps = minExitUpstreamBandwidthKbps;
-
-            return this;
-        }
-
-        /**
-         * Set the minimum upstream bandwidths that this template will match.
-         *
-         * <p>This template will not match a network that does not provide at least the bandwidth
-         * passed as the entry bandwidth, except in the case that the network is selected as the VCN
-         * Gateway Connection's underlying network, where it will continue to match until the
-         * bandwidth drops under the exit bandwidth.
-         *
-         * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
-         * invalid case where a network fulfills the entry criteria, but at the same time fails the
-         * exit criteria.
-         *
-         * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
-         * {@link NetworkCapabilities}. The provided estimates will be used without modification.
-         *
-         * @param minEntryDownstreamBandwidthKbps the minimum accepted downstream bandwidth for
-         *     networks that ARE NOT the already-selected underlying network, or {@code 0} to
-         *     disable this requirement. Disabled by default.
-         * @param minExitDownstreamBandwidthKbps the minimum accepted downstream bandwidth for a
-         *     network that IS the already-selected underlying network, or {@code 0} to disable this
-         *     requirement. Disabled by default.
-         * @return this {@link Builder} instance, for chaining
-         */
-        @NonNull
-        // The getter for the two integers are separated, and in the superclass. Please see {@link
-        // VcnUnderlyingNetworkTemplate#getMinEntryDownstreamBandwidthKbps()} and {@link
-        // VcnUnderlyingNetworkTemplate#getMinExitDownstreamBandwidthKbps()}
-        @SuppressLint("MissingGetterMatchingBuilder")
-        public Builder setMinDownstreamBandwidthKbps(
-                int minEntryDownstreamBandwidthKbps, int minExitDownstreamBandwidthKbps) {
-            validateMinBandwidthKbps(
-                    minEntryDownstreamBandwidthKbps, minExitDownstreamBandwidthKbps);
-
-            mMinEntryDownstreamBandwidthKbps = minEntryDownstreamBandwidthKbps;
-            mMinExitDownstreamBandwidthKbps = minExitDownstreamBandwidthKbps;
-
-            return this;
-        }
-
-        /** Build the VcnWifiUnderlyingNetworkTemplate. */
-        @NonNull
-        public VcnWifiUnderlyingNetworkTemplate build() {
-            return new VcnWifiUnderlyingNetworkTemplate(
-                    mMeteredMatchCriteria,
-                    mMinEntryUpstreamBandwidthKbps,
-                    mMinExitUpstreamBandwidthKbps,
-                    mMinEntryDownstreamBandwidthKbps,
-                    mMinExitDownstreamBandwidthKbps,
-                    mSsids);
-        }
-    }
-}
diff --git a/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java b/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
deleted file mode 100644
index ce5ec75..0000000
--- a/core/java/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.vcn.persistablebundleutils;
-
-import static com.android.internal.annotations.VisibleForTesting.Visibility;
-
-import android.annotation.NonNull;
-import android.net.ipsec.ike.ChildSaProposal;
-import android.os.PersistableBundle;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.vcn.util.PersistableBundleUtils;
-
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Provides utility methods to convert ChildSaProposal to/from PersistableBundle.
- *
- * @hide
- */
-@VisibleForTesting(visibility = Visibility.PRIVATE)
-public final class ChildSaProposalUtils extends SaProposalUtilsBase {
-    /** Serializes a ChildSaProposal to a PersistableBundle. */
-    @NonNull
-    public static PersistableBundle toPersistableBundle(ChildSaProposal proposal) {
-        return SaProposalUtilsBase.toPersistableBundle(proposal);
-    }
-
-    /** Constructs a ChildSaProposal by deserializing a PersistableBundle. */
-    @NonNull
-    public static ChildSaProposal fromPersistableBundle(@NonNull PersistableBundle in) {
-        Objects.requireNonNull(in, "PersistableBundle was null");
-
-        final ChildSaProposal.Builder builder = new ChildSaProposal.Builder();
-
-        final PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY);
-        Objects.requireNonNull(encryptionBundle, "Encryption algo bundle was null");
-        final List<EncryptionAlgoKeyLenPair> encryptList =
-                PersistableBundleUtils.toList(encryptionBundle, EncryptionAlgoKeyLenPair::new);
-        for (EncryptionAlgoKeyLenPair t : encryptList) {
-            builder.addEncryptionAlgorithm(t.encryptionAlgo, t.keyLen);
-        }
-
-        final int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY);
-        Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array was null");
-        for (int algo : integrityAlgoIdArray) {
-            builder.addIntegrityAlgorithm(algo);
-        }
-
-        final int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY);
-        Objects.requireNonNull(dhGroupArray, "DH Group array was null");
-        for (int dh : dhGroupArray) {
-            builder.addDhGroup(dh);
-        }
-
-        return builder.build();
-    }
-}
diff --git a/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java b/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java
deleted file mode 100644
index 853a52d..0000000
--- a/core/java/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.vcn.persistablebundleutils;
-
-
-import static com.android.internal.annotations.VisibleForTesting.Visibility;
-
-import android.annotation.NonNull;
-import android.net.eap.EapSessionConfig;
-import android.net.eap.EapSessionConfig.EapAkaConfig;
-import android.net.eap.EapSessionConfig.EapAkaPrimeConfig;
-import android.net.eap.EapSessionConfig.EapMethodConfig;
-import android.net.eap.EapSessionConfig.EapMsChapV2Config;
-import android.net.eap.EapSessionConfig.EapSimConfig;
-import android.net.eap.EapSessionConfig.EapTtlsConfig;
-import android.net.eap.EapSessionConfig.EapUiccConfig;
-import android.os.PersistableBundle;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.vcn.util.PersistableBundleUtils;
-
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.Objects;
-
-/**
- * Provides utility methods to convert EapSessionConfig to/from PersistableBundle.
- *
- * @hide
- */
-@VisibleForTesting(visibility = Visibility.PRIVATE)
-public final class EapSessionConfigUtils {
-    private static final String EAP_ID_KEY = "EAP_ID_KEY";
-    private static final String EAP_SIM_CONFIG_KEY = "EAP_SIM_CONFIG_KEY";
-    private static final String EAP_TTLS_CONFIG_KEY = "EAP_TTLS_CONFIG_KEY";
-    private static final String EAP_AKA_CONFIG_KEY = "EAP_AKA_CONFIG_KEY";
-    private static final String EAP_MSCHAP_V2_CONFIG_KEY = "EAP_MSCHAP_V2_CONFIG_KEY";
-    private static final String EAP_AKA_PRIME_CONFIG_KEY = "EAP_AKA_PRIME_CONFIG_KEY";
-
-    /** Serializes an EapSessionConfig to a PersistableBundle. */
-    @NonNull
-    public static PersistableBundle toPersistableBundle(@NonNull EapSessionConfig config) {
-        final PersistableBundle result = new PersistableBundle();
-
-        result.putPersistableBundle(
-                EAP_ID_KEY, PersistableBundleUtils.fromByteArray(config.getEapIdentity()));
-
-        if (config.getEapSimConfig() != null) {
-            result.putPersistableBundle(
-                    EAP_SIM_CONFIG_KEY,
-                    EapSimConfigUtils.toPersistableBundle(config.getEapSimConfig()));
-        }
-
-        if (config.getEapTtlsConfig() != null) {
-            result.putPersistableBundle(
-                    EAP_TTLS_CONFIG_KEY,
-                    EapTtlsConfigUtils.toPersistableBundle(config.getEapTtlsConfig()));
-        }
-
-        if (config.getEapAkaConfig() != null) {
-            result.putPersistableBundle(
-                    EAP_AKA_CONFIG_KEY,
-                    EapAkaConfigUtils.toPersistableBundle(config.getEapAkaConfig()));
-        }
-
-        if (config.getEapMsChapV2Config() != null) {
-            result.putPersistableBundle(
-                    EAP_MSCHAP_V2_CONFIG_KEY,
-                    EapMsChapV2ConfigUtils.toPersistableBundle(config.getEapMsChapV2Config()));
-        }
-
-        if (config.getEapAkaPrimeConfig() != null) {
-            result.putPersistableBundle(
-                    EAP_AKA_PRIME_CONFIG_KEY,
-                    EapAkaPrimeConfigUtils.toPersistableBundle(config.getEapAkaPrimeConfig()));
-        }
-
-        return result;
-    }
-
-    /** Constructs an EapSessionConfig by deserializing a PersistableBundle. */
-    @NonNull
-    public static EapSessionConfig fromPersistableBundle(@NonNull PersistableBundle in) {
-        Objects.requireNonNull(in, "PersistableBundle was null");
-
-        final EapSessionConfig.Builder builder = new EapSessionConfig.Builder();
-
-        final PersistableBundle eapIdBundle = in.getPersistableBundle(EAP_ID_KEY);
-        Objects.requireNonNull(eapIdBundle, "EAP ID was null");
-        builder.setEapIdentity(PersistableBundleUtils.toByteArray(eapIdBundle));
-
-        final PersistableBundle simBundle = in.getPersistableBundle(EAP_SIM_CONFIG_KEY);
-        if (simBundle != null) {
-            EapSimConfigUtils.setBuilderByReadingPersistableBundle(simBundle, builder);
-        }
-
-        final PersistableBundle ttlsBundle = in.getPersistableBundle(EAP_TTLS_CONFIG_KEY);
-        if (ttlsBundle != null) {
-            EapTtlsConfigUtils.setBuilderByReadingPersistableBundle(ttlsBundle, builder);
-        }
-
-        final PersistableBundle akaBundle = in.getPersistableBundle(EAP_AKA_CONFIG_KEY);
-        if (akaBundle != null) {
-            EapAkaConfigUtils.setBuilderByReadingPersistableBundle(akaBundle, builder);
-        }
-
-        final PersistableBundle msChapV2Bundle = in.getPersistableBundle(EAP_MSCHAP_V2_CONFIG_KEY);
-        if (msChapV2Bundle != null) {
-            EapMsChapV2ConfigUtils.setBuilderByReadingPersistableBundle(msChapV2Bundle, builder);
-        }
-
-        final PersistableBundle akaPrimeBundle = in.getPersistableBundle(EAP_AKA_PRIME_CONFIG_KEY);
-        if (akaPrimeBundle != null) {
-            EapAkaPrimeConfigUtils.setBuilderByReadingPersistableBundle(akaPrimeBundle, builder);
-        }
-
-        return builder.build();
-    }
-
-    private static class EapMethodConfigUtils {
-        private static final String METHOD_TYPE = "METHOD_TYPE";
-
-        /** Serializes an EapMethodConfig to a PersistableBundle. */
-        @NonNull
-        public static PersistableBundle toPersistableBundle(@NonNull EapMethodConfig config) {
-            final PersistableBundle result = new PersistableBundle();
-            result.putInt(METHOD_TYPE, config.getMethodType());
-            return result;
-        }
-    }
-
-    private static class EapUiccConfigUtils extends EapMethodConfigUtils {
-        static final String SUB_ID_KEY = "SUB_ID_KEY";
-        static final String APP_TYPE_KEY = "APP_TYPE_KEY";
-
-        @NonNull
-        protected static PersistableBundle toPersistableBundle(@NonNull EapUiccConfig config) {
-            final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config);
-            result.putInt(SUB_ID_KEY, config.getSubId());
-            result.putInt(APP_TYPE_KEY, config.getAppType());
-
-            return result;
-        }
-    }
-
-    private static final class EapSimConfigUtils extends EapUiccConfigUtils {
-        @NonNull
-        public static PersistableBundle toPersistableBundle(EapSimConfig config) {
-            return EapUiccConfigUtils.toPersistableBundle(config);
-        }
-
-        public static void setBuilderByReadingPersistableBundle(
-                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
-            Objects.requireNonNull(in, "PersistableBundle was null");
-            builder.setEapSimConfig(in.getInt(SUB_ID_KEY), in.getInt(APP_TYPE_KEY));
-        }
-    }
-
-    private static class EapAkaConfigUtils extends EapUiccConfigUtils {
-        @NonNull
-        public static PersistableBundle toPersistableBundle(@NonNull EapAkaConfig config) {
-            return EapUiccConfigUtils.toPersistableBundle(config);
-        }
-
-        public static void setBuilderByReadingPersistableBundle(
-                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
-            Objects.requireNonNull(in, "PersistableBundle was null");
-            builder.setEapAkaConfig(in.getInt(SUB_ID_KEY), in.getInt(APP_TYPE_KEY));
-        }
-    }
-
-    private static final class EapAkaPrimeConfigUtils extends EapAkaConfigUtils {
-        private static final String NETWORK_NAME_KEY = "NETWORK_NAME_KEY";
-        private static final String ALL_MISMATCHED_NETWORK_KEY = "ALL_MISMATCHED_NETWORK_KEY";
-
-        @NonNull
-        public static PersistableBundle toPersistableBundle(@NonNull EapAkaPrimeConfig config) {
-            final PersistableBundle result = EapUiccConfigUtils.toPersistableBundle(config);
-            result.putString(NETWORK_NAME_KEY, config.getNetworkName());
-            result.putBoolean(ALL_MISMATCHED_NETWORK_KEY, config.allowsMismatchedNetworkNames());
-
-            return result;
-        }
-
-        public static void setBuilderByReadingPersistableBundle(
-                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
-            Objects.requireNonNull(in, "PersistableBundle was null");
-            builder.setEapAkaPrimeConfig(
-                    in.getInt(SUB_ID_KEY),
-                    in.getInt(APP_TYPE_KEY),
-                    in.getString(NETWORK_NAME_KEY),
-                    in.getBoolean(ALL_MISMATCHED_NETWORK_KEY));
-        }
-    }
-
-    private static final class EapMsChapV2ConfigUtils extends EapMethodConfigUtils {
-        private static final String USERNAME_KEY = "USERNAME_KEY";
-        private static final String PASSWORD_KEY = "PASSWORD_KEY";
-
-        @NonNull
-        public static PersistableBundle toPersistableBundle(@NonNull EapMsChapV2Config config) {
-            final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config);
-            result.putString(USERNAME_KEY, config.getUsername());
-            result.putString(PASSWORD_KEY, config.getPassword());
-
-            return result;
-        }
-
-        public static void setBuilderByReadingPersistableBundle(
-                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
-            Objects.requireNonNull(in, "PersistableBundle was null");
-            builder.setEapMsChapV2Config(in.getString(USERNAME_KEY), in.getString(PASSWORD_KEY));
-        }
-    }
-
-    private static final class EapTtlsConfigUtils extends EapMethodConfigUtils {
-        private static final String TRUST_CERT_KEY = "TRUST_CERT_KEY";
-        private static final String EAP_SESSION_CONFIG_KEY = "EAP_SESSION_CONFIG_KEY";
-
-        @NonNull
-        public static PersistableBundle toPersistableBundle(@NonNull EapTtlsConfig config) {
-            final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config);
-            try {
-                if (config.getServerCaCert() != null) {
-                    final PersistableBundle caBundle =
-                            PersistableBundleUtils.fromByteArray(
-                                    config.getServerCaCert().getEncoded());
-                    result.putPersistableBundle(TRUST_CERT_KEY, caBundle);
-                }
-            } catch (CertificateEncodingException e) {
-                throw new IllegalStateException("Fail to encode the certificate");
-            }
-
-            result.putPersistableBundle(
-                    EAP_SESSION_CONFIG_KEY,
-                    EapSessionConfigUtils.toPersistableBundle(config.getInnerEapSessionConfig()));
-
-            return result;
-        }
-
-        public static void setBuilderByReadingPersistableBundle(
-                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
-            Objects.requireNonNull(in, "PersistableBundle was null");
-
-            final PersistableBundle caBundle = in.getPersistableBundle(TRUST_CERT_KEY);
-            X509Certificate caCert = null;
-            if (caBundle != null) {
-                caCert =
-                        CertUtils.certificateFromByteArray(
-                                PersistableBundleUtils.toByteArray(caBundle));
-            }
-
-            final PersistableBundle eapSessionConfigBundle =
-                    in.getPersistableBundle(EAP_SESSION_CONFIG_KEY);
-            Objects.requireNonNull(eapSessionConfigBundle, "Inner EAP Session Config was null");
-            final EapSessionConfig eapSessionConfig =
-                    EapSessionConfigUtils.fromPersistableBundle(eapSessionConfigBundle);
-
-            builder.setEapTtlsConfig(caCert, eapSessionConfig);
-        }
-    }
-}
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java
deleted file mode 100644
index 6acb34e..0000000
--- a/core/java/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.vcn.persistablebundleutils;
-
-import static com.android.internal.annotations.VisibleForTesting.Visibility;
-
-import android.annotation.NonNull;
-import android.net.InetAddresses;
-import android.net.ipsec.ike.IkeDerAsn1DnIdentification;
-import android.net.ipsec.ike.IkeFqdnIdentification;
-import android.net.ipsec.ike.IkeIdentification;
-import android.net.ipsec.ike.IkeIpv4AddrIdentification;
-import android.net.ipsec.ike.IkeIpv6AddrIdentification;
-import android.net.ipsec.ike.IkeKeyIdIdentification;
-import android.net.ipsec.ike.IkeRfc822AddrIdentification;
-import android.os.PersistableBundle;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.vcn.util.PersistableBundleUtils;
-
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.util.Objects;
-
-import javax.security.auth.x500.X500Principal;
-
-/**
- * Abstract utility class to convert IkeIdentification to/from PersistableBundle.
- *
- * @hide
- */
-@VisibleForTesting(visibility = Visibility.PRIVATE)
-public final class IkeIdentificationUtils {
-    private static final String ID_TYPE_KEY = "ID_TYPE_KEY";
-
-    private static final String DER_ASN1_DN_KEY = "DER_ASN1_DN_KEY";
-    private static final String FQDN_KEY = "FQDN_KEY";
-    private static final String KEY_ID_KEY = "KEY_ID_KEY";
-    private static final String IP4_ADDRESS_KEY = "IP4_ADDRESS_KEY";
-    private static final String IP6_ADDRESS_KEY = "IP6_ADDRESS_KEY";
-    private static final String RFC822_ADDRESS_KEY = "RFC822_ADDRESS_KEY";
-
-    private static final int ID_TYPE_DER_ASN1_DN = 1;
-    private static final int ID_TYPE_FQDN = 2;
-    private static final int ID_TYPE_IPV4_ADDR = 3;
-    private static final int ID_TYPE_IPV6_ADDR = 4;
-    private static final int ID_TYPE_KEY_ID = 5;
-    private static final int ID_TYPE_RFC822_ADDR = 6;
-
-    /** Serializes an IkeIdentification to a PersistableBundle. */
-    @NonNull
-    public static PersistableBundle toPersistableBundle(@NonNull IkeIdentification ikeId) {
-        if (ikeId instanceof IkeDerAsn1DnIdentification) {
-            final PersistableBundle result = createPersistableBundle(ID_TYPE_DER_ASN1_DN);
-            IkeDerAsn1DnIdentification id = (IkeDerAsn1DnIdentification) ikeId;
-            result.putPersistableBundle(
-                    DER_ASN1_DN_KEY,
-                    PersistableBundleUtils.fromByteArray(id.derAsn1Dn.getEncoded()));
-            return result;
-        } else if (ikeId instanceof IkeFqdnIdentification) {
-            final PersistableBundle result = createPersistableBundle(ID_TYPE_FQDN);
-            IkeFqdnIdentification id = (IkeFqdnIdentification) ikeId;
-            result.putString(FQDN_KEY, id.fqdn);
-            return result;
-        } else if (ikeId instanceof IkeIpv4AddrIdentification) {
-            final PersistableBundle result = createPersistableBundle(ID_TYPE_IPV4_ADDR);
-            IkeIpv4AddrIdentification id = (IkeIpv4AddrIdentification) ikeId;
-            result.putString(IP4_ADDRESS_KEY, id.ipv4Address.getHostAddress());
-            return result;
-        } else if (ikeId instanceof IkeIpv6AddrIdentification) {
-            final PersistableBundle result = createPersistableBundle(ID_TYPE_IPV6_ADDR);
-            IkeIpv6AddrIdentification id = (IkeIpv6AddrIdentification) ikeId;
-            result.putString(IP6_ADDRESS_KEY, id.ipv6Address.getHostAddress());
-            return result;
-        } else if (ikeId instanceof IkeKeyIdIdentification) {
-            final PersistableBundle result = createPersistableBundle(ID_TYPE_KEY_ID);
-            IkeKeyIdIdentification id = (IkeKeyIdIdentification) ikeId;
-            result.putPersistableBundle(KEY_ID_KEY, PersistableBundleUtils.fromByteArray(id.keyId));
-            return result;
-        } else if (ikeId instanceof IkeRfc822AddrIdentification) {
-            final PersistableBundle result = createPersistableBundle(ID_TYPE_RFC822_ADDR);
-            IkeRfc822AddrIdentification id = (IkeRfc822AddrIdentification) ikeId;
-            result.putString(RFC822_ADDRESS_KEY, id.rfc822Name);
-            return result;
-        } else {
-            throw new IllegalStateException("Unrecognized IkeIdentification subclass");
-        }
-    }
-
-    private static PersistableBundle createPersistableBundle(int idType) {
-        final PersistableBundle result = new PersistableBundle();
-        result.putInt(ID_TYPE_KEY, idType);
-        return result;
-    }
-
-    /** Constructs an IkeIdentification by deserializing a PersistableBundle. */
-    @NonNull
-    public static IkeIdentification fromPersistableBundle(@NonNull PersistableBundle in) {
-        Objects.requireNonNull(in, "PersistableBundle was null");
-        int idType = in.getInt(ID_TYPE_KEY);
-        switch (idType) {
-            case ID_TYPE_DER_ASN1_DN:
-                final PersistableBundle dnBundle = in.getPersistableBundle(DER_ASN1_DN_KEY);
-                Objects.requireNonNull(dnBundle, "ASN1 DN was null");
-                return new IkeDerAsn1DnIdentification(
-                        new X500Principal(PersistableBundleUtils.toByteArray(dnBundle)));
-            case ID_TYPE_FQDN:
-                return new IkeFqdnIdentification(in.getString(FQDN_KEY));
-            case ID_TYPE_IPV4_ADDR:
-                final String v4AddressStr = in.getString(IP4_ADDRESS_KEY);
-                Objects.requireNonNull(v4AddressStr, "IPv4 address was null");
-                return new IkeIpv4AddrIdentification(
-                        (Inet4Address) InetAddresses.parseNumericAddress(v4AddressStr));
-            case ID_TYPE_IPV6_ADDR:
-                final String v6AddressStr = in.getString(IP6_ADDRESS_KEY);
-                Objects.requireNonNull(v6AddressStr, "IPv6 address was null");
-                return new IkeIpv6AddrIdentification(
-                        (Inet6Address) InetAddresses.parseNumericAddress(v6AddressStr));
-            case ID_TYPE_KEY_ID:
-                final PersistableBundle keyIdBundle = in.getPersistableBundle(KEY_ID_KEY);
-                Objects.requireNonNull(in, "Key ID was null");
-                return new IkeKeyIdIdentification(PersistableBundleUtils.toByteArray(keyIdBundle));
-            case ID_TYPE_RFC822_ADDR:
-                return new IkeRfc822AddrIdentification(in.getString(RFC822_ADDRESS_KEY));
-            default:
-                throw new IllegalStateException("Unrecognized IKE ID type: " + idType);
-        }
-    }
-}
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
deleted file mode 100644
index 1459671..0000000
--- a/core/java/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.vcn.persistablebundleutils;
-
-import static com.android.internal.annotations.VisibleForTesting.Visibility;
-
-import android.annotation.NonNull;
-import android.net.ipsec.ike.IkeSaProposal;
-import android.os.PersistableBundle;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.vcn.util.PersistableBundleUtils;
-
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Provides utility methods to convert IkeSaProposal to/from PersistableBundle.
- *
- * @hide
- */
-@VisibleForTesting(visibility = Visibility.PRIVATE)
-public final class IkeSaProposalUtils extends SaProposalUtilsBase {
-    private static final String PRF_KEY = "PRF_KEY";
-
-    /** Serializes an IkeSaProposal to a PersistableBundle. */
-    @NonNull
-    public static PersistableBundle toPersistableBundle(IkeSaProposal proposal) {
-        final PersistableBundle result = SaProposalUtilsBase.toPersistableBundle(proposal);
-
-        final int[] prfArray =
-                proposal.getPseudorandomFunctions().stream().mapToInt(i -> i).toArray();
-        result.putIntArray(PRF_KEY, prfArray);
-
-        return result;
-    }
-
-    /** Constructs an IkeSaProposal by deserializing a PersistableBundle. */
-    @NonNull
-    public static IkeSaProposal fromPersistableBundle(@NonNull PersistableBundle in) {
-        Objects.requireNonNull(in, "PersistableBundle was null");
-
-        final IkeSaProposal.Builder builder = new IkeSaProposal.Builder();
-
-        final PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY);
-        Objects.requireNonNull(encryptionBundle, "Encryption algo bundle was null");
-        final List<EncryptionAlgoKeyLenPair> encryptList =
-                PersistableBundleUtils.toList(encryptionBundle, EncryptionAlgoKeyLenPair::new);
-        for (EncryptionAlgoKeyLenPair t : encryptList) {
-            builder.addEncryptionAlgorithm(t.encryptionAlgo, t.keyLen);
-        }
-
-        final int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY);
-        Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array was null");
-        for (int algo : integrityAlgoIdArray) {
-            builder.addIntegrityAlgorithm(algo);
-        }
-
-        final int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY);
-        Objects.requireNonNull(dhGroupArray, "DH Group array was null");
-        for (int dh : dhGroupArray) {
-            builder.addDhGroup(dh);
-        }
-
-        final int[] prfArray = in.getIntArray(PRF_KEY);
-        Objects.requireNonNull(prfArray, "PRF array was null");
-        for (int prf : prfArray) {
-            builder.addPseudorandomFunction(prf);
-        }
-
-        return builder.build();
-    }
-}
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
deleted file mode 100644
index d1531a1..0000000
--- a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
+++ /dev/null
@@ -1,568 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.vcn.persistablebundleutils;
-
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.AF_INET6;
-
-import static com.android.internal.annotations.VisibleForTesting.Visibility;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.InetAddresses;
-import android.net.eap.EapSessionConfig;
-import android.net.ipsec.ike.IkeSaProposal;
-import android.net.ipsec.ike.IkeSessionParams;
-import android.net.ipsec.ike.IkeSessionParams.ConfigRequestIpv4PcscfServer;
-import android.net.ipsec.ike.IkeSessionParams.ConfigRequestIpv6PcscfServer;
-import android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig;
-import android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignLocalConfig;
-import android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignRemoteConfig;
-import android.net.ipsec.ike.IkeSessionParams.IkeAuthEapConfig;
-import android.net.ipsec.ike.IkeSessionParams.IkeAuthPskConfig;
-import android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest;
-import android.os.PersistableBundle;
-import android.util.ArraySet;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.vcn.util.PersistableBundleUtils;
-
-import java.net.InetAddress;
-import java.security.PrivateKey;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * Abstract utility class to convert IkeSessionParams to/from PersistableBundle.
- *
- * @hide
- */
-@VisibleForTesting(visibility = Visibility.PRIVATE)
-public final class IkeSessionParamsUtils {
-    private static final String TAG = IkeSessionParamsUtils.class.getSimpleName();
-
-    private static final String SERVER_HOST_NAME_KEY = "SERVER_HOST_NAME_KEY";
-    private static final String SA_PROPOSALS_KEY = "SA_PROPOSALS_KEY";
-    private static final String LOCAL_ID_KEY = "LOCAL_ID_KEY";
-    private static final String REMOTE_ID_KEY = "REMOTE_ID_KEY";
-    private static final String LOCAL_AUTH_KEY = "LOCAL_AUTH_KEY";
-    private static final String REMOTE_AUTH_KEY = "REMOTE_AUTH_KEY";
-    private static final String CONFIG_REQUESTS_KEY = "CONFIG_REQUESTS_KEY";
-    private static final String RETRANS_TIMEOUTS_KEY = "RETRANS_TIMEOUTS_KEY";
-    private static final String HARD_LIFETIME_SEC_KEY = "HARD_LIFETIME_SEC_KEY";
-    private static final String SOFT_LIFETIME_SEC_KEY = "SOFT_LIFETIME_SEC_KEY";
-    private static final String DPD_DELAY_SEC_KEY = "DPD_DELAY_SEC_KEY";
-    private static final String NATT_KEEPALIVE_DELAY_SEC_KEY = "NATT_KEEPALIVE_DELAY_SEC_KEY";
-    private static final String IKE_OPTIONS_KEY = "IKE_OPTIONS_KEY";
-    private static final String IP_VERSION_KEY = "IP_VERSION_KEY";
-    private static final String ENCAP_TYPE_KEY = "ENCAP_TYPE_KEY";
-    // TODO: add DSCP_KEY and IS_IKE_FRAGMENT_SUPPORTED_KEY.
-
-    // TODO: b/243181760 Use the IKE API when they are exposed
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static final int IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION = 6;
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static final int IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES = 7;
-
-    private static final Set<Integer> IKE_OPTIONS = new ArraySet<>();
-
-    static {
-        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID);
-        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH);
-        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_MOBIKE);
-        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500);
-        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT);
-        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY);
-        IKE_OPTIONS.add(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION);
-        IKE_OPTIONS.add(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES);
-        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_AUTOMATIC_KEEPALIVE_ON_OFF);
-    }
-
-    /**
-     * Check if an IKE option is supported in the IPsec module installed on the device
-     *
-     * <p>This method ensures caller to safely access options that are added between dessert
-     * releases.
-     */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static boolean isIkeOptionValid(int option) {
-        try {
-            new IkeSessionParams.Builder().addIkeOption(option);
-            return true;
-        } catch (IllegalArgumentException e) {
-            Log.d(TAG, "Option not supported; discarding: " + option);
-            return false;
-        }
-    }
-
-    /** Serializes an IkeSessionParams to a PersistableBundle. */
-    @NonNull
-    public static PersistableBundle toPersistableBundle(@NonNull IkeSessionParams params) {
-        if (params.getNetwork() != null || params.getIke3gppExtension() != null) {
-            throw new IllegalStateException(
-                    "Cannot convert a IkeSessionParams with a caller configured network or with"
-                            + " 3GPP extension enabled");
-        }
-
-        final PersistableBundle result = new PersistableBundle();
-
-        result.putString(SERVER_HOST_NAME_KEY, params.getServerHostname());
-
-        final PersistableBundle saProposalBundle =
-                PersistableBundleUtils.fromList(
-                        params.getSaProposals(), IkeSaProposalUtils::toPersistableBundle);
-        result.putPersistableBundle(SA_PROPOSALS_KEY, saProposalBundle);
-
-        result.putPersistableBundle(
-                LOCAL_ID_KEY,
-                IkeIdentificationUtils.toPersistableBundle(params.getLocalIdentification()));
-        result.putPersistableBundle(
-                REMOTE_ID_KEY,
-                IkeIdentificationUtils.toPersistableBundle(params.getRemoteIdentification()));
-
-        result.putPersistableBundle(
-                LOCAL_AUTH_KEY, AuthConfigUtils.toPersistableBundle(params.getLocalAuthConfig()));
-        result.putPersistableBundle(
-                REMOTE_AUTH_KEY, AuthConfigUtils.toPersistableBundle(params.getRemoteAuthConfig()));
-
-        final List<ConfigRequest> reqList = new ArrayList<>();
-        for (IkeConfigRequest req : params.getConfigurationRequests()) {
-            reqList.add(new ConfigRequest(req));
-        }
-        final PersistableBundle configReqListBundle =
-                PersistableBundleUtils.fromList(reqList, ConfigRequest::toPersistableBundle);
-        result.putPersistableBundle(CONFIG_REQUESTS_KEY, configReqListBundle);
-
-        result.putIntArray(RETRANS_TIMEOUTS_KEY, params.getRetransmissionTimeoutsMillis());
-        result.putInt(HARD_LIFETIME_SEC_KEY, params.getHardLifetimeSeconds());
-        result.putInt(SOFT_LIFETIME_SEC_KEY, params.getSoftLifetimeSeconds());
-        result.putInt(DPD_DELAY_SEC_KEY, params.getDpdDelaySeconds());
-        result.putInt(NATT_KEEPALIVE_DELAY_SEC_KEY, params.getNattKeepAliveDelaySeconds());
-        result.putInt(IP_VERSION_KEY, params.getIpVersion());
-        result.putInt(ENCAP_TYPE_KEY, params.getEncapType());
-
-        final List<Integer> enabledIkeOptions = new ArrayList<>();
-
-        try {
-            // TODO: b/328844044: Ideally this code should gate the behavior by checking the
-            // com.android.ipsec.flags.enabled_ike_options_api flag but that flag is not accessible
-            // right now. We should either update the code when the flag is accessible or remove the
-            // legacy behavior after VIC SDK finalization
-            enabledIkeOptions.addAll(params.getIkeOptions());
-        } catch (Exception e) {
-            // getIkeOptions throws. It means the API is not available
-            enabledIkeOptions.clear();
-            for (int option : IKE_OPTIONS) {
-                if (isIkeOptionValid(option) && params.hasIkeOption(option)) {
-                    enabledIkeOptions.add(option);
-                }
-            }
-        }
-
-        final int[] optionArray = enabledIkeOptions.stream().mapToInt(i -> i).toArray();
-        result.putIntArray(IKE_OPTIONS_KEY, optionArray);
-
-        return result;
-    }
-
-    /** Constructs an IkeSessionParams by deserializing a PersistableBundle. */
-    @NonNull
-    public static IkeSessionParams fromPersistableBundle(@NonNull PersistableBundle in) {
-        Objects.requireNonNull(in, "PersistableBundle is null");
-
-        final IkeSessionParams.Builder builder = new IkeSessionParams.Builder();
-
-        builder.setServerHostname(in.getString(SERVER_HOST_NAME_KEY));
-
-        PersistableBundle proposalBundle = in.getPersistableBundle(SA_PROPOSALS_KEY);
-        Objects.requireNonNull(in, "SA Proposals was null");
-        List<IkeSaProposal> saProposals =
-                PersistableBundleUtils.toList(
-                        proposalBundle, IkeSaProposalUtils::fromPersistableBundle);
-        for (IkeSaProposal proposal : saProposals) {
-            builder.addSaProposal(proposal);
-        }
-
-        builder.setLocalIdentification(
-                IkeIdentificationUtils.fromPersistableBundle(
-                        in.getPersistableBundle(LOCAL_ID_KEY)));
-        builder.setRemoteIdentification(
-                IkeIdentificationUtils.fromPersistableBundle(
-                        in.getPersistableBundle(REMOTE_ID_KEY)));
-
-        AuthConfigUtils.setBuilderByReadingPersistableBundle(
-                in.getPersistableBundle(LOCAL_AUTH_KEY),
-                in.getPersistableBundle(REMOTE_AUTH_KEY),
-                builder);
-
-        builder.setRetransmissionTimeoutsMillis(in.getIntArray(RETRANS_TIMEOUTS_KEY));
-        builder.setLifetimeSeconds(
-                in.getInt(HARD_LIFETIME_SEC_KEY), in.getInt(SOFT_LIFETIME_SEC_KEY));
-        builder.setDpdDelaySeconds(in.getInt(DPD_DELAY_SEC_KEY));
-        builder.setNattKeepAliveDelaySeconds(in.getInt(NATT_KEEPALIVE_DELAY_SEC_KEY));
-        builder.setIpVersion(in.getInt(IP_VERSION_KEY));
-        builder.setEncapType(in.getInt(ENCAP_TYPE_KEY));
-
-        final PersistableBundle configReqListBundle = in.getPersistableBundle(CONFIG_REQUESTS_KEY);
-        Objects.requireNonNull(configReqListBundle, "Config request list was null");
-        final List<ConfigRequest> reqList =
-                PersistableBundleUtils.toList(configReqListBundle, ConfigRequest::new);
-        for (ConfigRequest req : reqList) {
-            switch (req.type) {
-                case ConfigRequest.IPV4_P_CSCF_ADDRESS:
-                    if (req.address == null) {
-                        builder.addPcscfServerRequest(AF_INET);
-                    } else {
-                        builder.addPcscfServerRequest(req.address);
-                    }
-                    break;
-                case ConfigRequest.IPV6_P_CSCF_ADDRESS:
-                    if (req.address == null) {
-                        builder.addPcscfServerRequest(AF_INET6);
-                    } else {
-                        builder.addPcscfServerRequest(req.address);
-                    }
-                    break;
-                default:
-                    throw new IllegalArgumentException(
-                            "Unrecognized config request type: " + req.type);
-            }
-        }
-
-        // Clear IKE Options that are by default enabled
-        for (int option : IKE_OPTIONS) {
-            if (isIkeOptionValid(option)) {
-                builder.removeIkeOption(option);
-            }
-        }
-
-        final int[] optionArray = in.getIntArray(IKE_OPTIONS_KEY);
-        for (int option : optionArray) {
-            if (isIkeOptionValid(option)) {
-                builder.addIkeOption(option);
-            }
-        }
-
-        return builder.build();
-    }
-
-    private static final class AuthConfigUtils {
-        private static final int IKE_AUTH_METHOD_PSK = 1;
-        private static final int IKE_AUTH_METHOD_PUB_KEY_SIGNATURE = 2;
-        private static final int IKE_AUTH_METHOD_EAP = 3;
-
-        private static final String AUTH_METHOD_KEY = "AUTH_METHOD_KEY";
-
-        @NonNull
-        public static PersistableBundle toPersistableBundle(@NonNull IkeAuthConfig authConfig) {
-            if (authConfig instanceof IkeAuthPskConfig) {
-                IkeAuthPskConfig config = (IkeAuthPskConfig) authConfig;
-                return IkeAuthPskConfigUtils.toPersistableBundle(
-                        config, createPersistableBundle(IKE_AUTH_METHOD_PSK));
-            } else if (authConfig instanceof IkeAuthDigitalSignLocalConfig) {
-                IkeAuthDigitalSignLocalConfig config = (IkeAuthDigitalSignLocalConfig) authConfig;
-                return IkeAuthDigitalSignConfigUtils.toPersistableBundle(
-                        config, createPersistableBundle(IKE_AUTH_METHOD_PUB_KEY_SIGNATURE));
-            } else if (authConfig instanceof IkeAuthDigitalSignRemoteConfig) {
-                IkeAuthDigitalSignRemoteConfig config = (IkeAuthDigitalSignRemoteConfig) authConfig;
-                return IkeAuthDigitalSignConfigUtils.toPersistableBundle(
-                        config, createPersistableBundle(IKE_AUTH_METHOD_PUB_KEY_SIGNATURE));
-            } else if (authConfig instanceof IkeAuthEapConfig) {
-                IkeAuthEapConfig config = (IkeAuthEapConfig) authConfig;
-                return IkeAuthEapConfigUtils.toPersistableBundle(
-                        config, createPersistableBundle(IKE_AUTH_METHOD_EAP));
-            } else {
-                throw new IllegalStateException("Invalid IkeAuthConfig subclass");
-            }
-        }
-
-        private static PersistableBundle createPersistableBundle(int type) {
-            final PersistableBundle result = new PersistableBundle();
-            result.putInt(AUTH_METHOD_KEY, type);
-            return result;
-        }
-
-        public static void setBuilderByReadingPersistableBundle(
-                @NonNull PersistableBundle localAuthBundle,
-                @NonNull PersistableBundle remoteAuthBundle,
-                @NonNull IkeSessionParams.Builder builder) {
-            Objects.requireNonNull(localAuthBundle, "localAuthBundle was null");
-            Objects.requireNonNull(remoteAuthBundle, "remoteAuthBundle was null");
-
-            final int localMethodType = localAuthBundle.getInt(AUTH_METHOD_KEY);
-            final int remoteMethodType = remoteAuthBundle.getInt(AUTH_METHOD_KEY);
-            switch (localMethodType) {
-                case IKE_AUTH_METHOD_PSK:
-                    if (remoteMethodType != IKE_AUTH_METHOD_PSK) {
-                        throw new IllegalArgumentException(
-                                "Expect remote auth method to be PSK based, but was "
-                                        + remoteMethodType);
-                    }
-                    IkeAuthPskConfigUtils.setBuilderByReadingPersistableBundle(
-                            localAuthBundle, remoteAuthBundle, builder);
-                    return;
-                case IKE_AUTH_METHOD_PUB_KEY_SIGNATURE:
-                    if (remoteMethodType != IKE_AUTH_METHOD_PUB_KEY_SIGNATURE) {
-                        throw new IllegalArgumentException(
-                                "Expect remote auth method to be digital signature based, but was "
-                                        + remoteMethodType);
-                    }
-                    IkeAuthDigitalSignConfigUtils.setBuilderByReadingPersistableBundle(
-                            localAuthBundle, remoteAuthBundle, builder);
-                    return;
-                case IKE_AUTH_METHOD_EAP:
-                    if (remoteMethodType != IKE_AUTH_METHOD_PUB_KEY_SIGNATURE) {
-                        throw new IllegalArgumentException(
-                                "When using EAP for local authentication, expect remote auth"
-                                        + " method to be digital signature based, but was "
-                                        + remoteMethodType);
-                    }
-                    IkeAuthEapConfigUtils.setBuilderByReadingPersistableBundle(
-                            localAuthBundle, remoteAuthBundle, builder);
-                    return;
-                default:
-                    throw new IllegalArgumentException(
-                            "Invalid EAP method type " + localMethodType);
-            }
-        }
-    }
-
-    private static final class IkeAuthPskConfigUtils {
-        private static final String PSK_KEY = "PSK_KEY";
-
-        @NonNull
-        public static PersistableBundle toPersistableBundle(
-                @NonNull IkeAuthPskConfig config, @NonNull PersistableBundle result) {
-            result.putPersistableBundle(
-                    PSK_KEY, PersistableBundleUtils.fromByteArray(config.getPsk()));
-            return result;
-        }
-
-        public static void setBuilderByReadingPersistableBundle(
-                @NonNull PersistableBundle localAuthBundle,
-                @NonNull PersistableBundle remoteAuthBundle,
-                @NonNull IkeSessionParams.Builder builder) {
-            Objects.requireNonNull(localAuthBundle, "localAuthBundle was null");
-            Objects.requireNonNull(remoteAuthBundle, "remoteAuthBundle was null");
-
-            final PersistableBundle localPskBundle = localAuthBundle.getPersistableBundle(PSK_KEY);
-            final PersistableBundle remotePskBundle =
-                    remoteAuthBundle.getPersistableBundle(PSK_KEY);
-            Objects.requireNonNull(localAuthBundle, "Local PSK was null");
-            Objects.requireNonNull(remoteAuthBundle, "Remote PSK was null");
-
-            final byte[] localPsk = PersistableBundleUtils.toByteArray(localPskBundle);
-            final byte[] remotePsk = PersistableBundleUtils.toByteArray(remotePskBundle);
-            if (!Arrays.equals(localPsk, remotePsk)) {
-                throw new IllegalArgumentException("Local PSK and remote PSK are different");
-            }
-            builder.setAuthPsk(localPsk);
-        }
-    }
-
-    private static class IkeAuthDigitalSignConfigUtils {
-        private static final String END_CERT_KEY = "END_CERT_KEY";
-        private static final String INTERMEDIATE_CERTS_KEY = "INTERMEDIATE_CERTS_KEY";
-        private static final String PRIVATE_KEY_KEY = "PRIVATE_KEY_KEY";
-        private static final String TRUST_CERT_KEY = "TRUST_CERT_KEY";
-
-        @NonNull
-        public static PersistableBundle toPersistableBundle(
-                @NonNull IkeAuthDigitalSignLocalConfig config, @NonNull PersistableBundle result) {
-            try {
-                result.putPersistableBundle(
-                        END_CERT_KEY,
-                        PersistableBundleUtils.fromByteArray(
-                                config.getClientEndCertificate().getEncoded()));
-
-                final List<X509Certificate> certList = config.getIntermediateCertificates();
-                final List<byte[]> encodedCertList = new ArrayList<>(certList.size());
-                for (X509Certificate cert : certList) {
-                    encodedCertList.add(cert.getEncoded());
-                }
-
-                final PersistableBundle certsBundle =
-                        PersistableBundleUtils.fromList(
-                                encodedCertList, PersistableBundleUtils::fromByteArray);
-                result.putPersistableBundle(INTERMEDIATE_CERTS_KEY, certsBundle);
-            } catch (CertificateEncodingException e) {
-                throw new IllegalArgumentException("Fail to encode certificate");
-            }
-
-            // TODO: b/170670506 Consider putting PrivateKey in Android KeyStore
-            result.putPersistableBundle(
-                    PRIVATE_KEY_KEY,
-                    PersistableBundleUtils.fromByteArray(config.getPrivateKey().getEncoded()));
-            return result;
-        }
-
-        @NonNull
-        public static PersistableBundle toPersistableBundle(
-                @NonNull IkeAuthDigitalSignRemoteConfig config, @NonNull PersistableBundle result) {
-            try {
-                X509Certificate caCert = config.getRemoteCaCert();
-                if (caCert != null) {
-                    result.putPersistableBundle(
-                            TRUST_CERT_KEY,
-                            PersistableBundleUtils.fromByteArray(caCert.getEncoded()));
-                }
-            } catch (CertificateEncodingException e) {
-                throw new IllegalArgumentException("Fail to encode the certificate");
-            }
-
-            return result;
-        }
-
-        public static void setBuilderByReadingPersistableBundle(
-                @NonNull PersistableBundle localAuthBundle,
-                @NonNull PersistableBundle remoteAuthBundle,
-                @NonNull IkeSessionParams.Builder builder) {
-            Objects.requireNonNull(localAuthBundle, "localAuthBundle was null");
-            Objects.requireNonNull(remoteAuthBundle, "remoteAuthBundle was null");
-
-            // Deserialize localAuth
-            final PersistableBundle endCertBundle =
-                    localAuthBundle.getPersistableBundle(END_CERT_KEY);
-            Objects.requireNonNull(endCertBundle, "End cert was null");
-            final byte[] encodedCert = PersistableBundleUtils.toByteArray(endCertBundle);
-            final X509Certificate endCert = CertUtils.certificateFromByteArray(encodedCert);
-
-            final PersistableBundle certsBundle =
-                    localAuthBundle.getPersistableBundle(INTERMEDIATE_CERTS_KEY);
-            Objects.requireNonNull(certsBundle, "Intermediate certs was null");
-            final List<byte[]> encodedCertList =
-                    PersistableBundleUtils.toList(certsBundle, PersistableBundleUtils::toByteArray);
-            final List<X509Certificate> certList = new ArrayList<>(encodedCertList.size());
-            for (byte[] encoded : encodedCertList) {
-                certList.add(CertUtils.certificateFromByteArray(encoded));
-            }
-
-            final PersistableBundle privateKeyBundle =
-                    localAuthBundle.getPersistableBundle(PRIVATE_KEY_KEY);
-            Objects.requireNonNull(privateKeyBundle, "PrivateKey bundle was null");
-            final PrivateKey privateKey =
-                    CertUtils.privateKeyFromByteArray(
-                            PersistableBundleUtils.toByteArray(privateKeyBundle));
-
-            // Deserialize remoteAuth
-            final PersistableBundle trustCertBundle =
-                    remoteAuthBundle.getPersistableBundle(TRUST_CERT_KEY);
-
-            X509Certificate caCert = null;
-            if (trustCertBundle != null) {
-                final byte[] encodedCaCert = PersistableBundleUtils.toByteArray(trustCertBundle);
-                caCert = CertUtils.certificateFromByteArray(encodedCaCert);
-            }
-
-            builder.setAuthDigitalSignature(caCert, endCert, certList, privateKey);
-        }
-    }
-
-    private static final class IkeAuthEapConfigUtils {
-        private static final String EAP_CONFIG_KEY = "EAP_CONFIG_KEY";
-
-        @NonNull
-        public static PersistableBundle toPersistableBundle(
-                @NonNull IkeAuthEapConfig config, @NonNull PersistableBundle result) {
-            result.putPersistableBundle(
-                    EAP_CONFIG_KEY,
-                    EapSessionConfigUtils.toPersistableBundle(config.getEapConfig()));
-            return result;
-        }
-
-        public static void setBuilderByReadingPersistableBundle(
-                @NonNull PersistableBundle localAuthBundle,
-                @NonNull PersistableBundle remoteAuthBundle,
-                @NonNull IkeSessionParams.Builder builder) {
-            // Deserialize localAuth
-            final PersistableBundle eapBundle =
-                    localAuthBundle.getPersistableBundle(EAP_CONFIG_KEY);
-            Objects.requireNonNull(eapBundle, "EAP Config was null");
-            final EapSessionConfig eapConfig =
-                    EapSessionConfigUtils.fromPersistableBundle(eapBundle);
-
-            // Deserialize remoteAuth
-            final PersistableBundle trustCertBundle =
-                    remoteAuthBundle.getPersistableBundle(
-                            IkeAuthDigitalSignConfigUtils.TRUST_CERT_KEY);
-
-            X509Certificate serverCaCert = null;
-            if (trustCertBundle != null) {
-                final byte[] encodedCaCert = PersistableBundleUtils.toByteArray(trustCertBundle);
-                serverCaCert = CertUtils.certificateFromByteArray(encodedCaCert);
-            }
-            builder.setAuthEap(serverCaCert, eapConfig);
-        }
-    }
-
-    private static final class ConfigRequest {
-        private static final int IPV4_P_CSCF_ADDRESS = 1;
-        private static final int IPV6_P_CSCF_ADDRESS = 2;
-
-        private static final String TYPE_KEY = "type";
-        private static final String ADDRESS_KEY = "address";
-
-        public final int type;
-
-        // Null when it is an empty request
-        @Nullable public final InetAddress address;
-
-        ConfigRequest(IkeConfigRequest config) {
-            if (config instanceof ConfigRequestIpv4PcscfServer) {
-                type = IPV4_P_CSCF_ADDRESS;
-                address = ((ConfigRequestIpv4PcscfServer) config).getAddress();
-            } else if (config instanceof ConfigRequestIpv6PcscfServer) {
-                type = IPV6_P_CSCF_ADDRESS;
-                address = ((ConfigRequestIpv6PcscfServer) config).getAddress();
-            } else {
-                throw new IllegalStateException("Unknown TunnelModeChildConfigRequest");
-            }
-        }
-
-        ConfigRequest(PersistableBundle in) {
-            Objects.requireNonNull(in, "PersistableBundle was null");
-
-            type = in.getInt(TYPE_KEY);
-
-            String addressStr = in.getString(ADDRESS_KEY);
-            if (addressStr == null) {
-                address = null;
-            } else {
-                address = InetAddresses.parseNumericAddress(addressStr);
-            }
-        }
-
-        @NonNull
-        public PersistableBundle toPersistableBundle() {
-            final PersistableBundle result = new PersistableBundle();
-
-            result.putInt(TYPE_KEY, type);
-            if (address != null) {
-                result.putString(ADDRESS_KEY, address.getHostAddress());
-            }
-
-            return result;
-        }
-    }
-}
diff --git a/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java b/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
deleted file mode 100644
index 0c9ee84..0000000
--- a/core/java/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.vcn.persistablebundleutils;
-
-import android.annotation.NonNull;
-import android.net.ipsec.ike.SaProposal;
-import android.os.PersistableBundle;
-import android.util.Pair;
-
-import com.android.server.vcn.util.PersistableBundleUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Abstract utility class to convert SaProposal to/from PersistableBundle.
- *
- * @hide
- */
-abstract class SaProposalUtilsBase {
-    static final String ENCRYPT_ALGO_KEY = "ENCRYPT_ALGO_KEY";
-    static final String INTEGRITY_ALGO_KEY = "INTEGRITY_ALGO_KEY";
-    static final String DH_GROUP_KEY = "DH_GROUP_KEY";
-
-    static class EncryptionAlgoKeyLenPair {
-        private static final String ALGO_KEY = "ALGO_KEY";
-        private static final String KEY_LEN_KEY = "KEY_LEN_KEY";
-
-        public final int encryptionAlgo;
-        public final int keyLen;
-
-        EncryptionAlgoKeyLenPair(int encryptionAlgo, int keyLen) {
-            this.encryptionAlgo = encryptionAlgo;
-            this.keyLen = keyLen;
-        }
-
-        EncryptionAlgoKeyLenPair(PersistableBundle in) {
-            Objects.requireNonNull(in, "PersistableBundle was null");
-
-            this.encryptionAlgo = in.getInt(ALGO_KEY);
-            this.keyLen = in.getInt(KEY_LEN_KEY);
-        }
-
-        public PersistableBundle toPersistableBundle() {
-            final PersistableBundle result = new PersistableBundle();
-
-            result.putInt(ALGO_KEY, encryptionAlgo);
-            result.putInt(KEY_LEN_KEY, keyLen);
-
-            return result;
-        }
-    }
-
-    /**
-     * Serializes common info of a SaProposal to a PersistableBundle.
-     *
-     * @hide
-     */
-    @NonNull
-    static PersistableBundle toPersistableBundle(SaProposal proposal) {
-        final PersistableBundle result = new PersistableBundle();
-
-        final List<EncryptionAlgoKeyLenPair> encryptAlgoKeyLenPairs = new ArrayList<>();
-        for (Pair<Integer, Integer> pair : proposal.getEncryptionAlgorithms()) {
-            encryptAlgoKeyLenPairs.add(new EncryptionAlgoKeyLenPair(pair.first, pair.second));
-        }
-        final PersistableBundle encryptionBundle =
-                PersistableBundleUtils.fromList(
-                        encryptAlgoKeyLenPairs, EncryptionAlgoKeyLenPair::toPersistableBundle);
-        result.putPersistableBundle(ENCRYPT_ALGO_KEY, encryptionBundle);
-
-        final int[] integrityAlgoIdArray =
-                proposal.getIntegrityAlgorithms().stream().mapToInt(i -> i).toArray();
-        result.putIntArray(INTEGRITY_ALGO_KEY, integrityAlgoIdArray);
-
-        final int[] dhGroupArray = proposal.getDhGroups().stream().mapToInt(i -> i).toArray();
-        result.putIntArray(DH_GROUP_KEY, dhGroupArray);
-
-        return result;
-    }
-}
diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java
deleted file mode 100644
index e62acac..0000000
--- a/core/java/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.vcn.persistablebundleutils;
-
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.AF_INET6;
-
-import static com.android.internal.annotations.VisibleForTesting.Visibility;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.InetAddresses;
-import android.net.ipsec.ike.ChildSaProposal;
-import android.net.ipsec.ike.IkeTrafficSelector;
-import android.net.ipsec.ike.TunnelModeChildSessionParams;
-import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Address;
-import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DhcpServer;
-import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DnsServer;
-import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Netmask;
-import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6Address;
-import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer;
-import android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest;
-import android.os.PersistableBundle;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.vcn.util.PersistableBundleUtils;
-
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Provides utility methods to convert TunnelModeChildSessionParams to/from PersistableBundle.
- *
- * @hide
- */
-@VisibleForTesting(visibility = Visibility.PRIVATE)
-public final class TunnelModeChildSessionParamsUtils {
-    private static final String TAG = TunnelModeChildSessionParamsUtils.class.getSimpleName();
-
-    private static final String INBOUND_TS_KEY = "INBOUND_TS_KEY";
-    private static final String OUTBOUND_TS_KEY = "OUTBOUND_TS_KEY";
-    private static final String SA_PROPOSALS_KEY = "SA_PROPOSALS_KEY";
-    private static final String HARD_LIFETIME_SEC_KEY = "HARD_LIFETIME_SEC_KEY";
-    private static final String SOFT_LIFETIME_SEC_KEY = "SOFT_LIFETIME_SEC_KEY";
-    private static final String CONFIG_REQUESTS_KEY = "CONFIG_REQUESTS_KEY";
-
-    private static class ConfigRequest {
-        private static final int TYPE_IPV4_ADDRESS = 1;
-        private static final int TYPE_IPV6_ADDRESS = 2;
-        private static final int TYPE_IPV4_DNS = 3;
-        private static final int TYPE_IPV6_DNS = 4;
-        private static final int TYPE_IPV4_DHCP = 5;
-        private static final int TYPE_IPV4_NETMASK = 6;
-
-        private static final String TYPE_KEY = "type";
-        private static final String VALUE_KEY = "address";
-        private static final String IP6_PREFIX_LEN = "ip6PrefixLen";
-
-        private static final int PREFIX_LEN_UNUSED = -1;
-
-        public final int type;
-        public final int ip6PrefixLen;
-
-        // Null when it is an empty request
-        @Nullable public final InetAddress address;
-
-        ConfigRequest(TunnelModeChildConfigRequest config) {
-            int prefixLen = PREFIX_LEN_UNUSED;
-
-            if (config instanceof ConfigRequestIpv4Address) {
-                type = TYPE_IPV4_ADDRESS;
-                address = ((ConfigRequestIpv4Address) config).getAddress();
-            } else if (config instanceof ConfigRequestIpv6Address) {
-                type = TYPE_IPV6_ADDRESS;
-                address = ((ConfigRequestIpv6Address) config).getAddress();
-                if (address != null) {
-                    prefixLen = ((ConfigRequestIpv6Address) config).getPrefixLength();
-                }
-            } else if (config instanceof ConfigRequestIpv4DnsServer) {
-                type = TYPE_IPV4_DNS;
-                address = null;
-            } else if (config instanceof ConfigRequestIpv6DnsServer) {
-                type = TYPE_IPV6_DNS;
-                address = null;
-            } else if (config instanceof ConfigRequestIpv4DhcpServer) {
-                type = TYPE_IPV4_DHCP;
-                address = null;
-            } else if (config instanceof ConfigRequestIpv4Netmask) {
-                type = TYPE_IPV4_NETMASK;
-                address = null;
-            } else {
-                throw new IllegalStateException("Unknown TunnelModeChildConfigRequest");
-            }
-
-            ip6PrefixLen = prefixLen;
-        }
-
-        ConfigRequest(PersistableBundle in) {
-            Objects.requireNonNull(in, "PersistableBundle was null");
-
-            type = in.getInt(TYPE_KEY);
-            ip6PrefixLen = in.getInt(IP6_PREFIX_LEN);
-
-            String addressStr = in.getString(VALUE_KEY);
-            if (addressStr == null) {
-                address = null;
-            } else {
-                address = InetAddresses.parseNumericAddress(addressStr);
-            }
-        }
-
-        @NonNull
-        public PersistableBundle toPersistableBundle() {
-            final PersistableBundle result = new PersistableBundle();
-
-            result.putInt(TYPE_KEY, type);
-            result.putInt(IP6_PREFIX_LEN, ip6PrefixLen);
-
-            if (address != null) {
-                result.putString(VALUE_KEY, address.getHostAddress());
-            }
-
-            return result;
-        }
-    }
-
-    /** Serializes a TunnelModeChildSessionParams to a PersistableBundle. */
-    @NonNull
-    public static PersistableBundle toPersistableBundle(
-            @NonNull TunnelModeChildSessionParams params) {
-        final PersistableBundle result = new PersistableBundle();
-
-        final PersistableBundle saProposalBundle =
-                PersistableBundleUtils.fromList(
-                        params.getSaProposals(), ChildSaProposalUtils::toPersistableBundle);
-        result.putPersistableBundle(SA_PROPOSALS_KEY, saProposalBundle);
-
-        final PersistableBundle inTsBundle =
-                PersistableBundleUtils.fromList(
-                        params.getInboundTrafficSelectors(),
-                        IkeTrafficSelectorUtils::toPersistableBundle);
-        result.putPersistableBundle(INBOUND_TS_KEY, inTsBundle);
-
-        final PersistableBundle outTsBundle =
-                PersistableBundleUtils.fromList(
-                        params.getOutboundTrafficSelectors(),
-                        IkeTrafficSelectorUtils::toPersistableBundle);
-        result.putPersistableBundle(OUTBOUND_TS_KEY, outTsBundle);
-
-        result.putInt(HARD_LIFETIME_SEC_KEY, params.getHardLifetimeSeconds());
-        result.putInt(SOFT_LIFETIME_SEC_KEY, params.getSoftLifetimeSeconds());
-
-        final List<ConfigRequest> reqList = new ArrayList<>();
-        for (TunnelModeChildConfigRequest req : params.getConfigurationRequests()) {
-            reqList.add(new ConfigRequest(req));
-        }
-        final PersistableBundle configReqListBundle =
-                PersistableBundleUtils.fromList(reqList, ConfigRequest::toPersistableBundle);
-        result.putPersistableBundle(CONFIG_REQUESTS_KEY, configReqListBundle);
-
-        return result;
-    }
-
-    private static List<IkeTrafficSelector> getTsFromPersistableBundle(
-            PersistableBundle in, String key) {
-        PersistableBundle tsBundle = in.getPersistableBundle(key);
-        Objects.requireNonNull(tsBundle, "Value for key " + key + " was null");
-        return PersistableBundleUtils.toList(
-                tsBundle, IkeTrafficSelectorUtils::fromPersistableBundle);
-    }
-
-    /** Constructs a TunnelModeChildSessionParams by deserializing a PersistableBundle. */
-    @NonNull
-    public static TunnelModeChildSessionParams fromPersistableBundle(
-            @NonNull PersistableBundle in) {
-        Objects.requireNonNull(in, "PersistableBundle was null");
-
-        final TunnelModeChildSessionParams.Builder builder =
-                new TunnelModeChildSessionParams.Builder();
-
-        final PersistableBundle proposalBundle = in.getPersistableBundle(SA_PROPOSALS_KEY);
-        Objects.requireNonNull(proposalBundle, "SA proposal was null");
-        final List<ChildSaProposal> proposals =
-                PersistableBundleUtils.toList(
-                        proposalBundle, ChildSaProposalUtils::fromPersistableBundle);
-        for (ChildSaProposal p : proposals) {
-            builder.addSaProposal(p);
-        }
-
-        for (IkeTrafficSelector ts : getTsFromPersistableBundle(in, INBOUND_TS_KEY)) {
-            builder.addInboundTrafficSelectors(ts);
-        }
-
-        for (IkeTrafficSelector ts : getTsFromPersistableBundle(in, OUTBOUND_TS_KEY)) {
-            builder.addOutboundTrafficSelectors(ts);
-        }
-
-        builder.setLifetimeSeconds(
-                in.getInt(HARD_LIFETIME_SEC_KEY), in.getInt(SOFT_LIFETIME_SEC_KEY));
-        final PersistableBundle configReqListBundle = in.getPersistableBundle(CONFIG_REQUESTS_KEY);
-        Objects.requireNonNull(configReqListBundle, "Config request list was null");
-        final List<ConfigRequest> reqList =
-                PersistableBundleUtils.toList(configReqListBundle, ConfigRequest::new);
-
-        boolean hasIpv4AddressReq = false;
-        boolean hasIpv4NetmaskReq = false;
-        for (ConfigRequest req : reqList) {
-            switch (req.type) {
-                case ConfigRequest.TYPE_IPV4_ADDRESS:
-                    hasIpv4AddressReq = true;
-                    if (req.address == null) {
-                        builder.addInternalAddressRequest(AF_INET);
-                    } else {
-                        builder.addInternalAddressRequest((Inet4Address) req.address);
-                    }
-                    break;
-                case ConfigRequest.TYPE_IPV6_ADDRESS:
-                    if (req.address == null) {
-                        builder.addInternalAddressRequest(AF_INET6);
-                    } else {
-                        builder.addInternalAddressRequest(
-                                (Inet6Address) req.address, req.ip6PrefixLen);
-                    }
-                    break;
-                case ConfigRequest.TYPE_IPV4_NETMASK:
-                    // Do not need to set netmask because it will be automatically set by the
-                    // builder when an IPv4 internal address request is set.
-                    hasIpv4NetmaskReq = true;
-                    break;
-                case ConfigRequest.TYPE_IPV4_DNS:
-                    if (req.address != null) {
-                        Log.w(TAG, "Requesting a specific IPv4 DNS server is unsupported");
-                    }
-                    builder.addInternalDnsServerRequest(AF_INET);
-                    break;
-                case ConfigRequest.TYPE_IPV6_DNS:
-                    if (req.address != null) {
-                        Log.w(TAG, "Requesting a specific IPv6 DNS server is unsupported");
-                    }
-                    builder.addInternalDnsServerRequest(AF_INET6);
-                    break;
-                case ConfigRequest.TYPE_IPV4_DHCP:
-                    if (req.address != null) {
-                        Log.w(TAG, "Requesting a specific IPv4 DHCP server is unsupported");
-                    }
-                    builder.addInternalDhcpServerRequest(AF_INET);
-                    break;
-                default:
-                    throw new IllegalArgumentException(
-                            "Unrecognized config request type: " + req.type);
-            }
-        }
-
-        if (hasIpv4AddressReq != hasIpv4NetmaskReq) {
-            Log.w(
-                    TAG,
-                    String.format(
-                            "Expect IPv4 address request and IPv4 netmask request either both"
-                                + " exist or both absent, but found hasIpv4AddressReq exists? %b,"
-                                + " hasIpv4AddressReq exists? %b, ",
-                            hasIpv4AddressReq, hasIpv4NetmaskReq));
-        }
-
-        return builder.build();
-    }
-}
diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java
index f0e12ca..f5fee4f 100644
--- a/core/java/android/os/AggregateBatteryConsumer.java
+++ b/core/java/android/os/AggregateBatteryConsumer.java
@@ -17,7 +17,6 @@
 package android.os;
 
 import android.annotation.NonNull;
-import android.util.proto.ProtoOutputStream;
 
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -100,19 +99,6 @@
         }
     }
 
-    void writePowerComponentModelProto(@NonNull ProtoOutputStream proto) {
-        for (int i = 0; i < POWER_COMPONENT_COUNT; i++) {
-            final int powerModel = getPowerModel(i);
-            if (powerModel == BatteryConsumer.POWER_MODEL_UNDEFINED) continue;
-
-            final long token = proto.start(BatteryUsageStatsAtomsProto.COMPONENT_MODELS);
-            proto.write(BatteryUsageStatsAtomsProto.PowerComponentModel.COMPONENT, i);
-            proto.write(BatteryUsageStatsAtomsProto.PowerComponentModel.POWER_MODEL,
-                    powerModelToProtoEnum(powerModel));
-            proto.end(token);
-        }
-    }
-
     /**
      * Builder for DeviceBatteryConsumer.
      */
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 14b67f6..96ea168 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -161,18 +161,27 @@
 
     /**
      * Unspecified power model.
+     *
+     * @deprecated PowerModel is no longer supported
      */
+    @Deprecated
     public static final int POWER_MODEL_UNDEFINED = 0;
 
     /**
      * Power model that is based on average consumption rates that hardware components
      * consume in various states.
+     *
+     * @deprecated PowerModel is no longer supported
      */
+    @Deprecated
     public static final int POWER_MODEL_POWER_PROFILE = 1;
 
     /**
      * Power model that is based on energy consumption stats provided by PowerStats HAL.
+     *
+     * @deprecated PowerModel is no longer supported
      */
+    @Deprecated
     public static final int POWER_MODEL_ENERGY_CONSUMPTION = 2;
 
     /**
@@ -380,19 +389,17 @@
         public final @ScreenState int screenState;
         public final @PowerState int powerState;
 
-        final int mPowerModelColumnIndex;
         final int mPowerColumnIndex;
         final int mDurationColumnIndex;
 
         private Key(@PowerComponentId int powerComponentId, @ProcessState int processState,
-                @ScreenState int screenState, @PowerState int powerState, int powerModelColumnIndex,
+                @ScreenState int screenState, @PowerState int powerState,
                 int powerColumnIndex, int durationColumnIndex) {
             this.powerComponentId = powerComponentId;
             this.processState = processState;
             this.screenState = screenState;
             this.powerState = powerState;
 
-            mPowerModelColumnIndex = powerModelColumnIndex;
             mPowerColumnIndex = powerColumnIndex;
             mDurationColumnIndex = durationColumnIndex;
         }
@@ -577,11 +584,11 @@
      *
      * @param componentId The ID of the power component, e.g.
      *                    {@link BatteryConsumer#POWER_COMPONENT_CPU}.
+     * @deprecated PowerModel is no longer supported
      */
+    @Deprecated
     public @PowerModel int getPowerModel(@PowerComponentId int componentId) {
-        return mPowerComponents.getPowerModel(
-                mData.layout.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED,
-                        SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED));
+        return POWER_MODEL_UNDEFINED;
     }
 
     /**
@@ -589,9 +596,11 @@
      *
      * @param key The key of the power component, obtained by calling {@link #getKey} or
      *            {@link #getKeys} method.
+     * @deprecated PowerModel is no longer supported
      */
+    @Deprecated
     public @PowerModel int getPowerModel(@NonNull BatteryConsumer.Key key) {
-        return mPowerComponents.getPowerModel(key);
+        return POWER_MODEL_UNDEFINED;
     }
 
     /**
@@ -657,20 +666,6 @@
     }
 
     /**
-     * Returns the name of the specified power model.  Intended for logging and debugging.
-     */
-    public static String powerModelToString(@BatteryConsumer.PowerModel int powerModel) {
-        switch (powerModel) {
-            case BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION:
-                return "energy consumption";
-            case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
-                return "power profile";
-            default:
-                return "";
-        }
-    }
-
-    /**
      * Returns the equivalent PowerModel enum for the specified power model.
      * {@see BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage.PowerModel}
      */
@@ -857,10 +852,8 @@
 
     static class BatteryConsumerDataLayout {
         private static final Key[] KEY_ARRAY = new Key[0];
-        public static final int POWER_MODEL_NOT_INCLUDED = -1;
         public final String[] customPowerComponentNames;
         public final int customPowerComponentCount;
-        public final boolean powerModelsIncluded;
         public final boolean processStateDataIncluded;
         public final boolean screenStateDataIncluded;
         public final boolean powerStateDataIncluded;
@@ -872,11 +865,10 @@
         private SparseArray<Key[]> mPerComponentKeys;
 
         private BatteryConsumerDataLayout(int firstColumn, String[] customPowerComponentNames,
-                boolean powerModelsIncluded, boolean includeProcessStateData,
-                boolean includeScreenState, boolean includePowerState) {
+                boolean includeProcessStateData, boolean includeScreenState,
+                boolean includePowerState) {
             this.customPowerComponentNames = customPowerComponentNames;
             this.customPowerComponentCount = customPowerComponentNames.length;
-            this.powerModelsIncluded = powerModelsIncluded;
             this.processStateDataIncluded = includeProcessStateData;
             this.screenStateDataIncluded = includeScreenState;
             this.powerStateDataIncluded = includePowerState;
@@ -904,7 +896,7 @@
                         continue;
                     }
                     for (int i = 0; i < powerComponentIds.length; i++) {
-                        columnIndex = addKeys(keyList, powerModelsIncluded, includeProcessStateData,
+                        columnIndex = addKeys(keyList, includeProcessStateData,
                                 powerComponentIds[i], screenState, powerState, columnIndex);
                     }
                 }
@@ -934,13 +926,10 @@
             }
         }
 
-        private int addKeys(List<Key> keys, boolean powerModelsIncluded,
-                boolean includeProcessStateData, @PowerComponentId int componentId,
-                int screenState, int powerState, int columnIndex) {
+        private int addKeys(List<Key> keys, boolean includeProcessStateData,
+                @PowerComponentId int componentId, int screenState, int powerState,
+                int columnIndex) {
             keys.add(new Key(componentId, PROCESS_STATE_UNSPECIFIED, screenState, powerState,
-                    powerModelsIncluded
-                            ? columnIndex++
-                            : POWER_MODEL_NOT_INCLUDED,  // power model
                     columnIndex++,      // power
                     columnIndex++       // usage duration
             ));
@@ -956,9 +945,6 @@
                             continue;
                         }
                         keys.add(new Key(componentId, processState, screenState, powerState,
-                                powerModelsIncluded
-                                        ? columnIndex++
-                                        : POWER_MODEL_NOT_INCLUDED, // power model
                                 columnIndex++,      // power
                                 columnIndex++       // usage duration
                         ));
@@ -1016,7 +1002,7 @@
     }
 
     static BatteryConsumerDataLayout createBatteryConsumerDataLayout(
-            String[] customPowerComponentNames, boolean includePowerModels,
+            String[] customPowerComponentNames,
             boolean includeProcessStateData, boolean includeScreenStateData,
             boolean includePowerStateData) {
         int columnCount = BatteryConsumer.COLUMN_COUNT;
@@ -1025,8 +1011,7 @@
         columnCount = Math.max(columnCount, UserBatteryConsumer.COLUMN_COUNT);
 
         return new BatteryConsumerDataLayout(columnCount, customPowerComponentNames,
-                includePowerModels, includeProcessStateData, includeScreenStateData,
-                includePowerStateData);
+                includeProcessStateData, includeScreenStateData, includePowerStateData);
     }
 
     protected abstract static class BaseBuilder<T extends BaseBuilder<?>> {
@@ -1086,7 +1071,7 @@
         public T setConsumedPower(@PowerComponentId int componentId, double componentPower,
                 @PowerModel int powerModel) {
             mPowerComponentsBuilder.setConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
-                    componentPower, powerModel);
+                    componentPower);
             return (T) this;
         }
 
@@ -1095,14 +1080,14 @@
         public T addConsumedPower(@PowerComponentId int componentId, double componentPower,
                 @PowerModel int powerModel) {
             mPowerComponentsBuilder.addConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
-                    componentPower, powerModel);
+                    componentPower);
             return (T) this;
         }
 
         @SuppressWarnings("unchecked")
         @NonNull
         public T setConsumedPower(Key key, double componentPower, @PowerModel int powerModel) {
-            mPowerComponentsBuilder.setConsumedPower(key, componentPower, powerModel);
+            mPowerComponentsBuilder.setConsumedPower(key, componentPower);
             return (T) this;
         }
 
@@ -1110,21 +1095,14 @@
         @NonNull
         public T addConsumedPower(@PowerComponentId int componentId, double componentPower) {
             mPowerComponentsBuilder.addConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
-                    componentPower, POWER_MODEL_UNDEFINED);
+                    componentPower);
             return (T) this;
         }
 
         @SuppressWarnings("unchecked")
         @NonNull
         public T addConsumedPower(Key key, double componentPower) {
-            mPowerComponentsBuilder.addConsumedPower(key, componentPower, POWER_MODEL_UNDEFINED);
-            return (T) this;
-        }
-
-        @SuppressWarnings("unchecked")
-        @NonNull
-        public T addConsumedPower(Key key, double componentPower, @PowerModel int powerModel) {
-            mPowerComponentsBuilder.addConsumedPower(key, componentPower, powerModel);
+            mPowerComponentsBuilder.addConsumedPower(key, componentPower);
             return (T) this;
         }
 
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 8b267bf..b63ad5f 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -167,76 +167,90 @@
     public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS";
 
     /**
-     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
-     * Int value representing the battery's capacity level. These constants are key indicators of
-     * battery status and system capabilities, guiding power management decisions for both the
-     * system and apps:
-     * {@link #BATTERY_CAPACITY_LEVEL_UNSUPPORTED}: Feature not supported on this device.
-     * {@link #BATTERY_CAPACITY_LEVEL_UNKNOWN}: Battery status is unavailable or uninitialized.
-     * {@link #BATTERY_CAPACITY_LEVEL_CRITICAL}: Battery is critically low and the Android
-     * framework has been notified to schedule a shutdown by this value
-     * {@link #BATTERY_CAPACITY_LEVEL_LOW}: Android framework must limit background jobs to
-     * avoid impacting charging speed
-     * {@link #BATTERY_CAPACITY_LEVEL_NORMAL}: Battery level and charging rates are normal,
-     * battery temperature is within normal range and adapter power is enough to charge the
-     * battery at an acceptable rate. Android framework can run light background tasks without
-     * affecting charging performance severely.
-     * {@link #BATTERY_CAPACITY_LEVEL_HIGH}: Battery level is high, battery temperature is
-     * within normal range and adapter power is enough to charge the battery at an acceptable
-     * rate while running background loads. Android framework can run background tasks without
-     * affecting charging or battery performance.
-     * {@link #BATTERY_CAPACITY_LEVEL_FULL}: The battery is full, battery temperature is
-     * within normal range and adapter power is enough to sustain running background loads.
-     * Android framework can run background tasks without affecting the battery level or
-     * battery performance.
-     */
-
-    @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
-    public static final String EXTRA_CAPACITY_LEVEL = "android.os.extra.CAPACITY_LEVEL";
-
-    /**
-     * Battery capacity level is unsupported. @see EXTRA_CAPACITY_LEVEL
+     * Battery capacity level is unsupported.
+     *
+     * @see #EXTRA_CAPACITY_LEVEL
      */
     @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
     public static final int BATTERY_CAPACITY_LEVEL_UNSUPPORTED = -1;
 
     /**
-     * Battery capacity level is unknown. @see EXTRA_CAPACITY_LEVEL
+     * Battery capacity level is unknown.
+     *
+     * @see #EXTRA_CAPACITY_LEVEL
      */
     @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
     public static final int BATTERY_CAPACITY_LEVEL_UNKNOWN = 0;
 
     /**
-     * Battery capacity level is critical. @see EXTRA_CAPACITY_LEVEL
+     * Battery capacity level is critical. The Android framework has been notified to schedule
+     * a shutdown by this value.
+     *
+     * @see #EXTRA_CAPACITY_LEVEL
      */
     @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
     public static final int BATTERY_CAPACITY_LEVEL_CRITICAL = 1;
 
     /**
-     * Battery capacity level is low. @see EXTRA_CAPACITY_LEVEL
+     * Battery capacity level is low. The Android framework must limit background jobs to avoid
+     * impacting charging speed.
+     *
+     * @see #EXTRA_CAPACITY_LEVEL
      */
     @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
     public static final int BATTERY_CAPACITY_LEVEL_LOW = 2;
 
     /**
-     * Battery capacity level is normal. @see EXTRA_CAPACITY_LEVEL
+     * Battery capacity level is normal. Battery level and charging rates are normal, battery
+     * temperature is within the normal range, and adapter power is enough to charge the battery
+     * at an acceptable rate. The Android framework can run light background tasks without
+     * affecting charging performance severely.
+     *
+     * @see #EXTRA_CAPACITY_LEVEL
      */
     @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
     public static final int BATTERY_CAPACITY_LEVEL_NORMAL = 3;
 
     /**
-     * Battery capacity level is high. @see EXTRA_CAPACITY_LEVEL
+     * Battery capacity level is high. Battery level is high, battery temperature is within the
+     * normal range, and adapter power is enough to charge the battery at an acceptable rate
+     * while running background loads. The Android framework can run background tasks without
+     * affecting charging or battery performance.
+     *
+     * @see #EXTRA_CAPACITY_LEVEL
      */
     @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
     public static final int BATTERY_CAPACITY_LEVEL_HIGH = 4;
 
     /**
-     * Battery capacity level is full. @see EXTRA_CAPACITY_LEVEL
+     * Battery capacity level is full. The battery is full, the battery temperature is within the
+     * normal range, and adapter power is enough to sustain running background loads. The Android
+     * framework can run background tasks without affecting the battery level or battery
+     * performance.
+     *
+     * @see #EXTRA_CAPACITY_LEVEL
      */
     @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
     public static final int BATTERY_CAPACITY_LEVEL_FULL = 5;
 
     /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * Int value representing the battery's capacity level. These constants are key indicators of
+     * battery status and system capabilities, guiding power management decisions for both the
+     * system and apps.
+     *
+     * @see #BATTERY_CAPACITY_LEVEL_UNSUPPORTED
+     * @see #BATTERY_CAPACITY_LEVEL_UNKNOWN
+     * @see #BATTERY_CAPACITY_LEVEL_CRITICAL
+     * @see #BATTERY_CAPACITY_LEVEL_LOW
+     * @see #BATTERY_CAPACITY_LEVEL_NORMAL
+     * @see #BATTERY_CAPACITY_LEVEL_HIGH
+     * @see #BATTERY_CAPACITY_LEVEL_FULL
+     */
+    @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+    public static final String EXTRA_CAPACITY_LEVEL = "android.os.extra.CAPACITY_LEVEL";
+
+    /**
      * Extra for {@link android.content.Intent#ACTION_BATTERY_LEVEL_CHANGED}:
      * Contains list of Bundles representing battery events
      * @hide
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 72e4cef..f913fcf 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -114,7 +114,6 @@
     static final String XML_ATTR_POWER_STATE = "power_state";
     static final String XML_ATTR_POWER = "power";
     static final String XML_ATTR_DURATION = "duration";
-    static final String XML_ATTR_MODEL = "model";
     static final String XML_ATTR_BATTERY_CAPACITY = "battery_capacity";
     static final String XML_ATTR_DISCHARGE_PERCENT = "discharge_pct";
     static final String XML_ATTR_DISCHARGE_LOWER = "discharge_lower";
@@ -155,7 +154,6 @@
     private final long mBatteryTimeRemainingMs;
     private final long mChargeTimeRemainingMs;
     private final String[] mCustomPowerComponentNames;
-    private final boolean mIncludesPowerModels;
     private final boolean mIncludesProcessStateData;
     private final boolean mIncludesScreenStateData;
     private final boolean mIncludesPowerStateData;
@@ -179,7 +177,6 @@
         mBatteryTimeRemainingMs = builder.mBatteryTimeRemainingMs;
         mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs;
         mCustomPowerComponentNames = builder.mCustomPowerComponentNames;
-        mIncludesPowerModels = builder.mIncludePowerModels;
         mIncludesProcessStateData = builder.mIncludesProcessStateData;
         mIncludesScreenStateData = builder.mIncludesScreenStateData;
         mIncludesPowerStateData = builder.mIncludesPowerStateData;
@@ -364,14 +361,13 @@
         mBatteryTimeRemainingMs = source.readLong();
         mChargeTimeRemainingMs = source.readLong();
         mCustomPowerComponentNames = source.readStringArray();
-        mIncludesPowerModels = source.readBoolean();
         mIncludesProcessStateData = source.readBoolean();
         mIncludesScreenStateData = source.readBoolean();
         mIncludesPowerStateData = source.readBoolean();
 
         mBatteryConsumersCursorWindow = CursorWindow.newFromParcel(source);
         mBatteryConsumerDataLayout = BatteryConsumer.createBatteryConsumerDataLayout(
-                mCustomPowerComponentNames, mIncludesPowerModels, mIncludesProcessStateData,
+                mCustomPowerComponentNames, mIncludesProcessStateData,
                 mIncludesScreenStateData, mIncludesPowerStateData);
 
         final int numRows = mBatteryConsumersCursorWindow.getNumRows();
@@ -424,7 +420,6 @@
         dest.writeLong(mBatteryTimeRemainingMs);
         dest.writeLong(mChargeTimeRemainingMs);
         dest.writeStringArray(mCustomPowerComponentNames);
-        dest.writeBoolean(mIncludesPowerModels);
         dest.writeBoolean(mIncludesProcessStateData);
         dest.writeBoolean(mIncludesScreenStateData);
         dest.writeBoolean(mIncludesPowerStateData);
@@ -506,9 +501,6 @@
                 getDischargeDurationMs());
         deviceBatteryConsumer.writeStatsProto(proto,
                 BatteryUsageStatsAtomsProto.DEVICE_BATTERY_CONSUMER);
-        if (mIncludesPowerModels) {
-            deviceBatteryConsumer.writePowerComponentModelProto(proto);
-        }
         writeUidBatteryConsumersProto(proto, maxRawSize);
     }
 
@@ -629,7 +621,7 @@
 
             printPowerComponent(pw, prefix,
                     mBatteryConsumerDataLayout.getPowerComponentName(powerComponent),
-                    devicePowerMah, appsPowerMah, BatteryConsumer.POWER_MODEL_UNDEFINED,
+                    devicePowerMah, appsPowerMah,
                     deviceConsumer.getUsageDurationMillis(powerComponent));
         }
 
@@ -716,23 +708,15 @@
             printPowerComponent(pw, prefix,
                     mBatteryConsumerDataLayout.getPowerComponentName(powerComponent),
                     devicePowerMah, appsPowerMah,
-                    mIncludesPowerModels ? deviceConsumer.getPowerModel(powerComponent)
-                            : BatteryConsumer.POWER_MODEL_UNDEFINED,
                     deviceConsumer.getUsageDurationMillis(dimensions));
         }
     }
 
     private void printPowerComponent(PrintWriter pw, String prefix, String label,
-            double devicePowerMah, double appsPowerMah, int powerModel, long durationMs) {
+            double devicePowerMah, double appsPowerMah, long durationMs) {
         StringBuilder sb = new StringBuilder();
         sb.append(prefix).append("    ").append(label).append(": ")
                 .append(BatteryStats.formatCharge(devicePowerMah));
-        if (powerModel != BatteryConsumer.POWER_MODEL_UNDEFINED
-                && powerModel != BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
-            sb.append(" [");
-            sb.append(BatteryConsumer.powerModelToString(powerModel));
-            sb.append("]");
-        }
         sb.append(" apps: ").append(BatteryStats.formatCharge(appsPowerMah));
         if (durationMs != 0) {
             sb.append(" duration: ");
@@ -828,7 +812,7 @@
                 final boolean includesPowerStateData = parser.getAttributeBoolean(null,
                         XML_ATTR_PREFIX_INCLUDES_POWER_STATE_DATA, false);
 
-                builder = new Builder(customComponentNames.toArray(new String[0]), true,
+                builder = new Builder(customComponentNames.toArray(new String[0]),
                         includesProcStateData, includesScreenStateData, includesPowerStateData, 0);
 
                 builder.setStatsStartTimestamp(
@@ -913,7 +897,6 @@
         private final CursorWindow mBatteryConsumersCursorWindow;
         @NonNull
         private final String[] mCustomPowerComponentNames;
-        private final boolean mIncludePowerModels;
         private final boolean mIncludesProcessStateData;
         private final boolean mIncludesScreenStateData;
         private final boolean mIncludesPowerStateData;
@@ -938,22 +921,21 @@
         private BatteryStatsHistory mBatteryStatsHistory;
 
         public Builder(@NonNull String[] customPowerComponentNames) {
-            this(customPowerComponentNames, false, false, false, false, 0);
+            this(customPowerComponentNames, false, false, false, 0);
         }
 
-        public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels,
+        public Builder(@NonNull String[] customPowerComponentNames,
                 boolean includeProcessStateData, boolean includeScreenStateData,
                 boolean includesPowerStateData, double minConsumedPowerThreshold) {
             mBatteryConsumersCursorWindow =
                     new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE);
             onCursorWindowAllocated(mBatteryConsumersCursorWindow);
             mBatteryConsumerDataLayout = BatteryConsumer.createBatteryConsumerDataLayout(
-                    customPowerComponentNames, includePowerModels, includeProcessStateData,
+                    customPowerComponentNames, includeProcessStateData,
                     includeScreenStateData, includesPowerStateData);
             mBatteryConsumersCursorWindow.setNumColumns(mBatteryConsumerDataLayout.columnCount);
 
             mCustomPowerComponentNames = customPowerComponentNames;
-            mIncludePowerModels = includePowerModels;
             mIncludesProcessStateData = includeProcessStateData;
             mIncludesScreenStateData = includeScreenStateData;
             mIncludesPowerStateData = includesPowerStateData;
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 6325b00..6e67578 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -24,6 +24,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
 
 /**
  * Query parameters for the {@link BatteryStatsManager#getBatteryUsageStats()} call.
@@ -65,12 +66,6 @@
      */
     public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY = 0x0002;
 
-    /**
-     * Indicates that identifiers of power models used for computations of power
-     * consumption should be included in the BatteryUsageStats.
-     */
-    public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS = 0x0004;
-
     public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA = 0x0008;
 
     public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS = 0x0010;
@@ -202,7 +197,24 @@
         return mAggregatedToTimestamp;
     }
 
+    @Override
+    public String toString() {
+        return "BatteryUsageStatsQuery{"
+                + "mFlags=" + Integer.toHexString(mFlags)
+                + ", mUserIds=" + Arrays.toString(mUserIds)
+                + ", mMaxStatsAgeMs=" + mMaxStatsAgeMs
+                + ", mAggregatedFromTimestamp=" + mAggregatedFromTimestamp
+                + ", mAggregatedToTimestamp=" + mAggregatedToTimestamp
+                + ", mMonotonicStartTime=" + mMonotonicStartTime
+                + ", mMonotonicEndTime=" + mMonotonicEndTime
+                + ", mMinConsumedPowerThreshold=" + mMinConsumedPowerThreshold
+                + ", mPowerComponents=" + Arrays.toString(mPowerComponents)
+                + '}';
+    }
+
     private BatteryUsageStatsQuery(Parcel in) {
+        mMonotonicStartTime = in.readLong();
+        mMonotonicEndTime = in.readLong();
         mFlags = in.readInt();
         mUserIds = new int[in.readInt()];
         in.readIntArray(mUserIds);
@@ -215,6 +227,8 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mMonotonicStartTime);
+        dest.writeLong(mMonotonicEndTime);
         dest.writeInt(mFlags);
         dest.writeInt(mUserIds.length);
         dest.writeIntArray(mUserIds);
@@ -311,7 +325,10 @@
          * power monitoring data is available.
          *
          * Should only be used for testing and debugging.
+         *
+         * @deprecated PowerModel is no longer supported
          */
+        @Deprecated
         public Builder powerProfileModeledOnly() {
             mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL;
             return this;
@@ -322,9 +339,10 @@
          * of power consumption.
          *
          * Should only be used for testing and debugging.
+         * @deprecated PowerModel is no longer supported
          */
+        @Deprecated
         public Builder includePowerModels() {
-            mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS;
             return this;
         }
 
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index c2e9260..8b6da7e 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -41,6 +41,8 @@
 import android.util.Slog;
 import android.view.View;
 
+import com.android.internal.util.FrameworkStatsLog;
+
 import dalvik.system.VMRuntime;
 
 import java.lang.annotation.Retention;
@@ -1546,6 +1548,57 @@
     }
 
     /**
+     * Convert a major.minor version String like "36.1" to an int that
+     * represents both major and minor version.
+     *
+     * @param version the String to parse
+     * @return an int encoding the major and minor version
+     * @throws IllegalArgumentException if the string could not be converted into an int
+     *
+     * @hide
+     */
+    @SuppressWarnings("FlaggedApi") // SDK_INT_MULTIPLIER is defined in this file
+    public static @SdkIntFull int parseFullVersion(@NonNull String version) {
+        int index = version.indexOf('.');
+        int major;
+        int minor = 0;
+        try {
+            if (index == -1) {
+                major = Integer.parseInt(version);
+            } else {
+                major = Integer.parseInt(version.substring(0, index));
+                minor = Integer.parseInt(version.substring(index + 1));
+            }
+            if (major < 0 || minor < 0) {
+                throw new NumberFormatException();
+            }
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("failed to parse '" + version
+                    + "' as a major.minor version code");
+        }
+        return major * VERSION_CODES_FULL.SDK_INT_MULTIPLIER + minor;
+    }
+
+    /**
+     * Convert an int representing a major.minor version like SDK_INT_FULL to a
+     * human readable string. The returned string is only intended for debug
+     * and error messages.
+     *
+     * @param version the int to convert to a string
+     * @return a String representing the same major.minor version as the int passed in
+     * @throws IllegalArgumentException if {@code version} is negative
+     *
+     * @hide
+     */
+    public static String fullVersionToString(@SdkIntFull int version) {
+        if (version < 0) {
+            throw new IllegalArgumentException("failed to convert '" + version
+                    + "' to string: not a valid major.minor version code");
+        }
+        return String.format("%d.%d", getMajorSdkVersion(version), getMinorSdkVersion(version));
+    }
+
+    /**
      * The vendor API for 2024 Q2
      *
      * <p>For Android 14-QPR3 and later, the vendor API level is completely decoupled from the SDK
@@ -1615,11 +1668,14 @@
      */
     @FlaggedApi(android.os.Flags.FLAG_API_FOR_BACKPORTED_FIXES)
     public static @BackportedFixStatus int getBackportedFixStatus(long id) {
-        if (id <= 0 || id > 1023) {
-            return BACKPORTED_FIX_STATUS_UNKNOWN;
+        @BackportedFixStatus int status = BACKPORTED_FIX_STATUS_UNKNOWN;
+        int uid = Binder.getCallingUid();
+        if (id > 0 && id <= 1023) {
+            status = isBitSet(BackportedFixesProperties.alias_bitset(), (int) id)
+                    ? BACKPORTED_FIX_STATUS_FIXED : BACKPORTED_FIX_STATUS_UNKNOWN;
         }
-        return isBitSet(BackportedFixesProperties.alias_bitset(), (int) id)
-                ? BACKPORTED_FIX_STATUS_FIXED : BACKPORTED_FIX_STATUS_UNKNOWN;
+        FrameworkStatsLog.write(FrameworkStatsLog.BACKPORTED_FIX_STATUS_REPORTED, uid, id, status);
+        return status;
     }
 
     private static boolean isBitSet(List<Long> bitsetLongArray, int bitIndex) {
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 036ccd8..804e8fa 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -32,6 +32,8 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.ravenwood.RavenwoodEnvironment;
+
 import dalvik.annotation.optimization.NeverCompile;
 
 import java.io.FileDescriptor;
@@ -88,14 +90,20 @@
     // queue for async messages when inserting a message at the tail.
     private int mAsyncMessageCount;
 
-    /*
+    /**
      * Select between two implementations of message queue. The legacy implementation is used
      * by default as it provides maximum compatibility with applications and tests that
      * reach into MessageQueue via the mMessages field. The concurrent implemmentation is used for
      * system processes and provides a higher level of concurrency and higher enqueue throughput
      * than the legacy implementation.
      */
-    private boolean mUseConcurrent;
+    private final boolean mUseConcurrent;
+
+    /**
+     * Caches process-level checks that determine `mUseConcurrent`.
+     * This is to avoid redoing checks that shouldn't change during the process's lifetime.
+     */
+    private static Boolean sIsProcessAllowedToUseConcurrent = null;
 
     @RavenwoodRedirect
     private native static long nativeInit();
@@ -112,37 +120,67 @@
     private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
 
     MessageQueue(boolean quitAllowed) {
-        // Concurrent mode modifies behavior that is observable via reflection and is commonly used
-        // by tests.
-        // For now, we limit it to system processes to avoid breaking apps and their tests.
-        mUseConcurrent = UserHandle.isCore(Process.myUid());
-        // Even then, we don't use it if instrumentation is loaded as it breaks some
-        // platform tests.
-        final Instrumentation instrumentation = getInstrumentation();
-        mUseConcurrent &= instrumentation == null || !instrumentation.isInstrumenting();
-        // We can lift this restriction in the future after we've made it possible for test authors
-        // to test Looper and MessageQueue without resorting to reflection.
-
-        // Holdback study.
-        if (mUseConcurrent && Flags.messageQueueForceLegacy()) {
-            mUseConcurrent = false;
-        }
-
+        initIsProcessAllowedToUseConcurrent();
+        mUseConcurrent = sIsProcessAllowedToUseConcurrent && !isInstrumenting();
         mQuitAllowed = quitAllowed;
         mPtr = nativeInit();
     }
 
-    @android.ravenwood.annotation.RavenwoodReplace(blockedBy = ActivityThread.class)
-    private static Instrumentation getInstrumentation() {
-        final ActivityThread activityThread = ActivityThread.currentActivityThread();
-        if (activityThread != null) {
-            return activityThread.getInstrumentation();
+    private static void initIsProcessAllowedToUseConcurrent() {
+        if (sIsProcessAllowedToUseConcurrent != null) {
+            return;
         }
-        return null;
+
+        if (RavenwoodEnvironment.getInstance().isRunningOnRavenwood()) {
+            sIsProcessAllowedToUseConcurrent = false;
+            return;
+        }
+
+        final String processName = Process.myProcessName();
+        if (processName == null) {
+            // Assume that this is a host-side test and avoid concurrent mode for now.
+            sIsProcessAllowedToUseConcurrent = false;
+            return;
+        }
+
+        // Concurrent mode modifies behavior that is observable via reflection and is commonly
+        // used by tests.
+        // For now, we limit it to system processes to avoid breaking apps and their tests.
+        sIsProcessAllowedToUseConcurrent = UserHandle.isCore(Process.myUid());
+
+        if (sIsProcessAllowedToUseConcurrent) {
+            // Some platform tests run in core UIDs.
+            // Use this awful heuristic to detect them.
+            if (processName.contains("test") || processName.contains("Test")) {
+                sIsProcessAllowedToUseConcurrent = false;
+            }
+        } else {
+            // Also explicitly allow SystemUI processes.
+            // SystemUI doesn't run in a core UID, but we want to give it the performance boost,
+            // and we know that it's safe to use the concurrent implementation in SystemUI.
+            sIsProcessAllowedToUseConcurrent =
+                    processName.equals("com.android.systemui")
+                            || processName.startsWith("com.android.systemui:");
+            // On Android distributions where SystemUI has a different process name,
+            // the above condition may need to be adjusted accordingly.
+        }
+
+        // We can lift these restrictions in the future after we've made it possible for test
+        // authors to test Looper and MessageQueue without resorting to reflection.
+
+        // Holdback study.
+        if (sIsProcessAllowedToUseConcurrent && Flags.messageQueueForceLegacy()) {
+            sIsProcessAllowedToUseConcurrent = false;
+        }
     }
 
-    private static Instrumentation getInstrumentation$ravenwood() {
-        return null; // Instrumentation not supported on Ravenwood yet.
+    private static boolean isInstrumenting() {
+        final ActivityThread activityThread = ActivityThread.currentActivityThread();
+        if (activityThread == null) {
+            return false;
+        }
+        final Instrumentation instrumentation = activityThread.getInstrumentation();
+        return instrumentation != null && instrumentation.isInstrumenting();
     }
 
     @Override
diff --git a/core/java/android/os/CpuHeadroomParams.java b/core/java/android/os/CpuHeadroomParams.java
index f0d4f7d..072c012 100644
--- a/core/java/android/os/CpuHeadroomParams.java
+++ b/core/java/android/os/CpuHeadroomParams.java
@@ -18,10 +18,13 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
 import android.os.health.SystemHealthManager;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
 
 /**
  * Headroom request params used by {@link SystemHealthManager#getCpuHeadroom(CpuHeadroomParams)}.
@@ -53,6 +56,10 @@
      */
     public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1;
 
+    private static final int CALCULATION_WINDOW_MILLIS_MIN = 50;
+    private static final int CALCULATION_WINDOW_MILLIS_MAX = 10000;
+    private static final int MAX_TID_COUNT = 5;
+
     /**
      * Sets the headroom calculation type.
      * <p>
@@ -83,6 +90,61 @@
     }
 
     /**
+     * Sets the headroom calculation window size in milliseconds.
+     * <p>
+     *
+     * @param windowMillis the window size in milliseconds ranges from [50, 10000]. The smaller the
+     *                     window size, the larger fluctuation in the headroom value should be
+     *                     expected. The default value can be retrieved from the
+     *                     {@link #getCalculationWindowMillis}. The device will try to use the
+     *                     closest feasible window size to this param.
+     * @throws IllegalArgumentException if the window size is not in allowed range.
+     */
+    public void setCalculationWindowMillis(
+            @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to =
+                    CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) {
+        if (windowMillis < CALCULATION_WINDOW_MILLIS_MIN
+                || windowMillis > CALCULATION_WINDOW_MILLIS_MAX) {
+            throw new IllegalArgumentException("Invalid calculation window: " + windowMillis);
+        }
+        mInternal.calculationWindowMillis = windowMillis;
+    }
+
+    /**
+     * Gets the headroom calculation window size in milliseconds.
+     * <p>
+     * This will return the default value chosen by the device if the params is not set.
+     */
+    public @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to =
+            CALCULATION_WINDOW_MILLIS_MAX) long getCalculationWindowMillis() {
+        return mInternal.calculationWindowMillis;
+    }
+
+    /**
+     * Sets the thread TIDs to track.
+     * <p>
+     * The TIDs should belong to the same of the process that will the headroom call. And they
+     * should not have different core affinity.
+     * <p>
+     * If not set, the headroom will be based on the PID of the process making the call.
+     *
+     * @param tids non-empty list of TIDs, maximum 5.
+     * @throws IllegalArgumentException if the list size is not in allowed range or TID is not
+     *                                  positive.
+     */
+    public void setTids(@NonNull int... tids) {
+        if (tids.length == 0 || tids.length > MAX_TID_COUNT) {
+            throw new IllegalArgumentException("Invalid number of TIDs: " + tids.length);
+        }
+        for (int tid : tids) {
+            if (tid <= 0) {
+                throw new IllegalArgumentException("Invalid TID: " + tid);
+            }
+        }
+        mInternal.tids = Arrays.copyOf(tids, tids.length);
+    }
+
+    /**
      * @hide
      */
     public CpuHeadroomParamsInternal getInternal() {
diff --git a/core/java/android/os/CpuHeadroomParamsInternal.aidl b/core/java/android/os/CpuHeadroomParamsInternal.aidl
index 6cc4699..12b2093 100644
--- a/core/java/android/os/CpuHeadroomParamsInternal.aidl
+++ b/core/java/android/os/CpuHeadroomParamsInternal.aidl
@@ -25,7 +25,8 @@
 @JavaDerive(equals = true, toString = true)
 parcelable CpuHeadroomParamsInternal {
     boolean usesDeviceHeadroom = false;
+    int[] tids;
+    int calculationWindowMillis = 1000;
     CpuHeadroomParams.CalculationType calculationType = CpuHeadroomParams.CalculationType.MIN;
-    CpuHeadroomParams.SelectionType selectionType = CpuHeadroomParams.SelectionType.ALL;
 }
 
diff --git a/core/java/android/os/GpuHeadroomParams.java b/core/java/android/os/GpuHeadroomParams.java
index efb2a28..126ee8c 100644
--- a/core/java/android/os/GpuHeadroomParams.java
+++ b/core/java/android/os/GpuHeadroomParams.java
@@ -18,6 +18,7 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.os.health.SystemHealthManager;
 
 import java.lang.annotation.Retention;
@@ -53,6 +54,9 @@
      */
     public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1;
 
+    private static final int CALCULATION_WINDOW_MILLIS_MIN = 50;
+    private static final int CALCULATION_WINDOW_MILLIS_MAX = 10000;
+
     /**
      * Sets the headroom calculation type.
      * <p>
@@ -71,7 +75,7 @@
 
     /**
      * Gets the headroom calculation type.
-     * Default to {@link #GPU_HEADROOM_CALCULATION_TYPE_MIN} if not set.
+     * Default to {@link #GPU_HEADROOM_CALCULATION_TYPE_MIN} if the params is not set.
      */
     public @GpuHeadroomCalculationType int getCalculationType() {
         @GpuHeadroomCalculationType int validatedType = switch ((int) mInternal.calculationType) {
@@ -83,6 +87,37 @@
     }
 
     /**
+     * Sets the headroom calculation window size in milliseconds.
+     * <p>
+     *
+     * @param windowMillis the window size in milliseconds ranges from [50, 10000]. The smaller the
+     *                     window size, the larger fluctuation in the headroom value should be
+     *                     expected. The default value can be retrieved from the
+     *                     {@link #getCalculationWindowMillis}. The device will try to use the
+     *                     closest feasible window size to this param.
+     * @throws IllegalArgumentException if the window is invalid.
+     */
+    public void setCalculationWindowMillis(
+            @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to =
+                    CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) {
+        if (windowMillis < CALCULATION_WINDOW_MILLIS_MIN
+                || windowMillis > CALCULATION_WINDOW_MILLIS_MAX) {
+            throw new IllegalArgumentException("Invalid calculation window: " + windowMillis);
+        }
+        mInternal.calculationWindowMillis = windowMillis;
+    }
+
+    /**
+     * Gets the headroom calculation window size in milliseconds.
+     * <p>
+     * This will return the default value chosen by the device if not set.
+     */
+    public @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to =
+            CALCULATION_WINDOW_MILLIS_MAX) int getCalculationWindowMillis() {
+        return mInternal.calculationWindowMillis;
+    }
+
+    /**
      * @hide
      */
     public GpuHeadroomParamsInternal getInternal() {
diff --git a/core/java/android/os/GpuHeadroomParamsInternal.aidl b/core/java/android/os/GpuHeadroomParamsInternal.aidl
index 20309e7..40d5d8e 100644
--- a/core/java/android/os/GpuHeadroomParamsInternal.aidl
+++ b/core/java/android/os/GpuHeadroomParamsInternal.aidl
@@ -24,5 +24,6 @@
  */
 @JavaDerive(equals = true, toString = true)
 parcelable GpuHeadroomParamsInternal {
+    int calculationWindowMillis = 1000;
     GpuHeadroomParams.CalculationType calculationType = GpuHeadroomParams.CalculationType.MIN;
 }
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 94768d1..8f6a508 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -98,6 +98,7 @@
     private static final int VULKAN_1_1 = 0x00401000;
     private static final int VULKAN_1_2 = 0x00402000;
     private static final int VULKAN_1_3 = 0x00403000;
+    private static final int VULKAN_1_4 = 0x00404000;
 
     // Values for UPDATABLE_DRIVER_ALL_APPS
     // 0: Default (Invalid values fallback to default as well)
@@ -179,6 +180,10 @@
     private int getVulkanVersion(PackageManager pm) {
         // PackageManager doesn't have an API to retrieve the version of a specific feature, and we
         // need to avoid retrieving all system features here and looping through them.
+        if (pm.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, VULKAN_1_4)) {
+            return VULKAN_1_4;
+        }
+
         if (pm.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, VULKAN_1_3)) {
             return VULKAN_1_3;
         }
diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl
index a043da9..4a14a8d 100644
--- a/core/java/android/os/IHintManager.aidl
+++ b/core/java/android/os/IHintManager.aidl
@@ -21,7 +21,9 @@
 import android.os.GpuHeadroomParamsInternal;
 import android.os.IHintSession;
 import android.os.SessionCreationConfig;
+import android.hardware.power.CpuHeadroomResult;
 import android.hardware.power.ChannelConfig;
+import android.hardware.power.GpuHeadroomResult;
 import android.hardware.power.SessionConfig;
 import android.hardware.power.SessionTag;
 
@@ -53,13 +55,19 @@
      */
     @nullable ChannelConfig getSessionChannel(in IBinder token);
     oneway void closeSessionChannel();
-    float[] getCpuHeadroom(in CpuHeadroomParamsInternal params);
+    @nullable CpuHeadroomResult getCpuHeadroom(in CpuHeadroomParamsInternal params);
     long getCpuHeadroomMinIntervalMillis();
-    float getGpuHeadroom(in GpuHeadroomParamsInternal params);
+    @nullable GpuHeadroomResult getGpuHeadroom(in GpuHeadroomParamsInternal params);
     long getGpuHeadroomMinIntervalMillis();
 
     /**
      * Get Maximum number of graphics pipeline threads allowed per-app.
      */
     int getMaxGraphicsPipelineThreadsCount();
+
+    /**
+     * Used by the JNI to pass an interface to the SessionManager;
+     * for internal use only.
+     */
+    oneway void passSessionManagerBinder(in IBinder sessionManager);
 }
diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl
index 6fd4f3c..e3f899d 100644
--- a/core/java/android/os/IHintSession.aidl
+++ b/core/java/android/os/IHintSession.aidl
@@ -27,4 +27,9 @@
     void sendHint(int hint);
     void setMode(int mode, boolean enabled);
     void reportActualWorkDuration2(in WorkDuration[] workDurations);
+
+    /**
+     * Used by apps to associate a session to a given set of layers
+     */
+    oneway void associateToLayers(in IBinder[] layerTokens);
 }
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index e85e580..4cac4dee 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -55,7 +55,7 @@
     void goToSleepWithDisplayId(int displayId, long time, int reason, int flags);
     @UnsupportedAppUsage(maxTargetSdk = 28)
     void nap(long time);
-    float getBrightnessConstraint(int constraint);
+    float getBrightnessConstraint(int displayId, int constraint);
     @UnsupportedAppUsage
     boolean isInteractive();
     boolean isDisplayInteractive(int displayId);
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 94259d7..bfcc5cc 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -91,10 +91,11 @@
 # PerformanceHintManager
 per-file CpuHeadroom*.aidl = file:/ADPF_OWNERS
 per-file GpuHeadroom*.aidl = file:/ADPF_OWNERS
-per-file PerformanceHintManager.java = file:/ADPF_OWNERS
+per-file CpuHeadroom*.java = file:/ADPF_OWNERS
+per-file GpuHeadroom*.java = file:/ADPF_OWNERS
 per-file WorkDuration.java = file:/ADPF_OWNERS
-per-file IHintManager.aidl = file:/ADPF_OWNERS
-per-file IHintSession.aidl = file:/ADPF_OWNERS
+per-file *Hint* = file:/ADPF_OWNERS
+per-file *Session* = file:/ADPF_OWNERS
 
 # IThermal interfaces
 per-file IThermal* = file:/THERMAL_OWNERS
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index bf7116d..6855d95 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -50,6 +50,7 @@
 
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
+import dalvik.annotation.optimization.NeverInline;
 
 import libcore.util.SneakyThrow;
 
@@ -587,6 +588,17 @@
         return parcel;
     }
 
+    @NeverInline
+    private void errorUsedWhileRecycling() {
+        Log.wtf(TAG, "Parcel used while recycled. "
+                + Log.getStackTraceString(new Throwable())
+                + " Original recycle call (if DEBUG_RECYCLE): ", mStack);
+    }
+
+    private void assertNotRecycled() {
+        if (mRecycled) errorUsedWhileRecycling();
+    }
+
     /**
      * Put a Parcel object back into the pool.  You must not touch
      * the object after this call.
@@ -635,6 +647,7 @@
      * @hide
      */
     public void setReadWriteHelper(@Nullable ReadWriteHelper helper) {
+        assertNotRecycled();
         mReadWriteHelper = helper != null ? helper : ReadWriteHelper.DEFAULT;
     }
 
@@ -644,6 +657,7 @@
      * @hide
      */
     public boolean hasReadWriteHelper() {
+        assertNotRecycled();
         return (mReadWriteHelper != null) && (mReadWriteHelper != ReadWriteHelper.DEFAULT);
     }
 
@@ -670,6 +684,7 @@
      * @hide
      */
     public final void markSensitive() {
+        assertNotRecycled();
         nativeMarkSensitive(mNativePtr);
     }
 
@@ -686,6 +701,7 @@
      * @hide
      */
     public final boolean isForRpc() {
+        assertNotRecycled();
         return nativeIsForRpc(mNativePtr);
     }
 
@@ -693,21 +709,25 @@
     @ParcelFlags
     @TestApi
     public int getFlags() {
+        assertNotRecycled();
         return mFlags;
     }
 
     /** @hide */
     public void setFlags(@ParcelFlags int flags) {
+        assertNotRecycled();
         mFlags = flags;
     }
 
     /** @hide */
     public void addFlags(@ParcelFlags int flags) {
+        assertNotRecycled();
         mFlags |= flags;
     }
 
     /** @hide */
     private boolean hasFlags(@ParcelFlags int flags) {
+        assertNotRecycled();
         return (mFlags & flags) == flags;
     }
 
@@ -720,6 +740,7 @@
     // We don't really need to protect it; even if 3p / non-system apps, nothing would happen.
     // This would only work when used on a reply parcel by a binder object that's allowed-blocking.
     public void setPropagateAllowBlocking() {
+        assertNotRecycled();
         addFlags(FLAG_PROPAGATE_ALLOW_BLOCKING);
     }
 
@@ -727,6 +748,7 @@
      * Returns the total amount of data contained in the parcel.
      */
     public int dataSize() {
+        assertNotRecycled();
         return nativeDataSize(mNativePtr);
     }
 
@@ -735,6 +757,7 @@
      * parcel.  That is, {@link #dataSize}-{@link #dataPosition}.
      */
     public final int dataAvail() {
+        assertNotRecycled();
         return nativeDataAvail(mNativePtr);
     }
 
@@ -743,6 +766,7 @@
      * more than {@link #dataSize}.
      */
     public final int dataPosition() {
+        assertNotRecycled();
         return nativeDataPosition(mNativePtr);
     }
 
@@ -753,6 +777,7 @@
      * data buffer.
      */
     public final int dataCapacity() {
+        assertNotRecycled();
         return nativeDataCapacity(mNativePtr);
     }
 
@@ -764,6 +789,7 @@
      * @param size The new number of bytes in the Parcel.
      */
     public final void setDataSize(int size) {
+        assertNotRecycled();
         nativeSetDataSize(mNativePtr, size);
     }
 
@@ -773,6 +799,7 @@
      * {@link #dataSize}.
      */
     public final void setDataPosition(int pos) {
+        assertNotRecycled();
         nativeSetDataPosition(mNativePtr, pos);
     }
 
@@ -784,11 +811,13 @@
      * with this method.
      */
     public final void setDataCapacity(int size) {
+        assertNotRecycled();
         nativeSetDataCapacity(mNativePtr, size);
     }
 
     /** @hide */
     public final boolean pushAllowFds(boolean allowFds) {
+        assertNotRecycled();
         return nativePushAllowFds(mNativePtr, allowFds);
     }
 
@@ -809,6 +838,7 @@
      * in different versions of the platform.
      */
     public final byte[] marshall() {
+        assertNotRecycled();
         return nativeMarshall(mNativePtr);
     }
 
@@ -816,15 +846,18 @@
      * Fills the raw bytes of this Parcel with the supplied data.
      */
     public final void unmarshall(@NonNull byte[] data, int offset, int length) {
+        assertNotRecycled();
         nativeUnmarshall(mNativePtr, data, offset, length);
     }
 
     public final void appendFrom(Parcel parcel, int offset, int length) {
+        assertNotRecycled();
         nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length);
     }
 
     /** @hide */
     public int compareData(Parcel other) {
+        assertNotRecycled();
         return nativeCompareData(mNativePtr, other.mNativePtr);
     }
 
@@ -835,6 +868,7 @@
 
     /** @hide */
     public final void setClassCookie(Class clz, Object cookie) {
+        assertNotRecycled();
         if (mClassCookies == null) {
             mClassCookies = new ArrayMap<>();
         }
@@ -844,11 +878,13 @@
     /** @hide */
     @Nullable
     public final Object getClassCookie(Class clz) {
+        assertNotRecycled();
         return mClassCookies != null ? mClassCookies.get(clz) : null;
     }
 
     /** @hide */
     public void removeClassCookie(Class clz, Object expectedCookie) {
+        assertNotRecycled();
         if (mClassCookies != null) {
             Object removedCookie = mClassCookies.remove(clz);
             if (removedCookie != expectedCookie) {
@@ -866,21 +902,25 @@
      * @hide
      */
     public boolean hasClassCookie(Class clz) {
+        assertNotRecycled();
         return mClassCookies != null && mClassCookies.containsKey(clz);
     }
 
     /** @hide */
     public final void adoptClassCookies(Parcel from) {
+        assertNotRecycled();
         mClassCookies = from.mClassCookies;
     }
 
     /** @hide */
     public Map<Class, Object> copyClassCookies() {
+        assertNotRecycled();
         return new ArrayMap<>(mClassCookies);
     }
 
     /** @hide */
     public void putClassCookies(Map<Class, Object> cookies) {
+        assertNotRecycled();
         if (cookies == null) {
             return;
         }
@@ -894,6 +934,7 @@
      * Report whether the parcel contains any marshalled file descriptors.
      */
     public boolean hasFileDescriptors() {
+        assertNotRecycled();
         return nativeHasFileDescriptors(mNativePtr);
     }
 
@@ -909,6 +950,7 @@
      * @throws IllegalArgumentException if the parameters are out of the permitted ranges.
      */
     public boolean hasFileDescriptors(int offset, int length) {
+        assertNotRecycled();
         return nativeHasFileDescriptorsInRange(mNativePtr, offset, length);
     }
 
@@ -993,6 +1035,7 @@
      * @hide
      */
     public boolean hasBinders() {
+        assertNotRecycled();
         return nativeHasBinders(mNativePtr);
     }
 
@@ -1010,6 +1053,7 @@
      * @hide
      */
     public boolean hasBinders(int offset, int length) {
+        assertNotRecycled();
         return nativeHasBindersInRange(mNativePtr, offset, length);
     }
 
@@ -1020,6 +1064,7 @@
      * at the beginning of transactions as a header.
      */
     public final void writeInterfaceToken(@NonNull String interfaceName) {
+        assertNotRecycled();
         nativeWriteInterfaceToken(mNativePtr, interfaceName);
     }
 
@@ -1030,6 +1075,7 @@
      * should propagate to the caller.
      */
     public final void enforceInterface(@NonNull String interfaceName) {
+        assertNotRecycled();
         nativeEnforceInterface(mNativePtr, interfaceName);
     }
 
@@ -1040,6 +1086,7 @@
      * When used over binder, this exception should propagate to the caller.
      */
     public void enforceNoDataAvail() {
+        assertNotRecycled();
         final int n = dataAvail();
         if (n > 0) {
             throw new BadParcelableException("Parcel data not fully consumed, unread size: " + n);
@@ -1056,6 +1103,7 @@
      * @hide
      */
     public boolean replaceCallingWorkSourceUid(int workSourceUid) {
+        assertNotRecycled();
         return nativeReplaceCallingWorkSourceUid(mNativePtr, workSourceUid);
     }
 
@@ -1072,6 +1120,7 @@
      * @hide
      */
     public int readCallingWorkSourceUid() {
+        assertNotRecycled();
         return nativeReadCallingWorkSourceUid(mNativePtr);
     }
 
@@ -1081,6 +1130,7 @@
      * @param b Bytes to place into the parcel.
      */
     public final void writeByteArray(@Nullable byte[] b) {
+        assertNotRecycled();
         writeByteArray(b, 0, (b != null) ? b.length : 0);
     }
 
@@ -1092,6 +1142,7 @@
      * @param len Number of bytes to write.
      */
     public final void writeByteArray(@Nullable byte[] b, int offset, int len) {
+        assertNotRecycled();
         if (b == null) {
             writeInt(-1);
             return;
@@ -1113,6 +1164,7 @@
      * @see #readBlob()
      */
     public final void writeBlob(@Nullable byte[] b) {
+        assertNotRecycled();
         writeBlob(b, 0, (b != null) ? b.length : 0);
     }
 
@@ -1131,6 +1183,7 @@
      * @see #readBlob()
      */
     public final void writeBlob(@Nullable byte[] b, int offset, int len) {
+        assertNotRecycled();
         if (b == null) {
             writeInt(-1);
             return;
@@ -1149,6 +1202,7 @@
      * growing dataCapacity() if needed.
      */
     public final void writeInt(int val) {
+        assertNotRecycled();
         int err = nativeWriteInt(mNativePtr, val);
         if (err != OK) {
             nativeSignalExceptionForError(err);
@@ -1160,6 +1214,7 @@
      * growing dataCapacity() if needed.
      */
     public final void writeLong(long val) {
+        assertNotRecycled();
         int err = nativeWriteLong(mNativePtr, val);
         if (err != OK) {
             nativeSignalExceptionForError(err);
@@ -1171,6 +1226,7 @@
      * dataPosition(), growing dataCapacity() if needed.
      */
     public final void writeFloat(float val) {
+        assertNotRecycled();
         int err = nativeWriteFloat(mNativePtr, val);
         if (err != OK) {
             nativeSignalExceptionForError(err);
@@ -1182,6 +1238,7 @@
      * current dataPosition(), growing dataCapacity() if needed.
      */
     public final void writeDouble(double val) {
+        assertNotRecycled();
         int err = nativeWriteDouble(mNativePtr, val);
         if (err != OK) {
             nativeSignalExceptionForError(err);
@@ -1193,16 +1250,19 @@
      * growing dataCapacity() if needed.
      */
     public final void writeString(@Nullable String val) {
+        assertNotRecycled();
         writeString16(val);
     }
 
     /** {@hide} */
     public final void writeString8(@Nullable String val) {
+        assertNotRecycled();
         mReadWriteHelper.writeString8(this, val);
     }
 
     /** {@hide} */
     public final void writeString16(@Nullable String val) {
+        assertNotRecycled();
         mReadWriteHelper.writeString16(this, val);
     }
 
@@ -1214,16 +1274,19 @@
      * @hide
      */
     public void writeStringNoHelper(@Nullable String val) {
+        assertNotRecycled();
         writeString16NoHelper(val);
     }
 
     /** {@hide} */
     public void writeString8NoHelper(@Nullable String val) {
+        assertNotRecycled();
         nativeWriteString8(mNativePtr, val);
     }
 
     /** {@hide} */
     public void writeString16NoHelper(@Nullable String val) {
+        assertNotRecycled();
         nativeWriteString16(mNativePtr, val);
     }
 
@@ -1235,6 +1298,7 @@
      * for true or false, respectively, but may change in the future.
      */
     public final void writeBoolean(boolean val) {
+        assertNotRecycled();
         writeInt(val ? 1 : 0);
     }
 
@@ -1246,6 +1310,7 @@
     @UnsupportedAppUsage
     @RavenwoodThrow(blockedBy = android.text.Spanned.class)
     public final void writeCharSequence(@Nullable CharSequence val) {
+        assertNotRecycled();
         TextUtils.writeToParcel(val, this, 0);
     }
 
@@ -1254,6 +1319,7 @@
      * growing dataCapacity() if needed.
      */
     public final void writeStrongBinder(IBinder val) {
+        assertNotRecycled();
         nativeWriteStrongBinder(mNativePtr, val);
     }
 
@@ -1262,6 +1328,7 @@
      * growing dataCapacity() if needed.
      */
     public final void writeStrongInterface(IInterface val) {
+        assertNotRecycled();
         writeStrongBinder(val == null ? null : val.asBinder());
     }
 
@@ -1276,6 +1343,7 @@
      * if {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE} is set.</p>
      */
     public final void writeFileDescriptor(@NonNull FileDescriptor val) {
+        assertNotRecycled();
         nativeWriteFileDescriptor(mNativePtr, val);
     }
 
@@ -1284,6 +1352,7 @@
      * This will be the new name for writeFileDescriptor, for consistency.
      **/
     public final void writeRawFileDescriptor(@NonNull FileDescriptor val) {
+        assertNotRecycled();
         nativeWriteFileDescriptor(mNativePtr, val);
     }
 
@@ -1294,6 +1363,7 @@
      * @param value The array of objects to be written.
      */
     public final void writeRawFileDescriptorArray(@Nullable FileDescriptor[] value) {
+        assertNotRecycled();
         if (value != null) {
             int N = value.length;
             writeInt(N);
@@ -1313,6 +1383,7 @@
      * the future.
      */
     public final void writeByte(byte val) {
+        assertNotRecycled();
         writeInt(val);
     }
 
@@ -1328,6 +1399,7 @@
      * allows you to avoid mysterious type errors at the point of marshalling.
      */
     public final void writeMap(@Nullable Map val) {
+        assertNotRecycled();
         writeMapInternal((Map<String, Object>) val);
     }
 
@@ -1336,6 +1408,7 @@
      * growing dataCapacity() if needed.  The Map keys must be String objects.
      */
     /* package */ void writeMapInternal(@Nullable Map<String,Object> val) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -1361,6 +1434,7 @@
      * growing dataCapacity() if needed.  The Map keys must be String objects.
      */
     /* package */ void writeArrayMapInternal(@Nullable ArrayMap<String, Object> val) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -1390,6 +1464,7 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public void writeArrayMap(@Nullable ArrayMap<String, Object> val) {
+        assertNotRecycled();
         writeArrayMapInternal(val);
     }
 
@@ -1408,6 +1483,7 @@
      */
     public <T extends Parcelable> void writeTypedArrayMap(@Nullable ArrayMap<String, T> val,
             int parcelableFlags) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -1429,6 +1505,7 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public void writeArraySet(@Nullable ArraySet<? extends Object> val) {
+        assertNotRecycled();
         final int size = (val != null) ? val.size() : -1;
         writeInt(size);
         for (int i = 0; i < size; i++) {
@@ -1441,6 +1518,7 @@
      * growing dataCapacity() if needed.
      */
     public final void writeBundle(@Nullable Bundle val) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -1454,6 +1532,7 @@
      * growing dataCapacity() if needed.
      */
     public final void writePersistableBundle(@Nullable PersistableBundle val) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -1467,6 +1546,7 @@
      * growing dataCapacity() if needed.
      */
     public final void writeSize(@NonNull Size val) {
+        assertNotRecycled();
         writeInt(val.getWidth());
         writeInt(val.getHeight());
     }
@@ -1476,6 +1556,7 @@
      * growing dataCapacity() if needed.
      */
     public final void writeSizeF(@NonNull SizeF val) {
+        assertNotRecycled();
         writeFloat(val.getWidth());
         writeFloat(val.getHeight());
     }
@@ -1486,6 +1567,7 @@
      * {@link #writeValue} and must follow the specification there.
      */
     public final void writeList(@Nullable List val) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -1505,6 +1587,7 @@
      * {@link #writeValue} and must follow the specification there.
      */
     public final void writeArray(@Nullable Object[] val) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -1525,6 +1608,7 @@
      * specification there.
      */
     public final <T> void writeSparseArray(@Nullable SparseArray<T> val) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -1540,6 +1624,7 @@
     }
 
     public final void writeSparseBooleanArray(@Nullable SparseBooleanArray val) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -1558,6 +1643,7 @@
      * @hide
      */
     public final void writeSparseIntArray(@Nullable SparseIntArray val) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -1573,6 +1659,7 @@
     }
 
     public final void writeBooleanArray(@Nullable boolean[] val) {
+        assertNotRecycled();
         if (val != null) {
             int N = val.length;
             writeInt(N);
@@ -1607,6 +1694,7 @@
     }
 
     private void ensureWithinMemoryLimit(int typeSize, @NonNull int... dimensions) {
+        assertNotRecycled();
         // For Multidimensional arrays, Calculate total object
         // which will be allocated.
         int totalObjects = 1;
@@ -1624,6 +1712,7 @@
     }
 
     private void ensureWithinMemoryLimit(int typeSize, int length) {
+        assertNotRecycled();
         int estimatedAllocationSize = 0;
         try {
             estimatedAllocationSize = Math.multiplyExact(typeSize, length);
@@ -1647,6 +1736,7 @@
 
     @Nullable
     public final boolean[] createBooleanArray() {
+        assertNotRecycled();
         int N = readInt();
         ensureWithinMemoryLimit(SIZE_BOOLEAN, N);
         // >>2 as a fast divide-by-4 works in the create*Array() functions
@@ -1664,6 +1754,7 @@
     }
 
     public final void readBooleanArray(@NonNull boolean[] val) {
+        assertNotRecycled();
         int N = readInt();
         if (N == val.length) {
             for (int i=0; i<N; i++) {
@@ -1676,6 +1767,7 @@
 
     /** @hide */
     public void writeShortArray(@Nullable short[] val) {
+        assertNotRecycled();
         if (val != null) {
             int n = val.length;
             writeInt(n);
@@ -1690,6 +1782,7 @@
     /** @hide */
     @Nullable
     public short[] createShortArray() {
+        assertNotRecycled();
         int n = readInt();
         ensureWithinMemoryLimit(SIZE_SHORT, n);
         if (n >= 0 && n <= (dataAvail() >> 2)) {
@@ -1705,6 +1798,7 @@
 
     /** @hide */
     public void readShortArray(@NonNull short[] val) {
+        assertNotRecycled();
         int n = readInt();
         if (n == val.length) {
             for (int i = 0; i < n; i++) {
@@ -1716,6 +1810,7 @@
     }
 
     public final void writeCharArray(@Nullable char[] val) {
+        assertNotRecycled();
         if (val != null) {
             int N = val.length;
             writeInt(N);
@@ -1729,6 +1824,7 @@
 
     @Nullable
     public final char[] createCharArray() {
+        assertNotRecycled();
         int N = readInt();
         ensureWithinMemoryLimit(SIZE_CHAR, N);
         if (N >= 0 && N <= (dataAvail() >> 2)) {
@@ -1743,6 +1839,7 @@
     }
 
     public final void readCharArray(@NonNull char[] val) {
+        assertNotRecycled();
         int N = readInt();
         if (N == val.length) {
             for (int i=0; i<N; i++) {
@@ -1754,6 +1851,7 @@
     }
 
     public final void writeIntArray(@Nullable int[] val) {
+        assertNotRecycled();
         if (val != null) {
             int N = val.length;
             writeInt(N);
@@ -1767,6 +1865,7 @@
 
     @Nullable
     public final int[] createIntArray() {
+        assertNotRecycled();
         int N = readInt();
         ensureWithinMemoryLimit(SIZE_INT, N);
         if (N >= 0 && N <= (dataAvail() >> 2)) {
@@ -1781,6 +1880,7 @@
     }
 
     public final void readIntArray(@NonNull int[] val) {
+        assertNotRecycled();
         int N = readInt();
         if (N == val.length) {
             for (int i=0; i<N; i++) {
@@ -1792,6 +1892,7 @@
     }
 
     public final void writeLongArray(@Nullable long[] val) {
+        assertNotRecycled();
         if (val != null) {
             int N = val.length;
             writeInt(N);
@@ -1805,6 +1906,7 @@
 
     @Nullable
     public final long[] createLongArray() {
+        assertNotRecycled();
         int N = readInt();
         ensureWithinMemoryLimit(SIZE_LONG, N);
         // >>3 because stored longs are 64 bits
@@ -1820,6 +1922,7 @@
     }
 
     public final void readLongArray(@NonNull long[] val) {
+        assertNotRecycled();
         int N = readInt();
         if (N == val.length) {
             for (int i=0; i<N; i++) {
@@ -1831,6 +1934,7 @@
     }
 
     public final void writeFloatArray(@Nullable float[] val) {
+        assertNotRecycled();
         if (val != null) {
             int N = val.length;
             writeInt(N);
@@ -1844,6 +1948,7 @@
 
     @Nullable
     public final float[] createFloatArray() {
+        assertNotRecycled();
         int N = readInt();
         ensureWithinMemoryLimit(SIZE_FLOAT, N);
         // >>2 because stored floats are 4 bytes
@@ -1859,6 +1964,7 @@
     }
 
     public final void readFloatArray(@NonNull float[] val) {
+        assertNotRecycled();
         int N = readInt();
         if (N == val.length) {
             for (int i=0; i<N; i++) {
@@ -1870,6 +1976,7 @@
     }
 
     public final void writeDoubleArray(@Nullable double[] val) {
+        assertNotRecycled();
         if (val != null) {
             int N = val.length;
             writeInt(N);
@@ -1883,6 +1990,7 @@
 
     @Nullable
     public final double[] createDoubleArray() {
+        assertNotRecycled();
         int N = readInt();
         ensureWithinMemoryLimit(SIZE_DOUBLE, N);
         // >>3 because stored doubles are 8 bytes
@@ -1898,6 +2006,7 @@
     }
 
     public final void readDoubleArray(@NonNull double[] val) {
+        assertNotRecycled();
         int N = readInt();
         if (N == val.length) {
             for (int i=0; i<N; i++) {
@@ -1909,20 +2018,24 @@
     }
 
     public final void writeStringArray(@Nullable String[] val) {
+        assertNotRecycled();
         writeString16Array(val);
     }
 
     @Nullable
     public final String[] createStringArray() {
+        assertNotRecycled();
         return createString16Array();
     }
 
     public final void readStringArray(@NonNull String[] val) {
+        assertNotRecycled();
         readString16Array(val);
     }
 
     /** {@hide} */
     public final void writeString8Array(@Nullable String[] val) {
+        assertNotRecycled();
         if (val != null) {
             int N = val.length;
             writeInt(N);
@@ -1937,6 +2050,7 @@
     /** {@hide} */
     @Nullable
     public final String[] createString8Array() {
+        assertNotRecycled();
         int N = readInt();
         ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N);
         if (N >= 0) {
@@ -1952,6 +2066,7 @@
 
     /** {@hide} */
     public final void readString8Array(@NonNull String[] val) {
+        assertNotRecycled();
         int N = readInt();
         if (N == val.length) {
             for (int i=0; i<N; i++) {
@@ -1964,6 +2079,7 @@
 
     /** {@hide} */
     public final void writeString16Array(@Nullable String[] val) {
+        assertNotRecycled();
         if (val != null) {
             int N = val.length;
             writeInt(N);
@@ -1978,6 +2094,7 @@
     /** {@hide} */
     @Nullable
     public final String[] createString16Array() {
+        assertNotRecycled();
         int N = readInt();
         ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N);
         if (N >= 0) {
@@ -1993,6 +2110,7 @@
 
     /** {@hide} */
     public final void readString16Array(@NonNull String[] val) {
+        assertNotRecycled();
         int N = readInt();
         if (N == val.length) {
             for (int i=0; i<N; i++) {
@@ -2004,6 +2122,7 @@
     }
 
     public final void writeBinderArray(@Nullable IBinder[] val) {
+        assertNotRecycled();
         if (val != null) {
             int N = val.length;
             writeInt(N);
@@ -2028,6 +2147,7 @@
      */
     public final <T extends IInterface> void writeInterfaceArray(
             @SuppressLint("ArrayReturn") @Nullable T[] val) {
+        assertNotRecycled();
         if (val != null) {
             int N = val.length;
             writeInt(N);
@@ -2043,6 +2163,7 @@
      * @hide
      */
     public final void writeCharSequenceArray(@Nullable CharSequence[] val) {
+        assertNotRecycled();
         if (val != null) {
             int N = val.length;
             writeInt(N);
@@ -2058,6 +2179,7 @@
      * @hide
      */
     public final void writeCharSequenceList(@Nullable ArrayList<CharSequence> val) {
+        assertNotRecycled();
         if (val != null) {
             int N = val.size();
             writeInt(N);
@@ -2071,6 +2193,7 @@
 
     @Nullable
     public final IBinder[] createBinderArray() {
+        assertNotRecycled();
         int N = readInt();
         ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N);
         if (N >= 0) {
@@ -2085,6 +2208,7 @@
     }
 
     public final void readBinderArray(@NonNull IBinder[] val) {
+        assertNotRecycled();
         int N = readInt();
         if (N == val.length) {
             for (int i=0; i<N; i++) {
@@ -2106,6 +2230,7 @@
     @Nullable
     public final <T extends IInterface> T[] createInterfaceArray(
             @NonNull IntFunction<T[]> newArray, @NonNull Function<IBinder, T> asInterface) {
+        assertNotRecycled();
         int N = readInt();
         ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N);
         if (N >= 0) {
@@ -2130,6 +2255,7 @@
     public final <T extends IInterface> void readInterfaceArray(
             @SuppressLint("ArrayReturn") @NonNull T[] val,
             @NonNull Function<IBinder, T> asInterface) {
+        assertNotRecycled();
         int N = readInt();
         if (N == val.length) {
             for (int i=0; i<N; i++) {
@@ -2155,6 +2281,7 @@
      * @see Parcelable
      */
     public final <T extends Parcelable> void writeTypedList(@Nullable List<T> val) {
+        assertNotRecycled();
         writeTypedList(val, 0);
     }
 
@@ -2174,6 +2301,7 @@
      */
     public final <T extends Parcelable> void writeTypedSparseArray(@Nullable SparseArray<T> val,
             int parcelableFlags) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -2203,6 +2331,7 @@
      * @see Parcelable
      */
     public <T extends Parcelable> void writeTypedList(@Nullable List<T> val, int parcelableFlags) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -2228,6 +2357,7 @@
      * @see #readStringList
      */
     public final void writeStringList(@Nullable List<String> val) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -2253,6 +2383,7 @@
      * @see #readBinderList
      */
     public final void writeBinderList(@Nullable List<IBinder> val) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -2275,6 +2406,7 @@
      * @see #readInterfaceList
      */
     public final <T extends IInterface> void writeInterfaceList(@Nullable List<T> val) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -2296,6 +2428,7 @@
      * @see #readParcelableList(List, ClassLoader)
      */
     public final <T extends Parcelable> void writeParcelableList(@Nullable List<T> val, int flags) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -2330,6 +2463,7 @@
      */
     public final <T extends Parcelable> void writeTypedArray(@Nullable T[] val,
             int parcelableFlags) {
+        assertNotRecycled();
         if (val != null) {
             int N = val.length;
             writeInt(N);
@@ -2352,6 +2486,7 @@
      */
     public final <T extends Parcelable> void writeTypedObject(@Nullable T val,
             int parcelableFlags) {
+        assertNotRecycled();
         if (val != null) {
             writeInt(1);
             val.writeToParcel(this, parcelableFlags);
@@ -2389,6 +2524,7 @@
      */
     public <T> void writeFixedArray(@Nullable T val, int parcelableFlags,
             @NonNull int... dimensions) {
+        assertNotRecycled();
         if (val == null) {
             writeInt(-1);
             return;
@@ -2500,6 +2636,7 @@
      * should be used).</p>
      */
     public final void writeValue(@Nullable Object v) {
+        assertNotRecycled();
         if (v instanceof LazyValue) {
             LazyValue value = (LazyValue) v;
             value.writeToParcel(this);
@@ -2617,6 +2754,7 @@
      * @hide
      */
     public void writeValue(int type, @Nullable Object v) {
+        assertNotRecycled();
         switch (type) {
             case VAL_NULL:
                 break;
@@ -2730,6 +2868,7 @@
      * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}.
      */
     public final void writeParcelable(@Nullable Parcelable p, int parcelableFlags) {
+        assertNotRecycled();
         if (p == null) {
             writeString(null);
             return;
@@ -2745,6 +2884,7 @@
      * @see #readParcelableCreator
      */
     public final void writeParcelableCreator(@NonNull Parcelable p) {
+        assertNotRecycled();
         String name = p.getClass().getName();
         writeString(name);
     }
@@ -2783,6 +2923,7 @@
      */
     @TestApi
     public boolean allowSquashing() {
+        assertNotRecycled();
         boolean previous = mAllowSquashing;
         mAllowSquashing = true;
         return previous;
@@ -2794,6 +2935,7 @@
      */
     @TestApi
     public void restoreAllowSquashing(boolean previous) {
+        assertNotRecycled();
         mAllowSquashing = previous;
         if (!mAllowSquashing) {
             mWrittenSquashableParcelables = null;
@@ -2850,6 +2992,7 @@
      * @hide
      */
     public boolean maybeWriteSquashed(@NonNull Parcelable p) {
+        assertNotRecycled();
         if (!mAllowSquashing) {
             // Don't squash, and don't put it in the map either.
             writeInt(0);
@@ -2900,6 +3043,7 @@
     @SuppressWarnings("unchecked")
     @Nullable
     public <T extends Parcelable> T readSquashed(SquashReadHelper<T> reader) {
+        assertNotRecycled();
         final int offset = readInt();
         final int pos = dataPosition();
 
@@ -2933,6 +3077,7 @@
      * using the other approaches to writing data in to a Parcel.
      */
     public final void writeSerializable(@Nullable Serializable s) {
+        assertNotRecycled();
         if (s == null) {
             writeString(null);
             return;
@@ -2985,6 +3130,7 @@
      */
     @RavenwoodReplace(blockedBy = AppOpsManager.class)
     public final void writeException(@NonNull Exception e) {
+        assertNotRecycled();
         AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
 
         int code = getExceptionCode(e);
@@ -3065,6 +3211,7 @@
 
     /** @hide */
     public void writeStackTrace(@NonNull Throwable e) {
+        assertNotRecycled();
         final int sizePosition = dataPosition();
         writeInt(0); // Header size will be filled in later
         StackTraceElement[] stackTrace = e.getStackTrace();
@@ -3090,6 +3237,7 @@
      */
     @RavenwoodReplace(blockedBy = AppOpsManager.class)
     public final void writeNoException() {
+        assertNotRecycled();
         AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
 
         // Despite the name of this function ("write no exception"),
@@ -3133,6 +3281,7 @@
      * @see #writeNoException
      */
     public final void readException() {
+        assertNotRecycled();
         int code = readExceptionCode();
         if (code != 0) {
             String msg = readString();
@@ -3156,6 +3305,7 @@
     @UnsupportedAppUsage
     @TestApi
     public final int readExceptionCode() {
+        assertNotRecycled();
         int code = readInt();
         if (code == EX_HAS_NOTED_APPOPS_REPLY_HEADER) {
             AppOpsManager.readAndLogNotedAppops(this);
@@ -3189,6 +3339,7 @@
      * @param msg The exception message.
      */
     public final void readException(int code, String msg) {
+        assertNotRecycled();
         String remoteStackTrace = null;
         final int remoteStackPayloadSize = readInt();
         if (remoteStackPayloadSize > 0) {
@@ -3219,6 +3370,7 @@
 
     /** @hide */
     public Exception createExceptionOrNull(int code, String msg) {
+        assertNotRecycled();
         switch (code) {
             case EX_PARCELABLE:
                 if (readInt() > 0) {
@@ -3251,6 +3403,7 @@
      * Read an integer value from the parcel at the current dataPosition().
      */
     public final int readInt() {
+        assertNotRecycled();
         return nativeReadInt(mNativePtr);
     }
 
@@ -3258,6 +3411,7 @@
      * Read a long integer value from the parcel at the current dataPosition().
      */
     public final long readLong() {
+        assertNotRecycled();
         return nativeReadLong(mNativePtr);
     }
 
@@ -3266,6 +3420,7 @@
      * dataPosition().
      */
     public final float readFloat() {
+        assertNotRecycled();
         return nativeReadFloat(mNativePtr);
     }
 
@@ -3274,6 +3429,7 @@
      * current dataPosition().
      */
     public final double readDouble() {
+        assertNotRecycled();
         return nativeReadDouble(mNativePtr);
     }
 
@@ -3282,16 +3438,19 @@
      */
     @Nullable
     public final String readString() {
+        assertNotRecycled();
         return readString16();
     }
 
     /** {@hide} */
     public final @Nullable String readString8() {
+        assertNotRecycled();
         return mReadWriteHelper.readString8(this);
     }
 
     /** {@hide} */
     public final @Nullable String readString16() {
+        assertNotRecycled();
         return mReadWriteHelper.readString16(this);
     }
 
@@ -3303,16 +3462,19 @@
      * @hide
      */
     public @Nullable String readStringNoHelper() {
+        assertNotRecycled();
         return readString16NoHelper();
     }
 
     /** {@hide} */
     public @Nullable String readString8NoHelper() {
+        assertNotRecycled();
         return nativeReadString8(mNativePtr);
     }
 
     /** {@hide} */
     public @Nullable String readString16NoHelper() {
+        assertNotRecycled();
         return nativeReadString16(mNativePtr);
     }
 
@@ -3320,6 +3482,7 @@
      * Read a boolean value from the parcel at the current dataPosition().
      */
     public final boolean readBoolean() {
+        assertNotRecycled();
         return readInt() != 0;
     }
 
@@ -3330,6 +3493,7 @@
     @UnsupportedAppUsage
     @Nullable
     public final CharSequence readCharSequence() {
+        assertNotRecycled();
         return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(this);
     }
 
@@ -3337,6 +3501,7 @@
      * Read an object from the parcel at the current dataPosition().
      */
     public final IBinder readStrongBinder() {
+        assertNotRecycled();
         final IBinder result = nativeReadStrongBinder(mNativePtr);
 
         // If it's a reply from a method with @PropagateAllowBlocking, then inherit allow-blocking
@@ -3352,6 +3517,7 @@
      * Read a FileDescriptor from the parcel at the current dataPosition().
      */
     public final ParcelFileDescriptor readFileDescriptor() {
+        assertNotRecycled();
         FileDescriptor fd = nativeReadFileDescriptor(mNativePtr);
         return fd != null ? new ParcelFileDescriptor(fd) : null;
     }
@@ -3359,6 +3525,7 @@
     /** {@hide} */
     @UnsupportedAppUsage
     public final FileDescriptor readRawFileDescriptor() {
+        assertNotRecycled();
         return nativeReadFileDescriptor(mNativePtr);
     }
 
@@ -3369,6 +3536,7 @@
      **/
     @Nullable
     public final FileDescriptor[] createRawFileDescriptorArray() {
+        assertNotRecycled();
         int N = readInt();
         if (N < 0) {
             return null;
@@ -3388,6 +3556,7 @@
      * @return the FileDescriptor array, or null if the array is null.
      **/
     public final void readRawFileDescriptorArray(FileDescriptor[] val) {
+        assertNotRecycled();
         int N = readInt();
         if (N == val.length) {
             for (int i=0; i<N; i++) {
@@ -3402,6 +3571,7 @@
      * Read a byte value from the parcel at the current dataPosition().
      */
     public final byte readByte() {
+        assertNotRecycled();
         return (byte)(readInt() & 0xff);
     }
 
@@ -3416,6 +3586,7 @@
      */
     @Deprecated
     public final void readMap(@NonNull Map outVal, @Nullable ClassLoader loader) {
+        assertNotRecycled();
         readMapInternal(outVal, loader, /* clazzKey */ null, /* clazzValue */ null);
     }
 
@@ -3429,6 +3600,7 @@
     public <K, V> void readMap(@NonNull Map<? super K, ? super V> outVal,
             @Nullable ClassLoader loader, @NonNull Class<K> clazzKey,
             @NonNull Class<V> clazzValue) {
+        assertNotRecycled();
         Objects.requireNonNull(clazzKey);
         Objects.requireNonNull(clazzValue);
         readMapInternal(outVal, loader, clazzKey, clazzValue);
@@ -3447,6 +3619,7 @@
      */
     @Deprecated
     public final void readList(@NonNull List outVal, @Nullable ClassLoader loader) {
+        assertNotRecycled();
         int N = readInt();
         readListInternal(outVal, N, loader, /* clazz */ null);
     }
@@ -3468,6 +3641,7 @@
      */
     public <T> void readList(@NonNull List<? super T> outVal,
             @Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+        assertNotRecycled();
         Objects.requireNonNull(clazz);
         int n = readInt();
         readListInternal(outVal, n, loader, clazz);
@@ -3487,6 +3661,7 @@
     @Deprecated
     @Nullable
     public HashMap readHashMap(@Nullable ClassLoader loader) {
+        assertNotRecycled();
         return readHashMapInternal(loader, /* clazzKey */ null, /* clazzValue */ null);
     }
 
@@ -3501,6 +3676,7 @@
     @Nullable
     public <K, V> HashMap<K, V> readHashMap(@Nullable ClassLoader loader,
             @NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) {
+        assertNotRecycled();
         Objects.requireNonNull(clazzKey);
         Objects.requireNonNull(clazzValue);
         return readHashMapInternal(loader, clazzKey, clazzValue);
@@ -3513,6 +3689,7 @@
      */
     @Nullable
     public final Bundle readBundle() {
+        assertNotRecycled();
         return readBundle(null);
     }
 
@@ -3524,6 +3701,7 @@
      */
     @Nullable
     public final Bundle readBundle(@Nullable ClassLoader loader) {
+        assertNotRecycled();
         int length = readInt();
         if (length < 0) {
             if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length);
@@ -3544,6 +3722,7 @@
      */
     @Nullable
     public final PersistableBundle readPersistableBundle() {
+        assertNotRecycled();
         return readPersistableBundle(null);
     }
 
@@ -3555,6 +3734,7 @@
      */
     @Nullable
     public final PersistableBundle readPersistableBundle(@Nullable ClassLoader loader) {
+        assertNotRecycled();
         int length = readInt();
         if (length < 0) {
             if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length);
@@ -3573,6 +3753,7 @@
      */
     @NonNull
     public final Size readSize() {
+        assertNotRecycled();
         final int width = readInt();
         final int height = readInt();
         return new Size(width, height);
@@ -3583,6 +3764,7 @@
      */
     @NonNull
     public final SizeF readSizeF() {
+        assertNotRecycled();
         final float width = readFloat();
         final float height = readFloat();
         return new SizeF(width, height);
@@ -3593,6 +3775,7 @@
      */
     @Nullable
     public final byte[] createByteArray() {
+        assertNotRecycled();
         return nativeCreateByteArray(mNativePtr);
     }
 
@@ -3601,6 +3784,7 @@
      * given byte array.
      */
     public final void readByteArray(@NonNull byte[] val) {
+        assertNotRecycled();
         boolean valid = nativeReadByteArray(mNativePtr, val, (val != null) ? val.length : 0);
         if (!valid) {
             throw new RuntimeException("bad array lengths");
@@ -3613,6 +3797,7 @@
      */
     @Nullable
     public final byte[] readBlob() {
+        assertNotRecycled();
         return nativeReadBlob(mNativePtr);
     }
 
@@ -3623,6 +3808,7 @@
     @UnsupportedAppUsage
     @Nullable
     public final String[] readStringArray() {
+        assertNotRecycled();
         return createString16Array();
     }
 
@@ -3632,6 +3818,7 @@
      */
     @Nullable
     public final CharSequence[] readCharSequenceArray() {
+        assertNotRecycled();
         CharSequence[] array = null;
 
         int length = readInt();
@@ -3654,6 +3841,7 @@
      */
     @Nullable
     public final ArrayList<CharSequence> readCharSequenceList() {
+        assertNotRecycled();
         ArrayList<CharSequence> array = null;
 
         int length = readInt();
@@ -3683,6 +3871,7 @@
     @Deprecated
     @Nullable
     public ArrayList readArrayList(@Nullable ClassLoader loader) {
+        assertNotRecycled();
         return readArrayListInternal(loader, /* clazz */ null);
     }
 
@@ -3705,6 +3894,7 @@
     @Nullable
     public <T> ArrayList<T> readArrayList(@Nullable ClassLoader loader,
             @NonNull Class<? extends T> clazz) {
+        assertNotRecycled();
         Objects.requireNonNull(clazz);
         return readArrayListInternal(loader, clazz);
     }
@@ -3724,6 +3914,7 @@
     @Deprecated
     @Nullable
     public Object[] readArray(@Nullable ClassLoader loader) {
+        assertNotRecycled();
         return readArrayInternal(loader, /* clazz */ null);
     }
 
@@ -3745,6 +3936,7 @@
     @SuppressLint({"ArrayReturn", "NullableCollection"})
     @Nullable
     public <T> T[] readArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+        assertNotRecycled();
         Objects.requireNonNull(clazz);
         return readArrayInternal(loader, clazz);
     }
@@ -3764,6 +3956,7 @@
     @Deprecated
     @Nullable
     public <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader) {
+        assertNotRecycled();
         return readSparseArrayInternal(loader, /* clazz */ null);
     }
 
@@ -3785,6 +3978,7 @@
     @Nullable
     public <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader,
             @NonNull Class<? extends T> clazz) {
+        assertNotRecycled();
         Objects.requireNonNull(clazz);
         return readSparseArrayInternal(loader, clazz);
     }
@@ -3796,6 +3990,7 @@
      */
     @Nullable
     public final SparseBooleanArray readSparseBooleanArray() {
+        assertNotRecycled();
         int N = readInt();
         if (N < 0) {
             return null;
@@ -3812,6 +4007,7 @@
      */
     @Nullable
     public final SparseIntArray readSparseIntArray() {
+        assertNotRecycled();
         int N = readInt();
         if (N < 0) {
             return null;
@@ -3836,6 +4032,7 @@
      */
     @Nullable
     public final <T> ArrayList<T> createTypedArrayList(@NonNull Parcelable.Creator<T> c) {
+        assertNotRecycled();
         int N = readInt();
         if (N < 0) {
             return null;
@@ -3859,6 +4056,7 @@
      * @see #writeTypedList
      */
     public final <T> void readTypedList(@NonNull List<T> list, @NonNull Parcelable.Creator<T> c) {
+        assertNotRecycled();
         int M = list.size();
         int N = readInt();
         int i = 0;
@@ -3888,6 +4086,7 @@
      */
     public final @Nullable <T extends Parcelable> SparseArray<T> createTypedSparseArray(
             @NonNull Parcelable.Creator<T> creator) {
+        assertNotRecycled();
         final int count = readInt();
         if (count < 0) {
             return null;
@@ -3917,6 +4116,7 @@
      */
     public final @Nullable <T extends Parcelable> ArrayMap<String, T> createTypedArrayMap(
             @NonNull Parcelable.Creator<T> creator) {
+        assertNotRecycled();
         final int count = readInt();
         if (count < 0) {
             return null;
@@ -3944,6 +4144,7 @@
      */
     @Nullable
     public final ArrayList<String> createStringArrayList() {
+        assertNotRecycled();
         int N = readInt();
         if (N < 0) {
             return null;
@@ -3970,6 +4171,7 @@
      */
     @Nullable
     public final ArrayList<IBinder> createBinderArrayList() {
+        assertNotRecycled();
         int N = readInt();
         if (N < 0) {
             return null;
@@ -3997,6 +4199,7 @@
     @Nullable
     public final <T extends IInterface> ArrayList<T> createInterfaceArrayList(
             @NonNull Function<IBinder, T> asInterface) {
+        assertNotRecycled();
         int N = readInt();
         if (N < 0) {
             return null;
@@ -4017,6 +4220,7 @@
      * @see #writeStringList
      */
     public final void readStringList(@NonNull List<String> list) {
+        assertNotRecycled();
         int M = list.size();
         int N = readInt();
         int i = 0;
@@ -4038,6 +4242,7 @@
      * @see #writeBinderList
      */
     public final void readBinderList(@NonNull List<IBinder> list) {
+        assertNotRecycled();
         int M = list.size();
         int N = readInt();
         int i = 0;
@@ -4060,6 +4265,7 @@
      */
     public final <T extends IInterface> void readInterfaceList(@NonNull List<T> list,
             @NonNull Function<IBinder, T> asInterface) {
+        assertNotRecycled();
         int M = list.size();
         int N = readInt();
         int i = 0;
@@ -4091,6 +4297,7 @@
     @NonNull
     public final <T extends Parcelable> List<T> readParcelableList(@NonNull List<T> list,
             @Nullable ClassLoader cl) {
+        assertNotRecycled();
         return readParcelableListInternal(list, cl, /*clazz*/ null);
     }
 
@@ -4112,6 +4319,7 @@
     @NonNull
     public <T> List<T> readParcelableList(@NonNull List<T> list,
             @Nullable ClassLoader cl, @NonNull Class<? extends T> clazz) {
+        assertNotRecycled();
         Objects.requireNonNull(list);
         Objects.requireNonNull(clazz);
         return readParcelableListInternal(list, cl, clazz);
@@ -4157,6 +4365,7 @@
      */
     @Nullable
     public final <T> T[] createTypedArray(@NonNull Parcelable.Creator<T> c) {
+        assertNotRecycled();
         int N = readInt();
         if (N < 0) {
             return null;
@@ -4170,6 +4379,7 @@
     }
 
     public final <T> void readTypedArray(@NonNull T[] val, @NonNull Parcelable.Creator<T> c) {
+        assertNotRecycled();
         int N = readInt();
         if (N == val.length) {
             for (int i=0; i<N; i++) {
@@ -4186,6 +4396,7 @@
      */
     @Deprecated
     public final <T> T[] readTypedArray(Parcelable.Creator<T> c) {
+        assertNotRecycled();
         return createTypedArray(c);
     }
 
@@ -4202,6 +4413,7 @@
      */
     @Nullable
     public final <T> T readTypedObject(@NonNull Parcelable.Creator<T> c) {
+        assertNotRecycled();
         if (readInt() != 0) {
             return c.createFromParcel(this);
         } else {
@@ -4228,6 +4440,7 @@
      * @see #readTypedArray
      */
     public <T> void readFixedArray(@NonNull T val) {
+        assertNotRecycled();
         Class<?> componentType = val.getClass().getComponentType();
         if (componentType == boolean.class) {
             readBooleanArray((boolean[]) val);
@@ -4268,6 +4481,7 @@
      */
     public <T, S extends IInterface> void readFixedArray(@NonNull T val,
             @NonNull Function<IBinder, S> asInterface) {
+        assertNotRecycled();
         Class<?> componentType = val.getClass().getComponentType();
         if (IInterface.class.isAssignableFrom(componentType)) {
             readInterfaceArray((S[]) val, asInterface);
@@ -4294,6 +4508,7 @@
      */
     public <T, S extends Parcelable> void readFixedArray(@NonNull T val,
             @NonNull Parcelable.Creator<S> c) {
+        assertNotRecycled();
         Class<?> componentType = val.getClass().getComponentType();
         if (Parcelable.class.isAssignableFrom(componentType)) {
             readTypedArray((S[]) val, c);
@@ -4351,6 +4566,7 @@
      */
     @Nullable
     public <T> T createFixedArray(@NonNull Class<T> cls, @NonNull int... dimensions) {
+        assertNotRecycled();
         // Check if type matches with dimensions
         // If type is one-dimensional array, delegate to other creators
         // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
@@ -4424,6 +4640,7 @@
     @Nullable
     public <T, S extends IInterface> T createFixedArray(@NonNull Class<T> cls,
             @NonNull Function<IBinder, S> asInterface, @NonNull int... dimensions) {
+        assertNotRecycled();
         // Check if type matches with dimensions
         // If type is one-dimensional array, delegate to other creators
         // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
@@ -4484,6 +4701,7 @@
     @Nullable
     public <T, S extends Parcelable> T createFixedArray(@NonNull Class<T> cls,
             @NonNull Parcelable.Creator<S> c, @NonNull int... dimensions) {
+        assertNotRecycled();
         // Check if type matches with dimensions
         // If type is one-dimensional array, delegate to other creators
         // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray
@@ -4547,6 +4765,7 @@
      */
     public final <T extends Parcelable> void writeParcelableArray(@Nullable T[] value,
             int parcelableFlags) {
+        assertNotRecycled();
         if (value != null) {
             int N = value.length;
             writeInt(N);
@@ -4565,6 +4784,7 @@
      */
     @Nullable
     public final Object readValue(@Nullable ClassLoader loader) {
+        assertNotRecycled();
         return readValue(loader, /* clazz */ null);
     }
 
@@ -4620,6 +4840,7 @@
      */
     @Nullable
     public Object readLazyValue(@Nullable ClassLoader loader) {
+        assertNotRecycled();
         int start = dataPosition();
         int type = readInt();
         if (isLengthPrefixed(type)) {
@@ -5022,6 +5243,7 @@
     @Deprecated
     @Nullable
     public final <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader) {
+        assertNotRecycled();
         return readParcelableInternal(loader, /* clazz */ null);
     }
 
@@ -5041,6 +5263,7 @@
      */
     @Nullable
     public <T> T readParcelable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+        assertNotRecycled();
         Objects.requireNonNull(clazz);
         return readParcelableInternal(loader, clazz);
     }
@@ -5069,6 +5292,7 @@
     @Nullable
     public final <T extends Parcelable> T readCreator(@NonNull Parcelable.Creator<?> creator,
             @Nullable ClassLoader loader) {
+        assertNotRecycled();
         if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
           Parcelable.ClassLoaderCreator<?> classLoaderCreator =
               (Parcelable.ClassLoaderCreator<?>) creator;
@@ -5096,6 +5320,7 @@
     @Deprecated
     @Nullable
     public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) {
+        assertNotRecycled();
         return readParcelableCreatorInternal(loader, /* clazz */ null);
     }
 
@@ -5116,6 +5341,7 @@
     @Nullable
     public <T> Parcelable.Creator<T> readParcelableCreator(
             @Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+        assertNotRecycled();
         Objects.requireNonNull(clazz);
         return readParcelableCreatorInternal(loader, clazz);
     }
@@ -5238,6 +5464,7 @@
     @Deprecated
     @Nullable
     public Parcelable[] readParcelableArray(@Nullable ClassLoader loader) {
+        assertNotRecycled();
         return readParcelableArrayInternal(loader, /* clazz */ null);
     }
 
@@ -5258,6 +5485,7 @@
     @SuppressLint({"ArrayReturn", "NullableCollection"})
     @Nullable
     public <T> T[] readParcelableArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+        assertNotRecycled();
         return readParcelableArrayInternal(loader, requireNonNull(clazz));
     }
 
@@ -5291,6 +5519,7 @@
     @Deprecated
     @Nullable
     public Serializable readSerializable() {
+        assertNotRecycled();
         return readSerializableInternal(/* loader */ null, /* clazz */ null);
     }
 
@@ -5307,6 +5536,7 @@
      */
     @Nullable
     public <T> T readSerializable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+        assertNotRecycled();
         Objects.requireNonNull(clazz);
         return readSerializableInternal(
                 loader == null ? getClass().getClassLoader() : loader, clazz);
@@ -5548,6 +5778,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public void readArrayMap(@NonNull ArrayMap<? super String, Object> outVal,
             @Nullable ClassLoader loader) {
+        assertNotRecycled();
         final int N = readInt();
         if (N < 0) {
             return;
@@ -5564,6 +5795,7 @@
      */
     @UnsupportedAppUsage
     public @Nullable ArraySet<? extends Object> readArraySet(@Nullable ClassLoader loader) {
+        assertNotRecycled();
         final int size = readInt();
         if (size < 0) {
             return null;
@@ -5703,6 +5935,7 @@
      * @hide For testing
      */
     public long getOpenAshmemSize() {
+        assertNotRecycled();
         return nativeGetOpenAshmemSize(mNativePtr);
     }
 
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index d116e07..4db1f1b 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -15,7 +15,6 @@
  */
 package android.os;
 
-import static android.os.BatteryConsumer.BatteryConsumerDataLayout.POWER_MODEL_NOT_INCLUDED;
 import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
 import static android.os.BatteryConsumer.POWER_COMPONENT_BASE;
 import static android.os.BatteryConsumer.POWER_STATE_ANY;
@@ -156,15 +155,6 @@
         return mData.layout.getPowerComponentName(componentId);
     }
 
-    @BatteryConsumer.PowerModel
-    int getPowerModel(BatteryConsumer.Key key) {
-        if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
-            throw new IllegalStateException(
-                    "Power model IDs were not requested in the BatteryUsageStatsQuery");
-        }
-        return mData.getInt(key.mPowerModelColumnIndex);
-    }
-
     /**
      * Returns the amount of time used by the specified component, e.g. CPU, WiFi etc.
      *
@@ -378,10 +368,6 @@
             if (durationMs != 0) {
                 serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
             }
-            if (mData.layout.powerModelsIncluded) {
-                serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL,
-                        getPowerModel(key));
-            }
             serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
         }
         serializer.endTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
@@ -411,7 +397,6 @@
                         int powerState = POWER_STATE_UNSPECIFIED;
                         double powerMah = 0;
                         long durationMs = 0;
-                        int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
                         for (int i = 0; i < parser.getAttributeCount(); i++) {
                             switch (parser.getAttributeName(i)) {
                                 case BatteryUsageStats.XML_ATTR_ID:
@@ -432,14 +417,11 @@
                                 case BatteryUsageStats.XML_ATTR_DURATION:
                                     durationMs = parser.getAttributeLong(i);
                                     break;
-                                case BatteryUsageStats.XML_ATTR_MODEL:
-                                    model = parser.getAttributeInt(i);
-                                    break;
                             }
                         }
                         final BatteryConsumer.Key key = builder.mData.layout.getKey(componentId,
                                 processState, screenState, powerState);
-                        builder.addConsumedPower(key, powerMah, model);
+                        builder.addConsumedPower(key, powerMah);
                         builder.addUsageDurationMillis(key, durationMs);
                         break;
                     }
@@ -453,43 +435,28 @@
      * Builder for PowerComponents.
      */
     static final class Builder {
-        private static final byte POWER_MODEL_UNINITIALIZED = -1;
-
         private final BatteryConsumer.BatteryConsumerData mData;
         private final double mMinConsumedPowerThreshold;
 
         Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold) {
             mData = data;
             mMinConsumedPowerThreshold = minConsumedPowerThreshold;
-            for (BatteryConsumer.Key key : mData.layout.keys) {
-                if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
-                    mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED);
-                }
-            }
         }
 
         /**
-         * @deprecated use {@link #addConsumedPower(BatteryConsumer.Key, double, int)}
+         * @deprecated use {@link #addConsumedPower(BatteryConsumer.Key, double)}
          */
         @Deprecated
         @NonNull
-        public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower,
-                int powerModel) {
+        public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower) {
             mData.putDouble(key.mPowerColumnIndex, componentPower);
-            if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
-                mData.putInt(key.mPowerModelColumnIndex, powerModel);
-            }
             return this;
         }
 
         @NonNull
-        public Builder addConsumedPower(BatteryConsumer.Key key, double componentPower,
-                int powerModel) {
+        public Builder addConsumedPower(BatteryConsumer.Key key, double componentPower) {
             mData.putDouble(key.mPowerColumnIndex,
                     mData.getDouble(key.mPowerColumnIndex) + componentPower);
-            if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
-                mData.putInt(key.mPowerModelColumnIndex, powerModel);
-            }
             return this;
         }
 
@@ -547,28 +514,6 @@
                             mData.getLong(key.mDurationColumnIndex)
                                     + otherData.getLong(otherKey.mDurationColumnIndex));
                 }
-                if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
-                    continue;
-                }
-
-                boolean undefined = false;
-                if (otherKey.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
-                    undefined = true;
-                } else {
-                    final int powerModel = mData.getInt(key.mPowerModelColumnIndex);
-                    int otherPowerModel = otherData.getInt(otherKey.mPowerModelColumnIndex);
-                    if (powerModel == POWER_MODEL_UNINITIALIZED) {
-                        mData.putInt(key.mPowerModelColumnIndex, otherPowerModel);
-                    } else if (powerModel != otherPowerModel
-                            && otherPowerModel != POWER_MODEL_UNINITIALIZED) {
-                        undefined = true;
-                    }
-                }
-
-                if (undefined) {
-                    mData.putInt(key.mPowerModelColumnIndex,
-                            BatteryConsumer.POWER_MODEL_UNDEFINED);
-                }
             }
         }
 
@@ -594,13 +539,6 @@
         @NonNull
         public PowerComponents build() {
             for (BatteryConsumer.Key key : mData.layout.keys) {
-                if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
-                    if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) {
-                        mData.putInt(key.mPowerModelColumnIndex,
-                                BatteryConsumer.POWER_MODEL_UNDEFINED);
-                    }
-                }
-
                 if (mMinConsumedPowerThreshold != 0) {
                     if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) {
                         mData.putDouble(key.mPowerColumnIndex, 0);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 9e7bf47..cd48f08 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1266,9 +1266,17 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public float getBrightnessConstraint(int constraint) {
+    public float getBrightnessConstraint(@BrightnessConstraint int constraint) {
+        return getBrightnessConstraint(Display.DEFAULT_DISPLAY, constraint);
+    }
+
+    /**
+     * Gets a float screen brightness setting for a specific display.
+     * @hide
+     */
+    public float getBrightnessConstraint(int displayId, @BrightnessConstraint int constraint) {
         try {
-            return mService.getBrightnessConstraint(constraint);
+            return mService.getBrightnessConstraint(displayId, constraint);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/os/SELinux.java b/core/java/android/os/SELinux.java
index f64a811..11c54ef 100644
--- a/core/java/android/os/SELinux.java
+++ b/core/java/android/os/SELinux.java
@@ -193,4 +193,31 @@
             return false;
         }
     }
+
+    /**
+     * Gets the genfs labels version of the vendor. The genfs labels version is
+     * specified in {@code /vendor/etc/selinux/genfs_labels_version.txt}. The
+     * version follows the VINTF version format "YYYYMM" and affects how {@code
+     * genfs_contexts} entries are applied.
+     *
+     * <p>The genfs labels version indicates changes in the SELinux labeling
+     * scheme over time. For example:
+     * <ul>
+     *   <li>For version 202504 and later, {@code /sys/class/udc} is labeled as
+     *   {@code sysfs_udc}.
+     *   <li>For version 202404 and earlier, {@code /sys/class/udc} is labeled
+     *   as {@code sysfs}.
+     * </ul>
+     * Check {@code /system/etc/selinux/plat_sepolicy_genfs_{version}.cil} to
+     * see which labels are new in {version}.
+     *
+     * <p>Older vendors may override {@code genfs_contexts} with vendor-specific
+     * extensions. The framework must not break such labellings to maintain
+     * compatibility with such vendors, by checking the genfs labels version and
+     * implementing a fallback mechanism.
+     *
+     * @return an integer representing the genfs labels version of /vendor, in
+     *         the format YYYYMM.
+     */
+    public static final native int getGenfsLabelsVersion();
 }
diff --git a/core/java/android/os/SessionCreationConfig.aidl b/core/java/android/os/SessionCreationConfig.aidl
index cdc0ef4..17147e4 100644
--- a/core/java/android/os/SessionCreationConfig.aidl
+++ b/core/java/android/os/SessionCreationConfig.aidl
@@ -36,4 +36,12 @@
      * List of the modes to be enabled upon session creation.
      */
     SessionMode[] modesToEnable;
+
+    /**
+     * List of layers to attach this session to.
+     *
+     * Note: DO NOT STORE THESE IN HintSessionManager, as
+     * it will break the layer lifecycle.
+     */
+    IBinder[] layerTokens;
 }
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index 0a8f62f..81e4549 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
@@ -667,4 +668,23 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Run postinstall script for specified partition |partition|
+     *
+     * @param partition The partition to trigger postinstall runs
+     *
+     * @throws ServiceSpecificException error code of this exception would be one of
+     * https://cs.android.com/android/platform/superproject/main/+/main:system/update_engine/common/error_code.h
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_UPDATE_ENGINE_API)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public void triggerPostinstall(@NonNull String partition) {
+        try {
+            mUpdateEngine.triggerPostinstall(partition);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a1ede5f..7e73a5d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3846,7 +3846,7 @@
     }
 
     /**
-     * Return the time when the context user was unlocked elapsed milliseconds since boot,
+     * Return the time when the calling user was unlocked elapsed milliseconds since boot,
      * or 0 if not unlocked.
      *
      * @hide
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index e5340a7..6357baa 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -96,6 +96,7 @@
     namespace: "crumpet"
     description: "Allow privileged apps to call bugreport generation without enforcing user consent and delegate it to the calling app instead"
     bug: "324046728"
+    is_exported: true
 }
 
 # This flag guards the private space feature, its APIs, and some of the feature implementations. The flag android.multiuser.Flags.enable_private_space_features exclusively guards all the implementations.
@@ -178,6 +179,7 @@
     namespace: "game"
     description: "Feature flag for adding CPU/GPU headroom API"
     bug: "346604998"
+    is_exported: true
 }
 
 flag {
@@ -223,6 +225,14 @@
 }
 
 flag {
+    name: "material_motion_tokens"
+    namespace: "systemui"
+    description: "Adding new Material Tokens for M3 Motion Spec"
+    bug: "324922198"
+    is_exported: true
+}
+
+flag {
     name: "message_queue_tail_tracking"
     namespace: "system_performance"
     description: "track tail of message queue."
@@ -231,6 +241,15 @@
 }
 
 flag {
+     name: "message_queue_testability"
+     namespace: "system_performance"
+     is_exported: true
+     description: "Whether MessageQueue implements test APIs."
+     bug: "379472827"
+     is_fixed_read_only: true
+}
+
+flag {
     name: "network_time_uses_shared_memory"
     namespace: "system_performance"
     description: "SystemClock.currentNetworkTimeMillis() reads network time offset from shared memory"
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index 4db9bc3..cd79e41 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -23,6 +23,8 @@
 import android.annotation.SystemService;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.hardware.power.CpuHeadroomResult;
+import android.hardware.power.GpuHeadroomResult;
 import android.os.BatteryStats;
 import android.os.Build;
 import android.os.Bundle;
@@ -110,15 +112,16 @@
     }
 
     /**
-     * Provides an estimate of global available CPU headroom of the calling thread.
+     * Provides an estimate of global available CPU headroom.
      * <p>
      *
      * @param  params params to customize the CPU headroom calculation, null to use default params.
-     * @return a single value a {@code Float.NaN} if it's temporarily unavailable.
+     * @return a single value headroom or a {@code Float.NaN} if it's temporarily unavailable.
      *         A valid value is ranged from [0, 100], where 0 indicates no more CPU resources can be
      *         granted.
-     * @throws UnsupportedOperationException if the API is unsupported or the request params can't
-     *         be served.
+     * @throws UnsupportedOperationException if the API is unsupported.
+     * @throws SecurityException if the TIDs of the params don't belong to the same process.
+     * @throws IllegalStateException if the TIDs of the params don't have the same affinity setting.
      */
     @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS)
     public @FloatRange(from = 0f, to = 100f) float getCpuHeadroom(
@@ -127,8 +130,12 @@
             throw new UnsupportedOperationException();
         }
         try {
-            return mHintManager.getCpuHeadroom(
-                    params != null ? params.getInternal() : new CpuHeadroomParamsInternal())[0];
+            final CpuHeadroomResult ret = mHintManager.getCpuHeadroom(
+                    params != null ? params.getInternal() : new CpuHeadroomParamsInternal());
+            if (ret == null || ret.getTag() != CpuHeadroomResult.globalHeadroom) {
+                return Float.NaN;
+            }
+            return ret.getGlobalHeadroom();
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -144,8 +151,7 @@
      * @return a single value headroom or a {@code Float.NaN} if it's temporarily unavailable.
      *         A valid value is ranged from [0, 100], where 0 indicates no more GPU resources can be
      *         granted.
-     * @throws UnsupportedOperationException if the API is unsupported or the request params can't
-     *         be served.
+     * @throws UnsupportedOperationException if the API is unsupported.
      */
     @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS)
     public @FloatRange(from = 0f, to = 100f) float getGpuHeadroom(
@@ -154,8 +160,12 @@
             throw new UnsupportedOperationException();
         }
         try {
-            return mHintManager.getGpuHeadroom(
+            final GpuHeadroomResult ret = mHintManager.getGpuHeadroom(
                     params != null ? params.getInternal() : new GpuHeadroomParamsInternal());
+            if (ret == null || ret.getTag() != GpuHeadroomResult.globalHeadroom) {
+                return Float.NaN;
+            }
+            return ret.getGlobalHeadroom();
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 55ba4af..57e1b58 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -58,7 +58,7 @@
     is_fixed_read_only: true
     namespace: "permissions"
     description: "enable enhanced confirmation incall apis"
-    bug: "310220212"
+    bug: "364535720"
 }
 
 flag {
@@ -67,7 +67,7 @@
     is_fixed_read_only: true
     namespace: "permissions"
     description: "enable the blocking of certain app installs during an unknown call"
-    bug: "310220212"
+    bug: "364535720"
 }
 
 flag {
@@ -213,6 +213,7 @@
     namespace: "permissions"
     description: "Enable getDeviceId API in OpEventProxyInfo"
     bug: "337340961"
+    is_exported: true
 }
 
 flag {
@@ -254,6 +255,7 @@
   namespace: "permissions"
   description: "New setOnOpNotedCallback API to allow subscribing to only sync ops."
   bug: "372910217"
+  is_exported: true
 }
 
 flag {
@@ -436,3 +438,48 @@
     description: "Use profile labels from UserManager for default app section titles to allow partner customization"
     bug: "358369931"
 }
+
+flag {
+    name: "wallet_role_cross_user_enabled"
+    is_exported: true
+    is_fixed_read_only: true
+    namespace: "wallet_integration"
+    description: "Enable the Wallet role within profiles"
+    bug: "356107987"
+}
+
+flag {
+    name: "text_classifier_choice_api_enabled"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "permissions"
+    description: "API change to enable getTextClassifier by type"
+    bug: "377229653"
+}
+
+flag {
+    name: "updatable_text_classifier_for_otp_detection_enabled"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "permissions"
+    description: "Enables text classifier for OTP detection that is updatable from mainline module"
+    bug: "377229653"
+}
+
+flag {
+    name: "cross_user_role_platform_api_enabled"
+    is_exported: true
+    is_fixed_read_only: true
+    namespace: "permissions"
+    description: "Enable cross-user roles platform API"
+    bug: "367732307"
+}
+
+flag {
+    name: "rate_limit_batched_note_op_async_callbacks_enabled"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "permissions"
+    description: "Rate limit async noteOp callbacks for batched noteOperation binder call"
+    bug: "366013082"
+}
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index f6bdc18..e4a3c9f 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -16,6 +16,8 @@
 
 package android.preference;
 
+import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
+
 import android.animation.LayoutTransition;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
@@ -54,6 +56,8 @@
 import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.TextView;
+import android.window.OnBackInvokedCallback;
+import android.window.WindowOnBackInvokedDispatcher;
 
 import com.android.internal.util.XmlUtils;
 
@@ -209,6 +213,8 @@
     private int mPreferenceHeaderItemResId = 0;
     private boolean mPreferenceHeaderRemoveEmptyIcon = false;
 
+    private final OnBackInvokedCallback mOnBackInvokedCallback = this::onBackInvoked;
+
     /**
      * The starting request code given out to preference framework.
      */
@@ -699,10 +705,26 @@
                 skipButton.setVisibility(View.VISIBLE);
             }
         }
+        updateBackCallbackRegistrationState();
     }
 
     @Override
     public void onBackPressed() {
+        onBackInvoked();
+    }
+
+    private void updateBackCallbackRegistrationState() {
+        if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)) return;
+        if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0
+                && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) {
+            getOnBackInvokedDispatcher()
+                    .registerOnBackInvokedCallback(PRIORITY_DEFAULT, mOnBackInvokedCallback);
+        } else {
+            getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+        }
+    }
+
+    private void onBackInvoked() {
         if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0
                 && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) {
             mCurHeader = null;
@@ -713,9 +735,10 @@
                 showBreadCrumbs(mActivityTitle, null);
             }
             getListView().clearChoices();
-        } else {
+        } else if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)) {
             super.onBackPressed();
         }
+        updateBackCallbackRegistrationState();
     }
 
     /**
@@ -1221,6 +1244,7 @@
             getListView().clearChoices();
         }
         showBreadCrumbs(header);
+        updateBackCallbackRegistrationState();
     }
 
     void showBreadCrumbs(Header header) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d5b5258..0ae9ffa 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -85,6 +85,7 @@
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.service.voice.VisualQueryDetectedResult;
 import android.speech.tts.TextToSpeech;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -6289,6 +6290,13 @@
         public static final String TOUCHPAD_RIGHT_CLICK_ZONE = "touchpad_right_click_zone";
 
         /**
+         * Whether to enable system gestures (three- and four-finger swipes) on touchpads.
+         *
+         * @hide
+         */
+        public static final String TOUCHPAD_SYSTEM_GESTURES = "touchpad_system_gestures";
+
+        /**
          * Whether to enable reversed vertical scrolling for connected mice.
          *
          * When enabled, scrolling down on the mouse wheel will move the screen up and vice versa.
@@ -6374,6 +6382,14 @@
         public static final String LOCALE_PREFERENCES = "locale_preferences";
 
         /**
+         * User can change the region from region settings. This records user's preferred region.
+         *
+         * E.g. : if user's locale is en-US, this will record US
+         * @hide
+         */
+        public static final String PREFERRED_REGION = "preferred_region";
+
+        /**
          * Setting to enable camera flash notification feature.
          * <ul>
          *     <li> 0 = Off
@@ -6541,12 +6557,14 @@
             PRIVATE_SETTINGS.add(TOUCHPAD_TAP_TO_CLICK);
             PRIVATE_SETTINGS.add(TOUCHPAD_TAP_DRAGGING);
             PRIVATE_SETTINGS.add(TOUCHPAD_RIGHT_CLICK_ZONE);
+            PRIVATE_SETTINGS.add(TOUCHPAD_SYSTEM_GESTURES);
             PRIVATE_SETTINGS.add(CAMERA_FLASH_NOTIFICATION);
             PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
             PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
             PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE);
             PRIVATE_SETTINGS.add(MOUSE_REVERSE_VERTICAL_SCROLLING);
             PRIVATE_SETTINGS.add(MOUSE_SWAP_PRIMARY_BUTTON);
+            PRIVATE_SETTINGS.add(PREFERRED_REGION);
         }
 
         /**
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
index 0302faf..59628e8 100644
--- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
@@ -16,7 +16,10 @@
 
 package android.security.advancedprotection;
 
+import static android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.os.UserManager.DISALLOW_CELLULAR_2G;
+import static android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
@@ -343,6 +346,28 @@
         return intent;
     }
 
+    /** @hide */
+    public @NonNull Intent createSupportIntentForPolicyIdentifierOrRestriction(
+            @NonNull String identifier, @Nullable @SupportDialogType String type) {
+        Objects.requireNonNull(identifier);
+        if (type != null && !ALL_SUPPORT_DIALOG_TYPES.contains(type)) {
+            throw new IllegalArgumentException(type + " is not a valid type. See"
+                    + " SUPPORT_DIALOG_TYPE_* APIs.");
+        }
+        final String featureId;
+        if (DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY.equals(identifier)) {
+            featureId = FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES;
+        } else if (DISALLOW_CELLULAR_2G.equals(identifier)) {
+            featureId = FEATURE_ID_DISALLOW_CELLULAR_2G;
+        } else if (android.app.admin.flags.Flags.setMtePolicyCoexistence() && MEMORY_TAGGING_POLICY
+                .equals(identifier)) {
+            featureId = FEATURE_ID_ENABLE_MTE;
+        } else {
+            throw new UnsupportedOperationException("Unsupported identifier: " + identifier);
+        }
+        return createSupportIntent(featureId, type);
+    }
+
     /**
      * A callback class for monitoring changes to Advanced Protection state
      *
diff --git a/core/java/android/security/advancedprotection/OWNERS b/core/java/android/security/advancedprotection/OWNERS
index ddac8ed..bfb7e16 100644
--- a/core/java/android/security/advancedprotection/OWNERS
+++ b/core/java/android/security/advancedprotection/OWNERS
@@ -2,7 +2,6 @@
 
 achim@google.com
 azharaa@google.com
-cpinelli@google.com
 eranm@google.com
 hanikazmi@google.com
 haok@google.com
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 09004b3..34bae46 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -112,6 +112,7 @@
     namespace: "hardware_backed_security"
     description: "AFL feature"
     bug: "365994454"
+    is_exported: true
 }
 
 flag {
@@ -127,6 +128,7 @@
     namespace: "hardware_backed_security"
     description: "Feature flag for exposing KeyStore grant APIs"
     bug: "351158708"
+    is_exported: true
 }
 
 flag {
@@ -134,4 +136,5 @@
     namespace: "biometrics"
     description: "Feature flag for Secure Lockdown feature"
     bug: "373422357"
+    is_exported: true
 }
\ No newline at end of file
diff --git a/core/java/android/security/intrusiondetection/IIntrusionDetectionEventTransport.aidl b/core/java/android/security/intrusiondetection/IIntrusionDetectionEventTransport.aidl
index 8759f72..eab1c15 100644
--- a/core/java/android/security/intrusiondetection/IIntrusionDetectionEventTransport.aidl
+++ b/core/java/android/security/intrusiondetection/IIntrusionDetectionEventTransport.aidl
@@ -15,6 +15,7 @@
  */
 
 package android.security.intrusiondetection;
+
 import android.security.intrusiondetection.IntrusionDetectionEvent;
 
 import com.android.internal.infra.AndroidFuture;
@@ -24,18 +25,20 @@
     /**
      * Initialize the server side.
      */
-    void initialize(in AndroidFuture<int> resultFuture);
+    void initialize(in AndroidFuture<boolean> resultFuture);
 
     /**
-     * Send intrusiondetection logging data to the backup destination.
+     * Send intrusiondetection logging data to the transport destination.
      * The data is a list of IntrusionDetectionEvent.
      * The IntrusionDetectionEvent is an abstract class that represents
-     * different type of events.
+     * different types of events.
      */
-    void addData(in List<IntrusionDetectionEvent> events, in AndroidFuture<int> resultFuture);
+    void addData(
+        in List<IntrusionDetectionEvent> events,
+        in AndroidFuture<boolean> resultFuture);
 
     /**
      * Release the binder to the server.
      */
-    void release(in AndroidFuture<int> resultFuture);
+    void release(in AndroidFuture<boolean> resultFuture);
 }
diff --git a/core/java/android/security/intrusiondetection/IntrusionDetectionEvent.java b/core/java/android/security/intrusiondetection/IntrusionDetectionEvent.java
index 538acf9..b479ca7 100644
--- a/core/java/android/security/intrusiondetection/IntrusionDetectionEvent.java
+++ b/core/java/android/security/intrusiondetection/IntrusionDetectionEvent.java
@@ -22,6 +22,7 @@
 import android.app.admin.ConnectEvent;
 import android.app.admin.DnsEvent;
 import android.app.admin.SecurityLog.SecurityEvent;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.security.Flags;
@@ -31,14 +32,36 @@
 
 /**
  * A class that represents a intrusiondetection event.
+ *
  * @hide
  */
+@SystemApi
 @FlaggedApi(Flags.FLAG_AFL_API)
 public final class IntrusionDetectionEvent implements Parcelable {
     private static final String TAG = "IntrusionDetectionEvent";
 
+    /**
+     * Event type representing a security-related event.
+     * This type is associated with a {@link SecurityEvent} object.
+     *
+     * @see SecurityEvent
+     */
     public static final int SECURITY_EVENT = 0;
+
+    /**
+     * Event type representing a network DNS event.
+     * This type is associated with a {@link DnsEvent} object.
+     *
+     * @see DnsEvent
+     */
     public static final int NETWORK_EVENT_DNS = 1;
+
+    /**
+     * Event type representing a network connection event.
+     * This type is associated with a {@link ConnectEvent} object.
+     *
+     * @see ConnectEvent
+     */
     public static final int NETWORK_EVENT_CONNECT = 2;
 
     /** @hide */
@@ -67,6 +90,12 @@
                 }
             };
 
+    /**
+     * Creates an IntrusionDetectionEvent object with a
+     * {@link SecurityEvent} object as the event source.
+     *
+     * @param securityEvent The SecurityEvent object.
+     */
     public IntrusionDetectionEvent(@NonNull SecurityEvent securityEvent) {
         mType = SECURITY_EVENT;
         mSecurityEvent = securityEvent;
@@ -74,6 +103,12 @@
         mNetworkEventConnect = null;
     }
 
+    /**
+     * Creates an IntrusionDetectionEvent object with a
+     * {@link DnsEvent} object as the event source.
+     *
+     * @param dnsEvent The DnsEvent object.
+     */
     public IntrusionDetectionEvent(@NonNull DnsEvent dnsEvent) {
         mType = NETWORK_EVENT_DNS;
         mNetworkEventDns = dnsEvent;
@@ -81,6 +116,12 @@
         mNetworkEventConnect = null;
     }
 
+    /**
+     * Creates an IntrusionDetectionEvent object with a
+     * {@link ConnectEvent} object as the event source.
+     *
+     * @param connectEvent The ConnectEvent object.
+     */
     public IntrusionDetectionEvent(@NonNull ConnectEvent connectEvent) {
         mType = NETWORK_EVENT_CONNECT;
         mNetworkEventConnect = connectEvent;
diff --git a/core/java/android/security/intrusiondetection/IntrusionDetectionEventTransport.java b/core/java/android/security/intrusiondetection/IntrusionDetectionEventTransport.java
new file mode 100644
index 0000000..2e2d0f7
--- /dev/null
+++ b/core/java/android/security/intrusiondetection/IntrusionDetectionEventTransport.java
@@ -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 android.security.intrusiondetection;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.util.CloseGuard;
+
+import android.security.Flags;
+import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.security.intrusiondetection.IIntrusionDetectionEventTransport;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.lang.AutoCloseable;
+import java.util.List;
+
+/**
+ * A class that provides a stable API for transporting intrusion detection events
+ * to a transport location, such as a file or a network endpoint.
+ *
+ * This class acts as a bridge between the {@link IIntrusionDetectionEventTransport}
+ * interface and its implementations. It allows system components to add intrusion
+ * detection events ({@link IntrusionDetectionEvent}) to a transport queue,
+ * which will then be delivered to the specified location.
+ *
+ * Usage:
+ * 1. Obtain an instance of {@link IntrusionDetectionEventTransport} using the constructor.
+ * 2. Initialize the transport by calling {@link #initialize()}.
+ * 3. Add events to the transport queue using {@link #addData(List)}.
+ * 4. Release the transport when finished by calling {@link #release()}.
+ *
+ * Key Components:
+ * - {@link IIntrusionDetectionEventTransport}: The underlying AIDL interface
+ *     for interacting with transport implementations.
+ * - {@link IntrusionDetectionEvent}: Represents a single event.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_AFL_API)
+@SuppressLint("NotCloseable")
+public class IntrusionDetectionEventTransport {
+    IIntrusionDetectionEventTransport mBinderImpl = new TransportImpl();
+
+    /**
+     * Returns the binder interface for this transport.
+     */
+    @NonNull
+    public IBinder getBinder() {
+        return mBinderImpl.asBinder();
+    }
+
+    /**
+     * Initializes the transport.
+     *
+     * @return whether the initialization was successful.
+     */
+    public boolean initialize() {
+        return false;
+    }
+
+    /**
+     * Adds data to the transport.
+     *
+     * @param events the events to add.
+     * @return whether the addition was successful.
+     */
+    public boolean addData(@NonNull List<IntrusionDetectionEvent> events) {
+        return false;
+    }
+
+    /**
+     * Releases the transport.
+     *
+     * The release() method is a callback implemented by the concrete transport
+     * endpoint.
+     * The "SuppressLint" annotation is used to allow the release() method to be
+     * included in the API without requiring the class to implement AutoCloseable.
+     *
+     * @return whether the release was successful.
+     */
+    public boolean release() {
+        return false;
+    }
+
+    /**
+     * Bridge between the actual IIntrusionDetectionEventTransport implementation
+     * and the stable API.  If the binder interface needs to change, we use this
+     * layer to translate so that we can decouple those framework-side changes
+     * from the IntrusionDetectionEventTransport implementations.
+     */
+    class TransportImpl extends IIntrusionDetectionEventTransport.Stub {
+        @Override
+        public void initialize(AndroidFuture<Boolean> resultFuture) {
+            try {
+                boolean result = IntrusionDetectionEventTransport.this.initialize();
+                resultFuture.complete(result);
+            } catch (RuntimeException e) {
+                resultFuture.cancel(/* mayInterruptIfRunning */ true);
+            }
+        }
+
+        @Override
+        public void addData(
+            List<IntrusionDetectionEvent> events,
+            AndroidFuture<Boolean> resultFuture) {
+            try {
+                boolean result = IntrusionDetectionEventTransport.this.addData(events);
+                resultFuture.complete(result);
+            } catch (RuntimeException e) {
+                resultFuture.cancel(/* mayInterruptIfRunning */ true);
+            }
+        }
+
+        @Override
+        public void release(AndroidFuture<Boolean> resultFuture) {
+            try {
+                boolean result = IntrusionDetectionEventTransport.this.release();
+                resultFuture.complete(result);
+            } catch (RuntimeException e) {
+                resultFuture.cancel(/* mayInterruptIfRunning */ true);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 6c92991..42dbd37 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -78,6 +78,7 @@
     description: "Prevent intent redirect attacks"
     bug: "361143368"
     is_fixed_read_only: true
+    is_exported: true
 }
 
 flag {
@@ -96,6 +97,36 @@
 }
 
 flag {
+    name: "prevent_intent_redirect_show_toast_if_nested_keys_not_collected"
+    namespace: "responsible_apis"
+    description: "Prevent intent redirect attacks by showing a toast if not yet collected"
+    bug: "361143368"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "prevent_intent_redirect_show_toast_if_nested_keys_not_collected_r_w"
+    namespace: "responsible_apis"
+    description: "Prevent intent redirect attacks by showing a toast if not yet collected"
+    bug: "361143368"
+}
+
+flag {
+    name: "prevent_intent_redirect_throw_exception_if_nested_keys_not_collected"
+    namespace: "responsible_apis"
+    description: "Prevent intent redirect attacks by throwing exception if the intent does not collect nested keys"
+    bug: "361143368"
+}
+
+flag {
+    name: "prevent_intent_redirect_collect_nested_keys_on_server_if_not_collected"
+    namespace: "responsible_apis"
+    description: "Prevent intent redirect attacks by collecting nested keys on server if not yet collected"
+    bug: "361143368"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "enable_intent_matching_flags"
     is_exported: true
     namespace: "permissions"
@@ -110,3 +141,16 @@
     description: "Android Advanced Protection Mode Feature: Disable Install Unknown Sources"
     bug: "369361373"
 }
+
+flag {
+    name: "aapm_feature_memory_tagging_extension"
+    namespace: "responsible_apis"
+    description: "Android Advanced Protection Mode Feature: Memory Tagging Extension"
+    bug: "378931989"
+}
+flag {
+    name: "aapm_feature_disable_cellular_2g"
+    namespace: "responsible_apis"
+    description: "Android Advanced Protection Mode Feature: Disable Cellular 2G"
+    bug: "377748286"
+}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 269839b..2d922b4 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -15,9 +15,12 @@
  */
 package android.service.autofill;
 
+import static android.service.autofill.Flags.FLAG_AUTOFILL_SESSION_DESTROYED;
+
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
@@ -669,6 +672,14 @@
                     AutofillService.this,
                     new SavedDatasetsInfoCallbackImpl(receiver, SavedDatasetsInfo.TYPE_PASSWORDS)));
         }
+
+        @Override
+        public void onSessionDestroyed(@Nullable FillEventHistory history) {
+            mHandler.sendMessage(obtainMessage(
+                    AutofillService::onSessionDestroyed,
+                    AutofillService.this,
+                    history));
+        }
     };
 
     private Handler mHandler;
@@ -783,26 +794,42 @@
     }
 
     /**
-     * Gets the events that happened after the last
-     * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
+     * Called when an Autofill context has ended and the Autofill session is finished. This will be
+     * called as the last step of the Autofill lifecycle described in {@link AutofillManager}.
+     *
+     * <p>This will also contain the finished Session's FillEventHistory, so providers do not need
+     * to explicitly call {@link #getFillEventHistory()}
+     *
+     * <p>This will usually happens whenever {@link AutofillManager#commit()} or {@link
+     * AutofillManager#cancel()} is called.
+     */
+    @FlaggedApi(FLAG_AUTOFILL_SESSION_DESTROYED)
+    public void onSessionDestroyed(@Nullable FillEventHistory history) {}
+
+    /**
+     * Gets the events that happened after the last {@link
+     * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
      * call.
      *
      * <p>This method is typically used to keep track of previous user actions to optimize further
      * requests. For example, the service might return email addresses in alphabetical order by
      * default, but change that order based on the address the user picked on previous requests.
      *
-     * <p>The history is not persisted over reboots, and it's cleared every time the service
-     * replies to a {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} by calling
-     * {@link FillCallback#onSuccess(FillResponse)} or {@link FillCallback#onFailure(CharSequence)}
-     * (if the service doesn't call any of these methods, the history will clear out after some
-     * pre-defined time). Hence, the service should call {@link #getFillEventHistory()} before
-     * finishing the {@link FillCallback}.
+     * <p>The history is not persisted over reboots, and it's cleared every time the service replies
+     * to a {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} by calling {@link
+     * FillCallback#onSuccess(FillResponse)} or {@link FillCallback#onFailure(CharSequence)} (if the
+     * service doesn't call any of these methods, the history will clear out after some pre-defined
+     * time). Hence, the service should call {@link #getFillEventHistory()} before finishing the
+     * {@link FillCallback}.
      *
      * @return The history or {@code null} if there are no events.
-     *
      * @throws RuntimeException if the event history could not be retrieved.
+     * @deprecated Use {@link #onSessionDestroyed(FillEventHistory) instead}
      */
-    @Nullable public final FillEventHistory getFillEventHistory() {
+    @FlaggedApi(FLAG_AUTOFILL_SESSION_DESTROYED)
+    @Deprecated
+    @Nullable
+    public final FillEventHistory getFillEventHistory() {
         final AutofillManager afm = getSystemService(AutofillManager.class);
 
         if (afm == null) {
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 14a14e6..fba8e42 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -170,6 +170,9 @@
                 }
                 parcel.writeInt(event.mSaveDialogNotShowReason);
                 parcel.writeInt(event.mUiType);
+                if (Flags.addLastFocusedIdToFillEventHistory()) {
+                    parcel.writeParcelable(event.mFocusedId, 0);
+                }
             }
         }
     }
@@ -375,6 +378,8 @@
         @UiType
         private final int mUiType;
 
+        @Nullable private final AutofillId mFocusedId;
+
         /**
          * Returns the type of the event.
          *
@@ -388,7 +393,7 @@
         @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
         @Nullable
         public AutofillId getFocusedId() {
-            return null;
+            return mFocusedId;
         }
 
         /**
@@ -624,6 +629,7 @@
          * @param manuallyFilledDatasetIds The ids of datasets that had values matching the
          * respective entry on {@code manuallyFilledFieldIds}.
          * @param detectedFieldClassifications the field classification matches.
+         * @param focusedId the field which was focused at the time of event trigger
          *
          * @throws IllegalArgumentException If the length of {@code changedFieldIds} and
          * {@code changedDatasetIds} doesn't match.
@@ -640,11 +646,12 @@
                 @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
                 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
                 @Nullable AutofillId[] detectedFieldIds,
-                @Nullable FieldClassification[] detectedFieldClassifications) {
+                @Nullable FieldClassification[] detectedFieldClassifications,
+                @Nullable AutofillId focusedId) {
             this(eventType, datasetId, clientState, selectedDatasetIds, ignoredDatasetIds,
                     changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
                     manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
-                    NO_SAVE_UI_REASON_NONE);
+                    NO_SAVE_UI_REASON_NONE, focusedId);
         }
 
         /**
@@ -665,6 +672,7 @@
          * respective entry on {@code manuallyFilledFieldIds}.
          * @param detectedFieldClassifications the field classification matches.
          * @param saveDialogNotShowReason The reason why a save dialog was not shown.
+         * @param focusedId the field which was focused at the time of event trigger
          *
          * @throws IllegalArgumentException If the length of {@code changedFieldIds} and
          * {@code changedDatasetIds} doesn't match.
@@ -682,11 +690,12 @@
                 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
                 @Nullable AutofillId[] detectedFieldIds,
                 @Nullable FieldClassification[] detectedFieldClassifications,
-                int saveDialogNotShowReason) {
+                int saveDialogNotShowReason,
+                @Nullable AutofillId focusedId) {
             this(eventType, datasetId, clientState, selectedDatasetIds, ignoredDatasetIds,
                     changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
                     manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
-                    saveDialogNotShowReason, UI_TYPE_UNKNOWN);
+                    saveDialogNotShowReason, UI_TYPE_UNKNOWN, focusedId);
         }
 
         /**
@@ -708,6 +717,7 @@
          * @param detectedFieldClassifications the field classification matches.
          * @param saveDialogNotShowReason The reason why a save dialog was not shown.
          * @param uiType The ui presentation type for fill suggestion.
+         * @param focusedId the field which was focused at the time of event trigger
          *
          * @throws IllegalArgumentException If the length of {@code changedFieldIds} and
          * {@code changedDatasetIds} doesn't match.
@@ -725,7 +735,7 @@
                 @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
                 @Nullable AutofillId[] detectedFieldIds,
                 @Nullable FieldClassification[] detectedFieldClassifications,
-                int saveDialogNotShowReason, int uiType) {
+                int saveDialogNotShowReason, int uiType, @Nullable AutofillId focusedId) {
             mEventType = Preconditions.checkArgumentInRange(eventType, 0,
                     TYPE_VIEW_REQUESTED_AUTOFILL, "eventType");
             mDatasetId = datasetId;
@@ -756,6 +766,7 @@
                     NO_SAVE_UI_REASON_NONE, NO_SAVE_UI_REASON_DATASET_MATCH,
                     "saveDialogNotShowReason");
             mUiType = uiType;
+            mFocusedId = focusedId;
         }
 
         @Override
@@ -852,13 +863,17 @@
                                 : null;
                         final int saveDialogNotShowReason = parcel.readInt();
                         final int uiType = parcel.readInt();
+                        AutofillId focusedId = null;
+                        if (Flags.addLastFocusedIdToFillEventHistory()) {
+                            focusedId = parcel.readParcelable(null, AutofillId.class);
+                        }
 
                         selection.addEvent(new Event(eventType, datasetId, clientState,
                                 selectedDatasetIds, ignoredDatasets,
                                 changedFieldIds, changedDatasetIds,
                                 manuallyFilledFieldIds, manuallyFilledDatasetIds,
                                 detectedFieldIds, detectedFieldClassifications,
-                                saveDialogNotShowReason, uiType));
+                                saveDialogNotShowReason, uiType, focusedId));
                     }
                     return selection;
                 }
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index 3b64b8a..71b75e7 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -25,6 +25,8 @@
 import android.service.autofill.SaveRequest;
 import com.android.internal.os.IResultReceiver;
 
+parcelable FillEventHistory;
+
 /**
  * Interface from the system to an auto fill service.
  *
@@ -38,4 +40,5 @@
     void onSaveRequest(in SaveRequest request, in ISaveCallback callback);
     void onSavedPasswordCountRequest(in IResultReceiver receiver);
     void onConvertCredentialRequest(in ConvertCredentialRequest convertCredentialRequest, in IConvertCredentialCallback convertCredentialCallback);
+    void onSessionDestroyed(in FillEventHistory history);
 }
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 5d0ec73..7256907 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -2363,7 +2363,6 @@
         // -- parcelable interface --
 
         private RankingMap(Parcel in) {
-            final ClassLoader cl = getClass().getClassLoader();
             final int count = in.readInt();
             mOrderedKeys.ensureCapacity(count);
             mRankings.ensureCapacity(count);
diff --git a/core/java/android/service/quickaccesswallet/flags.aconfig b/core/java/android/service/quickaccesswallet/flags.aconfig
index 75a9309..9736345 100644
--- a/core/java/android/service/quickaccesswallet/flags.aconfig
+++ b/core/java/android/service/quickaccesswallet/flags.aconfig
@@ -6,4 +6,12 @@
     namespace: "wallet_integration"
     description: "Option to launch the Wallet app on double-tap of the power button"
     bug: "378469025"
+    is_exported: true
+}
+
+flag {
+    name: "launch_selected_card_from_qs_tile"
+    namespace: "wallet_integration"
+    description: "When the wallet QS tile is tapped, launch the selected card pending intent instead of the home screen pending intent."
+    bug: "378469025"
 }
\ No newline at end of file
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 7d79fd3..68fd115 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1541,7 +1541,7 @@
                 mInternalCallback,
                 new RecognitionConfig.Builder()
                     .setCaptureRequested(captureTriggerAudio)
-                    .setAllowMultipleTriggers(allowMultipleTriggers)
+                    .setMultipleTriggersAllowed(allowMultipleTriggers)
                     .setKeyphrases(recognitionExtra)
                     .setData(data)
                     .setAudioCapabilities(audioCapabilities)
diff --git a/core/java/android/service/voice/OWNERS b/core/java/android/service/voice/OWNERS
index 5f9f6bd..b6f0270 100644
--- a/core/java/android/service/voice/OWNERS
+++ b/core/java/android/service/voice/OWNERS
@@ -1,6 +1,6 @@
 # Bug component: 533220
-
 include /core/java/android/app/assist/OWNERS
+atneya@google.com
 
 # The owner here should not be assist owner
 adudani@google.com
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 2c585e6..e8b32ce 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -658,12 +658,15 @@
      *
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
     public static final int EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED = 42;
 
     /**
      * Event for listening to changes in carrier roaming non-terrestrial network eligibility.
      *
-     * @see CarrierRoamingNtnModeListener
+     * @see CarrierRoamingNtnModeListener#onCarrierRoamingNtnEligibleStateChanged(boolean)
      *
      * Device is eligible for satellite communication if all the following conditions are met:
      * <ul>
@@ -679,11 +682,15 @@
      *
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
     public static final int EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED = 43;
 
     /**
      * Event for listening to changes in carrier roaming non-terrestrial network available services
-     * via callback onCarrierRoamingNtnAvailableServicesChanged().
+     * via callback {@link
+     * CarrierRoamingNtnModeListener#onCarrierRoamingNtnAvailableServicesChanged(List)}
      * This callback is triggered when the available services provided by the carrier roaming
      * satellite changes. The carrier roaming satellite is defined by the following conditions.
      * <ul>
@@ -693,15 +700,22 @@
      *
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
     public static final int EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED = 44;
 
     /**
      * Event for listening to carrier roaming non-terrestrial network signal strength changes.
      *
-     * @see CarrierRoamingNtnModeListener
+     * @see CarrierRoamingNtnModeListener#onCarrierRoamingNtnSignalStrengthChanged(
+     * NtnSignalStrength)
      *
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
     public static final int EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED = 45;
 
     /**
@@ -1803,6 +1817,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public interface CarrierRoamingNtnModeListener {
         /**
          * Callback invoked when carrier roaming non-terrestrial network mode changes.
@@ -1836,10 +1852,10 @@
          * Callback invoked when carrier roaming non-terrestrial network available
          * service changes.
          *
-         * @param availableServices The list of the supported services.
+         * @param availableServices array of supported services.
          */
         default void onCarrierRoamingNtnAvailableServicesChanged(
-                @NetworkRegistrationInfo.ServiceType List<Integer> availableServices) {}
+                @NonNull @NetworkRegistrationInfo.ServiceType int[] availableServices) {}
 
         /**
          * Callback invoked when carrier roaming non-terrestrial network signal strength changes.
@@ -2343,10 +2359,8 @@
                     (CarrierRoamingNtnModeListener) mTelephonyCallbackWeakRef.get();
             if (listener == null) return;
 
-            List<Integer> ServiceList = Arrays.stream(availableServices).boxed()
-                    .collect(Collectors.toList());
             Binder.withCleanCallingIdentity(() -> mExecutor.execute(
-                    () -> listener.onCarrierRoamingNtnAvailableServicesChanged(ServiceList)));
+                    () -> listener.onCarrierRoamingNtnAvailableServicesChanged(availableServices)));
         }
 
         public void onCarrierRoamingNtnSignalStrengthChanged(
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f43f172..c2e542c 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -91,6 +91,7 @@
   metadata {
     purpose: PURPOSE_BUGFIX
   }
+  is_exported: true
 }
 
 flag {
@@ -196,6 +197,7 @@
   namespace: "text"
   description: "Feature flag for adding a TYPE_DURATION to TtsSpan"
   bug: "337103893"
+  is_exported: true
 }
 
 flag {
@@ -203,6 +205,7 @@
   namespace: "text"
   description: "Deprecate the Paint#elegantTextHeight API and stick it to true"
   bug: "349519475"
+  is_exported: true
 }
 
 flag {
@@ -210,4 +213,5 @@
   namespace: "text"
   description: "Make Paint class work for vertical layout text."
   bug: "355296926"
+  is_exported: true
 }
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 1dd9d46..f8737a5 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -75,7 +75,7 @@
 @android.ravenwood.annotation.RavenwoodClassLoadHook(
         "com.android.platform.test.ravenwood.runtimehelper.ClassLoadHook.onClassLoaded")
 // Uncomment the following annotation to switch to the Java substitution version.
-@android.ravenwood.annotation.RavenwoodRedirectionClass("Log_host")
+@android.ravenwood.annotation.RavenwoodRedirectionClass("Log_ravenwood")
 public final class Log {
     /** @hide */
     @IntDef({ASSERT, ERROR, WARN, INFO, DEBUG, VERBOSE})
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 09a78e5..c9d560c 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
 import static android.hardware.flags.Flags.FLAG_OVERLAYPROPERTIES_CLASS_API;
 
+import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_GET_SUPPORTED_REFRESH_RATES;
 import static com.android.server.display.feature.flags.Flags.FLAG_HIGHEST_HDR_SDR_RATIO_API;
 import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_HAS_ARR_SUPPORT;
 import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_GET_SUGGESTED_FRAME_RATE;
@@ -63,6 +64,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -1207,17 +1209,36 @@
 
     /**
      * Get the supported refresh rates of this display in frames per second.
-     * <p>
-     * This method only returns refresh rates for the display's default modes. For more options, use
-     * {@link #getSupportedModes()}.
      *
-     * @deprecated use {@link #getSupportedModes()} instead
+     * <ul>
+     * <li> Android version {@link Build.VERSION_CODES#BAKLAVA} and above:
+     * returns display supported render rates.
+     * <li> Android version {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and below:
+     * This method only returns refresh rates for the display's default modes. For more options,
+     * use {@link #getSupportedModes()}.
+     * </ul>
      */
-    @Deprecated
-    public float[] getSupportedRefreshRates() {
+    @FlaggedApi(FLAG_ENABLE_GET_SUPPORTED_REFRESH_RATES)
+    public @NonNull float[] getSupportedRefreshRates() {
         synchronized (mLock) {
             updateDisplayInfoLocked();
-            return mDisplayInfo.getDefaultRefreshRates();
+            final float[] refreshRates = mDisplayInfo.getDefaultRefreshRates();
+            Objects.requireNonNull(refreshRates);
+            return refreshRates;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @SuppressLint({"UnflaggedApi"}) // Usage in the CTS to test backward compatibility.
+    public @NonNull float[] getSupportedRefreshRatesLegacy() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            final float[] refreshRates = mDisplayInfo.getDefaultRefreshRatesLegacy();
+            Objects.requireNonNull(refreshRates);
+            return refreshRates;
         }
     }
 
@@ -2449,6 +2470,8 @@
          * constrained by the system.
          * @hide
          */
+        @SuppressWarnings("UnflaggedApi") // For testing only
+        @TestApi
         public float getVsyncRate() {
             return mVsyncRate;
         }
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index bb233d2..3ff5f95 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -286,7 +286,7 @@
      * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
      * timebase.
      * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
-     * @param modeId The new mode Id
+     * @param modeId The new mode ID
      * @param renderPeriod The render frame period, which is a multiple of the mode's vsync period
      */
     public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
@@ -294,6 +294,15 @@
     }
 
     /**
+     * Called when a display mode rejection event is received.
+     *
+     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
+     * @param modeId The mode ID of the mode that was rejected
+     */
+    public void onModeRejected(long physicalDisplayId, int modeId) {
+    }
+
+    /**
      * Called when a display hdcp levels change event is received.
      *
      * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
@@ -386,6 +395,12 @@
 
     // Called from native code.
     @SuppressWarnings("unused")
+    private void dispatchModeRejected(long physicalDisplayId, int modeId) {
+        onModeRejected(physicalDisplayId, modeId);
+    }
+
+    // Called from native code.
+    @SuppressWarnings("unused")
     private void dispatchFrameRateOverrides(long timestampNanos, long physicalDisplayId,
             FrameRateOverride[] overrides) {
         onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 4ff04d5..4307884 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -210,6 +210,11 @@
     public FrameRateCategoryRate frameRateCategoryRate;
 
     /**
+     * All the refresh rates supported in the active mode.
+     */
+    public float[] supportedRefreshRates = new float[0];
+
+    /**
      * The default display mode.
      */
     public int defaultModeId;
@@ -356,6 +361,12 @@
     public float brightnessDefault;
 
     /**
+     * The current dim brightness of the display. Value between 0.0 and 1.0,
+     * derived from the configuration of the display device of this logical display.
+     */
+    public float brightnessDim;
+
+    /**
      * The {@link RoundedCorners} if present, otherwise {@code null}.
      */
     @Nullable
@@ -449,6 +460,7 @@
                 && modeId == other.modeId
                 && hasArrSupport == other.hasArrSupport
                 && Objects.equals(frameRateCategoryRate, other.frameRateCategoryRate)
+                && Arrays.equals(supportedRefreshRates, other.supportedRefreshRates)
                 && defaultModeId == other.defaultModeId
                 && userPreferredModeId == other.userPreferredModeId
                 && Arrays.equals(supportedModes, other.supportedModes)
@@ -473,6 +485,7 @@
                 && brightnessMinimum == other.brightnessMinimum
                 && brightnessMaximum == other.brightnessMaximum
                 && brightnessDefault == other.brightnessDefault
+                && brightnessDim == other.brightnessDim
                 && Objects.equals(roundedCorners, other.roundedCorners)
                 && installOrientation == other.installOrientation
                 && Objects.equals(displayShape, other.displayShape)
@@ -512,6 +525,8 @@
         renderFrameRate = other.renderFrameRate;
         hasArrSupport = other.hasArrSupport;
         frameRateCategoryRate = other.frameRateCategoryRate;
+        supportedRefreshRates = Arrays.copyOf(
+                other.supportedRefreshRates, other.supportedRefreshRates.length);
         defaultModeId = other.defaultModeId;
         userPreferredModeId = other.userPreferredModeId;
         supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length);
@@ -538,6 +553,7 @@
         brightnessMinimum = other.brightnessMinimum;
         brightnessMaximum = other.brightnessMaximum;
         brightnessDefault = other.brightnessDefault;
+        brightnessDim = other.brightnessDim;
         roundedCorners = other.roundedCorners;
         installOrientation = other.installOrientation;
         displayShape = other.displayShape;
@@ -571,6 +587,11 @@
         hasArrSupport = source.readBoolean();
         frameRateCategoryRate = source.readParcelable(null,
                 android.view.FrameRateCategoryRate.class);
+        int numOfSupportedRefreshRates = source.readInt();
+        supportedRefreshRates = new float[numOfSupportedRefreshRates];
+        for (int i = 0; i < numOfSupportedRefreshRates; i++) {
+            supportedRefreshRates[i] = source.readFloat();
+        }
         defaultModeId = source.readInt();
         userPreferredModeId = source.readInt();
         int nModes = source.readInt();
@@ -607,6 +628,7 @@
         brightnessMinimum = source.readFloat();
         brightnessMaximum = source.readFloat();
         brightnessDefault = source.readFloat();
+        brightnessDim = source.readFloat();
         roundedCorners = source.readTypedObject(RoundedCorners.CREATOR);
         int numUserDisabledFormats = source.readInt();
         userDisabledHdrTypes = new int[numUserDisabledFormats];
@@ -646,6 +668,10 @@
         dest.writeFloat(renderFrameRate);
         dest.writeBoolean(hasArrSupport);
         dest.writeParcelable(frameRateCategoryRate, flags);
+        dest.writeInt(supportedRefreshRates.length);
+        for (float supportedRefreshRate : supportedRefreshRates) {
+            dest.writeFloat(supportedRefreshRate);
+        }
         dest.writeInt(defaultModeId);
         dest.writeInt(userPreferredModeId);
         dest.writeInt(supportedModes.length);
@@ -679,6 +705,7 @@
         dest.writeFloat(brightnessMinimum);
         dest.writeFloat(brightnessMaximum);
         dest.writeFloat(brightnessDefault);
+        dest.writeFloat(brightnessDim);
         dest.writeTypedObject(roundedCorners, flags);
         dest.writeInt(userDisabledHdrTypes.length);
         for (int i = 0; i < userDisabledHdrTypes.length; i++) {
@@ -750,9 +777,19 @@
     }
 
     /**
-     * Returns the list of supported refresh rates in the default mode.
+     * Returns the list of supported refresh rates in the active mode.
      */
     public float[] getDefaultRefreshRates() {
+        if (supportedRefreshRates.length == 0) {
+            return getDefaultRefreshRatesLegacy();
+        }
+        return Arrays.copyOf(supportedRefreshRates, supportedRefreshRates.length);
+    }
+
+    /**
+     * Returns the list of supported refresh rates in the default mode.
+     */
+    public float[] getDefaultRefreshRatesLegacy() {
         Display.Mode[] modes = appsSupportedModes;
         ArraySet<Float> rates = new ArraySet<>();
         Display.Mode defaultMode = getDefaultMode();
@@ -898,6 +935,8 @@
         sb.append(hasArrSupport);
         sb.append(", frameRateCategoryRate ");
         sb.append(frameRateCategoryRate);
+        sb.append(", supportedRefreshRates ");
+        sb.append(Arrays.toString(supportedRefreshRates));
         sb.append(", defaultMode ");
         sb.append(defaultModeId);
         sb.append(", userPreferredModeId ");
@@ -965,6 +1004,8 @@
         sb.append(brightnessMaximum);
         sb.append(", brightnessDefault ");
         sb.append(brightnessDefault);
+        sb.append(", brightnessDim ");
+        sb.append(brightnessDim);
         sb.append(", installOrientation ");
         sb.append(Surface.rotationToString(installOrientation));
         sb.append(", layoutLimitedRefreshRate ");
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
index 45dbe43..21b969c 100644
--- a/core/java/android/view/IDisplayWindowInsetsController.aidl
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -60,5 +60,5 @@
      * Reports the requested IME visibility of the IME input target to
      * the IDisplayWindowInsetsController
      */
-    void setImeInputTargetRequestedVisibility(boolean visible);
+    void setImeInputTargetRequestedVisibility(boolean visible, in ImeTracker.Token statsToken);
 }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index e5be531..6d85e75 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -62,6 +62,7 @@
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.IInputFilter;
+import android.view.inputmethod.ImeTracker;
 import android.view.AppTransitionAnimationSpec;
 import android.view.WindowContentFrameStats;
 import android.view.WindowManager;
@@ -765,7 +766,8 @@
      * container.
      */
     @EnforcePermission("MANAGE_APP_TOKENS")
-    void updateDisplayWindowRequestedVisibleTypes(int displayId, int requestedVisibleTypes);
+    void updateDisplayWindowRequestedVisibleTypes(int displayId, int requestedVisibleTypes,
+            in @nullable ImeTracker.Token statsToken);
 
     /**
      * Called to get the expected window insets.
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 58ef5ef..6cd4a40 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -260,6 +260,15 @@
         touchableRegionSurfaceControl = new WeakReference<>(bounds);
     }
 
+    /**
+     * Resize the window touchable region.
+     * @param rect new touchable region rectangle.
+     */
+    public void setTouchableRegion(Rect rect) {
+        touchableRegion.set(rect);
+    }
+
+
     public void setWindowToken(IBinder iwindow) {
         windowToken = iwindow;
     }
diff --git a/core/java/android/view/LetterboxScrollProcessor.java b/core/java/android/view/LetterboxScrollProcessor.java
index 1364a82..8c1b0f8 100644
--- a/core/java/android/view/LetterboxScrollProcessor.java
+++ b/core/java/android/view/LetterboxScrollProcessor.java
@@ -78,6 +78,11 @@
     @Nullable
     @VisibleForTesting(visibility = PACKAGE)
     public List<MotionEvent> processMotionEvent(@NonNull MotionEvent motionEvent) {
+        if (!motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
+            // This is a non-pointer event that doesn't correspond to any location on the screen.
+            // Ignore it.
+            return null;
+        }
         mProcessedEvents.clear();
         final Rect appBounds = getAppBounds();
 
diff --git a/core/java/android/view/ScrollCaptureSearchResults.java b/core/java/android/view/ScrollCaptureSearchResults.java
index 3469b9d..b1ce949 100644
--- a/core/java/android/view/ScrollCaptureSearchResults.java
+++ b/core/java/android/view/ScrollCaptureSearchResults.java
@@ -16,10 +16,16 @@
 
 package android.view;
 
+import static android.view.flags.Flags.scrollCaptureTargetZOrderFix;
+
+import static java.util.Comparator.comparing;
 import static java.util.Objects.requireNonNull;
+import static java.util.Objects.requireNonNullElse;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UiThread;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.CancellationSignal;
 import android.util.IndentingPrintWriter;
@@ -113,7 +119,9 @@
 
     private void signalComplete() {
         mComplete = true;
-        mTargets.sort(PRIORITY_ORDER);
+        if (!scrollCaptureTargetZOrderFix()) {
+            mTargets.sort(PRIORITY_ORDER);
+        }
         if (mOnCompleteListener != null) {
             mOnCompleteListener.run();
             mOnCompleteListener = null;
@@ -125,14 +133,73 @@
         return new ArrayList<>(mTargets);
     }
 
+    private Rect getScrollBoundsInWindow(@Nullable ScrollCaptureTarget target) {
+        if (target == null || target.getScrollBounds() == null) {
+            return new Rect();
+        }
+        Rect windowRect = new Rect(target.getScrollBounds());
+        Point windowPosition = target.getPositionInWindow();
+        windowRect.offset(windowPosition.x, windowPosition.y);
+        return windowRect;
+    }
+
     /**
      * Get the top ranked result out of all completed requests.
      *
      * @return the top ranked result
      */
+    @Nullable
     public ScrollCaptureTarget getTopResult() {
-        ScrollCaptureTarget target = mTargets.isEmpty() ? null : mTargets.get(0);
-        return target != null && target.getScrollBounds() != null ? target : null;
+        if (!scrollCaptureTargetZOrderFix()) {
+            ScrollCaptureTarget target = mTargets.isEmpty() ? null : mTargets.get(0);
+            return target != null && target.getScrollBounds() != null ? target : null;
+        }
+        List<ScrollCaptureTarget> filtered = new ArrayList<>();
+
+        mTargets.removeIf(a -> nullOrEmpty(a.getScrollBounds()));
+
+        // Remove scroll targets obscured or covered by other scrolling views.
+        nextTarget:
+        for (int i = 0; i <  mTargets.size(); i++) {
+            ScrollCaptureTarget current = mTargets.get(i);
+
+            View currentView = current.getContainingView();
+
+            // Nested scroll containers:
+            // Check if the next view is a child of the current. If so, skip the current.
+            if (i + 1 < mTargets.size()) {
+                ScrollCaptureTarget next = mTargets.get(i + 1);
+                View nextView = next.getContainingView();
+                // Honor explicit include hint on parent as escape hatch (unless both have it)
+                if (isDescendant(currentView, nextView)
+                        && (!hasIncludeHint(currentView) || hasIncludeHint(nextView))) {
+                    continue;
+                }
+            }
+
+            // Check if any views will be drawn partially or fully over this one.
+            for (int j = i + 1; j < mTargets.size(); j++) {
+                ScrollCaptureTarget above = mTargets.get(j);
+                if (Rect.intersects(getScrollBoundsInWindow(current),
+                        getScrollBoundsInWindow(above))) {
+                    continue nextTarget;
+                }
+            }
+
+            filtered.add(current);
+        }
+
+        // natural order, false->true
+        Comparator<ScrollCaptureTarget> byIncludeHintPresence = comparing(
+                ScrollCaptureSearchResults::hasIncludeHint);
+
+        // natural order, smallest->largest area
+        Comparator<ScrollCaptureTarget> byArea = comparing(
+                target -> area(requireNonNullElse(target.getScrollBounds(), new Rect())));
+
+        // The top result is the last one (with include hint if present, then by largest area)
+        filtered.sort(byIncludeHintPresence.thenComparing(byArea));
+        return filtered.isEmpty() ? null : filtered.getLast();
     }
 
     private class SearchRequest implements Consumer<Rect> {
@@ -226,6 +293,10 @@
         return r == null || r.isEmpty();
     }
 
+    private static boolean hasIncludeHint(ScrollCaptureTarget target) {
+        return hasIncludeHint(target.getContainingView());
+    }
+
     private static boolean hasIncludeHint(View view) {
         return (view.getScrollCaptureHint() & View.SCROLL_CAPTURE_HINT_INCLUDE) != 0;
     }
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 03f9d98..6e6e87b 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -205,7 +205,8 @@
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"FRAME_RATE_COMPATIBILITY_"},
-            value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE})
+            value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                    FRAME_RATE_COMPATIBILITY_GTE})
     public @interface FrameRateCompatibility {}
 
     // From native_window.h. Keep these in sync.
@@ -214,6 +215,11 @@
      * system selects a frame rate other than what the app requested, the app will be able
      * to run at the system frame rate without requiring pull down. This value should be
      * used when displaying game content, UIs, and anything that isn't video.
+     *
+     * In Android version {@link Build.VERSION_CODES#BAKLAVA} and above, use
+     * {@link FRAME_RATE_COMPATIBILITY_DEFAULT} for game content.
+     * For other cases, see {@link FRAME_RATE_COMPATIBILITY_FIXED_SOURCE} and
+     * {@link FRAME_RATE_COMPATIBILITY_GTE}.
      */
     public static final int FRAME_RATE_COMPATIBILITY_DEFAULT = 0;
 
@@ -228,6 +234,17 @@
     public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1;
 
     /**
+     * The surface requests a frame rate that is greater than or equal to the specified frame rate.
+     * This value should be used for UIs, animations, scrolling and fling, and anything that is not
+     * a game or video.
+     *
+     * For video, use {@link FRAME_RATE_COMPATIBILITY_FIXED_SOURCE} instead. For game content, use
+     * {@link FRAME_RATE_COMPATIBILITY_DEFAULT}.
+     */
+    @FlaggedApi(com.android.graphics.surfaceflinger.flags.Flags.FLAG_ARR_SETFRAMERATE_GTE_ENUM)
+    public static final int FRAME_RATE_COMPATIBILITY_GTE = 2;
+
+    /**
      * This surface belongs to an app on the High Refresh Rate Deny list, and needs the display
      * to operate at the exact frame rate.
      *
@@ -250,13 +267,6 @@
      */
     public static final int FRAME_RATE_COMPATIBILITY_MIN = 102;
 
-    // From window.h. Keep these in sync.
-    /**
-     * The surface requests a frame rate that is greater than or equal to {@code frameRate}.
-     * @hide
-     */
-    public static final int FRAME_RATE_COMPATIBILITY_GTE = 103;
-
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"CHANGE_FRAME_RATE_"},
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 68674dd..dd9a95e 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -1932,6 +1932,7 @@
         public float renderFrameRate;
         public boolean hasArrSupport;
         public FrameRateCategoryRate frameRateCategoryRate;
+        public float[] supportedRefreshRates;
 
         public int[] supportedColorModes;
         public int activeColorMode;
@@ -1951,6 +1952,7 @@
                     + ", renderFrameRate=" + renderFrameRate
                     + ", hasArrSupport=" + hasArrSupport
                     + ", frameRateCategoryRate=" + frameRateCategoryRate
+                    + ", supportedRefreshRates=" + Arrays.toString(supportedRefreshRates)
                     + ", supportedColorModes=" + Arrays.toString(supportedColorModes)
                     + ", activeColorMode=" + activeColorMode
                     + ", hdrCapabilities=" + hdrCapabilities
@@ -1972,14 +1974,15 @@
                 && Objects.equals(hdrCapabilities, that.hdrCapabilities)
                 && preferredBootDisplayMode == that.preferredBootDisplayMode
                 && hasArrSupport == that.hasArrSupport
-                && Objects.equals(frameRateCategoryRate, that.frameRateCategoryRate);
+                && Objects.equals(frameRateCategoryRate, that.frameRateCategoryRate)
+                && Arrays.equals(supportedRefreshRates, that.supportedRefreshRates);
         }
 
         @Override
         public int hashCode() {
             return Objects.hash(Arrays.hashCode(supportedDisplayModes), activeDisplayModeId,
                     renderFrameRate, activeColorMode, hdrCapabilities, hasArrSupport,
-                    frameRateCategoryRate);
+                    frameRateCategoryRate, Arrays.hashCode(supportedRefreshRates));
         }
     }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 4a9916c..949b667 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -20,6 +20,7 @@
 import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
 import static android.view.flags.Flags.FLAG_TOOLKIT_VIEWGROUP_SET_REQUESTED_FRAME_RATE_API;
 import static android.view.flags.Flags.toolkitViewgroupSetRequestedFrameRateApi;
+import static android.view.flags.Flags.scrollCaptureTargetZOrderFix;
 
 import android.animation.LayoutTransition;
 import android.annotation.CallSuper;
@@ -7657,6 +7658,11 @@
             @NonNull Rect localVisibleRect, @NonNull Point windowOffset,
             @NonNull Consumer<ScrollCaptureTarget> targets) {
 
+        // Only visible views can be captured.
+        if (getVisibility() != View.VISIBLE) {
+            return;
+        }
+
         if (getClipToPadding() && !localVisibleRect.intersect(mPaddingLeft, mPaddingTop,
                     (mRight - mLeft)  - mPaddingRight, (mBottom - mTop) - mPaddingBottom)) {
             return;
@@ -7665,19 +7671,39 @@
         // Dispatch to self first.
         super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets);
 
+        final int childrenCount = mChildrenCount;
+        if (childrenCount == 0) {
+            return;
+        }
+
         // Skip children if descendants excluded.
         if ((getScrollCaptureHint() & SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS) != 0) {
             return;
         }
-
         final Rect tmpRect = getTempRect();
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = getChildAt(i);
+
+        ArrayList<View> preorderedList = null;
+        boolean customOrder = false;
+        if (scrollCaptureTargetZOrderFix()) {
+            preorderedList = buildOrderedChildList();
+            customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
+        }
+        final View[] children = mChildren;
+        for (int i = 0; i < childrenCount; i++) {
+            View child;
+            if (scrollCaptureTargetZOrderFix()) {
+                // Traverse children in the same order they will be drawn (honors Z if set)
+                final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+                child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+            } else {
+                child = children[i];
+            }
+
             // Only visible views can be captured.
             if (child.getVisibility() != View.VISIBLE) {
                 continue;
             }
+
             // Offset the given rectangle (in parent's local coordinates) into child's coordinate
             // space and clip the result to the child View's bounds, padding and clipRect as needed.
             // If the resulting rectangle is not empty, the request is forwarded to the child.
@@ -7706,6 +7732,9 @@
                 child.dispatchScrollCaptureSearch(tmpRect, childWindowOffset, targets);
             }
         }
+        if (preorderedList != null) {
+            preorderedList.clear();
+        }
     }
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b6e114b..e9c2cf8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.adpf.Flags.adpfViewrootimplActionDownBoost;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
 import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
@@ -1208,6 +1209,8 @@
     private long mRenderThreadDrawStartTimeNs = -1;
     private long mFirstFramePresentedTimeNs = -1;
 
+    private final boolean mSendPerfHintOnTouch;
+
     private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
     private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue;
     private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
@@ -1236,6 +1239,8 @@
     private @ActivityInfo.ColorMode int mCurrentColorMode = ActivityInfo.COLOR_MODE_DEFAULT;
     private long mColorModeLastSetMillis = -1;
 
+    private final boolean mIsSubscribeGranularDisplayEventsEnabled;
+
     public ViewRootImpl(Context context, Display display) {
         this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout());
     }
@@ -1333,6 +1338,10 @@
         // Disable DRAW_WAKE_LOCK starting U.
         mDisableDrawWakeLock =
                 CompatChanges.isChangeEnabled(DISABLE_DRAW_WAKE_LOCK) && disableDrawWakeLock();
+        mIsSubscribeGranularDisplayEventsEnabled =
+                com.android.server.display.feature.flags.Flags.subscribeGranularDisplayEvents();
+
+        mSendPerfHintOnTouch = adpfViewrootimplActionDownBoost();
     }
 
     public static void addFirstDrawHandler(Runnable callback) {
@@ -1810,14 +1819,22 @@
                 mAccessibilityInteractionConnectionManager, mHandler);
         mAccessibilityManager.addHighContrastTextStateChangeListener(
                 mExecutor, mHighContrastTextManager);
+
+
+        long eventsToBeRegistered =
+                (mIsSubscribeGranularDisplayEventsEnabled)
+                ? DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_STATE
+                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED
+                : DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
         DisplayManagerGlobal
                 .getInstance()
                 .registerDisplayListener(
                         mDisplayListener,
                         mHandler,
-                        DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
-                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
+                        eventsToBeRegistered,
                         mBasePackageName);
 
         if (forceInvertColor()) {
@@ -7098,6 +7115,10 @@
                 + "touch mode is " + mAttachInfo.mInTouchMode);
         if (mAttachInfo.mInTouchMode == inTouchMode) return false;
 
+        if (inTouchMode && mAttachInfo.mThreadedRenderer != null && mSendPerfHintOnTouch) {
+            mAttachInfo.mThreadedRenderer.notifyExpensiveFrame();
+        }
+
         // tell the window manager
         try {
             IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index b4b0687..101d5c9 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -80,6 +80,9 @@
 import static android.view.WindowLayoutParamsProto.X;
 import static android.view.WindowLayoutParamsProto.Y;
 
+import static com.android.hardware.input.Flags.FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
+import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
+
 import android.Manifest.permission;
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
@@ -1426,8 +1429,9 @@
             "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
 
     /**
-     * Activity-level {@link android.content.pm.PackageManager.Property PackageManager.Property}
-     * that specifies whether this activity can declare or request
+     * Application or Activity level
+     * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+     * that specifies whether this package or activity can declare or request
      * {@link android.R.attr#screenOrientation fixed orientation},
      * {@link android.R.attr#minAspectRatio max aspect ratio},
      * {@link android.R.attr#maxAspectRatio min aspect ratio}
@@ -1438,6 +1442,13 @@
      *
      * <p><b>Syntax:</b>
      * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE"
+     *     android:value="false"/&gt;
+     * &lt;/application&gt;
+     * </pre>or
+     * <pre>
      * &lt;activity&gt;
      *   &lt;property
      *     android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY"
@@ -1446,7 +1457,7 @@
      * </pre>
      * @hide
      */
-    // TODO(b/357141415): Make this public API.
+    // TODO(b/357141415): Remove this from sdk 37
     String PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY =
             "android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY";
 
@@ -4457,6 +4468,29 @@
         public static final int INPUT_FEATURE_SENSITIVE_FOR_PRIVACY = 1 << 3;
 
         /**
+         * Input feature used to indicate that the system should send power key events to this
+         * window when it's in the foreground. The window can override the double press power key
+         * gesture behavior.
+         *
+         * A double press gesture is defined as two
+         * {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)} events within a time span defined by
+         *  {@link ViewConfiguration#getMultiPressTimeout()}.
+         *
+         * Note: While the window may receive all power key {@link KeyEvent}s, it can only
+         * override the double press gesture behavior. The system will perform default behavior for
+         * single, long-press and other multi-press gestures, regardless of if the app handles the
+         * key or not.
+         *
+         * To override the default behavior for double press, the app must return true for the
+         * second {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)}. If the app returns false, the
+         * system behavior will be performed for double press.
+         * @hide
+         */
+        @RequiresPermission(permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+        public static final int
+                INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS = 1 << 4;
+
+        /**
          * An internal annotation for flags that can be specified to {@link #inputFeatures}.
          *
          * NOTE: These are not the same as {@link android.os.InputConfig} flags.
@@ -4469,6 +4503,7 @@
                 INPUT_FEATURE_DISABLE_USER_ACTIVITY,
                 INPUT_FEATURE_SPY,
                 INPUT_FEATURE_SENSITIVE_FOR_PRIVACY,
+                INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS
         })
         public @interface InputFeatureFlags {
         }
@@ -4758,6 +4793,44 @@
         }
 
         /**
+         * Specifies if the system should send power key events to this window when it's in the
+         * foreground, with only the double tap gesture behavior being overrideable.
+         *
+         * @param enabled if true, the system should send power key events to this window when it's
+         *              in the foreground, with only the power key double tap gesture being
+         *              overrideable.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+        @FlaggedApi(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+        public void setReceivePowerKeyDoublePressEnabled(boolean enabled) {
+            if (enabled) {
+                inputFeatures
+                        |= INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
+            } else {
+                inputFeatures
+                        &= ~INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
+            }
+        }
+
+        /**
+         * Returns whether or not the system should send power key events to this window when it's
+         * in the foreground, with only the double tap gesture being overrideable.
+         *
+         * @return if the system should send power key events to this window when it's in the
+         * foreground, with only the double tap gesture being overrideable.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+        @FlaggedApi(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+        public boolean isReceivePowerKeyDoublePressEnabled() {
+            return (inputFeatures
+                    & INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS) != 0;
+        }
+
+        /**
          * Specifies that the window should be considered a trusted system overlay. Trusted system
          * overlays are ignored when considering whether windows are obscured during input
          * dispatch. Requires the {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW}
@@ -6149,6 +6222,16 @@
                 inputFeatures &= ~INPUT_FEATURE_SPY;
                 features.add("INPUT_FEATURE_SPY");
             }
+            if (overridePowerKeyBehaviorInFocusedWindow()) {
+                if ((inputFeatures
+                        & INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS)
+                        != 0) {
+                    inputFeatures
+                            &=
+                            ~INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
+                    features.add("INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS");
+                }
+            }
             if (inputFeatures != 0) {
                 features.add(Integer.toHexString(inputFeatures));
             }
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 330e46a..97cf8fc 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -85,7 +85,7 @@
  * @see WindowManagerGlobal
  * @hide
  */
-public final class WindowManagerImpl implements WindowManager {
+public class WindowManagerImpl implements WindowManager {
     private static final String TAG = "WindowManager";
 
     @UnsupportedAppUsage
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 0dfaf41..88ccf88 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -959,6 +959,8 @@
                 CONTENT_CHANGE_TYPE_CONTENT_INVALID,
                 CONTENT_CHANGE_TYPE_ERROR,
                 CONTENT_CHANGE_TYPE_ENABLED,
+                CONTENT_CHANGE_TYPE_CHECKED,
+                CONTENT_CHANGE_TYPE_EXPANDED,
                 CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION,
             })
     public @interface ContentChangeTypes {}
@@ -1241,6 +1243,16 @@
             case CONTENT_CHANGE_TYPE_ERROR: return "CONTENT_CHANGE_TYPE_ERROR";
             case CONTENT_CHANGE_TYPE_ENABLED: return "CONTENT_CHANGE_TYPE_ENABLED";
             default: {
+                if (Flags.triStateChecked()) {
+                    if (type == CONTENT_CHANGE_TYPE_CHECKED) {
+                        return "CONTENT_CHANGE_TYPE_CHECKED";
+                    }
+                }
+                if (Flags.a11yExpansionStateApi()) {
+                    if (type == CONTENT_CHANGE_TYPE_EXPANDED) {
+                        return "CONTENT_CHANGE_TYPE_EXPANDED";
+                    }
+                }
                 if (Flags.supplementalDescription()) {
                     if (type == CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION) {
                         return "CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION";
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 0204517..df0c5a3 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -6551,16 +6551,24 @@
         /**
          * Range type: indeterminate.
          *
-         * A {@link RangeInfo} type used to represent a node which may typically expose range
-         * information but is presently in an indeterminate state, such as a {@link
-         * android.widget.ProgressBar} representing a loading operation of unknown duration.
          * When using this type, the {@code min}, {@code max}, and {@code current} values used to
-         * construct an instance may be ignored. It is recommended to use {@code Float.NaN} for
-         * these values.
+         * construct an instance may be ignored.
+         *
+         *  @see #INDETERMINATE
          */
         @FlaggedApi(Flags.FLAG_INDETERMINATE_RANGE_INFO)
         public static final int RANGE_TYPE_INDETERMINATE = 3;
 
+        /**
+         * A {@link RangeInfo} type used to represent a node which may typically expose range
+         * information but is presently in an indeterminate state, such as a {@link
+         * android.widget.ProgressBar} representing a loading operation of unknown duration.
+         */
+        @NonNull
+        @FlaggedApi(Flags.FLAG_INDETERMINATE_RANGE_INFO)
+        public static final RangeInfo INDETERMINATE = new RangeInfo(RANGE_TYPE_INDETERMINATE, 0.0f,
+                0.0f, 0.0f);
+
         private int mType;
         private float mMin;
         private float mMax;
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index e60fc3a..049ad20 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -8,6 +8,7 @@
     namespace: "accessibility"
     description: "Enables new APIs for an app to convey if a node is expanded or collapsed."
     bug: "362782536"
+    is_exported: true
 }
 
 flag {
@@ -15,6 +16,7 @@
     namespace: "accessibility"
     description: "Adds an API to indicate whether a form field (or similar element) is required."
     bug: "362784403"
+    is_exported: true
 }
 
 flag {
@@ -44,6 +46,7 @@
     namespace: "accessibility"
     description: "Enables new extra data key for an AccessibilityService to request character bounds in unmagnified window coordinates."
     bug: "375429616"
+    is_exported: true
 }
 
 flag {
@@ -88,6 +91,7 @@
     name: "deprecate_accessibility_announcement_apis"
     description: "Controls the deprecation of platform APIs related to disruptive accessibility announcements"
     bug: "376727542"
+    is_exported: true
 }
 
 flag {
@@ -95,6 +99,7 @@
     name: "deprecate_ani_label_for_apis"
     description: "Controls the deprecation of AccessibilityNodeInfo labelFor apis"
     bug: "333783827"
+    is_exported: true
 }
 
 flag {
@@ -145,6 +150,7 @@
     name: "global_action_menu"
     description: "Allow AccessibilityService to perform GLOBAL_ACTION_MENU"
     bug: "334954140"
+    is_exported: true
 }
 
 flag {
@@ -152,6 +158,7 @@
     name: "global_action_media_play_pause"
     description: "Allow AccessibilityService to perform GLOBAL_ACTION_MEDIA_PLAY_PAUSE"
     bug: "334954140"
+    is_exported: true
 }
 
 flag {
@@ -230,6 +237,7 @@
     namespace: "accessibility"
     description: "Feature flag for supplemental description api"
     bug: "375266174"
+    is_exported: true
 }
 
 flag {
@@ -237,6 +245,7 @@
     namespace: "accessibility"
     description: "Feature flag for supporting multiple labels in AccessibilityNodeInfo labeledby api"
     bug: "333780959"
+    is_exported: true
 }
 
 flag {
@@ -251,6 +260,7 @@
     namespace: "accessibility"
     description: "Feature flag for adding tri-state checked api"
     bug: "333784774"
+    is_exported: true
 }
 
 flag {
@@ -261,11 +271,12 @@
     metadata {
         purpose: PURPOSE_BUGFIX
     }
- }
+}
 
- flag {
+flag {
     name: "indeterminate_range_info"
     namespace: "accessibility"
     description: "Creates a way to create an INDETERMINATE RangeInfo"
     bug: "376108874"
- }
+    is_exported: true
+}
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 905f350..d527007 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -603,7 +603,7 @@
         return DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_AUTOFILL,
                 DEVICE_CONFIG_ENABLE_RELAYOUT,
-                false);
+                true);
     }
 
     /** @hide */
@@ -611,7 +611,7 @@
         return DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_AUTOFILL,
                 DEVICE_CONFIG_ENABLE_RELATIVE_LOCATION_FOR_RELAYOUT,
-                false);
+                true);
     }
 
     /** @hide **/
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 52c5af8..1de0474 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -26,6 +26,7 @@
 import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
 import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
 import static android.service.autofill.Flags.FLAG_FILL_DIALOG_IMPROVEMENTS;
+import static android.service.autofill.Flags.relayoutFix;
 import static android.view.ContentInfo.SOURCE_AUTOFILL;
 import static android.view.autofill.Helper.sDebug;
 import static android.view.autofill.Helper.sVerbose;
@@ -1013,7 +1014,7 @@
                 AutofillFeatureFlags.shouldIncludeInvisibleViewInAssistStructure();
 
         mRelayoutFixDeprecated = AutofillFeatureFlags.shouldIgnoreRelayoutWhenAuthPending();
-        mRelayoutFix = AutofillFeatureFlags.enableRelayoutFixes();
+        mRelayoutFix = relayoutFix() && AutofillFeatureFlags.enableRelayoutFixes();
         mRelativePositionForRelayout = AutofillFeatureFlags.enableRelativeLocationForRelayout();
         mIsCredmanIntegrationEnabled = Flags.autofillCredmanIntegration();
     }
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 1c7570e..675e5a1 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -127,6 +127,7 @@
     description: "Feature flag to introduce new frame rate setting APIs on ViewGroup"
     bug: "335874198"
     is_fixed_read_only: true
+    is_exported: true
 }
 
 flag {
diff --git a/core/java/android/view/flags/scroll_capture.aconfig b/core/java/android/view/flags/scroll_capture.aconfig
new file mode 100644
index 0000000..9080b16
--- /dev/null
+++ b/core/java/android/view/flags/scroll_capture.aconfig
@@ -0,0 +1,13 @@
+package: "android.view.flags"
+container: "system"
+
+flag {
+    name: "scroll_capture_target_z_order_fix"
+    namespace: "systemui"
+    description: "Always prefer targets with higher z-order"
+    bug: "365969802"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 3b6343e..f6fdec9 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -116,6 +116,7 @@
     description: "Add a SurfaceView composition order control API."
     bug: "341021569"
     is_fixed_read_only: true
+    is_exported: true
 }
 
 flag {
@@ -124,6 +125,7 @@
     description: "Add APIs to manage SurfacePackage of the parent SurfaceView."
     bug: "341021569"
     is_fixed_read_only: true
+    is_exported: true
 }
 
 flag {
@@ -140,4 +142,12 @@
     description: "Recover from buffer stuffing when SurfaceFlinger misses a frame"
     bug: "294922229"
     is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+    name: "date_time_view_relative_time_display_configs"
+    namespace: "systemui"
+    description: "Enables DateTimeView to use additional display configurations for relative time"
+    bug: "364653005"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index dd32d57..4d354e0 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -222,6 +222,8 @@
             PHASE_CLIENT_ALREADY_HIDDEN,
             PHASE_CLIENT_VIEW_HANDLER_AVAILABLE,
             PHASE_SERVER_UPDATE_CLIENT_VISIBILITY,
+            PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE,
+            PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface Phase {}
@@ -436,6 +438,12 @@
      * app or the RemoteInsetsControlTarget).
      */
     int PHASE_SERVER_UPDATE_CLIENT_VISIBILITY = ImeProtoEnums.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY;
+    /** DisplayImeController received the requested visibility for the IME and stored it. */
+    int PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE =
+            ImeProtoEnums.PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE;
+    /** The control target reported its requestedVisibleTypes back to WindowManagerService. */
+    int PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES =
+            ImeProtoEnums.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES;
 
     /**
      * Called when an IME request is started.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 6303c76..5dd29b2 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2626,10 +2626,12 @@
                         // The view is running on a different thread than our own, so
                         // we need to reschedule our work for over there.
                         if (DEBUG) Log.v(TAG, "Hiding soft input: reschedule to view thread");
+                        final var finalStatsToken = statsToken;
                         vh.post(() -> viewRootImpl.getInsetsController().hide(
-                                WindowInsets.Type.ime()));
+                                WindowInsets.Type.ime(), false /* fromIme */, finalStatsToken));
                     } else {
-                        viewRootImpl.getInsetsController().hide(WindowInsets.Type.ime());
+                        viewRootImpl.getInsetsController().hide(WindowInsets.Type.ime(),
+                                false /* fromIme */, statsToken);
                     }
                 }
                 return true;
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 73abc47..41567fb 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -165,6 +165,7 @@
     description: "Writing tools API"
     bug: "373788889"
     is_fixed_read_only: true
+    is_exported: true
 }
 
 flag {
@@ -191,4 +192,5 @@
   description: "Verify KeyEvents in IME"
   bug: "331730488"
   is_fixed_read_only: true
+  is_exported: true
 }
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index b18dbbc..1bc952c 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -21,7 +21,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.os.Build;
 import android.os.UserHandle;
 import android.os.UserManager;
 
@@ -36,8 +35,6 @@
     private final UserHandle mUser;
     private final PackageInfo mPackageInfo;
 
-    public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.TIRAMISU;
-
     public UserPackage(@NonNull UserHandle user, @Nullable PackageInfo packageInfo) {
         mUser = user;
         mPackageInfo = packageInfo;
@@ -83,14 +80,6 @@
                         & ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0));
     }
 
-    /**
-     * Returns whether the package represented by {@param packageInfo} targets a sdk version
-     * supported by the current framework version.
-     */
-    public static boolean hasCorrectTargetSdkVersion(PackageInfo packageInfo) {
-        return packageInfo.applicationInfo.targetSdkVersion >= MINIMUM_SUPPORTED_SDK;
-    }
-
     public UserHandle getUser() {
         return mUser;
     }
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index de303f8..1a48bbb 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -51,12 +51,6 @@
  */
 @SystemApi
 public final class WebViewFactory {
-
-    // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
-    /** @hide */
-    private static final String CHROMIUM_WEBVIEW_FACTORY =
-            "com.android.webview.chromium.WebViewChromiumFactoryProviderForT";
-
     private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
 
     private static final String LOGTAG = "WebViewFactory";
@@ -275,8 +269,8 @@
      */
     public static Class<WebViewFactoryProvider> getWebViewProviderClass(ClassLoader clazzLoader)
             throws ClassNotFoundException {
-        return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY,
-                true, clazzLoader);
+        return (Class<WebViewFactoryProvider>) Class.forName(
+                WebViewFactoryProvider.getWebViewFactoryClassName(), true, clazzLoader);
     }
 
     /**
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 3d64506..4a2f9ba 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -20,8 +20,11 @@
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageInfo;
 import android.net.Network;
 import android.net.Uri;
+import android.os.Build;
+import android.text.TextUtils;
 
 import java.util.List;
 
@@ -34,6 +37,61 @@
 @SystemApi
 public interface WebViewFactoryProvider {
     /**
+     * Used as the requirement when Flags.useBEntryPoint() is false.
+     * @hide
+     */
+    int MINIMUM_SUPPORTED_TARGET_SDK = Build.VERSION_CODES.TIRAMISU;
+
+    /**
+     * Used as the requirement when Flags.useBEntryPoint() is true.
+     * TODO: set to the actual minimum required version code - this is just the
+     *       version shipped in V.
+     * @hide
+     */
+    long MINIMUM_SUPPORTED_VERSION_CODE = 661308800L;
+
+    /**
+     * Returns whether the WebView implementation represented by {@code packageInfo}
+     * is compatible with this version of Android.
+     * @hide
+     */
+    static boolean isCompatibleImplementationPackage(@NonNull PackageInfo packageInfo) {
+        if (Flags.useBEntryPoint()) {
+            return packageInfo.versionCode >= MINIMUM_SUPPORTED_VERSION_CODE;
+        } else {
+            return packageInfo.applicationInfo.targetSdkVersion >= MINIMUM_SUPPORTED_TARGET_SDK;
+        }
+    }
+
+    /**
+     * Returns a string describing the minimum requirement for a WebView implementation
+     * to be compatible with this version of Android, for debugging purposes.
+     * @hide
+     */
+    static @NonNull String describeCompatibleImplementationPackage() {
+        if (Flags.useBEntryPoint()) {
+            return TextUtils.formatSimple("Minimum versionCode for OS support: %d",
+                    MINIMUM_SUPPORTED_VERSION_CODE);
+        } else {
+            return TextUtils.formatSimple("Minimum targetSdkVersion: %d",
+                    MINIMUM_SUPPORTED_TARGET_SDK);
+        }
+    }
+
+    /**
+     * Returns the name of the class that should be used when loading the
+     * WebView implementation on this version of Android.
+     * @hide
+     */
+    static @NonNull String getWebViewFactoryClassName() {
+        if (Flags.useBEntryPoint()) {
+            return "com.android.webview.chromium.WebViewChromiumFactoryProviderForB";
+        } else {
+            return "com.android.webview.chromium.WebViewChromiumFactoryProviderForT";
+        }
+    }
+
+    /**
      * This Interface provides glue for implementing the backend of WebView static methods which
      * cannot be implemented in-situ in the proxy class.
      */
diff --git a/core/java/android/webkit/flags.aconfig b/core/java/android/webkit/flags.aconfig
index c9e94d2..c5176a2 100644
--- a/core/java/android/webkit/flags.aconfig
+++ b/core/java/android/webkit/flags.aconfig
@@ -42,3 +42,11 @@
     description: "New APIs required by File System Access"
     bug: "40101963"
 }
+
+flag {
+    name: "use_b_entry_point"
+    namespace: "webview"
+    description: "Use B-specific entry point to WebView APK"
+    bug: "373617389"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java
index 0bf6380..eb3b768 100644
--- a/core/java/android/widget/Button.java
+++ b/core/java/android/widget/Button.java
@@ -134,6 +134,7 @@
         // 1. app target sdk is 36 or above.
         // 2. feature flag rolled-out.
         // 3. device is a watch.
+        // 4. button uses Theme.DeviceDefault.
         // getButtonDefaultStyleAttr and getButtonDefaultStyleRes works together to alter the UI
         // while considering the conditions above.
         // Their results are mutual exclusive. i.e. when conditions above are all true,
@@ -229,6 +230,7 @@
 
     private static boolean useWearMaterial3Style(Context context) {
         return Flags.useWearMaterial3Ui() && CompatChanges.isChangeEnabled(WEAR_MATERIAL3_BUTTON)
-                && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+                && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+                && context.getThemeResId() == com.android.internal.R.style.Theme_DeviceDefault;
     }
 }
diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java
index 41ff69d..143b4b7 100644
--- a/core/java/android/widget/DateTimeView.java
+++ b/core/java/android/widget/DateTimeView.java
@@ -21,6 +21,7 @@
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.DateUtils.YEAR_IN_MILLIS;
 
+import android.annotation.IntDef;
 import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
@@ -41,6 +42,8 @@
 
 import com.android.internal.R;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.text.DateFormat;
 import java.time.Instant;
 import java.time.LocalDate;
@@ -70,6 +73,23 @@
     private static final int SHOW_TIME = 0;
     private static final int SHOW_MONTH_DAY_YEAR = 1;
 
+    /** @hide */
+    @IntDef(value = {UNIT_DISPLAY_LENGTH_SHORTEST, UNIT_DISPLAY_LENGTH_MEDIUM})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UnitDisplayLength {}
+    public static final int UNIT_DISPLAY_LENGTH_SHORTEST = 0;
+    public static final int UNIT_DISPLAY_LENGTH_MEDIUM = 1;
+
+    /** @hide */
+    @IntDef(flag = true, value = {DISAMBIGUATION_TEXT_PAST, DISAMBIGUATION_TEXT_FUTURE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DisambiguationTextMask {}
+    public static final int DISAMBIGUATION_TEXT_PAST = 0x01;
+    public static final int DISAMBIGUATION_TEXT_FUTURE = 0x02;
+
+    private final boolean mCanUseRelativeTimeDisplayConfigs =
+            android.view.flags.Flags.dateTimeViewRelativeTimeDisplayConfigs();
+
     private long mTimeMillis;
     // The LocalDateTime equivalent of mTimeMillis but truncated to minute, i.e. no seconds / nanos.
     private LocalDateTime mLocalTime;
@@ -81,6 +101,8 @@
     private static final ThreadLocal<ReceiverInfo> sReceiverInfo = new ThreadLocal<ReceiverInfo>();
     private String mNowText;
     private boolean mShowRelativeTime;
+    private int mRelativeTimeDisambiguationTextMask;
+    private int mRelativeTimeUnitDisplayLength = UNIT_DISPLAY_LENGTH_SHORTEST;
 
     public DateTimeView(Context context) {
         this(context, null);
@@ -89,20 +111,23 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public DateTimeView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        final TypedArray a = context.obtainStyledAttributes(attrs,
-                com.android.internal.R.styleable.DateTimeView, 0,
-                0);
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.DateTimeView, 0, 0);
 
-        final int N = a.getIndexCount();
-        for (int i = 0; i < N; i++) {
-            int attr = a.getIndex(i);
-            switch (attr) {
-                case R.styleable.DateTimeView_showRelative:
-                    boolean relative = a.getBoolean(i, false);
-                    setShowRelativeTime(relative);
-                    break;
-            }
+        setShowRelativeTime(a.getBoolean(R.styleable.DateTimeView_showRelative, false));
+        if (mCanUseRelativeTimeDisplayConfigs) {
+            setRelativeTimeDisambiguationTextMask(
+                    a.getInt(
+                            R.styleable.DateTimeView_relativeTimeDisambiguationText,
+                            // The original implementation showed disambiguation text for future
+                            // times only, so continue with that default.
+                            DISAMBIGUATION_TEXT_FUTURE));
+            setRelativeTimeUnitDisplayLength(
+                    a.getInt(
+                            R.styleable.DateTimeView_relativeTimeUnitDisplayLength,
+                            UNIT_DISPLAY_LENGTH_SHORTEST));
         }
+
         a.recycle();
     }
 
@@ -150,6 +175,29 @@
         update();
     }
 
+    /** See {@link R.styleable.DateTimeView_relativeTimeDisambiguationText}. */
+    @android.view.RemotableViewMethod
+    public void setRelativeTimeDisambiguationTextMask(
+            @DisambiguationTextMask int disambiguationTextMask) {
+        if (!mCanUseRelativeTimeDisplayConfigs) {
+            return;
+        }
+        mRelativeTimeDisambiguationTextMask = disambiguationTextMask;
+        updateNowText();
+        update();
+    }
+
+    /** See {@link R.styleable.DateTimeView_relativeTimeUnitDisplayLength}. */
+    @android.view.RemotableViewMethod
+    public void setRelativeTimeUnitDisplayLength(@UnitDisplayLength int unitDisplayLength) {
+        if (!mCanUseRelativeTimeDisplayConfigs) {
+            return;
+        }
+        mRelativeTimeUnitDisplayLength = unitDisplayLength;
+        updateNowText();
+        update();
+    }
+
     /**
      * Returns whether this view shows relative time
      *
@@ -264,17 +312,11 @@
             return;
         } else if (duration < HOUR_IN_MILLIS) {
             count = (int)(duration / MINUTE_IN_MILLIS);
-            result = getContext().getResources().getString(past
-                    ? com.android.internal.R.string.duration_minutes_shortest
-                    : com.android.internal.R.string.duration_minutes_shortest_future,
-                    count);
+            result = getContext().getResources().getString(getMinutesStringId(past), count);
             millisIncrease = MINUTE_IN_MILLIS;
         } else if (duration < DAY_IN_MILLIS) {
             count = (int)(duration / HOUR_IN_MILLIS);
-            result = getContext().getResources().getString(past
-                            ? com.android.internal.R.string.duration_hours_shortest
-                            : com.android.internal.R.string.duration_hours_shortest_future,
-                            count);
+            result = getContext().getResources().getString(getHoursStringId(past), count);
             millisIncrease = HOUR_IN_MILLIS;
         } else if (duration < YEAR_IN_MILLIS) {
             // In weird cases it can become 0 because of daylight savings
@@ -283,10 +325,7 @@
             LocalDateTime localNow = toLocalDateTime(now, zoneId);
 
             count = Math.max(Math.abs(dayDistance(localDateTime, localNow)), 1);
-            result = getContext().getResources().getString(past
-                    ? com.android.internal.R.string.duration_days_shortest
-                    : com.android.internal.R.string.duration_days_shortest_future,
-                    count);
+            result = getContext().getResources().getString(getDaysStringId(past), count);
             if (past || count != 1) {
                 mUpdateTimeMillis = computeNextMidnight(localNow, zoneId);
                 millisIncrease = -1;
@@ -296,10 +335,7 @@
 
         } else {
             count = (int)(duration / YEAR_IN_MILLIS);
-            result = getContext().getResources().getString(past
-                    ? com.android.internal.R.string.duration_years_shortest
-                    : com.android.internal.R.string.duration_years_shortest_future,
-                    count);
+            result = getContext().getResources().getString(getYearsStringId(past), count);
             millisIncrease = YEAR_IN_MILLIS;
         }
         if (millisIncrease != -1) {
@@ -312,6 +348,139 @@
         maybeSetText(result);
     }
 
+    private int getMinutesStringId(boolean past) {
+        if (!mCanUseRelativeTimeDisplayConfigs) {
+            return past
+                    ? com.android.internal.R.string.duration_minutes_shortest
+                    : com.android.internal.R.string.duration_minutes_shortest_future;
+        }
+
+        if (mRelativeTimeUnitDisplayLength == UNIT_DISPLAY_LENGTH_SHORTEST) {
+            if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+                // "1m ago"
+                return com.android.internal.R.string.duration_minutes_shortest_past;
+            } else if (!past
+                    && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+                // "in 1m"
+                return com.android.internal.R.string.duration_minutes_shortest_future;
+            } else {
+                // "1m"
+                return com.android.internal.R.string.duration_minutes_shortest;
+            }
+        } else { // UNIT_DISPLAY_LENGTH_MEDIUM
+            if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+                // "1min ago"
+                return com.android.internal.R.string.duration_minutes_medium_past;
+            } else if (!past
+                    && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+                // "in 1min"
+                return com.android.internal.R.string.duration_minutes_medium_future;
+            } else {
+                // "1min"
+                return com.android.internal.R.string.duration_minutes_medium;
+            }
+        }
+    }
+
+    private int getHoursStringId(boolean past) {
+        if (!mCanUseRelativeTimeDisplayConfigs) {
+            return past
+                    ? com.android.internal.R.string.duration_hours_shortest
+                    : com.android.internal.R.string.duration_hours_shortest_future;
+        }
+        if (mRelativeTimeUnitDisplayLength == UNIT_DISPLAY_LENGTH_SHORTEST) {
+            if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+                // "1h ago"
+                return com.android.internal.R.string.duration_hours_shortest_past;
+            } else if (!past
+                    && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+                // "in 1h"
+                return com.android.internal.R.string.duration_hours_shortest_future;
+            } else {
+                // "1h"
+                return com.android.internal.R.string.duration_hours_shortest;
+            }
+        } else { // UNIT_DISPLAY_LENGTH_MEDIUM
+            if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+                // "1hr ago"
+                return com.android.internal.R.string.duration_hours_medium_past;
+            } else if (!past
+                    && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+                // "in 1hr"
+                return com.android.internal.R.string.duration_hours_medium_future;
+            } else {
+                // "1hr"
+                return com.android.internal.R.string.duration_hours_medium;
+            }
+        }
+    }
+
+    private int getDaysStringId(boolean past) {
+        if (!mCanUseRelativeTimeDisplayConfigs) {
+            return past
+                    ? com.android.internal.R.string.duration_days_shortest
+                    : com.android.internal.R.string.duration_days_shortest_future;
+        }
+        if (mRelativeTimeUnitDisplayLength == UNIT_DISPLAY_LENGTH_SHORTEST) {
+            if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+                // "1d ago"
+                return com.android.internal.R.string.duration_days_shortest_past;
+            } else if (!past
+                    && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+                // "in 1d"
+                return com.android.internal.R.string.duration_days_shortest_future;
+            } else {
+                // "1d"
+                return com.android.internal.R.string.duration_days_shortest;
+            }
+        } else { // UNIT_DISPLAY_LENGTH_MEDIUM
+            if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+                // "1d ago"
+                return com.android.internal.R.string.duration_days_medium_past;
+            } else if (!past
+                    && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+                // "in 1d"
+                return com.android.internal.R.string.duration_days_medium_future;
+            } else {
+                // "1d"
+                return com.android.internal.R.string.duration_days_medium;
+            }
+        }
+    }
+
+    private int getYearsStringId(boolean past) {
+        if (!mCanUseRelativeTimeDisplayConfigs) {
+            return past
+                    ? com.android.internal.R.string.duration_years_shortest
+                    : com.android.internal.R.string.duration_years_shortest_future;
+        }
+        if (mRelativeTimeUnitDisplayLength == UNIT_DISPLAY_LENGTH_SHORTEST) {
+            if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+                // "1y ago"
+                return com.android.internal.R.string.duration_years_shortest_past;
+            } else if (!past
+                    && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+                // "in 1y"
+                return com.android.internal.R.string.duration_years_shortest_future;
+            } else {
+                // "1y"
+                return com.android.internal.R.string.duration_years_shortest;
+            }
+        } else { // UNIT_DISPLAY_LENGTH_MEDIUM
+            if (past && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_PAST) != 0) {
+                // "1y ago"
+                return com.android.internal.R.string.duration_years_medium_past;
+            } else if (!past
+                    && (mRelativeTimeDisambiguationTextMask & DISAMBIGUATION_TEXT_FUTURE) != 0) {
+                // "in 1y"
+                return com.android.internal.R.string.duration_years_medium_future;
+            } else {
+                // "1y"
+                return com.android.internal.R.string.duration_years_medium;
+            }
+        }
+    }
+
     /**
      * Sets text only if the text has actually changed. This prevents needles relayouts of this
      * view when set to wrap_content.
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 6e43d0f..2a8a928 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import static android.view.accessibility.Flags.indeterminateRangeInfo;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -2364,15 +2366,22 @@
     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfoInternal(info);
 
-        if (!isIndeterminate()) {
-            AccessibilityNodeInfo.RangeInfo rangeInfo = AccessibilityNodeInfo.RangeInfo.obtain(
+        AccessibilityNodeInfo.RangeInfo rangeInfo = null;
+        if (isIndeterminate()) {
+            if (indeterminateRangeInfo()) {
+                rangeInfo = AccessibilityNodeInfo.RangeInfo.INDETERMINATE;
+            }
+        }  else {
+            rangeInfo = new AccessibilityNodeInfo.RangeInfo(
                     AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT, getMin(), getMax(),
                     getProgress());
-            info.setRangeInfo(rangeInfo);
         }
 
-        // Only set the default state description when custom state descripton is null.
+        info.setRangeInfo(rangeInfo);
+
+        // Only set the default state description when custom state description is null.
         if (getStateDescription() == null) {
+            // TODO(b/380340432): Remove after accessibility services stop relying on this.
             if (isIndeterminate()) {
                 info.setStateDescription(getResources().getString(R.string.in_progress));
             } else {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 80d39d1..2cd3901 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -128,6 +128,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.IRemoteViewsFactory;
 import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.player.RemoteComposeDocument;
 import com.android.internal.widget.remotecompose.player.RemoteComposePlayer;
 
@@ -1298,7 +1299,13 @@
 
         @Override
         public void visitUris(@NonNull Consumer<Uri> visitor) {
-            if (mIntentId != -1 || mItems == null) {
+            if (mItems == null) {
+                // Null item indicates adapter conversion took place, so the URIs in cached items
+                // need to be validated.
+                RemoteCollectionItems cachedItems = mCollectionCache.getItemsForId(mIntentId);
+                if (cachedItems != null) {
+                    cachedItems.visitUris(visitor);
+                }
                 return;
             }
 
@@ -5819,7 +5826,7 @@
                 }
                 try (ByteArrayInputStream is = new ByteArrayInputStream(bytes.get(0))) {
                     player.setDocument(new RemoteComposeDocument(is));
-                    player.addClickListener((viewId, metadata) -> {
+                    player.addIdActionListener((viewId, metadata) -> {
                         mActions.forEach(action -> {
                             if (viewId == action.mViewId
                                     && action instanceof SetOnClickResponse setOnClickResponse) {
@@ -9333,7 +9340,11 @@
         Set<Integer> bitmapIdSet = getBitmapIdsUsedByActions(new HashSet<>());
         int result = 0;
         for (int bitmapId: bitmapIdSet) {
-            result += mBitmapCache.getBitmapForId(bitmapId).getAllocationByteCount();
+            Bitmap currentBitmap = mBitmapCache.getBitmapForId(bitmapId);
+            if (currentBitmap == null) {
+                continue;
+            }
+            result += currentBitmap.getAllocationByteCount();
         }
 
         return result;
@@ -9819,7 +9830,7 @@
          */
         @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
         public static long getSupportedVersion() {
-            return VERSION;
+            return (long) CoreDocument.getDocumentApiLevel();
         }
 
         /**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index cb70466..d7750bd 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -106,7 +106,6 @@
 import android.os.ParcelableParcel;
 import android.os.Process;
 import android.os.SystemClock;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.BoringLayout;
@@ -9230,179 +9229,174 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "TextView.onDraw");
-        try {
-            restartMarqueeIfNeeded();
+        restartMarqueeIfNeeded();
 
-            // Draw the background for this view
-            super.onDraw(canvas);
+        // Draw the background for this view
+        super.onDraw(canvas);
 
-            final int compoundPaddingLeft = getCompoundPaddingLeft();
-            final int compoundPaddingTop = getCompoundPaddingTop();
-            final int compoundPaddingRight = getCompoundPaddingRight();
-            final int compoundPaddingBottom = getCompoundPaddingBottom();
-            final int scrollX = mScrollX;
-            final int scrollY = mScrollY;
-            final int right = mRight;
-            final int left = mLeft;
-            final int bottom = mBottom;
-            final int top = mTop;
-            final boolean isLayoutRtl = isLayoutRtl();
-            final int offset = getHorizontalOffsetForDrawables();
-            final int leftOffset = isLayoutRtl ? 0 : offset;
-            final int rightOffset = isLayoutRtl ? offset : 0;
+        final int compoundPaddingLeft = getCompoundPaddingLeft();
+        final int compoundPaddingTop = getCompoundPaddingTop();
+        final int compoundPaddingRight = getCompoundPaddingRight();
+        final int compoundPaddingBottom = getCompoundPaddingBottom();
+        final int scrollX = mScrollX;
+        final int scrollY = mScrollY;
+        final int right = mRight;
+        final int left = mLeft;
+        final int bottom = mBottom;
+        final int top = mTop;
+        final boolean isLayoutRtl = isLayoutRtl();
+        final int offset = getHorizontalOffsetForDrawables();
+        final int leftOffset = isLayoutRtl ? 0 : offset;
+        final int rightOffset = isLayoutRtl ? offset : 0;
 
-            final Drawables dr = mDrawables;
-            if (dr != null) {
-                /*
-                 * Compound, not extended, because the icon is not clipped
-                 * if the text height is smaller.
-                 */
+        final Drawables dr = mDrawables;
+        if (dr != null) {
+            /*
+             * Compound, not extended, because the icon is not clipped
+             * if the text height is smaller.
+             */
 
-                int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
-                int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
+            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
+            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
 
-                // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
-                // Make sure to update invalidateDrawable() when changing this code.
-                if (dr.mShowing[Drawables.LEFT] != null) {
-                    canvas.save();
-                    canvas.translate(scrollX + mPaddingLeft + leftOffset,
-                            scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
-                    dr.mShowing[Drawables.LEFT].draw(canvas);
-                    canvas.restore();
-                }
-
-                // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
-                // Make sure to update invalidateDrawable() when changing this code.
-                if (dr.mShowing[Drawables.RIGHT] != null) {
-                    canvas.save();
-                    canvas.translate(scrollX + right - left - mPaddingRight
-                                    - dr.mDrawableSizeRight - rightOffset,
-                            scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
-                    dr.mShowing[Drawables.RIGHT].draw(canvas);
-                    canvas.restore();
-                }
-
-                // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
-                // Make sure to update invalidateDrawable() when changing this code.
-                if (dr.mShowing[Drawables.TOP] != null) {
-                    canvas.save();
-                    canvas.translate(scrollX + compoundPaddingLeft
-                            + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
-                    dr.mShowing[Drawables.TOP].draw(canvas);
-                    canvas.restore();
-                }
-
-                // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
-                // Make sure to update invalidateDrawable() when changing this code.
-                if (dr.mShowing[Drawables.BOTTOM] != null) {
-                    canvas.save();
-                    canvas.translate(scrollX + compoundPaddingLeft
-                                    + (hspace - dr.mDrawableWidthBottom) / 2,
-                            scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
-                    dr.mShowing[Drawables.BOTTOM].draw(canvas);
-                    canvas.restore();
-                }
+            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+            // Make sure to update invalidateDrawable() when changing this code.
+            if (dr.mShowing[Drawables.LEFT] != null) {
+                canvas.save();
+                canvas.translate(scrollX + mPaddingLeft + leftOffset,
+                        scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
+                dr.mShowing[Drawables.LEFT].draw(canvas);
+                canvas.restore();
             }
 
-            int color = mCurTextColor;
-
-            if (mLayout == null) {
-                assumeLayout();
+            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+            // Make sure to update invalidateDrawable() when changing this code.
+            if (dr.mShowing[Drawables.RIGHT] != null) {
+                canvas.save();
+                canvas.translate(scrollX + right - left - mPaddingRight
+                        - dr.mDrawableSizeRight - rightOffset,
+                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
+                dr.mShowing[Drawables.RIGHT].draw(canvas);
+                canvas.restore();
             }
 
-            Layout layout = mLayout;
-
-            if (mHint != null && !mHideHint && mText.length() == 0) {
-                if (mHintTextColor != null) {
-                    color = mCurHintTextColor;
-                }
-
-                layout = mHintLayout;
+            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+            // Make sure to update invalidateDrawable() when changing this code.
+            if (dr.mShowing[Drawables.TOP] != null) {
+                canvas.save();
+                canvas.translate(scrollX + compoundPaddingLeft
+                        + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
+                dr.mShowing[Drawables.TOP].draw(canvas);
+                canvas.restore();
             }
 
-            mTextPaint.setColor(color);
-            mTextPaint.drawableState = getDrawableState();
-
-            canvas.save();
-            /*  Would be faster if we didn't have to do this. Can we chop the
-                (displayable) text so that we don't need to do this ever?
-            */
-
-            int extendedPaddingTop = getExtendedPaddingTop();
-            int extendedPaddingBottom = getExtendedPaddingBottom();
-
-            final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
-            final int maxScrollY = mLayout.getHeight() - vspace;
-
-            float clipLeft = compoundPaddingLeft + scrollX;
-            float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
-            float clipRight = right - left - getCompoundPaddingRight() + scrollX;
-            float clipBottom = bottom - top + scrollY
-                    - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
-
-            if (mShadowRadius != 0) {
-                clipLeft += Math.min(0, mShadowDx - mShadowRadius);
-                clipRight += Math.max(0, mShadowDx + mShadowRadius);
-
-                clipTop += Math.min(0, mShadowDy - mShadowRadius);
-                clipBottom += Math.max(0, mShadowDy + mShadowRadius);
+            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+            // Make sure to update invalidateDrawable() when changing this code.
+            if (dr.mShowing[Drawables.BOTTOM] != null) {
+                canvas.save();
+                canvas.translate(scrollX + compoundPaddingLeft
+                        + (hspace - dr.mDrawableWidthBottom) / 2,
+                         scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
+                dr.mShowing[Drawables.BOTTOM].draw(canvas);
+                canvas.restore();
             }
-
-            canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
-
-            int voffsetText = 0;
-            int voffsetCursor = 0;
-
-            // translate in by our padding
-            /* shortcircuit calling getVerticaOffset() */
-            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
-                voffsetText = getVerticalOffset(false);
-                voffsetCursor = getVerticalOffset(true);
-            }
-            canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
-
-            final int layoutDirection = getLayoutDirection();
-            final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
-            if (isMarqueeFadeEnabled()) {
-                if (!mSingleLine && getLineCount() == 1 && canMarquee()
-                        && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
-                    final int width = mRight - mLeft;
-                    final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
-                    final float dx = mLayout.getLineRight(0) - (width - padding);
-                    canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
-                }
-
-                if (mMarquee != null && mMarquee.isRunning()) {
-                    final float dx = -mMarquee.getScroll();
-                    canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
-                }
-            }
-
-            final int cursorOffsetVertical = voffsetCursor - voffsetText;
-
-            maybeUpdateHighlightPaths();
-            // If there is a gesture preview highlight, then the selection or cursor is not drawn.
-            Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
-            if (mEditor != null) {
-                mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
-                        mHighlightPaint, cursorOffsetVertical);
-            } else {
-                layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
-                        cursorOffsetVertical);
-            }
-
-            if (mMarquee != null && mMarquee.shouldDrawGhost()) {
-                final float dx = mMarquee.getGhostOffset();
-                canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
-                layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
-                        cursorOffsetVertical);
-            }
-
-            canvas.restore();
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
         }
+
+        int color = mCurTextColor;
+
+        if (mLayout == null) {
+            assumeLayout();
+        }
+
+        Layout layout = mLayout;
+
+        if (mHint != null && !mHideHint && mText.length() == 0) {
+            if (mHintTextColor != null) {
+                color = mCurHintTextColor;
+            }
+
+            layout = mHintLayout;
+        }
+
+        mTextPaint.setColor(color);
+        mTextPaint.drawableState = getDrawableState();
+
+        canvas.save();
+        /*  Would be faster if we didn't have to do this. Can we chop the
+            (displayable) text so that we don't need to do this ever?
+        */
+
+        int extendedPaddingTop = getExtendedPaddingTop();
+        int extendedPaddingBottom = getExtendedPaddingBottom();
+
+        final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
+        final int maxScrollY = mLayout.getHeight() - vspace;
+
+        float clipLeft = compoundPaddingLeft + scrollX;
+        float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
+        float clipRight = right - left - getCompoundPaddingRight() + scrollX;
+        float clipBottom = bottom - top + scrollY
+                - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
+
+        if (mShadowRadius != 0) {
+            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
+            clipRight += Math.max(0, mShadowDx + mShadowRadius);
+
+            clipTop += Math.min(0, mShadowDy - mShadowRadius);
+            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
+        }
+
+        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
+
+        int voffsetText = 0;
+        int voffsetCursor = 0;
+
+        // translate in by our padding
+        /* shortcircuit calling getVerticaOffset() */
+        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
+            voffsetText = getVerticalOffset(false);
+            voffsetCursor = getVerticalOffset(true);
+        }
+        canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
+
+        final int layoutDirection = getLayoutDirection();
+        final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
+        if (isMarqueeFadeEnabled()) {
+            if (!mSingleLine && getLineCount() == 1 && canMarquee()
+                    && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
+                final int width = mRight - mLeft;
+                final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
+                final float dx = mLayout.getLineRight(0) - (width - padding);
+                canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
+            }
+
+            if (mMarquee != null && mMarquee.isRunning()) {
+                final float dx = -mMarquee.getScroll();
+                canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
+            }
+        }
+
+        final int cursorOffsetVertical = voffsetCursor - voffsetText;
+
+        maybeUpdateHighlightPaths();
+        // If there is a gesture preview highlight, then the selection or cursor is not drawn.
+        Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
+        if (mEditor != null) {
+            mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
+                    mHighlightPaint, cursorOffsetVertical);
+        } else {
+            layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
+                    cursorOffsetVertical);
+        }
+
+        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
+            final float dx = mMarquee.getGhostOffset();
+            canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
+            layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
+                    cursorOffsetVertical);
+        }
+
+        canvas.restore();
     }
 
     @Override
@@ -11260,201 +11254,192 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "TextView.onMeasure");
-        try {
-            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
-            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
-            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
-            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
 
-            int width;
-            int height;
+        int width;
+        int height;
 
-            BoringLayout.Metrics boring = UNKNOWN_BORING;
-            BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
+        BoringLayout.Metrics boring = UNKNOWN_BORING;
+        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
 
-            if (mTextDir == null) {
-                mTextDir = getTextDirectionHeuristic();
+        if (mTextDir == null) {
+            mTextDir = getTextDirectionHeuristic();
+        }
+
+        int des = -1;
+        boolean fromexisting = false;
+        final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
+                ?  (float) widthSize : Float.MAX_VALUE;
+
+        if (widthMode == MeasureSpec.EXACTLY) {
+            // Parent has told us how big to be. So be it.
+            width = widthSize;
+        } else {
+            if (mLayout != null && mEllipsize == null) {
+                des = desired(mLayout, mUseBoundsForWidth);
             }
 
-            int des = -1;
-            boolean fromexisting = false;
-            final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
-                    ? (float) widthSize : Float.MAX_VALUE;
-
-            if (widthMode == MeasureSpec.EXACTLY) {
-                // Parent has told us how big to be. So be it.
-                width = widthSize;
+            if (des < 0) {
+                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
+                        isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
+                        mBoring);
+                if (boring != null) {
+                    mBoring = boring;
+                }
             } else {
-                if (mLayout != null && mEllipsize == null) {
-                    des = desired(mLayout, mUseBoundsForWidth);
-                }
+                fromexisting = true;
+            }
 
+            if (boring == null || boring == UNKNOWN_BORING) {
                 if (des < 0) {
-                    boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
-                            isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
-                            mBoring);
-                    if (boring != null) {
-                        mBoring = boring;
-                    }
+                    des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
+                            mTransformed.length(), mTextPaint, mTextDir, widthLimit,
+                            mUseBoundsForWidth));
+                }
+                width = des;
+            } else {
+                if (mUseBoundsForWidth) {
+                    RectF bbox = boring.getDrawingBoundingBox();
+                    float rightMax = Math.max(bbox.right, boring.width);
+                    float leftMin = Math.min(bbox.left, 0);
+                    width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin));
                 } else {
-                    fromexisting = true;
+                    width = boring.width;
+                }
+            }
+
+            final Drawables dr = mDrawables;
+            if (dr != null) {
+                width = Math.max(width, dr.mDrawableWidthTop);
+                width = Math.max(width, dr.mDrawableWidthBottom);
+            }
+
+            if (mHint != null) {
+                int hintDes = -1;
+                int hintWidth;
+
+                if (mHintLayout != null && mEllipsize == null) {
+                    hintDes = desired(mHintLayout, mUseBoundsForWidth);
                 }
 
-                if (boring == null || boring == UNKNOWN_BORING) {
-                    if (des < 0) {
-                        des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
-                                mTransformed.length(), mTextPaint, mTextDir, widthLimit,
+                if (hintDes < 0) {
+                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
+                            isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
+                            mHintBoring);
+                    if (hintBoring != null) {
+                        mHintBoring = hintBoring;
+                    }
+                }
+
+                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
+                    if (hintDes < 0) {
+                        hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
+                                mHint.length(), mTextPaint, mTextDir, widthLimit,
                                 mUseBoundsForWidth));
                     }
-                    width = des;
+                    hintWidth = hintDes;
                 } else {
-                    if (mUseBoundsForWidth) {
-                        RectF bbox = boring.getDrawingBoundingBox();
-                        float rightMax = Math.max(bbox.right, boring.width);
-                        float leftMin = Math.min(bbox.left, 0);
-                        width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin));
-                    } else {
-                        width = boring.width;
-                    }
+                    hintWidth = hintBoring.width;
                 }
 
-                final Drawables dr = mDrawables;
-                if (dr != null) {
-                    width = Math.max(width, dr.mDrawableWidthTop);
-                    width = Math.max(width, dr.mDrawableWidthBottom);
-                }
-
-                if (mHint != null) {
-                    int hintDes = -1;
-                    int hintWidth;
-
-                    if (mHintLayout != null && mEllipsize == null) {
-                        hintDes = desired(mHintLayout, mUseBoundsForWidth);
-                    }
-
-                    if (hintDes < 0) {
-                        hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
-                                isFallbackLineSpacingForBoringLayout(),
-                                getResolvedMinimumFontMetrics(),
-                                mHintBoring);
-                        if (hintBoring != null) {
-                            mHintBoring = hintBoring;
-                        }
-                    }
-
-                    if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
-                        if (hintDes < 0) {
-                            hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
-                                    mHint.length(), mTextPaint, mTextDir, widthLimit,
-                                    mUseBoundsForWidth));
-                        }
-                        hintWidth = hintDes;
-                    } else {
-                        hintWidth = hintBoring.width;
-                    }
-
-                    if (hintWidth > width) {
-                        width = hintWidth;
-                    }
-                }
-
-                width += getCompoundPaddingLeft() + getCompoundPaddingRight();
-
-                if (mMaxWidthMode == EMS) {
-                    width = Math.min(width, mMaxWidth * getLineHeight());
-                } else {
-                    width = Math.min(width, mMaxWidth);
-                }
-
-                if (mMinWidthMode == EMS) {
-                    width = Math.max(width, mMinWidth * getLineHeight());
-                } else {
-                    width = Math.max(width, mMinWidth);
-                }
-
-                // Check against our minimum width
-                width = Math.max(width, getSuggestedMinimumWidth());
-
-                if (widthMode == MeasureSpec.AT_MOST) {
-                    width = Math.min(widthSize, width);
+                if (hintWidth > width) {
+                    width = hintWidth;
                 }
             }
 
-            int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
-            int unpaddedWidth = want;
+            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
 
-            if (mHorizontallyScrolling) want = VERY_WIDE;
-
-            int hintWant = want;
-            int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
-
-            if (mLayout == null) {
-                makeNewLayout(want, hintWant, boring, hintBoring,
-                        width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
+            if (mMaxWidthMode == EMS) {
+                width = Math.min(width, mMaxWidth * getLineHeight());
             } else {
-                final boolean layoutChanged =
-                        (mLayout.getWidth() != want) || (hintWidth != hintWant)
-                                || (mLayout.getEllipsizedWidth()
-                                != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
-
-                final boolean widthChanged = (mHint == null) && (mEllipsize == null)
-                        && (want > mLayout.getWidth())
-                        && (mLayout instanceof BoringLayout
-                        || (fromexisting && des >= 0 && des <= want));
-
-                final boolean maximumChanged =
-                        (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
-
-                if (layoutChanged || maximumChanged) {
-                    if (!maximumChanged && widthChanged) {
-                        mLayout.increaseWidthTo(want);
-                    } else {
-                        makeNewLayout(want, hintWant, boring, hintBoring,
-                                width - getCompoundPaddingLeft() - getCompoundPaddingRight(),
-                                false);
-                    }
-                } else {
-                    // Nothing has changed
-                }
+                width = Math.min(width, mMaxWidth);
             }
 
-            if (heightMode == MeasureSpec.EXACTLY) {
-                // Parent has told us how big to be. So be it.
-                height = heightSize;
-                mDesiredHeightAtMeasure = -1;
+            if (mMinWidthMode == EMS) {
+                width = Math.max(width, mMinWidth * getLineHeight());
             } else {
-                int desired = getDesiredHeight();
-
-                height = desired;
-                mDesiredHeightAtMeasure = desired;
-
-                if (heightMode == MeasureSpec.AT_MOST) {
-                    height = Math.min(desired, heightSize);
-                }
+                width = Math.max(width, mMinWidth);
             }
 
-            int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
-            if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
-                unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
-            }
+            // Check against our minimum width
+            width = Math.max(width, getSuggestedMinimumWidth());
 
-            /*
-             * We didn't let makeNewLayout() register to bring the cursor into view,
-             * so do it here if there is any possibility that it is needed.
-             */
-            if (mMovement != null
-                    || mLayout.getWidth() > unpaddedWidth
-                    || mLayout.getHeight() > unpaddedHeight) {
-                registerForPreDraw();
-            } else {
-                scrollTo(0, 0);
+            if (widthMode == MeasureSpec.AT_MOST) {
+                width = Math.min(widthSize, width);
             }
-
-            setMeasuredDimension(width, height);
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
         }
+
+        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
+        int unpaddedWidth = want;
+
+        if (mHorizontallyScrolling) want = VERY_WIDE;
+
+        int hintWant = want;
+        int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
+
+        if (mLayout == null) {
+            makeNewLayout(want, hintWant, boring, hintBoring,
+                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
+        } else {
+            final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
+                    || (mLayout.getEllipsizedWidth()
+                            != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
+
+            final boolean widthChanged = (mHint == null) && (mEllipsize == null)
+                    && (want > mLayout.getWidth())
+                    && (mLayout instanceof BoringLayout
+                            || (fromexisting && des >= 0 && des <= want));
+
+            final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
+
+            if (layoutChanged || maximumChanged) {
+                if (!maximumChanged && widthChanged) {
+                    mLayout.increaseWidthTo(want);
+                } else {
+                    makeNewLayout(want, hintWant, boring, hintBoring,
+                            width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
+                }
+            } else {
+                // Nothing has changed
+            }
+        }
+
+        if (heightMode == MeasureSpec.EXACTLY) {
+            // Parent has told us how big to be. So be it.
+            height = heightSize;
+            mDesiredHeightAtMeasure = -1;
+        } else {
+            int desired = getDesiredHeight();
+
+            height = desired;
+            mDesiredHeightAtMeasure = desired;
+
+            if (heightMode == MeasureSpec.AT_MOST) {
+                height = Math.min(desired, heightSize);
+            }
+        }
+
+        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
+        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
+            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
+        }
+
+        /*
+         * We didn't let makeNewLayout() register to bring the cursor into view,
+         * so do it here if there is any possibility that it is needed.
+         */
+        if (mMovement != null
+                || mLayout.getWidth() > unpaddedWidth
+                || mLayout.getHeight() > unpaddedHeight) {
+            registerForPreDraw();
+        } else {
+            scrollTo(0, 0);
+        }
+
+        setMeasuredDimension(width, height);
     }
 
     /**
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 7a01ad3..289c5cf 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -68,7 +68,8 @@
             Flags::enableDesktopWindowingTaskbarRunningApps, true),
     // TODO: b/369763947 - remove this once ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS is ramped up
     ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
-    ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
+    ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS(
+            Flags::enableDesktopWindowingEnterTransitions, false),
     ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false),
     ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
             Flags::enableWindowingTransitionHandlersObservers, false),
@@ -77,7 +78,15 @@
     ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS(
             Flags::enableDesktopAppLaunchTransitions, false),
     ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, false),
-    ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true);
+    ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true),
+    ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX(
+            Flags::enableDesktopWindowingEnterTransitionBugfix, false),
+    ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX(
+            Flags::enableDesktopWindowingExitTransitionsBugfix, false),
+    ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX(
+            Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, false),
+    ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(
+            Flags::enableDesktopAppLaunchTransitionsBugfix, false);
 
     private static final String TAG = "DesktopModeFlagsUtil";
     // Function called to obtain aconfig flag value.
diff --git a/core/java/android/window/KeyguardState.java b/core/java/android/window/KeyguardState.java
index 6584d30..159275a 100644
--- a/core/java/android/window/KeyguardState.java
+++ b/core/java/android/window/KeyguardState.java
@@ -30,28 +30,23 @@
  */
 public final class KeyguardState implements Parcelable {
 
-    private final int mDisplayId;
-
     private final boolean mKeyguardShowing;
 
     private final boolean mAodShowing;
 
 
-    private KeyguardState(int displayId, boolean keyguardShowing, boolean aodShowing) {
-        mDisplayId = displayId;
+    private KeyguardState(boolean keyguardShowing, boolean aodShowing) {
         mKeyguardShowing = keyguardShowing;
         mAodShowing = aodShowing;
     }
 
     private KeyguardState(Parcel in) {
-        mDisplayId = in.readInt();
         mKeyguardShowing = in.readBoolean();
         mAodShowing = in.readBoolean();
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mDisplayId);
         dest.writeBoolean(mKeyguardShowing);
         dest.writeBoolean(mAodShowing);
     }
@@ -70,13 +65,6 @@
                 }
             };
 
-    /**
-     * Gets the display id of this {@link KeyguardState}.
-     */
-    public int getDisplayId() {
-        return mDisplayId;
-    }
-
     /** Returns the keyguard showing value. */
     public boolean getKeyguardShowing() {
         return mKeyguardShowing;
@@ -89,15 +77,14 @@
 
     @Override
     public String toString() {
-        return "KeyguardState{ displayId=" + mDisplayId
-                + ", keyguardShowing=" + mKeyguardShowing
+        return "KeyguardState{ keyguardShowing=" + mKeyguardShowing
                 + ", aodShowing=" + mAodShowing
                 + '}';
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mDisplayId, mKeyguardShowing, mAodShowing);
+        return Objects.hash(mKeyguardShowing, mAodShowing);
     }
 
     @Override
@@ -105,8 +92,7 @@
         if (!(obj instanceof KeyguardState other)) {
             return false;
         }
-        return mDisplayId == other.mDisplayId
-                && mKeyguardShowing == other.mKeyguardShowing
+        return mKeyguardShowing == other.mKeyguardShowing
                 && mAodShowing == other.mAodShowing;
     }
 
@@ -117,18 +103,11 @@
 
     /** Builder to construct the {@link KeyguardState}. */
     public static final class Builder {
-
-        private final int mDisplayId;
-
         private boolean mKeyguardShowing;
 
         private boolean mAodShowing;
 
-        /**
-         * @param displayId the display of this {@link KeyguardState}.
-         */
-        public Builder(int displayId) {
-            mDisplayId = displayId;
+        public Builder() {
         }
 
         /**
@@ -154,7 +133,7 @@
          */
         @NonNull
         public KeyguardState build() {
-            return new KeyguardState(mDisplayId, mKeyguardShowing, mAodShowing);
+            return new KeyguardState(mKeyguardShowing, mAodShowing);
         }
     }
 }
diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java
index cc2329fc..f8899c5 100644
--- a/core/java/android/window/SystemPerformanceHinter.java
+++ b/core/java/android/window/SystemPerformanceHinter.java
@@ -163,7 +163,6 @@
     // The active sessions
     private final ArrayList<HighPerfSession> mActiveSessions = new ArrayList<>();
     private final SurfaceControl.Transaction mTransaction;
-    private final PerformanceHintManager mPerfHintManager;
     private @Nullable PerformanceHintManager.Session mAdpfSession;
     private @Nullable DisplayRootProvider mDisplayRootProvider;
 
@@ -184,7 +183,6 @@
             @Nullable DisplayRootProvider displayRootProvider,
             @Nullable Supplier<SurfaceControl.Transaction> transactionSupplier) {
         mDisplayRootProvider = displayRootProvider;
-        mPerfHintManager = context.getSystemService(PerformanceHintManager.class);
         mTransaction = transactionSupplier != null
                 ? transactionSupplier.get()
                 : new SurfaceControl.Transaction();
@@ -273,7 +271,7 @@
                 asyncTraceBegin(HINT_SF_EARLY_WAKEUP, Display.INVALID_DISPLAY);
             }
         }
-        if (nowEnabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
+        if (mAdpfSession != null && nowEnabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
             mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_UP);
             if (isTraceEnabled) {
                 asyncTraceBegin(HINT_ADPF, Display.INVALID_DISPLAY);
@@ -323,7 +321,7 @@
                 asyncTraceEnd(HINT_SF_EARLY_WAKEUP);
             }
         }
-        if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
+        if (mAdpfSession != null && nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
             mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
             if (isTraceEnabled) {
                 asyncTraceEnd(HINT_ADPF);
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index fdaab66..2b370b9 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -94,6 +94,23 @@
         mController.attachToDisplayArea(mType, getDisplayId(), mOptions);
     }
 
+    /**
+     * Updates this context to a new displayId.
+     * <p>
+     * Note that this doesn't re-parent previously attached windows (they should be removed and
+     * re-added manually after this is called). Resources associated with this context will have
+     * the correct value and configuration for the new display after this is called.
+     */
+    @Override
+    public void updateDisplay(int displayId) {
+        if (displayId == getDisplayId()) {
+            return;
+        }
+        super.updateDisplay(displayId);
+        mController.detachIfNeeded();
+        mController.attachToDisplayArea(mType, displayId, mOptions);
+    }
+
     @Override
     public Object getSystemService(String name) {
         if (WINDOW_SERVICE.equals(name)) {
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 15adc80..6e76d8d 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -19,6 +19,8 @@
 import static android.window.ConfigurationHelper.isDifferentDisplay;
 import static android.window.ConfigurationHelper.shouldUpdateResources;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
@@ -104,7 +106,7 @@
      * @param newConfig the updated {@link Configuration}
      * @param newDisplayId the updated {@link android.view.Display} ID
      */
-    @VisibleForTesting
+    @VisibleForTesting(visibility = PACKAGE)
     @MainThread
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
         onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */);
@@ -113,7 +115,7 @@
     /**
      * Posts an {@link #onConfigurationChanged} to the main thread.
      */
-    @VisibleForTesting
+    @VisibleForTesting(visibility = PACKAGE)
     public void postOnConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId) {
         mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig,
                 newDisplayId, true /* shouldReportConfigChange */).recycleOnUse());
@@ -232,7 +234,7 @@
     /**
      * Called when the attached window is removed from the display.
      */
-    @VisibleForTesting
+    @VisibleForTesting(visibility = PACKAGE)
     @MainThread
     public void onWindowTokenRemoved() {
         final Context context = mContextRef.get();
diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java
index abf7bb1..fa34595 100644
--- a/core/java/android/window/WindowTokenClientController.java
+++ b/core/java/android/window/WindowTokenClientController.java
@@ -28,7 +28,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 import android.view.IWindowManager;
 import android.view.WindowManagerGlobal;
@@ -50,9 +50,9 @@
     private final IApplicationThread mAppThread = ActivityThread.currentActivityThread()
             .getApplicationThread();
 
-    /** Mapping from a client defined token to the {@link WindowTokenClient} it represents. */
+    /** Attached {@link WindowTokenClient}. */
     @GuardedBy("mLock")
-    private final ArrayMap<IBinder, WindowTokenClient> mWindowTokenClientMap = new ArrayMap<>();
+    private final ArraySet<WindowTokenClient> mWindowTokenClients = new ArraySet<>();
 
     /** Gets the singleton controller. */
     @NonNull
@@ -85,11 +85,15 @@
     /** Gets the {@link WindowContext} instance for the token. */
     @Nullable
     public Context getWindowContext(@NonNull IBinder clientToken) {
-        final WindowTokenClient windowTokenClient;
-        synchronized (mLock) {
-            windowTokenClient = mWindowTokenClientMap.get(clientToken);
+        if (!(clientToken instanceof WindowTokenClient windowTokenClient)) {
+            return null;
         }
-        return windowTokenClient != null ? windowTokenClient.getContext() : null;
+        synchronized (mLock) {
+            if (!mWindowTokenClients.contains(windowTokenClient)) {
+                return null;
+            }
+        }
+        return windowTokenClient.getContext();
     }
 
     /**
@@ -126,8 +130,14 @@
      */
     public boolean attachToDisplayContent(@NonNull WindowTokenClient client, int displayId) {
         final IWindowManager wms = getWindowManagerService();
-        // #createSystemUiContext may call this method before WindowManagerService is initialized.
         if (wms == null) {
+            // #createSystemUiContext may call this method before WindowManagerService is
+            // initialized.
+            // Regardless of whether or not it is ready, keep track of the token so that when WMS
+            // is initialized later, the SystemUiContext will start reporting from
+            // DisplayContent#registerSystemUiContext, and WindowTokenClientController can report
+            // the Configuration to the correct client.
+            recordWindowContextToken(client);
             return false;
         }
         final WindowContextInfo info;
@@ -170,12 +180,18 @@
     /** Detaches a {@link WindowTokenClient} from associated WindowContainer if there's one. */
     public void detachIfNeeded(@NonNull WindowTokenClient client) {
         synchronized (mLock) {
-            if (mWindowTokenClientMap.remove(client) == null) {
+            if (!mWindowTokenClients.remove(client)) {
                 return;
             }
         }
+        final IWindowManager wms = getWindowManagerService();
+        if (wms == null) {
+            // #createSystemUiContext may call this method before WindowManagerService is
+            // initialized. If it is GC'ed before WMS is initialized, skip calling into WMS.
+            return;
+        }
         try {
-            getWindowManagerService().detachWindowContext(client);
+            wms.detachWindowContext(client);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -183,9 +199,7 @@
 
     private void onWindowContextTokenAttached(@NonNull WindowTokenClient client,
             @NonNull WindowContextInfo info, boolean shouldReportConfigChange) {
-        synchronized (mLock) {
-            mWindowTokenClientMap.put(client, client);
-        }
+        recordWindowContextToken(client);
         if (shouldReportConfigChange) {
             // Should trigger an #onConfigurationChanged callback to the WindowContext. Post the
             // dispatch in the next loop to prevent the callback from being dispatched before
@@ -199,10 +213,16 @@
         }
     }
 
+    private void recordWindowContextToken(@NonNull WindowTokenClient client) {
+        synchronized (mLock) {
+            mWindowTokenClients.add(client);
+        }
+    }
+
     /** Called when receives {@link WindowContextInfoChangeItem}. */
     public void onWindowContextInfoChanged(@NonNull IBinder clientToken,
             @NonNull WindowContextInfo info) {
-        final WindowTokenClient windowTokenClient = getWindowTokenClient(clientToken);
+        final WindowTokenClient windowTokenClient = getWindowTokenClientIfAttached(clientToken);
         if (windowTokenClient != null) {
             windowTokenClient.onConfigurationChanged(info.getConfiguration(), info.getDisplayId());
         }
@@ -210,20 +230,23 @@
 
     /** Called when receives {@link WindowContextWindowRemovalItem}. */
     public void onWindowContextWindowRemoved(@NonNull IBinder clientToken) {
-        final WindowTokenClient windowTokenClient = getWindowTokenClient(clientToken);
+        final WindowTokenClient windowTokenClient = getWindowTokenClientIfAttached(clientToken);
         if (windowTokenClient != null) {
             windowTokenClient.onWindowTokenRemoved();
         }
     }
 
     @Nullable
-    private WindowTokenClient getWindowTokenClient(@NonNull IBinder clientToken) {
-        final WindowTokenClient windowTokenClient;
-        synchronized (mLock) {
-            windowTokenClient = mWindowTokenClientMap.get(clientToken);
+    private WindowTokenClient getWindowTokenClientIfAttached(@NonNull IBinder clientToken) {
+        if (!(clientToken instanceof WindowTokenClient windowTokenClient)) {
+            Log.e(TAG, "getWindowTokenClient failed for non-window token " + clientToken);
+            return null;
         }
-        if (windowTokenClient == null) {
-            Log.w(TAG, "Can't find attached WindowTokenClient for " + clientToken);
+        synchronized (mLock) {
+            if (!mWindowTokenClients.contains(windowTokenClient)) {
+                Log.w(TAG, "Can't find attached WindowTokenClient for " + clientToken);
+                return null;
+            }
         }
         return windowTokenClient;
     }
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 7ad14b0..801698c 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
@@ -112,6 +112,18 @@
 }
 
 flag {
+  name: "app_compat_async_relayout"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether we use the SurfaceViewHost overload to apply a change in /n"
+               "position and size in a Transaction provided by a callback invoked /n"
+               "after the View relayout."
+  bug: "322463856"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "app_compat_refactoring"
   namespace: "large_screen_experiences_app_compat"
   description: "Whether the changes about app compat refactoring are enabled./n"
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index eebdeadc..5053aa8 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -58,10 +58,13 @@
 }
 
 flag {
-    name: "enable_desktop_windowing_scvh_cache"
+    name: "enable_desktop_windowing_scvh_cache_bug_fix"
     namespace: "lse_desktop_experience"
     description: "Enables a SurfaceControlViewHost cache for window decorations"
-    bug: "345146928"
+    bug: "360452034"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
 
 flag {
@@ -256,6 +259,16 @@
 }
 
 flag {
+    name: "enable_desktop_windowing_enter_transition_bugfix"
+    namespace: "lse_desktop_experience"
+    description: "Enables enter desktop windowing transition & motion polish changes"
+    bug: "380224875"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "enable_desktop_windowing_exit_transitions"
     namespace: "lse_desktop_experience"
     description: "Enables exit desktop windowing transition & motion polish changes"
@@ -263,6 +276,16 @@
 }
 
 flag {
+    name: "enable_desktop_windowing_exit_transitions_bugfix"
+    namespace: "lse_desktop_experience"
+    description: "Enables exit desktop windowing transition & motion polish changes"
+    bug: "380224768"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "enable_compat_ui_visibility_status"
     namespace: "lse_desktop_experience"
     description: "Enables the tracking of the status for compat ui elements."
@@ -274,6 +297,14 @@
     namespace: "lse_desktop_experience"
     description: "Enables desktop windowing app-to-web education"
     bug: "348205896"
+    is_exported: true
+}
+
+flag {
+    name: "enable_desktop_windowing_app_to_web_education_integration"
+    namespace: "lse_desktop_experience"
+    description: "Enables desktop windowing App-to-Web education and integrates new APIs"
+    bug: "380272815"
 }
 
 flag {
@@ -346,6 +377,16 @@
 }
 
 flag {
+    name: "enable_desktop_app_launch_alttab_transitions_bugfix"
+    namespace: "lse_desktop_experience"
+    description: "Enables custom transitions for alt-tab app launches in Desktop Mode."
+    bug: "380225486"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "enable_desktop_app_launch_transitions"
     namespace: "lse_desktop_experience"
     description: "Enables custom transitions for app launches in Desktop Mode."
@@ -353,6 +394,16 @@
 }
 
 flag {
+    name: "enable_desktop_app_launch_transitions_bugfix"
+    namespace: "lse_desktop_experience"
+    description: "Enables custom transitions for app launches in Desktop Mode."
+    bug: "380224832"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "enable_desktop_system_dialogs_transitions"
     namespace: "lse_desktop_experience"
     description: "Enables custom transitions for system dialogs in Desktop Mode."
@@ -409,4 +460,18 @@
     namespace: "lse_desktop_experience"
     description: "Enable multiple desktop sessions for desktop windowing."
     bug: "379158791"
+}
+
+flag {
+    name: "enable_connected_displays_dnd"
+    namespace: "lse_desktop_experience"
+    description: "Enable drag-and-drop between connected displays."
+    bug: "381793841"
+}
+
+flag {
+    name: "enable_connected_displays_window_drag"
+    namespace: "lse_desktop_experience"
+    description: "Enable window drag between connected displays."
+    bug: "381172172"
 }
\ No newline at end of file
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index b2f125d..d5ba32c 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -48,6 +48,7 @@
     namespace: "responsible_apis"
     description: "Introduce additional start modes."
     bug: "352182359"
+    is_exported: true
 }
 
 flag {
@@ -55,6 +56,7 @@
     namespace: "responsible_apis"
     description: "Add options parameter to IntentSender.sendIntent."
     bug: "339720406"
+    is_exported: true
 }
 
 flag {
@@ -63,6 +65,7 @@
     description: "Strict mode flag"
     bug: "324089586"
     is_fixed_read_only: true
+    is_exported: true
 }
 
 flag {
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index efacc34..1ddfe87 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -14,6 +14,7 @@
   namespace: "systemui"
   description: "Support storing different wallpaper crops for different display dimensions. Only effective after rebooting."
   bug: "281648899"
+  is_exported: true
 }
 
 flag {
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index ebbe483..30f0c73 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -368,6 +368,7 @@
     description: "PRIORITY_SYSTEM_NAVIGATION_OBSERVER predictive back API extension"
     is_fixed_read_only: true
     bug: "362938401"
+    is_exported: true
 }
 
 flag {
@@ -376,6 +377,7 @@
     description: "expose timestamp in BackEvent (API extension)"
     is_fixed_read_only: true
     bug: "362938401"
+    is_exported: true
 }
 
 flag {
@@ -384,6 +386,7 @@
     description: "EDGE_NONE swipeEdge option in BackEvent"
     is_fixed_read_only: true
     bug: "362938401"
+    is_exported: true
 }
 
 flag {
@@ -422,6 +425,7 @@
     description: "Provide pre-make predictive back API extension"
     is_fixed_read_only: true
     bug: "362938401"
+    is_exported: true
 }
 
 flag {
@@ -449,3 +453,11 @@
     is_fixed_read_only: true
     bug: "376407910"
 }
+
+flag {
+  name: "relative_insets"
+  namespace: "windowing_frontend"
+  description: "Support insets definition and calculation relative to task bounds."
+  bug: "277292497"
+  is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index b7012b6..d0d4af6 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -100,6 +100,7 @@
     name: "touch_pass_through_opt_in"
     description: "Requires apps to opt-in to overlay pass through touches and provide APIs to opt-in"
     bug: "358129114"
+    is_exported: true
 }
 
 flag {
@@ -115,3 +116,14 @@
     description: "Relax the assumption of non-match parent activity"
     bug: "356277166"
 }
+
+flag {
+    namespace: "windowing_sdk"
+    name: "allow_multiple_adjacent_task_fragments"
+    description: "Refactor to allow more than 2 adjacent TaskFragments"
+    bug: "373709676"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index e8831ec..a27eeb8 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -58,7 +58,6 @@
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
 import android.widget.Toast;
 
 import com.android.internal.R;
@@ -289,9 +288,7 @@
                     cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStatus.SHOWN,
                     userId);
         } else {
-            if (Flags.restoreA11yShortcutTargetService()) {
-                enableDefaultHardwareShortcut(userId);
-            }
+            enableDefaultHardwareShortcut(userId);
             playNotificationTone();
             if (mAlertDialog != null) {
                 mAlertDialog.dismiss();
@@ -360,43 +357,44 @@
 
         // Avoid non-a11y users accidentally turning shortcut on without reading this carefully.
         // Put "don't turn on" as the primary action.
-        final AlertDialog alertDialog = mFrameworkObjectProvider.getAlertDialogBuilder(
-                        // Use SystemUI context so we pick up any theme set in a vendor overlay
-                        mFrameworkObjectProvider.getSystemUiContext())
-                .setTitle(getShortcutWarningTitle(targets))
-                .setMessage(getShortcutWarningMessage(targets))
-                .setCancelable(false)
-                .setNegativeButton(R.string.accessibility_shortcut_on,
-                        (DialogInterface d, int which) -> enableDefaultHardwareShortcut(userId))
-                .setPositiveButton(R.string.accessibility_shortcut_off,
-                        (DialogInterface d, int which) -> {
-                            Set<String> targetServices =
-                                    ShortcutUtils.getShortcutTargetsFromSettings(
-                                            mContext,
-                                            HARDWARE,
+        final AlertDialog alertDialog =
+                mFrameworkObjectProvider
+                        .getAlertDialogBuilder(
+                                // Use SystemUI context so we pick up any theme set in a vendor
+                                // overlay
+                                mFrameworkObjectProvider.getSystemUiContext())
+                        .setTitle(getShortcutWarningTitle(targets))
+                        .setMessage(getShortcutWarningMessage(targets))
+                        .setCancelable(false)
+                        .setNegativeButton(
+                                R.string.accessibility_shortcut_on,
+                                (DialogInterface d, int which) ->
+                                        enableDefaultHardwareShortcut(userId))
+                        .setPositiveButton(
+                                R.string.accessibility_shortcut_off,
+                                (DialogInterface d, int which) -> {
+                                    Set<String> targetServices =
+                                            ShortcutUtils.getShortcutTargetsFromSettings(
+                                                    mContext, HARDWARE, userId);
+                                    am.enableShortcutsForTargets(
+                                            false, HARDWARE, targetServices, userId);
+                                    // If canceled, treat as if the dialog has never been shown
+                                    Settings.Secure.putIntForUser(
+                                            mContext.getContentResolver(),
+                                            Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                                            DialogStatus.NOT_SHOWN,
                                             userId);
-                            if (Flags.migrateEnableShortcuts()) {
-                                am.enableShortcutsForTargets(
-                                        false, HARDWARE, targetServices, userId);
-                            } else {
-                                Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
-                                        userId);
-                                ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
-                                        mContext, targetServices, userId);
-                            }
-                            // If canceled, treat as if the dialog has never been shown
-                            Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                                    Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
-                                    DialogStatus.NOT_SHOWN, userId);
-                        })
-                .setOnCancelListener((DialogInterface d) -> {
-                    // If canceled, treat as if the dialog has never been shown
-                    Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                            Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
-                            DialogStatus.NOT_SHOWN, userId);
-                })
-                .create();
+                                })
+                        .setOnCancelListener(
+                                (DialogInterface d) -> {
+                                    // If canceled, treat as if the dialog has never been shown
+                                    Settings.Secure.putIntForUser(
+                                            mContext.getContentResolver(),
+                                            Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                                            DialogStatus.NOT_SHOWN,
+                                            userId);
+                                })
+                        .create();
         return alertDialog;
     }
 
@@ -531,14 +529,8 @@
             // Default service is invalid, so nothing we can do here.
             return;
         }
-        if (Flags.migrateEnableShortcuts()) {
-            accessibilityManager.enableShortcutsForTargets(true, HARDWARE,
-                    Set.of(defaultServiceComponent.flattenToString()), userId);
-        } else {
-            Settings.Secure.putStringForUser(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
-                    defaultServiceComponent.flattenToString(), userId);
-        }
+        accessibilityManager.enableShortcutsForTargets(true, HARDWARE,
+                Set.of(defaultServiceComponent.flattenToString()), userId);
     }
 
     private boolean performTtsPrompt(AlertDialog alertDialog) {
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java
index 3557633..2e1a0bf 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java
@@ -29,7 +29,6 @@
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
-import android.view.accessibility.Flags;
 import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -65,9 +64,6 @@
         Window window = ad.getWindow();
         WindowManager.LayoutParams params = window.getAttributes();
         params.privateFlags |= SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-        if (!Flags.warningUseDefaultDialogType()) {
-            params.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
-        }
         window.setAttributes(params);
         return ad;
     }
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index a753110..f0582d0 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -19,8 +19,6 @@
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
-import static com.android.internal.accessibility.util.ShortcutUtils.optInValueToSettings;
-import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,7 +29,6 @@
 import android.os.UserHandle;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
 
 import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -118,18 +115,9 @@
     @Override
     public void onCheckedChanged(boolean isChecked) {
         setShortcutEnabled(isChecked);
-        if (Flags.migrateEnableShortcuts()) {
-            final AccessibilityManager am =
-                    getContext().getSystemService(AccessibilityManager.class);
-            am.enableShortcutsForTargets(
-                    isChecked, getShortcutType(), Set.of(mId), UserHandle.myUserId());
-        } else {
-            if (isChecked) {
-                optInValueToSettings(getContext(), getShortcutType(), getId());
-            } else {
-                optOutValueFromSettings(getContext(), getShortcutType(), getId());
-            }
-        }
+        final AccessibilityManager am = getContext().getSystemService(AccessibilityManager.class);
+        am.enableShortcutsForTargets(
+                isChecked, getShortcutType(), Set.of(mId), UserHandle.myUserId());
     }
 
     public void setStateDescription(CharSequence stateDescription) {
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index f690bd3..92f9e60 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -20,9 +20,13 @@
 
 import android.annotation.Nullable;
 import android.app.AlertDialog;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.pm.PackageManager;
 import android.content.res.TypedArray;
 import android.database.Cursor;
 import android.graphics.drawable.Drawable;
@@ -58,6 +62,7 @@
 import android.widget.ScrollView;
 import android.widget.SimpleCursorAdapter;
 import android.widget.TextView;
+import android.widget.flags.Flags;
 
 import com.android.internal.R;
 
@@ -66,6 +71,12 @@
 public class AlertController {
     public static final int MICRO = 1;
 
+    private static boolean sUseWearMaterial3Style;
+
+    @ChangeId
+    @EnabledSince(targetSdkVersion = 36)
+    private static final long WEAR_MATERIAL3_ALERTDIALOG = 379365266L;
+
     private final Context mContext;
     private final DialogInterface mDialogInterface;
     protected final Window mWindow;
@@ -210,7 +221,8 @@
         mHandler = new ButtonHandler(di);
 
         final TypedArray a = context.obtainStyledAttributes(null,
-                R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
+                R.styleable.AlertDialog, getAlertDialogDefStyleAttr(context),
+                getAlertDialogDefStyleRes());
 
         mAlertDialogLayout = a.getResourceId(
                 R.styleable.AlertDialog_layout, R.layout.alert_dialog);
@@ -236,6 +248,28 @@
         window.requestFeature(Window.FEATURE_NO_TITLE);
     }
 
+    private int getAlertDialogDefStyleAttr(Context context) {
+        sUseWearMaterial3Style = useWearMaterial3Style(context);
+        if (sUseWearMaterial3Style) {
+            return 0;
+        }
+        return R.attr.alertDialogStyle;
+    }
+
+    private int getAlertDialogDefStyleRes() {
+        if (sUseWearMaterial3Style) {
+            return com.android.internal.R.style.AlertDialog_DeviceDefault_WearMaterial3;
+        }
+        return 0;
+    }
+
+    private static boolean useWearMaterial3Style(Context context) {
+        return Flags.useWearMaterial3Ui()
+                && CompatChanges.isChangeEnabled(WEAR_MATERIAL3_ALERTDIALOG)
+                && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+                && context.getThemeResId() == com.android.internal.R.style.Theme_DeviceDefault;
+    }
+
     static boolean canTextInput(View v) {
         if (v.onCheckIsTextEditor()) {
             return true;
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 2cfc680..f01aa80 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -163,4 +163,5 @@
     void finishOperationForDevice(IBinder clientId, int code, int uid, String packageName,
             @nullable String attributionTag, int virtualDeviceId);
    List<AppOpsManager.PackageOps> getPackagesForOpsForDevice(in int[] ops, String persistentDeviceId);
+   oneway void noteOperationsInBatch(in Map batchedNoteOps);
 }
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index ee5bd65..644d699 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -70,6 +70,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.chooser.TargetInfo;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
@@ -352,6 +353,7 @@
         findViewById(R.id.use_same_profile_browser).setOnClickListener(v -> finish());
 
         findViewById(R.id.button_open).setOnClickListener(v -> {
+            TargetInfo.refreshIntentCreatorToken(launchIntent);
             startActivityAsCaller(
                     launchIntent,
                     ActivityOptions.makeCustomAnimation(
@@ -476,6 +478,7 @@
 
     private void startActivityAsCaller(Intent newIntent, int userId) {
         try {
+            TargetInfo.refreshIntentCreatorToken(newIntent);
             startActivityAsCaller(
                     newIntent,
                     /* options= */ null,
@@ -502,6 +505,7 @@
             return;
         }
         sanitizeIntent(innerIntent);
+        TargetInfo.refreshIntentCreatorToken(intentReceived);
         startActivityAsCaller(intentReceived, null, false, getUserId());
         finish();
     }
@@ -525,6 +529,7 @@
         if (singleTabOnly) {
             intentReceived.putExtra(EXTRA_RESTRICT_TO_SINGLE_USER, true);
         }
+        TargetInfo.refreshIntentCreatorToken(intentReceived);
         startActivityAsCaller(intentReceived, null, false, userId);
         finish();
     }
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index 473134e..0c65077 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -173,6 +173,7 @@
     @Override
     public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
         TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, userId);
+        TargetInfo.refreshIntentCreatorToken(mResolvedIntent);
         activity.startActivityAsCaller(mResolvedIntent, options, false, userId);
         return true;
     }
@@ -180,6 +181,7 @@
     @Override
     public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
         TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier());
+        TargetInfo.refreshIntentCreatorToken(mResolvedIntent);
         activity.startActivityAsUser(mResolvedIntent, options, user);
         return false;
     }
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index d7f3a76..0eaa43d 100644
--- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -260,6 +260,7 @@
         intent.setComponent(mChooserTarget.getComponentName());
         intent.putExtras(mChooserTarget.getIntentExtras());
         TargetInfo.prepareIntentForCrossProfileLaunch(intent, userId);
+        TargetInfo.refreshIntentCreatorToken(intent);
 
         // Important: we will ignore the target security checks in ActivityManager
         // if and only if the ChooserTarget's target package is the same package
diff --git a/core/java/com/android/internal/app/chooser/TargetInfo.java b/core/java/com/android/internal/app/chooser/TargetInfo.java
index 7bb7ddc..fcf5883 100644
--- a/core/java/com/android/internal/app/chooser/TargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/TargetInfo.java
@@ -17,13 +17,17 @@
 package com.android.internal.app.chooser;
 
 
+import static android.security.Flags.preventIntentRedirect;
+
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.RemoteException;
 import android.os.UserHandle;
 
 import com.android.internal.app.ResolverActivity;
@@ -141,4 +145,20 @@
             intent.fixUris(currentUserId);
         }
     }
+
+    /**
+     * refreshes intent's creatorToken with its current intent key fields. This allows
+     * ChooserActivity to still keep original creatorToken's creator uid after making changes to
+     * the intent and still keep it valid.
+     * @param intent the intent's creatorToken needs to up refreshed.
+     */
+    static void refreshIntentCreatorToken(Intent intent) {
+        if (!preventIntentRedirect()) return;
+        try {
+            intent.setCreatorToken(ActivityManager.getService().refreshIntentCreatorToken(
+                    intent.cloneForCreatorToken()));
+        } catch (RemoteException e) {
+            throw new RuntimeException("Failure from system", e);
+        }
+    }
 }
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 0c56c67..6ad7fef 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -26,6 +26,7 @@
 import static android.system.OsConstants.S_IXOTH;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
@@ -177,6 +178,13 @@
     private native static int nativeCopyNativeBinaries(long handle, String sharedLibraryPath,
             String abiToCopy, boolean extractNativeLibs, boolean debuggable);
 
+    private static native int nativeCheckAlignment(
+            long handle,
+            String sharedLibraryPath,
+            String abi,
+            boolean extractNativeLibs,
+            boolean debuggable);
+
     private static long sumNativeBinaries(Handle handle, String abi) {
         long sum = 0;
         for (long apkHandle : handle.apkHandles) {
@@ -432,6 +440,51 @@
         }
     }
 
+    /**
+     * Checks alignment of APK and native libraries for 16KB device
+     *
+     * @param handle APK file to scan for native libraries
+     * @param libraryRoot directory for libraries
+     * @param abiOverride abiOverride for package
+     * @return {@link Modes from ApplicationInfo.PageSizeAppCompat} if successful or error code
+     *     which suggests undefined mode
+     */
+    @ApplicationInfo.PageSizeAppCompatFlags
+    public static int checkAlignmentForCompatMode(
+            Handle handle,
+            String libraryRoot,
+            boolean nativeLibraryRootRequiresIsa,
+            String abiOverride) {
+        // Keep the code below in sync with copyNativeBinariesForSupportedAbi
+        int abi = findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS);
+        if (abi < 0) {
+            return ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+        }
+
+        final String supportedAbi = Build.SUPPORTED_64_BIT_ABIS[abi];
+        final String instructionSet = VMRuntime.getInstructionSet(supportedAbi);
+        String subDir = libraryRoot;
+        if (nativeLibraryRootRequiresIsa) {
+            subDir += "/" + instructionSet;
+        }
+
+        int mode = ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+        for (long apkHandle : handle.apkHandles) {
+            int res =
+                    nativeCheckAlignment(
+                            apkHandle,
+                            subDir,
+                            Build.SUPPORTED_64_BIT_ABIS[abi],
+                            handle.extractNativeLibs,
+                            handle.debuggable);
+            if (res == ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ERROR) {
+                return res;
+            }
+            mode |= res;
+        }
+        return mode;
+    }
+
     public static long sumNativeBinariesWithOverride(Handle handle, String abiOverride)
             throws IOException {
         long sum = 0;
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index ac4c066..5fbb92d 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -16,6 +16,7 @@
 
 package com.android.internal.inputmethod;
 
+import android.graphics.Region;
 import android.net.Uri;
 import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodSubtype;
@@ -51,4 +52,5 @@
     void resetStylusHandwriting(int requestId);
     void switchKeyboardLayoutAsync(int direction);
     void setHandwritingSurfaceNotTouchable(boolean notTouchable);
+    void setHandwritingTouchableRegion(in Region region);
 }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index b009c99..36333a9 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -20,6 +20,7 @@
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.Region;
 import android.inputmethodservice.InputMethodService.BackDispositionMode;
 import android.inputmethodservice.InputMethodService.ImeWindowVisibility;
 import android.net.Uri;
@@ -159,6 +160,25 @@
         }
     }
 
+
+    /**
+     * Calls {@link IInputMethodPrivilegedOperations#setHandwritingTouchableRegion(Region)}.
+     *
+     * @param region {@link Region} to set handwritable.
+     */
+    @AnyThread
+    public void setHandwritingTouchableRegion(Region region) {
+        final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
+        if (ops == null) {
+            return;
+        }
+        try {
+            ops.setHandwritingTouchableRegion(region);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Calls {@link IInputMethodPrivilegedOperations#createInputContentUriToken(Uri, String,
      * AndroidFuture)}.
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index eb6a810..429a6a2 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -89,6 +89,8 @@
         SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT,
         SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION,
         SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION,
+        SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED,
+        SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED,
 })
 public @interface SoftInputShowHideReason {
     /** Default, undefined reason. */
@@ -337,6 +339,18 @@
     int HIDE_WINDOW_LEGACY_DIRECT = ImeProtoEnums.REASON_HIDE_WINDOW_LEGACY_DIRECT;
 
     /**
+     * Show soft input because the input target changed
+     * {@link com.android.server.wm.ImeInsetsSourceProvider#onInputTargetChanged}.
+     */
+    int SHOW_INPUT_TARGET_CHANGED = ImeProtoEnums.REASON_SHOW_INPUT_TARGET_CHANGED;
+
+    /**
+     * Hide soft input because the input target changed by
+     * {@link com.android.server.wm.ImeInsetsSourceProvider#onInputTargetChanged}.
+     */
+    int HIDE_INPUT_TARGET_CHANGED = ImeProtoEnums.REASON_HIDE_INPUT_TARGET_CHANGED;
+
+    /**
      * Show / Hide soft input by
      * {@link android.inputmethodservice.InputMethodService#resetStateForNewConfiguration}.
      */
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 1204ef3..fc41537 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -235,8 +235,19 @@
      */
     public static final int CUJ_DESKTOP_MODE_SNAP_RESIZE = 118;
 
+    /**
+     * Track unmaximize window interaction in desktop mode.
+     *
+     * <p> Tracking starts when the maximize button or option is clicked {@link
+     * com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel#onClick}
+     * and finishes when the animation finishes {@link
+     * com.android.wm.shell.windowdecor.ToggleResizeDesktopTaskTransitionHandler#startAnimation}
+     * </p>
+     */
+    public static final int CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW = 119;
+
     // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
-    @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_SNAP_RESIZE;
+    @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW;
 
     /** @hide */
     @IntDef({
@@ -346,7 +357,8 @@
             CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH,
             CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE,
             CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE,
-            CUJ_DESKTOP_MODE_SNAP_RESIZE
+            CUJ_DESKTOP_MODE_SNAP_RESIZE,
+            CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {}
@@ -467,6 +479,7 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_SNAP_RESIZE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_SNAP_RESIZE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_UNMAXIMIZE_WINDOW;
     }
 
     private Cuj() {
@@ -699,6 +712,8 @@
                 return "DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE";
             case CUJ_DESKTOP_MODE_SNAP_RESIZE:
                 return "DESKTOP_MODE_SNAP_RESIZE";
+            case CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW:
+                return "DESKTOP_MODE_UNMAXIMIZE_WINDOW";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 2834e68..454323b 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -685,14 +685,6 @@
         }
     }
 
-    ThreadedRendererWrapper getThreadedRenderer() {
-        return mRendererWrapper;
-    }
-
-    ViewRootWrapper getViewRoot() {
-        return mViewRoot;
-    }
-
     private boolean shouldTriggerPerfetto(int missedFramesCount, int maxFrameTimeNanos) {
         boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
                 && missedFramesCount >= mTraceThresholdMissedFrames;
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index ef08e49..26ff430 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -31,6 +31,7 @@
 import android.annotation.UiThread;
 import android.annotation.WorkerThread;
 import android.app.ActivityThread;
+import android.app.Application;
 import android.content.Context;
 import android.graphics.Color;
 import android.os.Build;
@@ -184,10 +185,12 @@
     @GuardedBy("mLock")
     private final SparseArray<RunningTracker> mRunningTrackers = new SparseArray<>();
     private final Handler mWorker;
+    private final Application mCurrentApplication;
     private final DisplayResolutionTracker mDisplayResolutionTracker;
     private final Object mLock = new Object();
     private @ColorInt int mDebugBgColor = Color.CYAN;
     private double mDebugYOffset = 0.1;
+    @GuardedBy("mLock")
     private InteractionMonitorDebugOverlay mDebugOverlay;
 
     private volatile boolean mEnabled = DEFAULT_ENABLED;
@@ -216,13 +219,15 @@
         mWorker = worker.getThreadHandler();
         mDisplayResolutionTracker = new DisplayResolutionTracker(mWorker);
 
-        final Context context = ActivityThread.currentApplication();
-        if (context == null || context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
+        mCurrentApplication = ActivityThread.currentApplication();
+        if (mCurrentApplication == null || mCurrentApplication.checkCallingOrSelfPermission(
+                READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
             Log.w(TAG, "Initializing without READ_DEVICE_CONFIG permission."
                     + " enabled=" + mEnabled + ", interval=" + mSamplingInterval
                     + ", missedFrameThreshold=" + mTraceThresholdMissedFrames
                     + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
-                    + ", package=" + (context == null ? "null" : context.getPackageName()));
+                    + ", package=" + (mCurrentApplication == null ? "null"
+                    : mCurrentApplication.getPackageName()));
             return;
         }
 
@@ -234,8 +239,8 @@
                         new HandlerExecutor(mWorker), this::updateProperties);
             } catch (SecurityException ex) {
                 Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted="
-                        + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
-                        + ", package=" + context.getPackageName());
+                        + mCurrentApplication.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+                        + ", package=" + mCurrentApplication.getPackageName());
             }
         });
     }
@@ -405,7 +410,11 @@
 
         RunningTracker tracker = putTrackerIfNoCurrent(cujType, () ->
                 new RunningTracker(
-                    conf, createFrameTracker(conf), () -> cancel(cujType, REASON_CANCEL_TIMEOUT)));
+                    conf, createFrameTracker(conf), () -> {
+                        Log.w(TAG, "CUJ cancelled due to timeout, CUJ="
+                                + Cuj.getNameOfCuj(cujType));
+                        cancel(cujType, REASON_CANCEL_TIMEOUT);
+                    }));
         if (tracker == null) {
             return false;
         }
@@ -534,7 +543,7 @@
 
             mRunningTrackers.put(cuj, tracker);
             if (mDebugOverlay != null) {
-                mDebugOverlay.onTrackerAdded(cuj, tracker);
+                mDebugOverlay.onTrackerAdded(cuj, tracker.mTracker.hashCode());
             }
 
             return tracker;
@@ -569,7 +578,7 @@
             running.mConfig.getHandler().removeCallbacks(running.mTimeoutAction);
             mRunningTrackers.remove(cuj);
             if (mDebugOverlay != null) {
-                mDebugOverlay.onTrackerRemoved(cuj, reason, mRunningTrackers);
+                mDebugOverlay.onTrackerRemoved(cuj, reason, tracker.hashCode());
             }
             return false;
         }
@@ -592,14 +601,18 @@
                         mEnabled = properties.getBoolean(property, DEFAULT_ENABLED);
                 case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY -> {
                     // Never allow the debug overlay to be used on user builds
-                    boolean debugOverlayEnabled = Build.IS_DEBUGGABLE
-                            && properties.getBoolean(property, DEFAULT_DEBUG_OVERLAY_ENABLED);
-                    if (debugOverlayEnabled && mDebugOverlay == null) {
-                        mDebugOverlay = new InteractionMonitorDebugOverlay(
-                                mLock, mDebugBgColor, mDebugYOffset);
-                    } else if (!debugOverlayEnabled && mDebugOverlay != null) {
-                        mDebugOverlay.dispose();
-                        mDebugOverlay = null;
+                    if (Build.IS_USER) break;
+                    boolean debugOverlayEnabled = properties.getBoolean(property,
+                            DEFAULT_DEBUG_OVERLAY_ENABLED);
+                    synchronized (mLock) {
+                        if (debugOverlayEnabled && mDebugOverlay == null) {
+                            // Use the worker thread as the UI thread for the debug overlay:
+                            mDebugOverlay = new InteractionMonitorDebugOverlay(
+                                    mCurrentApplication, mWorker, mDebugBgColor, mDebugYOffset);
+                        } else if (!debugOverlayEnabled && mDebugOverlay != null) {
+                            mDebugOverlay.dispose();
+                            mDebugOverlay = null;
+                        }
                     }
                 }
                 default -> Log.w(TAG, "Got a change event for an unknown property: "
diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
index d9cac12..97f8879 100644
--- a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
+++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
@@ -16,26 +16,39 @@
 
 package com.android.internal.jank;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Gravity.CENTER;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+
 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
 
+import android.annotation.AnyThread;
 import android.annotation.ColorInt;
+import android.annotation.NonNull;
 import android.annotation.UiThread;
-import android.app.ActivityThread;
+import android.app.Application;
 import android.content.Context;
+import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
-import android.graphics.RecordingCanvas;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.Trace;
+import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.view.WindowCallbacks;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.jank.FrameTracker.Reasons;
 
+import java.util.ArrayList;
+
 /**
  * An overlay that uses WindowCallbacks to draw the names of all running CUJs to the window
  * associated with one of the CUJs being tracked. There's no guarantee which window it will
@@ -50,236 +63,261 @@
  * <li> Grey text indicates the CUJ ended normally and is no longer running
  * <li> Red text with a strikethrough indicates the CUJ was canceled or ended abnormally
  * </ul>
+ *
  * @hide
  */
-class InteractionMonitorDebugOverlay implements WindowCallbacks {
+class InteractionMonitorDebugOverlay {
     private static final String TAG = "InteractionMonitorDebug";
     private static final int REASON_STILL_RUNNING = -1000;
-    private final Object mLock;
+    private static final long HIDE_OVERLAY_DELAY = 2000L;
     // Sparse array where the key in the CUJ and the value is the session status, or null if
     // it's currently running
-    @GuardedBy("mLock")
-    private final SparseIntArray mRunningCujs = new SparseIntArray();
-    private Handler mHandler = null;
-    private FrameTracker.ViewRootWrapper mViewRoot = null;
-    private final Paint mDebugPaint;
-    private final Paint.FontMetrics mDebugFontMetrics;
-    // Used to display the overlay in a different color and position for different processes.
-    // Otherwise, two overlays will overlap and be difficult to read.
-    private final int mBgColor;
-    private final double mYOffset;
-    private final String mPackageName;
-    private static final String TRACK_NAME = "InteractionJankMonitor";
+    private final Application mCurrentApplication;
+    private final Handler mUiThread;
+    private final DebugOverlayView mDebugOverlayView;
+    private final WindowManager mWindowManager;
+    private final ArrayList<TrackerState> mRunningCujs = new ArrayList<>();
 
-    InteractionMonitorDebugOverlay(Object lock, @ColorInt int bgColor, double yOffset) {
-        mLock = lock;
-        mBgColor = bgColor;
-        mYOffset = yOffset;
-        mDebugPaint = new Paint();
-        mDebugPaint.setAntiAlias(false);
-        mDebugFontMetrics = new Paint.FontMetrics();
-        final Context context = ActivityThread.currentApplication();
-        mPackageName = context == null ? "null" : context.getPackageName();
-    }
+    InteractionMonitorDebugOverlay(@NonNull Application currentApplication,
+            @NonNull @UiThread Handler uiThread, @ColorInt int bgColor, double yOffset) {
+        mCurrentApplication = currentApplication;
+        mUiThread = uiThread;
+        final Display display = mCurrentApplication.getSystemService(
+                DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
+        final Context windowContext = mCurrentApplication.createDisplayContext(
+                display).createWindowContext(TYPE_SYSTEM_OVERLAY, null /* options */);
+        mWindowManager = windowContext.getSystemService(WindowManager.class);
 
-    @UiThread
-    void dispose() {
-        if (mViewRoot != null && mHandler != null) {
-            mHandler.runWithScissors(() ->  mViewRoot.removeWindowCallbacks(this),
-                    InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
-            forceRedraw();
+        final Rect size = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
+        lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+                | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+
+        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        lp.setFitInsetsTypes(0 /* types */);
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
+
+        lp.width = size.width();
+        lp.height = size.height();
+        lp.gravity = CENTER;
+        lp.setTitle("InteractionMonitorDebugOverlay");
+
+        if (!mUiThread.getLooper().isCurrentThread()) {
+            Log.e(TAG, "InteractionMonitorDebugOverlay must be constructed on "
+                    + "InteractionJankMonitor's worker thread");
         }
-        mHandler = null;
-        mViewRoot = null;
-        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, 0);
+        mDebugOverlayView = new DebugOverlayView(mCurrentApplication, bgColor, yOffset);
+        mWindowManager.addView(mDebugOverlayView, lp);
+    }
+
+    private final Runnable mHideOverlayRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mRunningCujs.clear();
+            mDebugOverlayView.setVisibility(INVISIBLE);
+        }
+    };
+
+    @AnyThread
+    void onTrackerAdded(@Cuj.CujType int addedCuj, int cookie) {
+        mUiThread.removeCallbacks(mHideOverlayRunnable);
+        mUiThread.post(() -> {
+            String cujName = Cuj.getNameOfCuj(addedCuj);
+            Log.i(TAG, cujName + " started (cookie=" + cookie + ")");
+            mRunningCujs.add(new TrackerState(addedCuj, cookie));
+            mDebugOverlayView.setVisibility(VISIBLE);
+            mDebugOverlayView.invalidate();
+        });
+    }
+
+    @AnyThread
+    void onTrackerRemoved(@Cuj.CujType int removedCuj, @Reasons int reason, int cookie) {
+        mUiThread.post(() -> {
+            TrackerState foundTracker = null;
+            boolean allTrackersEnded = true;
+            for (int i = 0; i < mRunningCujs.size(); i++) {
+                TrackerState tracker = mRunningCujs.get(i);
+                if (tracker.mCuj == removedCuj && tracker.mCookie == cookie) {
+                    foundTracker = tracker;
+                } else {
+                    // If none of the trackers have REASON_STILL_RUNNING status, then
+                    // all CUJs have ended
+                    allTrackersEnded = allTrackersEnded && tracker.mState != REASON_STILL_RUNNING;
+                }
+            }
+
+            if (foundTracker != null) {
+                foundTracker.mState = reason;
+            }
+
+            String cujName = Cuj.getNameOfCuj(removedCuj);
+            Log.i(TAG, cujName + (reason == REASON_END_NORMAL ? " ended" : " cancelled")
+                    + " (cookie=" + cookie + ")");
+
+            if (allTrackersEnded) {
+                Log.i(TAG, "All CUJs ended");
+                mUiThread.postDelayed(mHideOverlayRunnable, HIDE_OVERLAY_DELAY);
+            }
+            mDebugOverlayView.invalidate();
+        });
+    }
+
+    @AnyThread
+    void dispose() {
+        mUiThread.post(() -> {
+            mWindowManager.removeView(mDebugOverlayView);
+        });
+    }
+
+    @AnyThread
+    private static class TrackerState {
+        final int mCookie;
+        final int mCuj;
+        int mState;
+
+        private TrackerState(int cuj, int cookie) {
+            mCuj = cuj;
+            mCookie = cookie;
+            // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
+            // is still running
+            mState = REASON_STILL_RUNNING;
+        }
     }
 
     @UiThread
-    private boolean attachViewRootIfNeeded(InteractionJankMonitor.RunningTracker tracker) {
-        FrameTracker.ViewRootWrapper viewRoot = tracker.mTracker.getViewRoot();
-        if (mViewRoot == null && viewRoot != null) {
+    private class DebugOverlayView extends View {
+        private static final String TRACK_NAME = "InteractionJankMonitor";
+
+        // Used to display the overlay in a different color and position for different processes.
+        // Otherwise, two overlays will overlap and be difficult to read.
+        private final int mBgColor;
+        private final double mYOffset;
+
+        private final float mDensity;
+        private final Paint mDebugPaint;
+        private final Paint.FontMetrics mDebugFontMetrics;
+        private final String mPackageNameText;
+
+        final int mPadding;
+        final int mPackageNameFontSize;
+        final int mCujFontSize;
+        final float mCujNameTextHeight;
+        final float mCujStatusWidth;
+        final float mPackageNameTextHeight;
+        final float mPackageNameWidth;
+
+        private DebugOverlayView(Context context, @ColorInt int bgColor, double yOffset) {
+            super(context);
+            setVisibility(INVISIBLE);
+            mBgColor = bgColor;
+            mYOffset = yOffset;
+            final DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
+            mDensity = displayMetrics.density;
+            mDebugPaint = new Paint();
+            mDebugPaint.setAntiAlias(false);
+            mDebugFontMetrics = new Paint.FontMetrics();
+            mPackageNameText = "package:" + mCurrentApplication.getPackageName();
+            mPadding = dipToPx(5);
+            mPackageNameFontSize = dipToPx(12);
+            mCujFontSize = dipToPx(18);
+            mCujNameTextHeight = getTextHeight(mCujFontSize);
+            mCujStatusWidth = mCujNameTextHeight * 1.2f;
+            mPackageNameTextHeight = getTextHeight(mPackageNameFontSize);
+            mPackageNameWidth = getWidthOfText(mPackageNameText, mPackageNameFontSize);
+        }
+
+        private int dipToPx(int dip) {
+            return (int) (mDensity * dip + 0.5f);
+        }
+
+        private float getTextHeight(int textSize) {
+            mDebugPaint.setTextSize(textSize);
+            mDebugPaint.getFontMetrics(mDebugFontMetrics);
+            return mDebugFontMetrics.descent - mDebugFontMetrics.ascent;
+        }
+
+        private float getWidthOfText(String text, int fontSize) {
+            mDebugPaint.setTextSize(fontSize);
+            return mDebugPaint.measureText(text);
+        }
+
+        private float getWidthOfLongestCujName(int cujFontSize) {
+            mDebugPaint.setTextSize(cujFontSize);
+            float maxLength = 0;
+            for (int i = 0; i < mRunningCujs.size(); i++) {
+                String cujName = Cuj.getNameOfCuj(mRunningCujs.get(i).mCuj);
+                float textLength = mDebugPaint.measureText(cujName);
+                if (textLength > maxLength) {
+                    maxLength = textLength;
+                }
+            }
+            return maxLength;
+        }
+
+        @Override
+        protected void onDraw(@NonNull Canvas canvas) {
+            super.onDraw(canvas);
+
             // Add a trace marker so we can identify traces that were captured while the debug
             // overlay was enabled. Traces that use the debug overlay should NOT be used for
             // performance analysis.
             Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, "DEBUG_OVERLAY_DRAW", 0);
-            mHandler = tracker.mConfig.getHandler();
-            mViewRoot = viewRoot;
-            mHandler.runWithScissors(() -> viewRoot.addWindowCallbacks(this),
-                    InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
-            forceRedraw();
-            return true;
-        }
-        return false;
-    }
 
-    @GuardedBy("mLock")
-    private float getWidthOfLongestCujName(int cujFontSize) {
-        mDebugPaint.setTextSize(cujFontSize);
-        float maxLength = 0;
-        for (int i = 0; i < mRunningCujs.size(); i++) {
-            String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i));
-            float textLength = mDebugPaint.measureText(cujName);
-            if (textLength > maxLength) {
-                maxLength = textLength;
-            }
-        }
-        return maxLength;
-    }
+            final int h = getHeight();
+            final int w = getWidth();
+            final int dy = (int) (h * mYOffset);
 
-    private float getTextHeight(int textSize) {
-        mDebugPaint.setTextSize(textSize);
-        mDebugPaint.getFontMetrics(mDebugFontMetrics);
-        return mDebugFontMetrics.descent - mDebugFontMetrics.ascent;
-    }
-
-    private int dipToPx(int dip) {
-        if (mViewRoot != null) {
-            return mViewRoot.dipToPx(dip);
-        } else {
-            return dip;
-        }
-    }
-
-    @UiThread
-    private void forceRedraw() {
-        if (mViewRoot != null && mHandler != null) {
-            mHandler.runWithScissors(() -> {
-                mViewRoot.requestInvalidateRootRenderNode();
-                mViewRoot.getView().invalidate();
-            }, InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT);
-        }
-    }
-
-    @UiThread
-    void onTrackerRemoved(@Cuj.CujType int removedCuj, @Reasons int reason,
-                          SparseArray<InteractionJankMonitor.RunningTracker> runningTrackers) {
-        synchronized (mLock) {
-            mRunningCujs.put(removedCuj, reason);
-            boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
-            if (isLoggable) {
-                String cujName = Cuj.getNameOfCuj(removedCuj);
-                Log.d(TAG, cujName + (reason == REASON_END_NORMAL ? " ended" : " cancelled"));
-            }
-            // If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
-            if (mRunningCujs.indexOfValue(REASON_STILL_RUNNING) < 0) {
-                if (isLoggable) Log.d(TAG, "All CUJs ended");
-                mRunningCujs.clear();
-                dispose();
-            } else {
-                boolean needsNewViewRoot = true;
-                if (mViewRoot != null) {
-                    // Check to see if this viewroot is still associated with one of the running
-                    // trackers
-                    for (int i = 0; i < runningTrackers.size(); i++) {
-                        if (mViewRoot.equals(
-                                runningTrackers.valueAt(i).mTracker.getViewRoot())) {
-                            needsNewViewRoot = false;
-                            break;
-                        }
-                    }
-                }
-                if (needsNewViewRoot) {
-                    dispose();
-                    for (int i = 0; i < runningTrackers.size(); i++) {
-                        if (attachViewRootIfNeeded(runningTrackers.valueAt(i))) {
-                            break;
-                        }
-                    }
-                } else {
-                    forceRedraw();
-                }
-            }
-        }
-    }
-
-    @UiThread
-    void onTrackerAdded(@Cuj.CujType int addedCuj, InteractionJankMonitor.RunningTracker tracker) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            String cujName = Cuj.getNameOfCuj(addedCuj);
-            Log.d(TAG, cujName + " started");
-        }
-        synchronized (mLock) {
-            // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
-            // is still running
-            mRunningCujs.put(addedCuj, REASON_STILL_RUNNING);
-            attachViewRootIfNeeded(tracker);
-            forceRedraw();
-        }
-    }
-
-    @Override
-    public void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen,
-                                       Rect systemInsets, Rect stableInsets) {
-    }
-
-    @Override
-    public void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen,
-                                        Rect systemInsets, Rect stableInsets) {
-    }
-
-    @Override
-    public void onWindowDragResizeEnd() {
-    }
-
-    @Override
-    public boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY) {
-        return false;
-    }
-
-    @Override
-    public void onRequestDraw(boolean reportNextDraw) {
-    }
-
-    @Override
-    public void onPostDraw(RecordingCanvas canvas) {
-        final int padding = dipToPx(5);
-        final int h = canvas.getHeight();
-        final int w = canvas.getWidth();
-        // Draw sysui CUjs near the bottom of the screen so they don't overlap with the shade,
-        // and draw launcher CUJs near the top of the screen so they don't overlap with gestures
-        final int dy = (int) (h * mYOffset);
-        int packageNameFontSize = dipToPx(12);
-        int cujFontSize = dipToPx(18);
-        final float cujNameTextHeight = getTextHeight(cujFontSize);
-        final float packageNameTextHeight = getTextHeight(packageNameFontSize);
-
-        synchronized (mLock) {
-            float maxLength = getWidthOfLongestCujName(cujFontSize);
+            float maxLength = Math.max(mPackageNameWidth, getWidthOfLongestCujName(mCujFontSize))
+                    + mCujStatusWidth;
 
             final int dx = (int) ((w - maxLength) / 2f);
             canvas.translate(dx, dy);
             // Draw background rectangle for displaying the text showing the CUJ name
             mDebugPaint.setColor(mBgColor);
-            canvas.drawRect(
-                    -padding * 2, // more padding on top so we can draw the package name
-                    -padding,
-                    padding * 2 + maxLength,
-                    padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(),
-                    mDebugPaint);
-            mDebugPaint.setTextSize(packageNameFontSize);
+            canvas.drawRect(-mPadding * 2, // more padding on top so we can draw the package name
+                    -mPadding, mPadding * 2 + maxLength, mPadding * 2 + mPackageNameTextHeight
+                            + mCujNameTextHeight * mRunningCujs.size(), mDebugPaint);
+            mDebugPaint.setTextSize(mPackageNameFontSize);
             mDebugPaint.setColor(Color.BLACK);
             mDebugPaint.setStrikeThruText(false);
-            canvas.translate(0, packageNameTextHeight);
-            canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint);
-            mDebugPaint.setTextSize(cujFontSize);
+            canvas.translate(0, mPackageNameTextHeight);
+            canvas.drawText(mPackageNameText, 0, 0, mDebugPaint);
+            mDebugPaint.setTextSize(mCujFontSize);
             // Draw text for CUJ names
             for (int i = 0; i < mRunningCujs.size(); i++) {
-                int status = mRunningCujs.valueAt(i);
-                if (status == REASON_STILL_RUNNING) {
-                    mDebugPaint.setColor(Color.BLACK);
-                    mDebugPaint.setStrikeThruText(false);
-                } else if (status == REASON_END_NORMAL) {
-                    mDebugPaint.setColor(Color.GRAY);
-                    mDebugPaint.setStrikeThruText(false);
-                } else {
-                    // Cancelled, or otherwise ended for a bad reason
-                    mDebugPaint.setColor(Color.RED);
-                    mDebugPaint.setStrikeThruText(true);
-                }
-                String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i));
-                canvas.translate(0, cujNameTextHeight);
-                canvas.drawText(cujName, 0, 0, mDebugPaint);
+                TrackerState tracker = mRunningCujs.get(i);
+                int status = tracker.mState;
+                String statusText = switch (status) {
+                    case REASON_STILL_RUNNING -> {
+                        mDebugPaint.setColor(Color.BLACK);
+                        mDebugPaint.setStrikeThruText(false);
+                        yield "☐"; // BALLOT BOX
+                    }
+                    case REASON_END_NORMAL -> {
+                        mDebugPaint.setColor(Color.GRAY);
+                        mDebugPaint.setStrikeThruText(false);
+                        yield "✅"; // WHITE HEAVY CHECK MARK
+                    }
+                    default -> {
+                        // Cancelled, or otherwise ended for a bad reason
+                        mDebugPaint.setColor(Color.RED);
+                        mDebugPaint.setStrikeThruText(true);
+                        yield "❌"; // CROSS MARK
+                    }
+                };
+                String cujName = Cuj.getNameOfCuj(tracker.mCuj);
+                canvas.translate(0, mCujNameTextHeight);
+                canvas.drawText(statusText, 0, 0, mDebugPaint);
+                canvas.drawText(cujName, mCujStatusWidth, 0, mDebugPaint);
             }
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, 0);
         }
     }
 }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index fafa085..cd17ed8 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -201,6 +201,9 @@
      */
     public static final int DEBUG_ENABLE_PTRACE = 1 << 25;
 
+    /** Load 4KB ELF files on 16KB device using appcompat mode */
+    public static final int ENABLE_PAGE_SIZE_APP_COMPAT = 1 << 26;
+
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
     /** Default external storage should be mounted. */
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index e402ddf..e60879e 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -19,6 +19,8 @@
 import static android.system.OsConstants.S_IRWXG;
 import static android.system.OsConstants.S_IRWXO;
 
+import static android.net.http.Flags.preloadHttpengineInZygote;
+
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SECONDARY_ZYGOTE_INIT_START;
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__ZYGOTE_INIT_START;
 
@@ -27,6 +29,7 @@
 import android.content.pm.SharedLibraryInfo;
 import android.content.res.Resources;
 import android.os.Build;
+import android.net.http.HttpEngine;
 import android.os.Environment;
 import android.os.IInstalld;
 import android.os.Process;
@@ -144,6 +147,23 @@
         Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
         preloadSharedLibraries();
         preloadTextResources();
+
+        // TODO: remove the try/catch and the flag read as soon as the flag is ramped and 25Q2
+        // starts building from source.
+        if (preloadHttpengineInZygote()) {
+            try {
+                HttpEngine.preload();
+            } catch (NoSuchMethodError e){
+                // The flag protecting this API is not an exported
+                // flag because ZygoteInit happens before the
+                // system service has initialized the flag which means
+                // that we can't query the real value of the flag
+                // from the tethering module. In order to avoid crashing
+                // in the case where we have (new zygote, old tethering).
+                // we catch the NoSuchMethodError and just log.
+                Log.d(TAG, "HttpEngine.preload() threw " + e);
+            }
+        }
         // Ask the WebViewFactory to do any initialization that must run in the zygote process,
         // for memory sharing purposes.
         WebViewFactory.prepareWebViewInZygote();
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index 48d0d6c..5ec5762 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -392,6 +392,10 @@
     private int memtagMode;
     @ApplicationInfo.NativeHeapZeroInitialized
     private int nativeHeapZeroInitialized;
+
+    @ApplicationInfo.PageSizeAppCompatFlags private int mPageSizeAppCompatFlags =
+            ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+
     @Nullable
     @DataClass.ParcelWith(Parcelling.BuiltIn.ForBoolean.class)
     private Boolean requestRawExternalStorageAccess;
@@ -1118,6 +1122,12 @@
         return nativeHeapZeroInitialized;
     }
 
+    @ApplicationInfo.PageSizeAppCompatFlags
+    @Override
+    public int getPageSizeAppCompatFlags() {
+        return mPageSizeAppCompatFlags;
+    }
+
     @Override
     public int getNetworkSecurityConfigResourceId() {
         return networkSecurityConfigRes;
@@ -2221,6 +2231,12 @@
     }
 
     @Override
+    public PackageImpl setPageSizeAppCompatFlags(@ApplicationInfo.PageSizeAppCompatFlags int flag) {
+        mPageSizeAppCompatFlags = flag;
+        return this;
+    }
+
+    @Override
     public PackageImpl setNetworkSecurityConfigResourceId(int value) {
         networkSecurityConfigRes = value;
         return this;
@@ -2703,6 +2719,7 @@
             appInfo.setKnownActivityEmbeddingCerts(mKnownActivityEmbeddingCerts);
         }
         appInfo.allowCrossUidActivitySwitchFromBelow = mAllowCrossUidActivitySwitchFromBelow;
+        appInfo.setPageSizeAppCompatFlags(mPageSizeAppCompatFlags);
 
         return appInfo;
     }
@@ -3305,6 +3322,7 @@
         dest.writeInt(this.mIntentMatchingFlags);
         dest.writeIntArray(this.mAlternateLauncherIconResIds);
         dest.writeIntArray(this.mAlternateLauncherLabelResIds);
+        dest.writeInt(this.mPageSizeAppCompatFlags);
     }
 
     private void writeFeatureFlagState(@NonNull Parcel dest) {
@@ -3499,6 +3517,7 @@
         this.mIntentMatchingFlags = in.readInt();
         this.mAlternateLauncherIconResIds = in.createIntArray();
         this.mAlternateLauncherLabelResIds = in.createIntArray();
+        this.mPageSizeAppCompatFlags = in.readInt();
 
         assignDerivedFields();
         assignDerivedFields2();
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index c953d88..445dac7 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -25,7 +25,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.Flags;
-import android.content.res.XmlResourceParser;
 import android.os.Environment;
 import android.os.Process;
 import android.util.ArrayMap;
@@ -247,20 +246,23 @@
             negated = true;
             featureFlag = featureFlag.substring(1).strip();
         }
-        final Boolean flagValue = getFlagValue(featureFlag);
-        boolean shouldSkip = false;
+        Boolean flagValue = getFlagValue(featureFlag);
+        boolean isUndefined = false;
         if (flagValue == null) {
-            Slog.w(LOG_TAG, "Skipping element " + parser.getName()
-                    + " due to unknown feature flag " + featureFlag);
-            shouldSkip = true;
-        } else if (flagValue == negated) {
+            isUndefined = true;
+            flagValue = false;
+        }
+        boolean shouldSkip = false;
+        if (flagValue == negated) {
             // Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated)
-            Slog.i(LOG_TAG, "Skipping element " + parser.getName()
-                    + " behind feature flag " + featureFlag + " = " + flagValue);
             shouldSkip = true;
         }
         if (pkg != null && android.content.pm.Flags.includeFeatureFlagsInPackageCacher()) {
-            pkg.addFeatureFlag(featureFlag, flagValue);
+            if (isUndefined) {
+                pkg.addFeatureFlag(featureFlag, null);
+            } else {
+                pkg.addFeatureFlag(featureFlag, flagValue);
+            }
         }
         return shouldSkip;
     }
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index 67b985a..5062d58 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -31,7 +31,6 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
-import com.android.internal.R;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.component.ParsedApexSystemService;
@@ -280,6 +279,9 @@
     ParsingPackage setNativeHeapZeroInitialized(
             @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized);
 
+    /** Manifest option pageSizeCompat will populate this field */
+    ParsingPackage setPageSizeAppCompatFlags(@ApplicationInfo.PageSizeAppCompatFlags int value);
+
     ParsingPackage setRequestRawExternalStorageAccess(
             @Nullable Boolean requestRawExternalStorageAccess);
 
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 5fc1276..c160b42 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -29,6 +29,7 @@
 import static android.os.Build.VERSION_CODES.DONUT;
 import static android.os.Build.VERSION_CODES.O;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.sdk.Flags.majorMinorVersioningScheme;
 
 import static com.android.internal.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
 
@@ -541,6 +542,7 @@
 
         pkg.setGwpAsanMode(-1);
         pkg.setMemtagMode(-1);
+        pkg.setPageSizeAppCompatFlags(ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED);
 
         afterParseBaseApplication(pkg);
 
@@ -1688,6 +1690,21 @@
                     targetCode = minCode;
                 }
 
+                if (majorMinorVersioningScheme()) {
+                    val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_minSdkVersionFull);
+                    if (val != null) {
+                        if (val.type == TypedValue.TYPE_STRING && val.string != null) {
+                            String minSdkVersionFullString = val.string.toString();
+                            ParseResult<Void> minSdkVersionFullResult =
+                                    FrameworkParsingPackageUtils.verifyMinSdkVersionFull(
+                                        minSdkVersionFullString, Build.VERSION.SDK_INT_FULL, input);
+                            if (minSdkVersionFullResult.isError()) {
+                                return input.error(minSdkVersionFullResult);
+                            }
+                        }
+                    }
+                }
+
                 if (isApkInApex) {
                     val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_maxSdkVersion);
                     if (val != null) {
@@ -2182,6 +2199,13 @@
 
             pkg.setGwpAsanMode(sa.getInt(R.styleable.AndroidManifestApplication_gwpAsanMode, -1));
             pkg.setMemtagMode(sa.getInt(R.styleable.AndroidManifestApplication_memtagMode, -1));
+
+            if (Flags.appCompatOption16kb()) {
+                pkg.setPageSizeAppCompatFlags(
+                        sa.getInt(R.styleable.AndroidManifestApplication_pageSizeCompat,
+                                ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED));
+            }
+
             if (sa.hasValue(R.styleable.AndroidManifestApplication_nativeHeapZeroInitialized)) {
                 final boolean v = sa.getBoolean(
                         R.styleable.AndroidManifestApplication_nativeHeapZeroInitialized, false);
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.java
new file mode 100644
index 0000000..a090c7a
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedBasicEnvelopeEffect.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.vibrator.persistence;
+
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DURATION_MS;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_INITIAL_SHARPNESS;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_INTENSITY;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_SHARPNESS;
+import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_BASIC_ENVELOPE_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_CONTROL_POINT;
+
+import android.annotation.NonNull;
+import android.os.VibrationEffect;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Serialized representation of a basic envelope effect created via
+ * {@link VibrationEffect.BasicEnvelopeBuilder}.
+ *
+ * @hide
+ */
+final class SerializedBasicEnvelopeEffect implements SerializedComposedEffect.SerializedSegment {
+    private final BasicControlPoint[] mControlPoints;
+    private final float mInitialSharpness;
+
+    SerializedBasicEnvelopeEffect(BasicControlPoint[] controlPoints, float initialSharpness) {
+        mControlPoints = controlPoints;
+        mInitialSharpness = initialSharpness;
+    }
+
+    @Override
+    public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
+        serializer.startTag(NAMESPACE, TAG_BASIC_ENVELOPE_EFFECT);
+
+        if (!Float.isNaN(mInitialSharpness)) {
+            serializer.attributeFloat(NAMESPACE, ATTRIBUTE_INITIAL_SHARPNESS, mInitialSharpness);
+        }
+
+        for (BasicControlPoint point : mControlPoints) {
+            serializer.startTag(NAMESPACE, TAG_CONTROL_POINT);
+            serializer.attributeFloat(NAMESPACE, ATTRIBUTE_INTENSITY, point.mIntensity);
+            serializer.attributeFloat(NAMESPACE, ATTRIBUTE_SHARPNESS, point.mSharpness);
+            serializer.attributeLong(NAMESPACE, ATTRIBUTE_DURATION_MS, point.mDurationMs);
+            serializer.endTag(NAMESPACE, TAG_CONTROL_POINT);
+        }
+
+        serializer.endTag(NAMESPACE, TAG_BASIC_ENVELOPE_EFFECT);
+    }
+
+    @Override
+    public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
+        VibrationEffect.BasicEnvelopeBuilder builder = new VibrationEffect.BasicEnvelopeBuilder();
+
+        if (!Float.isNaN(mInitialSharpness)) {
+            builder.setInitialSharpness(mInitialSharpness);
+        }
+
+        for (BasicControlPoint point : mControlPoints) {
+            builder.addControlPoint(point.mIntensity, point.mSharpness, point.mDurationMs);
+        }
+        composition.addEffect(builder.build());
+    }
+
+    @Override
+    public String toString() {
+        return "SerializedBasicEnvelopeEffect{"
+                + "initialSharpness=" + (Float.isNaN(mInitialSharpness) ? "" : mInitialSharpness)
+                + ", controlPoints=" + Arrays.toString(mControlPoints)
+                + '}';
+    }
+
+    static final class Builder {
+        private final List<BasicControlPoint> mControlPoints;
+        private float mInitialSharpness = Float.NaN;
+
+        Builder() {
+            mControlPoints = new ArrayList<>();
+        }
+
+        void setInitialSharpness(float sharpness) {
+            mInitialSharpness = sharpness;
+        }
+
+        void addControlPoint(float intensity, float sharpness, long durationMs) {
+            mControlPoints.add(new BasicControlPoint(intensity, sharpness, durationMs));
+        }
+
+        SerializedBasicEnvelopeEffect build() {
+            return new SerializedBasicEnvelopeEffect(
+                    mControlPoints.toArray(new BasicControlPoint[0]), mInitialSharpness);
+        }
+    }
+
+    /** Parser implementation for {@link SerializedBasicEnvelopeEffect}. */
+    static final class Parser {
+
+        @NonNull
+        static SerializedBasicEnvelopeEffect parseNext(@NonNull TypedXmlPullParser parser,
+                @XmlConstants.Flags int flags) throws XmlParserException, IOException {
+            XmlValidator.checkStartTag(parser, TAG_BASIC_ENVELOPE_EFFECT);
+            XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_INITIAL_SHARPNESS);
+
+            Builder builder = new Builder();
+            builder.setInitialSharpness(
+                    XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_INITIAL_SHARPNESS, 0f, 1f,
+                            Float.NaN));
+
+            int outerDepth = parser.getDepth();
+
+            // Read all nested tags
+            while (XmlReader.readNextTagWithin(parser, outerDepth)) {
+                parseControlPoint(parser, builder);
+                // Consume tag
+                XmlReader.readEndTag(parser);
+            }
+
+            // Check schema assertions about <basic-envelope-effect>
+            XmlValidator.checkParserCondition(!builder.mControlPoints.isEmpty(),
+                    "Expected tag %s to have at least one control point",
+                    TAG_BASIC_ENVELOPE_EFFECT);
+            XmlValidator.checkParserCondition(builder.mControlPoints.getLast().mIntensity == 0,
+                    "Basic envelope effects must end at a zero intensity control point");
+
+            return builder.build();
+        }
+
+        private static void parseControlPoint(TypedXmlPullParser parser, Builder builder)
+                throws XmlParserException {
+            XmlValidator.checkStartTag(parser, TAG_CONTROL_POINT);
+            XmlValidator.checkTagHasNoUnexpectedAttributes(
+                    parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_INTENSITY,
+                    ATTRIBUTE_SHARPNESS);
+            float intensity = XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_INTENSITY, 0,
+                    1);
+            float sharpness = XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_SHARPNESS, 0,
+                    1);
+            long durationMs = XmlReader.readAttributePositiveLong(parser, ATTRIBUTE_DURATION_MS);
+
+            builder.addControlPoint(intensity, sharpness, durationMs);
+        }
+    }
+
+    private static final class BasicControlPoint {
+        private final float mIntensity;
+        private final float mSharpness;
+        private final long mDurationMs;
+
+        BasicControlPoint(float intensity, float sharpness, long durationMs) {
+            mIntensity = intensity;
+            mSharpness = sharpness;
+            mDurationMs = durationMs;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(Locale.ROOT, "(%.2f, %.2f, %dms)", mIntensity, mSharpness,
+                    mDurationMs);
+        }
+    }
+}
+
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java b/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java
index 862f7cb..5eedec6 100644
--- a/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java
@@ -17,6 +17,7 @@
 package com.android.internal.vibrator.persistence;
 
 import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DELAY_MS;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DELAY_TYPE;
 import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_NAME;
 import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_SCALE;
 import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
@@ -25,9 +26,11 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
 import android.os.vibrator.PrimitiveSegment;
 
 import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
+import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveDelayType;
 import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveEffectName;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -46,17 +49,26 @@
     private final PrimitiveEffectName mPrimitiveName;
     private final float mPrimitiveScale;
     private final int mPrimitiveDelayMs;
+    @Nullable
+    private final PrimitiveDelayType mDelayType;
 
-    SerializedCompositionPrimitive(PrimitiveEffectName primitiveName, float scale, int delayMs) {
+    SerializedCompositionPrimitive(PrimitiveEffectName primitiveName, float scale, int delayMs,
+            @Nullable PrimitiveDelayType delayType) {
         mPrimitiveName = primitiveName;
         mPrimitiveScale = scale;
         mPrimitiveDelayMs = delayMs;
+        mDelayType = delayType;
     }
 
     @Override
     public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
-        composition.addPrimitive(mPrimitiveName.getPrimitiveId(), mPrimitiveScale,
-                mPrimitiveDelayMs);
+        if (Flags.primitiveCompositionAbsoluteDelay() && mDelayType != null) {
+            composition.addPrimitive(mPrimitiveName.getPrimitiveId(), mPrimitiveScale,
+                    mPrimitiveDelayMs, mDelayType.getDelayType());
+        } else {
+            composition.addPrimitive(mPrimitiveName.getPrimitiveId(), mPrimitiveScale,
+                    mPrimitiveDelayMs);
+        }
     }
 
     @Override
@@ -72,6 +84,12 @@
             serializer.attributeInt(NAMESPACE, ATTRIBUTE_DELAY_MS, mPrimitiveDelayMs);
         }
 
+        if (Flags.primitiveCompositionAbsoluteDelay() && mDelayType != null) {
+            if (mDelayType.getDelayType() != PrimitiveSegment.DEFAULT_DELAY_TYPE) {
+                serializer.attribute(NAMESPACE, ATTRIBUTE_DELAY_TYPE, mDelayType.toString());
+            }
+        }
+
         serializer.endTag(NAMESPACE, TAG_PRIMITIVE_EFFECT);
     }
 
@@ -81,6 +99,7 @@
                 + "name=" + mPrimitiveName
                 + ", scale=" + mPrimitiveScale
                 + ", delayMs=" + mPrimitiveDelayMs
+                + ", delayType=" + mDelayType
                 + '}';
     }
 
@@ -91,8 +110,14 @@
         static SerializedCompositionPrimitive parseNext(@NonNull TypedXmlPullParser parser)
                 throws XmlParserException, IOException {
             XmlValidator.checkStartTag(parser, TAG_PRIMITIVE_EFFECT);
-            XmlValidator.checkTagHasNoUnexpectedAttributes(parser,
-                    ATTRIBUTE_NAME, ATTRIBUTE_DELAY_MS, ATTRIBUTE_SCALE);
+
+            if (Flags.primitiveCompositionAbsoluteDelay()) {
+                XmlValidator.checkTagHasNoUnexpectedAttributes(parser,
+                        ATTRIBUTE_NAME, ATTRIBUTE_DELAY_MS, ATTRIBUTE_SCALE, ATTRIBUTE_DELAY_TYPE);
+            } else {
+                XmlValidator.checkTagHasNoUnexpectedAttributes(parser,
+                        ATTRIBUTE_NAME, ATTRIBUTE_DELAY_MS, ATTRIBUTE_SCALE);
+            }
 
             PrimitiveEffectName primitiveName = parsePrimitiveName(
                     parser.getAttributeValue(NAMESPACE, ATTRIBUTE_NAME));
@@ -100,11 +125,13 @@
                     parser, ATTRIBUTE_SCALE, 0, 1, PrimitiveSegment.DEFAULT_SCALE);
             int delayMs = XmlReader.readAttributeIntNonNegative(
                     parser, ATTRIBUTE_DELAY_MS, PrimitiveSegment.DEFAULT_DELAY_MILLIS);
+            PrimitiveDelayType delayType = parseDelayType(
+                    parser.getAttributeValue(NAMESPACE, ATTRIBUTE_DELAY_TYPE));
 
             // Consume tag
             XmlReader.readEndTag(parser);
 
-            return new SerializedCompositionPrimitive(primitiveName, scale, delayMs);
+            return new SerializedCompositionPrimitive(primitiveName, scale, delayMs, delayType);
         }
 
         @NonNull
@@ -119,5 +146,21 @@
             }
             return effectName;
         }
+
+        @Nullable
+        private static PrimitiveDelayType parseDelayType(@Nullable String name)
+                throws XmlParserException {
+            if (name == null) {
+                return null;
+            }
+            if (!Flags.primitiveCompositionAbsoluteDelay()) {
+                throw new XmlParserException("Unexpected primitive delay type " + name);
+            }
+            PrimitiveDelayType delayType = PrimitiveDelayType.findByName(name);
+            if (delayType == null) {
+                throw new XmlParserException("Unexpected primitive delay type " + name);
+            }
+            return delayType;
+        }
     }
 }
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java
new file mode 100644
index 0000000..6a89343
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEnvelopeEffect.java
@@ -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.vibrator.persistence;
+
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_AMPLITUDE;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DURATION_MS;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_FREQUENCY_HZ;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_INITIAL_FREQUENCY_HZ;
+import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_CONTROL_POINT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENVELOPE_EFFECT;
+
+import android.annotation.NonNull;
+import android.os.VibrationEffect;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Serialized representation of a waveform envelope effect created via
+ * {@link VibrationEffect.WaveformEnvelopeBuilder}.
+ *
+ * @hide
+ */
+final class SerializedWaveformEnvelopeEffect implements SerializedComposedEffect.SerializedSegment {
+
+    private final WaveformControlPoint[] mControlPoints;
+    private final float mInitialFrequency;
+
+    SerializedWaveformEnvelopeEffect(WaveformControlPoint[] controlPoints, float initialFrequency) {
+        mControlPoints = controlPoints;
+        mInitialFrequency = initialFrequency;
+    }
+
+    @Override
+    public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
+        serializer.startTag(NAMESPACE, TAG_WAVEFORM_ENVELOPE_EFFECT);
+
+        if (!Float.isNaN(mInitialFrequency)) {
+            serializer.attributeFloat(NAMESPACE, ATTRIBUTE_INITIAL_FREQUENCY_HZ, mInitialFrequency);
+        }
+
+        for (WaveformControlPoint point : mControlPoints) {
+            serializer.startTag(NAMESPACE, TAG_CONTROL_POINT);
+            serializer.attributeFloat(NAMESPACE, ATTRIBUTE_AMPLITUDE, point.mAmplitude);
+            serializer.attributeFloat(NAMESPACE, ATTRIBUTE_FREQUENCY_HZ, point.mFrequency);
+            serializer.attributeLong(NAMESPACE, ATTRIBUTE_DURATION_MS, point.mDurationMs);
+            serializer.endTag(NAMESPACE, TAG_CONTROL_POINT);
+        }
+
+        serializer.endTag(NAMESPACE, TAG_WAVEFORM_ENVELOPE_EFFECT);
+    }
+
+    @Override
+    public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
+        VibrationEffect.WaveformEnvelopeBuilder builder =
+                new VibrationEffect.WaveformEnvelopeBuilder();
+
+        if (!Float.isNaN(mInitialFrequency)) {
+            builder.setInitialFrequencyHz(mInitialFrequency);
+        }
+
+        for (WaveformControlPoint point : mControlPoints) {
+            builder.addControlPoint(point.mAmplitude, point.mFrequency, point.mDurationMs);
+        }
+        composition.addEffect(builder.build());
+    }
+
+    @Override
+    public String toString() {
+        return "SerializedWaveformEnvelopeEffect{"
+                + "InitialFrequency=" + (Float.isNaN(mInitialFrequency) ? "" : mInitialFrequency)
+                + ", controlPoints=" + Arrays.toString(mControlPoints)
+                + '}';
+    }
+
+    static final class Builder {
+        private final List<WaveformControlPoint> mControlPoints;
+        private float mInitialFrequencyHz = Float.NaN;
+
+        Builder() {
+            mControlPoints = new ArrayList<>();
+        }
+
+        void setInitialFrequencyHz(float frequencyHz) {
+            mInitialFrequencyHz = frequencyHz;
+        }
+
+        void addControlPoint(float amplitude, float frequencyHz, long durationMs) {
+            mControlPoints.add(new WaveformControlPoint(amplitude, frequencyHz, durationMs));
+        }
+
+        SerializedWaveformEnvelopeEffect build() {
+            return new SerializedWaveformEnvelopeEffect(
+                    mControlPoints.toArray(new WaveformControlPoint[0]), mInitialFrequencyHz);
+        }
+    }
+
+    /** Parser implementation for {@link SerializedWaveformEnvelopeEffect}. */
+    static final class Parser {
+
+        @NonNull
+        static SerializedWaveformEnvelopeEffect parseNext(@NonNull TypedXmlPullParser parser,
+                @XmlConstants.Flags int flags) throws XmlParserException, IOException {
+            XmlValidator.checkStartTag(parser, TAG_WAVEFORM_ENVELOPE_EFFECT);
+            XmlValidator.checkTagHasNoUnexpectedAttributes(parser, ATTRIBUTE_INITIAL_FREQUENCY_HZ);
+
+            Builder builder = new Builder();
+            builder.setInitialFrequencyHz(
+                    XmlReader.readAttributePositiveFloat(parser, ATTRIBUTE_INITIAL_FREQUENCY_HZ,
+                            Float.NaN));
+
+            int outerDepth = parser.getDepth();
+
+            while (XmlReader.readNextTagWithin(parser, outerDepth)) {
+                parseControlPoint(parser, builder);
+                // Consume tag
+                XmlReader.readEndTag(parser);
+            }
+
+            // Check schema assertions about <waveform-envelope-effect>
+            XmlValidator.checkParserCondition(!builder.mControlPoints.isEmpty(),
+                    "Expected tag %s to have at least one control point",
+                    TAG_WAVEFORM_ENVELOPE_EFFECT);
+
+            return builder.build();
+        }
+
+        private static void parseControlPoint(TypedXmlPullParser parser, Builder builder)
+                throws XmlParserException {
+            XmlValidator.checkStartTag(parser, TAG_CONTROL_POINT);
+            XmlValidator.checkTagHasNoUnexpectedAttributes(
+                    parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_AMPLITUDE,
+                    ATTRIBUTE_FREQUENCY_HZ);
+            float amplitude = XmlReader.readAttributeFloatInRange(parser, ATTRIBUTE_AMPLITUDE, 0,
+                    1);
+            float frequencyHz = XmlReader.readAttributePositiveFloat(parser,
+                    ATTRIBUTE_FREQUENCY_HZ);
+            long durationMs = XmlReader.readAttributePositiveLong(parser, ATTRIBUTE_DURATION_MS);
+
+            builder.addControlPoint(amplitude, frequencyHz, durationMs);
+        }
+    }
+
+    private static final class WaveformControlPoint {
+        private final float mAmplitude;
+        private final float mFrequency;
+        private final long mDurationMs;
+
+        WaveformControlPoint(float amplitude, float frequency, long durationMs) {
+            mAmplitude = amplitude;
+            mFrequency = frequency;
+            mDurationMs = durationMs;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(Locale.ROOT, "(%.2f, %.2f, %dms)", mAmplitude, mFrequency,
+                    mDurationMs);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
index a9fbcaf..314bfe4 100644
--- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
+++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
@@ -16,11 +16,13 @@
 
 package com.android.internal.vibrator.persistence;
 
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_BASIC_ENVELOPE_EFFECT;
 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREDEFINED_EFFECT;
 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PRIMITIVE_EFFECT;
 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VENDOR_EFFECT;
 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VIBRATION_EFFECT;
 import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENVELOPE_EFFECT;
 
 import android.annotation.NonNull;
 import android.os.VibrationEffect;
@@ -92,6 +94,32 @@
  *   }
  * </pre>
  *
+ * * Waveform Envelope effects
+ *
+ * <pre>
+ *     {@code
+ *       <vibration-effect>
+ *         <waveform-envelope-effect initialFrequencyHz="20.0">
+ *           <control-point amplitude="0.2" frequencyHz="80.0" durationMs="50" />
+ *           <control-point amplitude="0.5" frequencyHz="150.0" durationMs="50" />
+ *         </envelope-effect>
+ *       </vibration-effect>
+ *     }
+ * </pre>
+ *
+ * * Basic Envelope effects
+ *
+ * <pre>
+ *     {@code
+ *       <vibration-effect>
+ *         <basic-envelope-effect initialSharpness="0.3">
+ *            <control-point intensity="0.2" sharpness="0.5" durationMs="50" />
+ *            <control-point intensity="0.0" sharpness="1.0" durationMs="50" />
+ *          </envelope-effect>
+ *       </vibration-effect>
+ *     }
+ * </pre>
+ *
  * @hide
  */
 public class VibrationEffectXmlParser {
@@ -151,6 +179,18 @@
                 serializedVibration = new SerializedComposedEffect(
                         SerializedAmplitudeStepWaveform.Parser.parseNext(parser));
                 break;
+            case TAG_WAVEFORM_ENVELOPE_EFFECT:
+                if (Flags.normalizedPwleEffects()) {
+                    serializedVibration = new SerializedComposedEffect(
+                            SerializedWaveformEnvelopeEffect.Parser.parseNext(parser, flags));
+                    break;
+                } // else fall through
+            case TAG_BASIC_ENVELOPE_EFFECT:
+                if (Flags.normalizedPwleEffects()) {
+                    serializedVibration = new SerializedComposedEffect(
+                            SerializedBasicEnvelopeEffect.Parser.parseNext(parser, flags));
+                    break;
+                } // else fall through
             default:
                 throw new XmlParserException("Unexpected tag " + parser.getName()
                         + " in vibration tag " + vibrationTagName);
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
index d74a23d..ebe3434 100644
--- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
+++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
@@ -19,14 +19,17 @@
 import android.annotation.NonNull;
 import android.os.PersistableBundle;
 import android.os.VibrationEffect;
+import android.os.vibrator.BasicPwleSegment;
 import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.PwleSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
 
 import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
 import com.android.internal.vibrator.persistence.XmlConstants.PredefinedEffectName;
+import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveDelayType;
 import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveEffectName;
 
 import java.util.List;
@@ -44,6 +47,8 @@
  *     <li>A composition created exclusively via
  *         {@link VibrationEffect.Composition#addPrimitive(int, float, int)}
  *     <li>{@link VibrationEffect#createVendorEffect(PersistableBundle)}
+ *     <li>{@link VibrationEffect.WaveformEnvelopeBuilder}
+ *     <li>{@link VibrationEffect.BasicEnvelopeBuilder}
  * </ul>
  *
  * @hide
@@ -76,6 +81,12 @@
         if (firstSegment instanceof PrimitiveSegment) {
             return serializePrimitiveEffect(composed);
         }
+        if (Flags.normalizedPwleEffects() && firstSegment instanceof PwleSegment) {
+            return serializeWaveformEnvelopeEffect(composed);
+        }
+        if (Flags.normalizedPwleEffects() && firstSegment instanceof BasicPwleSegment) {
+            return serializeBasicEnvelopeEffect(composed);
+        }
         return serializeWaveformEffect(composed);
     }
 
@@ -109,6 +120,53 @@
         return new SerializedComposedEffect(primitives);
     }
 
+    private static SerializedComposedEffect serializeWaveformEnvelopeEffect(
+            VibrationEffect.Composed effect) throws XmlSerializerException {
+        SerializedWaveformEnvelopeEffect.Builder builder =
+                new SerializedWaveformEnvelopeEffect.Builder();
+        List<VibrationEffectSegment> segments = effect.getSegments();
+        XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1,
+                "Unsupported repeating waveform envelope effect %s", effect);
+        for (int i = 0; i < segments.size(); i++) {
+            XmlValidator.checkSerializerCondition(segments.get(i) instanceof PwleSegment,
+                    "Unsupported segment for waveform envelope effect %s", segments.get(i));
+            PwleSegment segment = (PwleSegment) segments.get(i);
+
+            if (i == 0 && segment.getStartFrequencyHz() != segment.getEndFrequencyHz()) {
+                // Initial frequency explicitly defined.
+                builder.setInitialFrequencyHz(segment.getStartFrequencyHz());
+            }
+
+            builder.addControlPoint(segment.getEndAmplitude(), segment.getEndFrequencyHz(),
+                    segment.getDuration());
+        }
+
+        return new SerializedComposedEffect(builder.build());
+    }
+
+    private static SerializedComposedEffect serializeBasicEnvelopeEffect(
+            VibrationEffect.Composed effect) throws XmlSerializerException {
+        SerializedBasicEnvelopeEffect.Builder builder = new SerializedBasicEnvelopeEffect.Builder();
+        List<VibrationEffectSegment> segments = effect.getSegments();
+        XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1,
+                "Unsupported repeating basic envelope effect %s", effect);
+        for (int i = 0; i < segments.size(); i++) {
+            XmlValidator.checkSerializerCondition(segments.get(i) instanceof BasicPwleSegment,
+                    "Unsupported segment for basic envelope effect %s", segments.get(i));
+            BasicPwleSegment segment = (BasicPwleSegment) segments.get(i);
+
+            if (i == 0 && segment.getStartSharpness() != segment.getEndSharpness()) {
+                // Initial sharpness explicitly defined.
+                builder.setInitialSharpness(segment.getStartSharpness());
+            }
+
+            builder.addControlPoint(segment.getEndIntensity(), segment.getEndSharpness(),
+                    segment.getDuration());
+        }
+
+        return new SerializedComposedEffect(builder.build());
+    }
+
     private static SerializedComposedEffect serializeWaveformEffect(
             VibrationEffect.Composed effect) throws XmlSerializerException {
         SerializedAmplitudeStepWaveform.Builder serializedWaveformBuilder =
@@ -170,8 +228,20 @@
         XmlValidator.checkSerializerCondition(primitiveName != null,
                 "Unsupported primitive effect id %s", primitive.getPrimitiveId());
 
+        PrimitiveDelayType delayType = null;
+
+        if (Flags.primitiveCompositionAbsoluteDelay()) {
+            delayType = PrimitiveDelayType.findByType(primitive.getDelayType());
+            XmlValidator.checkSerializerCondition(delayType != null,
+                    "Unsupported primitive delay type %s", primitive.getDelayType());
+        } else {
+            XmlValidator.checkSerializerCondition(
+                    primitive.getDelayType() == PrimitiveSegment.DEFAULT_DELAY_TYPE,
+                    "Unsupported primitive delay type %s", primitive.getDelayType());
+        }
+
         return new SerializedCompositionPrimitive(
-                primitiveName, primitive.getScale(), primitive.getDelay());
+                primitiveName, primitive.getScale(), primitive.getDelay(), delayType);
     }
 
     private static int toAmplitudeInt(float amplitude) {
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
index 2a55d99..df262cf 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.VibrationEffect;
+import android.os.VibrationEffect.Composition.DelayType;
 import android.os.VibrationEffect.Composition.PrimitiveType;
 
 import java.lang.annotation.Retention;
@@ -41,16 +42,25 @@
     public static final String TAG_PREDEFINED_EFFECT = "predefined-effect";
     public static final String TAG_PRIMITIVE_EFFECT = "primitive-effect";
     public static final String TAG_VENDOR_EFFECT = "vendor-effect";
+    public static final String TAG_WAVEFORM_ENVELOPE_EFFECT = "waveform-envelope-effect";
+    public static final String TAG_BASIC_ENVELOPE_EFFECT = "basic-envelope-effect";
     public static final String TAG_WAVEFORM_EFFECT = "waveform-effect";
     public static final String TAG_WAVEFORM_ENTRY = "waveform-entry";
     public static final String TAG_REPEATING = "repeating";
+    public static final String TAG_CONTROL_POINT = "control-point";
 
     public static final String ATTRIBUTE_NAME = "name";
     public static final String ATTRIBUTE_FALLBACK = "fallback";
     public static final String ATTRIBUTE_DURATION_MS = "durationMs";
     public static final String ATTRIBUTE_AMPLITUDE = "amplitude";
+    public static final String ATTRIBUTE_FREQUENCY_HZ = "frequencyHz";
+    public static final String ATTRIBUTE_INITIAL_FREQUENCY_HZ = "initialFrequencyHz";
+    public static final String ATTRIBUTE_INTENSITY = "intensity";
+    public static final String ATTRIBUTE_SHARPNESS = "sharpness";
+    public static final String ATTRIBUTE_INITIAL_SHARPNESS = "initialSharpness";
     public static final String ATTRIBUTE_SCALE = "scale";
     public static final String ATTRIBUTE_DELAY_MS = "delayMs";
+    public static final String ATTRIBUTE_DELAY_TYPE = "delayType";
 
     public static final String VALUE_AMPLITUDE_DEFAULT = "default";
 
@@ -87,7 +97,7 @@
 
         /**
          * Return the {@link PrimitiveEffectName} that represents given primitive id, or null if
-         * none of the available names maps to the given id.
+         * none of the available names map to the given id.
          */
         @Nullable
         public static PrimitiveEffectName findById(int primitiveId) {
@@ -200,4 +210,53 @@
             return name().toLowerCase(Locale.ROOT);
         }
     }
+
+    /** Represent supported values for attribute delay type in {@link #TAG_PRIMITIVE_EFFECT}  */
+    public enum PrimitiveDelayType {
+        PAUSE(VibrationEffect.Composition.DELAY_TYPE_PAUSE),
+        RELATIVE_START_OFFSET(VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET);
+
+        @DelayType private final int mDelayType;
+
+        PrimitiveDelayType(@DelayType int type) {
+            mDelayType = type;
+        }
+
+        /**
+         * Return the {@link PrimitiveEffectName} that represents given primitive id, or null if
+         * none of the available names maps to the given id.
+         */
+        @Nullable
+        public static PrimitiveDelayType findByType(int delayType) {
+            for (PrimitiveDelayType type : PrimitiveDelayType.values()) {
+                if (type.mDelayType == delayType) {
+                    return type;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Return the {@link PrimitiveEffectName} that represents given primitive name, or null if
+         * none of the available names maps to the given name.
+         */
+        @Nullable
+        public static PrimitiveDelayType findByName(@NonNull String delayType) {
+            try {
+                return PrimitiveDelayType.valueOf(delayType.toUpperCase(Locale.ROOT));
+            } catch (IllegalArgumentException e) {
+                return null;
+            }
+        }
+
+        @DelayType
+        public int getDelayType() {
+            return mDelayType;
+        }
+
+        @Override
+        public String toString() {
+            return name().toLowerCase(Locale.ROOT);
+        }
+    }
 }
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlReader.java b/core/java/com/android/internal/vibrator/persistence/XmlReader.java
index 0ac6fef..1c4a783 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlReader.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlReader.java
@@ -221,12 +221,63 @@
         if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) {
             return defaultValue;
         }
+
+        return readAttributeFloatInRange(parser, attrName, lowerInclusive, upperInclusive);
+    }
+
+    /**
+     * Read attribute from current tag as a float within given inclusive range.
+     */
+    public static float readAttributeFloatInRange(
+            TypedXmlPullParser parser, String attrName, float lowerInclusive,
+            float upperInclusive) throws XmlParserException {
         String tagName = parser.getName();
         float value = readAttributeFloat(parser, attrName);
 
         XmlValidator.checkParserCondition(value >= lowerInclusive && value <= upperInclusive,
-                "Unexpected %s = %f in tag %s, expected %s in [%f, %f]",
-                attrName, value, tagName, attrName, lowerInclusive, upperInclusive);
+                "Unexpected %s = %f in tag %s, expected %s in [%f, %f]", attrName, value, tagName,
+                attrName, lowerInclusive, upperInclusive);
+        return value;
+    }
+
+    /**
+     * Read attribute from current tag as a positive float, returning default value if attribute
+     * is missing.
+     */
+    public static float readAttributePositiveFloat(TypedXmlPullParser parser, String attrName,
+            float defaultValue) throws XmlParserException {
+        if (parser.getAttributeIndex(NAMESPACE, attrName) < 0) {
+            return defaultValue;
+        }
+
+        return readAttributePositiveFloat(parser, attrName);
+    }
+
+    /**
+     * Read attribute from current tag as a positive float.
+     */
+    public static float readAttributePositiveFloat(TypedXmlPullParser parser, String attrName)
+            throws XmlParserException {
+        String tagName = parser.getName();
+        float value = readAttributeFloat(parser, attrName);
+
+        XmlValidator.checkParserCondition(value > 0,
+                "Unexpected %s = %d in tag %s, expected %s > 0", attrName, value, tagName,
+                attrName);
+        return value;
+    }
+
+    /**
+     * Read attribute from current tag as a positive long.
+     */
+    public static long readAttributePositiveLong(TypedXmlPullParser parser, String attrName)
+            throws XmlParserException {
+        String tagName = parser.getName();
+        long value = readAttributeLong(parser, attrName);
+
+        XmlValidator.checkParserCondition(value > 0,
+                "Unexpected %s = %d in tag %s, expected %s > 0", attrName, value, tagName,
+                attrName);
         return value;
     }
 
@@ -251,4 +302,15 @@
             throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e);
         }
     }
+
+    private static long readAttributeLong(TypedXmlPullParser parser, String attrName)
+            throws XmlParserException {
+        String tagName = parser.getName();
+        try {
+            return parser.getAttributeLong(NAMESPACE, attrName);
+        } catch (XmlPullParserException e) {
+            String rawValue = parser.getAttributeValue(NAMESPACE, attrName);
+            throw XmlParserException.createFromPullParserException(tagName, attrName, rawValue, e);
+        }
+    }
 }
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 2bfbf84..4b90420 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.widget;
 
+import static android.app.Flags.notificationsRedesignTemplates;
 import static android.widget.flags.Flags.conversationLayoutUseMaximumChildHeight;
 
 import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_EXTERNAL;
@@ -692,7 +693,7 @@
     }
 
     private void updateActionListPadding() {
-        if (mActions != null) {
+        if (!notificationsRedesignTemplates() && mActions != null) {
             mActions.setCollapsibleIndentDimen(R.dimen.call_notification_collapsible_indent);
         }
     }
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 97a2d3b..4305ba7 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -261,7 +261,7 @@
         MessagingGroup createdGroup = sInstancePool.acquire();
         if (createdGroup == null) {
             createdGroup = (MessagingGroup) LayoutInflater.from(layout.getContext()).inflate(
-                    R.layout.notification_template_messaging_group, layout,
+                    getMessagingGroupLayoutResource(), layout,
                     false);
             createdGroup.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
         }
@@ -269,6 +269,14 @@
         return createdGroup;
     }
 
+    private static int getMessagingGroupLayoutResource() {
+        if (Flags.notificationsRedesignTemplates()) {
+            return R.layout.notification_2025_messaging_group;
+        } else {
+            return R.layout.notification_template_messaging_group;
+        }
+    }
+
     public void removeMessage(MessagingMessage messagingMessage,
             ArrayList<MessagingLinearLayout.MessagingChild> toRecycle) {
         View view = messagingMessage.getView();
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index 301dc39..cac2024 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -20,6 +20,7 @@
 import static android.app.Flags.evenlyDividedCallStyleActionLayout;
 
 import android.annotation.DimenRes;
+import android.app.Flags;
 import android.app.Notification;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -58,7 +59,7 @@
     private int mEmphasizedPaddingBottom;
     private int mEmphasizedHeight;
     private int mRegularHeight;
-    @DimenRes private int mCollapsibleIndentDimen = R.dimen.notification_actions_padding_start;
+    @DimenRes private int mCollapsibleIndentDimen;
     int mNumNotGoneChildren;
     int mNumPriorityChildren;
 
@@ -73,6 +74,10 @@
     public NotificationActionListLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
 
+        mCollapsibleIndentDimen = Flags.notificationsRedesignTemplates()
+                ? R.dimen.notification_2025_actions_margin_start
+                : R.dimen.notification_actions_padding_start;
+
         int[] attrIds = { android.R.attr.gravity };
         TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes);
         mGravity = ta.getInt(0, 0);
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/AndroidPlatformSemanticNodeApplier.java b/core/java/com/android/internal/widget/remotecompose/accessibility/AndroidPlatformSemanticNodeApplier.java
new file mode 100644
index 0000000..1bdbaa4
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/AndroidPlatformSemanticNodeApplier.java
@@ -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.internal.widget.remotecompose.accessibility;
+
+import android.graphics.Rect;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+public class AndroidPlatformSemanticNodeApplier
+        extends BaseSemanticNodeApplier<AccessibilityNodeInfo> {
+
+    private static final String ROLE_DESCRIPTION_KEY = "AccessibilityNodeInfo.roleDescription";
+
+    @Override
+    protected void setClickable(AccessibilityNodeInfo nodeInfo, boolean clickable) {
+        nodeInfo.setClickable(clickable);
+        if (clickable) {
+            nodeInfo.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
+        } else {
+            nodeInfo.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
+        }
+    }
+
+    @Override
+    protected void setEnabled(AccessibilityNodeInfo nodeInfo, boolean enabled) {
+        nodeInfo.setEnabled(enabled);
+    }
+
+    @Override
+    protected CharSequence getStateDescription(AccessibilityNodeInfo nodeInfo) {
+        return nodeInfo.getStateDescription();
+    }
+
+    @Override
+    protected void setStateDescription(AccessibilityNodeInfo nodeInfo, CharSequence description) {
+        nodeInfo.setStateDescription(description);
+    }
+
+    @Override
+    protected void setRoleDescription(AccessibilityNodeInfo nodeInfo, String description) {
+        nodeInfo.getExtras().putCharSequence(ROLE_DESCRIPTION_KEY, description);
+    }
+
+    @Override
+    protected CharSequence getText(AccessibilityNodeInfo nodeInfo) {
+        return nodeInfo.getText();
+    }
+
+    @Override
+    protected void setText(AccessibilityNodeInfo nodeInfo, CharSequence text) {
+        nodeInfo.setText(text);
+    }
+
+    @Override
+    protected CharSequence getContentDescription(AccessibilityNodeInfo nodeInfo) {
+        return nodeInfo.getContentDescription();
+    }
+
+    @Override
+    protected void setContentDescription(AccessibilityNodeInfo nodeInfo, CharSequence description) {
+        nodeInfo.setContentDescription(description);
+    }
+
+    @Override
+    protected void setBoundsInScreen(AccessibilityNodeInfo nodeInfo, Rect bounds) {
+        nodeInfo.setBoundsInParent(bounds);
+        nodeInfo.setBoundsInScreen(bounds);
+    }
+
+    @Override
+    protected void setUniqueId(AccessibilityNodeInfo nodeInfo, String id) {
+        nodeInfo.setUniqueId(id);
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/BaseSemanticNodeApplier.java b/core/java/com/android/internal/widget/remotecompose/accessibility/BaseSemanticNodeApplier.java
new file mode 100644
index 0000000..228afb8
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/BaseSemanticNodeApplier.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.accessibility;
+
+import android.graphics.Rect;
+
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
+
+import java.util.List;
+
+/**
+ * Base class for applying semantic information to a node.
+ *
+ * <p>This class provides common functionality for applying semantic information extracted from
+ * Compose UI components to a node representation used for accessibility purposes. It handles
+ * applying properties like content description, text, role, clickability, and bounds.
+ *
+ * <p>Subclasses are responsible for implementing methods to actually set these properties on the
+ * specific node type they handle.
+ *
+ * @param <N> The type of node this applier works with.
+ */
+public abstract class BaseSemanticNodeApplier<N> implements SemanticNodeApplier<N> {
+    @Override
+    public void applyComponent(
+            RemoteComposeDocumentAccessibility remoteComposeAccessibility,
+            N nodeInfo,
+            Component component,
+            List<AccessibilitySemantics> semantics) {
+        float[] locationInWindow = new float[2];
+        component.getLocationInWindow(locationInWindow);
+        Rect bounds =
+                new Rect(
+                        (int) locationInWindow[0],
+                        (int) locationInWindow[1],
+                        (int) (locationInWindow[0] + component.getWidth()),
+                        (int) (locationInWindow[1] + component.getHeight()));
+        setBoundsInScreen(nodeInfo, bounds);
+
+        setUniqueId(nodeInfo, String.valueOf(component.getComponentId()));
+
+        if (component instanceof AccessibleComponent) {
+            applyContentDescription(
+                    ((AccessibleComponent) component).getContentDescriptionId(),
+                    nodeInfo,
+                    remoteComposeAccessibility);
+
+            applyText(
+                    ((AccessibleComponent) component).getTextId(),
+                    nodeInfo,
+                    remoteComposeAccessibility);
+
+            applyRole(((AccessibleComponent) component).getRole(), nodeInfo);
+        }
+
+        applySemantics(remoteComposeAccessibility, nodeInfo, semantics);
+
+        if (getText(nodeInfo) == null && getContentDescription(nodeInfo) == null) {
+            setContentDescription(nodeInfo, "");
+        }
+    }
+
+    protected void applySemantics(
+            RemoteComposeDocumentAccessibility remoteComposeAccessibility,
+            N nodeInfo,
+            List<AccessibilitySemantics> semantics) {
+        for (AccessibilitySemantics semantic : semantics) {
+            if (semantic.isInterestingForSemantics()) {
+                if (semantic instanceof CoreSemantics) {
+                    CoreSemantics coreSemantics = (CoreSemantics) semantic;
+                    applyCoreSemantics(remoteComposeAccessibility, nodeInfo, coreSemantics);
+                } else if (semantic instanceof AccessibleComponent) {
+                    AccessibleComponent accessibleComponent = (AccessibleComponent) semantic;
+                    if (accessibleComponent.isClickable()) {
+                        setClickable(nodeInfo, true);
+                    }
+
+                    if (accessibleComponent.getContentDescriptionId() != null) {
+                        applyContentDescription(
+                                accessibleComponent.getContentDescriptionId(),
+                                nodeInfo,
+                                remoteComposeAccessibility);
+                    }
+
+                    if (accessibleComponent.getTextId() != null) {
+                        applyText(
+                                accessibleComponent.getTextId(),
+                                nodeInfo,
+                                remoteComposeAccessibility);
+                    }
+
+                    applyRole(accessibleComponent.getRole(), nodeInfo);
+                }
+            }
+        }
+    }
+
+    protected void applyCoreSemantics(
+            RemoteComposeDocumentAccessibility remoteComposeAccessibility,
+            N nodeInfo,
+            CoreSemantics coreSemantics) {
+        applyContentDescription(
+                coreSemantics.getContentDescriptionId(), nodeInfo, remoteComposeAccessibility);
+
+        applyRole(coreSemantics.getRole(), nodeInfo);
+
+        applyText(coreSemantics.getTextId(), nodeInfo, remoteComposeAccessibility);
+
+        applyStateDescription(
+                coreSemantics.getStateDescriptionId(), nodeInfo, remoteComposeAccessibility);
+
+        if (!coreSemantics.mEnabled) {
+            setEnabled(nodeInfo, false);
+        }
+    }
+
+    protected void applyStateDescription(
+            Integer stateDescriptionId,
+            N nodeInfo,
+            RemoteComposeDocumentAccessibility remoteComposeAccessibility) {
+        if (stateDescriptionId != null) {
+            setStateDescription(
+                    nodeInfo,
+                    appendNullable(
+                            getStateDescription(nodeInfo),
+                            remoteComposeAccessibility.stringValue(stateDescriptionId)));
+        }
+    }
+
+    protected void applyRole(AccessibleComponent.Role role, N nodeInfo) {
+        if (role != null) {
+            setRoleDescription(nodeInfo, role.getDescription());
+        }
+    }
+
+    protected void applyText(
+            Integer textId,
+            N nodeInfo,
+            RemoteComposeDocumentAccessibility remoteComposeAccessibility) {
+        if (textId != null) {
+            setText(
+                    nodeInfo,
+                    appendNullable(
+                            getText(nodeInfo), remoteComposeAccessibility.stringValue(textId)));
+        }
+    }
+
+    protected void applyContentDescription(
+            Integer contentDescriptionId,
+            N nodeInfo,
+            RemoteComposeDocumentAccessibility remoteComposeAccessibility) {
+        if (contentDescriptionId != null) {
+            setContentDescription(
+                    nodeInfo,
+                    appendNullable(
+                            getContentDescription(nodeInfo),
+                            remoteComposeAccessibility.stringValue(contentDescriptionId)));
+        }
+    }
+
+    private CharSequence appendNullable(CharSequence contentDescription, String value) {
+        if (contentDescription == null) {
+            return value;
+        } else if (value == null) {
+            return contentDescription;
+        } else {
+            return contentDescription + " " + value;
+        }
+    }
+
+    protected abstract void setClickable(N nodeInfo, boolean b);
+
+    protected abstract void setEnabled(N nodeInfo, boolean b);
+
+    protected abstract CharSequence getStateDescription(N nodeInfo);
+
+    protected abstract void setStateDescription(N nodeInfo, CharSequence charSequence);
+
+    protected abstract void setRoleDescription(N nodeInfo, String description);
+
+    protected abstract CharSequence getText(N nodeInfo);
+
+    protected abstract void setText(N nodeInfo, CharSequence charSequence);
+
+    protected abstract CharSequence getContentDescription(N nodeInfo);
+
+    protected abstract void setContentDescription(N nodeInfo, CharSequence charSequence);
+
+    protected abstract void setBoundsInScreen(N nodeInfo, Rect bounds);
+
+    protected abstract void setUniqueId(N nodeInfo, String s);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java
new file mode 100644
index 0000000..2cd4f03
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.accessibility;
+
+import android.annotation.Nullable;
+import android.graphics.PointF;
+import android.os.Bundle;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Java Player implementation of the {@link RemoteComposeDocumentAccessibility} interface. Each item
+ * in the semantic tree is a {@link Component} from the remote Compose UI. Each Component can have a
+ * list of modifiers that must be tagged with {@link AccessibilitySemantics} either incidentally
+ * (see {@link ClickModifierOperation}) or explicitly (see {@link CoreSemantics}).
+ */
+public class CoreDocumentAccessibility implements RemoteComposeDocumentAccessibility {
+    private final CoreDocument mDocument;
+
+    public CoreDocumentAccessibility(CoreDocument document) {
+        this.mDocument = document;
+    }
+
+    @Nullable
+    @Override
+    public Integer getComponentIdAt(PointF point) {
+        return RootId;
+    }
+
+    @Override
+    public @Nullable Component findComponentById(int virtualViewId) {
+        RootLayoutComponent root = mDocument.getRootLayoutComponent();
+
+        if (root == null || virtualViewId == -1) {
+            return root;
+        }
+
+        return componentStream(root)
+                .filter(op -> op.getComponentId() == virtualViewId)
+                .findFirst()
+                .orElse(null);
+    }
+
+    @Override
+    public CoreSemantics.Mode mergeMode(Component component) {
+        if (!(component instanceof LayoutComponent)) {
+            return CoreSemantics.Mode.SET;
+        }
+
+        CoreSemantics.Mode result = CoreSemantics.Mode.SET;
+
+        for (ModifierOperation modifier :
+                ((LayoutComponent) component).getComponentModifiers().getList()) {
+            if (modifier instanceof AccessibleComponent) {
+                AccessibleComponent semantics = (AccessibleComponent) modifier;
+
+                if (semantics.getMode().ordinal() > result.ordinal()) {
+                    result = semantics.getMode();
+                }
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public boolean performAction(Component component, int action, Bundle arguments) {
+        if (action == ACTION_CLICK) {
+            mDocument.performClick(component.getComponentId());
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Nullable
+    @Override
+    public String stringValue(int id) {
+        Object value = mDocument.getRemoteComposeState().getFromId(id);
+
+        return value != null ? String.valueOf(value) : null;
+    }
+
+    @Override
+    public List<AccessibilitySemantics> semanticModifiersForComponent(Component component) {
+        if (!(component instanceof LayoutComponent)) {
+            return Collections.emptyList();
+        }
+
+        List<ModifierOperation> modifiers =
+                ((LayoutComponent) component).getComponentModifiers().getList();
+
+        return modifiers.stream()
+                .filter(
+                        it ->
+                                it instanceof AccessibilitySemantics
+                                        && ((AccessibilitySemantics) it)
+                                                .isInterestingForSemantics())
+                .map(i -> (AccessibilitySemantics) i)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public List<Integer> semanticallyRelevantChildComponents(
+            Component component, boolean useUnmergedTree) {
+        if (!component.isVisible()) {
+            return Collections.emptyList();
+        }
+
+        CoreSemantics.Mode mergeMode = mergeMode(component);
+        if (mergeMode == CoreSemantics.Mode.CLEAR_AND_SET
+                || (!useUnmergedTree && mergeMode == CoreSemantics.Mode.MERGE)) {
+            return Collections.emptyList();
+        }
+
+        ArrayList<Integer> result = new ArrayList<>();
+
+        for (Operation child : component.mList) {
+            if (child instanceof Component) {
+                if (isInteresting((Component) child)) {
+                    result.add(((Component) child).getComponentId());
+                } else {
+                    result.addAll(
+                            semanticallyRelevantChildComponents(
+                                    (Component) child, useUnmergedTree));
+                }
+            }
+        }
+
+        return result;
+    }
+
+    static Stream<Component> componentStream(Component root) {
+        return Stream.concat(
+                Stream.of(root),
+                root.mList.stream()
+                        .flatMap(
+                                op -> {
+                                    if (op instanceof Component) {
+                                        return componentStream((Component) op);
+                                    } else {
+                                        return Stream.empty();
+                                    }
+                                }));
+    }
+
+    static Stream<ModifierOperation> modifiersStream(Component component) {
+        return component.mList.stream()
+                .filter(it -> it instanceof ComponentModifiers)
+                .flatMap(it -> ((ComponentModifiers) it).getList().stream());
+    }
+
+    static boolean isInteresting(Component component) {
+        if (!component.isVisible()) {
+            return false;
+        }
+
+        return isContainerWithSemantics(component)
+                || modifiersStream(component)
+                        .anyMatch(CoreDocumentAccessibility::isModifierWithSemantics);
+    }
+
+    static boolean isModifierWithSemantics(ModifierOperation modifier) {
+        return modifier instanceof AccessibilitySemantics
+                && ((AccessibilitySemantics) modifier).isInterestingForSemantics();
+    }
+
+    static boolean isContainerWithSemantics(Component component) {
+        if (component instanceof AccessibilitySemantics) {
+            return ((AccessibilitySemantics) component).isInterestingForSemantics();
+        }
+
+        if (!(component instanceof LayoutComponent)) {
+            return false;
+        }
+
+        return ((LayoutComponent) component)
+                .getComponentModifiers().getList().stream()
+                        .anyMatch(CoreDocumentAccessibility::isModifierWithSemantics);
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java
new file mode 100644
index 0000000..010253e
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeAccessibilityRegistrar.java
@@ -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.internal.widget.remotecompose.accessibility;
+
+import android.annotation.NonNull;
+import android.view.View;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+
+/**
+ * Trivial wrapper for calling setAccessibilityDelegate on a View. This exists primarily because the
+ * RemoteDocumentPlayer is either running in the platform on a known API version, or outside in
+ * which case it must use the Androidx ViewCompat class.
+ */
+public class PlatformRemoteComposeAccessibilityRegistrar
+        implements RemoteComposeAccessibilityRegistrar {
+    public PlatformRemoteComposeTouchHelper forRemoteComposePlayer(
+            View player, @NonNull CoreDocument coreDocument) {
+        return new PlatformRemoteComposeTouchHelper(
+                player,
+                new CoreDocumentAccessibility(coreDocument),
+                new AndroidPlatformSemanticNodeApplier());
+    }
+
+    public void setAccessibilityDelegate(View remoteComposePlayer, CoreDocument document) {
+        remoteComposePlayer.setAccessibilityDelegate(
+                forRemoteComposePlayer(remoteComposePlayer, document));
+    }
+
+    public void clearAccessibilityDelegate(View remoteComposePlayer) {
+        remoteComposePlayer.setAccessibilityDelegate(null);
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java
new file mode 100644
index 0000000..39a2ab3
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.accessibility;
+
+import static com.android.internal.widget.remotecompose.accessibility.RemoteComposeDocumentAccessibility.RootId;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.PointF;
+import android.os.Bundle;
+import android.util.IntArray;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.widget.ExploreByTouchHelper;
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics.Mode;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+
+public class PlatformRemoteComposeTouchHelper extends ExploreByTouchHelper {
+    private final RemoteComposeDocumentAccessibility mRemoteDocA11y;
+
+    private final SemanticNodeApplier<AccessibilityNodeInfo> mApplier;
+
+    public PlatformRemoteComposeTouchHelper(
+            View host,
+            RemoteComposeDocumentAccessibility remoteDocA11y,
+            SemanticNodeApplier<AccessibilityNodeInfo> applier) {
+        super(host);
+        this.mRemoteDocA11y = remoteDocA11y;
+        this.mApplier = applier;
+    }
+
+    public static PlatformRemoteComposeTouchHelper forRemoteComposePlayer(
+            View player, @NonNull CoreDocument coreDocument) {
+        return new PlatformRemoteComposeTouchHelper(
+                player,
+                new CoreDocumentAccessibility(coreDocument),
+                new AndroidPlatformSemanticNodeApplier());
+    }
+
+    /**
+     * Gets the virtual view ID at a given location on the screen.
+     *
+     * <p>This method is called by the Accessibility framework to determine which virtual view, if
+     * any, is located at a specific point on the screen. It uses the {@link
+     * RemoteComposeDocumentAccessibility#getComponentIdAt(PointF)} method to find the ID of the
+     * component at the given coordinates.
+     *
+     * @param x The x-coordinate of the location in pixels.
+     * @param y The y-coordinate of the location in pixels.
+     * @return The ID of the virtual view at the given location, or {@link #INVALID_ID} if no
+     *     virtual view is found at that location.
+     */
+    @Override
+    protected int getVirtualViewAt(float x, float y) {
+        Integer root = mRemoteDocA11y.getComponentIdAt(new PointF(x, y));
+
+        if (root == null) {
+            return INVALID_ID;
+        }
+
+        return root;
+    }
+
+    /**
+     * Populates a list with the visible virtual view IDs.
+     *
+     * <p>This method is called by the accessibility framework to retrieve the IDs of all visible
+     * virtual views in the accessibility hierarchy. It traverses the hierarchy starting from the
+     * root node (RootId) and adds the ID of each visible view to the provided list.
+     *
+     * @param virtualViewIds The list to be populated with the visible virtual view IDs.
+     */
+    @Override
+    protected void getVisibleVirtualViews(IntArray virtualViewIds) {
+        Stack<Integer> toVisit = new Stack<>();
+        Set<Integer> visited = new HashSet<>();
+
+        toVisit.push(RootId);
+
+        while (!toVisit.isEmpty()) {
+            Integer componentId = toVisit.remove(0);
+
+            if (visited.add(componentId)) {
+                Component component = mRemoteDocA11y.findComponentById(componentId);
+
+                // Only include the root when it has semantics such as content description
+                if (!RootId.equals(componentId)
+                        || !mRemoteDocA11y.semanticModifiersForComponent(component).isEmpty()) {
+                    virtualViewIds.add(componentId);
+                }
+
+                if (component != null) {
+                    Mode mergeMode = mRemoteDocA11y.mergeMode(component);
+
+                    if (mergeMode == Mode.SET) {
+                        List<Integer> childViews =
+                                mRemoteDocA11y.semanticallyRelevantChildComponents(
+                                        component, false);
+
+                        toVisit.addAll(childViews);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onPopulateNodeForVirtualView(
+            int virtualViewId, @NonNull AccessibilityNodeInfo node) {
+        Component component = mRemoteDocA11y.findComponentById(virtualViewId);
+
+        Mode mergeMode = mRemoteDocA11y.mergeMode(component);
+
+        // default to enabled
+        node.setEnabled(true);
+
+        if (mergeMode == Mode.MERGE) {
+            List<Integer> childViews =
+                    mRemoteDocA11y.semanticallyRelevantChildComponents(component, true);
+
+            for (Integer childView : childViews) {
+                onPopulateNodeForVirtualView(childView, node);
+            }
+        }
+
+        List<AccessibilitySemantics> semantics =
+                mRemoteDocA11y.semanticModifiersForComponent(component);
+        mApplier.applyComponent(mRemoteDocA11y, node, component, semantics);
+    }
+
+    @Override
+    protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {}
+
+    @Override
+    protected boolean onPerformActionForVirtualView(
+            int virtualViewId, int action, @Nullable Bundle arguments) {
+        Component component = mRemoteDocA11y.findComponentById(virtualViewId);
+
+        if (component != null) {
+            return mRemoteDocA11y.performAction(component, action, arguments);
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeAccessibilityRegistrar.java b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeAccessibilityRegistrar.java
new file mode 100644
index 0000000..7e8236b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeAccessibilityRegistrar.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.internal.widget.remotecompose.accessibility;
+
+import android.view.View;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+
+/**
+ * Interface for registering and clearing accessibility delegates for remote compose players.
+ *
+ * <p>This interface is responsible for managing the accessibility delegate associated with a remote
+ * compose player view. It allows for setting and clearing the delegate, which is used to handle
+ * accessibility events and provide accessibility information for the remote compose content.
+ */
+public interface RemoteComposeAccessibilityRegistrar {
+    void setAccessibilityDelegate(View remoteComposePlayer, CoreDocument document);
+
+    void clearAccessibilityDelegate(View remoteComposePlayer);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeDocumentAccessibility.java b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeDocumentAccessibility.java
new file mode 100644
index 0000000..50f75e4
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeDocumentAccessibility.java
@@ -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.internal.widget.remotecompose.accessibility;
+
+import android.annotation.Nullable;
+import android.graphics.PointF;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
+
+import java.util.List;
+
+/**
+ * Interface for interacting with the accessibility features of a remote Compose UI. This interface
+ * provides methods to perform actions, retrieve state, and query the accessibility tree of the
+ * remote Compose UI.
+ */
+public interface RemoteComposeDocumentAccessibility {
+    // Matches ExploreByTouchHelper.HOST_ID
+    Integer RootId = View.NO_ID;
+
+    // androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLICK
+    int ACTION_CLICK = 0x00000010;
+
+    /**
+     * Performs the specified action on the given component.
+     *
+     * @param component The component on which to perform the action.
+     * @param action The action to perform.
+     * @param arguments Optional arguments for the action.
+     * @return {@code true} if the action was performed successfully, {@code false} otherwise.
+     */
+    boolean performAction(Component component, int action, Bundle arguments);
+
+    /**
+     * Retrieves the string value associated with the given ID.
+     *
+     * @param id The ID to retrieve the string value for.
+     * @return The string value associated with the ID, or {@code null} if no such value exists.
+     */
+    @Nullable
+    String stringValue(int id);
+
+    /**
+     * Retrieves a list of child view IDs semantically contained within the given component/virtual
+     * view. These may later be hidden from accessibility services by properties, but should contain
+     * only possibly semantically relevant virtual views.
+     *
+     * @param component The component to retrieve child view IDs from, or [RootId] for the top
+     *     level.
+     * @param useUnmergedTree Whether to include merged children
+     * @return A list of integer IDs representing the child views of the component.
+     */
+    List<Integer> semanticallyRelevantChildComponents(Component component, boolean useUnmergedTree);
+
+    /**
+     * Retrieves the semantic modifiers associated with a given component.
+     *
+     * @param component The component for which to retrieve semantic modifiers.
+     * @return A list of semantic modifiers applicable to the component.
+     */
+    List<AccessibilitySemantics> semanticModifiersForComponent(Component component);
+
+    /**
+     * Gets all applied merge modes of the given component. A Merge mode is one of Set, Merge or
+     * Clear and describes how to apply and combine hierarchical semantics.
+     *
+     * @param component The component to merge the mode for.
+     * @return The effective merge modes, potentially conflicting but resolved to a single value.
+     */
+    CoreSemantics.Mode mergeMode(Component component);
+
+    /**
+     * Finds a component by its ID.
+     *
+     * @param id the ID of the component to find
+     * @return the component with the given ID, or {@code null} if no such component exists
+     */
+    @Nullable
+    Component findComponentById(int id);
+
+    @Nullable
+    Integer getComponentIdAt(PointF point);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeTouchHelper.java b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeTouchHelper.java
new file mode 100644
index 0000000..1364102
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeTouchHelper.java
@@ -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.internal.widget.remotecompose.accessibility;
+
+/**
+ * This class is the entry point for finding the AccessibilityDelegate for a RemoteCompose document.
+ */
+public class RemoteComposeTouchHelper {
+    /** Get the platform specific accessibility delegate registrar */
+    public static final RemoteComposeAccessibilityRegistrar REGISTRAR =
+            new PlatformRemoteComposeAccessibilityRegistrar();
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/SemanticNodeApplier.java b/core/java/com/android/internal/widget/remotecompose/accessibility/SemanticNodeApplier.java
new file mode 100644
index 0000000..832b542
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/SemanticNodeApplier.java
@@ -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.internal.widget.remotecompose.accessibility;
+
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics;
+
+import java.util.List;
+
+/**
+ * An interface for applying semantic information to a semantics node.
+ *
+ * <p>Implementations of this interface are responsible for taking a node represented by [nodeInfo]
+ * and applying a list of [semantics] (representing accessible actions and properties) to it. This
+ * process might involve: - Modifying the node's properties (e.g., content description, clickable
+ * state). - Adding a child node to represent a specific semantic element. - Performing any other
+ * action necessary to make the node semantically meaningful and accessible to assistive
+ * technologies.
+ *
+ * @param <N> The type representing information about the node. This could be an Androidx
+ *     `AccessibilityNodeInfoCompat`, or potentially a platform `AccessibilityNodeInfo`.
+ */
+public interface SemanticNodeApplier<N> {
+    void applyComponent(
+            RemoteComposeDocumentAccessibility remoteComposeAccessibility,
+            N nodeInfo,
+            Component component,
+            List<AccessibilitySemantics> semantics);
+
+    String VIRTUAL_VIEW_ID_KEY = "VirtualViewId";
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
index 370289a..da880e1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -18,17 +18,19 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.remotecompose.core.operations.ComponentValue;
 import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
 import com.android.internal.widget.remotecompose.core.operations.IntegerExpression;
 import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
 import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
+import com.android.internal.widget.remotecompose.core.operations.ShaderData;
+import com.android.internal.widget.remotecompose.core.operations.TextData;
 import com.android.internal.widget.remotecompose.core.operations.Theme;
 import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation;
-import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
 import com.android.internal.widget.remotecompose.core.operations.layout.LoopEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.OperationsListEnd;
@@ -38,12 +40,14 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.TouchUpModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -53,13 +57,14 @@
 public class CoreDocument {
 
     private static final boolean DEBUG = false;
+    private static final int DOCUMENT_API_LEVEL = 2;
 
     @NonNull ArrayList<Operation> mOperations = new ArrayList<>();
 
     @Nullable RootLayoutComponent mRootLayoutComponent = null;
 
     @NonNull RemoteComposeState mRemoteComposeState = new RemoteComposeState();
-    @NonNull TimeVariables mTimeVariables = new TimeVariables();
+    @VisibleForTesting @NonNull public TimeVariables mTimeVariables = new TimeVariables();
     // Semantic version of the document
     @NonNull Version mVersion = new Version(0, 1, 0);
 
@@ -86,6 +91,11 @@
 
     private int mLastId = 1; // last component id when inflating the file
 
+    /** Returns a version number that is monotonically increasing. */
+    public static int getDocumentApiLevel() {
+        return DOCUMENT_API_LEVEL;
+    }
+
     @Nullable
     public String getContentDescription() {
         return mContentDescription;
@@ -434,11 +444,11 @@
         mActionListeners.clear();
     }
 
-    public interface ClickCallbacks {
-        void click(int id, @Nullable String metadata);
+    public interface IdActionCallback {
+        void onAction(int id, @Nullable String metadata);
     }
 
-    @NonNull HashSet<ClickCallbacks> mClickListeners = new HashSet<>();
+    @NonNull HashSet<IdActionCallback> mIdActionListeners = new HashSet<>();
     @NonNull HashSet<TouchListener> mTouchListeners = new HashSet<>();
     @NonNull HashSet<ClickAreaRepresentation> mClickAreas = new HashSet<>();
 
@@ -463,6 +473,21 @@
         float mBottom;
         @Nullable final String mMetadata;
 
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof ClickAreaRepresentation)) return false;
+            ClickAreaRepresentation that = (ClickAreaRepresentation) o;
+            return mId == that.mId
+                    && Objects.equals(mContentDescription, that.mContentDescription)
+                    && Objects.equals(mMetadata, that.mMetadata);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mId, mContentDescription, mMetadata);
+        }
+
         public ClickAreaRepresentation(
                 int id,
                 @Nullable String contentDescription,
@@ -565,6 +590,7 @@
         TouchUpModifierOperation currentTouchUpModifier = null;
         TouchCancelModifierOperation currentTouchCancelModifier = null;
         LoopOperation currentLoop = null;
+        ScrollModifierOperation currentScrollModifier = null;
 
         mLastId = -1;
         for (Operation o : operations) {
@@ -579,8 +605,8 @@
                     mLastId = component.getComponentId();
                 }
             } else if (o instanceof ComponentEnd) {
-                if (currentComponent instanceof LayoutComponent) {
-                    ((LayoutComponent) currentComponent).inflate();
+                if (currentComponent != null) {
+                    currentComponent.inflate();
                 }
                 components.remove(components.size() - 1);
                 if (!components.isEmpty()) {
@@ -602,6 +628,9 @@
             } else if (o instanceof TouchCancelModifierOperation) {
                 currentTouchCancelModifier = (TouchCancelModifierOperation) o;
                 ops = currentTouchCancelModifier.getList();
+            } else if (o instanceof ScrollModifierOperation) {
+                currentScrollModifier = (ScrollModifierOperation) o;
+                ops = currentScrollModifier.getList();
             } else if (o instanceof OperationsListEnd) {
                 ops = currentComponent.getList();
                 if (currentClickModifier != null) {
@@ -616,6 +645,9 @@
                 } else if (currentTouchCancelModifier != null) {
                     ops.add(currentTouchCancelModifier);
                     currentTouchCancelModifier = null;
+                } else if (currentScrollModifier != null) {
+                    ops.add(currentScrollModifier);
+                    currentScrollModifier = null;
                 }
             } else if (o instanceof LoopOperation) {
                 currentLoop = (LoopOperation) o;
@@ -740,9 +772,13 @@
             float right,
             float bottom,
             @Nullable String metadata) {
-        mClickAreas.add(
+
+        ClickAreaRepresentation car =
                 new ClickAreaRepresentation(
-                        id, contentDescription, left, top, right, bottom, metadata));
+                        id, contentDescription, left, top, right, bottom, metadata);
+
+        boolean old = mClickAreas.remove(car);
+        mClickAreas.add(car);
     }
 
     /**
@@ -755,12 +791,12 @@
     }
 
     /**
-     * Add a click listener. This will get called when a click is detected on the document
+     * Add an id action listener. This will get called when e.g. a click is detected on the document
      *
-     * @param callback called when a click area has been hit, passing the click are id and metadata.
+     * @param callback called when an action is executed, passing the id and metadata.
      */
-    public void addClickListener(@NonNull ClickCallbacks callback) {
-        mClickListeners.add(callback);
+    public void addIdActionListener(@NonNull IdActionCallback callback) {
+        mIdActionListeners.add(callback);
     }
 
     /**
@@ -769,8 +805,8 @@
      * @return set of click listeners
      */
     @NonNull
-    public HashSet<CoreDocument.ClickCallbacks> getClickListeners() {
-        return mClickListeners;
+    public HashSet<IdActionCallback> getIdActionListeners() {
+        return mIdActionListeners;
     }
 
     /**
@@ -799,15 +835,15 @@
                 warnClickListeners(clickArea);
             }
         }
-        for (ClickCallbacks listener : mClickListeners) {
-            listener.click(id, "");
+        for (IdActionCallback listener : mIdActionListeners) {
+            listener.onAction(id, "");
         }
     }
 
     /** Warn click listeners when a click area is activated */
     private void warnClickListeners(@NonNull ClickAreaRepresentation clickArea) {
-        for (ClickCallbacks listener : mClickListeners) {
-            listener.click(clickArea.mId, clickArea.mMetadata);
+        for (IdActionCallback listener : mIdActionListeners) {
+            listener.onAction(clickArea.mId, clickArea.mMetadata);
         }
     }
 
@@ -881,7 +917,7 @@
         }
         if (mRootLayoutComponent != null) {
             for (Component component : mAppliedTouchOperations) {
-                component.onTouchUp(context, this, x, y, true);
+                component.onTouchUp(context, this, x, y, dx, dy, true);
             }
             mAppliedTouchOperations.clear();
         }
@@ -1039,7 +1075,13 @@
                                 || context.getTheme() == Theme.UNSPECIFIED;
             }
             if (apply) {
-                op.apply(context);
+                if (op.isDirty() || op instanceof PaintOperation) {
+                    if (op.isDirty() && op instanceof VariableSupport) {
+                        op.markNotDirty();
+                        ((VariableSupport) op).updateVariables(context);
+                    }
+                    op.apply(context);
+                }
             }
         }
         if (context.getPaintContext().doesNeedsRepaint()
@@ -1047,7 +1089,6 @@
             mRepaintNext = 1;
         }
         context.mMode = RemoteContext.ContextMode.UNSET;
-        // System.out.println(">>   " + (  System.nanoTime() - time)*1E-6f+" ms");
         if (DEBUG && mRootLayoutComponent != null) {
             System.out.println(mRootLayoutComponent.displayHierarchy());
         }
@@ -1143,4 +1184,30 @@
     public List<Operation> getOperations() {
         return mOperations;
     }
+
+    /** defines if a shader can be run */
+    public interface ShaderControl {
+        boolean isShaderValid(String shader);
+    }
+
+    /**
+     * validate the shaders
+     *
+     * @param context the remote context
+     * @param ctl the call back to allow evaluation of shaders
+     */
+    public void checkShaders(RemoteContext context, ShaderControl ctl) {
+        int count = 0;
+        for (Operation op : mOperations) {
+            if (op instanceof TextData) {
+                op.apply(context);
+            }
+            if (op instanceof ShaderData) {
+                ShaderData sd = (ShaderData) op;
+                int id = sd.getShaderTextId();
+                String str = context.getText(id);
+                sd.enable(ctl.isShaderValid(str));
+            }
+        }
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operation.java b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
index 102003e..150ebd0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
@@ -18,19 +18,47 @@
 import android.annotation.NonNull;
 
 /** Base interface for RemoteCompose operations */
-public interface Operation {
+public abstract class Operation {
+
+    private static final boolean ENABLE_DIRTY_FLAG_OPTIMIZATION = true;
 
     /** add the operation to the buffer */
-    void write(@NonNull WireBuffer buffer);
+    public abstract void write(@NonNull WireBuffer buffer);
 
     /**
      * paint an operation
      *
      * @param context the paint context used to paint the operation
      */
-    void apply(@NonNull RemoteContext context);
+    public abstract void apply(@NonNull RemoteContext context);
 
     /** Debug utility to display an operation + indentation */
     @NonNull
-    String deepToString(@NonNull String indent);
+    public abstract String deepToString(@NonNull String indent);
+
+    private boolean mDirty = true;
+
+    /** Mark the operation as "dirty" to indicate it will need to be re-executed. */
+    public void markDirty() {
+        mDirty = true;
+    }
+
+    /** Mark the operation as "not dirty" */
+    public void markNotDirty() {
+        if (ENABLE_DIRTY_FLAG_OPTIMIZATION) {
+            mDirty = false;
+        }
+    }
+
+    /**
+     * Returns true if the operation is marked as "dirty"
+     *
+     * @return true if dirty
+     */
+    public boolean isDirty() {
+        if (ENABLE_DIRTY_FLAG_OPTIMIZATION) {
+            return mDirty;
+        }
+        return true;
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/OperationInterface.java b/core/java/com/android/internal/widget/remotecompose/core/OperationInterface.java
new file mode 100644
index 0000000..06beffc
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/OperationInterface.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core;
+
+import android.annotation.NonNull;
+
+/** Base interface for RemoteCompose operations */
+public interface OperationInterface {
+
+    /** add the operation to the buffer */
+    void write(@NonNull WireBuffer buffer);
+
+    /**
+     * paint an operation
+     *
+     * @param context the paint context used to paint the operation
+     */
+    void apply(@NonNull RemoteContext context);
+
+    /** Debug utility to display an operation + indentation */
+    @NonNull
+    String deepToString(@NonNull String indent);
+
+    /**
+     * Returns true if the operation is marked as "dirty"
+     *
+     * @return true if dirty
+     */
+    boolean isDirty();
+
+    /** Mark the operation as "dirty" to indicate it will need to be re-executed. */
+    void markNotDirty();
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
index 687a99b..04e490f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -55,7 +55,10 @@
 import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate;
 import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
 import com.android.internal.widget.remotecompose.core.operations.PaintData;
+import com.android.internal.widget.remotecompose.core.operations.PathAppend;
+import com.android.internal.widget.remotecompose.core.operations.PathCreate;
 import com.android.internal.widget.remotecompose.core.operations.PathData;
+import com.android.internal.widget.remotecompose.core.operations.PathTween;
 import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
 import com.android.internal.widget.remotecompose.core.operations.RootContentDescription;
 import com.android.internal.widget.remotecompose.core.operations.ShaderData;
@@ -95,8 +98,10 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostNamedActionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.MarqueeModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.OffsetModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RippleModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueFloatChangeActionOperation;
@@ -107,6 +112,7 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
 import com.android.internal.widget.remotecompose.core.types.BooleanConstant;
 import com.android.internal.widget.remotecompose.core.types.IntegerConstant;
 import com.android.internal.widget.remotecompose.core.types.LongConstant;
@@ -123,6 +129,9 @@
     public static final int CLICK_AREA = 64;
     public static final int ROOT_CONTENT_BEHAVIOR = 65;
     public static final int ROOT_CONTENT_DESCRIPTION = 103;
+    // TODO reorder before submitting
+    public static final int ACCESSIBILITY_SEMANTICS = 250;
+    //    public static final int ACCESSIBILITY_CUSTOM_ACTION = 251;
 
     ////////////////////////////////////////
     // Draw commands
@@ -178,6 +187,9 @@
     public static final int TEXT_MEASURE = 155;
     public static final int TEXT_LENGTH = 156;
     public static final int TOUCH_EXPRESSION = 157;
+    public static final int PATH_TWEEN = 158;
+    public static final int PATH_CREATE = 159;
+    public static final int PATH_ADD = 160;
 
     ///////////////////////////////////////// ======================
 
@@ -217,6 +229,8 @@
     public static final int MODIFIER_ZINDEX = 223;
     public static final int MODIFIER_GRAPHICS_LAYER = 224;
     public static final int MODIFIER_SCROLL = 226;
+    public static final int MODIFIER_MARQUEE = 228;
+    public static final int MODIFIER_RIPPLE = 229;
 
     public static final int LOOP_START = 215;
     public static final int LOOP_END = 216;
@@ -321,6 +335,8 @@
         map.put(MODIFIER_ZINDEX, ZIndexModifierOperation::read);
         map.put(MODIFIER_GRAPHICS_LAYER, GraphicsLayerModifierOperation::read);
         map.put(MODIFIER_SCROLL, ScrollModifierOperation::read);
+        map.put(MODIFIER_MARQUEE, MarqueeModifierOperation::read);
+        map.put(MODIFIER_RIPPLE, RippleModifierOperation::read);
 
         map.put(OPERATIONS_LIST_END, OperationsListEnd::read);
 
@@ -353,5 +369,11 @@
         map.put(TEXT_MEASURE, TextMeasure::read);
         map.put(TEXT_LENGTH, TextLength::read);
         map.put(TOUCH_EXPRESSION, TouchExpression::read);
+        map.put(PATH_TWEEN, PathTween::read);
+        map.put(PATH_CREATE, PathCreate::read);
+        map.put(PATH_ADD, PathAppend::read);
+
+        map.put(ACCESSIBILITY_SEMANTICS, CoreSemantics::read);
+        //        map.put(ACCESSIBILITY_CUSTOM_ACTION, CoreSemantics::read);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
index 38b08e9..7ecd118 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
@@ -157,6 +157,8 @@
     public abstract void drawTweenPath(
             int path1Id, int path2Id, float tween, float start, float stop);
 
+    public abstract void tweenPath(int out, int path1, int path2, float tween);
+
     /**
      * This applies changes to the current paint
      *
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
index 9999182..cfdd522 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
@@ -21,7 +21,7 @@
  * PaintOperation interface, used for operations aimed at painting (while any operation _can_ paint,
  * this make it a little more explicit)
  */
-public abstract class PaintOperation implements Operation {
+public abstract class PaintOperation extends Operation {
 
     @Override
     public void apply(@NonNull RemoteContext context) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index c05079e..0ae7a94 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -15,7 +15,6 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
-import static com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression.ADD;
 import static com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression.MUL;
 
 import android.annotation.NonNull;
@@ -59,7 +58,10 @@
 import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate;
 import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
 import com.android.internal.widget.remotecompose.core.operations.PaintData;
+import com.android.internal.widget.remotecompose.core.operations.PathAppend;
+import com.android.internal.widget.remotecompose.core.operations.PathCreate;
 import com.android.internal.widget.remotecompose.core.operations.PathData;
+import com.android.internal.widget.remotecompose.core.operations.PathTween;
 import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
 import com.android.internal.widget.remotecompose.core.operations.RootContentDescription;
 import com.android.internal.widget.remotecompose.core.operations.TextData;
@@ -78,6 +80,7 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponentContent;
 import com.android.internal.widget.remotecompose.core.operations.layout.LoopEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.OperationsListEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout;
@@ -89,8 +92,10 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BorderModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.MarqueeModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.OffsetModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RippleModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
@@ -644,6 +649,37 @@
     }
 
     /**
+     * interpolate the two paths to produce a 3rd
+     *
+     * @param pid1 the first path
+     * @param pid2 the second path
+     * @param tween path is the path1+(pat2-path1)*tween
+     * @return id of the tweened path
+     */
+    public int pathTween(int pid1, int pid2, float tween) {
+        int out = mRemoteComposeState.nextId();
+        PathTween.apply(mBuffer, out, pid1, pid2, tween);
+        return out;
+    }
+
+    /**
+     * Create a path with an initial moveTo
+     *
+     * @param x x coordinate of the moveto
+     * @param y y coordinate of the moveto
+     * @return id of the created path
+     */
+    public int pathCreate(float x, float y) {
+        int out = mRemoteComposeState.nextId();
+        PathCreate.apply(mBuffer, out, x, y);
+        return out;
+    }
+
+    public void pathAppend(int id, float... path) {
+        PathAppend.apply(mBuffer, id, path);
+    }
+
+    /**
      * Draw the specified path
      *
      * @param pathId
@@ -1154,6 +1190,16 @@
     }
 
     /**
+     * Reserve a float and returns a NaN number pointing to that float
+     *
+     * @return the nan id of float
+     */
+    public float reserveFloatVariable() {
+        int id = mRemoteComposeState.cacheFloat(0f);
+        return Utils.asNan(id);
+    }
+
+    /**
      * Add a Integer return an id number pointing to that float.
      *
      * @param value adds an integer and assigns it an id
@@ -1566,7 +1612,7 @@
      * create and animation based on description and return as an array of floats. see
      * addAnimatedFloat
      *
-     * @param duration the duration of the aimation
+     * @param duration the duration of the animation in seconds
      * @param type the type of animation
      * @param spec the parameters of the animation if any
      * @param initialValue the initial value if it animates to a start
@@ -1651,10 +1697,13 @@
      */
     public void addModifierScroll(int direction, float positionId, int notches) {
         // TODO: add support for non-notch behaviors etc.
-        float max = this.addFloat(0f);
-        float notchMax = this.addFloat(0f);
+        float max = this.reserveFloatVariable();
+        float notchMax = this.reserveFloatVariable();
         float touchExpressionDirection =
                 direction != 0 ? RemoteContext.FLOAT_TOUCH_POS_X : RemoteContext.FLOAT_TOUCH_POS_Y;
+
+        ScrollModifierOperation.apply(mBuffer, direction, positionId, max, notchMax);
+
         this.addTouchExpression(
                 positionId,
                 0f,
@@ -1663,20 +1712,13 @@
                 0f,
                 3,
                 new float[] {
-                    touchExpressionDirection,
-                    -1,
-                    // TODO: remove this CONTINUOUS_SEC hack...
-                    MUL,
-                    RemoteContext.FLOAT_CONTINUOUS_SEC,
-                    0f,
-                    MUL,
-                    ADD
+                    touchExpressionDirection, -1, MUL,
                 },
                 TouchExpression.STOP_NOTCHES_EVEN,
                 new float[] {notches, notchMax},
                 null);
 
-        ScrollModifierOperation.apply(mBuffer, direction, positionId, max, notchMax);
+        OperationsListEnd.apply(mBuffer);
     }
 
     /**
@@ -1742,6 +1784,38 @@
         ZIndexModifierOperation.apply(mBuffer, value);
     }
 
+    /** Add a ripple effect on touch down as a modifier */
+    public void addModifierRipple() {
+        RippleModifierOperation.apply(mBuffer);
+    }
+
+    /**
+     * Add a marquee modifier
+     *
+     * @param iterations
+     * @param animationMode
+     * @param repeatDelayMillis
+     * @param initialDelayMillis
+     * @param spacing
+     * @param velocity
+     */
+    public void addModifierMarquee(
+            int iterations,
+            int animationMode,
+            float repeatDelayMillis,
+            float initialDelayMillis,
+            float spacing,
+            float velocity) {
+        MarqueeModifierOperation.apply(
+                mBuffer,
+                iterations,
+                animationMode,
+                repeatDelayMillis,
+                initialDelayMillis,
+                spacing,
+                velocity);
+    }
+
     /**
      * Add a graphics layer
      *
@@ -1807,8 +1881,8 @@
         ClipRectModifierOperation.apply(mBuffer);
     }
 
-    public void addLoopStart(float count, float from, float step, int indexId) {
-        LoopOperation.apply(mBuffer, count, from, step, indexId);
+    public void addLoopStart(int indexId, float from, float step, float until) {
+        LoopOperation.apply(mBuffer, indexId, from, step, until);
     }
 
     public void addLoopEnd() {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeOperation.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeOperation.java
index e60695f..97487e6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeOperation.java
@@ -15,4 +15,4 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
-public interface RemoteComposeOperation extends Operation {}
+public interface RemoteComposeOperation {}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
index a903e6e..11e58ba 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -48,6 +48,10 @@
     private final IntMap<DataMap> mDataMapMap = new IntMap<>();
     private final IntMap<Object> mObjectMap = new IntMap<>();
 
+    // path information
+    private final IntMap<Object> mPathMap = new IntMap<>();
+    private final IntMap<float[]> mPathData = new IntMap<>();
+
     private final boolean[] mColorOverride = new boolean[MAX_COLORS];
     @NonNull private final IntMap<ArrayAccess> mCollectionMap = new IntMap<>();
 
@@ -132,6 +136,48 @@
     }
 
     /**
+     * Get the path asociated with the Data
+     *
+     * @param id
+     * @return
+     */
+    public Object getPath(int id) {
+        return mPathMap.get(id);
+    }
+
+    /**
+     * Cache a path object. Object will be cleared if you update path data.
+     *
+     * @param id number asociated with path
+     * @param path the path object typically Android Path
+     */
+    public void putPath(int id, Object path) {
+        mPathMap.put(id, path);
+    }
+
+    /**
+     * The path data the Array of floats that is asoicated with the path It also removes the current
+     * path object.
+     *
+     * @param id the integer asociated with the data and path
+     * @param data the array of floats that represents the path
+     */
+    public void putPathData(int id, float[] data) {
+        mPathData.put(id, data);
+        mPathMap.remove(id);
+    }
+
+    /**
+     * Get the path data asociated with the id
+     *
+     * @param id number that represents the path
+     * @return path data
+     */
+    public float[] getPathData(int id) {
+        return mPathData.get(id);
+    }
+
+    /**
      * Adds a data Override.
      *
      * @param id
@@ -273,7 +319,7 @@
         ArrayList<VariableSupport> v = mVarListeners.get(id);
         if (v != null && mRemoteContext != null) {
             for (VariableSupport c : v) {
-                c.updateVariables(mRemoteContext);
+                c.markDirty();
             }
         }
     }
@@ -416,9 +462,6 @@
      * @return
      */
     public int getOpsToUpdate(@NonNull RemoteContext context) {
-        for (VariableSupport vs : mAllVarListeners) {
-            vs.updateVariables(context);
-        }
         if (mVarListeners.get(RemoteContext.ID_CONTINUOUS_SEC) != null) {
             return 1;
         }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
index 26305bf..c03f44b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -93,12 +93,20 @@
     /**
      * Load a path under an id. Paths can be use in clip drawPath and drawTweenPath
      *
-     * @param instanceId
-     * @param floatPath
+     * @param instanceId the id to save this path under
+     * @param floatPath the path as a float array
      */
     public abstract void loadPathData(int instanceId, @NonNull float[] floatPath);
 
     /**
+     * Load a path under an id. Paths can be use in clip drawPath and drawTweenPath
+     *
+     * @param instanceId
+     * @return the a
+     */
+    public abstract @Nullable float[] getPathData(int instanceId);
+
+    /**
      * Associate a name with a give id.
      *
      * @param varName the name
@@ -206,6 +214,13 @@
      */
     public abstract void hapticEffect(int type);
 
+    /** Set the repaint flag. This will trigger a repaint of the current document. */
+    public void needsRepaint() {
+        if (mPaintContext != null) {
+            mPaintContext.needsRepaint();
+        }
+    }
+
     /**
      * The context can be used in a few different mode, allowing operations to skip being executed:
      * - UNSET : all operations will get executed - DATA : only operations dealing with DATA (eg
@@ -478,6 +493,9 @@
 
     public static final int ID_DENSITY = 27;
 
+    /** Defines when the last build was made */
+    public static final int ID_API_LEVEL = 28;
+
     public static final float FLOAT_DENSITY = Utils.asNan(ID_DENSITY);
 
     /** CONTINUOUS_SEC is seconds from midnight looping every hour 0-3600 */
@@ -551,6 +569,9 @@
     /** Ambient light level in SI lux */
     public static final float FLOAT_LIGHT = Utils.asNan(ID_LIGHT);
 
+    /** When was this player built */
+    public static final float FLOAT_API_LEVEL = Utils.asNan(ID_API_LEVEL);
+
     ///////////////////////////////////////////////////////////////////////////////////////////////
     // Click handling
     ///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
index 14aed2f..cd7ebec 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
@@ -24,14 +24,14 @@
 
 /** This generates the standard system variables for time. */
 public class TimeVariables {
+    private static final float BUILD = 0.01f;
+
     /**
      * This class populates all time variables in the system
      *
      * @param context
      */
-    public void updateTime(@NonNull RemoteContext context) {
-        LocalDateTime dateTime =
-                LocalDateTime.now(ZoneId.systemDefault()); // TODO, pass in a timezone explicitly?
+    public void updateTime(@NonNull RemoteContext context, ZoneId zoneId, LocalDateTime dateTime) {
         // This define the time in the format
         // seconds run from Midnight=0 quantized to seconds hour 0..3599
         // minutes run from Midnight=0 quantized to minutes 0..1439
@@ -48,8 +48,7 @@
         float sec = currentSeconds + dateTime.getNano() * 1E-9f;
         int day_week = dateTime.getDayOfWeek().getValue();
 
-        ZoneId zone = ZoneId.systemDefault();
-        OffsetDateTime offsetDateTime = dateTime.atZone(zone).toOffsetDateTime();
+        OffsetDateTime offsetDateTime = dateTime.atZone(zoneId).toOffsetDateTime();
         ZoneOffset offset = offsetDateTime.getOffset();
 
         context.loadFloat(RemoteContext.ID_OFFSET_TO_UTC, offset.getTotalSeconds());
@@ -60,5 +59,18 @@
         context.loadFloat(RemoteContext.ID_CALENDAR_MONTH, month);
         context.loadFloat(RemoteContext.ID_DAY_OF_MONTH, month);
         context.loadFloat(RemoteContext.ID_WEEK_DAY, day_week);
+        context.loadFloat(RemoteContext.ID_API_LEVEL, CoreDocument.getDocumentApiLevel() + BUILD);
+    }
+
+    /**
+     * This class populates all time variables in the system
+     *
+     * @param context
+     */
+    public void updateTime(@NonNull RemoteContext context) {
+        ZoneId zone = ZoneId.systemDefault();
+        LocalDateTime dateTime = LocalDateTime.now(zone);
+
+        updateTime(context, zone, dateTime);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java b/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java
index 3dda678..611ba97 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java
@@ -15,10 +15,34 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+/** Interface used by objects to register for touch events */
 public interface TouchListener {
+    /**
+     * Called when touch down happens
+     *
+     * @param context The players context
+     * @param x the x location of the down touch
+     * @param y the y location of the down touch
+     */
     void touchDown(RemoteContext context, float x, float y);
 
+    /**
+     * called on touch up
+     *
+     * @param context the players context
+     * @param x the x location
+     * @param y the y location
+     * @param dx the x velocity when the touch up happened
+     * @param dy the y valocity when the touch up happened
+     */
     void touchUp(RemoteContext context, float x, float y, float dx, float dy);
 
+    /**
+     * Drag event (occur between down and up)
+     *
+     * @param context the players context
+     * @param x the x coord of the drag
+     * @param y the y coord of the drag
+     */
     void touchDrag(RemoteContext context, float x, float y);
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java b/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java
index e9fa897..1f3e290 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java
@@ -36,4 +36,7 @@
      * @param context
      */
     void updateVariables(@NonNull RemoteContext context);
+
+    /** Mark the operation as dirty to indicate that the variables it references are out of date. */
+    void markDirty();
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
index a64b706..2f1502c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -28,11 +28,17 @@
     int mStartingIndex = 0;
     int mSize = 0;
 
+    /**
+     * Create a wire buffer
+     *
+     * @param size the initial size of the buffer
+     */
     public WireBuffer(int size) {
         mMaxSize = size;
         mBuffer = new byte[mMaxSize];
     }
 
+    /** Create a wire buffer of default size */
     public WireBuffer() {
         this(BUFFER_SIZE);
     }
@@ -44,37 +50,74 @@
         }
     }
 
+    /**
+     * get the wire buffer's underlying byte array. Note the array will be bigger that the used
+     * portion
+     *
+     * @return byte array of the wire buffer
+     */
     public @NonNull byte[] getBuffer() {
         return mBuffer;
     }
 
+    /**
+     * The current mix size of the buffer
+     *
+     * @return max size
+     */
     public int getMax_size() {
         return mMaxSize;
     }
 
+    /**
+     * The current point in the buffer which will be written to
+     *
+     * @return index pointing into the buffer
+     */
     public int getIndex() {
         return mIndex;
     }
 
+    /**
+     * The size of the buffer
+     *
+     * @return the size of the buffer
+     */
     public int getSize() {
         return mSize;
     }
 
+    /**
+     * Reposition the pointer
+     *
+     * @param index the new position of the index
+     */
     public void setIndex(int index) {
         this.mIndex = index;
     }
 
+    /**
+     * Write a byte representing the command into the buffer
+     *
+     * @param type the command id
+     */
     public void start(int type) {
         mStartingIndex = mIndex;
         writeByte(type);
     }
 
+    /**
+     * Unused Todo remove?
+     *
+     * @param type the type of object to write
+     */
     public void startWithSize(int type) {
         mStartingIndex = mIndex;
         writeByte(type);
         mIndex += 4; // skip ahead for the future size
     }
 
+    /** Unused Todo remove? */
     public void endWithSize() {
         int size = mIndex - mStartingIndex;
         int currentIndex = mIndex;
@@ -97,10 +140,20 @@
         }
     }
 
+    /**
+     * return the size of the buffer todo rename to getSize
+     *
+     * @return the size of the buffer
+     */
     public int size() {
         return mSize;
     }
 
+    /**
+     * Bytes available
+     *
+     * @return the size - index
+     */
     public boolean available() {
         return mSize - mIndex > 0;
     }
@@ -109,28 +162,53 @@
     // Read values
     ///////////////////////////////////////////////////////////////////////////
 
+    /**
+     * read the operation type (reads a single byte)
+     *
+     * @return the byte cast to an integer
+     */
     public int readOperationType() {
         return readByte();
     }
 
+    /**
+     * Read a boolean (stored as a byte 1 = true)
+     *
+     * @return boolean of the byte
+     */
     public boolean readBoolean() {
         byte value = mBuffer[mIndex];
         mIndex++;
         return (value == 1);
     }
 
+    /**
+     * read a single byte byte
+     *
+     * @return byte from 0..255 as an Integer
+     */
     public int readByte() {
         int value = 0xFF & mBuffer[mIndex];
         mIndex++;
         return value;
     }
 
+    /**
+     * read a short [byte n] << 8 | [byte n+1]; index increast by 2
+     *
+     * @return return a short cast as an integer
+     */
     public int readShort() {
         int v1 = (mBuffer[mIndex++] & 0xFF) << 8;
         int v2 = (mBuffer[mIndex++] & 0xFF) << 0;
         return v1 + v2;
     }
 
+    /**
+     * Read an integer without incrementing the index
+     *
+     * @return the integer
+     */
     public int peekInt() {
         int tmp = mIndex;
         int v1 = (mBuffer[tmp++] & 0xFF) << 24;
@@ -140,6 +218,11 @@
         return v1 + v2 + v3 + v4;
     }
 
+    /**
+     * Read an integer. index increased by 4
+     *
+     * @return integer
+     */
     public int readInt() {
         int v1 = (mBuffer[mIndex++] & 0xFF) << 24;
         int v2 = (mBuffer[mIndex++] & 0xFF) << 16;
@@ -148,6 +231,11 @@
         return v1 + v2 + v3 + v4;
     }
 
+    /**
+     * Read a long index is increased by 8
+     *
+     * @return long
+     */
     public long readLong() {
         long v1 = (mBuffer[mIndex++] & 0xFFL) << 56;
         long v2 = (mBuffer[mIndex++] & 0xFFL) << 48;
@@ -160,14 +248,30 @@
         return v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8;
     }
 
+    /**
+     * Read a 32 bit float IEEE standard index is increased by 4
+     *
+     * @return the float
+     */
     public float readFloat() {
         return java.lang.Float.intBitsToFloat(readInt());
     }
 
+    /**
+     * Read a 64 bit double index is increased by 8
+     *
+     * @return double
+     */
     public double readDouble() {
         return java.lang.Double.longBitsToDouble(readLong());
     }
 
+    /**
+     * Read a byte buffer bytes are encoded as 4 byte length followed by length bytes index is
+     * increased by 4 + number of bytes
+     *
+     * @return byte array
+     */
     public @NonNull byte[] readBuffer() {
         int count = readInt();
         byte[] b = Arrays.copyOfRange(mBuffer, mIndex, mIndex + count);
@@ -175,6 +279,13 @@
         return b;
     }
 
+    /**
+     * Read a byte buffer limited to max size. bytes are encoded as 4 byte length followed by length
+     * bytes index is increased by 4 + number of bytes Throw an exception if the read excedes the
+     * max size. This is the preferred form of read buffer.
+     *
+     * @return byte array
+     */
     public @NonNull byte[] readBuffer(int maxSize) {
         int count = readInt();
         if (count < 0 || count > maxSize) {
@@ -186,12 +297,23 @@
         return b;
     }
 
+    /**
+     * Read a string encoded in UTF8 The buffer is red with readBuffer and converted to a String
+     *
+     * @return unicode string
+     */
     @NonNull
     public String readUTF8() {
         byte[] stringBuffer = readBuffer();
         return new String(stringBuffer);
     }
 
+    /**
+     * Read a string encoded in UTF8 The buffer is red with readBuffer and converted to a String
+     * This is the preferred readUTF8 because it catches errors
+     *
+     * @return unicode string
+     */
     @NonNull
     public String readUTF8(int maxSize) {
         byte[] stringBuffer = readBuffer(maxSize);
@@ -202,18 +324,33 @@
     // Write values
     ///////////////////////////////////////////////////////////////////////////
 
+    /**
+     * Write a boolean value. (written as a byte 1=true)
+     *
+     * @param value value to write
+     */
     public void writeBoolean(boolean value) {
         resize(1);
         mBuffer[mIndex++] = (byte) (value ? 1 : 0);
         mSize++;
     }
 
+    /**
+     * Write a byte value
+     *
+     * @param value value to write
+     */
     public void writeByte(int value) {
         resize(1);
         mBuffer[mIndex++] = (byte) value;
         mSize++;
     }
 
+    /**
+     * Write a short value
+     *
+     * @param value value to write
+     */
     public void writeShort(int value) {
         int need = 2;
         resize(need);
@@ -222,6 +359,11 @@
         mSize += need;
     }
 
+    /**
+     * Write a int (4 byte) value
+     *
+     * @param value value to write
+     */
     public void writeInt(int value) {
         int need = 4;
         resize(need);
@@ -232,6 +374,11 @@
         mSize += need;
     }
 
+    /**
+     * Write a long (8 byte) value
+     *
+     * @param value value to write
+     */
     public void writeLong(long value) {
         int need = 8;
         resize(need);
@@ -246,14 +393,29 @@
         mSize += need;
     }
 
+    /**
+     * Write a 32 bit IEEE float value
+     *
+     * @param value value to write
+     */
     public void writeFloat(float value) {
         writeInt(Float.floatToRawIntBits(value));
     }
 
+    /**
+     * Write a 64 bit IEEE double value
+     *
+     * @param value value to write
+     */
     public void writeDouble(double value) {
         writeLong(Double.doubleToRawLongBits(value));
     }
 
+    /**
+     * Write a buffer The buffer length is first written followed by the bytes
+     *
+     * @param b array of bytes write
+     */
     public void writeBuffer(@NonNull byte[] b) {
         resize(b.length + 4);
         writeInt(b.length);
@@ -263,6 +425,11 @@
         mSize += b.length;
     }
 
+    /**
+     * Write a string is encoded as UTF8
+     *
+     * @param content the string to write
+     */
     public void writeUTF8(@NonNull String content) {
         byte[] buffer = content.getBytes();
         writeBuffer(buffer);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
index 9480076..784897b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
@@ -36,7 +36,7 @@
  * Operation to deal with bitmap data On getting an Image during a draw call the bitmap is
  * compressed and saved in playback the image is decompressed
  */
-public class BitmapData implements Operation, SerializableToString {
+public class BitmapData extends Operation implements SerializableToString {
     private static final int OP_CODE = Operations.DATA_BITMAP;
     private static final String CLASS_NAME = "BitmapData";
     int mImageId;
@@ -45,15 +45,39 @@
     short mType;
     short mEncoding;
     @NonNull final byte[] mBitmap;
+
+    /** The max size of width or height */
     public static final int MAX_IMAGE_DIMENSION = 8000;
+
+    /** The data is encoded in the file (default) */
     public static final short ENCODING_INLINE = 0;
+
+    /** The data is encoded in the url */
     public static final short ENCODING_URL = 1;
+
+    /** The data is encoded as a reference to file */
     public static final short ENCODING_FILE = 2;
+
+    /** The data is encoded as PNG_8888 (default) */
     public static final short TYPE_PNG_8888 = 0;
+
+    /** The data is encoded as PNG */
     public static final short TYPE_PNG = 1;
+
+    /** The data is encoded as RAW 8 bit */
     public static final short TYPE_RAW8 = 2;
+
+    /** The data is encoded as RAW 8888 bit */
     public static final short TYPE_RAW8888 = 3;
 
+    /**
+     * create a bitmap structure
+     *
+     * @param imageId the id to store the image
+     * @param width the width of the image
+     * @param height the height of the image
+     * @param bitmap the data
+     */
     public BitmapData(int imageId, int width, int height, @NonNull byte[] bitmap) {
         this.mImageId = imageId;
         this.mImageWidth = width;
@@ -61,10 +85,20 @@
         this.mBitmap = bitmap;
     }
 
+    /**
+     * The width of the image
+     *
+     * @return the width
+     */
     public int getWidth() {
         return mImageWidth;
     }
 
+    /**
+     * The height of the image
+     *
+     * @return the height
+     */
     public int getHeight() {
         return mImageHeight;
     }
@@ -80,15 +114,34 @@
         return "BITMAP DATA " + mImageId;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
 
+    /**
+     * Add the image to the document
+     *
+     * @param buffer document to write to
+     * @param imageId the id the image will be stored under
+     * @param width the width of the image
+     * @param height the height of the image
+     * @param bitmap the data used to store/encode the image
+     */
     public static void apply(
             @NonNull WireBuffer buffer,
             int imageId,
@@ -102,6 +155,17 @@
         buffer.writeBuffer(bitmap);
     }
 
+    /**
+     * Add the image to the document (using the ehanced encoding)
+     *
+     * @param buffer document to write to
+     * @param imageId the id the image will be stored under
+     * @param type the type of image
+     * @param width the width of the image
+     * @param encoding the encoding
+     * @param height the height of the image
+     * @param bitmap the data used to store/encode the image
+     */
     public static void apply(
             @NonNull WireBuffer buffer,
             int imageId,
@@ -119,6 +183,12 @@
         buffer.writeBuffer(bitmap);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int imageId = buffer.readInt();
         int width = buffer.readInt();
@@ -133,6 +203,11 @@
         operations.add(new BitmapData(imageId, width, height, bitmap));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Bitmap data")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
index 310b194..29124d0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
@@ -21,14 +21,17 @@
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
 
 import java.util.List;
 
 /** Add a click area to the document */
-public class ClickArea implements RemoteComposeOperation {
+public class ClickArea extends Operation
+        implements RemoteComposeOperation, AccessibleComponent, VariableSupport {
     private static final int OP_CODE = Operations.CLICK_AREA;
     private static final String CLASS_NAME = "ClickArea";
     int mId;
@@ -37,6 +40,10 @@
     float mTop;
     float mRight;
     float mBottom;
+    float mOutLeft;
+    float mOutTop;
+    float mOutRight;
+    float mOutBottom;
     int mMetadata;
 
     /**
@@ -61,11 +68,35 @@
             int metadata) {
         this.mId = id;
         this.mContentDescription = contentDescription;
-        this.mLeft = left;
-        this.mTop = top;
-        this.mRight = right;
-        this.mBottom = bottom;
-        this.mMetadata = metadata;
+        mOutLeft = mLeft = left;
+        mOutTop = mTop = top;
+        mOutRight = mRight = right;
+        mOutBottom = mBottom = bottom;
+        mMetadata = metadata;
+    }
+
+    @Override
+    public void registerListening(@NonNull RemoteContext context) {
+        if (Float.isNaN(mLeft)) {
+            context.listensTo(Utils.idFromNan(mLeft), this);
+        }
+        if (Float.isNaN(mTop)) {
+            context.listensTo(Utils.idFromNan(mTop), this);
+        }
+        if (Float.isNaN(mRight)) {
+            context.listensTo(Utils.idFromNan(mRight), this);
+        }
+        if (Float.isNaN(mBottom)) {
+            context.listensTo(Utils.idFromNan(mBottom), this);
+        }
+    }
+
+    @Override
+    public void updateVariables(@NonNull RemoteContext context) {
+        mOutLeft = Float.isNaN(mLeft) ? context.getFloat(Utils.idFromNan(mLeft)) : mLeft;
+        mOutTop = Float.isNaN(mTop) ? context.getFloat(Utils.idFromNan(mTop)) : mTop;
+        mRight = Float.isNaN(mRight) ? context.getFloat(Utils.idFromNan(mRight)) : mRight;
+        mOutBottom = Float.isNaN(mBottom) ? context.getFloat(Utils.idFromNan(mBottom)) : mBottom;
     }
 
     @Override
@@ -104,7 +135,8 @@
         if (context.getMode() != RemoteContext.ContextMode.DATA) {
             return;
         }
-        context.addClickArea(mId, mContentDescription, mLeft, mTop, mRight, mBottom, mMetadata);
+        context.addClickArea(
+                mId, mContentDescription, mOutLeft, mOutTop, mOutRight, mOutBottom, mMetadata);
     }
 
     @NonNull
@@ -113,15 +145,40 @@
         return indent + toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
 
+    @Override
+    public Integer getContentDescriptionId() {
+        return mContentDescription;
+    }
+
+    /**
+     * @param buffer
+     * @param id
+     * @param contentDescription
+     * @param left
+     * @param top
+     * @param right
+     * @param bottom
+     * @param metadata
+     */
     public static void apply(
             @NonNull WireBuffer buffer,
             int id,
@@ -141,6 +198,12 @@
         buffer.writeInt(metadata);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int contentDescription = buffer.readInt();
@@ -154,6 +217,11 @@
         operations.add(clickArea);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Define a region you can click on")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
index db93829..b55f25c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
@@ -69,6 +69,12 @@
         return "ClipPath " + mId + ";";
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int pack = buffer.readInt();
         int id = pack & 0xFFFFF;
@@ -77,11 +83,21 @@
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -91,6 +107,11 @@
         buffer.writeInt(id);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Intersect the current clip with the path")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
index df54fb1..5a495d5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
@@ -28,18 +28,34 @@
 
 /** Support clip with a rectangle */
 public class ClipRect extends DrawBase4 {
-    public static final int OP_CODE = Operations.CLIP_RECT;
-    public static final String CLASS_NAME = "ClipRect";
+    private static final int OP_CODE = Operations.CLIP_RECT;
+    private static final String CLASS_NAME = "ClipRect";
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = ClipRect::new;
         read(m, buffer, operations);
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -50,6 +66,11 @@
         apply(buffer, v1, v2, v3, v4);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Intersect the current clip with rectangle")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
index 34e93f5..6802015 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
@@ -29,12 +29,22 @@
 import java.util.List;
 
 /** Operation that defines a simple Color based on ID Mainly for colors in theming. */
-public class ColorConstant implements Operation {
+public class ColorConstant extends Operation {
     private static final int OP_CODE = Operations.COLOR_CONSTANT;
     private static final String CLASS_NAME = "ColorConstant";
+
+    /** the id of the color */
     public int mColorId;
+
+    /** the color value (AARRGGBB) */
     public int mColor;
 
+    /**
+     * Creat a color constant
+     *
+     * @param colorId id of color
+     * @param color AARRGGBB value
+     */
     public ColorConstant(int colorId, int color) {
         this.mColorId = colorId;
         this.mColor = color;
@@ -51,11 +61,21 @@
         return "ColorConstant[" + mColorId + "] = " + Utils.colorInt(mColor) + "";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -73,12 +93,23 @@
         buffer.writeInt(color);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int colorId = buffer.readInt();
         int color = buffer.readInt();
         operations.add(new ColorConstant(colorId, color));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Define a Color")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
index c947d11..b385ecd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
@@ -34,7 +34,7 @@
  * Operation to Colors Color modes mMode = 0 two colors and a tween mMode = 1 color1 is a colorID.
  * mMode = 2 color2 is a colorID. mMode = 3 color1 & color2 are ids mMode = 4 H S V mode
  */
-public class ColorExpression implements Operation, VariableSupport {
+public class ColorExpression extends Operation implements VariableSupport {
     private static final int OP_CODE = Operations.COLOR_EXPRESSIONS;
     private static final String CLASS_NAME = "ColorExpression";
     public int mId;
@@ -199,11 +199,21 @@
                 + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -228,6 +238,12 @@
         buffer.writeFloat(tween);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int mode = buffer.readInt();
@@ -238,6 +254,11 @@
         operations.add(new ColorExpression(id, mode, color1, color2, tween));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("A Color defined by an expression")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
index b0ccd187..3e85236 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
@@ -30,9 +30,9 @@
 
 import java.util.List;
 
-public class ComponentValue implements Operation, SerializableToString {
-    public static final int OP_CODE = Operations.COMPONENT_VALUE;
-    public static final String CLASS_NAME = "ComponentValue";
+public class ComponentValue extends Operation implements SerializableToString {
+    private static final int OP_CODE = Operations.COMPONENT_VALUE;
+    private static final String CLASS_NAME = "ComponentValue";
 
     public static final int WIDTH = 0;
     public static final int HEIGHT = 1;
@@ -41,10 +41,20 @@
     private int mComponentID = -1;
     private int mValueId = -1;
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -78,6 +88,12 @@
         // Nothing
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int type = buffer.readInt();
         int componentId = buffer.readInt();
@@ -86,6 +102,11 @@
         operations.add(op);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a component-related value (eg its width, height etc.)")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
index bfaf139..ac6271c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
@@ -32,7 +32,7 @@
 import java.util.Arrays;
 import java.util.List;
 
-public class DataListFloat implements VariableSupport, ArrayAccess, Operation {
+public class DataListFloat extends Operation implements VariableSupport, ArrayAccess {
     private static final int OP_CODE = Operations.FLOAT_LIST;
     private static final String CLASS_NAME = "IdListData";
     private final int mId;
@@ -79,6 +79,12 @@
         }
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int len = buffer.readInt();
@@ -93,6 +99,11 @@
         operations.add(data);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("a list of Floats")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
index 9b286b9..47cbff3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
@@ -33,7 +33,7 @@
 import java.util.Arrays;
 import java.util.List;
 
-public class DataListIds implements VariableSupport, ArrayAccess, Operation {
+public class DataListIds extends Operation implements VariableSupport, ArrayAccess {
     private static final int OP_CODE = Operations.ID_LIST;
     private static final String CLASS_NAME = "IdListData";
     private final int mId;
@@ -71,6 +71,12 @@
         }
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int len = buffer.readInt();
@@ -85,6 +91,11 @@
         operations.add(data);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("a list of id's")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
index 643afc8..ff85721 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
@@ -31,7 +31,7 @@
 import java.util.List;
 
 /** This is a map of strings to type & Id */
-public class DataMapIds implements Operation {
+public class DataMapIds extends Operation {
     private static final int OP_CODE = Operations.ID_MAP;
     private static final String CLASS_NAME = "DataMapIds";
     int mId;
@@ -105,6 +105,12 @@
         }
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int len = buffer.readInt();
@@ -123,6 +129,11 @@
         operations.add(data);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a collection of name id pairs")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapLookup.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapLookup.java
index eae532c..9af2343 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapLookup.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapLookup.java
@@ -31,7 +31,7 @@
 import java.util.List;
 
 /** This can lookup in a map given a string writing the results to an id. */
-public class DataMapLookup implements Operation {
+public class DataMapLookup extends Operation {
     private static final int OP_CODE = Operations.DATA_MAP_LOOKUP;
     private static final String CLASS_NAME = "DataMapLookup";
     public int mId;
@@ -109,12 +109,17 @@
         operations.add(new DataMapLookup(id, mapId, stringId));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
-                .description("A float and its associated id")
+                .description("Look up a value in a data map")
                 .field(INT, "id", "id of float")
                 .field(INT, "dataMapId", "32-bit float value")
-                .field(INT, "value", "32-bit float value");
+                .field(INT, "stringId", "32-bit float value");
     }
 
     @Override
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
index 629f786..fd1f410 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
@@ -26,15 +26,27 @@
 
 import java.util.List;
 
+/** Draw an Arc command the specified arc, will be scaled to fit inside the specified oval. */
 public class DrawArc extends DrawBase6 {
-    public static final int OP_CODE = Operations.DRAW_ARC;
+    private static final int OP_CODE = Operations.DRAW_ARC;
     private static final String CLASS_NAME = "DrawArc";
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawArc::new;
         read(m, buffer, operations);
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -79,6 +91,11 @@
         apply(buffer, v1, v2, v3, v4, v5, v6);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description(
@@ -98,8 +115,20 @@
                         "Sweep angle (in degrees) measured clockwise");
     }
 
-    public DrawArc(float v1, float v2, float v3, float v4, float v5, float v6) {
-        super(v1, v2, v3, v4, v5, v6);
+    /**
+     * Create Draw Arc command Draw the specified arc, which will be scaled to fit inside the
+     * specified oval.
+     *
+     * @param left the left side of the oval
+     * @param top the top of the oval
+     * @param right the right side of the oval
+     * @param bottom the bottom of the oval
+     * @param startAngle Starting angle (in degrees) where the arc begins
+     * @param sweepAngle Sweep angle (in degrees) measured clockwise
+     */
+    public DrawArc(
+            float left, float top, float right, float bottom, float startAngle, float sweepAngle) {
+        super(left, top, right, bottom, startAngle, sweepAngle);
         mName = "DrawArc";
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
index 6c288a35..64c2730 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
@@ -145,6 +145,11 @@
         return null;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "DrawBase6";
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
index 9c23c95..cdb527d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
@@ -100,23 +100,39 @@
                 + ";";
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         float sLeft = buffer.readFloat();
         float srcTop = buffer.readFloat();
         float srcRight = buffer.readFloat();
         float srcBottom = buffer.readFloat();
-        int discriptionId = buffer.readInt();
+        int descriptionId = buffer.readInt();
 
-        DrawBitmap op = new DrawBitmap(id, sLeft, srcTop, srcRight, srcBottom, discriptionId);
+        DrawBitmap op = new DrawBitmap(id, sLeft, srcTop, srcRight, srcBottom, descriptionId);
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -138,6 +154,11 @@
         buffer.writeInt(descriptionId);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a bitmap")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
index da9fe24..638fe14 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
@@ -24,11 +24,12 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
 
 import java.util.List;
 
 /** Operation to draw a given cached bitmap */
-public class DrawBitmapInt extends PaintOperation {
+public class DrawBitmapInt extends PaintOperation implements AccessibleComponent {
     private static final int OP_CODE = Operations.DRAW_BITMAP_INT;
     private static final String CLASS_NAME = "DrawBitmapInt";
     int mImageId;
@@ -106,11 +107,26 @@
                 + ";";
     }
 
+    @Override
+    public Integer getContentDescriptionId() {
+        return mContentDescId;
+    }
+
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -140,6 +156,12 @@
         buffer.writeInt(cdId);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int imageId = buffer.readInt();
         int sLeft = buffer.readInt();
@@ -159,6 +181,11 @@
         operations.add(op);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a bitmap using integer coordinates")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
index e9f81d5..d6467c9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
@@ -27,11 +27,13 @@
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
 import com.android.internal.widget.remotecompose.core.operations.utilities.ImageScaling;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
 
 import java.util.List;
 
 /** Operation to draw a given cached bitmap */
-public class DrawBitmapScaled extends PaintOperation implements VariableSupport {
+public class DrawBitmapScaled extends PaintOperation
+        implements VariableSupport, AccessibleComponent {
     private static final int OP_CODE = Operations.DRAW_BITMAP_SCALED;
     private static final String CLASS_NAME = "DrawBitmapScaled";
     int mImageId;
@@ -191,11 +193,26 @@
                 + Utils.floatToString(mScaleFactor, mOutScaleFactor);
     }
 
+    @Override
+    public Integer getContentDescriptionId() {
+        return mContentDescId;
+    }
+
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -232,6 +249,12 @@
         buffer.writeInt(cdId);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int imageId = buffer.readInt();
 
@@ -265,6 +288,11 @@
         operations.add(op);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a bitmap using integer coordinates")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
index 11bd49a..735e262 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
@@ -30,20 +30,41 @@
     private static final int OP_CODE = Operations.DRAW_CIRCLE;
     private static final String CLASS_NAME = "DrawCircle";
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawCircle::new;
         read(m, buffer, operations);
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a Circle")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
index 7310a9d..f3a190d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
@@ -32,20 +32,41 @@
     private static final int OP_CODE = Operations.DRAW_LINE;
     private static final String CLASS_NAME = "DrawLine";
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawLine::new;
         read(m, buffer, operations);
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a line segment")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
index aa5116e..a009874 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
@@ -30,20 +30,41 @@
     private static final int OP_CODE = Operations.DRAW_OVAL;
     private static final String CLASS_NAME = "DrawOval";
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawOval::new;
         read(m, buffer, operations);
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw the specified oval")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
index d35094b..398cf48 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
@@ -50,17 +50,33 @@
         return "DrawPath " + "[" + mId + "]" + ", " + mStart + ", " + mEnd;
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         DrawPath op = new DrawPath(id);
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.DRAW_PATH;
     }
@@ -70,6 +86,11 @@
         buffer.writeInt(id);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a bitmap using integer coordinates")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
index db7633c..38477ad 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
@@ -31,20 +31,41 @@
     private static final int OP_CODE = Operations.DRAW_RECT;
     private static final String CLASS_NAME = "DrawRect";
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawRect::new;
         read(m, buffer, operations);
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw the specified rectangle")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
index c306e2b..a41e46e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
@@ -31,11 +31,22 @@
     private static final int OP_CODE = Operations.DRAW_ROUND_RECT;
     private static final String CLASS_NAME = "DrawRoundRect";
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawRoundRect::new;
         read(m, buffer, operations);
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -80,6 +91,11 @@
         apply(buffer, v1, v2, v3, v4, v5, v6);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw the specified round-rect")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
index 3b60df7..51ece77 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
@@ -27,14 +27,25 @@
 import java.util.List;
 
 public class DrawSector extends DrawBase6 {
-    public static final int OP_CODE = Operations.DRAW_SECTOR;
+    private static final int OP_CODE = Operations.DRAW_SECTOR;
     private static final String CLASS_NAME = "DrawSector";
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawSector::new;
         read(m, buffer, operations);
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -79,6 +90,11 @@
         apply(buffer, v1, v2, v3, v4, v5, v6);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
index 9c587ab..8adba1d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
@@ -101,6 +101,12 @@
                 + floatToString(mY, mOutY);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int text = buffer.readInt();
         int start = buffer.readInt();
@@ -115,11 +121,21 @@
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -158,6 +174,11 @@
         buffer.writeBoolean(rtl);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", id(), CLASS_NAME)
                 .description("Draw a run of text, all in a single direction")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
index 8b70181..f839922 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
@@ -111,6 +111,12 @@
         return Float.toString(v);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textID = buffer.readInt();
         float x = buffer.readFloat();
@@ -124,11 +130,21 @@
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -161,6 +177,11 @@
         buffer.writeInt(flags);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw text centered about an anchor point")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
index e90122b..86f3c99 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
@@ -83,6 +83,12 @@
                 + Utils.floatToString(mVOffset, mOutVOffset);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         int pathId = buffer.readInt();
@@ -92,11 +98,21 @@
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "DrawTextOnPath";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.DRAW_TEXT_ON_PATH;
     }
@@ -110,6 +126,11 @@
         buffer.writeFloat(hOffset);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw text along path object")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
index 0aaaf42..d4d4a5e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
@@ -92,6 +92,12 @@
                 + floatToString(mStop, mOutStop);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int path1Id = buffer.readInt();
         int path2Id = buffer.readInt();
@@ -102,11 +108,21 @@
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "DrawTweenPath";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.DRAW_TWEEN_PATH;
     }
@@ -126,6 +142,11 @@
         buffer.writeFloat(stop);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw text along path object")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
index 17aaf3b..e04e691 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
@@ -29,7 +29,7 @@
 import java.util.List;
 
 /** Operation to deal with Text data */
-public class FloatConstant implements com.android.internal.widget.remotecompose.core.Operation {
+public class FloatConstant extends Operation {
     private static final int OP_CODE = Operations.DATA_FLOAT;
     private static final String CLASS_NAME = "FloatConstant";
     public int mTextId;
@@ -51,11 +51,21 @@
         return "FloatConstant[" + mTextId + "] = " + mValue;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -73,6 +83,12 @@
         buffer.writeFloat(value);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
 
@@ -80,6 +96,11 @@
         operations.add(new FloatConstant(textId, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("A float and its associated id")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
index eef9746..c1872fd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
@@ -42,7 +42,7 @@
  * like injecting the width of the component int draw rect As well as supporting generalized
  * animation floats. The floats represent a RPN style calculator
  */
-public class FloatExpression implements Operation, VariableSupport {
+public class FloatExpression extends Operation implements VariableSupport {
     private static final int OP_CODE = Operations.ANIMATED_FLOAT;
     private static final String CLASS_NAME = "FloatExpression";
     public int mId;
@@ -139,6 +139,11 @@
         }
     }
 
+    // Keep track of the last computed value when we are animated,
+    // e.g. if FloatAnimation or Spring is used, so that we can
+    // ask for a repaint.
+    float mLastAnimatedValue = Float.NaN;
+
     @Override
     public void apply(@NonNull RemoteContext context) {
         updateVariables(context);
@@ -146,16 +151,30 @@
         if (Float.isNaN(mLastChange)) {
             mLastChange = t;
         }
-        if (mFloatAnimation != null) {
+        float lastComputedValue;
+        if (mFloatAnimation != null && !Float.isNaN(mLastCalculatedValue)) {
             float f = mFloatAnimation.get(t - mLastChange);
             context.loadFloat(mId, f);
+            lastComputedValue = f;
+            if (lastComputedValue != mLastAnimatedValue) {
+                mLastAnimatedValue = lastComputedValue;
+                context.needsRepaint();
+            }
         } else if (mSpring != null) {
             float f = mSpring.get(t - mLastChange);
             context.loadFloat(mId, f);
+            lastComputedValue = f;
+            if (lastComputedValue != mLastAnimatedValue) {
+                mLastAnimatedValue = lastComputedValue;
+                context.needsRepaint();
+            }
         } else {
-            context.loadFloat(
-                    mId,
-                    mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length));
+            float v =
+                    mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+            if (mFloatAnimation != null) {
+                mFloatAnimation.setTargetValue(v);
+            }
+            context.loadFloat(mId, v);
         }
     }
 
@@ -202,11 +221,21 @@
                 + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -228,6 +257,9 @@
         buffer.writeInt(id);
 
         int len = value.length;
+        if (len > MAX_EXPRESSION_SIZE) {
+            throw new RuntimeException(AnimatedFloatExpression.toString(value, null) + " to long");
+        }
         if (animation != null) {
             len |= (animation.length << 16);
         }
@@ -243,12 +275,18 @@
         }
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int len = buffer.readInt();
         int valueLen = len & 0xFFFF;
         if (valueLen > MAX_EXPRESSION_SIZE) {
-            throw new RuntimeException("Float expression to long");
+            throw new RuntimeException("Float expression too long");
         }
         int animLen = (len >> 16) & 0xFFFF;
         float[] values = new float[valueLen];
@@ -268,6 +306,11 @@
         operations.add(new FloatExpression(id, values, animation));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("A Float expression")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
index 009aa03..656dc09 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
@@ -35,11 +35,11 @@
  * <p>It encodes the version of the document (following semantic versioning) as well as the
  * dimensions of the document in pixels.
  */
-public class Header implements RemoteComposeOperation {
+public class Header extends Operation implements RemoteComposeOperation {
     private static final int OP_CODE = Operations.HEADER;
     private static final String CLASS_NAME = "Header";
     public static final int MAJOR_VERSION = 0;
-    public static final int MINOR_VERSION = 1;
+    public static final int MINOR_VERSION = 2;
     public static final int PATCH_VERSION = 0;
 
     int mMajorVersion;
@@ -115,11 +115,21 @@
         return toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -136,6 +146,12 @@
         buffer.writeLong(capabilities);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int majorVersion = buffer.readInt();
         int minorVersion = buffer.readInt();
@@ -157,6 +173,11 @@
         operations.add(header);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Protocol Operations", OP_CODE, CLASS_NAME)
                 .description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
index c49f74d..f04f30d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
@@ -38,7 +38,7 @@
  * like injecting the width of the component int draw rect As well as supporting generalized
  * animation floats. The floats represent a RPN style calculator
  */
-public class IntegerExpression implements Operation, VariableSupport {
+public class IntegerExpression extends Operation implements VariableSupport {
     private static final int OP_CODE = Operations.INTEGER_EXPRESSION;
     private static final String CLASS_NAME = "IntegerExpression";
     public int mId;
@@ -136,11 +136,21 @@
         return "IntegerExpression[" + mId + "] = (" + s + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -163,6 +173,12 @@
         }
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int mask = buffer.readInt();
@@ -178,6 +194,11 @@
         operations.add(new IntegerExpression(id, mask, values));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Expression that computes an integer")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
index 4e7ee4d..044430d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
@@ -26,6 +26,7 @@
 
 import java.util.List;
 
+/** The restore previous matrix command */
 public class MatrixRestore extends PaintOperation {
     private static final int OP_CODE = Operations.MATRIX_RESTORE;
     private static final String CLASS_NAME = "MatrixRestore";
@@ -37,6 +38,12 @@
         apply(buffer);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         MatrixRestore op = new MatrixRestore();
         operations.add(op);
@@ -48,11 +55,21 @@
         return "MatrixRestore";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -61,6 +78,11 @@
         buffer.start(OP_CODE);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Restore the matrix and clip");
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
index 438a2aa..57f5a0e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
@@ -26,10 +26,17 @@
 
 import java.util.List;
 
+/** The rotate the rendering command */
 public class MatrixRotate extends DrawBase3 {
-    public static final int OP_CODE = Operations.MATRIX_ROTATE;
+    private static final int OP_CODE = Operations.MATRIX_ROTATE;
     private static final String CLASS_NAME = "MatrixRotate";
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m =
                 new Maker() {
@@ -42,15 +49,30 @@
         read(m, buffer, operations);
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("apply rotation to matrix")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
index 09f54a5..aec316a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
@@ -26,6 +26,7 @@
 
 import java.util.List;
 
+/** The save the matrix state command */
 public class MatrixSave extends PaintOperation {
     private static final int OP_CODE = Operations.MATRIX_SAVE;
     private static final String CLASS_NAME = "MatrixSave";
@@ -41,16 +42,32 @@
         return "MatrixSave;";
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         MatrixSave op = new MatrixSave();
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -59,6 +76,11 @@
         buffer.start(Operations.MATRIX_SAVE);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Save the matrix and clip to a stack");
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
index 6304584..07f965f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
@@ -26,19 +26,36 @@
 
 import java.util.List;
 
+/** Scale the rendering matrix command */
 public class MatrixScale extends DrawBase4 {
-    public static final int OP_CODE = Operations.MATRIX_SCALE;
-    public static final String CLASS_NAME = "MatrixScale";
+    private static final int OP_CODE = Operations.MATRIX_SCALE;
+    private static final String CLASS_NAME = "MatrixScale";
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = MatrixScale::new;
         read(m, buffer, operations);
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -49,9 +66,14 @@
         apply(buffer, v1, v2, v3, v4);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
-                .description("Draw the specified Oval")
+                .description("Scale the following draw commands")
                 .field(DocumentedOperation.FLOAT, "scaleX", "The amount to scale in X")
                 .field(DocumentedOperation.FLOAT, "scaleY", "The amount to scale in Y")
                 .field(DocumentedOperation.FLOAT, "pivotX", "The x-coordinate for the pivot point")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
index 675cf0d..b31492d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
@@ -27,19 +27,36 @@
 
 import java.util.List;
 
+/** Skew the matrix command */
 public class MatrixSkew extends DrawBase2 {
-    public static final int OP_CODE = Operations.MATRIX_SKEW;
-    public static final String CLASS_NAME = "MatrixSkew";
+    private static final int OP_CODE = Operations.MATRIX_SKEW;
+    private static final String CLASS_NAME = "MatrixSkew";
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = MatrixSkew::new;
         read(m, buffer, operations);
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -50,6 +67,11 @@
         apply(buffer, v1, v2);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Current matrix with the specified skew.")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
index b0a7d35..11fa040 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
@@ -26,19 +26,36 @@
 
 import java.util.List;
 
+/** translate the matrix command */
 public class MatrixTranslate extends DrawBase2 {
-    public static final int OP_CODE = Operations.MATRIX_TRANSLATE;
-    public static final String CLASS_NAME = "MatrixTranslate";
+    private static final int OP_CODE = Operations.MATRIX_TRANSLATE;
+    private static final String CLASS_NAME = "MatrixTranslate";
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = MatrixTranslate::new;
         read(m, buffer, operations);
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -49,6 +66,11 @@
         apply(buffer, v1, v2);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, "MatrixTranslate")
                 .description("Preconcat the current matrix with the specified translation")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
index 5ca91af..dde632e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
@@ -30,7 +30,7 @@
 import java.util.List;
 
 /** Operation to deal with Text data */
-public class NamedVariable implements Operation {
+public class NamedVariable extends Operation {
     private static final int OP_CODE = Operations.NAMED_VARIABLE;
     private static final String CLASS_NAME = "NamedVariable";
     public final int mVarId;
@@ -64,11 +64,21 @@
                 + mVarType;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -89,6 +99,12 @@
         buffer.writeUTF8(text);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int varId = buffer.readInt();
         int varType = buffer.readInt();
@@ -96,6 +112,11 @@
         operations.add(new NamedVariable(varId, varType, text));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Add a string name for an ID")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
index b24df8a..daf2c55 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
@@ -61,11 +61,21 @@
         return "PaintData " + "\"" + mPaintData + "\"";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -75,12 +85,23 @@
         paintBundle.writeBundle(buffer);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         PaintData data = new PaintData();
         data.mPaintData.readBundle(buffer);
         operations.add(data);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a Paint ")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
new file mode 100644
index 0000000..7ff879e
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY;
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class PathAppend extends PaintOperation implements VariableSupport {
+    private static final int OP_CODE = Operations.PATH_ADD;
+    private static final String CLASS_NAME = "PathAppend";
+    int mInstanceId;
+    float[] mFloatPath;
+    float[] mOutputPath;
+
+    PathAppend(int instanceId, float[] floatPath) {
+        mInstanceId = instanceId;
+        mFloatPath = floatPath;
+        mOutputPath = Arrays.copyOf(mFloatPath, mFloatPath.length);
+    }
+
+    @Override
+    public void updateVariables(@NonNull RemoteContext context) {
+        for (int i = 0; i < mFloatPath.length; i++) {
+            float v = mFloatPath[i];
+            if (Utils.isVariable(v)) {
+                mOutputPath[i] = Float.isNaN(v) ? context.getFloat(Utils.idFromNan(v)) : v;
+            } else {
+                mOutputPath[i] = v;
+            }
+        }
+    }
+
+    @Override
+    public void registerListening(@NonNull RemoteContext context) {
+        for (float v : mFloatPath) {
+            if (Float.isNaN(v)) {
+                context.listensTo(Utils.idFromNan(v), this);
+            }
+        }
+    }
+
+    @Override
+    public void write(@NonNull WireBuffer buffer) {
+        apply(buffer, mInstanceId, mOutputPath);
+    }
+
+    @NonNull
+    @Override
+    public String deepToString(String indent) {
+        return PathData.pathString(mFloatPath);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "PathAppend[" + mInstanceId + "] += " + "\"" + pathString(mOutputPath) + "\"";
+    }
+
+    /**
+     * public float[] getFloatPath(PaintContext context) { float[] ret = mRetFloats; // Assume
+     * retFloats is declared elsewhere if (ret == null) { return mFloatPath; // Assume floatPath is
+     * declared elsewhere } float[] localRef = mRef; // Assume ref is of type Float[] if (localRef
+     * == null) { for (int i = 0; i < mFloatPath.length; i++) { ret[i] = mFloatPath[i]; } } else {
+     * for (int i = 0; i < mFloatPath.length; i++) { float lr = localRef[i]; if (Float.isNaN(lr)) {
+     * ret[i] = Utils.getActualValue(lr); } else { ret[i] = mFloatPath[i]; } } } return ret; }
+     */
+    public static final int MOVE = 10;
+
+    public static final int LINE = 11;
+    public static final int QUADRATIC = 12;
+    public static final int CONIC = 13;
+    public static final int CUBIC = 14;
+    public static final int CLOSE = 15;
+    public static final int DONE = 16;
+    public static final float MOVE_NAN = Utils.asNan(MOVE);
+    public static final float LINE_NAN = Utils.asNan(LINE);
+    public static final float QUADRATIC_NAN = Utils.asNan(QUADRATIC);
+    public static final float CONIC_NAN = Utils.asNan(CONIC);
+    public static final float CUBIC_NAN = Utils.asNan(CUBIC);
+    public static final float CLOSE_NAN = Utils.asNan(CLOSE);
+    public static final float DONE_NAN = Utils.asNan(DONE);
+
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
+    public static int id() {
+        return OP_CODE;
+    }
+
+    public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] data) {
+        buffer.start(OP_CODE);
+        buffer.writeInt(id);
+        buffer.writeInt(data.length);
+        for (float datum : data) {
+            buffer.writeFloat(datum);
+        }
+    }
+
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+        int id = buffer.readInt();
+        int len = buffer.readInt();
+        float[] data = new float[len];
+        for (int i = 0; i < data.length; i++) {
+            data[i] = buffer.readFloat();
+        }
+        operations.add(new PathAppend(id, data));
+    }
+
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
+    public static void documentation(@NonNull DocumentationBuilder doc) {
+        doc.operation("Data Operations", OP_CODE, CLASS_NAME)
+                .description("Append to a Path")
+                .field(DocumentedOperation.INT, "id", "id string")
+                .field(INT, "length", "id string")
+                .field(FLOAT_ARRAY, "pathData", "length", "path encoded as floats");
+    }
+
+    @Override
+    public void paint(PaintContext context) {
+        apply(context.getContext());
+    }
+
+    @Override
+    public void apply(@NonNull RemoteContext context) {
+        float[] data = context.getPathData(mInstanceId);
+        float[] out = mOutputPath;
+        if (data != null) {
+            out = new float[data.length + mOutputPath.length];
+
+            for (int i = 0; i < data.length; i++) {
+                out[i] = data[i];
+            }
+            for (int i = 0; i < mOutputPath.length; i++) {
+                out[i + data.length] = mOutputPath[i];
+            }
+        } else {
+            System.out.println(">>>>>>>>>>> DATA IS NULL");
+        }
+        context.loadPathData(mInstanceId, out);
+    }
+
+    @NonNull
+    public static String pathString(@Nullable float[] path) {
+        if (path == null) {
+            return "null";
+        }
+        StringBuilder str = new StringBuilder();
+        for (int i = 0; i < path.length; i++) {
+            if (Float.isNaN(path[i])) {
+                int id = Utils.idFromNan(path[i]); // Assume idFromNan is defined elsewhere
+                if (id <= DONE) { // Assume DONE is a constant
+                    switch (id) {
+                        case MOVE:
+                            str.append("M");
+                            break;
+                        case LINE:
+                            str.append("L");
+                            break;
+                        case QUADRATIC:
+                            str.append("Q");
+                            break;
+                        case CONIC:
+                            str.append("R");
+                            break;
+                        case CUBIC:
+                            str.append("C");
+                            break;
+                        case CLOSE:
+                            str.append("Z");
+                            break;
+                        case DONE:
+                            str.append(".");
+                            break;
+                        default:
+                            str.append("[" + id + "]");
+                            break;
+                    }
+                } else {
+                    str.append("(" + id + ")");
+                }
+            }
+        }
+        return str.toString();
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
new file mode 100644
index 0000000..75562cd
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class PathCreate extends PaintOperation implements VariableSupport {
+    private static final int OP_CODE = Operations.PATH_CREATE;
+    private static final String CLASS_NAME = "PathCreate";
+    int mInstanceId;
+    float[] mFloatPath;
+    float[] mOutputPath;
+
+    PathCreate(int instanceId, float startX, float startY) {
+        mInstanceId = instanceId;
+        mFloatPath = new float[] {PathData.MOVE_NAN, startX, startY};
+        mOutputPath = Arrays.copyOf(mFloatPath, mFloatPath.length);
+    }
+
+    @Override
+    public void updateVariables(@NonNull RemoteContext context) {
+
+        for (int i = 0; i < mFloatPath.length; i++) {
+            float v = mFloatPath[i];
+            if (Utils.isVariable(v)) {
+                mOutputPath[i] = Float.isNaN(v) ? context.getFloat(Utils.idFromNan(v)) : v;
+            } else {
+                mOutputPath[i] = v;
+            }
+        }
+    }
+
+    @Override
+    public void registerListening(@NonNull RemoteContext context) {
+        for (float v : mFloatPath) {
+            if (Float.isNaN(v)) {
+                context.listensTo(Utils.idFromNan(v), this);
+            }
+        }
+    }
+
+    @Override
+    public void write(@NonNull WireBuffer buffer) {
+        apply(buffer, mInstanceId, mFloatPath[1], mFloatPath[2]);
+    }
+
+    @NonNull
+    @Override
+    public String deepToString(String indent) {
+        return pathString(mFloatPath);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "PathCreate["
+                + mInstanceId
+                + "] = "
+                + "\""
+                + deepToString(" ")
+                + "\"["
+                + Utils.idStringFromNan(mFloatPath[1])
+                + "] "
+                + mOutputPath[1]
+                + " ["
+                + Utils.idStringFromNan(mFloatPath[2])
+                + "] "
+                + mOutputPath[2];
+    }
+
+    public static final int MOVE = 10;
+    public static final int LINE = 11;
+    public static final int QUADRATIC = 12;
+    public static final int CONIC = 13;
+    public static final int CUBIC = 14;
+    public static final int CLOSE = 15;
+    public static final int DONE = 16;
+    public static final float MOVE_NAN = Utils.asNan(MOVE);
+    public static final float LINE_NAN = Utils.asNan(LINE);
+    public static final float QUADRATIC_NAN = Utils.asNan(QUADRATIC);
+    public static final float CONIC_NAN = Utils.asNan(CONIC);
+    public static final float CUBIC_NAN = Utils.asNan(CUBIC);
+    public static final float CLOSE_NAN = Utils.asNan(CLOSE);
+    public static final float DONE_NAN = Utils.asNan(DONE);
+
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
+    public static int id() {
+        return OP_CODE;
+    }
+
+    public static void apply(@NonNull WireBuffer buffer, int id, float startX, float startY) {
+        buffer.start(OP_CODE);
+        buffer.writeInt(id);
+        buffer.writeFloat(startX);
+        buffer.writeFloat(startY);
+    }
+
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+
+        int id = buffer.readInt();
+        float startX = buffer.readFloat();
+        float startY = buffer.readFloat();
+        operations.add(new PathCreate(id, startX, startY));
+    }
+
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
+    public static void documentation(@NonNull DocumentationBuilder doc) {
+        doc.operation("Data Operations", OP_CODE, CLASS_NAME)
+                .description("Encode a Path ")
+                .field(DocumentedOperation.INT, "id", "id of path")
+                .field(FLOAT, "startX", "initial start x")
+                .field(FLOAT, "startX", "initial start y");
+    }
+
+    @NonNull
+    public static String pathString(@Nullable float[] path) {
+        if (path == null) {
+            return "null";
+        }
+        StringBuilder str = new StringBuilder();
+        for (int i = 0; i < path.length; i++) {
+            if (i != 0) {
+                str.append(" ");
+            }
+            if (Float.isNaN(path[i])) {
+                int id = Utils.idFromNan(path[i]); // Assume idFromNan is defined elsewhere
+                if (id <= DONE) { // Assume DONE is a constant
+                    switch (id) {
+                        case MOVE:
+                            str.append("M");
+                            break;
+                        case LINE:
+                            str.append("L");
+                            break;
+                        case QUADRATIC:
+                            str.append("Q");
+                            break;
+                        case CONIC:
+                            str.append("R");
+                            break;
+                        case CUBIC:
+                            str.append("C");
+                            break;
+                        case CLOSE:
+                            str.append("Z");
+                            break;
+                        case DONE:
+                            str.append(".");
+                            break;
+                        default:
+                            str.append("[" + id + "]");
+                            break;
+                    }
+                } else {
+                    str.append("(" + id + ")");
+                }
+            } else {
+                str.append(path[i]);
+            }
+        }
+        return str.toString();
+    }
+
+    @Override
+    public void paint(PaintContext context) {
+        apply(context.getContext());
+    }
+
+    @Override
+    public void apply(@NonNull RemoteContext context) {
+        context.loadPathData(mInstanceId, mOutputPath);
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
index 509f362..85a01fc 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
@@ -32,12 +32,13 @@
 import java.util.Arrays;
 import java.util.List;
 
-public class PathData implements Operation, VariableSupport {
+public class PathData extends Operation implements VariableSupport {
     private static final int OP_CODE = Operations.DATA_PATH;
     private static final String CLASS_NAME = "PathData";
     int mInstanceId;
     float[] mFloatPath;
     float[] mOutputPath;
+    private boolean mPathChanged = true;
 
     PathData(int instanceId, float[] floatPath) {
         mInstanceId = instanceId;
@@ -50,7 +51,11 @@
         for (int i = 0; i < mFloatPath.length; i++) {
             float v = mFloatPath[i];
             if (Utils.isVariable(v)) {
+                float tmp = mOutputPath[i];
                 mOutputPath[i] = Float.isNaN(v) ? context.getFloat(Utils.idFromNan(v)) : v;
+                if (tmp != mOutputPath[i]) {
+                    mPathChanged = true;
+                }
             } else {
                 mOutputPath[i] = v;
             }
@@ -107,11 +112,21 @@
     public static final float CLOSE_NAN = Utils.asNan(CLOSE);
     public static final float DONE_NAN = Utils.asNan(DONE);
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -125,6 +140,12 @@
         }
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int imageId = buffer.readInt();
         int len = buffer.readInt();
@@ -135,6 +156,11 @@
         operations.add(new PathData(imageId, data));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a Path ")
@@ -143,6 +169,12 @@
                 .field(FLOAT_ARRAY, "pathData", "length", "path encoded as floats");
     }
 
+    /**
+     * Render a path as a string
+     *
+     * @param path path as a array of floats
+     * @return string describing the path
+     */
     @NonNull
     public static String pathString(@Nullable float[] path) {
         if (path == null) {
@@ -194,6 +226,9 @@
 
     @Override
     public void apply(@NonNull RemoteContext context) {
-        context.loadPathData(mInstanceId, mOutputPath);
+        if (mPathChanged) {
+            context.loadPathData(mInstanceId, mOutputPath);
+        }
+        mPathChanged = false;
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
new file mode 100644
index 0000000..65adfea
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+
+import java.util.List;
+
+/** Operation to deal with Path data */
+public class PathTween extends PaintOperation implements VariableSupport {
+    private static final int OP_CODE = Operations.PATH_TWEEN;
+    private static final String CLASS_NAME = "PathTween";
+    public int mOutId;
+    public int mPathId1;
+    public int mPathId2;
+    public float mTween;
+    public float mTweenOut;
+
+    public PathTween(int outId, int pathId1, int pathId2, float tween) {
+        this.mOutId = outId;
+        this.mPathId1 = pathId1;
+        this.mPathId2 = pathId2;
+        this.mTween = tween;
+        this.mTweenOut = mTween;
+    }
+
+    @Override
+    public void updateVariables(@NonNull RemoteContext context) {
+        mTweenOut = Float.isNaN(mTween) ? context.getFloat(Utils.idFromNan(mTween)) : mTween;
+    }
+
+    @Override
+    public void registerListening(@NonNull RemoteContext context) {
+        if (Float.isNaN(mTween)) {
+            context.listensTo(Utils.idFromNan(mTween), this);
+        }
+    }
+
+    @Override
+    public void write(@NonNull WireBuffer buffer) {
+        apply(buffer, mOutId, mPathId1, mPathId2, mTween);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "PathTween["
+                + mOutId
+                + "] = ["
+                + mPathId1
+                + " ] + [ "
+                + mPathId2
+                + "], "
+                + floatToString(mTween, mTweenOut);
+    }
+
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
+    public static int id() {
+        return OP_CODE;
+    }
+
+    /**
+     * Writes out the operation to the buffer
+     *
+     * @param buffer buffer to write to
+     * @param outId id of the path
+     * @param pathId1 source path 1
+     * @param pathId2 source path 2
+     * @param tween interpolate between two paths
+     */
+    public static void apply(
+            @NonNull WireBuffer buffer, int outId, int pathId1, int pathId2, float tween) {
+        buffer.start(OP_CODE);
+        buffer.writeInt(outId);
+        buffer.writeInt(pathId1);
+        buffer.writeInt(pathId2);
+        buffer.writeFloat(tween);
+    }
+
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+        int outId1 = buffer.readInt();
+        int pathId1 = buffer.readInt();
+        int pathId2 = buffer.readInt();
+        float tween = buffer.readFloat();
+
+        operations.add(new PathTween(outId1, pathId1, pathId2, tween));
+    }
+
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
+    public static void documentation(@NonNull DocumentationBuilder doc) {
+        doc.operation("Data Operations", OP_CODE, CLASS_NAME)
+                .description("Merge two string into one")
+                .field(DocumentedOperation.INT, "pathId", "id of the path")
+                .field(INT, "srcPathId1", "id of the path")
+                .field(INT, "srcPathId1", "x Shift of the path");
+    }
+
+    @NonNull
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+
+    @Override
+    public void paint(PaintContext context) {
+        context.tweenPath(mOutId, mPathId1, mPathId2, mTweenOut);
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
index 8494126..55dd882 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
@@ -33,7 +33,7 @@
  * <p>It encodes the version of the document (following semantic versioning) as well as the
  * dimensions of the document in pixels.
  */
-public class RootContentBehavior implements RemoteComposeOperation {
+public class RootContentBehavior extends Operation implements RemoteComposeOperation {
     private static final int OP_CODE = Operations.ROOT_CONTENT_BEHAVIOR;
     private static final String CLASS_NAME = "RootContentBehavior";
     int mScroll = NONE;
@@ -200,11 +200,21 @@
         return toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -218,6 +228,12 @@
         buffer.writeInt(mode);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int scroll = buffer.readInt();
         int alignment = buffer.readInt();
@@ -228,6 +244,11 @@
         operations.add(rootContentBehavior);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Protocol Operations", OP_CODE, CLASS_NAME)
                 .description("Describes the behaviour of the root")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
index 109945f..ad86e0f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
@@ -24,11 +24,13 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
 
 import java.util.List;
 
 /** Describe a content description for the document */
-public class RootContentDescription implements RemoteComposeOperation {
+public class RootContentDescription extends Operation
+        implements RemoteComposeOperation, AccessibleComponent {
     private static final int OP_CODE = Operations.ROOT_CONTENT_DESCRIPTION;
     private static final String CLASS_NAME = "RootContentDescription";
     int mContentDescription;
@@ -43,6 +45,11 @@
     }
 
     @Override
+    public boolean isInterestingForSemantics() {
+        return mContentDescription != 0;
+    }
+
+    @Override
     public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mContentDescription);
     }
@@ -64,11 +71,26 @@
         return toString();
     }
 
+    @Override
+    public Integer getContentDescriptionId() {
+        return mContentDescription;
+    }
+
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -78,12 +100,23 @@
         buffer.writeInt(contentDescription);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int contentDescription = buffer.readInt();
         RootContentDescription header = new RootContentDescription(contentDescription);
         operations.add(header);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Protocol Operations", OP_CODE, CLASS_NAME)
                 .description("Content description of root")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
index e967ff4..891367e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
@@ -41,7 +41,7 @@
  * Operation to deal with bitmap data On getting an Image during a draw call the bitmap is
  * compressed and saved in playback the image is decompressed
  */
-public class ShaderData implements Operation, VariableSupport {
+public class ShaderData extends Operation implements VariableSupport {
     private static final int OP_CODE = Operations.DATA_SHADER;
     private static final String CLASS_NAME = "ShaderData";
     int mShaderTextId; // the actual text of a shader
@@ -50,6 +50,7 @@
     @Nullable HashMap<String, float[]> mUniformFloatMap = null;
     @Nullable HashMap<String, int[]> mUniformIntMap;
     @Nullable HashMap<String, Integer> mUniformBitmapMap = null;
+    private boolean mShaderValid = false;
 
     public ShaderData(
             int shaderID,
@@ -198,11 +199,21 @@
         }
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -266,6 +277,12 @@
         }
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int shaderID = buffer.readInt();
         int shaderTextId = buffer.readInt();
@@ -318,6 +335,11 @@
         operations.add(new ShaderData(shaderID, shaderTextId, floatMap, intMap, bitmapMap));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Shader")
@@ -337,7 +359,9 @@
 
     @Override
     public void apply(@NonNull RemoteContext context) {
-        context.loadShader(mShaderID, this);
+        if (mShaderValid) {
+            context.loadShader(mShaderID, this);
+        }
     }
 
     @NonNull
@@ -345,4 +369,13 @@
     public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
+
+    /**
+     * Enable or disable the shader
+     *
+     * @param shaderValid if true shader can be used
+     */
+    public void enable(boolean shaderValid) {
+        mShaderValid = shaderValid;
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
index ade008e..d48de37 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
@@ -31,7 +31,7 @@
 import java.util.List;
 
 /** Operation to deal with Text data */
-public class TextData implements Operation, SerializableToString {
+public class TextData extends Operation implements SerializableToString {
     private static final int OP_CODE = Operations.DATA_TEXT;
     private static final String CLASS_NAME = "TextData";
     public final int mTextId;
@@ -54,11 +54,21 @@
         return "TextData[" + mTextId + "] = \"" + Utils.trimString(mText, 10) + "\"";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -69,6 +79,12 @@
         buffer.writeUTF8(text);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
 
@@ -76,6 +92,11 @@
         operations.add(new TextData(textId, text));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a string ")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
index 865ee81f..cc0ff02 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
@@ -36,7 +36,7 @@
  * [command][textID][before,after][flags] before and after define number of digits before and after
  * the decimal point
  */
-public class TextFromFloat implements Operation, VariableSupport {
+public class TextFromFloat extends Operation implements VariableSupport {
     private static final int OP_CODE = Operations.TEXT_FROM_FLOAT;
     private static final String CLASS_NAME = "TextFromFloat";
     public int mTextId;
@@ -122,11 +122,21 @@
         }
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -155,6 +165,12 @@
         buffer.writeInt(flags);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         float value = buffer.readFloat();
@@ -166,6 +182,11 @@
         operations.add(new TextFromFloat(textId, value, pre, post, flags));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Draw text along path object")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
index 6ff687b..37ea567 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
@@ -28,7 +28,7 @@
 import java.util.List;
 
 /** Operation to measure the length of the text */
-public class TextLength implements Operation {
+public class TextLength extends Operation {
     private static final int OP_CODE = Operations.TEXT_LENGTH;
     private static final String CLASS_NAME = "TextLength";
     public int mLengthId;
@@ -54,6 +54,11 @@
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -71,12 +76,23 @@
         buffer.writeInt(textId);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int lengthId = buffer.readInt();
         int textId = buffer.readInt();
         operations.add(new TextLength(lengthId, textId));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("get the length of the text and store in float table")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
index cc812a8..dceb8b6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
@@ -34,7 +34,7 @@
  * [command][textID][before,after][flags] before and after define number of digits before and after
  * the decimal point
  */
-public class TextLookup implements Operation, VariableSupport {
+public class TextLookup extends Operation implements VariableSupport {
     private static final int OP_CODE = Operations.TEXT_LOOKUP;
     private static final String CLASS_NAME = "TextFromFloat";
     public int mTextId;
@@ -79,11 +79,21 @@
         }
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -103,6 +113,12 @@
         buffer.writeFloat(index);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         int dataSetId = buffer.readInt();
@@ -110,6 +126,11 @@
         operations.add(new TextLookup(textId, dataSetId, index));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Look an array and turn into a text object")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
index 74be698..823b706 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
@@ -30,7 +30,7 @@
 import java.util.List;
 
 /** Operation convert int index of a list to text */
-public class TextLookupInt implements Operation, VariableSupport {
+public class TextLookupInt extends Operation implements VariableSupport {
     private static final int OP_CODE = Operations.TEXT_LOOKUP_INT;
     private static final String CLASS_NAME = "TextFromINT";
     public int mTextId;
@@ -72,11 +72,21 @@
         context.listensTo(mIndex, this);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -96,6 +106,12 @@
         buffer.writeInt(indexId);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         int dataSetId = buffer.readInt();
@@ -103,6 +119,11 @@
         operations.add(new TextLookupInt(textId, dataSetId, indexId));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Look up an array and turn into a text object")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
index 6d48f67..d51b389 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
@@ -70,6 +70,11 @@
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -89,6 +94,12 @@
         buffer.writeInt(type);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int textId = buffer.readInt();
@@ -96,9 +107,14 @@
         operations.add(new TextMeasure(id, textId, type));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
-                .description("A float and its associated id")
+                .description("Measure text")
                 .field(INT, "id", "id of float result of the measure")
                 .field(INT, "textId", "id of text")
                 .field(INT, "type", "type: measure 0=width,1=height");
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
index ecd5baa..d695615 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
@@ -29,7 +29,7 @@
 import java.util.List;
 
 /** Operation to deal with Text data */
-public class TextMerge implements Operation {
+public class TextMerge extends Operation {
     private static final int OP_CODE = Operations.TEXT_MERGE;
     private static final String CLASS_NAME = "TextMerge";
     public int mTextId;
@@ -53,11 +53,21 @@
         return "TextMerge[" + mTextId + "] = [" + mSrcId1 + " ] + [ " + mSrcId2 + "]";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -77,6 +87,12 @@
         buffer.writeInt(srcId2);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         int srcId1 = buffer.readInt();
@@ -85,6 +101,11 @@
         operations.add(new TextMerge(textId, srcId1, srcId2));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Merge two string into one")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
index d265070..6c9105d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
@@ -33,7 +33,7 @@
  * "tag" the subsequent operations to a given theme. On playback, we can then filter operations
  * depending on the chosen theme.
  */
-public class Theme implements RemoteComposeOperation {
+public class Theme extends Operation implements RemoteComposeOperation {
     private static final int OP_CODE = Operations.THEME;
     private static final String CLASS_NAME = "Theme";
     int mTheme;
@@ -72,11 +72,21 @@
         return indent + toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -86,11 +96,22 @@
         buffer.writeInt(theme);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int theme = buffer.readInt();
         operations.add(new Theme(theme));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Protocol Operations", OP_CODE, CLASS_NAME)
                 .description("Set a theme")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
index 1bb7b2a..14b72af 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
@@ -21,6 +21,7 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -30,6 +31,7 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
 import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression;
 import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
 import com.android.internal.widget.remotecompose.core.operations.utilities.touch.VelocityEasing;
@@ -42,12 +44,12 @@
  * touch behaviours. Including animating to Notched, positions. and tweaking the dynamics of the
  * animation.
  */
-public class TouchExpression implements Operation, VariableSupport, TouchListener {
+public class TouchExpression extends Operation implements VariableSupport, TouchListener {
     private static final int OP_CODE = Operations.TOUCH_EXPRESSION;
     private static final String CLASS_NAME = "TouchExpression";
     private float mDefValue;
     private float mOutDefValue;
-    public int mId;
+    private int mId;
     public float[] mSrcExp;
     int mMode = 1; // 0 = delta, 1 = absolute
     float mMax = 1;
@@ -56,11 +58,14 @@
     float mOutMin = 1;
     float mValue = 0;
     boolean mUnmodified = true;
-    public float[] mPreCalcValue;
+    private float[] mPreCalcValue;
     private float mLastChange = Float.NaN;
     private float mLastCalculatedValue = Float.NaN;
     AnimatedFloatExpression mExp = new AnimatedFloatExpression();
+
+    /** The maximum number of floats in the expression */
     public static final int MAX_EXPRESSION_SIZE = 32;
+
     private VelocityEasing mEasyTouch = new VelocityEasing();
     private boolean mEasingToStop = false;
     private float mTouchUpTime = 0;
@@ -77,14 +82,41 @@
     int mTouchEffects;
     float mVelocityId;
 
+    /** Stop with some deceleration */
     public static final int STOP_GENTLY = 0;
+
+    /** Stop only at the start or end */
     public static final int STOP_ENDS = 2;
+
+    /** Stop on touch up */
     public static final int STOP_INSTANTLY = 1;
+
+    /** Stop at evenly spaced notches */
     public static final int STOP_NOTCHES_EVEN = 3;
+
+    /** Stop at a collection points described in percents of the range */
     public static final int STOP_NOTCHES_PERCENTS = 4;
+
+    /** Stop at a collectiond of point described in abslute cordnates */
     public static final int STOP_NOTCHES_ABSOLUTE = 5;
+
+    /** Jump to the absloute poition of the point */
     public static final int STOP_ABSOLUTE_POS = 6;
 
+    /**
+     * create a touch expression
+     *
+     * @param id The float id the value is output to
+     * @param exp the expression (containing TOUCH_* )
+     * @param defValue the default value
+     * @param min the minimum value
+     * @param max the maximum value
+     * @param touchEffects the type of touch mode
+     * @param velocityId the valocity (not used)
+     * @param stopMode the behavour on touch oup
+     * @param stopSpec the paraameters that affect the touch up behavour
+     * @param easingSpec the easing parameters for coming to a stop
+     */
     public TouchExpression(
             int id,
             float[] exp,
@@ -126,7 +158,6 @@
 
     @Override
     public void updateVariables(RemoteContext context) {
-
         if (mPreCalcValue == null || mPreCalcValue.length != mSrcExp.length) {
             mPreCalcValue = new float[mSrcExp.length];
         }
@@ -189,7 +220,9 @@
         if (Float.isNaN(mDefValue)) {
             context.listensTo(Utils.idFromNan(mDefValue), this);
         }
-        context.addTouchListener(this);
+        if (mComponent == null) {
+            context.addTouchListener(this);
+        }
         for (float v : mSrcExp) {
             if (Float.isNaN(v)
                     && !AnimatedFloatExpression.isMathOperator(v)
@@ -329,9 +362,26 @@
 
     float mScrLeft, mScrRight, mScrTop, mScrBottom;
 
-    @Override
-    public void apply(RemoteContext context) {
-        Component comp = context.mLastComponent;
+    @Nullable Component mComponent;
+
+    /**
+     * Set the component the touch expression is in (if any)
+     *
+     * @param component the component, or null if outside
+     */
+    public void setComponent(@Nullable Component component) {
+        mComponent = component;
+        if (mComponent != null) {
+            try {
+                RootLayoutComponent root = mComponent.getRoot();
+                root.setHasTouchListeners(true);
+            } catch (Exception e) {
+            }
+        }
+    }
+
+    private void updateBounds() {
+        Component comp = mComponent;
         if (comp != null) {
             float x = comp.getX();
             float y = comp.getY();
@@ -348,7 +398,11 @@
             mScrRight = w + x;
             mScrBottom = h + y;
         }
-        updateVariables(context);
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        updateBounds();
         if (mUnmodified) {
             mCurrentValue = mOutDefValue;
             context.loadFloat(mId, wrap(mCurrentValue));
@@ -368,6 +422,7 @@
                 mEasingToStop = false;
             }
             crossNotchCheck(context);
+            context.needsRepaint();
             return;
         }
         if (mTouchDown) {
@@ -392,11 +447,11 @@
 
     @Override
     public void touchDown(RemoteContext context, float x, float y) {
-
         if (!(x >= mScrLeft && x <= mScrRight && y >= mScrTop && y <= mScrBottom)) {
             Utils.log("NOT IN WINDOW " + x + ", " + y + " " + mScrLeft + ", " + mScrTop);
             return;
         }
+        mEasingToStop = false;
         mTouchDown = true;
         mUnmodified = false;
         if (mMode == 0) {
@@ -404,6 +459,7 @@
             mDownTouchValue =
                     mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
         }
+        context.needsRepaint();
     }
 
     @Override
@@ -438,6 +494,7 @@
         float time = mMaxTime * Math.abs(dest - value) / (2 * mMaxVelocity);
         mEasyTouch.config(value, dest, slope, time, mMaxAcceleration, mMaxVelocity, null);
         mEasingToStop = true;
+        context.needsRepaint();
     }
 
     @Override
@@ -446,7 +503,7 @@
             return;
         }
         apply(context);
-        context.getDocument().getRootLayoutComponent().needsRepaint();
+        context.needsRepaint();
     }
 
     @Override
@@ -491,10 +548,21 @@
 
     // ===================== static ======================
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -505,6 +573,14 @@
      * @param buffer The buffer to write to
      * @param id the id of the resulting float
      * @param value the float expression array
+     * @param min the minimum allowed value
+     * @param max the maximum allowed value
+     * @param velocityId the velocity id
+     * @param touchEffects the type touch effect
+     * @param exp the expression the maps touch drags to movement
+     * @param touchMode the touch mode e.g. notch modes
+     * @param touchSpec the spec of the touch modes
+     * @param easingSpec the spec of when the object comes to an easing
      */
     public static void apply(
             WireBuffer buffer,
@@ -549,7 +625,13 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         float startValue = buffer.readFloat();
         float min = buffer.readFloat();
@@ -594,6 +676,11 @@
                         easingData));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("A Float expression")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
index baca3e0..de43b90 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
@@ -19,34 +19,60 @@
 
 /** Utilities to be used across all core operations */
 public class Utils {
+    /**
+     * Convert an integer id into a float
+     *
+     * @param v the integer id to convert
+     * @return the id as an float
+     */
     public static float asNan(int v) {
         return Float.intBitsToFloat(v | -0x800000);
     }
 
+    /**
+     * convert a float into an integer id
+     *
+     * @param value the float id to convert
+     * @return the id as an integer
+     */
     public static int idFromNan(float value) {
         int b = Float.floatToRawIntBits(value);
         return b & 0x3FFFFF;
     }
 
+    /**
+     * convert a long into an ID
+     *
+     * @param v the long to convert
+     * @return the id still as a long
+     */
     public static long idFromLong(long v) {
         return v - 0x100000000L;
     }
 
+    /**
+     * convert a float id and turn it into a string
+     *
+     * @param value float to convert
+     * @return string form of an id
+     */
     @NonNull
     public static String idStringFromNan(float value) {
         int b = Float.floatToRawIntBits(value) & 0x3FFFFF;
         return idString(b);
     }
 
+    /**
+     * print an id as a string
+     *
+     * @param b the id
+     * @return the id as a string
+     */
     @NonNull
     public static String idString(int b) {
         return (b > 0xFFFFF) ? "A_" + (b & 0xFFFFF) : "" + b;
     }
 
-    public static float getActualValue(float lr) {
-        return 0;
-    }
-
     /**
      * trim a string to n characters if needing to trim end in "..."
      *
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ActionOperation.java
index 7f1d101..1c24160 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ActionOperation.java
@@ -18,14 +18,28 @@
 import android.annotation.NonNull;
 
 import com.android.internal.widget.remotecompose.core.CoreDocument;
-import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 /** Operations representing actions on the document */
-public interface ActionOperation extends Operation {
+public interface ActionOperation {
+    /**
+     * Serialize the string
+     *
+     * @param indent padding to display
+     * @param serializer append the string
+     */
     void serializeToString(int indent, @NonNull StringSerializer serializer);
 
+    /**
+     * Run the action
+     *
+     * @param context remote context
+     * @param document document
+     * @param component component
+     * @param x the x location of the action
+     * @param y the y location of the action
+     */
     void runAction(
             @NonNull RemoteContext context,
             @NonNull CoreDocument document,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
index 19f4c2b..652ab2b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
@@ -20,6 +20,7 @@
 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.GeneralEasing;
 
+/** Value animation for layouts */
 public class AnimatableValue {
     boolean mIsVariable = false;
     int mId = 0;
@@ -34,6 +35,11 @@
     int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
     FloatAnimation mMotionEasing;
 
+    /**
+     * Value to animate
+     *
+     * @param value value
+     */
     public AnimatableValue(float value) {
         if (Utils.isVariable(value)) {
             mId = Utils.idFromNan(value);
@@ -43,10 +49,21 @@
         }
     }
 
+    /**
+     * Get the value
+     *
+     * @return the value
+     */
     public float getValue() {
         return mValue;
     }
 
+    /**
+     * Evaluate going through FloatAnimation if needed
+     *
+     * @param context the paint context
+     * @return the current value
+     */
     public float evaluate(PaintContext context) {
         if (!mIsVariable) {
             return mValue;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java
index aa8f7580..34b7a23 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java
@@ -41,11 +41,21 @@
         super(parent, componentId, animationId, x, y, width, height);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "CanvasContent";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.LAYOUT_CANVAS_CONTENT;
     }
@@ -61,11 +71,22 @@
         buffer.writeInt(componentId);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         operations.add(new CanvasContent(componentId, 0, 0, 0, 0, null, -1));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .field(INT, "COMPONENT_ID", "unique id for this component")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
index f44e20d..dcf1d25 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
@@ -16,6 +16,7 @@
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
@@ -33,13 +34,15 @@
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.Easing;
 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
 
 import java.util.ArrayList;
 import java.util.List;
 
 /** Represents a click modifier + actions */
 public class ClickModifierOperation extends PaintOperation
-        implements ModifierOperation, DecoratorComponent, ClickHandler {
+        implements ModifierOperation, DecoratorComponent, ClickHandler, AccessibleComponent {
     private static final int OP_CODE = Operations.MODIFIER_CLICK;
 
     long mAnimateRippleStart = 0;
@@ -54,6 +57,22 @@
 
     @NonNull PaintBundle mPaint = new PaintBundle();
 
+    @Override
+    public boolean isClickable() {
+        return true;
+    }
+
+    @Nullable
+    @Override
+    public Role getRole() {
+        return Role.BUTTON;
+    }
+
+    @Override
+    public CoreSemantics.Mode getMode() {
+        return CoreSemantics.Mode.MERGE;
+    }
+
     public void animateRipple(float x, float y) {
         mAnimateRippleStart = System.currentTimeMillis();
         mAnimateRippleX = x;
@@ -80,6 +99,10 @@
 
     @Override
     public void apply(@NonNull RemoteContext context) {
+        RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
+        if (root != null) {
+            root.setHasTouchListeners(true);
+        }
         for (Operation op : mList) {
             if (op instanceof TextData) {
                 op.apply(context);
@@ -136,7 +159,8 @@
     }
 
     @Override
-    public void layout(@NonNull RemoteContext context, float width, float height) {
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {
         mWidth = width;
         mHeight = height;
     }
@@ -173,6 +197,11 @@
         context.hapticEffect(3);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "ClickModifier";
@@ -182,10 +211,21 @@
         buffer.start(OP_CODE);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new ClickModifierOperation());
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, name())
                 .description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
index fbfc796..e95dfda 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
@@ -26,9 +26,9 @@
 import com.android.internal.widget.remotecompose.core.SerializableToString;
 import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
-import com.android.internal.widget.remotecompose.core.operations.BitmapData;
 import com.android.internal.widget.remotecompose.core.operations.ComponentValue;
 import com.android.internal.widget.remotecompose.core.operations.TextData;
+import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
 import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimateMeasure;
 import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec;
 import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
@@ -120,6 +120,17 @@
         mHeight = value;
     }
 
+    @Override
+    public void apply(@NonNull RemoteContext context) {
+        for (Operation op : mList) {
+            if (op instanceof VariableSupport && op.isDirty()) {
+                op.markNotDirty();
+                ((VariableSupport) op).updateVariables(context);
+            }
+        }
+        super.apply(context);
+    }
+
     /**
      * Utility function to update variables referencing this component dimensions
      *
@@ -234,14 +245,6 @@
         if (!mComponentValues.isEmpty()) {
             updateComponentValues(context);
         }
-        for (Operation o : mList) {
-            if (o instanceof Component) {
-                ((Component) o).updateVariables(context);
-            }
-            if (o instanceof VariableSupport) {
-                o.apply(context);
-            }
-        }
         context.mLastComponent = prev;
     }
 
@@ -249,14 +252,40 @@
         mComponentValues.add(v);
     }
 
-    public float intrinsicWidth() {
+    /**
+     * Returns the intrinsic width of the layout
+     *
+     * @param context
+     * @return the width in pixels
+     */
+    public float intrinsicWidth(@Nullable RemoteContext context) {
         return getWidth();
     }
 
-    public float intrinsicHeight() {
+    /**
+     * Returns the intrinsic height of the layout
+     *
+     * @param context
+     * @return the height in pixels
+     */
+    public float intrinsicHeight(@Nullable RemoteContext context) {
         return getHeight();
     }
 
+    /**
+     * This function is called after a component is created, with its mList initialized. This let
+     * the component a chance to do some post-initialization work on its children ops.
+     */
+    public void inflate() {
+        for (Operation op : mList) {
+            if (op instanceof TouchExpression) {
+                // Make sure to set the component of a touch expression that belongs to us!
+                TouchExpression touchExpression = (TouchExpression) op;
+                touchExpression.setComponent(this);
+            }
+        }
+    }
+
     public enum Visibility {
         GONE,
         VISIBLE,
@@ -410,11 +439,23 @@
             if (op instanceof TouchHandler) {
                 ((TouchHandler) op).onTouchDown(context, document, this, cx, cy);
             }
+            if (op instanceof TouchExpression) {
+                TouchExpression touchExpression = (TouchExpression) op;
+                touchExpression.updateVariables(context);
+                touchExpression.touchDown(context, cx, cy);
+                document.appliedTouchOperation(this);
+            }
         }
     }
 
     public void onTouchUp(
-            RemoteContext context, CoreDocument document, float x, float y, boolean force) {
+            RemoteContext context,
+            CoreDocument document,
+            float x,
+            float y,
+            float dx,
+            float dy,
+            boolean force) {
         if (!force && !contains(x, y)) {
             return;
         }
@@ -422,10 +463,15 @@
         float cy = y - getScrollY();
         for (Operation op : mList) {
             if (op instanceof Component) {
-                ((Component) op).onTouchUp(context, document, cx, cy, force);
+                ((Component) op).onTouchUp(context, document, cx, cy, dx, dy, force);
             }
             if (op instanceof TouchHandler) {
-                ((TouchHandler) op).onTouchUp(context, document, this, cx, cy);
+                ((TouchHandler) op).onTouchUp(context, document, this, cx, cy, dx, dy);
+            }
+            if (op instanceof TouchExpression) {
+                TouchExpression touchExpression = (TouchExpression) op;
+                touchExpression.updateVariables(context);
+                touchExpression.touchUp(context, cx, cy, dx, dy);
             }
         }
     }
@@ -444,6 +490,11 @@
             if (op instanceof TouchHandler) {
                 ((TouchHandler) op).onTouchCancel(context, document, this, cx, cy);
             }
+            if (op instanceof TouchExpression) {
+                TouchExpression touchExpression = (TouchExpression) op;
+                touchExpression.updateVariables(context);
+                touchExpression.touchUp(context, cx, cy, 0, 0);
+            }
         }
     }
 
@@ -461,6 +512,11 @@
             if (op instanceof TouchHandler) {
                 ((TouchHandler) op).onTouchDrag(context, document, this, cx, cy);
             }
+            if (op instanceof TouchExpression) {
+                TouchExpression touchExpression = (TouchExpression) op;
+                touchExpression.updateVariables(context);
+                touchExpression.touchDrag(context, x, y);
+            }
         }
     }
 
@@ -468,11 +524,6 @@
         value[0] += mX;
         value[1] += mY;
         if (mParent != null) {
-            if (mParent instanceof LayoutComponent) {
-                LayoutComponent parent = (LayoutComponent) mParent;
-                value[0] += parent.getMarginLeft() + parent.getPaddingLeft();
-                value[1] += parent.getMarginTop() + parent.getPaddingTop();
-            }
             mParent.getLocationInWindow(value);
         }
     }
@@ -647,6 +698,9 @@
     }
 
     public void paintingComponent(@NonNull PaintContext context) {
+        if (!mComponentValues.isEmpty()) {
+            updateComponentValues(context.getContext());
+        }
         if (mPreTranslate != null) {
             mPreTranslate.paint(context);
         }
@@ -658,11 +712,14 @@
             debugBox(this, context);
         }
         for (Operation op : mList) {
-            if (op instanceof BitmapData) {
-                ((BitmapData) op).apply(context.getContext());
+            if (op.isDirty() && op instanceof VariableSupport) {
+                ((VariableSupport) op).updateVariables(context.getContext());
+                op.markNotDirty();
             }
             if (op instanceof PaintOperation) {
                 ((PaintOperation) op).paint(context);
+            } else {
+                op.apply(context.getContext());
             }
         }
         context.restore();
@@ -672,7 +729,7 @@
     public boolean applyAnimationAsNeeded(@NonNull PaintContext context) {
         if (context.isAnimationEnabled() && mAnimateMeasure != null) {
             mAnimateMeasure.apply(context);
-            needsRepaint();
+            context.needsRepaint();
             return true;
         }
         return false;
@@ -701,7 +758,7 @@
         if (applyAnimationAsNeeded(context)) {
             return;
         }
-        if (mVisibility == Visibility.GONE) {
+        if (mVisibility == Visibility.GONE || mVisibility == Visibility.INVISIBLE) {
             return;
         }
         paintingComponent(context);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
index 476b73c..5da0663 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
@@ -25,7 +25,7 @@
 
 import java.util.List;
 
-public class ComponentEnd implements Operation {
+public class ComponentEnd extends Operation {
 
     @Override
     public void write(@NonNull WireBuffer buffer) {
@@ -49,11 +49,21 @@
         return (indent != null ? indent : "") + toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "ComponentEnd";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.COMPONENT_END;
     }
@@ -66,10 +76,21 @@
         return 1 + 4 + 4 + 4;
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new ComponentEnd());
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
index def9f78..4349b31 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
@@ -28,7 +28,7 @@
 
 import java.util.List;
 
-public class ComponentStart implements ComponentStartOperation {
+public class ComponentStart extends Operation implements ComponentStartOperation {
 
     int mType = DEFAULT;
     float mX;
@@ -157,11 +157,21 @@
         }
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "ComponentStart";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.COMPONENT_START;
     }
@@ -179,6 +189,12 @@
         return 1 + 4 + 4 + 4;
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int type = buffer.readInt();
         int componentId = buffer.readInt();
@@ -187,6 +203,11 @@
         operations.add(new ComponentStart(type, componentId, width, height));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStartOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStartOperation.java
index abf2356a..a257d46 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStartOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStartOperation.java
@@ -15,6 +15,4 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
-import com.android.internal.widget.remotecompose.core.Operation;
-
-public interface ComponentStartOperation extends Operation {}
+public interface ComponentStartOperation {}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
index d617007..9ca2f2e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
@@ -24,5 +24,13 @@
  * measured. Eg borders, background, clips, etc.
  */
 public interface DecoratorComponent {
-    void layout(@NonNull RemoteContext context, float width, float height);
+    /**
+     * Layout the decorator
+     *
+     * @param context
+     * @param component the associated component
+     * @param width horizontal dimension in pixels
+     * @param height vertical dimension in pixels
+     */
+    void layout(@NonNull RemoteContext context, Component component, float width, float height);
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
index 0041582..e25392c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
@@ -19,8 +19,12 @@
 import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.OperationInterface;
 import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.operations.BitmapData;
+import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
 import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
 import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
 import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate;
@@ -34,6 +38,7 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
 
@@ -47,17 +52,17 @@
     @Nullable protected ZIndexModifierOperation mZIndexModifier = null;
     @Nullable protected GraphicsLayerModifierOperation mGraphicsLayerModifier = null;
 
-    // Margins
-    protected float mMarginLeft = 0f;
-    protected float mMarginRight = 0f;
-    protected float mMarginTop = 0f;
-    protected float mMarginBottom = 0f;
-
     protected float mPaddingLeft = 0f;
     protected float mPaddingRight = 0f;
     protected float mPaddingTop = 0f;
     protected float mPaddingBottom = 0f;
 
+    float mScrollX = 0f;
+    float mScrollY = 0f;
+
+    @Nullable protected ScrollDelegate mHorizontalScrollDelegate = null;
+    @Nullable protected ScrollDelegate mVerticalScrollDelegate = null;
+
     @NonNull protected ComponentModifiers mComponentModifiers = new ComponentModifiers();
 
     @NonNull
@@ -76,22 +81,6 @@
         super(parent, componentId, animationId, x, y, width, height);
     }
 
-    public float getMarginLeft() {
-        return mMarginLeft;
-    }
-
-    public float getMarginRight() {
-        return mMarginRight;
-    }
-
-    public float getMarginTop() {
-        return mMarginTop;
-    }
-
-    public float getMarginBottom() {
-        return mMarginBottom;
-    }
-
     public float getPaddingLeft() {
         return mPaddingLeft;
     }
@@ -131,10 +120,10 @@
     // Should be removed after ImageLayout is in
     private static final boolean USE_IMAGE_TEMP_FIX = true;
 
+    @Override
     public void inflate() {
         ArrayList<TextData> data = new ArrayList<>();
-        ArrayList<TouchExpression> touchExpressions = new ArrayList<>();
-        ArrayList<PaintData> paintData = new ArrayList<>();
+        ArrayList<Operation> supportedOperations = new ArrayList<>();
 
         for (Operation op : mList) {
             if (op instanceof LayoutComponentContent) {
@@ -165,6 +154,7 @@
                         if (!canvasContent.mList.isEmpty()) {
                             mContent.mList.clear();
                             mChildrenComponents.add(canvasContent);
+                            canvasContent.inflate();
                         }
                     } else {
                         content.getData(data);
@@ -176,13 +166,19 @@
                 if (op instanceof ComponentVisibilityOperation) {
                     ((ComponentVisibilityOperation) op).setParent(this);
                 }
+                if (op instanceof ScrollModifierOperation) {
+                    ((ScrollModifierOperation) op).inflate(this);
+                }
                 mComponentModifiers.add((ModifierOperation) op);
             } else if (op instanceof TextData) {
                 data.add((TextData) op);
-            } else if (op instanceof TouchExpression) {
-                touchExpressions.add((TouchExpression) op);
-            } else if (op instanceof PaintData) {
-                paintData.add((PaintData) op);
+            } else if (op instanceof TouchExpression
+                    || (op instanceof PaintData)
+                    || (op instanceof FloatExpression)) {
+                supportedOperations.add(op);
+                if (op instanceof TouchExpression) {
+                    ((TouchExpression) op).setComponent(this);
+                }
             } else {
                 // nothing
             }
@@ -190,8 +186,7 @@
 
         mList.clear();
         mList.addAll(data);
-        mList.addAll(touchExpressions);
-        mList.addAll(paintData);
+        mList.addAll(supportedOperations);
         mList.add(mComponentModifiers);
         for (Component c : mChildrenComponents) {
             c.mParent = this;
@@ -203,18 +198,12 @@
 
         mX = 0f;
         mY = 0f;
-        mMarginLeft = 0f;
-        mMarginTop = 0f;
-        mMarginRight = 0f;
-        mMarginBottom = 0f;
         mPaddingLeft = 0f;
         mPaddingTop = 0f;
         mPaddingRight = 0f;
         mPaddingBottom = 0f;
 
-        boolean applyHorizontalMargin = true;
-        boolean applyVerticalMargin = true;
-        for (Operation op : mComponentModifiers.getList()) {
+        for (OperationInterface op : mComponentModifiers.getList()) {
             if (op instanceof PaddingModifierOperation) {
                 // We are accumulating padding modifiers to compute the margin
                 // until we hit a dimension; the computed padding for the
@@ -223,32 +212,26 @@
                 float right = ((PaddingModifierOperation) op).getRight();
                 float top = ((PaddingModifierOperation) op).getTop();
                 float bottom = ((PaddingModifierOperation) op).getBottom();
-                if (applyHorizontalMargin) {
-                    mMarginLeft += left;
-                    mMarginRight += right;
-                }
-                if (applyVerticalMargin) {
-                    mMarginTop += top;
-                    mMarginBottom += bottom;
-                }
                 mPaddingLeft += left;
                 mPaddingTop += top;
                 mPaddingRight += right;
                 mPaddingBottom += bottom;
-            }
-            if (op instanceof WidthModifierOperation && mWidthModifier == null) {
+            } else if (op instanceof WidthModifierOperation && mWidthModifier == null) {
                 mWidthModifier = (WidthModifierOperation) op;
-                applyHorizontalMargin = false;
-            }
-            if (op instanceof HeightModifierOperation && mHeightModifier == null) {
+            } else if (op instanceof HeightModifierOperation && mHeightModifier == null) {
                 mHeightModifier = (HeightModifierOperation) op;
-                applyVerticalMargin = false;
-            }
-            if (op instanceof ZIndexModifierOperation) {
+            } else if (op instanceof ZIndexModifierOperation) {
                 mZIndexModifier = (ZIndexModifierOperation) op;
-            }
-            if (op instanceof GraphicsLayerModifierOperation) {
+            } else if (op instanceof GraphicsLayerModifierOperation) {
                 mGraphicsLayerModifier = (GraphicsLayerModifierOperation) op;
+            } else if (op instanceof ScrollDelegate) {
+                ScrollDelegate scrollDelegate = (ScrollDelegate) op;
+                if (scrollDelegate.handlesHorizontalScroll()) {
+                    mHorizontalScrollDelegate = scrollDelegate;
+                }
+                if (scrollDelegate.handlesVerticalScroll()) {
+                    mVerticalScrollDelegate = scrollDelegate;
+                }
             }
         }
         if (mWidthModifier == null) {
@@ -257,8 +240,8 @@
         if (mHeightModifier == null) {
             mHeightModifier = new HeightModifierOperation(DimensionModifierOperation.Type.WRAP);
         }
-        setWidth(computeModifierDefinedWidth());
-        setHeight(computeModifierDefinedHeight());
+        setWidth(computeModifierDefinedWidth(null));
+        setHeight(computeModifierDefinedHeight(null));
     }
 
     @NonNull
@@ -268,13 +251,36 @@
     }
 
     @Override
+    public void getLocationInWindow(@NonNull float[] value) {
+        value[0] += mX + mPaddingLeft;
+        value[1] += mY + mPaddingTop;
+        if (mParent != null) {
+            mParent.getLocationInWindow(value);
+        }
+    }
+
+    @Override
     public float getScrollX() {
-        return mComponentModifiers.getScrollX();
+        if (mHorizontalScrollDelegate != null) {
+            return mHorizontalScrollDelegate.getScrollX(mScrollX);
+        }
+        return mScrollX;
+    }
+
+    public void setScrollX(float value) {
+        mScrollX = value;
     }
 
     @Override
     public float getScrollY() {
-        return mComponentModifiers.getScrollY();
+        if (mVerticalScrollDelegate != null) {
+            return mVerticalScrollDelegate.getScrollY(mScrollY);
+        }
+        return mScrollY;
+    }
+
+    public void setScrollY(float value) {
+        mScrollY = value;
     }
 
     @Override
@@ -319,10 +325,18 @@
             ArrayList<Component> sorted = new ArrayList<Component>(mChildrenComponents);
             sorted.sort((a, b) -> (int) (a.getZIndex() - b.getZIndex()));
             for (Component child : sorted) {
+                if (child.isDirty() && child instanceof VariableSupport) {
+                    child.updateVariables(context.getContext());
+                    child.markNotDirty();
+                }
                 child.paint(context);
             }
         } else {
             for (Component child : mChildrenComponents) {
+                if (child.isDirty() && child instanceof VariableSupport) {
+                    child.updateVariables(context.getContext());
+                    child.markNotDirty();
+                }
                 child.paint(context);
             }
         }
@@ -335,11 +349,15 @@
     }
 
     /** Traverse the modifiers to compute indicated dimension */
-    public float computeModifierDefinedWidth() {
+    public float computeModifierDefinedWidth(@Nullable RemoteContext context) {
         float s = 0f;
         float e = 0f;
         float w = 0f;
-        for (Operation c : mComponentModifiers.getList()) {
+        for (OperationInterface c : mComponentModifiers.getList()) {
+            if (context != null && c.isDirty() && c instanceof VariableSupport) {
+                ((VariableSupport) c).updateVariables(context);
+                c.markNotDirty();
+            }
             if (c instanceof WidthModifierOperation) {
                 WidthModifierOperation o = (WidthModifierOperation) c;
                 if (o.getType() == DimensionModifierOperation.Type.EXACT
@@ -366,7 +384,7 @@
     public float computeModifierDefinedPaddingWidth(@NonNull float[] padding) {
         float s = 0f;
         float e = 0f;
-        for (Operation c : mComponentModifiers.getList()) {
+        for (OperationInterface c : mComponentModifiers.getList()) {
             if (c instanceof PaddingModifierOperation) {
                 PaddingModifierOperation pop = (PaddingModifierOperation) c;
                 s += pop.getLeft();
@@ -379,11 +397,15 @@
     }
 
     /** Traverse the modifiers to compute indicated dimension */
-    public float computeModifierDefinedHeight() {
+    public float computeModifierDefinedHeight(@Nullable RemoteContext context) {
         float t = 0f;
         float b = 0f;
         float h = 0f;
-        for (Operation c : mComponentModifiers.getList()) {
+        for (OperationInterface c : mComponentModifiers.getList()) {
+            if (context != null && c.isDirty() && c instanceof VariableSupport) {
+                ((VariableSupport) c).updateVariables(context);
+                c.markNotDirty();
+            }
             if (c instanceof HeightModifierOperation) {
                 HeightModifierOperation o = (HeightModifierOperation) c;
                 if (o.getType() == DimensionModifierOperation.Type.EXACT
@@ -410,7 +432,7 @@
     public float computeModifierDefinedPaddingHeight(@NonNull float[] padding) {
         float t = 0f;
         float b = 0f;
-        for (Operation c : mComponentModifiers.getList()) {
+        for (OperationInterface c : mComponentModifiers.getList()) {
             if (c instanceof PaddingModifierOperation) {
                 PaddingModifierOperation pop = (PaddingModifierOperation) c;
                 t += pop.getTop();
@@ -423,6 +445,11 @@
     }
 
     @NonNull
+    public ComponentModifiers getComponentModifiers() {
+        return mComponentModifiers;
+    }
+
+    @NonNull
     public ArrayList<Component> getChildrenComponents() {
         return mChildrenComponents;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
index 7eea885..9bfbe6a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
@@ -41,11 +41,21 @@
         super(parent, componentId, animationId, x, y, width, height);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "LayoutContent";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.LAYOUT_CONTENT;
     }
@@ -61,11 +71,22 @@
         buffer.writeInt(componentId);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         operations.add(new LayoutComponentContent(componentId, 0, 0, 0, 0, null, -1));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .field(INT, "COMPONENT_ID", "unique id for this component")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java
index df960e4..505656e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java
@@ -32,8 +32,8 @@
         implements ModifierOperation, DecoratorComponent {
 
     String mOperationName;
-    float mWidth = 0;
-    float mHeight = 0;
+    protected float mWidth = 0;
+    protected float mHeight = 0;
 
     private final float[] mLocationInWindow = new float[2];
 
@@ -71,7 +71,7 @@
     public void paint(PaintContext context) {}
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {
+    public void layout(RemoteContext context, Component component, float width, float height) {
         mWidth = width;
         mHeight = height;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java
index 71de285..3d389e5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java
@@ -25,7 +25,7 @@
 
 import java.util.List;
 
-public class LoopEnd implements Operation {
+public class LoopEnd extends Operation {
 
     @Override
     public void write(@NonNull WireBuffer buffer) {
@@ -49,11 +49,21 @@
         return (indent != null ? indent : "") + toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "LoopEnd";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.LOOP_END;
     }
@@ -62,10 +72,21 @@
         buffer.start(id());
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new LoopEnd());
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Operations", id(), name()).description("End tag for loops");
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
index d88382d..1b85681 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
@@ -21,31 +21,57 @@
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.operations.Utils;
 
 import java.util.ArrayList;
 import java.util.List;
 
 /** Represents a loop of operations */
-public class LoopOperation extends PaintOperation {
+public class LoopOperation extends PaintOperation implements VariableSupport {
     private static final int OP_CODE = Operations.LOOP_START;
 
     @NonNull public ArrayList<Operation> mList = new ArrayList<>();
 
     int mIndexVariableId;
-    float mUntil = 12;
-    float mFrom = 0;
-    float mStep = 1;
+    float mUntil;
+    float mFrom;
+    float mStep;
+    float mUntilOut;
+    float mFromOut;
+    float mStepOut;
 
     public LoopOperation(int count, int indexId) {
         mUntil = count;
         mIndexVariableId = indexId;
     }
 
-    public LoopOperation(float count, float from, float step, int indexId) {
-        mUntil = count;
+    @Override
+    public void registerListening(RemoteContext context) {
+        if (Float.isNaN(mUntil)) {
+            context.listensTo(Utils.idFromNan(mUntil), this);
+        }
+        if (Float.isNaN(mFrom)) {
+            context.listensTo(Utils.idFromNan(mFrom), this);
+        }
+        if (Float.isNaN(mStep)) {
+            context.listensTo(Utils.idFromNan(mStep), this);
+        }
+    }
+
+    @Override
+    public void updateVariables(RemoteContext context) {
+        mUntilOut = Float.isNaN(mUntil) ? context.getFloat(Utils.idFromNan(mUntil)) : mUntil;
+        mFromOut = Float.isNaN(mFrom) ? context.getFloat(Utils.idFromNan(mFrom)) : mFrom;
+        mStepOut = Float.isNaN(mStep) ? context.getFloat(Utils.idFromNan(mStep)) : mStep;
+    }
+
+    public LoopOperation(int indexId, float from, float step, float until) {
+        mUntil = until;
         mFrom = from;
         mStep = step;
         mIndexVariableId = indexId;
@@ -58,13 +84,19 @@
 
     @Override
     public void write(@NonNull WireBuffer buffer) {
-        apply(buffer, mUntil, mFrom, mStep, mIndexVariableId);
+        apply(buffer, mIndexVariableId, mFrom, mStep, mUntil);
     }
 
     @NonNull
     @Override
     public String toString() {
-        return "LoopOperation";
+        StringBuilder builder = new StringBuilder("LoopOperation\n");
+        for (Operation operation : mList) {
+            builder.append("  ");
+            builder.append(operation);
+            builder.append("\n");
+        }
+        return builder.toString();
     }
 
     @NonNull
@@ -76,16 +108,16 @@
     @Override
     public void paint(@NonNull PaintContext context) {
         if (mIndexVariableId == 0) {
-            for (float i = mFrom; i < mUntil; i += mStep) {
+            for (float i = mFromOut; i < mUntilOut; i += mStepOut) {
                 for (Operation op : mList) {
                     op.apply(context.getContext());
                 }
             }
         } else {
-            for (float i = mFrom; i < mUntil; i += mStep) {
+            for (float i = mFromOut; i < mUntilOut; i += mStepOut) {
                 context.getContext().loadFloat(mIndexVariableId, i);
                 for (Operation op : mList) {
-                    if (op instanceof VariableSupport) {
+                    if (op instanceof VariableSupport && op.isDirty()) {
                         ((VariableSupport) op).updateVariables(context.getContext());
                     }
                     op.apply(context.getContext());
@@ -94,30 +126,50 @@
         }
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "Loop";
     }
 
     public static void apply(
-            @NonNull WireBuffer buffer, float count, float from, float step, int indexId) {
+            @NonNull WireBuffer buffer, int indexId, float from, float step, float until) {
         buffer.start(OP_CODE);
-        buffer.writeFloat(count);
+        buffer.writeInt(indexId);
         buffer.writeFloat(from);
         buffer.writeFloat(step);
-        buffer.writeInt(indexId);
+        buffer.writeFloat(until);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
-        float count = buffer.readFloat();
+        int indexId = buffer.readInt();
         float from = buffer.readFloat();
         float step = buffer.readFloat();
-        int indexId = buffer.readInt();
-        operations.add(new LoopOperation(count, from, step, indexId));
+        float until = buffer.readFloat();
+        operations.add(new LoopOperation(indexId, from, step, until));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Operations", OP_CODE, name())
-                .description("Loop. This operation execute" + " a list of action in a loop");
+                .description("Loop. This operation execute" + " a list of action in a loop")
+                .field(DocumentedOperation.INT, "id", "if not 0 write value")
+                .field(DocumentedOperation.FLOAT, "from", "values starts at")
+                .field(DocumentedOperation.FLOAT, "step", "value step")
+                .field(DocumentedOperation.FLOAT, "until", "stops less than or equal");
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
index ca79003..12a673d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
@@ -25,7 +25,7 @@
 
 import java.util.List;
 
-public class OperationsListEnd implements Operation {
+public class OperationsListEnd extends Operation {
 
     @Override
     public void write(@NonNull WireBuffer buffer) {
@@ -49,11 +49,21 @@
         return (indent != null ? indent : "") + toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "ListEnd";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.OPERATIONS_LIST_END;
     }
@@ -62,10 +72,21 @@
         buffer.start(id());
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new OperationsListEnd());
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description("End tag for list of operations.");
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
index 85c7153..11c0f3f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
@@ -192,11 +192,21 @@
         }
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "RootLayout";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.LAYOUT_ROOT;
     }
@@ -206,11 +216,22 @@
         buffer.writeInt(componentId);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         operations.add(new RootLayoutComponent(componentId, 0, 0, 0, 0, null, -1));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .field(INT, "COMPONENT_ID", "unique id for this component")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ScrollDelegate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ScrollDelegate.java
new file mode 100644
index 0000000..7ef9766
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ScrollDelegate.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 com.android.internal.widget.remotecompose.core.operations.layout;
+
+/**
+ * Represent scroll delegates components.
+ *
+ * <p>Components have scroll X & Y properties. We can inject a scroll delegate as a modifier (e.g. a
+ * scrollView, a marquee...) to control the value of those properties.
+ */
+public interface ScrollDelegate {
+
+    /**
+     * Returns the horizontal scroll value
+     *
+     * @param currentValue the current value
+     * @return the value set by the delegate
+     */
+    float getScrollX(float currentValue);
+
+    /**
+     * Returns the vertical scroll value
+     *
+     * @param currentValue the current value
+     * @return the value set by the delegate
+     */
+    float getScrollY(float currentValue);
+
+    /**
+     * Returns true if the delegate can handle horizontal scroll
+     *
+     * @return true if the delegate handles horizontal scrolling
+     */
+    boolean handlesHorizontalScroll();
+
+    /**
+     * Returns true if the delegate can handle vertical scroll
+     *
+     * @return true if the delegate handles vertical scrolling
+     */
+    boolean handlesVerticalScroll();
+
+    /** Reset the delegate (e.g. the content of the component has changed) */
+    void reset();
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
index 0316f96..4977a15 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -60,7 +62,13 @@
 
     @Override
     public void onTouchUp(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            float dx,
+            float dy) {
         // nothing
     }
 
@@ -76,6 +84,12 @@
         // nothing
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     public static String name() {
         return "TouchCancelModifier";
     }
@@ -84,6 +98,12 @@
         buffer.start(OP_CODE);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(WireBuffer buffer, List<Operation> operations) {
         operations.add(new TouchCancelModifierOperation());
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
index d98911f..8c51f2e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -62,7 +64,13 @@
 
     @Override
     public void onTouchUp(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            float dx,
+            float dy) {
         // nothing
     }
 
@@ -78,6 +86,12 @@
         // nothing
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     public static String name() {
         return "TouchModifier";
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java
index ac9dd90..607060e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java
@@ -41,9 +41,17 @@
      * @param component the component on which the touch has been received
      * @param x the x position of the click in document coordinates
      * @param y the y position of the click in document coordinates
+     * @param dx
+     * @param dy
      */
     void onTouchUp(
-            RemoteContext context, CoreDocument document, Component component, float x, float y);
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            float dx,
+            float dy);
 
     /**
      * callback for a touch move event
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
index f6cb375..a12c356 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -60,7 +62,13 @@
 
     @Override
     public void onTouchUp(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            float dx,
+            float dy) {
         applyActions(context, document, component, x, y, true);
     }
 
@@ -76,6 +84,12 @@
         // nothing
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     public static String name() {
         return "TouchUpModifier";
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
index b343099..2af3c73 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
@@ -120,7 +120,7 @@
                 h -= pop.getTop() + pop.getBottom();
             }
             if (op instanceof DecoratorComponent) {
-                ((DecoratorComponent) op).layout(context.getContext(), w, h);
+                ((DecoratorComponent) op).layout(context.getContext(), mComponent, w, h);
             }
         }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
index 6fb7059..6dff4a8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
@@ -29,7 +29,7 @@
 import java.util.List;
 
 /** Basic component animation spec */
-public class AnimationSpec implements Operation {
+public class AnimationSpec extends Operation {
     int mAnimationId = -1;
     int mMotionDuration = 300;
     int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
@@ -137,11 +137,21 @@
         return (indent != null ? indent : "") + toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "AnimationSpec";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.ANIMATION_SPEC;
     }
@@ -193,6 +203,12 @@
         buffer.writeInt(animationToInt(exitAnimation));
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int animationId = buffer.readInt();
         int motionDuration = buffer.readInt();
@@ -213,6 +229,11 @@
         operations.add(op);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description("define the animation")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
index 47a9421..8076cb1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
@@ -108,6 +108,8 @@
             @NonNull PaintContext context,
             float maxWidth,
             float maxHeight,
+            boolean horizontalWrap,
+            boolean verticalWrap,
             @NonNull MeasurePass measure,
             @NonNull Size size) {
         for (Component c : mChildrenComponents) {
@@ -117,8 +119,9 @@
             size.setHeight(Math.max(size.getHeight(), m.getH()));
         }
         // add padding
-        size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth()));
-        size.setHeight(Math.max(size.getHeight(), computeModifierDefinedHeight()));
+        size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth(context.getContext())));
+        size.setHeight(
+                Math.max(size.getHeight(), computeModifierDefinedHeight(context.getContext())));
     }
 
     @Override
@@ -170,11 +173,21 @@
         }
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "BoxLayout";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.LAYOUT_BOX;
     }
@@ -192,6 +205,12 @@
         buffer.writeInt(verticalPositioning);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
@@ -206,6 +225,11 @@
                         verticalPositioning));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
index 476b1a66..0091a47 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
@@ -72,11 +72,21 @@
         return "CANVAS";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "CanvasLayout";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.LAYOUT_CANVAS;
     }
@@ -87,12 +97,23 @@
         buffer.writeInt(animationId);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
         operations.add(new CanvasLayout(null, componentId, animationId));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description("Canvas implementation. Encapsulate draw operations.\n\n")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
index 68e18c6..249e84a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
@@ -24,6 +24,7 @@
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
@@ -125,17 +126,21 @@
             @NonNull PaintContext context,
             float maxWidth,
             float maxHeight,
+            boolean horizontalWrap,
+            boolean verticalWrap,
             @NonNull MeasurePass measure,
             @NonNull Size size) {
         DebugLog.s(() -> "COMPUTE WRAP SIZE in " + this + " (" + mComponentId + ")");
         int visibleChildrens = 0;
+        float currentMaxHeight = maxHeight;
         for (Component c : mChildrenComponents) {
-            c.measure(context, 0f, maxWidth, 0f, maxHeight, measure);
+            c.measure(context, 0f, maxWidth, 0f, currentMaxHeight, measure);
             ComponentMeasure m = measure.get(c);
             if (m.getVisibility() != Visibility.GONE) {
                 size.setWidth(Math.max(size.getWidth(), m.getW()));
                 size.setHeight(size.getHeight() + m.getH());
                 visibleChildrens++;
+                currentMaxHeight -= m.getH();
             }
         }
         if (!mChildrenComponents.isEmpty()) {
@@ -165,11 +170,11 @@
     }
 
     @Override
-    public float intrinsicHeight() {
-        float height = computeModifierDefinedHeight();
+    public float intrinsicHeight(@NonNull RemoteContext context) {
+        float height = computeModifierDefinedHeight(context);
         float componentHeights = 0f;
         for (Component c : mChildrenComponents) {
-            componentHeights += c.intrinsicHeight();
+            componentHeights += c.intrinsicHeight(context);
         }
         return Math.max(height, componentHeights);
     }
@@ -337,11 +342,21 @@
         DebugLog.e();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "ColumnLayout";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.LAYOUT_COLUMN;
     }
@@ -361,6 +376,12 @@
         buffer.writeFloat(spacedBy);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
@@ -377,6 +398,11 @@
                         spacedBy));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
index 3b5aaf3..a5edaa8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
@@ -53,25 +53,27 @@
             @NonNull PaintContext context,
             float maxWidth,
             float maxHeight,
+            boolean horizontalWrap,
+            boolean verticalWrap,
             @NonNull MeasurePass measure,
             @NonNull Size size) {
         // nothing here
     }
 
     @Override
-    public float intrinsicHeight() {
-        float height = computeModifierDefinedHeight();
+    public float intrinsicHeight(@Nullable RemoteContext context) {
+        float height = computeModifierDefinedHeight(context);
         for (Component c : mChildrenComponents) {
-            height = Math.max(c.intrinsicHeight(), height);
+            height = Math.max(c.intrinsicHeight(context), height);
         }
         return height;
     }
 
     @Override
-    public float intrinsicWidth() {
-        float width = computeModifierDefinedWidth();
+    public float intrinsicWidth(@Nullable RemoteContext context) {
+        float width = computeModifierDefinedWidth(context);
         for (Component c : mChildrenComponents) {
-            width = Math.max(c.intrinsicWidth(), width);
+            width = Math.max(c.intrinsicWidth(context), width);
         }
         return width;
     }
@@ -129,42 +131,70 @@
             float maxHeight,
             @NonNull MeasurePass measure) {
         boolean hasWrap = true;
-        float measuredWidth =
-                Math.min(maxWidth, computeModifierDefinedWidth() - mMarginLeft - mMarginRight);
+
+        float measuredWidth = Math.min(maxWidth, computeModifierDefinedWidth(context.getContext()));
         float measuredHeight =
-                Math.min(maxHeight, computeModifierDefinedHeight() - mMarginTop - mMarginBottom);
-        float insetMaxWidth = maxWidth - mMarginLeft - mMarginRight;
-        float insetMaxHeight = maxHeight - mMarginTop - mMarginBottom;
+                Math.min(maxHeight, computeModifierDefinedHeight(context.getContext()));
+        float insetMaxWidth = maxWidth - mPaddingLeft - mPaddingRight;
+        float insetMaxHeight = maxHeight - mPaddingTop - mPaddingBottom;
+
         if (mWidthModifier.isIntrinsicMin()) {
-            maxWidth = intrinsicWidth();
+            maxWidth = intrinsicWidth(context.getContext());
         }
         if (mHeightModifier.isIntrinsicMin()) {
-            maxHeight = intrinsicHeight();
+            maxHeight = intrinsicHeight(context.getContext());
         }
-        if (mWidthModifier.isWrap() || mHeightModifier.isWrap()) { // TODO: potential npe -- bbade@
+
+        boolean hasHorizontalWrap = mWidthModifier.isWrap();
+        boolean hasVerticalWrap = mHeightModifier.isWrap();
+        if (hasHorizontalWrap || hasVerticalWrap) { // TODO: potential npe -- bbade@
             mCachedWrapSize.setWidth(0f);
             mCachedWrapSize.setHeight(0f);
-            computeWrapSize(context, maxWidth, maxHeight, measure, mCachedWrapSize);
+            float wrapMaxWidth = insetMaxWidth;
+            float wrapMaxHeight = insetMaxHeight;
+            if (hasHorizontalWrap) {
+                wrapMaxWidth = insetMaxWidth - mPaddingLeft - mPaddingRight;
+            }
+            if (hasVerticalWrap) {
+                wrapMaxHeight = insetMaxHeight - mPaddingTop - mPaddingBottom;
+            }
+            computeWrapSize(
+                    context,
+                    wrapMaxWidth,
+                    wrapMaxHeight,
+                    mWidthModifier.isWrap(),
+                    mHeightModifier.isWrap(),
+                    measure,
+                    mCachedWrapSize);
             measuredWidth = mCachedWrapSize.getWidth();
+            if (hasHorizontalWrap) {
+                measuredWidth += mPaddingLeft + mPaddingRight;
+            }
             measuredHeight = mCachedWrapSize.getHeight();
+            if (hasVerticalWrap) {
+                measuredHeight += mPaddingTop + mPaddingBottom;
+            }
         } else {
             hasWrap = false;
         }
+
         if (isInHorizontalFill()) {
-            measuredWidth = insetMaxWidth;
+            measuredWidth = maxWidth;
         } else if (mWidthModifier.hasWeight()) {
-            measuredWidth = Math.max(measuredWidth, computeModifierDefinedWidth());
+            measuredWidth =
+                    Math.max(measuredWidth, computeModifierDefinedWidth(context.getContext()));
         } else {
             measuredWidth = Math.max(measuredWidth, minWidth);
-            measuredWidth = Math.min(measuredWidth, insetMaxWidth);
+            measuredWidth = Math.min(measuredWidth, maxWidth);
         }
         if (isInVerticalFill()) { // todo: potential npe -- bbade@
-            measuredHeight = insetMaxHeight;
+            measuredHeight = maxHeight;
         } else if (mHeightModifier.hasWeight()) {
-            measuredHeight = Math.max(measuredHeight, computeModifierDefinedHeight());
+            measuredHeight =
+                    Math.max(measuredHeight, computeModifierDefinedHeight(context.getContext()));
         } else {
             measuredHeight = Math.max(measuredHeight, minHeight);
-            measuredHeight = Math.min(measuredHeight, insetMaxHeight);
+            measuredHeight = Math.min(measuredHeight, maxHeight);
         }
         if (minWidth == maxWidth) {
             measuredWidth = maxWidth;
@@ -172,25 +202,34 @@
         if (minHeight == maxHeight) {
             measuredHeight = maxHeight;
         }
-        measuredWidth = Math.min(measuredWidth, insetMaxWidth);
-        measuredHeight = Math.min(measuredHeight, insetMaxHeight);
+
         if (!hasWrap) {
             if (hasHorizontalScroll()) {
                 mCachedWrapSize.setWidth(0f);
                 mCachedWrapSize.setHeight(0f);
-                computeWrapSize(context, Float.MAX_VALUE, maxHeight, measure, mCachedWrapSize);
+                computeWrapSize(
+                        context,
+                        Float.MAX_VALUE,
+                        maxHeight,
+                        false,
+                        false,
+                        measure,
+                        mCachedWrapSize);
                 float w = mCachedWrapSize.getWidth();
                 computeSize(context, 0f, w, 0, measuredHeight, measure);
                 mComponentModifiers.setHorizontalScrollDimension(measuredWidth, w);
             } else if (hasVerticalScroll()) {
                 mCachedWrapSize.setWidth(0f);
                 mCachedWrapSize.setHeight(0f);
-                computeWrapSize(context, maxWidth, Float.MAX_VALUE, measure, mCachedWrapSize);
+                computeWrapSize(
+                        context, maxWidth, Float.MAX_VALUE, false, false, measure, mCachedWrapSize);
                 float h = mCachedWrapSize.getHeight();
                 computeSize(context, 0f, measuredWidth, 0, h, measure);
                 mComponentModifiers.setVerticalScrollDimension(measuredHeight, h);
             } else {
-                computeSize(context, 0f, measuredWidth, 0f, measuredHeight, measure);
+                float maxChildWidth = measuredWidth - mPaddingLeft - mPaddingRight;
+                float maxChildHeight = measuredHeight - mPaddingTop - mPaddingBottom;
+                computeSize(context, 0f, maxChildWidth, 0f, maxChildHeight, measure);
             }
         }
 
@@ -202,9 +241,6 @@
             cm.setH(measuredHeight);
         }
 
-        measuredWidth += mMarginLeft + mMarginRight;
-        measuredHeight += mMarginTop + mMarginBottom;
-
         ComponentMeasure m = measure.get(this);
         m.setW(measuredWidth);
         m.setH(measuredHeight);
@@ -227,7 +263,7 @@
         super.layout(context, measure);
         ComponentMeasure self = measure.get(this);
 
-        mComponentModifiers.layout(context, self.getW(), self.getH());
+        mComponentModifiers.layout(context, this, self.getW(), self.getH());
         for (Component c : mChildrenComponents) {
             c.layout(context, measure);
         }
@@ -244,7 +280,7 @@
         super.layout(context, measure);
         ComponentMeasure self = measure.get(this);
 
-        mComponentModifiers.layout(context, self.getW(), self.getH());
+        mComponentModifiers.layout(context, this, self.getW(), self.getH());
         this.mNeedsMeasure = false;
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
index 0ce634f..37b9a68 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
@@ -24,6 +24,7 @@
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
@@ -123,21 +124,25 @@
             @NonNull PaintContext context,
             float maxWidth,
             float maxHeight,
+            boolean horizontalWrap,
+            boolean verticalWrap,
             @NonNull MeasurePass measure,
             @NonNull Size size) {
         DebugLog.s(() -> "COMPUTE WRAP SIZE in " + this + " (" + mComponentId + ")");
-        //        int visibleChildrens = 0;
+        int visibleChildrens = 0;
+        float currentMaxWidth = maxWidth;
         for (Component c : mChildrenComponents) {
-            c.measure(context, 0f, maxWidth, 0f, maxHeight, measure);
+            c.measure(context, 0f, currentMaxWidth, 0f, maxHeight, measure);
             ComponentMeasure m = measure.get(c);
             if (m.getVisibility() != Visibility.GONE) {
                 size.setWidth(size.getWidth() + m.getW());
                 size.setHeight(Math.max(size.getHeight(), m.getH()));
-                //                visibleChildrens++;
+                visibleChildrens++;
+                currentMaxWidth -= m.getW();
             }
         }
         if (!mChildrenComponents.isEmpty()) {
-            size.setWidth(size.getWidth() + (mSpacedBy * (mChildrenComponents.size() - 1)));
+            size.setWidth(size.getWidth() + (mSpacedBy * (visibleChildrens - 1)));
         }
         DebugLog.e();
     }
@@ -163,11 +168,11 @@
     }
 
     @Override
-    public float intrinsicWidth() {
-        float width = computeModifierDefinedWidth();
+    public float intrinsicWidth(@Nullable RemoteContext context) {
+        float width = computeModifierDefinedWidth(context);
         float componentWidths = 0f;
         for (Component c : mChildrenComponents) {
-            componentWidths += c.intrinsicWidth();
+            componentWidths += c.intrinsicWidth(context);
         }
         return Math.max(width, componentWidths);
     }
@@ -340,11 +345,21 @@
         DebugLog.e();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "RowLayout";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.LAYOUT_ROW;
     }
@@ -364,6 +379,12 @@
         buffer.writeFloat(spacedBy);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
@@ -380,6 +401,11 @@
                         spacedBy));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
index 73a104b..61a3ec9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
@@ -159,10 +159,13 @@
             @NonNull PaintContext context,
             float maxWidth,
             float maxHeight,
+            boolean horizontalWrap,
+            boolean verticalWrap,
             @NonNull MeasurePass measure,
             @NonNull Size size) {
         LayoutManager layout = getLayout(currentLayoutIndex);
-        layout.computeWrapSize(context, maxWidth, maxHeight, measure, size);
+        layout.computeWrapSize(
+                context, maxWidth, maxHeight, horizontalWrap, verticalWrap, measure, size);
     }
 
     @Override
@@ -442,11 +445,7 @@
                     int id = c.getPaintId();
                     for (int i = 0; i < idIndex; i++) {
                         if (cacheListElementsId[i] == id) {
-                            context.translate(
-                                    previousLayout.getMarginLeft(), previousLayout.getMarginTop());
                             c.paint(context);
-                            context.translate(
-                                    -currentLayout.getMarginLeft(), -currentLayout.getMarginTop());
                             break;
                         }
                     }
@@ -472,16 +471,10 @@
                     // and fade in the new one
                     Component previousComponent = stateComponents[previousLayoutIndex];
                     if (previousComponent != null && component != previousComponent) {
-                        context.translate(
-                                currentLayout.getMarginLeft(), currentLayout.getMarginTop());
                         previousComponent.paint(context);
-                        context.translate(
-                                -currentLayout.getMarginLeft(), -currentLayout.getMarginTop());
                     }
                 }
-                context.translate(currentLayout.getMarginLeft(), currentLayout.getMarginTop());
                 component.paint(context);
-                context.translate(-currentLayout.getMarginLeft(), -currentLayout.getMarginTop());
             } else if (op instanceof PaintOperation) {
                 ((PaintOperation) op).paint(context);
             }
@@ -563,6 +556,12 @@
         buffer.writeInt(indexId);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
index a527e5a..910205e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
@@ -34,11 +34,13 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
 
 import java.util.List;
 
 /** Text component, referencing a text id */
-public class TextLayout extends LayoutManager implements ComponentStartOperation, VariableSupport {
+public class TextLayout extends LayoutManager
+        implements ComponentStartOperation, VariableSupport, AccessibleComponent {
 
     private static final boolean DEBUG = false;
     private int mTextId = -1;
@@ -52,11 +54,17 @@
     private int mType = -1;
     private float mTextX;
     private float mTextY;
-    private float mTextW;
-    private float mTextH;
+    private float mTextW = -1;
+    private float mTextH = -1;
 
     @Nullable private String mCachedString = "";
 
+    @Nullable
+    @Override
+    public Integer getTextId() {
+        return mTextId;
+    }
+
     @Override
     public void registerListening(@NonNull RemoteContext context) {
         if (mTextId != -1) {
@@ -66,7 +74,11 @@
 
     @Override
     public void updateVariables(@NonNull RemoteContext context) {
-        mCachedString = context.getText(mTextId);
+        String cachedString = context.getText(mTextId);
+        if (cachedString != null && cachedString.equalsIgnoreCase(mCachedString)) {
+            return;
+        }
+        mCachedString = cachedString;
         if (mType == -1) {
             if (mFontFamilyId != -1) {
                 String fontFamily = context.getText(mFontFamilyId);
@@ -86,8 +98,16 @@
                 mType = 0;
             }
         }
-        mNeedsMeasure = true;
-        needsRepaint();
+        mTextW = -1;
+        mTextH = -1;
+
+        if (mHorizontalScrollDelegate != null) {
+            mHorizontalScrollDelegate.reset();
+        }
+        if (mVerticalScrollDelegate != null) {
+            mVerticalScrollDelegate.reset();
+        }
+        invalidateMeasure();
     }
 
     public TextLayout(
@@ -168,7 +188,19 @@
             return;
         }
         int length = mCachedString.length();
-        context.drawTextRun(mTextId, 0, length, 0, 0, mTextX, mTextY, false);
+        if (mTextW > mWidth) {
+            context.save();
+            context.clipRect(
+                    mPaddingLeft,
+                    mPaddingTop,
+                    mWidth - mPaddingLeft - mPaddingRight,
+                    mHeight - mPaddingTop - mPaddingBottom);
+            context.translate(getScrollX(), getScrollY());
+            context.drawTextRun(mTextId, 0, length, 0, 0, mTextX, mTextY, false);
+            context.restore();
+        } else {
+            context.drawTextRun(mTextId, 0, length, 0, 0, mTextX, mTextY, false);
+        }
         if (DEBUG) {
             mPaint.setStyle(PaintBundle.STYLE_FILL_AND_STROKE);
             mPaint.setColor(1f, 1F, 1F, 1F);
@@ -246,6 +278,8 @@
             @NonNull PaintContext context,
             float maxWidth,
             float maxHeight,
+            boolean horizontalWrap,
+            boolean verticalWrap,
             @NonNull MeasurePass measure,
             @NonNull Size size) {
         context.savePaint();
@@ -262,29 +296,39 @@
         context.restorePaint();
         float w = bounds[2] - bounds[0];
         float h = bounds[3] - bounds[1];
-        size.setWidth(w);
+        size.setWidth(Math.min(maxWidth, w));
         mTextX = -bounds[0];
-        size.setHeight(h);
+        size.setHeight(Math.min(maxHeight, h));
         mTextY = -bounds[1];
         mTextW = w;
         mTextH = h;
     }
 
     @Override
-    public float intrinsicHeight() {
+    public float intrinsicHeight(@Nullable RemoteContext context) {
         return mTextH;
     }
 
     @Override
-    public float intrinsicWidth() {
+    public float intrinsicWidth(@Nullable RemoteContext context) {
         return mTextW;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "TextLayout";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.LAYOUT_TEXT;
     }
@@ -312,6 +356,12 @@
         buffer.writeInt(textAlign);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
@@ -336,6 +386,11 @@
                         textAlign));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description("Text layout implementation.\n\n")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
index 71d2ba6..b4240d0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
@@ -25,6 +25,7 @@
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
@@ -98,7 +99,8 @@
     }
 
     @Override
-    public void layout(@NonNull RemoteContext context, float width, float height) {
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {
         this.mWidth = width;
         this.mHeight = height;
     }
@@ -109,11 +111,21 @@
         return "BackgroundModifierOperation(" + mWidth + " x " + mHeight + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -142,6 +154,12 @@
         buffer.writeInt(shapeType);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float x = buffer.readFloat();
         float y = buffer.readFloat();
@@ -171,6 +189,11 @@
         context.restorePaint();
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the Background Modifier")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
index 0707cd6..df30d9f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
@@ -25,6 +25,7 @@
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
@@ -124,7 +125,8 @@
     }
 
     @Override
-    public void layout(@NonNull RemoteContext context, float width, float height) {
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {
         this.mWidth = width;
         this.mHeight = height;
     }
@@ -155,11 +157,21 @@
                 + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -192,6 +204,12 @@
         buffer.writeInt(shapeType);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float x = buffer.readFloat();
         float y = buffer.readFloat();
@@ -229,6 +247,11 @@
         context.restorePaint();
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the Border Modifier")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
index e05b027..b27fb920 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
@@ -23,6 +23,7 @@
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 import java.util.List;
@@ -40,7 +41,8 @@
     }
 
     @Override
-    public void layout(@NonNull RemoteContext context, float width, float height) {
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {
         this.mWidth = width;
         this.mHeight = height;
     }
@@ -55,11 +57,21 @@
         apply(buffer);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -68,10 +80,21 @@
         buffer.start(OP_CODE);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new ClipRectModifierOperation());
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw the specified round-rect");
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
index d11f26f..d2ba13f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
@@ -21,6 +21,7 @@
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
 import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
@@ -86,6 +87,10 @@
         float tx = 0f;
         float ty = 0f;
         for (ModifierOperation op : mList) {
+            if (op.isDirty() && op instanceof VariableSupport) {
+                ((VariableSupport) op).updateVariables(context.getContext());
+                op.markNotDirty();
+            }
             if (op instanceof PaddingModifierOperation) {
                 PaddingModifierOperation pop = (PaddingModifierOperation) op;
                 context.translate(pop.getLeft(), pop.getTop());
@@ -109,7 +114,8 @@
     }
 
     @Override
-    public void layout(@NonNull RemoteContext context, float width, float height) {
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {
         float w = width;
         float h = height;
         for (ModifierOperation op : mList) {
@@ -119,9 +125,9 @@
                 h -= pop.getTop() + pop.getBottom();
             }
             if (op instanceof ClickModifierOperation) {
-                ((DecoratorComponent) op).layout(context, width, height);
+                ((DecoratorComponent) op).layout(context, component, width, height);
             } else if (op instanceof DecoratorComponent) {
-                ((DecoratorComponent) op).layout(context, w, h);
+                ((DecoratorComponent) op).layout(context, component, w, h);
             }
         }
     }
@@ -156,10 +162,16 @@
 
     @Override
     public void onTouchUp(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            float dx,
+            float dy) {
         for (ModifierOperation op : mList) {
             if (op instanceof TouchHandler) {
-                ((TouchHandler) op).onTouchUp(context, document, component, x, y);
+                ((TouchHandler) op).onTouchUp(context, document, component, x, y, dx, dy);
             }
         }
     }
@@ -208,32 +220,6 @@
         return false;
     }
 
-    public float getScrollX() {
-        float scroll = 0;
-        for (ModifierOperation op : mList) {
-            if (op instanceof ScrollModifierOperation) {
-                ScrollModifierOperation scrollModifier = (ScrollModifierOperation) op;
-                if (scrollModifier.isHorizontalScroll()) {
-                    scroll = Math.min(scroll, scrollModifier.getScrollX());
-                }
-            }
-        }
-        return scroll;
-    }
-
-    public float getScrollY() {
-        float scroll = 0;
-        for (ModifierOperation op : mList) {
-            if (op instanceof ScrollModifierOperation) {
-                ScrollModifierOperation scrollModifier = (ScrollModifierOperation) op;
-                if (scrollModifier.isVerticalScroll()) {
-                    scroll = Math.min(scroll, scrollModifier.getScrollY());
-                }
-            }
-        }
-        return scroll;
-    }
-
     public void setHorizontalScrollDimension(float hostDimension, float contentDimension) {
         for (ModifierOperation op : mList) {
             if (op instanceof ScrollModifierOperation) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
index 471db0b..c377b75 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
@@ -34,7 +34,7 @@
 import java.util.List;
 
 /** Allows setting visibility on a component */
-public class ComponentVisibilityOperation
+public class ComponentVisibilityOperation extends Operation
         implements ModifierOperation, VariableSupport, DecoratorComponent {
     private static final int OP_CODE = Operations.MODIFIER_VISIBILITY;
 
@@ -79,11 +79,22 @@
         buffer.writeInt(valueId);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int valueId = buffer.readInt();
         operations.add(new ComponentVisibilityOperation(valueId));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ComponentVisibility")
                 .description(
@@ -119,5 +130,6 @@
     }
 
     @Override
-    public void layout(@NonNull RemoteContext context, float width, float height) {}
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {}
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
index b9324f0..b11deae 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
@@ -17,13 +17,15 @@
 
 import android.annotation.NonNull;
 
+import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.operations.Utils;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 /** Base class for dimension modifiers */
-public abstract class DimensionModifierOperation implements ModifierOperation, VariableSupport {
+public abstract class DimensionModifierOperation extends Operation
+        implements ModifierOperation, VariableSupport {
 
     public enum Type {
         EXACT,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
index 571e554..15c2f46 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
@@ -27,6 +27,7 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.operations.layout.AnimatableValue;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 import java.util.List;
@@ -202,10 +203,21 @@
         return "GraphicsLayerModifierOperation(" + mScaleX + ", " + mScaleY + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -301,5 +313,5 @@
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {}
+    public void layout(RemoteContext context, Component component, float width, float height) {}
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
index 7bb4a75..ec078a9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
@@ -32,11 +32,21 @@
     private static final int OP_CODE = Operations.MODIFIER_HEIGHT;
     public static final String CLASS_NAME = "HeightModifierOperation";
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -47,6 +57,12 @@
         buffer.writeFloat(value);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Type type = Type.fromInt(buffer.readInt());
         float value = buffer.readFloat();
@@ -83,6 +99,11 @@
         return "HEIGHT";
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the animation")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
index d239bc8..2e9d661 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
@@ -32,7 +32,7 @@
 import java.util.List;
 
 /** Capture a host action information. This can be triggered on eg. a click. */
-public class HostActionOperation implements ActionOperation {
+public class HostActionOperation extends Operation implements ActionOperation {
     private static final int OP_CODE = Operations.HOST_ACTION;
 
     int mActionId = -1;
@@ -88,11 +88,22 @@
         buffer.writeInt(actionId);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int actionId = buffer.readInt();
         operations.add(new HostActionOperation(actionId));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "HostAction")
                 .description("Host action. This operation represents a host action")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
index 3268e5e..49ef58e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
@@ -32,7 +32,7 @@
 import java.util.List;
 
 /** Capture a host action information. This can be triggered on eg. a click. */
-public class HostNamedActionOperation implements ActionOperation {
+public class HostNamedActionOperation extends Operation implements ActionOperation {
     private static final int OP_CODE = Operations.HOST_NAMED_ACTION;
 
     public static final int FLOAT_TYPE = 0;
@@ -112,6 +112,12 @@
         buffer.writeInt(valueId);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         int type = buffer.readInt();
@@ -119,6 +125,11 @@
         operations.add(new HostNamedActionOperation(textId, type, valueId));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "HostNamedAction")
                 .description("Host Named action. This operation represents a host action")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
new file mode 100644
index 0000000..9588e99
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.ScrollDelegate;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Represents a Marquee modifier. */
+public class MarqueeModifierOperation extends DecoratorModifierOperation implements ScrollDelegate {
+    private static final int OP_CODE = Operations.MODIFIER_MARQUEE;
+    public static final String CLASS_NAME = "MarqueeModifierOperation";
+
+    int mIterations;
+    int mAnimationMode;
+    float mRepeatDelayMillis;
+    float mInitialDelayMillis;
+    float mSpacing;
+    float mVelocity;
+
+    private float mComponentWidth;
+    private float mComponentHeight;
+    private float mContentWidth;
+    private float mContentHeight;
+
+    public MarqueeModifierOperation(
+            int iterations,
+            int animationMode,
+            float repeatDelayMillis,
+            float initialDelayMillis,
+            float spacing,
+            float velocity) {
+        this.mIterations = iterations;
+        this.mAnimationMode = animationMode;
+        this.mRepeatDelayMillis = repeatDelayMillis;
+        this.mInitialDelayMillis = initialDelayMillis;
+        this.mSpacing = spacing;
+        this.mVelocity = velocity;
+    }
+
+    public void setContentWidth(float value) {
+        mContentWidth = value;
+    }
+
+    public void setContentHeight(float value) {
+        mContentHeight = value;
+    }
+
+    @Override
+    public float getScrollX(float currentValue) {
+        return mScrollX;
+    }
+
+    @Override
+    public float getScrollY(float currentValue) {
+        return 0;
+    }
+
+    @Override
+    public boolean handlesHorizontalScroll() {
+        return true;
+    }
+
+    @Override
+    public boolean handlesVerticalScroll() {
+        return false;
+    }
+
+    /** Reset the modifier */
+    public void reset() {
+        mLastTime = 0;
+        mScrollX = 0f;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(
+                buffer,
+                mIterations,
+                mAnimationMode,
+                mRepeatDelayMillis,
+                mInitialDelayMillis,
+                mSpacing,
+                mVelocity);
+    }
+
+    // @Override
+    public void serializeToString(int indent, StringSerializer serializer) {
+        serializer.append(indent, "MARQUEE = [" + mIterations + "]");
+    }
+
+    @NonNull
+    @Override
+    public String deepToString(@NonNull String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    private long mLastTime = 0;
+    private long mStartTime = 0;
+
+    private float mScrollX = 0f;
+
+    @Override
+    public void paint(PaintContext context) {
+        long currentTime = System.currentTimeMillis();
+        if (mLastTime == 0) {
+            mLastTime = currentTime;
+            mStartTime = mLastTime + (long) mInitialDelayMillis;
+            context.needsRepaint();
+        }
+        if (mContentWidth > mComponentWidth && currentTime - mStartTime > mInitialDelayMillis) {
+            float density = context.getContext().getDensity(); // in dp
+            float delta = mContentWidth - mComponentWidth;
+            float duration = delta / (density * mVelocity);
+            float elapsed = ((System.currentTimeMillis() - mStartTime) / 1000f);
+            elapsed = (elapsed % duration) / duration;
+            float offset =
+                    (1f + (float) Math.sin(elapsed * 2 * Math.PI - Math.PI / 2f)) / 2f * -delta;
+
+            mScrollX = offset;
+            context.needsRepaint();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "MarqueeModifierOperation(" + mIterations + ")";
+    }
+
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    public static int id() {
+        return OP_CODE;
+    }
+
+    public static void apply(
+            WireBuffer buffer,
+            int iterations,
+            int animationMode,
+            float repeatDelayMillis,
+            float initialDelayMillis,
+            float spacing,
+            float velocity) {
+        buffer.start(OP_CODE);
+        buffer.writeInt(iterations);
+        buffer.writeInt(animationMode);
+        buffer.writeFloat(repeatDelayMillis);
+        buffer.writeFloat(initialDelayMillis);
+        buffer.writeFloat(spacing);
+        buffer.writeFloat(velocity);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        int iterations = buffer.readInt();
+        int animationMode = buffer.readInt();
+        float repeatDelayMillis = buffer.readFloat();
+        float initialDelayMillis = buffer.readFloat();
+        float spacing = buffer.readFloat();
+        float velocity = buffer.readFloat();
+        operations.add(
+                new MarqueeModifierOperation(
+                        iterations,
+                        animationMode,
+                        repeatDelayMillis,
+                        initialDelayMillis,
+                        spacing,
+                        velocity));
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
+                .description("specify a Marquee Modifier")
+                .field(FLOAT, "value", "");
+    }
+
+    @Override
+    public void layout(RemoteContext context, Component component, float width, float height) {
+        mComponentWidth = width;
+        mComponentHeight = height;
+        if (component instanceof LayoutComponent) {
+            LayoutComponent layoutComponent = (LayoutComponent) component;
+            setContentWidth(layoutComponent.intrinsicWidth(context));
+            setContentHeight(layoutComponent.intrinsicHeight(context));
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java
index 8f08f14..f8926fe 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java
@@ -17,10 +17,10 @@
 
 import android.annotation.NonNull;
 
-import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.OperationInterface;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 /** Represents a modifier */
-public interface ModifierOperation extends Operation {
+public interface ModifierOperation extends OperationInterface {
     void serializeToString(int indent, @NonNull StringSerializer serializer);
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
index 8c07059..4271947 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
@@ -26,6 +26,7 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 import java.util.List;
@@ -90,10 +91,21 @@
         return "OffsetModifierOperation(" + mX + ", " + mY + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -118,5 +130,5 @@
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {}
+    public void layout(RemoteContext context, Component component, float width, float height) {}
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
index 2b6621e..bcfbdd6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
@@ -32,7 +32,7 @@
  * Represents a padding modifier. Padding modifiers can be chained and will impact following
  * modifiers.
  */
-public class PaddingModifierOperation implements ModifierOperation {
+public class PaddingModifierOperation extends Operation implements ModifierOperation {
     private static final int OP_CODE = Operations.MODIFIER_PADDING;
     public static final String CLASS_NAME = "PaddingModifierOperation";
     float mLeft;
@@ -113,11 +113,21 @@
                 + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.MODIFIER_PADDING;
     }
@@ -131,6 +141,12 @@
         buffer.writeFloat(bottom);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float left = buffer.readFloat();
         float top = buffer.readFloat();
@@ -139,6 +155,11 @@
         operations.add(new PaddingModifierOperation(left, top, right, bottom));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the Padding Modifier")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
new file mode 100644
index 0000000..fe074e4
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchHandler;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.utilities.ColorUtils;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.Easing;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
+
+import java.util.List;
+
+/** Represents a ripple effect */
+public class RippleModifierOperation extends DecoratorModifierOperation implements TouchHandler {
+    private static final int OP_CODE = Operations.MODIFIER_RIPPLE;
+
+    long mAnimateRippleStart = 0;
+    float mAnimateRippleX = 0f;
+    float mAnimateRippleY = 0f;
+    int mAnimateRippleDuration = 1000;
+
+    float mWidth = 0;
+    float mHeight = 0;
+
+    @NonNull public float[] locationInWindow = new float[2];
+
+    @NonNull PaintBundle mPaint = new PaintBundle();
+
+    /**
+     * Animate the ripple effect
+     *
+     * @param x
+     * @param y
+     */
+    public void animateRipple(float x, float y) {
+        mAnimateRippleStart = System.currentTimeMillis();
+        mAnimateRippleX = x;
+        mAnimateRippleY = y;
+    }
+
+    @Override
+    public void write(@NonNull WireBuffer buffer) {
+        apply(buffer);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "RippleModifier";
+    }
+
+    @Override
+    public void apply(@NonNull RemoteContext context) {
+        RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
+        if (root != null) {
+            root.setHasTouchListeners(true);
+        }
+    }
+
+    @NonNull
+    @Override
+    public String deepToString(@NonNull String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    @Override
+    public void paint(@NonNull PaintContext context) {
+        if (mAnimateRippleStart == 0) {
+            return;
+        }
+        context.needsRepaint();
+
+        float progress = (System.currentTimeMillis() - mAnimateRippleStart);
+        progress /= (float) mAnimateRippleDuration;
+        if (progress > 1f) {
+            mAnimateRippleStart = 0;
+        }
+        progress = Math.min(1f, progress);
+        context.save();
+        context.savePaint();
+        mPaint.reset();
+
+        FloatAnimation anim1 =
+                new FloatAnimation(Easing.CUBIC_STANDARD, 1f, null, Float.NaN, Float.NaN);
+        anim1.setInitialValue(0f);
+        anim1.setTargetValue(1f);
+        float tween = anim1.get(progress);
+
+        FloatAnimation anim2 =
+                new FloatAnimation(Easing.CUBIC_STANDARD, 0.5f, null, Float.NaN, Float.NaN);
+        anim2.setInitialValue(0f);
+        anim2.setTargetValue(1f);
+        float tweenRadius = anim2.get(progress);
+
+        int startColor = ColorUtils.createColor(250, 250, 250, 180);
+        int endColor = ColorUtils.createColor(200, 200, 200, 0);
+        int paintedColor = Utils.interpolateColor(startColor, endColor, tween);
+
+        float radius = Math.max(mWidth, mHeight) * tweenRadius;
+        mPaint.setColor(paintedColor);
+        context.applyPaint(mPaint);
+        context.clipRect(0f, 0f, mWidth, mHeight);
+        context.drawCircle(mAnimateRippleX, mAnimateRippleY, radius);
+        context.restorePaint();
+        context.restore();
+    }
+
+    @Override
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {
+        mWidth = width;
+        mHeight = height;
+    }
+
+    @Override
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+        serializer.append(indent, "RIPPLE_MODIFIER");
+    }
+
+    @NonNull
+    public static String name() {
+        return "RippleModifier";
+    }
+
+    public static void apply(@NonNull WireBuffer buffer) {
+        buffer.start(OP_CODE);
+    }
+
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+        operations.add(new RippleModifierOperation());
+    }
+
+    public static void documentation(@NonNull DocumentationBuilder doc) {
+        doc.operation("Layout Operations", OP_CODE, name())
+                .description(
+                        "Ripple modifier. This modifier will do a ripple animation on touch down");
+    }
+
+    @Override
+    public void onTouchDown(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        locationInWindow[0] = 0f;
+        locationInWindow[1] = 0f;
+        component.getLocationInWindow(locationInWindow);
+        animateRipple(x - locationInWindow[0], y - locationInWindow[1]);
+        context.hapticEffect(3);
+    }
+
+    @Override
+    public void onTouchUp(
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            float dx,
+            float dy) {}
+
+    @Override
+    public void onTouchDrag(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {}
+
+    @Override
+    public void onTouchCancel(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {}
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
index 3fefc58..4c1f04e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
@@ -26,6 +26,7 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.operations.DrawBase4;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
@@ -37,15 +38,31 @@
     public static final int OP_CODE = Operations.MODIFIER_ROUNDED_CLIP_RECT;
     public static final String CLASS_NAME = "RoundedClipRectModifierOperation";
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = RoundedClipRectModifierOperation::new;
         read(m, buffer, operations);
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -56,6 +73,11 @@
         apply(buffer, v1, v2, v3, v4);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", id(), "RoundedClipRectModifierOperation")
                 .description("clip with rectangle")
@@ -96,7 +118,8 @@
     }
 
     @Override
-    public void layout(@NonNull RemoteContext context, float width, float height) {
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {
         this.mWidth = width;
         this.mHeight = height;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
index 8dcfed9..a5f79ee 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
@@ -24,18 +24,24 @@
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
 import com.android.internal.widget.remotecompose.core.operations.Utils;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.ListActionsOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.ScrollDelegate;
 import com.android.internal.widget.remotecompose.core.operations.layout.TouchHandler;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 import java.util.List;
 
 /** Represents a scroll modifier. */
-public class ScrollModifierOperation extends DecoratorModifierOperation implements TouchHandler {
+public class ScrollModifierOperation extends ListActionsOperation
+        implements TouchHandler, DecoratorComponent, ScrollDelegate, VariableSupport {
     private static final int OP_CODE = Operations.MODIFIER_SCROLL;
     public static final String CLASS_NAME = "ScrollModifierOperation";
 
@@ -43,9 +49,6 @@
     private final float mMax;
     private final float mNotchMax;
 
-    float mWidth = 0;
-    float mHeight = 0;
-
     int mDirection;
 
     float mTouchDownX;
@@ -63,13 +66,44 @@
     float mHostDimension;
     float mContentDimension;
 
+    private TouchExpression mTouchExpression;
+
     public ScrollModifierOperation(int direction, float position, float max, float notchMax) {
+        super("SCROLL_MODIFIER");
         this.mDirection = direction;
         this.mPositionExpression = position;
         this.mMax = max;
         this.mNotchMax = notchMax;
     }
 
+    /**
+     * Inflate the operation
+     *
+     * @param component
+     */
+    public void inflate(Component component) {
+        for (Operation op : mList) {
+            if (op instanceof TouchExpression) {
+                mTouchExpression = (TouchExpression) op;
+                mTouchExpression.setComponent(component);
+            }
+        }
+    }
+
+    @Override
+    public void registerListening(@NonNull RemoteContext context) {
+        if (mTouchExpression != null) {
+            mTouchExpression.registerListening(context);
+        }
+    }
+
+    @Override
+    public void updateVariables(@NonNull RemoteContext context) {
+        if (mTouchExpression != null) {
+            mTouchExpression.updateVariables(context);
+        }
+    }
+
     public boolean isVerticalScroll() {
         return mDirection == 0;
     }
@@ -113,6 +147,12 @@
 
     @Override
     public void paint(PaintContext context) {
+        for (Operation op : mList) {
+            op.apply(context.getContext());
+        }
+        if (mTouchExpression == null) {
+            return;
+        }
         float position =
                 context.getContext()
                         .mRemoteComposeState
@@ -130,10 +170,21 @@
         return "ScrollModifierOperation(" + mDirection + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -162,7 +213,7 @@
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {
+    public void layout(RemoteContext context, Component component, float width, float height) {
         mWidth = width;
         mHeight = height;
         if (mDirection == 0) { // VERTICAL
@@ -181,18 +232,36 @@
         mTouchDownY = y;
         mInitialScrollX = mScrollX;
         mInitialScrollY = mScrollY;
+        if (mTouchExpression != null) {
+            mTouchExpression.updateVariables(context);
+            mTouchExpression.touchDown(context, x + mScrollX, y + mScrollY);
+        }
         document.appliedTouchOperation(component);
     }
 
     @Override
     public void onTouchUp(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            float dx,
+            float dy) {
+        if (mTouchExpression != null) {
+            mTouchExpression.updateVariables(context);
+            mTouchExpression.touchUp(context, x + mScrollX, y + mScrollY, dx, dy);
+        }
         // If not using touch expression, should add velocity decay here
     }
 
     @Override
     public void onTouchDrag(
             RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        if (mTouchExpression != null) {
+            mTouchExpression.updateVariables(context);
+            mTouchExpression.touchDrag(context, x + mScrollX, y + mScrollY);
+        }
         float dx = x - mTouchDownX;
         float dy = y - mTouchDownY;
 
@@ -224,4 +293,35 @@
     public float getContentDimension() {
         return mContentDimension;
     }
+
+    @Override
+    public float getScrollX(float currentValue) {
+        if (mDirection == 1) {
+            return mScrollX;
+        }
+        return 0f;
+    }
+
+    @Override
+    public float getScrollY(float currentValue) {
+        if (mDirection == 0) {
+            return mScrollY;
+        }
+        return 0f;
+    }
+
+    @Override
+    public boolean handlesHorizontalScroll() {
+        return mDirection == 1;
+    }
+
+    @Override
+    public boolean handlesVerticalScroll() {
+        return mDirection == 0;
+    }
+
+    @Override
+    public void reset() {
+        // nothing here for now
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
index a97fcff..b6977a0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
@@ -33,7 +33,7 @@
 import java.util.List;
 
 /** Apply a value change on an float variable. */
-public class ValueFloatChangeActionOperation implements ActionOperation {
+public class ValueFloatChangeActionOperation extends Operation implements ActionOperation {
     private static final int OP_CODE = Operations.VALUE_FLOAT_CHANGE_ACTION;
 
     int mTargetValueId = -1;
@@ -73,7 +73,6 @@
     @Override
     public void runAction(
             RemoteContext context, CoreDocument document, Component component, float x, float y) {
-        System.out.println("OVERRIDE " + mTargetValueId + " TO " + mValue);
         context.overrideFloat(mTargetValueId, mValue);
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
index 41586b4..766271a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
@@ -32,7 +32,8 @@
 import java.util.List;
 
 /** Apply a value change on an integer variable. */
-public class ValueFloatExpressionChangeActionOperation implements ActionOperation {
+public class ValueFloatExpressionChangeActionOperation extends Operation
+        implements ActionOperation {
     private static final int OP_CODE = Operations.VALUE_FLOAT_EXPRESSION_CHANGE_ACTION;
 
     int mTargetValueId = -1;
@@ -88,12 +89,23 @@
         buffer.writeInt(value);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int valueId = buffer.readInt();
         int value = buffer.readInt();
         operations.add(new ValueFloatExpressionChangeActionOperation(valueId, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ValueIntegerExpressionChangeActionOperation")
                 .description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
index c2cd2ab..60166a7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
@@ -32,7 +32,7 @@
 import java.util.List;
 
 /** Apply a value change on an integer variable. */
-public class ValueIntegerChangeActionOperation implements ActionOperation {
+public class ValueIntegerChangeActionOperation extends Operation implements ActionOperation {
     private static final int OP_CODE = Operations.VALUE_INTEGER_CHANGE_ACTION;
 
     int mTargetValueId = -1;
@@ -87,12 +87,23 @@
         buffer.writeInt(value);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int valueId = buffer.readInt();
         int value = buffer.readInt();
         operations.add(new ValueIntegerChangeActionOperation(valueId, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ValueIntegerChangeActionOperation")
                 .description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
index 43fbb85..5025080 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
@@ -32,7 +32,8 @@
 import java.util.List;
 
 /** Apply a value change on an integer variable. */
-public class ValueIntegerExpressionChangeActionOperation implements ActionOperation {
+public class ValueIntegerExpressionChangeActionOperation extends Operation
+        implements ActionOperation {
     private static final int OP_CODE = Operations.VALUE_INTEGER_EXPRESSION_CHANGE_ACTION;
 
     long mTargetValueId = -1;
@@ -88,12 +89,23 @@
         buffer.writeLong(value);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         long valueId = buffer.readLong();
         long value = buffer.readLong();
         operations.add(new ValueIntegerExpressionChangeActionOperation(valueId, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ValueIntegerExpressionChangeActionOperation")
                 .description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
index 1107889..8093bb3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
@@ -32,7 +32,7 @@
 import java.util.List;
 
 /** Apply a value change on a string variable. */
-public class ValueStringChangeActionOperation implements ActionOperation {
+public class ValueStringChangeActionOperation extends Operation implements ActionOperation {
     private static final int OP_CODE = Operations.VALUE_STRING_CHANGE_ACTION;
 
     int mTargetValueId = -1;
@@ -91,12 +91,23 @@
         buffer.writeInt(value);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int valueId = buffer.readInt();
         int value = buffer.readInt();
         operations.add(new ValueStringChangeActionOperation(valueId, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ValueStringChangeActionOperation")
                 .description(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
index 3c757a8..0530598 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
@@ -32,11 +32,21 @@
     private static final int OP_CODE = Operations.MODIFIER_WIDTH;
     public static final String CLASS_NAME = "WidthModifierOperation";
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -47,6 +57,12 @@
         buffer.writeFloat(value);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Type type = Type.fromInt(buffer.readInt());
         float value = buffer.readFloat();
@@ -83,6 +99,11 @@
         return "WIDTH";
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the animation")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
index 82c8f34..35de33a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
@@ -26,6 +26,7 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 import java.util.List;
@@ -45,7 +46,7 @@
         return mCurrentValue;
     }
 
-    public void setmValue(float value) {
+    public void setValue(float value) {
         this.mValue = value;
     }
 
@@ -79,10 +80,21 @@
         return "ZIndexModifierOperation(" + mValue + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return OP_CODE;
     }
@@ -104,5 +116,5 @@
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {}
+    public void layout(RemoteContext context, Component component, float width, float height) {}
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
index 07cf762..9543469 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
@@ -498,6 +498,11 @@
         return ret;
     }
 
+    /**
+     * Write a bundle of paint changes to the buffer
+     *
+     * @param buffer bundle to write
+     */
     public void writeBundle(@NonNull WireBuffer buffer) {
         buffer.writeInt(mPos);
         for (int index = 0; index < mPos; index++) {
@@ -505,6 +510,11 @@
         }
     }
 
+    /**
+     * This will read the paint bundle off the wire buffer
+     *
+     * @param buffer the buffer to read
+     */
     public void readBundle(@NonNull WireBuffer buffer) {
         int len = buffer.readInt();
         if (len <= 0 || len > 1024) {
@@ -587,6 +597,9 @@
     public static final int RADIAL_GRADIENT = 1;
     public static final int SWEEP_GRADIENT = 2;
 
+    private int mLastShaderSet = -1;
+    private boolean mColorFilterSet = false;
+
     /**
      * sets a shader that draws a linear gradient along a line.
      *
@@ -722,12 +735,14 @@
         mArray[mPos] = COLOR_FILTER_ID | (mode << 16);
         mPos++;
         mArray[mPos++] = color;
+        mColorFilterSet = true;
     }
 
     /** This sets the color filter to null */
     public void clearColorFilter() {
         mArray[mPos] = CLEAR_COLOR_FILTER;
         mPos++;
+        mColorFilterSet = false;
     }
 
     /**
@@ -843,6 +858,7 @@
      * @param shaderId
      */
     public void setShader(int shaderId) {
+        mLastShaderSet = shaderId;
         mArray[mPos] = SHADER;
         mPos++;
         mArray[mPos] = shaderId;
@@ -909,11 +925,23 @@
         mPos++;
     }
 
+    /**
+     * clear a series of paint parameters. Currently not used
+     *
+     * @param mask bit pattern of the attributes to clear
+     */
     public void clear(long mask) { // unused for now
     }
 
+    /** Reset the content of the paint bundle so that it can be reused */
     public void reset() {
         mPos = 0;
+        if (mColorFilterSet) {
+            clearColorFilter();
+        }
+        if (mLastShaderSet != -1 && mLastShaderSet != 0) {
+            setShader(0);
+        }
     }
 
     public static @NonNull String blendModeString(int mode) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
index e5633c7..7e46701 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
@@ -18,61 +18,149 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
-import com.android.internal.widget.remotecompose.core.operations.Utils;
 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.MonotonicSpline;
 
+import java.util.Random;
+
 /** high performance floating point expression evaluator used in animation */
 public class AnimatedFloatExpression {
     @NonNull static IntMap<String> sNames = new IntMap<>();
+
+    /** The START POINT in the float NaN space for operators */
     public static final int OFFSET = 0x310_000;
+
+    /** ADD operator */
     public static final float ADD = asNan(OFFSET + 1);
+
+    /** SUB operator */
     public static final float SUB = asNan(OFFSET + 2);
+
+    /** MUL operator */
     public static final float MUL = asNan(OFFSET + 3);
+
+    /** DIV operator */
     public static final float DIV = asNan(OFFSET + 4);
+
+    /** MOD operator */
     public static final float MOD = asNan(OFFSET + 5);
+
+    /** MIN operator */
     public static final float MIN = asNan(OFFSET + 6);
+
+    /** MAX operator */
     public static final float MAX = asNan(OFFSET + 7);
+
+    /** POW operator */
     public static final float POW = asNan(OFFSET + 8);
+
+    /** SQRT operator */
     public static final float SQRT = asNan(OFFSET + 9);
+
+    /** ABS operator */
     public static final float ABS = asNan(OFFSET + 10);
+
+    /** SIGN operator */
     public static final float SIGN = asNan(OFFSET + 11);
+
+    /** COPY_SIGN operator */
     public static final float COPY_SIGN = asNan(OFFSET + 12);
+
+    /** EXP operator */
     public static final float EXP = asNan(OFFSET + 13);
+
+    /** FLOOR operator */
     public static final float FLOOR = asNan(OFFSET + 14);
+
+    /** LOG operator */
     public static final float LOG = asNan(OFFSET + 15);
+
+    /** LN operator */
     public static final float LN = asNan(OFFSET + 16);
+
+    /** ROUND operator */
     public static final float ROUND = asNan(OFFSET + 17);
+
+    /** SIN operator */
     public static final float SIN = asNan(OFFSET + 18);
+
+    /** COS operator */
     public static final float COS = asNan(OFFSET + 19);
+
+    /** TAN operator */
     public static final float TAN = asNan(OFFSET + 20);
+
+    /** ASIN operator */
     public static final float ASIN = asNan(OFFSET + 21);
+
+    /** ACOS operator */
     public static final float ACOS = asNan(OFFSET + 22);
 
+    /** ATAN operator */
     public static final float ATAN = asNan(OFFSET + 23);
 
+    /** ATAN2 operator */
     public static final float ATAN2 = asNan(OFFSET + 24);
+
+    /** MAD operator */
     public static final float MAD = asNan(OFFSET + 25);
+
+    /** IFELSE operator */
     public static final float IFELSE = asNan(OFFSET + 26);
 
+    /** CLAMP operator */
     public static final float CLAMP = asNan(OFFSET + 27);
+
+    /** CBRT operator */
     public static final float CBRT = asNan(OFFSET + 28);
+
+    /** DEG operator */
     public static final float DEG = asNan(OFFSET + 29);
+
+    /** RAD operator */
     public static final float RAD = asNan(OFFSET + 30);
+
+    /** CEIL operator */
     public static final float CEIL = asNan(OFFSET + 31);
 
     // Array ops
+    /** A DEREF operator */
     public static final float A_DEREF = asNan(OFFSET + 32);
+
+    /** Array MAX operator */
     public static final float A_MAX = asNan(OFFSET + 33);
+
+    /** Array MIN operator */
     public static final float A_MIN = asNan(OFFSET + 34);
+
+    /** A_SUM operator */
     public static final float A_SUM = asNan(OFFSET + 35);
+
+    /** A_AVG operator */
     public static final float A_AVG = asNan(OFFSET + 36);
+
+    /** A_LEN operator */
     public static final float A_LEN = asNan(OFFSET + 37);
+
+    /** A_SPLINE operator */
     public static final float A_SPLINE = asNan(OFFSET + 38);
 
-    public static final int LAST_OP = OFFSET + 38;
+    /** RAND Random number 0..1 */
+    public static final float RAND = asNan(OFFSET + 39);
 
-    public static final float VAR1 = asNan(OFFSET + 39);
-    public static final float VAR2 = asNan(OFFSET + 40);
+    /** RAND_SEED operator */
+    public static final float RAND_SEED = asNan(OFFSET + 40);
+
+    /** LAST valid operator */
+    public static final int LAST_OP = OFFSET + 40;
+
+    /** VAR1 operator */
+    public static final float VAR1 = asNan(OFFSET + 41);
+
+    /** VAR2 operator */
+    public static final float VAR2 = asNan(OFFSET + 42);
+
+    /** VAR2 operator */
+    public static final float VAR3 = asNan(OFFSET + 43);
 
     // TODO CLAMP, CBRT, DEG, RAD, EXPM1, CEIL, FLOOR
     //    private static final float FP_PI = (float) Math.PI;
@@ -84,6 +172,7 @@
     @NonNull float[] mVar = new float[0];
     @Nullable CollectionsAccess mCollectionsAccess;
     IntMap<MonotonicSpline> mSplineMap = new IntMap<>();
+    private Random mRandom;
 
     private float getSplineValue(int arrayId, float pos) {
         MonotonicSpline fit = mSplineMap.get(arrayId);
@@ -141,7 +230,7 @@
         for (int i = 0; i < mStack.length; i++) {
             float v = mStack[i];
             if (Float.isNaN(v)) {
-                sp = mOps[fromNaN(v) - OFFSET].eval(sp);
+                sp = opEval(sp, fromNaN(v));
             } else {
                 mStack[++sp] = v;
             }
@@ -152,10 +241,11 @@
     /**
      * Evaluate a float expression
      *
-     * @param ca
-     * @param exp
-     * @param var
-     * @return
+     * @param ca Access to float array collections
+     * @param exp the expressions
+     * @param len the length of the expression array
+     * @param var variables if the expression contains VAR tags
+     * @return the value the expression evaluated to
      */
     public float eval(
             @NonNull CollectionsAccess ca, @NonNull float[] exp, int len, @NonNull float... var) {
@@ -170,7 +260,7 @@
             if (Float.isNaN(v)) {
                 int id = fromNaN(v);
                 if ((id & NanMap.ID_REGION_MASK) != NanMap.ID_REGION_ARRAY) {
-                    sp = mOps[id - OFFSET].eval(sp);
+                    sp = opEval(sp, id);
                 } else {
                     mStack[++sp] = v;
                 }
@@ -184,9 +274,10 @@
     /**
      * Evaluate a float expression
      *
-     * @param ca
-     * @param exp
-     * @return
+     * @param ca The access to float arrays
+     * @param exp the expression
+     * @param len the length of the expression sections
+     * @return the value the expression evaluated to
      */
     public float eval(@NonNull CollectionsAccess ca, @NonNull float[] exp, int len) {
         System.arraycopy(exp, 0, mLocalStack, 0, len);
@@ -199,7 +290,7 @@
             if (Float.isNaN(v)) {
                 int id = fromNaN(v);
                 if ((id & NanMap.ID_REGION_MASK) != NanMap.ID_REGION_ARRAY) {
-                    sp = mOps[id - OFFSET].eval(sp);
+                    sp = opEval(sp, id);
                 } else {
                     mStack[++sp] = v;
                 }
@@ -228,10 +319,11 @@
         mStack = mLocalStack;
         mVar = var;
         int sp = -1;
+
         for (int i = 0; i < len; i++) {
             float v = mStack[i];
             if (Float.isNaN(v)) {
-                sp = mOps[fromNaN(v) - OFFSET].eval(sp);
+                sp = opEval(sp, fromNaN(v));
             } else {
                 mStack[++sp] = v;
             }
@@ -250,9 +342,10 @@
         mStack = exp;
         mVar = var;
         int sp = -1;
+
         for (float v : exp) {
             if (Float.isNaN(v)) {
-                sp = mOps[fromNaN(v) - OFFSET].eval(sp);
+                sp = opEval(sp, fromNaN(v));
             } else {
                 System.out.print(" " + v);
                 mStack[++sp] = v;
@@ -261,294 +354,6 @@
         return mStack[sp];
     }
 
-    @NonNull Op[] mOps;
-
-    {
-        Op mADD =
-                (sp) -> { // ADD
-                    mStack[sp - 1] = mStack[sp - 1] + mStack[sp];
-                    return sp - 1;
-                };
-        Op mSUB =
-                (sp) -> { // SUB
-                    mStack[sp - 1] = mStack[sp - 1] - mStack[sp];
-                    return sp - 1;
-                };
-        Op mMUL =
-                (sp) -> { // MUL
-                    mStack[sp - 1] = mStack[sp - 1] * mStack[sp];
-                    return sp - 1;
-                };
-        Op mDIV =
-                (sp) -> { // DIV
-                    mStack[sp - 1] = mStack[sp - 1] / mStack[sp];
-                    return sp - 1;
-                };
-        Op mMOD =
-                (sp) -> { // MOD
-                    mStack[sp - 1] = mStack[sp - 1] % mStack[sp];
-                    return sp - 1;
-                };
-        Op mMIN =
-                (sp) -> { // MIN
-                    mStack[sp - 1] = (float) Math.min(mStack[sp - 1], mStack[sp]);
-                    return sp - 1;
-                };
-        Op mMAX =
-                (sp) -> { // MAX
-                    mStack[sp - 1] = (float) Math.max(mStack[sp - 1], mStack[sp]);
-                    return sp - 1;
-                };
-        Op mPOW =
-                (sp) -> { // POW
-                    mStack[sp - 1] = (float) Math.pow(mStack[sp - 1], mStack[sp]);
-                    return sp - 1;
-                };
-        Op mSQRT =
-                (sp) -> { // SQRT
-                    mStack[sp] = (float) Math.sqrt(mStack[sp]);
-                    return sp;
-                };
-        Op mABS =
-                (sp) -> { // ABS
-                    mStack[sp] = (float) Math.abs(mStack[sp]);
-                    return sp;
-                };
-        Op mSIGN =
-                (sp) -> { // SIGN
-                    mStack[sp] = (float) Math.signum(mStack[sp]);
-                    return sp;
-                };
-        Op mCOPY_SIGN =
-                (sp) -> { // copySign
-                    mStack[sp - 1] = (float) Math.copySign(mStack[sp - 1], mStack[sp]);
-                    return sp - 1;
-                };
-        Op mEXP =
-                (sp) -> { // EXP
-                    mStack[sp] = (float) Math.exp(mStack[sp]);
-                    return sp;
-                };
-        Op mFLOOR =
-                (sp) -> { // FLOOR
-                    mStack[sp] = (float) Math.floor(mStack[sp]);
-                    return sp;
-                };
-        Op mLOG =
-                (sp) -> { // LOG
-                    mStack[sp] = (float) Math.log10(mStack[sp]);
-                    return sp;
-                };
-        Op mLN =
-                (sp) -> { // LN
-                    mStack[sp] = (float) Math.log(mStack[sp]);
-                    return sp;
-                };
-        Op mROUND =
-                (sp) -> { // ROUND
-                    mStack[sp] = (float) Math.round(mStack[sp]);
-                    return sp;
-                };
-        Op mSIN =
-                (sp) -> { // SIN
-                    mStack[sp] = (float) Math.sin(mStack[sp]);
-                    return sp;
-                };
-        Op mCOS =
-                (sp) -> { // COS
-                    mStack[sp] = (float) Math.cos(mStack[sp]);
-                    return sp;
-                };
-        Op mTAN =
-                (sp) -> { // TAN
-                    mStack[sp] = (float) Math.tan(mStack[sp]);
-                    return sp;
-                };
-        Op mASIN =
-                (sp) -> { // ASIN
-                    mStack[sp] = (float) Math.asin(mStack[sp]);
-                    return sp;
-                };
-        Op mACOS =
-                (sp) -> { // ACOS
-                    mStack[sp] = (float) Math.acos(mStack[sp]);
-                    return sp;
-                };
-        Op mATAN =
-                (sp) -> { // ATAN
-                    mStack[sp] = (float) Math.atan(mStack[sp]);
-                    return sp;
-                };
-        Op mATAN2 =
-                (sp) -> { // ATAN2
-                    mStack[sp - 1] = (float) Math.atan2(mStack[sp - 1], mStack[sp]);
-                    return sp - 1;
-                };
-        Op mMAD =
-                (sp) -> { // MAD
-                    mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2];
-                    return sp - 2;
-                };
-        Op mTERNARY_CONDITIONAL =
-                (sp) -> { // TERNARY_CONDITIONAL
-                    mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2];
-                    return sp - 2;
-                };
-        Op mCLAMP =
-                (sp) -> { // CLAMP (min, max, value)
-                    mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]);
-                    return sp - 2;
-                };
-        Op mCBRT =
-                (sp) -> { // CBRT is cube root
-                    mStack[sp] = (float) Math.pow(mStack[sp], 1 / 3.);
-                    return sp;
-                };
-        Op mDEG =
-                (sp) -> { // DEG
-                    mStack[sp] = mStack[sp] * FP_TO_RAD;
-                    return sp;
-                };
-        Op mRAD =
-                (sp) -> { // RAD
-                    mStack[sp] = mStack[sp] * FP_TO_DEG;
-                    return sp;
-                };
-        Op mCEIL =
-                (sp) -> { // CEIL
-                    mStack[sp] = (float) Math.ceil(mStack[sp]);
-                    return sp;
-                };
-        Op mA_DEREF =
-                (sp) -> { // A_DEREF
-                    Utils.log(" \n >>> DREF " + Integer.toHexString(fromNaN(mStack[sp - 1])));
-                    Utils.log(" >>> DREF " + mStack[sp] + "  " + mStack[sp - 1]);
-                    int id = fromNaN(mStack[sp - 1]);
-                    mStack[sp - 1] = mCollectionsAccess.getFloatValue(id, (int) mStack[sp]);
-                    return sp - 1;
-                };
-        Op mA_MAX =
-                (sp) -> { // A_MAX
-                    int id = fromNaN(mStack[sp]);
-                    float[] array = mCollectionsAccess.getFloats(id);
-                    float max = array[0];
-                    for (int i = 1; i < array.length; i++) {
-                        max = Math.max(max, array[i]);
-                    }
-                    mStack[sp] = max;
-                    return sp;
-                };
-        Op mA_MIN =
-                (sp) -> { // A_MIN
-                    int id = fromNaN(mStack[sp]);
-                    float[] array = mCollectionsAccess.getFloats(id);
-                    if (array.length == 0) {
-                        return sp;
-                    }
-                    float min = array[0];
-                    for (int i = 1; i < array.length; i++) {
-                        min = Math.min(min, array[i]);
-                    }
-                    mStack[sp] = min;
-                    return sp;
-                };
-        Op mA_SUM =
-                (sp) -> { // A_SUM
-                    int id = fromNaN(mStack[sp]);
-                    float[] array = mCollectionsAccess.getFloats(id);
-                    float sum = 0;
-                    for (int i = 0; i < array.length; i++) {
-                        sum += array[i];
-                    }
-                    mStack[sp] = sum;
-                    return sp;
-                };
-        Op mA_AVG =
-                (sp) -> { // A_AVG
-                    int id = fromNaN(mStack[sp]);
-                    float[] array = mCollectionsAccess.getFloats(id);
-                    float sum = 0;
-                    for (int i = 0; i < array.length; i++) {
-                        sum += array[i];
-                    }
-                    mStack[sp] = sum / array.length;
-                    return sp;
-                };
-        Op mA_LEN =
-                (sp) -> { // A_LEN
-                    int id = fromNaN(mStack[sp]);
-                    mStack[sp] = mCollectionsAccess.getListLength(id);
-                    return sp;
-                };
-        Op mA_SPLINE =
-                (sp) -> { // A_SPLINE
-                    int id = fromNaN(mStack[sp - 1]);
-                    mStack[sp - 1] = getSplineValue(id, mStack[sp]);
-                    return sp - 1;
-                };
-        Op mFIRST_VAR =
-                (sp) -> { // FIRST_VAR
-                    mStack[sp] = mVar[0];
-                    return sp;
-                };
-        Op mSECOND_VAR =
-                (sp) -> { // SECOND_VAR
-                    mStack[sp] = mVar[1];
-                    return sp;
-                };
-        Op mTHIRD_VAR =
-                (sp) -> { // THIRD_VAR
-                    mStack[sp] = mVar[2];
-                    return sp;
-                };
-
-        Op[] ops = {
-            null,
-            mADD,
-            mSUB,
-            mMUL,
-            mDIV,
-            mMOD,
-            mMIN,
-            mMAX,
-            mPOW,
-            mSQRT,
-            mABS,
-            mSIGN,
-            mCOPY_SIGN,
-            mEXP,
-            mFLOOR,
-            mLOG,
-            mLN,
-            mROUND,
-            mSIN,
-            mCOS,
-            mTAN,
-            mASIN,
-            mACOS,
-            mATAN,
-            mATAN2,
-            mMAD,
-            mTERNARY_CONDITIONAL,
-            mCLAMP,
-            mCBRT,
-            mDEG,
-            mRAD,
-            mCEIL,
-            mA_DEREF,
-            mA_MAX,
-            mA_MIN,
-            mA_SUM,
-            mA_AVG,
-            mA_LEN,
-            mA_SPLINE,
-            mFIRST_VAR,
-            mSECOND_VAR,
-            mTHIRD_VAR,
-        };
-        mOps = ops;
-    }
-
     static {
         int k = 0;
         sNames.put(k++, "NOP");
@@ -591,6 +396,8 @@
         sNames.put(k++, "A_AVG");
         sNames.put(k++, "A_LEN");
         sNames.put(k++, "A_SPLINE");
+        sNames.put(k++, "RAND");
+        sNames.put(k++, "RAND_SEED");
 
         sNames.put(k++, "a[0]");
         sNames.put(k++, "a[1]");
@@ -765,4 +572,271 @@
         int b = Float.floatToRawIntBits(v);
         return b & 0x7FFFFF;
     }
+
+    // ================= New approach ========
+    private static final int OP_ADD = OFFSET + 1;
+    private static final int OP_SUB = OFFSET + 2;
+    private static final int OP_MUL = OFFSET + 3;
+    private static final int OP_DIV = OFFSET + 4;
+    private static final int OP_MOD = OFFSET + 5;
+    private static final int OP_MIN = OFFSET + 6;
+    private static final int OP_MAX = OFFSET + 7;
+    private static final int OP_POW = OFFSET + 8;
+    private static final int OP_SQRT = OFFSET + 9;
+    private static final int OP_ABS = OFFSET + 10;
+    private static final int OP_SIGN = OFFSET + 11;
+    private static final int OP_COPY_SIGN = OFFSET + 12;
+    private static final int OP_EXP = OFFSET + 13;
+    private static final int OP_FLOOR = OFFSET + 14;
+    private static final int OP_LOG = OFFSET + 15;
+    private static final int OP_LN = OFFSET + 16;
+    private static final int OP_ROUND = OFFSET + 17;
+    private static final int OP_SIN = OFFSET + 18;
+    private static final int OP_COS = OFFSET + 19;
+    private static final int OP_TAN = OFFSET + 20;
+    private static final int OP_ASIN = OFFSET + 21;
+    private static final int OP_ACOS = OFFSET + 22;
+    private static final int OP_ATAN = OFFSET + 23;
+    private static final int OP_ATAN2 = OFFSET + 24;
+    private static final int OP_MAD = OFFSET + 25;
+    private static final int OP_TERNARY_CONDITIONAL = OFFSET + 26;
+    private static final int OP_CLAMP = OFFSET + 27;
+    private static final int OP_CBRT = OFFSET + 28;
+    private static final int OP_DEG = OFFSET + 29;
+    private static final int OP_RAD = OFFSET + 30;
+    private static final int OP_CEIL = OFFSET + 31;
+    private static final int OP_A_DEREF = OFFSET + 32;
+    private static final int OP_A_MAX = OFFSET + 33;
+    private static final int OP_A_MIN = OFFSET + 34;
+    private static final int OP_A_SUM = OFFSET + 35;
+    private static final int OP_A_AVG = OFFSET + 36;
+    private static final int OP_A_LEN = OFFSET + 37;
+    private static final int OP_A_SPLINE = OFFSET + 38;
+    private static final int OP_RAND = OFFSET + 39;
+    private static final int OP_RAND_SEED = OFFSET + 40;
+
+    private static final int OP_FIRST_VAR = OFFSET + 41;
+    private static final int OP_SECOND_VAR = OFFSET + 42;
+    private static final int OP_THIRD_VAR = OFFSET + 43;
+
+    int opEval(int sp, int id) {
+        float[] array;
+
+        switch (id) {
+            case OP_ADD:
+                mStack[sp - 1] = mStack[sp - 1] + mStack[sp];
+                return sp - 1;
+
+            case OP_SUB:
+                mStack[sp - 1] = mStack[sp - 1] - mStack[sp];
+                return sp - 1;
+
+            case OP_MUL:
+                mStack[sp - 1] = mStack[sp - 1] * mStack[sp];
+                return sp - 1;
+
+            case OP_DIV:
+                mStack[sp - 1] = mStack[sp - 1] / mStack[sp];
+                return sp - 1;
+
+            case OP_MOD:
+                mStack[sp - 1] = mStack[sp - 1] % mStack[sp];
+                return sp - 1;
+
+            case OP_MIN:
+                mStack[sp - 1] = (float) Math.min(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+
+            case OP_MAX:
+                mStack[sp - 1] = (float) Math.max(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+
+            case OP_POW:
+                mStack[sp - 1] = (float) Math.pow(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+
+            case OP_SQRT:
+                mStack[sp] = (float) Math.sqrt(mStack[sp]);
+                return sp;
+
+            case OP_ABS:
+                mStack[sp] = (float) Math.abs(mStack[sp]);
+                return sp;
+
+            case OP_SIGN:
+                mStack[sp] = (float) Math.signum(mStack[sp]);
+                return sp;
+
+            case OP_COPY_SIGN:
+                mStack[sp - 1] = (float) Math.copySign(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+
+            case OP_EXP:
+                mStack[sp] = (float) Math.exp(mStack[sp]);
+                return sp;
+
+            case OP_FLOOR:
+                mStack[sp] = (float) Math.floor(mStack[sp]);
+                return sp;
+
+            case OP_LOG:
+                mStack[sp] = (float) Math.log10(mStack[sp]);
+                return sp;
+
+            case OP_LN:
+                mStack[sp] = (float) Math.log(mStack[sp]);
+                return sp;
+
+            case OP_ROUND:
+                mStack[sp] = (float) Math.round(mStack[sp]);
+                return sp;
+
+            case OP_SIN:
+                mStack[sp] = (float) Math.sin(mStack[sp]);
+                return sp;
+
+            case OP_COS:
+                mStack[sp] = (float) Math.cos(mStack[sp]);
+                return sp;
+
+            case OP_TAN:
+                mStack[sp] = (float) Math.tan(mStack[sp]);
+                return sp;
+
+            case OP_ASIN:
+                mStack[sp] = (float) Math.asin(mStack[sp]);
+                return sp;
+
+            case OP_ACOS:
+                mStack[sp] = (float) Math.acos(mStack[sp]);
+                return sp;
+
+            case OP_ATAN:
+                mStack[sp] = (float) Math.atan(mStack[sp]);
+                return sp;
+
+            case OP_ATAN2:
+                mStack[sp - 1] = (float) Math.atan2(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+
+            case OP_MAD:
+                mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2];
+                return sp - 2;
+
+            case OP_TERNARY_CONDITIONAL:
+                mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2];
+                return sp - 2;
+
+            case OP_CLAMP:
+                mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]);
+                return sp - 2;
+
+            case OP_CBRT:
+                mStack[sp] = (float) Math.pow(mStack[sp], 1 / 3.);
+                return sp;
+
+            case OP_DEG:
+                mStack[sp] = mStack[sp] * FP_TO_RAD;
+                return sp;
+
+            case OP_RAD:
+                mStack[sp] = mStack[sp] * FP_TO_DEG;
+                return sp;
+
+            case OP_CEIL:
+                mStack[sp] = (float) Math.ceil(mStack[sp]);
+                return sp;
+
+            case OP_A_DEREF:
+                id = fromNaN(mStack[sp - 1]);
+                mStack[sp - 1] = mCollectionsAccess.getFloatValue(id, (int) mStack[sp]);
+                return sp - 1;
+
+            case OP_A_MAX:
+                id = fromNaN(mStack[sp]);
+                array = mCollectionsAccess.getFloats(id);
+                float max = array[0];
+                for (int i = 1; i < array.length; i++) {
+                    max = Math.max(max, array[i]);
+                }
+                mStack[sp] = max;
+                return sp;
+
+            case OP_A_MIN:
+                id = fromNaN(mStack[sp]);
+                array = mCollectionsAccess.getFloats(id);
+                if (array.length == 0) {
+                    return sp;
+                }
+                float min = array[0];
+                for (int i = 1; i < array.length; i++) {
+                    min = Math.min(min, array[i]);
+                }
+                mStack[sp] = min;
+                return sp;
+
+            case OP_A_SUM:
+                id = fromNaN(mStack[sp]);
+                array = mCollectionsAccess.getFloats(id);
+                float sum = 0;
+                for (int i = 0; i < array.length; i++) {
+                    sum += array[i];
+                }
+                mStack[sp] = sum;
+                return sp;
+
+            case OP_A_AVG:
+                id = fromNaN(mStack[sp]);
+                array = mCollectionsAccess.getFloats(id);
+                sum = 0;
+                for (int i = 0; i < array.length; i++) {
+                    sum += array[i];
+                }
+                mStack[sp] = sum / array.length;
+                return sp;
+
+            case OP_A_LEN:
+                id = fromNaN(mStack[sp]);
+                mStack[sp] = mCollectionsAccess.getListLength(id);
+                return sp;
+
+            case OP_A_SPLINE:
+                id = fromNaN(mStack[sp - 1]);
+                mStack[sp - 1] = getSplineValue(id, mStack[sp]);
+                return sp - 1;
+
+            case OP_RAND:
+                if (mRandom == null) {
+                    mRandom = new Random();
+                }
+                mStack[sp + 1] = mRandom.nextFloat();
+                return sp + 1;
+
+            case OP_RAND_SEED:
+                float seed = mStack[sp];
+                if (seed == 0) {
+                    mRandom = new Random();
+                } else {
+                    if (mRandom == null) {
+                        mRandom = new Random(Float.floatToRawIntBits(seed));
+                    } else {
+                        mRandom.setSeed(Float.floatToRawIntBits(seed));
+                    }
+                }
+                return sp - 1;
+
+            case OP_FIRST_VAR:
+                mStack[sp] = mVar[0];
+                return sp;
+
+            case OP_SECOND_VAR:
+                mStack[sp] = mVar[1];
+                return sp;
+
+            case OP_THIRD_VAR:
+                mStack[sp] = mVar[2];
+                return sp;
+        }
+        return sp;
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ArrayAccess.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ArrayAccess.java
index 182d36a..69de535 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ArrayAccess.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ArrayAccess.java
@@ -23,17 +23,45 @@
  * FloatArrayAccess, ListAccess, MapAccess
  */
 public interface ArrayAccess {
+    /**
+     * Get a value as a float for an index
+     *
+     * @param index position in the collection
+     * @return
+     */
     float getFloatValue(int index);
 
+    /**
+     * If the objects have id's return the id
+     *
+     * @param index index of the object
+     * @return id or -1 if no id is available
+     */
     default int getId(int index) {
         return 0;
     }
 
+    /**
+     * Get the backing array of float if available for float arrays
+     *
+     * @return
+     */
     @Nullable
     float[] getFloats();
 
+    /**
+     * Get the length of the collection
+     *
+     * @return length of the collection
+     */
     int getLength();
 
+    /**
+     * Get the value as an integer if available
+     *
+     * @param index the position in the collection
+     * @return it value as and integer
+     */
     default int getIntValue(int index) {
         return (int) getFloatValue(index);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java
index 4f12872..b92f96f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java
@@ -24,13 +24,39 @@
 public interface CollectionsAccess {
     float getFloatValue(int id, int index);
 
+    /**
+     * Get the array of float if it is a float array
+     *
+     * @param id the id of the float array
+     * @return
+     */
     @Nullable
     float[] getFloats(int id);
 
+    /**
+     * Get the number of entries in the list
+     *
+     * @param id the id of the list
+     * @return
+     */
     int getListLength(int id);
 
+    /**
+     * get the id of an entry if the list is a list of id's
+     *
+     * @param listId the list id
+     * @param index the index into the list
+     * @return
+     */
     int getId(int listId, int index);
 
+    /**
+     * Get the value as an integer
+     *
+     * @param listId the list id to access
+     * @param index the index into the list
+     * @return
+     */
     default int getIntValue(int listId, int index) {
         return (int) getFloatValue(listId, index);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
index f73ab39..0a33511 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
@@ -82,7 +82,7 @@
         for (int i = 0; i < mStack.length; i++) {
             int v = mStack[i];
             if (((1 << i) & mask) != 0) {
-                sp = mOps[v - OFFSET].eval(sp);
+                sp = opEval(sp, v);
             } else {
                 mStack[++sp] = v;
             }
@@ -107,7 +107,7 @@
         for (int i = 0; i < len; i++) {
             int v = mStack[i];
             if (((1 << i) & mask) != 0) {
-                sp = mOps[v - OFFSET].eval(sp);
+                sp = opEval(sp, v);
             } else {
                 mStack[++sp] = v;
             }
@@ -130,7 +130,7 @@
         for (int i = 0; i < exp.length; i++) {
             int v = mStack[i];
             if (((1 << i) & opMask) != 0) {
-                sp = mOps[v - OFFSET].eval(sp);
+                sp = opEval(sp, v);
             } else {
                 mStack[++sp] = v;
             }
@@ -138,171 +138,140 @@
         return mStack[sp];
     }
 
-    @NonNull Op[] mOps;
+    private static final int OP_ADD = OFFSET + 1;
+    private static final int OP_SUB = OFFSET + 2;
+    private static final int OP_MUL = OFFSET + 3;
+    private static final int OP_DIV = OFFSET + 4;
+    private static final int OP_MOD = OFFSET + 5;
+    private static final int OP_SHL = OFFSET + 6;
+    private static final int OP_SHR = OFFSET + 7;
+    private static final int OP_USHR = OFFSET + 8;
+    private static final int OP_OR = OFFSET + 9;
+    private static final int OP_AND = OFFSET + 10;
+    private static final int OP_XOR = OFFSET + 11;
+    private static final int OP_COPY_SIGN = OFFSET + 12;
+    private static final int OP_MIN = OFFSET + 13;
+    private static final int OP_MAX = OFFSET + 14;
+    private static final int OP_NEG = OFFSET + 15;
+    private static final int OP_ABS = OFFSET + 16;
+    private static final int OP_INCR = OFFSET + 17;
+    private static final int OP_DECR = OFFSET + 18;
+    private static final int OP_NOT = OFFSET + 19;
+    private static final int OP_SIGN = OFFSET + 20;
+    private static final int OP_CLAMP = OFFSET + 21;
+    private static final int OP_TERNARY_CONDITIONAL = OFFSET + 22;
+    private static final int OP_MAD = OFFSET + 23;
+    private static final int OP_FIRST_VAR = OFFSET + 24;
+    private static final int OP_SECOND_VAR = OFFSET + 25;
+    private static final int OP_THIRD_VAR = OFFSET + 26;
 
-    {
-        Op mADD =
-                (sp) -> { // ADD
-                    mStack[sp - 1] = mStack[sp - 1] + mStack[sp];
-                    return sp - 1;
-                };
-        Op mSUB =
-                (sp) -> { // SUB
-                    mStack[sp - 1] = mStack[sp - 1] - mStack[sp];
-                    return sp - 1;
-                };
-        Op mMUL =
-                (sp) -> { // MUL
-                    mStack[sp - 1] = mStack[sp - 1] * mStack[sp];
-                    return sp - 1;
-                };
-        Op mDIV =
-                (sp) -> { // DIV
-                    mStack[sp - 1] = mStack[sp - 1] / mStack[sp];
-                    return sp - 1;
-                };
-        Op mMOD =
-                (sp) -> { // MOD
-                    mStack[sp - 1] = mStack[sp - 1] % mStack[sp];
-                    return sp - 1;
-                };
-        Op mSHL =
-                (sp) -> { // SHL
-                    mStack[sp - 1] = mStack[sp - 1] << mStack[sp];
-                    return sp - 1;
-                };
-        Op mSHR =
-                (sp) -> { // SHR
-                    mStack[sp - 1] = mStack[sp - 1] >> mStack[sp];
-                    return sp - 1;
-                };
-        Op mUSHR =
-                (sp) -> { // USHR
-                    mStack[sp - 1] = mStack[sp - 1] >>> mStack[sp];
-                    return sp - 1;
-                };
-        Op mOR =
-                (sp) -> { // OR
-                    mStack[sp - 1] = mStack[sp - 1] | mStack[sp];
-                    return sp - 1;
-                };
-        Op mAND =
-                (sp) -> { // AND
-                    mStack[sp - 1] = mStack[sp - 1] & mStack[sp];
-                    return sp - 1;
-                };
-        Op mXOR =
-                (sp) -> { // XOR
-                    mStack[sp - 1] = mStack[sp - 1] ^ mStack[sp];
-                    return sp - 1;
-                };
-        Op mCOPY_SIGN =
-                (sp) -> { // COPY_SIGN copy the sign via bit manipulation
-                    mStack[sp - 1] = (mStack[sp - 1] ^ (mStack[sp] >> 31)) - (mStack[sp] >> 31);
-                    return sp - 1;
-                };
-        Op mMIN =
-                (sp) -> { // MIN
-                    mStack[sp - 1] = Math.min(mStack[sp - 1], mStack[sp]);
-                    return sp - 1;
-                };
-        Op mMAX =
-                (sp) -> { // MAX
-                    mStack[sp - 1] = Math.max(mStack[sp - 1], mStack[sp]);
-                    return sp - 1;
-                };
-        Op mNEG =
-                (sp) -> { // NEG
-                    mStack[sp] = -mStack[sp];
-                    return sp;
-                };
-        Op mABS =
-                (sp) -> { // ABS
-                    mStack[sp] = Math.abs(mStack[sp]);
-                    return sp;
-                };
-        Op mINCR =
-                (sp) -> { // INCR
-                    mStack[sp] = mStack[sp] + 1;
-                    return sp;
-                };
-        Op mDECR =
-                (sp) -> { // DECR
-                    mStack[sp] = mStack[sp] - 1;
-                    return sp;
-                };
-        Op mNOT =
-                (sp) -> { // NOT
-                    mStack[sp] = ~mStack[sp];
-                    return sp;
-                };
-        Op mSIGN =
-                (sp) -> { // SIGN x<0 = -1,x==0 =  0 , x>0 = 1
-                    mStack[sp] = (mStack[sp] >> 31) | (-mStack[sp] >>> 31);
-                    return sp;
-                };
-        Op mCLAMP =
-                (sp) -> { // CLAMP(min,max, val)
-                    mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]);
-                    return sp - 2;
-                };
-        Op mTERNARY_CONDITIONAL =
-                (sp) -> { // TERNARY_CONDITIONAL
-                    mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2];
-                    return sp - 2;
-                };
-        Op mMAD =
-                (sp) -> { // MAD
-                    mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2];
-                    return sp - 2;
-                };
-        Op mFIRST_VAR =
-                (sp) -> { // FIRST_VAR
-                    mStack[sp] = mVar[0];
-                    return sp;
-                };
-        Op mSECOND_VAR =
-                (sp) -> { // SECOND_VAR
-                    mStack[sp] = mVar[1];
-                    return sp;
-                };
-        Op mTHIRD_VAR =
-                (sp) -> { // THIRD_VAR
-                    mStack[sp] = mVar[2];
-                    return sp;
-                };
+    int opEval(int sp, int id) {
+        switch (id) {
+            case OP_ADD: // ADD
+                mStack[sp - 1] = mStack[sp - 1] + mStack[sp];
+                return sp - 1;
 
-        Op[] ops = {
-            null,
-            mADD,
-            mSUB,
-            mMUL,
-            mDIV,
-            mMOD,
-            mSHL,
-            mSHR,
-            mUSHR,
-            mOR,
-            mAND,
-            mXOR,
-            mCOPY_SIGN,
-            mMIN,
-            mMAX,
-            mNEG,
-            mABS,
-            mINCR,
-            mDECR,
-            mNOT,
-            mSIGN,
-            mCLAMP,
-            mTERNARY_CONDITIONAL,
-            mMAD,
-            mFIRST_VAR,
-            mSECOND_VAR,
-            mTHIRD_VAR,
-        };
+            case OP_SUB: // SUB
+                mStack[sp - 1] = mStack[sp - 1] - mStack[sp];
+                return sp - 1;
 
-        mOps = ops;
+            case OP_MUL: // MUL
+                mStack[sp - 1] = mStack[sp - 1] * mStack[sp];
+                return sp - 1;
+
+            case OP_DIV: // DIV
+                mStack[sp - 1] = mStack[sp - 1] / mStack[sp];
+                return sp - 1;
+
+            case OP_MOD: // MOD
+                mStack[sp - 1] = mStack[sp - 1] % mStack[sp];
+                return sp - 1;
+
+            case OP_SHL: // SHL
+                mStack[sp - 1] = mStack[sp - 1] << mStack[sp];
+                return sp - 1;
+
+            case OP_SHR: // SHR
+                mStack[sp - 1] = mStack[sp - 1] >> mStack[sp];
+                return sp - 1;
+
+            case OP_USHR: // USHR
+                mStack[sp - 1] = mStack[sp - 1] >>> mStack[sp];
+                return sp - 1;
+
+            case OP_OR: // OR
+                mStack[sp - 1] = mStack[sp - 1] | mStack[sp];
+                return sp - 1;
+
+            case OP_AND: // AND
+                mStack[sp - 1] = mStack[sp - 1] & mStack[sp];
+                return sp - 1;
+
+            case OP_XOR: // XOR
+                mStack[sp - 1] = mStack[sp - 1] ^ mStack[sp];
+                return sp - 1;
+
+            case OP_COPY_SIGN: // COPY_SIGN copy the sign via bit manipulation
+                mStack[sp - 1] = (mStack[sp - 1] ^ (mStack[sp] >> 31)) - (mStack[sp] >> 31);
+                return sp - 1;
+
+            case OP_MIN: // MIN
+                mStack[sp - 1] = Math.min(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+
+            case OP_MAX: // MAX
+                mStack[sp - 1] = Math.max(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+
+            case OP_NEG: // NEG
+                mStack[sp] = -mStack[sp];
+                return sp;
+
+            case OP_ABS: // ABS
+                mStack[sp] = Math.abs(mStack[sp]);
+                return sp;
+
+            case OP_INCR: // INCR
+                mStack[sp] = mStack[sp] + 1;
+                return sp;
+
+            case OP_DECR: // DECR
+                mStack[sp] = mStack[sp] - 1;
+                return sp;
+
+            case OP_NOT: // NOT
+                mStack[sp] = ~mStack[sp];
+                return sp;
+
+            case OP_SIGN: // SIGN x<0 = -1,x==0 =  0 , x>0 = 1
+                mStack[sp] = (mStack[sp] >> 31) | (-mStack[sp] >>> 31);
+                return sp;
+
+            case OP_CLAMP: // CLAMP(min,max, val)
+                mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]);
+                return sp - 2;
+
+            case OP_TERNARY_CONDITIONAL: // TERNARY_CONDITIONAL
+                mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2];
+                return sp - 2;
+
+            case OP_MAD: // MAD
+                mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2];
+                return sp - 2;
+
+            case OP_FIRST_VAR: // FIRST_VAR
+                mStack[sp] = mVar[0];
+                return sp;
+
+            case OP_SECOND_VAR: // SECOND_VAR
+                mStack[sp] = mVar[1];
+                return sp;
+
+            case OP_THIRD_VAR: // THIRD_VAR
+                mStack[sp] = mVar[2];
+                return sp;
+        }
+        return 0;
     }
 
     static {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java
index 7e02bc9..c7e2442 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java
@@ -31,6 +31,11 @@
  * limitations under the License.
  */
 
+/**
+ * This computes an form of easing such that the values constrained to be consistent in velocity The
+ * easing function is also constrained by the configure To have: a maximum time to stop, a maximum
+ * velocity, a maximum acceleration
+ */
 public class VelocityEasing {
     private float mStartPos = 0;
     private float mStartV = 0;
@@ -46,6 +51,11 @@
     private boolean mOneDimension = true;
     private float mTotalEasingDuration = 0;
 
+    /**
+     * get the duration the easing will take
+     *
+     * @return the duration for the easing
+     */
     public float getDuration() {
         if (mEasing != null) {
             return mTotalEasingDuration;
@@ -53,6 +63,12 @@
         return mDuration;
     }
 
+    /**
+     * Get the velocity at time t
+     *
+     * @param t time in seconds
+     * @return the velocity units/second
+     */
     public float getV(float t) {
         if (mEasing == null) {
             for (int i = 0; i < mNumberOfStages; i++) {
@@ -71,6 +87,12 @@
         return (float) getEasingDiff((t - mStage[lastStages].mStartTime));
     }
 
+    /**
+     * Get the position t seconds after the configure
+     *
+     * @param t time in seconds
+     * @return the position at time t
+     */
     public float getPos(float t) {
         if (mEasing == null) {
             for (int i = 0; i < mNumberOfStages; i++) {
@@ -91,6 +113,7 @@
         return ret;
     }
 
+    @Override
     public String toString() {
         var s = " ";
         for (int i = 0; i < mNumberOfStages; i++) {
@@ -100,6 +123,17 @@
         return s;
     }
 
+    /**
+     * Configure the Velocity easing curve The system is in arbitrary units
+     *
+     * @param currentPos the current position
+     * @param destination the destination
+     * @param currentVelocity the current velocity units/seconds
+     * @param maxTime the max time to achieve position
+     * @param maxAcceleration the max acceleration units/s^2
+     * @param maxVelocity the maximum velocity
+     * @param easing End in using this easing curve
+     */
     public void config(
             float currentPos,
             float destination,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilityModifier.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilityModifier.java
new file mode 100644
index 0000000..cd8b7b8
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilityModifier.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.semantics;
+
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+
+/** A Modifier that provides semantic info. */
+public interface AccessibilityModifier extends ModifierOperation, AccessibleComponent {
+    int getOpCode();
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilitySemantics.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilitySemantics.java
new file mode 100644
index 0000000..291ad47
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilitySemantics.java
@@ -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.internal.widget.remotecompose.core.semantics;
+
+/** Marker interface for a Component or Modifier that is relevant for Semantics. */
+public interface AccessibilitySemantics {
+
+    /**
+     * Determines if this element is interesting for semantic analysis.
+     *
+     * <p>This method is used to filter elements during semantic analysis. By default, all elements
+     * are considered interesting. Subclasses can override this method to exclude specific elements
+     * from semantic analysis.
+     *
+     * @return {@code true} if this element is interesting for semantic analysis, {@code false}
+     *     otherwise.
+     */
+    default boolean isInterestingForSemantics() {
+        return true;
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java
new file mode 100644
index 0000000..e07fc4d
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.semantics;
+
+import android.annotation.Nullable;
+
+public interface AccessibleComponent extends AccessibilitySemantics {
+    default @Nullable Integer getContentDescriptionId() {
+        return null;
+    }
+
+    default @Nullable Integer getTextId() {
+        return null;
+    }
+
+    default @Nullable Role getRole() {
+        return null;
+    }
+
+    default boolean isClickable() {
+        return false;
+    }
+
+    default CoreSemantics.Mode getMode() {
+        return CoreSemantics.Mode.SET;
+    }
+
+    // Our master list
+    // https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/Role
+    enum Role {
+        BUTTON("Button"),
+        CHECKBOX("Checkbox"),
+        SWITCH("Switch"),
+        RADIO_BUTTON("RadioButton"),
+        TAB("Tab"),
+        IMAGE("Image"),
+        DROPDOWN_LIST("DropdownList"),
+        PICKER("Picker"),
+        CAROUSEL("Carousel"),
+        UNKNOWN(null);
+
+        @Nullable private final String mDescription;
+
+        Role(@Nullable String description) {
+            this.mDescription = description;
+        }
+
+        @Nullable
+        public String getDescription() {
+            return mDescription;
+        }
+
+        public static Role fromInt(int i) {
+            if (i < UNKNOWN.ordinal()) {
+                return Role.values()[i];
+            }
+            return Role.UNKNOWN;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java
new file mode 100644
index 0000000..4047dd2
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.semantics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Implementation of the most common semantics used in typical Android apps. */
+public class CoreSemantics extends Operation implements AccessibilityModifier {
+    public int mContentDescriptionId = 0;
+    public @Nullable Role mRole = null;
+    public int mTextId = 0;
+    public int mStateDescriptionId = 0;
+    public boolean mEnabled = true;
+    public Mode mMode = Mode.SET;
+    public boolean mClickable = false;
+
+    @Override
+    public int getOpCode() {
+        return Operations.ACCESSIBILITY_SEMANTICS;
+    }
+
+    @Nullable
+    @Override
+    public Role getRole() {
+        return mRole;
+    }
+
+    @Override
+    public Mode getMode() {
+        return mMode;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        buffer.writeInt(mContentDescriptionId);
+        buffer.writeByte((mRole != null) ? mRole.ordinal() : -1);
+        buffer.writeInt(mTextId);
+        buffer.writeInt(mStateDescriptionId);
+        buffer.writeByte(mMode.ordinal());
+        buffer.writeBoolean(mEnabled);
+        buffer.writeBoolean(mClickable);
+    }
+
+    private void read(WireBuffer buffer) {
+        mContentDescriptionId = buffer.readInt();
+        mRole = Role.fromInt(buffer.readByte());
+        mTextId = buffer.readInt();
+        mStateDescriptionId = buffer.readInt();
+        mMode = Mode.values()[buffer.readByte()];
+        mEnabled = buffer.readBoolean();
+        mClickable = buffer.readBoolean();
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        // Handled via touch helper
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("SEMANTICS");
+        if (mMode != Mode.SET) {
+            builder.append(" ");
+            builder.append(mMode);
+        }
+        if (mRole != null) {
+            builder.append(" ");
+            builder.append(mRole);
+        }
+        if (mContentDescriptionId > 0) {
+            builder.append(" contentDescription=");
+            builder.append(mContentDescriptionId);
+        }
+        if (mTextId > 0) {
+            builder.append(" text=");
+            builder.append(mTextId);
+        }
+        if (mStateDescriptionId > 0) {
+            builder.append(" stateDescription=");
+            builder.append(mStateDescriptionId);
+        }
+        if (!mEnabled) {
+            builder.append(" disabled");
+        }
+        if (mClickable) {
+            builder.append(" clickable");
+        }
+        return builder.toString();
+    }
+
+    @Nullable
+    @Override
+    public String deepToString(String indent) {
+        return indent + this;
+    }
+
+    @NonNull
+    public String serializedName() {
+        return "SEMANTICS";
+    }
+
+    @Override
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+        serializer.append(indent, serializedName() + " = " + this);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        CoreSemantics semantics = new CoreSemantics();
+
+        semantics.read(buffer);
+
+        operations.add(semantics);
+    }
+
+    @Override
+    public Integer getContentDescriptionId() {
+        return mContentDescriptionId != 0 ? mContentDescriptionId : null;
+    }
+
+    public @Nullable Integer getStateDescriptionId() {
+        return mStateDescriptionId != 0 ? mStateDescriptionId : null;
+    }
+
+    public @Nullable Integer getTextId() {
+        return mTextId != 0 ? mTextId : null;
+    }
+
+    public enum Mode {
+        SET,
+        CLEAR_AND_SET,
+        MERGE
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
index 4af79f3..2c874b1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
@@ -29,7 +29,7 @@
 import java.util.List;
 
 /** Used to represent a boolean */
-public class BooleanConstant implements Operation {
+public class BooleanConstant extends Operation {
     private static final int OP_CODE = Operations.DATA_BOOLEAN;
     private boolean mValue = false;
     private int mId;
@@ -68,11 +68,21 @@
         return "BooleanConstant[" + mId + "] = " + mValue + "";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "OrigamiBoolean";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.DATA_BOOLEAN;
     }
@@ -90,6 +100,12 @@
         buffer.writeBoolean(value);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
 
@@ -97,6 +113,11 @@
         operations.add(new BooleanConstant(id, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, "BooleanConstant")
                 .description("A boolean and its associated id")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
index 613e732..5462d3e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
@@ -29,7 +29,7 @@
 import java.util.List;
 
 /** Represents a single integer typically used for states or named for input into the system */
-public class IntegerConstant implements Operation {
+public class IntegerConstant extends Operation {
     private int mValue = 0;
     private int mId;
 
@@ -60,11 +60,21 @@
         return "IntegerConstant[" + mId + "] = " + mValue + "";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "IntegerConstant";
     }
 
+    /**
+     * The OP_CODE for this command
+     *
+     * @return the opcode
+     */
     public static int id() {
         return Operations.DATA_INT;
     }
@@ -82,6 +92,12 @@
         buffer.writeInt(value);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
 
@@ -89,6 +105,11 @@
         operations.add(new IntegerConstant(id, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", id(), "IntegerConstant")
                 .description("A integer and its associated id")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
index 745caa3..1a3cdb1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
@@ -29,7 +29,7 @@
 import java.util.List;
 
 /** Used to represent a long */
-public class LongConstant implements Operation {
+public class LongConstant extends Operation {
     private static final int OP_CODE = Operations.DATA_LONG;
     private long mValue;
     private int mId;
@@ -83,6 +83,12 @@
         buffer.writeLong(value);
     }
 
+    /**
+     * Read this operation and add it to the list of operations
+     *
+     * @param buffer the buffer to read
+     * @param operations the list of operations that will be added to
+     */
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
 
@@ -90,6 +96,11 @@
         operations.add(new LongConstant(id, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, "LongConstant")
                 .description("A boolean and its associated id")
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
index 648f7bf..7dad293 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -32,6 +32,7 @@
 import android.widget.HorizontalScrollView;
 import android.widget.ScrollView;
 
+import com.android.internal.widget.remotecompose.accessibility.RemoteComposeTouchHelper;
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
@@ -61,6 +62,15 @@
     }
 
     /**
+     * Returns true if the document supports drag touch events
+     *
+     * @return true if draggable content, false otherwise
+     */
+    public boolean isDraggable() {
+        return mInner.isDraggable();
+    }
+
+    /**
      * Turn on debug information
      *
      * @param debugFlags 1 to set debug on
@@ -83,8 +93,12 @@
             } else {
                 Log.e("RemoteComposePlayer", "Unsupported document ");
             }
+
+            RemoteComposeTouchHelper.REGISTRAR.setAccessibilityDelegate(this, value.getDocument());
         } else {
             mInner.setDocument(null);
+
+            RemoteComposeTouchHelper.REGISTRAR.clearAccessibilityDelegate(this);
         }
         mapColors();
         setupSensors();
@@ -96,6 +110,7 @@
                         provideHapticFeedback(type);
                     }
                 });
+        mInner.checkShaders(mShaderControl);
     }
 
     /**
@@ -235,22 +250,23 @@
         mInner.clearLocalString("SYSTEM:" + name);
     }
 
-    public interface ClickCallbacks {
-        void click(int id, String metadata);
+    /** Id action callback interface */
+    public interface IdActionCallbacks {
+        void onAction(int id, String metadata);
     }
 
     /**
-     * Add a callback for handling click events on the document
+     * Add a callback for handling id actions events on the document
      *
-     * @param callback the callback lambda that will be used when a click is detected
+     * @param callback the callback lambda that will be used when a action is executed
      *     <p>The parameter of the callback are:
      *     <ul>
-     *       <li>id : the id of the clicked area
-     *       <li>metadata: a client provided unstructured string associated with that area
+     *       <li>id : the id of the action
+     *       <li>metadata: a client provided unstructured string associated with that id action
      *     </ul>
      */
-    public void addClickListener(ClickCallbacks callback) {
-        mInner.addClickListener((id, metadata) -> callback.click(id, metadata));
+    public void addIdActionListener(IdActionCallbacks callback) {
+        mInner.addIdActionListener((id, metadata) -> callback.onAction(id, metadata));
     }
 
     /**
@@ -659,4 +675,29 @@
         }
         mListener = null;
     }
+
+    /**
+     * This returns the amount of time in ms the player used to evalueate a pass it is averaged over
+     * a number of evaluations.
+     *
+     * @return time in ms
+     */
+    public float getEvalTime() {
+        return mInner.getEvalTime();
+    }
+
+    private CoreDocument.ShaderControl mShaderControl =
+            (shader) -> {
+                return false;
+            };
+
+    /**
+     * Sets the controller for shaders. Note set before loading the document. The default is to not
+     * accept shaders.
+     *
+     * @param ctl the controller
+     */
+    public void setShaderControl(CoreDocument.ShaderControl ctl) {
+        mShaderControl = ctl;
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
index 3c91cff..0712ea4 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
@@ -250,7 +250,7 @@
     @Override
     public void getTextBounds(int textId, int start, int end, int flags, @NonNull float[] bounds) {
         String str = getText(textId);
-        if (end == -1) {
+        if (end == -1 || end > str.length()) {
             end = str.length();
         }
 
@@ -505,6 +505,9 @@
                             return;
                         }
                         ShaderData data = getShaderData(shaderId);
+                        if (data == null) {
+                            return;
+                        }
                         RuntimeShader shader = new RuntimeShader(getText(data.getShaderTextId()));
                         String[] names = data.getUniformFloatNames();
                         for (int i = 0; i < names.length; i++) {
@@ -529,6 +532,7 @@
                     @Override
                     public void setImageFilterQuality(int quality) {
                         Utils.log(" quality =" + quality);
+                        mPaint.setFilterBitmap(quality == 1);
                     }
 
                     @Override
@@ -711,20 +715,32 @@
     }
 
     @Override
+    public void tweenPath(int out, int path1, int path2, float tween) {
+        float[] p = getPathArray(path1, path2, tween);
+        AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
+        androidContext.mRemoteComposeState.putPathData(out, p);
+    }
+
+    @Override
     public void reset() {
         mPaint.reset();
     }
 
     private Path getPath(int path1Id, int path2Id, float tween, float start, float end) {
+        return getPath(getPathArray(path1Id, path2Id, tween), start, end);
+    }
+
+    private float[] getPathArray(int path1Id, int path2Id, float tween) {
+        AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
         if (tween == 0.0f) {
-            return getPath(path1Id, start, end);
+            return androidContext.mRemoteComposeState.getPathData(path1Id);
         }
         if (tween == 1.0f) {
-            return getPath(path2Id, start, end);
+            return androidContext.mRemoteComposeState.getPathData(path2Id);
         }
-        AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
-        float[] data1 = (float[]) androidContext.mRemoteComposeState.getFromId(path1Id);
-        float[] data2 = (float[]) androidContext.mRemoteComposeState.getFromId(path2Id);
+
+        float[] data1 = androidContext.mRemoteComposeState.getPathData(path1Id);
+        float[] data2 = androidContext.mRemoteComposeState.getPathData(path2Id);
         float[] tmp = new float[data2.length];
         for (int i = 0; i < tmp.length; i++) {
             if (Float.isNaN(data1[i]) || Float.isNaN(data2[i])) {
@@ -733,6 +749,10 @@
                 tmp[i] = (data2[i] - data1[i]) * tween + data1[i];
             }
         }
+        return tmp;
+    }
+
+    private Path getPath(float[] tmp, float start, float end) {
         Path path = new Path();
         FloatsToPath.genPath(path, tmp, start, end);
         return path;
@@ -740,11 +760,17 @@
 
     private Path getPath(int id, float start, float end) {
         AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
-        Path path = new Path();
-        if (androidContext.mRemoteComposeState.containsId(id)) {
-            float[] data = (float[]) androidContext.mRemoteComposeState.getFromId(id);
-            FloatsToPath.genPath(path, data, start, end);
+        Path p = (Path) androidContext.mRemoteComposeState.getPath(id);
+        if (p != null) {
+            return p;
         }
+        Path path = new Path();
+        float[] pathData = androidContext.mRemoteComposeState.getPathData(id);
+        if (pathData != null) {
+            FloatsToPath.genPath(path, pathData, start, end);
+            androidContext.mRemoteComposeState.putPath(id, path);
+        }
+
         return path;
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
index 77c2514..0fb0a28 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
@@ -60,9 +60,12 @@
 
     @Override
     public void loadPathData(int instanceId, @NonNull float[] floatPath) {
-        if (!mRemoteComposeState.containsId(instanceId)) {
-            mRemoteComposeState.cacheData(instanceId, floatPath);
-        }
+        mRemoteComposeState.putPathData(instanceId, floatPath);
+    }
+
+    @Override
+    public float[] getPathData(int instanceId) {
+        return mRemoteComposeState.getPathData(instanceId);
     }
 
     static class VarName {
@@ -162,7 +165,7 @@
      * @param type the type of the data 0 = RGBA 8888, 1 = 888, 2 = 8 gray
      * @param width with of image to be loaded largest dimension is 32767
      * @param height height of image to be loaded
-     * @param bitmap a byte array containing the image information
+     * @param data a byte array containing the image information
      * @oaram imageId the id of the image
      */
     @Override
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
index 8f55f8a..c7b1166 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
@@ -205,19 +205,46 @@
         return count;
     }
 
+    /**
+     * set a float externally
+     *
+     * @param id
+     * @param value
+     */
     public void setExternalFloat(int id, float value) {
         mARContext.loadFloat(id, value);
     }
 
+    /**
+     * Returns true if the document supports drag touch events
+     *
+     * @return true if draggable content, false otherwise
+     */
+    public boolean isDraggable() {
+        if (mDocument == null) {
+            return false;
+        }
+        return mDocument.getDocument().hasTouchListener();
+    }
+
+    /**
+     * Check shaders and disable them
+     *
+     * @param shaderControl the callback to validate the shader
+     */
+    public void checkShaders(CoreDocument.ShaderControl shaderControl) {
+        mDocument.getDocument().checkShaders(mARContext, shaderControl);
+    }
+
     public interface ClickCallbacks {
         void click(int id, String metadata);
     }
 
-    public void addClickListener(ClickCallbacks callback) {
+    public void addIdActionListener(ClickCallbacks callback) {
         if (mDocument == null) {
             return;
         }
-        mDocument.getDocument().addClickListener((id, metadata) -> callback.click(id, metadata));
+        mDocument.getDocument().addIdActionListener((id, metadata) -> callback.click(id, metadata));
     }
 
     public int getTheme() {
@@ -241,9 +268,9 @@
             case MotionEvent.ACTION_DOWN:
                 mActionDownPoint.x = (int) event.getX();
                 mActionDownPoint.y = (int) event.getY();
-                mInActionDown = true;
                 CoreDocument doc = mDocument.getDocument();
                 if (doc.hasTouchListener()) {
+                    mInActionDown = true;
                     if (mVelocityTracker == null) {
                         mVelocityTracker = VelocityTracker.obtain();
                     } else {
@@ -251,8 +278,10 @@
                     }
                     mVelocityTracker.addMovement(event);
                     doc.touchDown(mARContext, event.getX(), event.getY());
+                    invalidate();
+                    return true;
                 }
-                return true;
+                return false;
 
             case MotionEvent.ACTION_CANCEL:
                 mInActionDown = false;
@@ -262,8 +291,11 @@
                     float dx = mVelocityTracker.getXVelocity(pointerId);
                     float dy = mVelocityTracker.getYVelocity(pointerId);
                     doc.touchCancel(mARContext, event.getX(), event.getY(), dx, dy);
+                    invalidate();
+                    return true;
                 }
-                return true;
+                return false;
+
             case MotionEvent.ACTION_UP:
                 mInActionDown = false;
                 performClick();
@@ -273,8 +305,10 @@
                     float dx = mVelocityTracker.getXVelocity(pointerId);
                     float dy = mVelocityTracker.getYVelocity(pointerId);
                     doc.touchUp(mARContext, event.getX(), event.getY(), dx, dy);
+                    invalidate();
+                    return true;
                 }
-                return true;
+                return false;
 
             case MotionEvent.ACTION_MOVE:
                 if (mInActionDown) {
@@ -286,7 +320,9 @@
                             invalidate();
                         }
                     }
+                    return true;
                 }
+                return false;
         }
         return false;
     }
@@ -349,6 +385,27 @@
 
     private int mCount;
     private long mTime = System.nanoTime();
+    private long mDuration;
+    private boolean mEvalTime = false;
+
+    /**
+     * This returns the amount of time in ms the player used to evalueate a pass it is averaged over
+     * a number of evaluations.
+     *
+     * @return time in ms
+     */
+    public float getEvalTime() {
+        if (!mEvalTime) {
+            mEvalTime = true;
+            return 0.0f;
+        }
+        double avg = mDuration / (double) mCount;
+        if (mCount > 100) {
+            mDuration /= 2;
+            mCount /= 2;
+        }
+        return (float) (avg * 1E-6); // ms
+    }
 
     @Override
     protected void onDraw(Canvas canvas) {
@@ -356,6 +413,7 @@
         if (mDocument == null) {
             return;
         }
+        long start = mEvalTime ? System.nanoTime() : 0;
         mARContext.setAnimationEnabled(true);
         mARContext.currentTime = System.currentTimeMillis();
         mARContext.setDebug(mDebug);
@@ -376,5 +434,9 @@
         if (mDocument.needsRepaint() > 0) {
             invalidate();
         }
+        if (mEvalTime) {
+            mDuration += System.nanoTime() - start;
+            mCount++;
+        }
     }
 }
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index d05f5e3..70dd10f 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -875,6 +875,14 @@
     int getMemtagMode();
 
     /**
+     * @see ApplicationInfo#getPageSizeAppCompatFlags()
+     * @see R.styleable#AndroidManifestApplication_pageSizeCompat
+     * @hide
+     */
+    @ApplicationInfo.PageSizeAppCompatFlags
+    int getPageSizeAppCompatFlags();
+
+    /**
      * TODO(b/135203078): Make all the Bundles immutable (and non-null by shared empty reference?)
      * @see R.styleable#AndroidManifestMetaData
      * @hide
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 5c03c5c..b7a7f96 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -214,6 +214,7 @@
                 "android_media_ToneGenerator.cpp",
                 "android_hardware_Camera.cpp",
                 "android_hardware_camera2_CameraMetadata.cpp",
+                "android_hardware_camera2_CameraDevice.cpp",
                 "android_hardware_camera2_DngCreator.cpp",
                 "android_hardware_camera2_impl_CameraExtensionJpegProcessor.cpp",
                 "android_hardware_camera2_utils_SurfaceUtils.cpp",
@@ -278,6 +279,7 @@
                 "libasync_safe",
                 "libbinderthreadstateutils",
                 "libdmabufinfo",
+                "libgenfslabelsversion.ffi",
                 "libgui_window_info_static",
                 "libkernelconfigs",
                 "libnativehelper_lazy",
@@ -304,7 +306,12 @@
                 "av-types-aidl-cpp",
                 "android.hardware.camera.device@3.2",
                 "camera_platform_flags_c_lib",
+                "android.hardware.common.fmq-V1-cpp",
+                "android.hardware.common-V2-cpp",
+                "android.hardware.common.fmq-V1-ndk",
+                "android.hardware.common-V2-ndk",
                 "libandroid_net",
+                "libfmq",
                 "libbattery",
                 "libnetdutils",
                 "libmemtrack",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 00a6297..ac187b0 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -77,6 +77,7 @@
 
 extern int register_android_hardware_Camera(JNIEnv *env);
 extern int register_android_hardware_camera2_CameraMetadata(JNIEnv *env);
+extern int register_android_hardware_camera2_CameraDevice(JNIEnv *env);
 extern int register_android_hardware_camera2_DngCreator(JNIEnv *env);
 extern int register_android_hardware_camera2_impl_CameraExtensionJpegProcessor(JNIEnv* env);
 extern int register_android_hardware_camera2_utils_SurfaceUtils(JNIEnv* env);
@@ -1623,6 +1624,7 @@
         REG_JNI(register_com_android_internal_util_VirtualRefBasePtr),
         REG_JNI(register_android_hardware_Camera),
         REG_JNI(register_android_hardware_camera2_CameraMetadata),
+        REG_JNI(register_android_hardware_camera2_CameraDevice),
         REG_JNI(register_android_hardware_camera2_DngCreator),
         REG_JNI(register_android_hardware_camera2_impl_CameraExtensionJpegProcessor),
         REG_JNI(register_android_hardware_camera2_utils_SurfaceUtils),
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index ded1a99..1e7bfe3 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -36,6 +36,8 @@
 
 namespace android {
 
+static constexpr bool kLogWeakReachableDeletedAssets = false;
+
 static struct overlayableinfo_offsets_t {
   jclass classObject;
   jmethodID constructor;
@@ -97,7 +99,7 @@
       if (useCount > 1) {
         ALOGW("ApkAssets: Deleting an object '%s' with %d > 1 strong and %d weak references",
               (*assets)->GetDebugName().c_str(), int(useCount), int(weakCount));
-      } else if (weakCount > 0) {
+      } else if constexpr (kLogWeakReachableDeletedAssets) if (weakCount > 0) {
         ALOGW("ApkAssets: Deleting an ApkAssets object '%s' with %d weak references",
               (*assets)->GetDebugName().c_str(), int(weakCount));
       }
diff --git a/core/jni/android_hardware_camera2_CameraDevice.cpp b/core/jni/android_hardware_camera2_CameraDevice.cpp
new file mode 100644
index 0000000..493c707
--- /dev/null
+++ b/core/jni/android_hardware_camera2_CameraDevice.cpp
@@ -0,0 +1,148 @@
+/*
+**
+** Copyright 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.
+*/
+
+// #define LOG_NDEBUG 0
+
+#define ATRACE_TAG ATRACE_TAG_CAMERA
+
+#include <memory>
+#define LOG_TAG "CameraDevice-JNI"
+#include <utils/Errors.h>
+#include <utils/Log.h>
+#include <utils/Trace.h>
+#include <vector>
+
+#include "jni.h"
+#include <nativehelper/JNIHelp.h>
+#include "android_os_Parcel.h"
+#include "core_jni_helpers.h"
+#include <android/binder_parcel_jni.h>
+#include <android/hardware/camera2/ICameraDeviceUser.h>
+#include <aidl/android/hardware/common/fmq/MQDescriptor.h>
+#include <aidl/android/hardware/common/fmq/SynchronizedReadWrite.h>
+#include <fmq/AidlMessageQueue.h>
+#include <camera/CameraMetadata.h>
+
+using namespace android;
+
+using ::android::AidlMessageQueue;
+using ResultMetadataQueue = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
+
+class FMQReader {
+    public:
+    FMQReader(MQDescriptor<int8_t, SynchronizedReadWrite> &resultMQ) {
+        mCaptureResultMetadataQueue = std::make_shared<ResultMetadataQueue>(resultMQ);
+    }
+    std::shared_ptr<CameraMetadata> readOneResultMetadata(long metadataSize);
+    private:
+    std::shared_ptr<ResultMetadataQueue> mCaptureResultMetadataQueue = nullptr;
+};
+
+std::shared_ptr<CameraMetadata> FMQReader::readOneResultMetadata(long metadataSize) {
+    ATRACE_CALL();
+    if (metadataSize == 0) {
+        return nullptr;
+    }
+    auto metadataVec = std::make_unique<int8_t []>(metadataSize);
+    bool read = mCaptureResultMetadataQueue->read(metadataVec.get(), metadataSize);
+    if (!read) {
+        ALOGE("%s capture metadata could't be read from fmq", __FUNCTION__);
+        return nullptr;
+    }
+
+    // Takes ownership of metadataVec, this doesn't copy
+    std::shared_ptr<CameraMetadata> retVal =
+            std::make_shared<CameraMetadata>(
+                    reinterpret_cast<camera_metadata_t *>(metadataVec.release()));
+    return retVal;
+}
+
+extern "C" {
+
+static jlong CameraDevice_createFMQReader(JNIEnv *env, jclass thiz,
+        jobject resultParcel) {
+    AParcel *resultAParcel = AParcel_fromJavaParcel(env, resultParcel);
+    if (resultAParcel == nullptr) {
+        ALOGE("%s: Error creating result parcel", __FUNCTION__);
+        return 0;
+    }
+    AParcel_setDataPosition(resultAParcel, 0);
+
+    MQDescriptor<int8_t, SynchronizedReadWrite> resultMQ;
+    if (resultMQ.readFromParcel(resultAParcel) != OK) {
+        ALOGE("%s: read from result parcel failed", __FUNCTION__);
+        return 0;
+    }
+    return reinterpret_cast<jlong>(new std::shared_ptr<FMQReader>(
+            new FMQReader(resultMQ)));
+}
+
+static std::shared_ptr<FMQReader>* FMQReader_getSharedPtr(jlong fmqReaderLongPtr) {
+    return reinterpret_cast<std::shared_ptr<FMQReader>* >(fmqReaderLongPtr);
+}
+
+static jlong CameraDevice_readResultMetadata(JNIEnv *env, jclass thiz, jlong ptr,
+        jlong metadataSize) {
+    ALOGV("%s", __FUNCTION__);
+
+    FMQReader *fmqReader = FMQReader_getSharedPtr(ptr)->get();
+    auto metadataSp = fmqReader->readOneResultMetadata(metadataSize);
+    auto retVal = new std::shared_ptr<CameraMetadata>(metadataSp);
+    return reinterpret_cast<jlong>(retVal);
+}
+
+static void CameraDevice_close(JNIEnv *env, jclass thiz, jlong ptr) {
+    ALOGV("%s", __FUNCTION__);
+
+    auto fmqPtr = FMQReader_getSharedPtr(ptr);
+    if (fmqPtr != nullptr) {
+        delete fmqPtr;
+    }
+}
+
+}
+
+//-------------------------------------------------
+#define CAMERA_DEVICE_CLASS_NAME "android/hardware/camera2/impl/CameraDeviceImpl"
+static const JNINativeMethod gCameraDeviceMethods[] = {
+// static methods
+  { "nativeCreateFMQReader",
+    "(Landroid/os/Parcel;)J",
+    (void *)CameraDevice_createFMQReader},
+  { "nativeReadResultMetadata",
+    "(JJ)J",
+    (void *)CameraDevice_readResultMetadata},
+  { "nativeClose",
+    "(J)V",
+    (void*)CameraDevice_close},
+// instance methods
+};
+
+
+// Get all the required offsets in java class and register native functions
+int register_android_hardware_camera2_CameraDevice(JNIEnv *env)
+{
+    // Register native functions
+    return RegisterMethodsOrDie(env,
+            CAMERA_DEVICE_CLASS_NAME,
+            gCameraDeviceMethods,
+            NELEM(gCameraDeviceMethods));
+}
+
+extern "C" {
+
+} // extern "C"
\ No newline at end of file
diff --git a/core/jni/android_os_SELinux.cpp b/core/jni/android_os_SELinux.cpp
index 7a4670f4..805d5ad 100644
--- a/core/jni/android_os_SELinux.cpp
+++ b/core/jni/android_os_SELinux.cpp
@@ -18,18 +18,19 @@
 
 #include <errno.h>
 #include <fcntl.h>
-
-#include <utils/Log.h>
-
+#include <genfslabelsversion.h>
 #include <nativehelper/JNIPlatformHelp.h>
-#include "jni.h"
-#include "core_jni_helpers.h"
-#include "selinux/selinux.h"
-#include "selinux/android.h"
-#include <memory>
-#include <atomic>
 #include <nativehelper/ScopedLocalRef.h>
 #include <nativehelper/ScopedUtfChars.h>
+#include <utils/Log.h>
+
+#include <atomic>
+#include <memory>
+
+#include "core_jni_helpers.h"
+#include "jni.h"
+#include "selinux/android.h"
+#include "selinux/selinux.h"
 
 namespace android {
 namespace {
@@ -404,8 +405,19 @@
 }
 
 /*
+ * Function: getGenfsLabelsVersion
+ * Purpose: get which genfs labels version /vendor uses
+ * Returns: int: genfs labels version of /vendor
+ * Exceptions: none
+ */
+static jint getGenfsLabelsVersion(JNIEnv *, jclass) {
+    return get_genfs_labels_version();
+}
+
+/*
  * JNI registration.
  */
+// clang-format off
 static const JNINativeMethod method_table[] = {
     /* name,                     signature,                    funcPtr */
     { "checkSELinuxAccess"       , "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z" , (void*)checkSELinuxAccess },
@@ -420,7 +432,9 @@
     { "setFileContext"           , "(Ljava/lang/String;Ljava/lang/String;)Z"      , (void*)setFileCon       },
     { "setFSCreateContext"       , "(Ljava/lang/String;)Z"                        , (void*)setFSCreateCon   },
     { "fileSelabelLookup"        , "(Ljava/lang/String;)Ljava/lang/String;"       , (void*)fileSelabelLookup},
+    { "getGenfsLabelsVersion"    , "()I"                                          , (void *)getGenfsLabelsVersion},
 };
+// clang-format on
 
 static int log_callback(int type, const char *fmt, ...) {
     va_list ap;
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index a09c405..7ff1f8c 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -40,6 +40,7 @@
     jmethodID dispatchHotplug;
     jmethodID dispatchHotplugConnectionError;
     jmethodID dispatchModeChanged;
+    jmethodID dispatchModeRejected;
     jmethodID dispatchFrameRateOverrides;
     jmethodID dispatchHdcpLevelsChanged;
 
@@ -95,6 +96,7 @@
     void dispatchHotplugConnectionError(nsecs_t timestamp, int errorCode) override;
     void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
                              nsecs_t renderPeriod) override;
+    void dispatchModeRejected(PhysicalDisplayId displayId, int32_t modeId) override;
     void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
                                     std::vector<FrameRateOverride> overrides) override;
     void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
@@ -271,6 +273,18 @@
     mMessageQueue->raiseAndClearException(env, "dispatchModeChanged");
 }
 
+void NativeDisplayEventReceiver::dispatchModeRejected(PhysicalDisplayId displayId, int32_t modeId) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+    ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
+    if (receiverObj.get()) {
+        ALOGV("receiver %p ~ Invoking Mode Rejected handler.", this);
+        env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchModeRejected,
+                            displayId.value, modeId);
+        ALOGV("receiver %p ~ Returned from Mode Rejected handler.", this);
+    }
+}
+
 void NativeDisplayEventReceiver::dispatchFrameRateOverrides(
         nsecs_t timestamp, PhysicalDisplayId displayId, std::vector<FrameRateOverride> overrides) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -405,6 +419,9 @@
     gDisplayEventReceiverClassInfo.dispatchModeChanged =
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchModeChanged",
                              "(JJIJ)V");
+    gDisplayEventReceiverClassInfo.dispatchModeRejected =
+            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchModeRejected",
+                             "(JI)V");
     gDisplayEventReceiverClassInfo.dispatchFrameRateOverrides =
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz,
                              "dispatchFrameRateOverrides",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 68e6420..0c243d1 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -119,6 +119,7 @@
     jfieldID renderFrameRate;
     jfieldID hasArrSupport;
     jfieldID frameRateCategoryRate;
+    jfieldID supportedRefreshRates;
     jfieldID supportedColorModes;
     jfieldID activeColorMode;
     jfieldID hdrCapabilities;
@@ -1508,6 +1509,21 @@
     env->SetBooleanField(object, gDynamicDisplayInfoClassInfo.hasArrSupport, info.hasArrSupport);
     env->SetObjectField(object, gDynamicDisplayInfoClassInfo.frameRateCategoryRate,
                         convertFrameRateCategoryRateToJavaObject(env, info.frameRateCategoryRate));
+
+    jfloatArray supportedRefreshRatesArray = env->NewFloatArray(info.supportedRefreshRates.size());
+    if (supportedRefreshRatesArray == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return NULL;
+    }
+    jfloat* supportedRefreshRatesArrayValues =
+            env->GetFloatArrayElements(supportedRefreshRatesArray, 0);
+    for (size_t i = 0; i < info.supportedRefreshRates.size(); i++) {
+        supportedRefreshRatesArrayValues[i] = static_cast<jfloat>(info.supportedRefreshRates[i]);
+    }
+    env->ReleaseFloatArrayElements(supportedRefreshRatesArray, supportedRefreshRatesArrayValues, 0);
+    env->SetObjectField(object, gDynamicDisplayInfoClassInfo.supportedRefreshRates,
+                        supportedRefreshRatesArray);
+
     jintArray colorModesArray = env->NewIntArray(info.supportedColorModes.size());
     if (colorModesArray == NULL) {
         jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
@@ -2766,6 +2782,8 @@
     gFrameRateCategoryRateClassInfo.ctor =
             GetMethodIDOrDie(env, frameRateCategoryRateClazz, "<init>", "(FF)V");
 
+    gDynamicDisplayInfoClassInfo.supportedRefreshRates =
+            GetFieldIDOrDie(env, dynamicInfoClazz, "supportedRefreshRates", "[F");
     gDynamicDisplayInfoClassInfo.supportedColorModes =
             GetFieldIDOrDie(env, dynamicInfoClazz, "supportedColorModes", "[I");
     gDynamicDisplayInfoClassInfo.activeColorMode =
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 7ad18b8..b2eeff3 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -21,6 +21,7 @@
 #include <androidfw/ApkParsing.h>
 #include <androidfw/ZipFileRO.h>
 #include <androidfw/ZipUtils.h>
+#include <elf.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
@@ -38,6 +39,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "com_android_internal_content_FileSystemUtils.h"
 #include "core_jni_helpers.h"
@@ -60,6 +62,12 @@
     NO_NATIVE_LIBRARIES = -114
 };
 
+// These code should match with PageSizeAppCompatFlags inside ApplicationInfo.java
+constexpr int PAGE_SIZE_APP_COMPAT_FLAG_ERROR = -1;
+constexpr int PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED = 0;
+constexpr int PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED = 1 << 1;
+constexpr int PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED = 1 << 2;
+
 typedef install_status_t (*iterFunc)(JNIEnv*, void*, ZipFileRO*, ZipEntryRO, const char*);
 
 static bool
@@ -524,11 +532,7 @@
     static const size_t kPageSize = getpagesize();
 
     // App compat is only applicable on 16kb-page-size devices.
-    if (kPageSize != 0x4000) {
-        return false;
-    }
-
-    return android::base::GetBoolProperty("bionic.linker.16kb.app_compat.enabled", false);
+    return kPageSize == 0x4000;
 }
 
 static jint
@@ -626,6 +630,166 @@
     return reinterpret_cast<jlong>(zipFile);
 }
 
+static jint checkLoadSegmentAlignment(const char* fileName, off64_t offset) {
+    std::vector<Elf64_Phdr> programHeaders;
+    if (!getLoadSegmentPhdrs(fileName, offset, programHeaders)) {
+        ALOGE("Failed to read program headers from ELF file.");
+        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+    }
+
+    int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+    for (auto programHeader : programHeaders) {
+        if (programHeader.p_type != PT_LOAD) {
+            continue;
+        }
+
+        // Set ELF alignment bit if 4 KB aligned LOAD segment is found
+        if (programHeader.p_align == 0x1000) {
+            ALOGI("Setting page size compat mode PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED");
+            mode |= PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED;
+            break;
+        }
+    }
+
+    return mode;
+}
+
+static jint checkExtractedLibAlignment(ZipFileRO* zipFile, ZipEntryRO zipEntry,
+                                       const char* fileName, const std::string nativeLibPath) {
+    // Build local file path
+    const size_t fileNameLen = strlen(fileName);
+    char localFileName[nativeLibPath.size() + fileNameLen + 2];
+
+    if (strlcpy(localFileName, nativeLibPath.c_str(), sizeof(localFileName)) !=
+        nativeLibPath.size()) {
+        ALOGE("Couldn't allocate local file name for library");
+        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+    }
+
+    *(localFileName + nativeLibPath.size()) = '/';
+
+    if (strlcpy(localFileName + nativeLibPath.size() + 1, fileName,
+                sizeof(localFileName) - nativeLibPath.size() - 1) != fileNameLen) {
+        ALOGE("Couldn't allocate local file name for library");
+        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+    }
+
+    struct statfs64 fsInfo;
+    int result = statfs64(localFileName, &fsInfo);
+    if (result < 0) {
+        ALOGE("Failed to stat file :%s", localFileName);
+        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+    }
+
+    return checkLoadSegmentAlignment(localFileName, 0);
+}
+
+static jint checkAlignment(JNIEnv* env, jstring javaNativeLibPath, jboolean extractNativeLibs,
+                           ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName) {
+    int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+    // Need two separate install status for APK and ELF alignment
+    static const size_t kPageSize = getpagesize();
+    jint ret = INSTALL_SUCCEEDED;
+
+    ScopedUtfChars nativeLibPath(env, javaNativeLibPath);
+    if (extractNativeLibs) {
+        ALOGI("extractNativeLibs specified, checking for extracted lib %s", fileName);
+        return checkExtractedLibAlignment(zipFile, zipEntry, fileName, nativeLibPath.c_str());
+    }
+
+    uint16_t method;
+    off64_t offset;
+    if (!zipFile->getEntryInfo(zipEntry, &method, nullptr, nullptr, &offset, nullptr, nullptr,
+                               nullptr)) {
+        ALOGE("Couldn't read zip entry info from zipFile %s", zipFile->getZipFileName());
+        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+    }
+
+    // check if library is uncompressed and page-aligned
+    if (method != ZipFileRO::kCompressStored) {
+        ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n",
+              fileName);
+        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+    }
+
+    if (offset % kPageSize != 0) {
+        ALOGW("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly "
+              "from apk.\n",
+              fileName, kPageSize);
+        mode |= PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED;
+        ALOGI("Setting page size compat mode "
+              "PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED for %s",
+              zipFile->getZipFileName());
+    }
+
+    int loadMode = checkLoadSegmentAlignment(zipFile->getZipFileName(), offset);
+    if (loadMode == PAGE_SIZE_APP_COMPAT_FLAG_ERROR) {
+        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+    }
+    mode |= loadMode;
+    return mode;
+}
+
+// TODO(b/371049373): This function is copy of iterateOverNativeFiles with different way of handling
+// and combining return values for all ELF and APKs. Find a way to consolidate two functions.
+static jint com_android_internal_content_NativeLibraryHelper_checkApkAlignment(
+        JNIEnv* env, jclass clazz, jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi,
+        jboolean extractNativeLibs, jboolean debuggable) {
+    int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+    ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
+    if (zipFile == nullptr) {
+        ALOGE("zipfile handle is null");
+        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+    }
+
+    auto result = NativeLibrariesIterator::create(zipFile, debuggable);
+    if (!result.ok()) {
+        ALOGE("Can't iterate over native libs for file:%s", zipFile->getZipFileName());
+        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+    }
+    std::unique_ptr<NativeLibrariesIterator> it(std::move(result.value()));
+
+    const ScopedUtfChars cpuAbi(env, javaCpuAbi);
+    if (cpuAbi.c_str() == nullptr) {
+        ALOGE("cpuAbi is nullptr");
+        // This would've thrown, so this return code isn't observable by Java.
+        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+    }
+
+    while (true) {
+        auto next = it->next();
+        if (!next.ok()) {
+            ALOGE("next iterator not found Error:%d", next.error());
+            return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+        }
+        auto entry = next.value();
+        if (entry == nullptr) {
+            break;
+        }
+
+        const char* fileName = it->currentEntry();
+        const char* lastSlash = it->lastSlash();
+
+        // Check to make sure the CPU ABI of this file is one we support.
+        const char* cpuAbiOffset = fileName + APK_LIB_LEN;
+        const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset;
+
+        if (cpuAbi.size() == cpuAbiRegionSize &&
+            !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) {
+            int ret = checkAlignment(env, javaNativeLibPath, extractNativeLibs, zipFile, entry,
+                                     lastSlash + 1);
+            if (ret == PAGE_SIZE_APP_COMPAT_FLAG_ERROR) {
+                ALOGE("Alignment check returned for zipfile: %s, entry:%s",
+                      zipFile->getZipFileName(), lastSlash + 1);
+                return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+            }
+            mode |= ret;
+        }
+    }
+
+    return mode;
+}
+
 static void
 com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlong apkHandle)
 {
@@ -633,29 +797,23 @@
 }
 
 static const JNINativeMethod gMethods[] = {
-    {"nativeOpenApk",
-            "(Ljava/lang/String;)J",
-            (void *)com_android_internal_content_NativeLibraryHelper_openApk},
-    {"nativeOpenApkFd",
-            "(Ljava/io/FileDescriptor;Ljava/lang/String;)J",
-            (void *)com_android_internal_content_NativeLibraryHelper_openApkFd},
-    {"nativeClose",
-            "(J)V",
-            (void *)com_android_internal_content_NativeLibraryHelper_close},
-    {"nativeCopyNativeBinaries",
-            "(JLjava/lang/String;Ljava/lang/String;ZZ)I",
-            (void *)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries},
-    {"nativeSumNativeBinaries",
-            "(JLjava/lang/String;Z)J",
-            (void *)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries},
-    {"nativeFindSupportedAbi",
-            "(J[Ljava/lang/String;Z)I",
-            (void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi},
-    {"hasRenderscriptBitcode", "(J)I",
-            (void *)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode},
+        {"nativeOpenApk", "(Ljava/lang/String;)J",
+         (void*)com_android_internal_content_NativeLibraryHelper_openApk},
+        {"nativeOpenApkFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;)J",
+         (void*)com_android_internal_content_NativeLibraryHelper_openApkFd},
+        {"nativeClose", "(J)V", (void*)com_android_internal_content_NativeLibraryHelper_close},
+        {"nativeCopyNativeBinaries", "(JLjava/lang/String;Ljava/lang/String;ZZ)I",
+         (void*)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries},
+        {"nativeSumNativeBinaries", "(JLjava/lang/String;Z)J",
+         (void*)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries},
+        {"nativeFindSupportedAbi", "(J[Ljava/lang/String;Z)I",
+         (void*)com_android_internal_content_NativeLibraryHelper_findSupportedAbi},
+        {"hasRenderscriptBitcode", "(J)I",
+         (void*)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode},
+        {"nativeCheckAlignment", "(JLjava/lang/String;Ljava/lang/String;ZZ)I",
+         (void*)com_android_internal_content_NativeLibraryHelper_checkApkAlignment},
 };
 
-
 int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env)
 {
     return RegisterMethodsOrDie(env,
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 284c299..aeaeeca 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -89,6 +89,7 @@
 
 #if defined(__BIONIC__)
 extern "C" void android_reset_stack_guards();
+extern "C" void android_set_16kb_appcompat_mode(bool enable_app_compat);
 #endif
 
 namespace {
@@ -350,6 +351,7 @@
     NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23,
     PROFILEABLE = 1 << 24,
     DEBUG_ENABLE_PTRACE = 1 << 25,
+    ENABLE_PAGE_SIZE_APP_COMPAT = 1 << 26,
 };
 
 enum UnsolicitedZygoteMessageTypes : uint32_t {
@@ -2117,6 +2119,12 @@
     SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities,
                     fail_fn);
 
+    if ((runtime_flags & RuntimeFlags::ENABLE_PAGE_SIZE_APP_COMPAT) != 0) {
+        android_set_16kb_appcompat_mode(true);
+        // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART
+        // runtime.
+        runtime_flags &= ~RuntimeFlags::ENABLE_PAGE_SIZE_APP_COMPAT;
+    }
     __android_log_close();
     AStatsSocket_close();
 
diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto
index b7408a4..facadee 100644
--- a/core/proto/android/content/package_item_info.proto
+++ b/core/proto/android/content/package_item_info.proto
@@ -114,6 +114,7 @@
         optional int32 enable_memtag = 20;
         optional bool native_heap_zero_init = 21;
         optional bool allow_cross_uid_activity_switch_from_below = 22;
+        optional int32 enable_page_size_app_compat = 23;
     }
     optional Detail detail = 17;
     repeated string overlay_paths = 18;
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 16d2565..e424e82 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -218,6 +218,7 @@
         optional SettingProto tap_to_click = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto tap_dragging = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto three_finger_tap_customization = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto system_gestures = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];;
     }
     optional Touchpad touchpad = 36;
 
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 8042b30..903d08b 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -160,7 +160,9 @@
         "android.app.contextualsearch.flags-aconfig",
         "android.app.flags-aconfig",
         "android.appwidget.flags-aconfig",
+        "android.companion.virtualdevice.flags-aconfig",
         "android.content.pm.flags-aconfig",
+        "android.location.flags-aconfig",
         "android.media.audio-aconfig",
         "android.provider.flags-aconfig",
         "camera_platform_flags",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index cedcd2f..a86d551 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -848,10 +848,10 @@
     <protected-broadcast android:name="android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED" />
     <protected-broadcast android:name="android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED" />
     <protected-broadcast android:name="com.android.uwb.uwbcountrycode.GEOCODE_RETRY" />
-    <protected-broadcast android:name="android.telephony.action.ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED" />
+    <protected-broadcast android:name="android.telephony.satellite.action.SATELLITE_SUBSCRIBER_ID_LIST_CHANGED" />
     <protected-broadcast android:name="android.service.ondeviceintelligence.MODEL_LOADED" />
     <protected-broadcast android:name="android.service.ondeviceintelligence.MODEL_UNLOADED" />
-    <protected-broadcast android:name="android.telephony.action.ACTION_SATELLITE_START_NON_EMERGENCY_SESSION" />
+    <protected-broadcast android:name="android.telephony.satellite.action.SATELLITE_START_NON_EMERGENCY_SESSION" />
 
 
     <!-- ====================================================================== -->
@@ -1139,6 +1139,16 @@
                 android:protectionLevel="signature|privileged|vendorPrivileged"
                 android:featureFlag="android.media.tv.flags.media_quality_fw"/>
 
+    <!--
+        @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+        Allows an application to read the aggregated color zones on the screen for use cases like
+        TV ambient backlight usages.
+        <p> Protection level: normal
+    -->
+    <permission android:name="android.permission.READ_COLOR_ZONES"
+                android:protectionLevel="normal"
+                android:featureFlag="android.media.tv.flags.media_quality_fw"/>
+
     <!-- ====================================================================== -->
     <!-- Permissions for accessing external storage                             -->
     <!-- ====================================================================== -->
@@ -2145,6 +2155,21 @@
     <permission android:name="android.permission.CONTROL_AUTOMOTIVE_GNSS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide Allows an application to bind to a
+         android.service.PopulationDensityProviderService for the purpose of
+         querying population density. This prevents arbitrary clients connecting
+         to the service. The system server checks that the provider's intent
+         service explicitly sets this permission via the android:permission
+         attribute of the service.
+         This is only expected to be possessed by the system server outside of
+         tests.
+         @FlaggedApi(android.location.flags.Flags.FLAG_POPULATION_DENSITY_PROVIDER)
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.BIND_POPULATION_DENSITY_PROVIDER_SERVICE"
+        android:featureFlag="android.location.flags.population_density_provider"
+        android:protectionLevel="signature" />
+
     <!-- ======================================= -->
     <!-- Permissions for accessing networks -->
     <!-- ======================================= -->
@@ -6516,7 +6541,7 @@
      @hide -->
     <permission android:name="android.permission.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION"
         android:featureFlag="android.media.audio.concurrent_audio_record_bypass_permission"
-        android:protectionLevel="signature|privileged|role" />
+        android:protectionLevel="signature|privileged" />
 
     <!-- @SystemApi Allows an application to capture audio for hotword detection.
          <p>Not for use by third-party applications.</p>
@@ -8060,6 +8085,13 @@
     <permission android:name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY"
                 android:protectionLevel="signature|role"/>
 
+    <!-- Allows an application to create displays that mirror other displays' content.
+         @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ENABLE_LIMITED_VDM_ROLE)
+         @hide @SystemApi -->
+    <permission android:name="android.permission.ADD_MIRROR_DISPLAY"
+        android:protectionLevel="internal|role"
+        android:featureFlag="android.companion.virtualdevice.flags.enable_limited_vdm_role" />
+
     <!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. -->
     <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
                 android:protectionLevel="signature|role" />
@@ -8689,7 +8721,7 @@
         android:featureFlag="android.security.secure_lockdown" />
 
     <!-- Allows app to enter trade-in-mode.
-        <p>Protection level: signature|privileged
+        <p>Protection level: signature
         @hide
     -->
     <permission android:name="android.permission.ENTER_TRADE_IN_MODE"
@@ -9344,6 +9376,17 @@
             </intent-filter>
         </service>
 
+        <service android:name="com.android.ecm.EnhancedConfirmationCallTrackerService"
+            android:permission="android.permission.BIND_INCALL_SERVICE"
+            android:featureFlag="android.permission.flags.enhanced_confirmation_in_call_apis_enabled"
+            android:exported="true">
+            <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+                android:value="true" />
+            <intent-filter>
+                <action android:name="android.telecom.InCallService"/>
+            </intent-filter>
+        </service>
+
         <service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncConnectionService"
                  android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
                  android:exported="true">
diff --git a/core/res/res/color-watch-v36/btn_material_outlined_background_color.xml b/core/res/res/color-watch-v36/btn_material_outlined_background_color.xml
new file mode 100644
index 0000000..665f47f
--- /dev/null
+++ b/core/res/res/color-watch-v36/btn_material_outlined_background_color.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="?attr/disabledAlpha"
+          android:color="?attr/materialColorOnSurface" />
+    <item android:color="?attr/materialColorOutline" />
+</selector>
diff --git a/core/res/res/drawable-watch-v36/btn_background_material_outlined.xml b/core/res/res/drawable-watch-v36/btn_background_material_outlined.xml
new file mode 100644
index 0000000..7bc4060
--- /dev/null
+++ b/core/res/res/drawable-watch-v36/btn_background_material_outlined.xml
@@ -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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/config_wearMaterial3_buttonCornerRadius"/>
+            <solid android:color="#fff"/>
+            <size
+                android:width="@dimen/btn_material_width"
+                android:height="@dimen/btn_material_height" />
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/config_wearMaterial3_buttonCornerRadius"/>
+            <stroke
+                android:width="1dp"
+                android:color="@color/btn_material_outlined_background_color" />
+            <size
+                android:width="@dimen/btn_material_width"
+                android:height="@dimen/btn_material_height" />
+        </shape>
+    </item>
+</ripple>
diff --git a/core/res/res/drawable-watch-v36/btn_background_material_text.xml b/core/res/res/drawable-watch-v36/btn_background_material_text.xml
new file mode 100644
index 0000000..145685c
--- /dev/null
+++ b/core/res/res/drawable-watch-v36/btn_background_material_text.xml
@@ -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.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/config_wearMaterial3_buttonCornerRadius"/>
+            <solid android:color="#fff"/>
+            <size
+                android:width="@dimen/btn_material_width"
+                android:height="@dimen/btn_material_height" />
+        </shape>
+    </item>
+</ripple>
diff --git a/core/res/res/drawable-watch-v36/dialog_alert_button_background_negative.xml b/core/res/res/drawable-watch-v36/dialog_alert_button_background_negative.xml
index b6b8eac3..0314bbe 100644
--- a/core/res/res/drawable-watch-v36/dialog_alert_button_background_negative.xml
+++ b/core/res/res/drawable-watch-v36/dialog_alert_button_background_negative.xml
@@ -18,7 +18,7 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
     <solid android:color="@color/btn_material_filled_tonal_background_color"/>
-    <corners android:radius="@dimen/config_bottomDialogCornerRadius" />
+    <corners android:radius="@dimen/config_wearMaterial3_bottomDialogCornerRadius" />
     <size
         android:width="@dimen/dialog_btn_negative_width"
         android:height="@dimen/dialog_btn_negative_height" />
diff --git a/core/res/res/drawable-watch-v36/progress_ring_wear_material3.xml b/core/res/res/drawable-watch-v36/progress_ring_wear_material3.xml
new file mode 100644
index 0000000..5c0e5f6
--- /dev/null
+++ b/core/res/res/drawable-watch-v36/progress_ring_wear_material3.xml
@@ -0,0 +1,43 @@
+<?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.
+  -->
+
+<rotate xmlns:android="http://schemas.android.com/apk/res/android"
+        android:fromDegrees = "270"
+        android:toDegrees="270"
+        android:pivotX="50%"
+        android:pivotY="50%" >
+    <layer-list>
+        <item>
+            <shape
+                android:shape="ring"
+                android:innerRadiusRatio="@dimen/progressbar_inner_radius_ratio"
+                android:thickness="@dimen/progressbar_thickness"
+                android:useLevel="false">
+                <solid android:color="?attr/materialColorSurfaceContainer"/>
+            </shape>
+        </item>
+        <item>
+            <shape
+                android:shape="ring"
+                android:innerRadiusRatio="@dimen/progressbar_inner_radius_ratio"
+                android:thickness="@dimen/progressbar_thickness"
+                android:useLevel="true">
+                <solid android:color="?attr/materialColorPrimary"/>
+            </shape>
+        </item>
+    </layer-list>
+</rotate>
\ No newline at end of file
diff --git a/core/res/res/layout-sw600dp/preference_list_content_single.xml b/core/res/res/layout-sw600dp/preference_list_content_single.xml
index 88b1aa8..ebe4eca 100644
--- a/core/res/res/layout-sw600dp/preference_list_content_single.xml
+++ b/core/res/res/layout-sw600dp/preference_list_content_single.xml
@@ -19,6 +19,7 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
+    android:fitsSystemWindows="true"
     android:layout_height="match_parent"
     android:layout_width="match_parent">
 
diff --git a/core/res/res/layout-watch-v36/alert_dialog_icon_button_wear_material3.xml b/core/res/res/layout-watch-v36/alert_dialog_icon_button_wear_material3.xml
new file mode 100644
index 0000000..407ec7a
--- /dev/null
+++ b/core/res/res/layout-watch-v36/alert_dialog_icon_button_wear_material3.xml
@@ -0,0 +1,123 @@
+<!--
+  ~ 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 layout is the AlertDialog template. It overrides the system layout with the same name.
+    Make sure to include all the existing id of the overridden alert_dialog_material.-->
+<com.android.internal.widget.WatchListDecorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/parentPanel"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <ScrollView
+        android:id="@+id/scrollView"
+        android:fillViewport="true"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <!-- Top Panel -->
+            <FrameLayout
+                android:paddingLeft="?dialogPreferredPadding"
+                android:paddingRight="?dialogPreferredPadding"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:id="@+id/topPanel"
+                android:minHeight="@dimen/dialog_list_padding_top_no_title">
+                <include android:id="@+id/title_template"
+                         android:layout_width="match_parent"
+                         android:layout_height="wrap_content"
+                         layout="@layout/alert_dialog_title_material"/>
+            </FrameLayout>
+
+            <!-- Content Panel -->
+            <FrameLayout android:id="@+id/contentPanel"
+                         android:layout_width="match_parent"
+                         android:layout_height="wrap_content"
+                         android:clipToPadding="false">
+                <TextView android:id="@+id/message"
+                          android:layout_width="match_parent"
+                          android:layout_height="wrap_content"
+                          android:gravity="center_horizontal|top"
+                          android:textAppearance="@style/TextAppearance.DeviceDefault.Body1"
+                          android:paddingStart="?dialogPreferredPadding"
+                          android:paddingEnd="?dialogPreferredPadding"
+                          android:paddingTop="8dip"
+                          android:paddingBottom="8dip"/>
+            </FrameLayout>
+
+            <!-- Custom Panel, to replace content panel if needed -->
+            <FrameLayout android:id="@+id/customPanel"
+                         android:layout_width="match_parent"
+                         android:layout_height="match_parent"
+                         android:minHeight="64dp">
+                <FrameLayout android:id="@+android:id/custom"
+                             android:layout_width="match_parent"
+                             android:layout_height="wrap_content" />
+            </FrameLayout>
+
+            <!-- Button Panel -->
+            <FrameLayout
+                android:id="@+id/buttonPanel"
+                android:minHeight="@dimen/dialog_list_padding_bottom_no_buttons"
+                android:layout_weight="1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center">
+                <LinearLayout
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="bottom"
+                    android:orientation="horizontal"
+                    android:paddingBottom="?dialogPreferredPadding"
+                    style="?android:attr/buttonBarStyle"
+                    android:measureWithLargestChild="true">
+                    <Button android:id="@+id/button2"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="center"
+                            android:gravity="center"
+                            android:layout_weight="1"
+                            style="@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog.WearMaterial3.Negative" />
+                    <Button android:id="@+id/button3"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="center"
+                            android:gravity="center"
+                            android:layout_weight="1"
+                            style="?android:attr/buttonBarButtonStyle"/>
+                    <FrameLayout
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content">
+                        <Button android:id="@+id/button1"
+                                android:layout_width="match_parent"
+                                android:layout_height="match_parent"
+                                android:layout_gravity="center"
+                                android:gravity="center"
+                                android:layout_weight="1"
+                                style="@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog.WearMaterial3.Confirm" />
+                        <!-- This works as background. -->
+                        <ImageView
+                            android:layout_width="match_parent"
+                            android:layout_height="match_parent"
+                            android:src="@drawable/dialog_alert_button_positive"/>
+                    </FrameLayout>
+                </LinearLayout>
+            </FrameLayout>
+        </LinearLayout>
+    </ScrollView>
+</com.android.internal.widget.WatchListDecorLayout>
diff --git a/core/res/res/layout-watch-v36/alert_dialog_material.xml b/core/res/res/layout-watch-v36/alert_dialog_material.xml
deleted file mode 100644
index 900102f..0000000
--- a/core/res/res/layout-watch-v36/alert_dialog_material.xml
+++ /dev/null
@@ -1,123 +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.
-  -->
-
-<!-- This layout is the AlertDialog template. It overrides the system layout with the same name.
-    Make sure to include all the existing id of the overridden alert_dialog_material.-->
-<com.android.internal.widget.WatchListDecorLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/parentPanel"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <ScrollView
-        android:id="@+id/scrollView"
-        android:fillViewport="true"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-        <LinearLayout
-            android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content">
-            <!-- Top Panel -->
-            <FrameLayout
-                android:paddingLeft="?dialogPreferredPadding"
-                android:paddingRight="?dialogPreferredPadding"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:id="@+id/topPanel"
-                android:minHeight="@dimen/dialog_list_padding_top_no_title">
-                <include android:id="@+id/title_template"
-                         android:layout_width="match_parent"
-                         android:layout_height="wrap_content"
-                         layout="@layout/alert_dialog_title_material"/>
-            </FrameLayout>
-
-            <!-- Content Panel -->
-            <FrameLayout android:id="@+id/contentPanel"
-                         android:layout_width="match_parent"
-                         android:layout_height="wrap_content"
-                         android:clipToPadding="false">
-                <TextView android:id="@+id/message"
-                          android:layout_width="match_parent"
-                          android:layout_height="wrap_content"
-                          android:gravity="center_horizontal|top"
-                          android:textAppearance="@style/TextAppearance.DeviceDefault.Body1"
-                          android:paddingStart="?dialogPreferredPadding"
-                          android:paddingEnd="?dialogPreferredPadding"
-                          android:paddingTop="8dip"
-                          android:paddingBottom="8dip"/>
-            </FrameLayout>
-
-            <!-- Custom Panel, to replace content panel if needed -->
-            <FrameLayout android:id="@+id/customPanel"
-                         android:layout_width="match_parent"
-                         android:layout_height="match_parent"
-                         android:minHeight="64dp">
-                <FrameLayout android:id="@+android:id/custom"
-                             android:layout_width="match_parent"
-                             android:layout_height="wrap_content" />
-            </FrameLayout>
-
-            <!-- Button Panel -->
-            <FrameLayout
-                android:id="@+id/buttonPanel"
-                android:minHeight="@dimen/dialog_list_padding_bottom_no_buttons"
-                android:layout_weight="1"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center">
-                <LinearLayout
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="bottom"
-                    android:orientation="horizontal"
-                    android:paddingBottom="?dialogPreferredPadding"
-                    style="?android:attr/buttonBarStyle"
-                    android:measureWithLargestChild="true">
-                    <Button android:id="@+id/button2"
-                            android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
-                            android:layout_gravity="center"
-                            android:gravity="center"
-                            android:layout_weight="1"
-                            style="@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog.Negative" />
-                    <Button android:id="@+id/button3"
-                            android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
-                            android:layout_gravity="center"
-                            android:gravity="center"
-                            android:layout_weight="1"
-                            style="?android:attr/buttonBarButtonStyle"/>
-                    <FrameLayout
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content">
-                        <Button android:id="@+id/button1"
-                                android:layout_width="match_parent"
-                                android:layout_height="match_parent"
-                                android:layout_gravity="center"
-                                android:gravity="center"
-                                android:layout_weight="1"
-                                style="@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog.Confirm"/>
-                        <!-- This works as background. -->
-                        <ImageView
-                            android:layout_width="match_parent"
-                            android:layout_height="match_parent"
-                            android:src="@drawable/dialog_alert_button_positive"/>
-                    </FrameLayout>
-                </LinearLayout>
-            </FrameLayout>
-        </LinearLayout>
-    </ScrollView>
-</com.android.internal.widget.WatchListDecorLayout>
diff --git a/core/res/res/layout-watch-v36/alert_dialog_wear_material3.xml b/core/res/res/layout-watch-v36/alert_dialog_wear_material3.xml
new file mode 100644
index 0000000..8f75456
--- /dev/null
+++ b/core/res/res/layout-watch-v36/alert_dialog_wear_material3.xml
@@ -0,0 +1,113 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- This layout is the AlertDialog template. It overrides the system layout with the same name.
+    Make sure to include all the existing id of the overridden alert_dialog_material.-->
+<com.android.internal.widget.WatchListDecorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/parentPanel"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <ScrollView
+        android:id="@+id/scrollView"
+        android:fillViewport="true"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <!-- Top Panel -->
+            <FrameLayout
+                android:paddingLeft="?dialogPreferredPadding"
+                android:paddingRight="?dialogPreferredPadding"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:id="@+id/topPanel"
+                android:minHeight="@dimen/dialog_list_padding_top_no_title">
+                <include android:id="@+id/title_template"
+                         android:layout_width="match_parent"
+                         android:layout_height="wrap_content"
+                         layout="@layout/alert_dialog_title_material"/>
+            </FrameLayout>
+
+            <!-- Content Panel -->
+            <FrameLayout android:id="@+id/contentPanel"
+                         android:layout_width="match_parent"
+                         android:layout_height="wrap_content"
+                         android:clipToPadding="false">
+                <TextView android:id="@+id/message"
+                          android:layout_width="match_parent"
+                          android:layout_height="wrap_content"
+                          android:gravity="center_horizontal|top"
+                          android:textAppearance="@style/TextAppearance.DeviceDefault.Body1"
+                          android:paddingStart="?dialogPreferredPadding"
+                          android:paddingEnd="?dialogPreferredPadding"
+                          android:paddingTop="8dip"
+                          android:paddingBottom="8dip"/>
+            </FrameLayout>
+
+            <!-- Custom Panel, to replace content panel if needed -->
+            <FrameLayout android:id="@+id/customPanel"
+                         android:layout_width="match_parent"
+                         android:layout_height="match_parent"
+                         android:minHeight="64dp">
+                <FrameLayout android:id="@+android:id/custom"
+                             android:layout_width="match_parent"
+                             android:layout_height="wrap_content" />
+            </FrameLayout>
+
+            <!-- Button Panel -->
+            <FrameLayout
+                android:id="@+id/buttonPanel"
+                android:minHeight="@dimen/dialog_list_padding_bottom_no_buttons"
+                android:layout_weight="1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center">
+                <LinearLayout
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="bottom"
+                    android:orientation="vertical"
+                    android:paddingBottom="?dialogPreferredPadding"
+                    android:measureWithLargestChild="true">
+                    <Button android:id="@+id/button2"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="center"
+                            android:gravity="center"
+                            android:layout_weight="1"
+                            style="@*android:style/Widget.DeviceDefault.Button.WearMaterial3"/>
+                    <Button android:id="@+id/button3"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="center"
+                            android:gravity="center"
+                            android:layout_weight="1"
+                            style="?android:attr/buttonBarButtonStyle"/>
+                    <Button android:id="@+id/button1"
+                            android:layout_width="match_parent"
+                            android:layout_height="match_parent"
+                            android:layout_gravity="center"
+                            android:gravity="center"
+                            android:layout_weight="1"
+                            style="@*android:style/Widget.DeviceDefault.Button.Filled"/>
+                </LinearLayout>
+            </FrameLayout>
+        </LinearLayout>
+    </ScrollView>
+</com.android.internal.widget.WatchListDecorLayout>
diff --git a/core/res/res/layout/notification_2025_conversation_face_pile_layout.xml b/core/res/res/layout/notification_2025_conversation_face_pile_layout.xml
new file mode 100644
index 0000000..b25adaa
--- /dev/null
+++ b/core/res/res/layout/notification_2025_conversation_face_pile_layout.xml
@@ -0,0 +1,54 @@
+<?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 underthe License
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/conversation_face_pile"
+    android:layout_width="@dimen/conversation_avatar_size"
+    android:layout_height="@dimen/conversation_avatar_size"
+    android:forceHasOverlappingRendering="false"
+    >
+    <ImageView
+        android:id="@+id/conversation_face_pile_top"
+        android:layout_width="@dimen/messaging_avatar_size"
+        android:layout_height="@dimen/messaging_avatar_size"
+        android:scaleType="centerCrop"
+        android:layout_gravity="end|top"
+        android:background="@drawable/notification_icon_circle"
+        android:clipToOutline="true"
+        />
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start|bottom">
+        <ImageView
+            android:id="@+id/conversation_face_pile_bottom_background"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/conversation_badge_background"
+            />
+        <ImageView
+            android:id="@+id/conversation_face_pile_bottom"
+            android:layout_width="@dimen/messaging_avatar_size"
+            android:layout_height="@dimen/messaging_avatar_size"
+            android:scaleType="centerCrop"
+            android:layout_gravity="center"
+            android:background="@drawable/notification_icon_circle"
+            android:clipToOutline="true"
+            />
+    </FrameLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_conversation_icon_container.xml b/core/res/res/layout/notification_2025_conversation_icon_container.xml
new file mode 100644
index 0000000..90befd9
--- /dev/null
+++ b/core/res/res/layout/notification_2025_conversation_icon_container.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/conversation_icon_container"
+    android:layout_width="@dimen/conversation_content_start"
+    android:layout_height="wrap_content"
+    android:gravity="start|top"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:paddingTop="20dp"
+    android:paddingBottom="16dp"
+    android:importantForAccessibility="no"
+    >
+
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layout_gravity="top|center_horizontal"
+        >
+
+        <!-- Big icon: 48x48, 12dp padding top, 16dp padding sides -->
+        <com.android.internal.widget.CachingIconView
+            android:id="@+id/conversation_icon"
+            android:layout_width="@dimen/conversation_avatar_size"
+            android:layout_height="@dimen/conversation_avatar_size"
+            android:layout_marginLeft="@dimen/conversation_badge_protrusion"
+            android:layout_marginRight="@dimen/conversation_badge_protrusion"
+            android:layout_marginBottom="@dimen/conversation_badge_protrusion"
+            android:background="@drawable/notification_icon_circle"
+            android:clipToOutline="true"
+            android:scaleType="centerCrop"
+            android:importantForAccessibility="no"
+            />
+
+        <ViewStub
+            android:layout="@layout/notification_2025_conversation_face_pile_layout"
+            android:layout_width="@dimen/conversation_avatar_size"
+            android:layout_height="@dimen/conversation_avatar_size"
+            android:layout_marginLeft="@dimen/conversation_badge_protrusion"
+            android:layout_marginRight="@dimen/conversation_badge_protrusion"
+            android:layout_marginBottom="@dimen/conversation_badge_protrusion"
+            android:id="@+id/conversation_face_pile"
+            />
+
+        <FrameLayout
+            android:id="@+id/conversation_icon_badge"
+            android:layout_width="@dimen/conversation_icon_size_badged"
+            android:layout_height="@dimen/conversation_icon_size_badged"
+            android:layout_gravity="end|bottom"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            >
+
+            <com.android.internal.widget.CachingIconView
+                android:id="@+id/conversation_icon_badge_bg"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_gravity="center"
+                android:src="@drawable/conversation_badge_background"
+                android:forceHasOverlappingRendering="false"
+                android:scaleType="center"
+                />
+
+            <com.android.internal.widget.NotificationRowIconView
+                android:id="@+id/icon"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_margin="4dp"
+                android:layout_gravity="center"
+                android:forceHasOverlappingRendering="false"
+                />
+
+            <com.android.internal.widget.CachingIconView
+                android:id="@+id/conversation_icon_badge_ring"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:src="@drawable/conversation_badge_ring"
+                android:visibility="gone"
+                android:forceHasOverlappingRendering="false"
+                android:clipToPadding="false"
+                android:scaleType="center"
+                />
+        </FrameLayout>
+    </FrameLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_messaging_group.xml b/core/res/res/layout/notification_2025_messaging_group.xml
new file mode 100644
index 0000000..c1b491f
--- /dev/null
+++ b/core/res/res/layout/notification_2025_messaging_group.xml
@@ -0,0 +1,80 @@
+<?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
+  -->
+
+<!-- extends LinearLayout -->
+<com.android.internal.widget.MessagingGroup
+    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="wrap_content"
+    android:orientation="horizontal" >
+    <FrameLayout
+        android:id="@+id/message_icon_container"
+        android:layout_width="@dimen/conversation_content_start"
+        android:layout_height="wrap_content">
+        <ImageView
+            android:layout_gravity="top|center_horizontal"
+            android:id="@+id/message_icon"
+            android:layout_width="@dimen/messaging_avatar_size"
+            android:layout_height="@dimen/messaging_avatar_size"
+            android:background="@drawable/notification_icon_circle"
+            android:clipToOutline="true"
+            android:scaleType="centerCrop"
+            android:importantForAccessibility="no" />
+    </FrameLayout>
+    <com.android.internal.widget.RemeasuringLinearLayout
+        android:id="@+id/messaging_group_content_container"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:baselineAligned="true"
+        android:orientation="vertical">
+        <com.android.internal.widget.ImageFloatingTextView
+            android:id="@+id/message_name"
+            style="@style/Widget.DeviceDefault.Notification.MessagingName"
+            android:layout_width="wrap_content"
+            android:textAlignment="viewStart"
+        />
+        <com.android.internal.widget.MessagingLinearLayout
+            android:id="@+id/group_message_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/notification_text_margin_top"
+            android:spacing="2dp" />
+    </com.android.internal.widget.RemeasuringLinearLayout>
+    <FrameLayout
+        android:id="@+id/messaging_group_icon_container"
+        android:layout_width="@dimen/messaging_avatar_size"
+        android:layout_height="@dimen/messaging_avatar_size"
+        android:layout_marginStart="12dp"
+        android:visibility="gone"/>
+    <FrameLayout
+        android:id="@+id/messaging_group_sending_progress_container"
+        android:layout_width="@dimen/messaging_group_sending_progress_size"
+        android:layout_height="@dimen/messaging_avatar_size"
+        android:layout_marginStart="12dp"
+        android:layout_gravity="top"
+        android:visibility="gone">
+        <ProgressBar
+            android:id="@+id/messaging_group_sending_progress"
+            android:layout_height="@dimen/messaging_group_sending_progress_size"
+            android:layout_width="@dimen/messaging_group_sending_progress_size"
+            android:layout_gravity="center"
+            android:indeterminate="true"
+            style="?android:attr/progressBarStyleSmall" />
+    </FrameLayout>
+</com.android.internal.widget.MessagingGroup>
diff --git a/core/res/res/layout/notification_2025_reply_container.xml b/core/res/res/layout/notification_2025_reply_container.xml
new file mode 100644
index 0000000..6923b59
--- /dev/null
+++ b/core/res/res/layout/notification_2025_reply_container.xml
@@ -0,0 +1,79 @@
+<?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
+  -->
+
+<!-- Note: this layout is included from a view stub; layout attributes will be overridden. -->
+<LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/notification_material_reply_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingStart="@dimen/notification_2025_content_margin_start">
+
+    <ImageView
+            android:layout_width="match_parent"
+            android:layout_height="1dip"
+            android:id="@+id/action_divider"
+            android:layout_marginTop="@dimen/notification_content_margin"
+            android:layout_marginBottom="@dimen/notification_content_margin"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:background="@drawable/notification_template_divider" />
+
+    <TextView
+            android:id="@+id/notification_material_reply_text_3"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:visibility="gone"
+            android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Reply"
+            android:singleLine="true" />
+
+    <TextView
+            android:id="@+id/notification_material_reply_text_2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:visibility="gone"
+            android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Reply"
+            android:singleLine="true" />
+
+    <LinearLayout
+            android:id="@+id/notification_material_reply_text_1_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_marginEnd="@dimen/notification_content_margin_end">
+        <TextView
+                android:id="@+id/notification_material_reply_text_1"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:layout_marginEnd="@dimen/notification_content_margin_end"
+                android:layout_gravity="center"
+                android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Reply"
+                android:singleLine="true" />
+        <ProgressBar
+            android:id="@+id/notification_material_reply_progress"
+            android:layout_height="@dimen/messaging_group_sending_progress_size"
+            android:layout_width="@dimen/messaging_group_sending_progress_size"
+            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+            android:layout_gravity="center"
+            android:indeterminate="true"
+            style="?android:attr/progressBarStyleSmall" />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml
index 4eac6d9..09c02c9 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -20,7 +20,7 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_weight="1"
-    android:minHeight="@dimen/notification_headerless_min_height"
+    android:minHeight="@dimen/notification_2025_min_height"
     android:tag="base"
     >
 
@@ -72,8 +72,8 @@
             android:layout_height="wrap_content"
             android:layout_gravity="center_vertical"
             android:layout_weight="1"
-            android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
-            android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
+            android:layout_marginBottom="@dimen/notification_2025_margin"
+            android:layout_marginTop="@dimen/notification_2025_margin"
             android:orientation="vertical"
             >
 
@@ -81,7 +81,7 @@
                 android:id="@+id/notification_top_line"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:minHeight="@dimen/notification_headerless_line_height"
+                android:minHeight="@dimen/notification_2025_content_min_height"
                 android:clipChildren="false"
                 android:theme="@style/Theme.DeviceDefault.Notification"
                 >
@@ -123,7 +123,7 @@
                     <!-- This is the simplest way to keep this text vertically centered without
                      gravity="center_vertical" which causes jumpiness in expansion animations. -->
                     <include
-                        layout="@layout/notification_2025_template_text"
+                        layout="@layout/notification_2025_text"
                         android:layout_width="match_parent"
                         android:layout_height="@dimen/notification_text_height"
                         android:layout_gravity="center_vertical"
diff --git a/core/res/res/layout/notification_2025_template_collapsed_call.xml b/core/res/res/layout/notification_2025_template_collapsed_call.xml
new file mode 100644
index 0000000..614444d
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml
@@ -0,0 +1,79 @@
+<?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
+  -->
+
+<!-- Extends FrameLayout -->
+<com.android.internal.widget.CallLayout
+    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="wrap_content"
+    android:clipChildren="false"
+    android:tag="call"
+    android:theme="@style/Theme.DeviceDefault.Notification"
+    >
+
+    <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts -->
+    <include layout="@layout/notification_2025_conversation_icon_container" />
+
+    <LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="@dimen/notification_2025_min_height"
+        android:orientation="horizontal"
+        >
+
+        <LinearLayout
+            android:id="@+id/notification_main_column"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_marginStart="@dimen/conversation_content_start"
+            android:orientation="vertical"
+            android:paddingBottom="@dimen/notification_2025_margin"
+            >
+
+            <include
+                layout="@layout/notification_template_conversation_header"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                />
+
+            <include layout="@layout/notification_template_text"
+                android:layout_height="wrap_content"
+                android:minHeight="@dimen/notification_text_height"
+                />
+
+        </LinearLayout>
+
+        <FrameLayout
+            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"
+                />
+
+        </FrameLayout>
+
+    </LinearLayout>
+
+</com.android.internal.widget.CallLayout>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml
new file mode 100644
index 0000000..f539105
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -0,0 +1,197 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2014 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<!-- Note: This is the old media style notification (different from UMO). -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.MediaNotificationView
+    android:id="@+id/status_bar_latest_event_content"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="@dimen/notification_2025_min_height"
+    android:tag="media"
+    >
+
+
+    <ImageView
+        android:id="@+id/left_icon"
+        android:layout_width="@dimen/notification_2025_left_icon_size"
+        android:layout_height="@dimen/notification_2025_left_icon_size"
+        android:layout_gravity="center_vertical|start"
+        android:layout_marginStart="@dimen/notification_left_icon_start"
+        android:background="@drawable/notification_large_icon_outline"
+        android:clipToOutline="true"
+        android:importantForAccessibility="no"
+        android:scaleType="centerCrop"
+        android:visibility="gone"
+        />
+
+    <com.android.internal.widget.NotificationRowIconView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/notification_2025_icon_circle_size"
+        android:layout_height="@dimen/notification_2025_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_2025_icon_circle_padding"
+        />
+
+    <FrameLayout
+        android:id="@+id/alternate_expand_target"
+        android:layout_width="@dimen/notification_2025_content_margin_start"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:importantForAccessibility="no"
+        android:focusable="false"
+        />
+
+    <LinearLayout
+        android:id="@+id/notification_headerless_view_row"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+        android:orientation="horizontal"
+        >
+
+        <LinearLayout
+            android:id="@+id/notification_headerless_view_column"
+            android:layout_width="0px"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_weight="1"
+            android:layout_marginBottom="@dimen/notification_2025_margin"
+            android:layout_marginTop="@dimen/notification_2025_margin"
+            android:orientation="vertical"
+            >
+
+            <NotificationTopLineView
+                android:id="@+id/notification_top_line"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:minHeight="@dimen/notification_headerless_line_height"
+                android:clipChildren="false"
+                android:theme="@style/Theme.DeviceDefault.Notification"
+                >
+
+                <!--
+                NOTE: The notification_top_line_views layout contains the app_name_text.
+                In order to include the title view at the beginning, the Notification.Builder
+                has logic to hide that view whenever this title view is to be visible.
+                -->
+
+                <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>
+
+            <LinearLayout
+                android:id="@+id/notification_main_column"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                >
+
+                <com.android.internal.widget.NotificationVanishingFrameLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/notification_headerless_line_height"
+                    >
+                    <!-- This is the simplest way to keep this text vertically centered without
+                     gravity="center_vertical" which causes jumpiness in expansion animations. -->
+                    <include
+                        layout="@layout/notification_template_text"
+                        android:layout_width="match_parent"
+                        android:layout_height="@dimen/notification_text_height"
+                        android:layout_gravity="center_vertical"
+                        android:layout_marginTop="0dp"
+                        />
+                </com.android.internal.widget.NotificationVanishingFrameLayout>
+
+                <include
+                    layout="@layout/notification_template_progress"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/notification_headerless_line_height"
+                    />
+
+            </LinearLayout>
+
+        </LinearLayout>
+
+        <ImageView
+            android:id="@+id/right_icon"
+            android:layout_width="@dimen/notification_right_icon_size"
+            android:layout_height="@dimen/notification_right_icon_size"
+            android:layout_gravity="center_vertical|end"
+            android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+            android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+            android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+            android:background="@drawable/notification_large_icon_outline"
+            android:clipToOutline="true"
+            android:importantForAccessibility="no"
+            android:scaleType="centerCrop"
+            />
+
+        <LinearLayout
+            android:id="@+id/media_actions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layoutDirection="ltr"
+            android:orientation="horizontal"
+            >
+            <include
+                layout="@layout/notification_material_media_action"
+                android:id="@+id/action0"
+                />
+            <include
+                layout="@layout/notification_material_media_action"
+                android:id="@+id/action1"
+                />
+            <include
+                layout="@layout/notification_material_media_action"
+                android:id="@+id/action2"
+                />
+        </LinearLayout>
+
+        <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>
+</com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
new file mode 100644
index 0000000..ddf3ebc
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -0,0 +1,220 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<!-- Note: This is the old "Messaging Style" notification (not a conversation). -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.MessagingLayout
+    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="wrap_content"
+    android:clipChildren="false"
+    android:tag="messaging"
+    >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:clipChildren="false"
+        android:orientation="vertical"
+        >
+
+
+        <com.android.internal.widget.NotificationMaxHeightFrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:minHeight="@dimen/notification_2025_min_height"
+            android:clipChildren="false"
+            >
+
+            <ImageView
+                android:id="@+id/left_icon"
+                android:layout_width="@dimen/notification_2025_left_icon_size"
+                android:layout_height="@dimen/notification_2025_left_icon_size"
+                android:layout_gravity="center_vertical|start"
+                android:layout_marginStart="@dimen/notification_left_icon_start"
+                android:background="@drawable/notification_large_icon_outline"
+                android:clipToOutline="true"
+                android:importantForAccessibility="no"
+                android:scaleType="centerCrop"
+                android:visibility="gone"
+                />
+
+            <com.android.internal.widget.NotificationRowIconView
+                android:id="@+id/icon"
+                android:layout_width="@dimen/notification_2025_icon_circle_size"
+                android:layout_height="@dimen/notification_2025_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_2025_icon_circle_padding"
+                />
+
+            <FrameLayout
+                android:id="@+id/alternate_expand_target"
+                android:layout_width="@dimen/notification_2025_content_margin_start"
+                android:layout_height="match_parent"
+                android:layout_gravity="start"
+                android:importantForAccessibility="no"
+                android:focusable="false"
+                />
+
+            <!--
+              NOTE: to make the expansion animation of id/notification_messaging happen vertically,
+              its X positioning must be the left edge of the notification, so instead of putting the
+              layout_marginStart on the id/notification_headerless_view_row, we put it on
+              id/notification_top_line, making the layout here just a bit different from the base.
+              -->
+            <LinearLayout
+                android:id="@+id/notification_headerless_view_row"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="horizontal"
+                android:clipChildren="false"
+                >
+
+                <!--
+                  NOTE: because messaging will always have 2 lines, this LinearLayout should NOT
+                  have the id/notification_headerless_view_column, as that is used for modifying
+                   vertical margins to accommodate the single-line state that base supports
+                  -->
+                <LinearLayout
+                    android:layout_width="0px"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical"
+                    android:layout_weight="1"
+                    android:layout_marginBottom="@dimen/notification_2025_margin"
+                    android:layout_marginTop="@dimen/notification_2025_margin"
+                    android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+                    android:clipChildren="false"
+                    android:orientation="vertical"
+                    >
+
+                    <NotificationTopLineView
+                        android:id="@+id/notification_top_line"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:minHeight="@dimen/notification_headerless_line_height"
+                        android:clipChildren="false"
+                        android:theme="@style/Theme.DeviceDefault.Notification"
+                        >
+
+                        <!--
+                        NOTE: The notification_top_line_views layout contains the app_name_text.
+                        In order to include the title view at the beginning, the Notification.Builder
+                        has logic to hide that view whenever this title view is to be visible.
+                        -->
+
+                        <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>
+
+                    <LinearLayout
+                        android:id="@+id/notification_main_column"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="vertical"
+                        android:clipChildren="false"
+                        >
+                        <com.android.internal.widget.MessagingLinearLayout
+                            android:id="@+id/notification_messaging"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:clipChildren="false"
+                            android:spacing="@dimen/notification_messaging_spacing" />
+                    </LinearLayout>
+
+                </LinearLayout>
+
+                <!-- Images -->
+                <com.android.internal.widget.MessagingLinearLayout
+                    android:id="@+id/conversation_image_message_container"
+                    android:layout_width="@dimen/notification_right_icon_size"
+                    android:layout_height="@dimen/notification_right_icon_size"
+                    android:layout_gravity="center_vertical|end"
+                    android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+                    android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+                    android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+                    android:forceHasOverlappingRendering="false"
+                    android:spacing="0dp"
+                    android:clipChildren="false"
+                    android:visibility="gone"
+                    />
+
+                <ImageView
+                    android:id="@+id/right_icon"
+                    android:layout_width="@dimen/notification_right_icon_size"
+                    android:layout_height="@dimen/notification_right_icon_size"
+                    android:layout_gravity="center_vertical|end"
+                    android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+                    android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+                    android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+                    android:background="@drawable/notification_large_icon_outline"
+                    android:clipToOutline="true"
+                    android:importantForAccessibility="no"
+                    android:scaleType="centerCrop"
+                    />
+
+                <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>
+
+        </com.android.internal.widget.NotificationMaxHeightFrameLayout>
+
+    <LinearLayout
+            android:id="@+id/notification_action_list_margin_target"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="-20dp"
+            android:clipChildren="false"
+            android:orientation="vertical">
+        <include layout="@layout/notification_template_smart_reply_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/notification_content_margin"
+                android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+                android:layout_marginEnd="@dimen/notification_content_margin_end" />
+        <include layout="@layout/notification_material_action_list" />
+    </LinearLayout>
+</LinearLayout>
+</com.android.internal.widget.MessagingLayout>
diff --git a/core/res/res/layout/notification_2025_template_conversation.xml b/core/res/res/layout/notification_2025_template_conversation.xml
new file mode 100644
index 0000000..0c4c7fb
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_conversation.xml
@@ -0,0 +1,159 @@
+<?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
+  -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.ConversationLayout
+    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="wrap_content"
+    android:clipChildren="false"
+    android:tag="conversation"
+    android:theme="@style/Theme.DeviceDefault.Notification"
+    >
+
+    <include layout="@layout/notification_2025_conversation_icon_container" />
+
+    <!-- Wraps entire "expandable" notification -->
+    <com.android.internal.widget.RemeasuringLinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top"
+        android:clipToPadding="false"
+        android:clipChildren="false"
+        android:orientation="vertical"
+        >
+        <!-- LinearLayout for Expand Button-->
+        <com.android.internal.widget.RemeasuringLinearLayout
+            android:id="@+id/expand_button_and_content_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:gravity="start|top"
+            android:orientation="horizontal"
+            android:clipChildren="false"
+            android:clipToPadding="false">
+            <!--TODO: move this into a separate layout and share logic with the header to bring back app opps etc-->
+            <com.android.internal.widget.RemeasuringLinearLayout
+                android:id="@+id/notification_action_list_margin_target"
+                android:layout_width="0dp"
+                android:orientation="vertical"
+                android:layout_height="wrap_content"
+                android:layout_weight="1">
+
+                <!-- Header -->
+
+                <!-- Use layout_marginStart instead of paddingStart to work around strange
+                     measurement behavior on lower display densities. -->
+                <include
+                    layout="@layout/notification_template_conversation_header"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginBottom="2dp"
+                    android:layout_marginStart="@dimen/conversation_content_start"
+                    />
+
+                <!-- Messages -->
+                <com.android.internal.widget.MessagingLinearLayout
+                    android:id="@+id/notification_messaging"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:minHeight="@dimen/notification_text_size"
+                    android:spacing="@dimen/notification_messaging_spacing"
+                    android:clipToPadding="false"
+                    android:clipChildren="false"
+                    />
+            </com.android.internal.widget.RemeasuringLinearLayout>
+
+            <!-- This is where the expand button container will be placed when collapsed-->
+        </com.android.internal.widget.RemeasuringLinearLayout>
+
+        <include layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/notification_content_margin"
+            android:layout_marginStart="@dimen/conversation_content_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end" />
+        <include layout="@layout/notification_material_action_list" />
+    </com.android.internal.widget.RemeasuringLinearLayout>
+
+    <!--expand_button_a11y_container ensures talkback focus order is correct when view is expanded.
+    The -1px of marginTop and 1px of paddingTop make sure expand_button_a11y_container is prior to
+    its sibling view in accessibility focus order.
+    {see android.view.ViewGroup.addChildrenForAccessibility()}
+    expand_button_container will be moved under expand_button_and_content_container when collapsed,
+    this dynamic movement ensures message can flow under expand button when expanded-->
+    <FrameLayout
+        android:id="@+id/expand_button_a11y_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="end|top"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layout_marginTop="-1px"
+        android:paddingTop="1px"
+        >
+        <!--expand_button_container is dynamically placed between here and at the end of the
+        layout. It starts here since only FrameLayout layout params have gravity-->
+        <LinearLayout
+            android:id="@+id/expand_button_container"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="end|top"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:orientation="vertical">
+            <include layout="@layout/notification_close_button"
+                android:layout_width="@dimen/notification_close_button_size"
+                android:layout_height="@dimen/notification_close_button_size"
+                android:layout_gravity="end"
+                android:layout_marginEnd="20dp"
+                />
+            <!--expand_button_touch_container makes sure that we can nicely center the expand
+            content in the collapsed layout while the parent makes sure that we're never laid out
+            bigger than the messaging content.-->
+            <LinearLayout
+                android:id="@+id/expand_button_touch_container"
+                android:layout_width="wrap_content"
+                android:layout_height="@dimen/conversation_expand_button_height"
+                android:orientation="horizontal"
+                android:layout_gravity="end|top"
+                android:paddingEnd="0dp"
+                android:clipToPadding="false"
+                android:clipChildren="false"
+                >
+                <!-- Images -->
+                <com.android.internal.widget.MessagingLinearLayout
+                    android:id="@+id/conversation_image_message_container"
+                    android:forceHasOverlappingRendering="false"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_marginStart="@dimen/conversation_image_start_margin"
+                    android:spacing="0dp"
+                    android:layout_gravity="center"
+                    android:clipToPadding="false"
+                    android:clipChildren="false"
+                    />
+                <include layout="@layout/notification_expand_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    />
+            </LinearLayout>
+        </LinearLayout>
+    </FrameLayout>
+</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_base.xml b/core/res/res/layout/notification_2025_template_expanded_base.xml
new file mode 100644
index 0000000..e480fe5
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_base.xml
@@ -0,0 +1,86 @@
+<?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="wrap_content"
+    android:orientation="vertical"
+    android:clipChildren="false"
+    android:tag="big"
+    >
+
+    <LinearLayout
+        android:id="@+id/notification_action_list_margin_target"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/notification_content_margin"
+        android:orientation="vertical"
+        >
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_gravity="top"
+            >
+
+            <include layout="@layout/notification_2025_template_header" />
+
+            <LinearLayout
+                android:id="@+id/notification_main_column"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+                android:layout_marginEnd="@dimen/notification_content_margin_end"
+                android:layout_marginTop="@dimen/notification_content_margin_top"
+                android:orientation="vertical"
+                >
+
+                <include layout="@layout/notification_template_part_line1" />
+
+                <include layout="@layout/notification_template_text_multiline" />
+
+                <include
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/notification_progress_bar_height"
+                    android:layout_marginTop="@dimen/notification_progress_margin_top"
+                    layout="@layout/notification_template_progress"
+                    />
+            </LinearLayout>
+
+            <include layout="@layout/notification_template_right_icon" />
+        </FrameLayout>
+
+        <ViewStub
+            android:layout="@layout/notification_2025_reply_container"
+            android:id="@+id/notification_material_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+
+        <include
+            layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginTop="@dimen/notification_content_margin"
+            />
+
+        <include layout="@layout/notification_material_action_list" />
+    </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_big_picture.xml b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml
new file mode 100644
index 0000000..18bafe0
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml
@@ -0,0 +1,93 @@
+<?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="match_parent"
+    android:tag="bigPicture"
+    android:clipChildren="false"
+    >
+
+    <include layout="@layout/notification_2025_template_header" />
+
+    <include layout="@layout/notification_template_right_icon" />
+
+    <LinearLayout
+        android:id="@+id/notification_action_list_margin_target"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="top"
+        android:layout_marginTop="@dimen/notification_content_margin_top"
+        android:clipToPadding="false"
+        android:orientation="vertical"
+        >
+
+        <LinearLayout
+            android:id="@+id/notification_main_column"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:orientation="vertical"
+            >
+
+            <include layout="@layout/notification_template_part_line1" />
+
+            <include
+                layout="@layout/notification_template_progress"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/notification_progress_bar_height"
+                android:layout_marginTop="@dimen/notification_progress_margin_top"
+                />
+
+            <include layout="@layout/notification_template_text_multiline" />
+        </LinearLayout>
+
+        <com.android.internal.widget.BigPictureNotificationImageView
+            android:id="@+id/big_picture"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:adjustViewBounds="true"
+            android:layout_weight="1"
+            android:layout_marginTop="13dp"
+            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:background="@drawable/notification_big_picture_outline"
+            android:clipToOutline="true"
+            android:scaleType="centerCrop"
+            />
+
+        <ViewStub
+            android:layout="@layout/notification_material_reply_text"
+            android:id="@+id/notification_2025_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+
+        <include
+            layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginTop="@dimen/notification_content_margin"
+            />
+
+        <include layout="@layout/notification_material_action_list" />
+    </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_big_text.xml b/core/res/res/layout/notification_2025_template_expanded_big_text.xml
new file mode 100644
index 0000000..9ff141b
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_big_text.xml
@@ -0,0 +1,94 @@
+<?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="wrap_content"
+    android:clipChildren="false"
+    android:tag="bigText"
+    >
+
+    <include layout="@layout/notification_2025_template_header" />
+
+    <com.android.internal.widget.RemeasuringLinearLayout
+        android:id="@+id/notification_action_list_margin_target"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top"
+        android:layout_marginTop="@dimen/notification_content_margin_top"
+        android:layout_marginBottom="@dimen/notification_content_margin"
+        android:clipToPadding="false"
+        android:orientation="vertical"
+        >
+
+        <com.android.internal.widget.RemeasuringLinearLayout
+            android:id="@+id/notification_main_column"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
+            android:paddingStart="@dimen/notification_2025_content_margin_start"
+            android:paddingEnd="@dimen/notification_content_margin_end"
+            android:clipToPadding="false"
+            android:orientation="vertical"
+            android:layout_weight="1"
+            >
+
+            <include layout="@layout/notification_template_part_line1" />
+
+            <include
+                layout="@layout/notification_template_progress"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/notification_progress_bar_height"
+                android:layout_marginTop="@dimen/notification_progress_margin_top"
+                android:layout_marginBottom="6dp"
+                />
+
+            <com.android.internal.widget.ImageFloatingTextView
+                android:id="@+id/big_text"
+                style="@style/Widget.DeviceDefault.Notification.Text"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/notification_text_margin_top"
+                android:singleLine="false"
+                android:gravity="top"
+                android:visibility="gone"
+                android:textAlignment="viewStart"
+                />
+        </com.android.internal.widget.RemeasuringLinearLayout>
+
+        <ViewStub
+            android:layout="@layout/notification_material_reply_text"
+            android:id="@+id/notification_2025_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+
+        <include
+            layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginTop="@dimen/notification_content_margin"
+            />
+
+        <include layout="@layout/notification_material_action_list" />
+    </com.android.internal.widget.RemeasuringLinearLayout>
+
+    <include layout="@layout/notification_template_right_icon" />
+</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_call.xml b/core/res/res/layout/notification_2025_template_expanded_call.xml
new file mode 100644
index 0000000..3ff71b7
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_call.xml
@@ -0,0 +1,109 @@
+<?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
+  -->
+
+<!-- Extends FrameLayout -->
+<com.android.internal.widget.CallLayout
+    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="wrap_content"
+    android:clipChildren="false"
+    android:tag="call"
+    android:theme="@style/Theme.DeviceDefault.Notification"
+    >
+
+    <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts -->
+    <include layout="@layout/notification_2025_conversation_icon_container" />
+
+    <LinearLayout
+        android:id="@+id/notification_action_list_margin_target"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/notification_content_margin"
+        android:orientation="vertical"
+        >
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="horizontal"
+            >
+
+            <LinearLayout
+                android:id="@+id/notification_main_column"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:layout_marginStart="@dimen/conversation_content_start"
+                android:orientation="vertical"
+                android:minHeight="68dp"
+                >
+
+                <include
+                    layout="@layout/notification_template_conversation_header"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    />
+
+                <include layout="@layout/notification_template_text_multiline" />
+
+                <include
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/notification_progress_bar_height"
+                    android:layout_marginTop="@dimen/notification_progress_margin_top"
+                    layout="@layout/notification_template_progress"
+                    />
+            </LinearLayout>
+
+            <FrameLayout
+                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"
+                    />
+
+            </FrameLayout>
+
+        </LinearLayout>
+
+        <ViewStub
+            android:layout="@layout/notification_material_reply_text"
+            android:id="@+id/notification_material_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+
+        <include
+            layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginTop="@dimen/notification_content_margin"
+            />
+
+        <include layout="@layout/notification_material_action_list" />
+
+    </LinearLayout>
+
+</com.android.internal.widget.CallLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_inbox.xml b/core/res/res/layout/notification_2025_template_expanded_inbox.xml
new file mode 100644
index 0000000..9fb44ccc
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_inbox.xml
@@ -0,0 +1,131 @@
+<?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="wrap_content"
+    android:tag="inbox"
+    android:clipChildren="false"
+    >
+    <include layout="@layout/notification_2025_template_header" />
+    <LinearLayout
+            android:id="@+id/notification_action_list_margin_target"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="top"
+            android:layout_marginTop="@dimen/notification_content_margin_top"
+            android:clipToPadding="false"
+            android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/notification_main_column"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
+            android:paddingStart="@dimen/notification_2025_content_margin_start"
+            android:paddingEnd="@dimen/notification_content_margin_end"
+            android:layout_weight="1"
+            android:clipToPadding="false"
+            android:orientation="vertical"
+            >
+            <include layout="@layout/notification_template_part_line1"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+            <include layout="@layout/notification_template_progress"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/notification_progress_bar_height"
+                android:layout_marginTop="@dimen/notification_progress_margin_top"
+                android:layout_marginBottom="2dp"/>
+            <TextView android:id="@+id/inbox_text0"
+                style="@style/Widget.DeviceDefault.Notification.Text"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:visibility="gone"
+                android:layout_weight="1"
+                />
+            <TextView android:id="@+id/inbox_text1"
+                style="@style/Widget.DeviceDefault.Notification.Text"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:visibility="gone"
+                android:layout_weight="1"
+                />
+            <TextView android:id="@+id/inbox_text2"
+                style="@style/Widget.DeviceDefault.Notification.Text"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:visibility="gone"
+                android:layout_weight="1"
+                />
+            <TextView android:id="@+id/inbox_text3"
+                style="@style/Widget.DeviceDefault.Notification.Text"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:visibility="gone"
+                android:layout_weight="1"
+                />
+            <TextView android:id="@+id/inbox_text4"
+                style="@style/Widget.DeviceDefault.Notification.Text"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:visibility="gone"
+                android:layout_weight="1"
+                />
+            <TextView android:id="@+id/inbox_text5"
+                style="@style/Widget.DeviceDefault.Notification.Text"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:visibility="gone"
+                android:layout_weight="1"
+                />
+            <TextView android:id="@+id/inbox_text6"
+                style="@style/Widget.DeviceDefault.Notification.Text"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:visibility="gone"
+                android:layout_weight="1"
+                />
+        </LinearLayout>
+        <ViewStub android:layout="@layout/notification_material_reply_text"
+                android:id="@+id/notification_2025_reply_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+        <include layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginTop="@dimen/notification_content_margin" />
+        <include layout="@layout/notification_material_action_list" />
+    </LinearLayout>
+    <include layout="@layout/notification_template_right_icon" />
+</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_media.xml b/core/res/res/layout/notification_2025_template_expanded_media.xml
new file mode 100644
index 0000000..578a0b2
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_media.xml
@@ -0,0 +1,103 @@
+<?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
+  -->
+
+<!-- Note: This is the expanded version of the old media style notification (different from UMO). -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.MediaNotificationView
+    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="wrap_content"
+    android:tag="bigMediaNarrow"
+    >
+
+    <include layout="@layout/notification_2025_template_header" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:id="@+id/notification_media_content"
+        >
+
+        <LinearLayout
+            android:id="@+id/notification_main_column"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/notification_content_margin_top"
+            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:orientation="vertical"
+            >
+            <include layout="@layout/notification_template_part_line1"/>
+            <include layout="@layout/notification_template_text"/>
+        </LinearLayout>
+
+        <!-- this FrameLayout's minHeight serves as a padding for the content above -->
+        <FrameLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_2025_media_actions_margin_start"
+            android:minHeight="@dimen/notification_content_margin"
+            >
+
+            <!-- Nesting in FrameLayout is required to ensure that the marginStart actually applies
+                 at the start instead of always the left, given that the media_actions LinearLayout
+                 has layoutDirection="ltr". -->
+            <LinearLayout
+                android:id="@+id/media_actions"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="@dimen/media_notification_actions_padding_bottom"
+                android:gravity="top"
+                android:orientation="horizontal"
+                android:layoutDirection="ltr"
+                >
+
+                <include
+                    layout="@layout/notification_material_media_action"
+                    android:id="@+id/action0"
+                    />
+
+                <include
+                    layout="@layout/notification_material_media_action"
+                    android:id="@+id/action1"
+                    />
+
+                <include
+                    layout="@layout/notification_material_media_action"
+                    android:id="@+id/action2"
+                    />
+
+                <include
+                    layout="@layout/notification_material_media_action"
+                    android:id="@+id/action3"
+                    />
+
+                <include
+                    layout="@layout/notification_material_media_action"
+                    android:id="@+id/action4"
+                    />
+            </LinearLayout>
+
+        </FrameLayout>
+
+    </LinearLayout>
+
+    <include layout="@layout/notification_template_right_icon" />
+
+</com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/layout/notification_2025_template_expanded_messaging.xml b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
new file mode 100644
index 0000000..5b58726
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
@@ -0,0 +1,71 @@
+<?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
+  -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.MessagingLayout
+    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="wrap_content"
+    android:clipToPadding="false"
+    android:clipChildren="false"
+    android:tag="messaging"
+    >
+
+    <include layout="@layout/notification_2025_template_header"/>
+
+    <com.android.internal.widget.RemeasuringLinearLayout
+            android:id="@+id/notification_action_list_margin_target"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
+            android:layout_marginTop="@dimen/notification_content_margin_top"
+            android:clipChildren="false"
+            android:orientation="vertical">
+
+        <com.android.internal.widget.RemeasuringLinearLayout
+            android:id="@+id/notification_main_column"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
+            android:layout_weight="1"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:orientation="vertical"
+            android:clipChildren="false"
+            >
+            <com.android.internal.widget.MessagingLinearLayout
+                android:id="@+id/notification_messaging"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:clipChildren="false"
+                android:spacing="@dimen/notification_messaging_spacing" />
+        </com.android.internal.widget.RemeasuringLinearLayout>
+
+        <include layout="@layout/notification_template_smart_reply_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/notification_content_margin"
+                android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+                android:layout_marginEnd="@dimen/notification_content_margin_end" />
+
+        <include layout="@layout/notification_material_action_list" />
+
+    </com.android.internal.widget.RemeasuringLinearLayout>
+
+    <include layout="@layout/notification_template_right_icon" />
+
+</com.android.internal.widget.MessagingLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_progress.xml b/core/res/res/layout/notification_2025_template_expanded_progress.xml
new file mode 100644
index 0000000..afa4bc6
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_expanded_progress.xml
@@ -0,0 +1,122 @@
+<?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="wrap_content"
+    android:orientation="vertical"
+    android:clipChildren="false"
+    android:tag="progress"
+    >
+
+    <LinearLayout
+        android:id="@+id/notification_action_list_margin_target"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/notification_content_margin"
+        android:orientation="vertical"
+        >
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_gravity="top"
+            >
+
+            <include layout="@layout/notification_2025_template_header" />
+
+            <LinearLayout
+                android:id="@+id/notification_main_column"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+                android:layout_marginEnd="@dimen/notification_content_margin_end"
+                android:layout_marginTop="@dimen/notification_content_margin_top"
+                android:orientation="vertical"
+                >
+
+                <include layout="@layout/notification_template_part_line1" />
+
+                <include layout="@layout/notification_template_text_multiline" />
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="center_vertical"
+                    android:layout_marginTop="@dimen/notification_progress_margin_top"
+                    android:orientation="horizontal">
+
+                    <com.android.internal.widget.CachingIconView
+                        android:id="@+id/notification_progress_start_icon"
+                        android:layout_width="@dimen/notification_progress_icon_size"
+                        android:layout_height="@dimen/notification_progress_icon_size"
+                        android:background="@drawable/notification_progress_icon_background"
+                        android:clipToOutline="true"
+                        android:importantForAccessibility="no"
+                        android:layout_marginEnd="@dimen/notification_progress_margin_horizontal"
+                        android:scaleType="centerCrop"
+                        android:maxDrawableWidth="@dimen/notification_progress_icon_size"
+                        android:maxDrawableHeight="@dimen/notification_progress_icon_size"
+                        />
+
+
+                    <include
+                        android:layout_width="0dp"
+                        android:layout_weight="1"
+                        android:layout_height="@dimen/notification_progress_tracker_height"
+                        layout="@layout/notification_template_notification_progress_bar"
+                        />
+
+                    <com.android.internal.widget.CachingIconView
+                        android:id="@+id/notification_progress_end_icon"
+                        android:layout_width="@dimen/notification_progress_icon_size"
+                        android:layout_height="@dimen/notification_progress_icon_size"
+                        android:background="@drawable/notification_progress_icon_background"
+                        android:clipToOutline="true"
+                        android:importantForAccessibility="no"
+                        android:scaleType="centerCrop"
+                        android:layout_marginStart="@dimen/notification_progress_margin_horizontal"
+                        android:maxDrawableWidth="@dimen/notification_progress_icon_size"
+                        android:maxDrawableHeight="@dimen/notification_progress_icon_size"
+                        />
+                </LinearLayout>
+            </LinearLayout>
+
+            <include layout="@layout/notification_template_right_icon" />
+        </FrameLayout>
+
+        <ViewStub
+            android:layout="@layout/notification_material_reply_text"
+            android:id="@+id/notification_2025_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+
+        <include
+            layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginTop="@dimen/notification_content_margin"
+            />
+
+        <include layout="@layout/notification_material_action_list" />
+    </LinearLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_2025_template_text.xml b/core/res/res/layout/notification_2025_text.xml
similarity index 100%
rename from core/res/res/layout/notification_2025_template_text.xml
rename to core/res/res/layout/notification_2025_text.xml
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 565d584..5795936 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -13,7 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<!-- extends FrameLayout -->
+<!-- extends RelativeLayout -->
 <NotificationHeaderView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/notification_header"
@@ -62,7 +62,7 @@
         android:layout_height="match_parent"
         android:layout_alignParentStart="true"
         android:layout_centerVertical="true"
-        android:layout_toStartOf="@id/notification_buttons_column"
+        android:layout_toStartOf="@id/expand_button"
         android:layout_alignWithParentIfMissing="true"
         android:clipChildren="false"
         android:gravity="center_vertical"
@@ -83,28 +83,17 @@
         android:focusable="false"
         />
 
-    <LinearLayout
-        android:id="@+id/notification_buttons_column"
+    <include layout="@layout/notification_expand_button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_alignParentEnd="true"
-        android:orientation="vertical"
-        >
+        android:layout_centerVertical="true"
+        android:layout_alignParentEnd="true" />
 
-        <include layout="@layout/notification_close_button"
-            android:layout_width="@dimen/notification_close_button_size"
-            android:layout_height="@dimen/notification_close_button_size"
-            android:layout_gravity="end"
-            android:layout_marginEnd="20dp"
-            />
-
-        <include layout="@layout/notification_expand_button"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentEnd="true"
-            android:layout_centerVertical="true"
-            />
-
-    </LinearLayout>
+    <include layout="@layout/notification_close_button"
+        android:id="@+id/close_button"
+        android:layout_width="@dimen/notification_close_button_size"
+        android:layout_height="@dimen/notification_close_button_size"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentEnd="true" />
 
 </NotificationHeaderView>
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 29f14a4..227f84b 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -157,39 +157,27 @@
             android:maxDrawableHeight="@dimen/notification_right_icon_size"
             />
 
-        <LinearLayout
-            android:id="@+id/notification_buttons_column"
+        <FrameLayout
+            android:id="@+id/expand_button_touch_container"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:layout_alignParentEnd="true"
-            android:orientation="vertical"
+            android:minWidth="@dimen/notification_content_margin_end"
             >
 
-            <include layout="@layout/notification_close_button"
-                android:layout_width="@dimen/notification_close_button_size"
-                android:layout_height="@dimen/notification_close_button_size"
-                android:layout_gravity="end"
-                android:layout_marginEnd="20dp"
+            <include layout="@layout/notification_expand_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical|end"
                 />
 
-            <FrameLayout
-                android:id="@+id/expand_button_touch_container"
-                android:layout_width="wrap_content"
-                android:layout_height="0dp"
-                android:layout_weight="1"
-                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>
 
     </LinearLayout>
 
+    <include layout="@layout/notification_close_button"
+        android:id="@+id/close_button"
+        android:layout_width="@dimen/notification_close_button_size"
+        android:layout_height="@dimen/notification_close_button_size"
+        android:layout_gravity="top|end" />
+
 </FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index 6e9d17f..5459fa8 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -15,6 +15,7 @@
   ~ limitations under the License
   -->
 
+<!-- extends FrameLayout -->
 <com.android.internal.widget.MediaNotificationView
     android:id="@+id/status_bar_latest_event_content"
     xmlns:android="http://schemas.android.com/apk/res/android"
@@ -191,4 +192,11 @@
         </FrameLayout>
 
     </LinearLayout>
+
+    <include layout="@layout/notification_close_button"
+        android:id="@+id/close_button"
+        android:layout_width="@dimen/notification_close_button_size"
+        android:layout_height="@dimen/notification_close_button_size"
+        android:layout_gravity="top|end" />
+
 </com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index 1eae41d..2b3b7d8 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -195,6 +195,12 @@
 
             </LinearLayout>
 
+            <include layout="@layout/notification_close_button"
+                android:id="@+id/close_button"
+                android:layout_width="@dimen/notification_close_button_size"
+                android:layout_height="@dimen/notification_close_button_size"
+                android:layout_gravity="top|end" />
+
         </com.android.internal.widget.NotificationMaxHeightFrameLayout>
 
     <LinearLayout
diff --git a/core/res/res/layout/preference_list_content.xml b/core/res/res/layout/preference_list_content.xml
index bed80ed..7a2fb0b 100644
--- a/core/res/res/layout/preference_list_content.xml
+++ b/core/res/res/layout/preference_list_content.xml
@@ -20,6 +20,7 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
+    android:fitsSystemWindows="true"
     android:layout_height="match_parent"
     android:layout_width="match_parent">
 
diff --git a/core/res/res/layout/preference_list_content_material.xml b/core/res/res/layout/preference_list_content_material.xml
index 37b4119..23c8250 100644
--- a/core/res/res/layout/preference_list_content_material.xml
+++ b/core/res/res/layout/preference_list_content_material.xml
@@ -20,6 +20,7 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
+    android:fitsSystemWindows="true"
     android:layout_height="match_parent"
     android:layout_width="match_parent">
 
diff --git a/core/res/res/layout/preference_list_content_single.xml b/core/res/res/layout/preference_list_content_single.xml
index 726ce78..4f072da 100644
--- a/core/res/res/layout/preference_list_content_single.xml
+++ b/core/res/res/layout/preference_list_content_single.xml
@@ -19,6 +19,7 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
+    android:fitsSystemWindows="true"
     android:layout_height="match_parent"
     android:layout_width="match_parent">
 
diff --git a/core/res/res/layout/preference_list_fragment.xml b/core/res/res/layout/preference_list_fragment.xml
index c43975e..44a5df9 100644
--- a/core/res/res/layout/preference_list_fragment.xml
+++ b/core/res/res/layout/preference_list_fragment.xml
@@ -19,6 +19,7 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
+    android:fitsSystemWindows="true"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
     android:background="@android:color/transparent"
diff --git a/core/res/res/layout/preference_list_fragment_material.xml b/core/res/res/layout/preference_list_fragment_material.xml
index db2fe7d..4df7602 100644
--- a/core/res/res/layout/preference_list_fragment_material.xml
+++ b/core/res/res/layout/preference_list_fragment_material.xml
@@ -19,6 +19,7 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
+    android:fitsSystemWindows="true"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
     android:background="@android:color/transparent"
diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml
index 78299ab..7bb6fcf 100644
--- a/core/res/res/layout/side_fps_toast.xml
+++ b/core/res/res/layout/side_fps_toast.xml
@@ -25,7 +25,7 @@
         android:layout_height="wrap_content"
         android:layout_width="0dp"
         android:layout_weight="6"
-        android:paddingBottom="10dp"
+        android:paddingBottom="16dp"
         android:text="@string/fp_power_button_enrollment_title"
         android:textColor="@color/side_fps_text_color"
         android:paddingLeft="20dp"/>
@@ -37,7 +37,7 @@
         android:layout_height="wrap_content"
         android:layout_width="0dp"
         android:layout_weight="3"
-        android:paddingBottom="10dp"
+        android:paddingBottom="16dp"
         android:text="@string/fp_power_button_enrollment_button_text"
         style="?android:attr/buttonBarNegativeButtonStyle"
         android:textColor="@color/side_fps_button_color"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index a4499ef..bfa3b06 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Beller-ID se verstek is beperk. Volgende oproep: nie beperk nie"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Beller-ID se verstek is nie beperk nie. Volgende oproep: beperk"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Beller-ID se verstek is nie beperk nie. Volgende oproep: nie beperk nie"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Diens nie verskaf nie."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Jy kan nie die beller-ID-instelling verander nie."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Het data oorgeskakel na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -523,7 +529,7 @@
     <string name="permdesc_systemCamera" msgid="5938360914419175986">"Hierdie bevoorregte of stelselprogram kan enige tyd met \'n stelselkamera foto\'s neem en video\'s opneem. Vereis dat die program ook die android.permission.CAMERA-toestemming het"</string>
     <string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Laat \'n program of diens toe om terugbeloproepe te ontvang oor kameratoestelle wat oopgemaak of toegemaak word."</string>
     <string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"Hierdie program kan terugbeloproepe ontvang wanneer enige kameratoestel oopgemaak (deur watter program) of toegemaak word."</string>
-    <string name="permlab_cameraHeadlessSystemUser" msgid="680194666834500050">"Laat ’n program of diens toe om toegang tot die kamera te verkry as ’n stelselgebruiker sonder koppelvlak."</string>
+    <string name="permlab_cameraHeadlessSystemUser" msgid="680194666834500050">"Laat ’n app of diens toe om toegang tot die kamera te verkry as ’n stelselgebruiker sonder koppelvlak."</string>
     <string name="permdesc_cameraHeadlessSystemUser" msgid="6963163319710996412">"Hierdie app het toegang tot die kamera as ’n stelselgebruiker sonder koppelvlak."</string>
     <string name="permlab_vibrate" msgid="8596800035791962017">"beheer vibrasie"</string>
     <string name="permdesc_vibrate" msgid="8733343234582083721">"Laat die program toe om die vibrator te beheer."</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> tot <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Enige kalender"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> demp sekere klanke"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Daar is \'n interne probleem met jou toestel en dit sal dalk onstabiel wees totdat jy \'n fabriekterugstelling doen."</string>
@@ -2038,7 +2045,7 @@
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Jy kan nie op jou <xliff:g id="DEVICE">%1$s</xliff:g> toegang hiertoe kry nie. Probeer eerder op jou Android TV-toestel."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Jy kan nie op jou <xliff:g id="DEVICE">%1$s</xliff:g> toegang hiertoe kry nie. Probeer eerder op jou tablet."</string>
     <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Jy kan nie op jou <xliff:g id="DEVICE">%1$s</xliff:g> toegang hiertoe kry nie. Probeer eerder op jou foon."</string>
-    <string name="deprecated_target_sdk_message" msgid="5246906284426844596">"Hierdie program is vir ’n ouer weergawe van Android gebou. Dit sal dalk nie behoorlik werk nie en dit sluit nie die jongste sekuriteit en privaatheidbeskermings in nie. Kyk of daar ’n opdatering is of kontak die program se ontwikkelaar."</string>
+    <string name="deprecated_target_sdk_message" msgid="5246906284426844596">"Hierdie app is vir ’n ouer weergawe van Android gebou. Dit sal dalk nie behoorlik werk nie en dit sluit nie die jongste sekuriteit en privaatheidbeskermings in nie. Kyk of daar ’n opdatering is of kontak die app se ontwikkelaar."</string>
     <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kyk vir opdatering"</string>
     <string name="deprecated_abi_message" msgid="6820548011196218091">"Hierdie app is nie met die jongste weergawe van Android versoenbaar nie. Kyk of daar ’n opdatering is, of kontak die app se ontwikkelaar."</string>
     <string name="new_sms_notification_title" msgid="6528758221319927107">"Jy het nuwe boodskappe"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 225f04d..dcb7186 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"የደዋይ  ID  ወደ ተከልክሏል ነባሪዎች።ቀጥሎ ጥሪ፡ አልተከለከለም"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"የደዋይ  ID  ወደ አልተከለከለም ነባሪዎች።ቀጥሎ ጥሪ፡ ተከልክሏል"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"የደዋይ ID ነባሪዎች ወደአልተከለከለም። ቀጥሎ ጥሪ፡አልተከለከለም"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"አገልግሎት አልቀረበም።"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"የደዋይ መታወቂያ ቅንብሮች  መለወጥ አትችልም፡፡"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"ውሂብ ወደ <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ተቀይሯል"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"፣ "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"ከ<xliff:g id="START">%1$s</xliff:g> እስከ <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>፣ <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ማንኛውም ቀን መቁጠሪያ"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> አንዳንድ ድምጾችን እየዘጋ ነው"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"መሣሪያዎ ላይ የውስጣዊ ችግር አለ፣ የፋብሪካ ውሂብ ዳግም እስኪያስጀምሩት ድረስ ላይረጋጋ ይችላል።"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 484d220..9c590ad 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -75,6 +75,9 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"الإعداد التلقائي لمعرف المتصل هو محظور  . الاتصال التالي: غير محظور"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"الإعداد التلقائي لمعرف المتصل هو غير محظور  . الاتصال التالي: محظور"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"الإعداد التلقائي لمعرف المتصل هو غير محظور  . الاتصال التالي: غير محظور"</string>
+    <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"‏هذا التطبيق غير متوافق مع حجم الصفحة الذي يبلغ 16 كيلوبايت. وتعذَّر إكمال عملية التأكُّد من محاذاة ملفات APK. وسيتم تشغيل هذا التطبيق باستخدام الوضع المتوافق مع حجم الصفحة. ولكي يتوافق التطبيق مع الأجهزة بشكلٍ أفضل، يُرجى إعادة تحويله برمجيًا ليصبح متوافقًا مع حجم الصفحة الذي يبلغ 16 كيلوبايت. لمزيد من المعلومات، يُرجى الاطّلاع على الرابط &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"‏هذا التطبيق غير متوافق مع حجم الصفحة الذي يبلغ 16 كيلوبايت. وتعذَّر إكمال عملية التأكُّد من محاذاة ملفات ELF. وسيتم تشغيل هذا التطبيق باستخدام الوضع المتوافق مع حجم الصفحة. ولكي يتوافق التطبيق مع الأجهزة بشكلٍ أفضل، يُرجى إعادة تحويله برمجيًا ليصبح متوافقًا مع حجم الصفحة الذي يبلغ 16 كيلوبايت. لمزيد من المعلومات، يُرجى الاطّلاع على الرابط &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"‏هذا التطبيق غير متوافق مع حجم الصفحة الذي يبلغ 16 كيلوبايت. وتعذَّر إكمال عمليات التأكُّد من محاذاة ملفات APK وELF. وسيتم تشغيل هذا التطبيق باستخدام الوضع المتوافق مع حجم الصفحة. ولكي يتوافق التطبيق مع الأجهزة بشكلٍ أفضل، يُرجى إعادة تحويله برمجيًا ليصبح متوافقًا مع حجم الصفحة الذي يبلغ 16 كيلوبايت. لمزيد من المعلومات، يُرجى الاطّلاع على الرابط &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"الخدمة غير متوفرة."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"لا يمكنك تغيير إعداد معرّف المتصل."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"تم تبديل البيانات إلى <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1951,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"، "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"من <xliff:g id="START">%1$s</xliff:g> إلى <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"من <xliff:g id="START">%1$s</xliff:g> إلى <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>، <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"أي تقويم"</string>
     <string name="muted_by" msgid="91464083490094950">"يعمل <xliff:g id="THIRD_PARTY">%1$s</xliff:g> على كتم بعض الأصوات."</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"حدثت مشكلة داخلية في جهازك، وقد لا يستقر وضعه حتى إجراء إعادة الضبط على الإعدادات الأصلية."</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index fdddc70..319cf4bd 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"কলাৰ আইডি সীমিত কৰিবলৈ পূর্বনির্ধাৰণ কৰা হৈছে। পৰৱৰ্তী কল: সীমিত কৰা হৈছে"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"কলাৰ আইডি সীমিত নকৰিবলৈ পূর্বনির্ধাৰণ কৰা হৈছে। পৰৱৰ্তী কল: সীমিত কৰা হোৱা নাই"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"কলাৰ আইডি সীমিত নকৰিবলৈ পূর্বনির্ধাৰণ কৰা হৈছে। পৰৱৰ্তী কল: সীমিত কৰা হোৱা নাই"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"সুবিধা যোগান ধৰা হোৱা নাই।"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"আপুনি কলাৰ আইডি ছেটিং সলনি কৰিব নোৱাৰে।"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"ডেটা <xliff:g id="CARRIERDISPLAY">%s</xliff:g>লৈ সলনি কৰা হৈছে"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>ৰ পৰা <xliff:g id="END">%2$s</xliff:g>লৈ"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"যিকোনো কেলেণ্ডাৰ"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>এ কিছুমান ধ্বনি মিউট কৰি আছে"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"আপোনাৰ ডিভাইচত এটা আভ্যন্তৰীণ সমস্যা আছে আৰু আপুনি ফেক্টৰী ডেটা ৰিছেট নকৰালৈকে ই সুস্থিৰভাৱে কাম নকৰিব পাৰে।"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 003c0db..1f8fe2c 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Zəng edənin kimliyi defolt olaraq qadağan deyil. Növbəti zəng: Qadağan deyil"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Zəng edənin kimliyi defolt olaraq qadağan deyil. Növbəti zəng: Qadağandır"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Zəng edənin kimliyi defolt olaraq qadağan deyil. Növbəti zəng: Qadağan deyil"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Xidmət təmin edilməyib."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Çağrı kimliyi ayarını dəyişə bilməzsiniz."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Data <xliff:g id="CARRIERDISPLAY">%s</xliff:g> operatoruna keçirilib"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"İstənilən təqvim"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> bəzi səsləri səssiz rejimə salır"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Cihazınızın daxili problemi var və istehsalçı sıfırlanması olmayana qədər qeyri-stabil ola bilər."</string>
@@ -2418,7 +2425,7 @@
     <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="private_space_biometric_prompt_title" msgid="5777592909271728154">"Şəxsi sahə"</string>
+    <string name="private_space_biometric_prompt_title" msgid="5777592909271728154">"Məxfi sahə"</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">"Tətbiq kontenti güvənlik məsələlərinə görə ekran paylaşımından gizlədildi"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 5a848ba..645239d 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -72,6 +72,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID pozivaoca je podrazumevano ograničen. Sledeći poziv: Nije ograničen."</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID pozivaoca podrazumevano nije ograničen. Sledeći poziv: ograničen."</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID pozivaoca podrazumevano nije ograničen. Sledeći poziv: Nije ograničen."</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Usluga nije dobavljena."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ne možete da promenite podešavanje ID-a korisnika."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Mobilni podaci su prebačeni na operatera <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1948,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Bilo koji kalendar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> isključuje neke zvuke"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Došlo je do internog problema u vezi sa uređajem i možda će biti nestabilan dok ne obavite resetovanje na fabrička podešavanja."</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index ce98a5f..7fce298 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -73,6 +73,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Ідэнтыфікатар АВН па змаўчанні абмежаваны. Наступны выклік: не абмежавана"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Па змаўчанні ідэнтыфікатар АВН не абмежаваны. Наступны выклік: абмежаваны"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Налады ідэнтыфікатару АВН па змаўчанні: не абмяжавана. Наступны выклік: не абмежавана"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Служба не прадастаўляецца."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Вы не можаце змяніць налады ідэнтыфікатара абанента, якi тэлефануе."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Мабільны трафік пераключаны на аператара \"<xliff:g id="CARRIERDISPLAY">%s</xliff:g>\""</string>
@@ -1949,6 +1955,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Любы каляндар"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> выключае некаторыя гукі"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"На вашай прыладзе ўзнікла ўнутраная праблема, і яна можа працаваць нестабільна, пакуль вы не зробіце скід да заводскіх налад."</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index aad5d48..aa03a69 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Стандартната идентификация на повикванията е „забранено“. За следващото обаждане тя е разрешена."</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Стандартната идентификация на повикванията е „разрешено“. За следващото обаждане тя е забранена."</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Стандартната идентификация на повикванията е „разрешено“. За следващото обаждане тя е разрешена."</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Услугата не е обезпечена."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Не можете да променяте настройката за идентификация на обажданията."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Преминахте към мобилни данни от <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"От <xliff:g id="START">%1$s</xliff:g> до <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Всички календари"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> заглушава някои звуци"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Възникна вътрешен проблем с устройството ви. То може да е нестабилно, докато не възстановите фабричните настройки."</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index d52f6e9..84d9c20 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ডিফল্টরূপে কলার আইডি সীমাবদ্ধ করা থাকে৷ পরবর্তী কল: সীমাবদ্ধ নয়"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ডিফল্টরূপে কলার আইডি সীমাবদ্ধ করা থাকে না৷ পরবর্তী কল: সীমাবদ্ধ"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ডিফল্টরূপে কলার আইডি সীমাবদ্ধ করা থাকে না৷ পরবর্তী কল: সীমাবদ্ধ নয়"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"পরিষেবা প্রস্তুত নয়৷"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"আপনি কলার আইডি এর সেটিংস পরিবর্তন করতে পারবেন না৷"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g>-এর ডেটা ব্যবহার করা হয়েছে"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> থেকে <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"যেকোনও ক্যালেন্ডার"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> কিছু সাউন্ডকে মিউট করে দিচ্ছে"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"আপনার ডিভাইসে একটি অভ্যন্তরীন সমস্যা হয়েছে, এবং আপনি যতক্ষণ না পর্যন্ত এটিকে ফ্যাক্টরি ডেটা রিসেট করছেন ততক্ষণ এটি ঠিকভাবে কাজ নাও করতে পারে৷"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index b74b139..13d4ac3 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -72,6 +72,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Prikaz ID-a pozivaoca u zadanim postavkama zabranjen. Sljedeći poziv: nije zabranjen"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Prikaz ID-a pozivaoca u zadanim postavkama nije zabranjen. Sljedeći poziv: zabranjen"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Prikaz ID-a pozivaoca u zadanim postavkama nije zabranjen. Sljedeći poziv: nije zabranjen"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Uslugu nije moguće koristiti."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ne možete promijeniti postavke ID-a pozivaoca."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Prijenos podataka usmjeravanjem na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1948,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Bilo koji kalendar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> isključuje neke zvukove"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Postoji problem u vašem uređaju i može biti nestabilan dok ga ne vratite na fabričke postavke."</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 7500122..a5287c6 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -72,6 +72,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"El valor predeterminat de l\'identificador de trucada és restringit. Trucada següent: no restringit"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"El valor predeterminat de l\'identificador de trucada és no restringit. Trucada següent: restringit"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"El valor predeterminat de l\'identificador de trucada és no restringit. Trucada següent: no restringit"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"No s\'ha proveït el servei."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"No pots canviar la configuració de l\'identificador de trucada."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Les dades s\'han canviat a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1948,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"De <xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Qualsevol calendari"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> està silenciant alguns sons"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"S\'ha produït un error intern al dispositiu i és possible que funcioni de manera inestable fins que restableixis les dades de fàbrica."</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 277d359..ad4df0f 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -73,6 +73,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Ve výchozím nastavení je funkce ID volajícího omezena. Příští hovor: Neomezeno"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Ve výchozím nastavení není funkce ID volajícího omezena. Příští hovor: Omezeno"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Ve výchozím nastavení není funkce ID volajícího omezena. Příští hovor: Neomezeno"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Služba není zřízena."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Nastavení ID volajícího nesmíte měnit."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Datové připojení bylo přepnuto na operátora <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1949,6 +1955,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"V libovolném kalendáři"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> vypíná určité zvuky"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"V zařízení došlo k internímu problému. Dokud neprovedete obnovení továrních dat, může být nestabilní."</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 9db6076..e77acd0 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Standarder for opkalds-id til begrænset. Næste opkald: Ikke begrænset"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Standarder for opkalds-id til ikke begrænset. Næste opkald: Begrænset"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Standarder for opkalds-id til ikke begrænset. Næste opkald: Ikke begrænset"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Tjenesten provisioneres ikke."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Du kan ikke ændre indstillingen for opkalds-id\'et."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Der blev skiftet til <xliff:g id="CARRIERDISPLAY">%s</xliff:g>-data"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> til <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g> kl. <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Alle kalendere"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> slår nogle lyde fra"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Der er et internt problem med enheden, og den vil muligvis være ustabil, indtil du gendanner fabriksdataene."</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 9561b3f..ac0a73b 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Anrufer-ID ist standardmäßig beschränkt. Nächster Anruf: Nicht beschränkt"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Anrufer-ID ist standardmäßig nicht beschränkt. Nächster Anruf: Beschränkt"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Anrufer-ID ist standardmäßig nicht beschränkt. Nächster Anruf: Nicht beschränkt"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Dienst nicht eingerichtet."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Du kannst die Einstellung für die Anrufer-ID nicht ändern."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Mobile Daten wurden auf <xliff:g id="CARRIERDISPLAY">%s</xliff:g> umgestellt"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> bis <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Alle Kalender"</string>
     <string name="muted_by" msgid="91464083490094950">"Einige Töne werden von <xliff:g id="THIRD_PARTY">%1$s</xliff:g> stummgeschaltet"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Es liegt ein internes Problem mit deinem Gerät vor. Möglicherweise verhält es sich instabil, bis du es auf die Werkseinstellungen zurücksetzt."</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 551fe70..e0c2678 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Η αναγνώριση κλήσης βρίσκεται από προεπιλογή στην \"περιορισμένη\". Επόμενη κλήση: Μη περιορισμένη"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Η αναγνώριση κλήσης βρίσκεται από προεπιλογή στην \"μη περιορισμένη\". Επόμενη κλήση: Περιορισμένη."</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Η αναγνώριση κλήσης βρίσκεται από προεπιλογή στην \"μη περιορισμένη\". Επόμενη κλήση: Μη περιορισμένη"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Η υπηρεσία δεν προβλέπεται."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Δεν μπορείτε να αλλάξετε τη ρύθμιση του αναγνωριστικού καλούντος."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Έγινε εναλλαγή των δεδομένων σε <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> έως <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Οποιοδήποτε ημερολόγιο"</string>
     <string name="muted_by" msgid="91464083490094950">"Το τρίτο μέρος <xliff:g id="THIRD_PARTY">%1$s</xliff:g> θέτει ορισμένους ήχους σε σίγαση"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Υπάρχει ένα εσωτερικό πρόβλημα με τη συσκευή σας και ενδέχεται να είναι ασταθής μέχρι την επαναφορά των εργοστασιακών ρυθμίσεων."</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 5cedb1f..d17058a 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Caller ID defaults to restricted. Next call: Not restricted"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Caller ID defaults to not restricted. Next call: Restricted"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> to <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Any calendar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index b09a79a..3224c47 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -71,6 +71,9 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Caller ID defaults to restricted. Next call: Not restricted"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Caller ID defaults to not restricted. Next call: Restricted"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string>
+    <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"This app isn’t 16 KB compatible. APK alignment check failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"This app isn’t 16 KB compatible. ELF alignment check failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"This app isn’t 16 KB compatible. APK and ELF alignment checks failed. This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support. For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1950,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> to <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Any calendar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 4e728ff0..ed4e5966 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Caller ID defaults to restricted. Next call: Not restricted"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Caller ID defaults to not restricted. Next call: Restricted"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> to <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Any calendar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 578e479..b17c07b 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Caller ID defaults to restricted. Next call: Not restricted"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Caller ID defaults to not restricted. Next call: Restricted"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> to <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Any calendar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index da3c4c4..e9f795e 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -72,6 +72,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"El Identificador de llamadas está predeterminado en restringido. Llamada siguiente: no restringido"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"El identificador de llamadas está predeterminado en no restringido. Llamada siguiente: restringida"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"El Identificador de llamadas está predeterminado en no restringido. Llamada siguiente: no restringido"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Servicio no suministrado."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"No puedes cambiar la configuración del identificador de llamadas."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Se cambiaron los datos a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1948,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"De <xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Cualquier calendario"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> silencia algunos sonidos"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Existe un problema interno con el dispositivo, de modo que el dispositivo puede estar inestable hasta que restablezcas la configuración de fábrica."</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 37bf70e..b7893df 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -72,6 +72,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"La identificación del emisor presenta el valor predeterminado de restringido. Siguiente llamada: No restringido"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"La la identificación del emisor presenta el valor predeterminado de no restringido. Siguiente llamada: Restringido"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"La identificación del emisor presenta el valor predeterminado de no restringido. Siguiente llamada: No restringido"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"El servicio no se suministra."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"No puedes modificar la identificación de emisor."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Datos cambiados a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1948,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"De <xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Cualquier calendario"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> silencia algunos sonidos"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Se ha producido un problema interno en el dispositivo y es posible que este no sea estable hasta que restablezcas el estado de fábrica."</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 0d79e8a..090d0cc 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Helistaja ID vaikimisi piiratud. Järgmine kõne: pole piiratud"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Vaikimisi pole helistaja ID piiratud. Järgmine kõne: piiratud"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Helistaja ID pole vaikimisi piiratud. Järgmine kõne: pole piiratud"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Teenus pole ette valmistatud."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Helistaja ID seadet ei saa muuta."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Mobiilne andmeside lülitati operaatorile <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> kuni <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Mis tahes kalender"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> vaigistab teatud helid"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Seadmes ilmnes sisemine probleem ja seade võib olla ebastabiilne seni, kuni lähtestate seadme tehase andmetele."</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index b96f9eb..d27a582 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Deitzailearen identitatea adierazteko zerbitzuaren balio lehenetsiak murriztapenak ezartzen ditu. Hurrengo deia: murriztapenik gabe."</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Deitzailearen identitatea zerbitzuaren balio lehenetsiak ez du murriztapenik ezartzen. Hurrengo deia: murriztapenekin."</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Deitzailearen identitatea zerbitzuaren balio lehenetsiak ez du murriztapenik ezartzen. Hurrengo deia: murriztapenik gabe."</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Zerbitzua ez da hornitu."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ezin duzu aldatu deitzailearen identitatearen ezarpena."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> operadorearen datu-konexiora aldatu zara"</string>
@@ -421,8 +427,8 @@
     <string name="permdesc_useDataInBackground" msgid="1230753883865891987">"Aplikazioak datuak erabil litzake atzeko planoan eta horrek datu-erabilera areago lezake."</string>
     <string name="permlab_schedule_exact_alarm" msgid="6683283918033029730">"antolatu ekintzak une zehatzetan gerta daitezen"</string>
     <string name="permdesc_schedule_exact_alarm" msgid="8198009212013211497">"Aplikazio honek ekintzak programa ditzake etorkizunean egin daitezen. Horrek esan nahi du gailua aktiboki erabiltzen ari ez zarenean ere exekuta daitekeela aplikazioa."</string>
-    <string name="permlab_use_exact_alarm" msgid="348045139777131552">"antolatu alarmak edo gertaera-abisuak"</string>
-    <string name="permdesc_use_exact_alarm" msgid="7033761461886938912">"Aplikazioak hainbat ekintza programa ditzake; adibidez, alarmak eta abisuak, etorkizuneko une batean jakinarazpen bat jaso dezazun."</string>
+    <string name="permlab_use_exact_alarm" msgid="348045139777131552">"antolatu alarmak edo gertaera-gogorarazpenak"</string>
+    <string name="permdesc_use_exact_alarm" msgid="7033761461886938912">"Aplikazioak hainbat ekintza programa ditzake; adibidez, alarmak eta gogorarazpenak, etorkizuneko une batean jakinarazpen bat jaso dezazun."</string>
     <string name="permlab_persistentActivity" msgid="464970041740567970">"izan aplikazioa beti abian"</string>
     <string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"Bere zati batzuk memoria modu iraunkorrean ezartzeko baimena ematen dio aplikazioari. Horrela, beste aplikazioek erabilgarri duten memoria murritz daiteke eta tableta motel daiteke."</string>
     <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"Bere zati batzuk memorian modu iraunkorrean ezartzeko baimena ematen dio aplikazioari. Ondorioz, beste aplikazioek memoria gutxiago izan lezakete erabilgarri, eta Android TV gailuak motelago funtziona lezake."</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g> (<xliff:g id="TIMES">%2$s</xliff:g>)"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Edozein egutegi"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> soinu batzuk isilarazten ari da"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Barneko arazo bat dago zure gailuan eta agian ezegonkor egongo da jatorrizko datuak berrezartzen dituzun arte."</string>
@@ -2438,7 +2445,7 @@
     <string name="satellite_sos_not_supported_notification_title" msgid="2659100983227637285">"Satelite bidezko SOS komunikazioa ez da bateragarria"</string>
     <string name="satellite_sos_not_supported_notification_summary" msgid="1071762454665310549">"Satelite bidezko SOS komunikazioa ez da bateragarria gailu honekin"</string>
     <string name="satellite_sos_not_provisioned_notification_title" msgid="8564738683795406715">"Satelite bidezko SOS komunikazioa ez dago konfiguratuta"</string>
-    <string name="satellite_sos_not_provisioned_notification_summary" msgid="3127320958911180629">"Ziurtatu Internetera konektatuta zaudela eta saiatu konfiguratzen berriro"</string>
+    <string name="satellite_sos_not_provisioned_notification_summary" msgid="3127320958911180629">"Ziurtatu Internetera konektatuta zaudela eta saiatu berriro konfiguratzen"</string>
     <string name="satellite_sos_not_in_allowed_region_notification_title" msgid="3164093193467075926">"Satelite bidezko SOS komunikazioa ez dago erabilgarri"</string>
     <string name="satellite_sos_not_in_allowed_region_notification_summary" msgid="7686947667515679672">"Satelite bidezko SOS komunikazioa ez dago erabilgarri herrialde edo lurralde honetan"</string>
     <string name="satellite_sos_unsupported_default_sms_app_notification_title" msgid="292528603128702080">"Satelite bidezko SOS komunikazioa ez dago konfiguratuta"</string>
@@ -2450,7 +2457,7 @@
     <string name="satellite_messaging_not_supported_notification_title" msgid="8202139632766878610">"Satelite bidezko mezularitza ez da bateragarria"</string>
     <string name="satellite_messaging_not_supported_notification_summary" msgid="61629858627638545">"Satelite bidezko mezularitza ez da bateragarria gailu honekin"</string>
     <string name="satellite_messaging_not_provisioned_notification_title" msgid="961909101918169727">"Satelite bidezko mezularitza ez dago konfiguratuta"</string>
-    <string name="satellite_messaging_not_provisioned_notification_summary" msgid="1060961852174442155">"Ziurtatu Internetera konektatuta zaudela eta saiatu konfiguratzen berriro"</string>
+    <string name="satellite_messaging_not_provisioned_notification_summary" msgid="1060961852174442155">"Ziurtatu Internetera konektatuta zaudela eta saiatu berriro konfiguratzen"</string>
     <string name="satellite_messaging_not_in_allowed_region_notification_title" msgid="2035303593479031655">"Satelite bidezko mezularitza ez dago erabilgarri"</string>
     <string name="satellite_messaging_not_in_allowed_region_notification_summary" msgid="5270294879531815854">"Satelite bidezko mezularitza ez dago erabilgarri herrialde edo lurralde honetan"</string>
     <string name="satellite_messaging_unsupported_default_sms_app_notification_title" msgid="1004808759472360189">"Satelite bidezko mezularitza ez dago konfiguratuta"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 7627ff3..50c0e37 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"پیش‌فرض شناسه تماس‌گیرنده روی محدود است. تماس بعدی: بدون محدودیت"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"پیش‌فرض شناسه تماس‌گیرنده روی غیرمحدود است. تماس بعدی: محدود"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"پیش‌فرض شناسه تماس‌گیرنده روی غیرمحدود است. تماس بعدی: بدون محدودیت"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"سرویس دارای مجوز نیست."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"‏شما می‎توانید تنظیم شناسه تماس‌گیرنده را تغییر دهید."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"داده به <xliff:g id="CARRIERDISPLAY">%s</xliff:g> تغییر کرد"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"، "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"‫<xliff:g id="START">%1$s</xliff:g> تا <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>، <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"هر تقویمی"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> درحال قطع کردن بعضی از صداهاست"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"دستگاهتان یک مشکل داخلی دارد، و ممکن است تا زمانی که بازنشانی داده‌های کارخانه انجام نگیرد، بی‌ثبات بماند."</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 78fb2a0..7d47541 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Soittajan tunnukseksi muutetaan rajoitettu. Seuraava puhelu: ei rajoitettu"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Soittajan tunnukseksi muutetaan rajoittamaton. Seuraava puhelu: rajoitettu"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Soittajan tunnukseksi muutetaan rajoittamaton. Seuraava puhelu: ei rajoitettu"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Palvelua ei tarjota."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Et voi muuttaa soittajan tunnuksen asetusta."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Data vaihdettu: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Kaikki kalenterit"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> mykistää joitakin ääniä"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Laitteellasi on sisäinen ongelma, joka aiheuttaa epävakautta. Voit korjata tilanteen palauttamalla tehdasasetukset."</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 163d70d..685d940 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -72,6 +72,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : non restreint"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : restreint"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : non restreint"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Ce service n\'est pas pris en charge."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Impossible de modifier le paramètre relatif au numéro de l\'appelant."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Données changées à <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1948,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"De <xliff:g id="START">%1$s</xliff:g> à <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"N\'importe quel agenda"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> désactive certains sons"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Un problème interne est survenu avec votre appareil. Il se peut qu\'il soit instable jusqu\'à ce que vous le réinitialisiez à ses paramètres par défaut."</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 75b8e31..e576e92 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -72,6 +72,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : non restreint"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : restreint"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : non restreint"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Ce service n\'est pas pris en charge."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Impossible de modifier le paramètre relatif au numéro de l\'appelant."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Données transférées vers <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1948,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"De <xliff:g id="START">%1$s</xliff:g> à <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Tous les agendas"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> coupe certains sons"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Un problème interne lié à votre appareil est survenu. Ce dernier risque d\'être instable jusqu\'à ce que vous rétablissiez la configuration d\'usine."</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 70a7ec9..f2354f5 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"O valor predeterminado do identificador de chamada é restrinxido. Próxima chamada: non restrinxido"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"O valor predeterminado do identificador de chamada é non restrinxido. Próxima chamada: restrinxido"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O valor predeterminado do identificador de chamada é restrinxido. Próxima chamada: non restrinxido"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Servizo non ofrecido."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Non podes cambiar a configuración do identificador de chamada."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Cambiouse a conexión de datos a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"De <xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Calquera calendario"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está silenciando algúns sons"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Produciuse un erro interno no teu dispositivo e quizais funcione de maneira inestable ata o restablecemento dos datos de fábrica."</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 1535e4f..6291688 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"કૉલર ID પ્રતિબંધિત પર ડિફોલ્ટ છે. આગલો કૉલ: પ્રતિબંધિત નહીં"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"કૉલર ID પ્રતિબંધિત નહીં પર ડિફોલ્ટ છે. આગલો કૉલ: પ્રતિબંધિત"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"કૉલર ID પ્રતિબંધિત નહીં પર ડિફોલ્ટ છે. આગલો કૉલ: પ્રતિબંધિત નહીં"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"સેવાની જોગવાઈ કરી નથી."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"તમે કૉલર ID સેટિંગ બદલી શકતાં નથી."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"ડેટા <xliff:g id="CARRIERDISPLAY">%s</xliff:g> પર સ્વિચ કર્યો"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>થી <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"કોઈપણ કૅલેન્ડર"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> અમુક અવાજોને મ્યૂટ કરે છે"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"તમારા ઉપકરણમાં આંતરિક સમસ્યા છે અને જ્યાં સુધી તમે ફેક્ટરી ડેટા ફરીથી સેટ કરશો નહીં ત્યાં સુધી તે અસ્થિર રહી શકે છે."</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index c5825fe..75c8bd6 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"कॉलर आईडी डिफ़ॉल्ट रूप से सीमित है. अगली कॉल: सीमित नहीं"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"कॉलर आईडी डिफ़ॉल्ट रूप से सीमित नहीं है. अगली कॉल: सीमित"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कॉलर आईडी डिफ़ॉल्ट रूप से सीमित नहीं है. अगली कॉल: सीमित नहीं"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवा प्रावधान की हुई नहीं है."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"आप कॉलर आईडी सेटिंग नहीं बदल सकते."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"डेटा को <xliff:g id="CARRIERDISPLAY">%s</xliff:g> पर स्विच किया गया"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> से <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"किसी भी कैलेंडर के इवेंट के लिए"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> कुछ आवाज़ें म्‍यूट कर रहा है"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"आपके डिवाइस में कोई अंदरूनी समस्या है और यह तब तक ठीक नहीं होगी जब तक आप फ़ैक्‍टरी डेटा रीसेट नहीं करते."</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 954f4cb..0516c46 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -72,6 +72,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Zadana postavka ID-a pozivatelja ima ograničenje. Sljedeći poziv: Nije ograničen"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Zadana postavka ID-a pozivatelja nema ograničenje. Sljedeći poziv: Ograničen"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Zadana postavka ID-a pozivatelja nema ograničenje. Sljedeći poziv: Nije ograničen"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Usluga nije rezervirana."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ne možete promijeniti postavku ID-a pozivatelja."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Podaci su prebačeni na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1948,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Bilo koji kalendar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> isključuje neke zvukove"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Na vašem uređaju postoji interni problem i možda neće biti stabilan dok ga ne vratite na tvorničko stanje."</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 58a5cbd..e980141 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"A hívóazonosító alapértelmezett értéke korlátozott. Következő hívás: nem korlátozott"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"A hívóazonosító alapértelmezett értéke nem korlátozott. Következő hívás: korlátozott"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"A hívóazonosító alapértelmezett értéke nem korlátozott. Következő hívás: nem korlátozott"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"A szolgáltatás nincs biztosítva."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Nem tudja módosítani a hívó fél azonosítója beállítást."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Adatforgalom átváltva a következőre: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Bármilyen naptár"</string>
     <string name="muted_by" msgid="91464083490094950">"A(z) <xliff:g id="THIRD_PARTY">%1$s</xliff:g> lenémít néhány hangot"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Belső probléma van az eszközzel, és instabil lehet, amíg vissza nem állítja a gyári adatokat."</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index f457c42d..f734a5e 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Զանգողի ID-ն լռելյայն սահմանափակված է: Հաջորդ զանգը` չսահմանափակված"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Զանգողի ID-ն լռելյայն չսահմանափակված է: Հաջորդ զանգը` Սահմանափակված"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Զանգողի ID-ն լռելյայն չսահմանափակված է: Հաջորդ զանգը` չսահմանափակված"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Ծառայությունը չի տրամադրվում:"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Դուք չեք կարող փոխել զանգողի ID-ի կարգավորումները:"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Օգտագործվում է <xliff:g id="CARRIERDISPLAY">%s</xliff:g>-ի բջջային ինտերնետը"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Ցանկացած օրացույց"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>-ն անջատում է որոշ ձայներ"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Սարքում ներքին խնդիր է առաջացել և այն կարող է կրկնվել, մինչև չվերականգնեք գործարանային կարգավորումները:"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 88764c4..3312662 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID penelepon diatur default ke \"dibatasi\". Panggilan selanjutnya: Tidak dibatasi."</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID penelepon diatur default ke tidak dibatasi. Panggilan selanjutnya: Dibatasi"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID penelepon diatur default ke tidak dibatasi. Panggilan selanjutnya: Tidak dibatasi"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Layanan tidak diperlengkapi."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Anda tidak dapat mengubah setelan ID penelepon."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Mengalihkan data seluler ke <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> hingga <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Kalender mana saja"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> mematikan beberapa suara"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Ada masalah dengan perangkat. Hal ini mungkin membuat perangkat jadi tidak stabil dan perlu dikembalikan ke setelan pabrik."</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 8e92ed1..cbf9798 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -71,6 +71,9 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Númerabirting er sjálfgefið með takmörkunum. Næsta símtal: Án takmarkana"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Númerabirting er sjálfgefið án takmarkana. Næsta símtal: Með takmörkunum"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Númerabirting er sjálfgefið án takmarkana. Næsta símtal: Án takmarkana"</string>
+    <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Þetta forrit er ekki samhæft 16 KB. Athugun á samræmi við APK mistókst. Þetta forrit verður keyrt með stillingu sem er samhæf blaðsíðufjölda. Þýddu forritið aftur með stuðningi við 16 KB til að tryggja sem best samhæfi. Frekari upplýsingar má finna á &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Þetta forrit er ekki samhæft 16 KB. Athugun á samræmi við ELF mistókst. Þetta forrit verður keyrt með stillingu sem er samhæf blaðsíðufjölda. Þýddu forritið aftur með stuðningi við 16 KB til að tryggja sem best samhæfi. Frekari upplýsingar má finna á &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Þetta forrit er ekki samhæft 16 KB. Athuganir á samræmingu APK og ELF mistókst. Þetta forrit verður keyrt með stillingu sem er samhæf blaðsíðufjölda. Þýddu forritið aftur með stuðningi við 16 KB til að tryggja sem best samhæfi. Frekari upplýsingar má finna á &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Þjónustu ekki útdeilt."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Þú getur ekki breytt stillingu númerabirtingar."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Skipt yfir í farsímagögn hjá <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1950,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> til <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Öll dagatöl"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> þaggar í einhverjum hljóðum"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Innra vandamál kom upp í tækinu og það kann að vera óstöðugt þangað til þú núllstillir það."</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 49f1f21..747212a 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -72,6 +72,9 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID chiamante generalmente limitato. Prossima chiamata: non limitato"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID chiamante generalmente non limitato. Prossima chiamata: limitato"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID chiamante generalmente non limitato. Prossima chiamata: non limitato"</string>
+    <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Questa app non è compatibile con 16 kB. Controllo allineamento APK non riuscito. Questa app verrà eseguita utilizzando la modalità compatibile con le dimensioni della pagina. Per la massima compatibilità, ricompila l\'applicazione con il supporto a 16 kB. Per maggiori dettagli, consulta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Questa app non è compatibile con 16 kB. Controllo allineamento ELF non riuscito. Questa app verrà eseguita utilizzando la modalità compatibile con le dimensioni della pagina. Per la massima compatibilità, ricompila l\'applicazione con il supporto a 16 kB. Per maggiori dettagli, consulta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Questa app non è compatibile con 16 kB. Controlli di allineamento APK ed ELF non riusciti. Questa app verrà eseguita utilizzando la modalità compatibile con le dimensioni della pagina. Per la massima compatibilità, ricompila l\'applicazione con il supporto a 16 kB. Per maggiori dettagli, consulta &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Servizio non fornito."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Non è possibile modificare l\'impostazione ID chiamante."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"I dati sono stati trasferiti a <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -169,7 +172,7 @@
     <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Ok"</string>
     <string name="fcComplete" msgid="1080909484660507044">"Codice funzione completo."</string>
     <string name="fcError" msgid="5325116502080221346">"Problema di connessione o codice funzione non valido."</string>
-    <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string>
+    <string name="httpErrorOk" msgid="6206751415788256357">"Ok"</string>
     <string name="httpError" msgid="3406003584150566720">"Si è verificato un errore di rete."</string>
     <string name="httpErrorLookup" msgid="3099834738227549349">"Impossibile trovare l\'URL."</string>
     <string name="httpErrorUnsupportedAuthScheme" msgid="3976195595501606787">"Schema di autenticazione del sito non supportato."</string>
@@ -1167,7 +1170,7 @@
     <string name="VideoView_error_title" msgid="5750686717225068016">"Problemi video"</string>
     <string name="VideoView_error_text_invalid_progressive_playback" msgid="3782449246085134720">"Questo video non è valido per lo streaming su questo dispositivo."</string>
     <string name="VideoView_error_text_unknown" msgid="7658683339707607138">"Impossibile riprodurre il video."</string>
-    <string name="VideoView_error_button" msgid="5138809446603764272">"OK"</string>
+    <string name="VideoView_error_button" msgid="5138809446603764272">"Ok"</string>
     <string name="relative_time" msgid="8572030016028033243">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="noon" msgid="8365974533050605886">"mezzogiorno"</string>
     <string name="Noon" msgid="6902418443846838189">"Mezzogiorno"</string>
@@ -1206,7 +1209,7 @@
     <string name="app_running_notification_text" msgid="5120815883400228566">"Tocca per ulteriori informazioni o per interrompere l\'app."</string>
     <string name="ok" msgid="2646370155170753815">"Ok"</string>
     <string name="cancel" msgid="6908697720451760115">"Annulla"</string>
-    <string name="yes" msgid="9069828999585032361">"OK"</string>
+    <string name="yes" msgid="9069828999585032361">"Ok"</string>
     <string name="no" msgid="5122037903299899715">"Annulla"</string>
     <string name="dialog_alert_title" msgid="651856561974090712">"Attenzione"</string>
     <string name="loading" msgid="3138021523725055037">"Caricamento…"</string>
@@ -1265,7 +1268,7 @@
     <string name="anr_activity_process" msgid="3477362583767128667">"L\'app <xliff:g id="ACTIVITY">%1$s</xliff:g> non risponde"</string>
     <string name="anr_application_process" msgid="4978772139461676184">"L\'app <xliff:g id="APPLICATION">%1$s</xliff:g> non risponde"</string>
     <string name="anr_process" msgid="1664277165911816067">"Il processo <xliff:g id="PROCESS">%1$s</xliff:g> non risponde"</string>
-    <string name="force_close" msgid="9035203496368973803">"OK"</string>
+    <string name="force_close" msgid="9035203496368973803">"Ok"</string>
     <string name="report" msgid="2149194372340349521">"Segnala"</string>
     <string name="wait" msgid="7765985809494033348">"Attendi"</string>
     <string name="webpage_unresponsive" msgid="7850879412195273433">"La pagina non risponde più.\n\nVuoi chiuderla?"</string>
@@ -1395,7 +1398,7 @@
     <string name="perms_description_app" msgid="2747752389870161996">"Fornito da <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="no_permissions" msgid="5729199278862516390">"Nessuna autorizzazione richiesta"</string>
     <string name="perm_costs_money" msgid="749054595022779685">"potrebbe comportare dei costi"</string>
-    <string name="dlg_ok" msgid="5103447663504839312">"OK"</string>
+    <string name="dlg_ok" msgid="5103447663504839312">"Ok"</string>
     <string name="usb_charging_notification_title" msgid="1674124518282666955">"Dispositivo in carica tramite USB"</string>
     <string name="usb_supplying_notification_title" msgid="5378546632408101811">"Dispositivo collegato in carica tramite USB"</string>
     <string name="usb_mtp_notification_title" msgid="1065989144124499810">"Trasferimento file tramite USB attivato"</string>
@@ -1892,7 +1895,7 @@
     <string name="restr_pin_try_later" msgid="5897719962541636727">"Riprova più tardi"</string>
     <string name="immersive_cling_title" msgid="2307034298721541791">"Visualizzazione a schermo intero"</string>
     <string name="immersive_cling_description" msgid="2896205051090870978">"Per uscire, scorri verso il basso dalla parte superiore dello schermo"</string>
-    <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
+    <string name="immersive_cling_positive" msgid="7047498036346489883">"Ok"</string>
     <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Ruota per migliorare l\'anteprima"</string>
     <string name="display_rotation_camera_compat_toast_in_multi_window" msgid="2473122980393502775">"Apri <xliff:g id="NAME">%s</xliff:g> a schermo intero per migliorare la visualizzazione"</string>
     <string name="done_label" msgid="7283767013231718521">"Fine"</string>
@@ -1914,7 +1917,7 @@
     <string name="package_installed_device_owner" msgid="8684974629306529138">"Installato dall\'amministratore.\nVai alle impostazioni per visualizzare le autorizzazioni concesse"</string>
     <string name="package_updated_device_owner" msgid="7560272363805506941">"Aggiornato dall\'amministratore"</string>
     <string name="package_deleted_device_owner" msgid="2292335928930293023">"Eliminato dall\'amministratore"</string>
-    <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
+    <string name="confirm_battery_saver" msgid="5247976246208245754">"Ok"</string>
     <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Il risparmio energetico attiva il tema scuro e limita o disattiva l\'attività in background, nonché alcuni effetti visivi, funzionalità e connessioni di rete."</string>
     <string name="battery_saver_description" msgid="8518809702138617167">"Il risparmio energetico attiva il tema scuro e limita o disattiva l\'attività in background, nonché alcuni effetti visivi, funzionalità e connessioni di rete."</string>
     <string name="data_saver_description" msgid="4995164271550590517">"Per contribuire a ridurre l\'utilizzo dei dati, la funzionalità Risparmio dati impedisce ad alcune app di inviare o ricevere dati in background. Un\'app in uso può accedere ai dati, ma potrebbe farlo con meno frequenza. Per esempio, è possibile che le immagini non vengano visualizzate finché non le tocchi."</string>
@@ -1948,6 +1951,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"Da <xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Qualsiasi calendario"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> sta disattivando alcuni suoni"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Si è verificato un problema interno con il dispositivo, che potrebbe essere instabile fino al ripristino dei dati di fabbrica."</string>
@@ -2150,7 +2154,7 @@
     <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Questa notifica è stata posizionata più in basso. Tocca per dare un feedback."</string>
     <string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Notifiche avanzate"</string>
     <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"Ora le risposte e le azioni suggerite vengono fornite dalle notifiche avanzate. Le notifiche adattive Android non sono più supportate."</string>
-    <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</string>
+    <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"Ok"</string>
     <string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Disattiva"</string>
     <string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Scopri di più"</string>
     <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Le notifiche adattive Android sono state sostituite dalle notifiche avanzate in Android 12. Questa funzionalità mostra risposte e azioni suggerite e organizza le tue notifiche.\n\nLe notifiche avanzate possono accedere ai contenuti di una notifica, incluse le informazioni personali, come i nomi dei contatti e i messaggi. Questa funzionalità può anche ignorare le notifiche o rispondervi, ad esempio accettando le telefonate, e controllare la modalità Non disturbare."</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 0e8f472..afdd157 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -72,6 +72,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"שירות השיחה המזוהה עובר כברירת מחדל למצב מוגבל. השיחה הבאה: לא מוגבלת"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"שירות \'שיחה מזוהה\' עובר כברירת מחדל למצב לא מוגבל. השיחה הבאה: מוגבלת"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"זיהוי מתקשר עובר כברירת מחדל למצב לא מוגבל. השיחה הבאה: לא מוגבלת"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"השירות לא הוקצה."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"אינך יכול לשנות את הגדרת זיהוי המתקשר."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"הנתונים עברו אל <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1948,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"‫<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"‫<xliff:g id="START">%1$s</xliff:g> עד <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"כל יומן"</string>
     <string name="muted_by" msgid="91464083490094950">"חלק מהצלילים מושתקים על ידי <xliff:g id="THIRD_PARTY">%1$s</xliff:g>"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"קיימת בעיה פנימית במכשיר שלך, וייתכן שהוא לא יתפקד כראוי עד שיבוצע איפוס לנתוני היצרן."</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 6eabd4a..5139e2a 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -71,6 +71,9 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"既定: 発信者番号非通知、次の発信: 通知"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"既定: 発信者番号通知、次の発信: 非通知"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"既定: 発信者番号通知、次の発信: 通知"</string>
+    <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"このアプリは 16 KB アライメントではありません。APK のアライメント チェックが失敗しました。このアプリはページサイズ互換モードを使用して実行されます。最適な互換性を実現するには、16 KB をサポートするようにアプリケーションを再コンパイルしてください。詳しくは、&lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; をご覧ください。"</string>
+    <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"このアプリは 16 KB アライメントではありません。ELF のアライメント チェックに失敗しました。このアプリはページサイズ互換モードを使用して実行されます。最適な互換性を実現するには、16 KB をサポートするようにアプリケーションを再コンパイルしてください。詳しくは、&lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; をご覧ください。"</string>
+    <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"このアプリは 16 KB アライメントではありません。APK と ELF のアライメント チェックが失敗しました。このアプリはページサイズ互換モードを使用して実行されます。最適な互換性を実現するには、16 KB をサポートするようにアプリケーションを再コンパイルしてください。詳しくは、&lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; をご覧ください。"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"提供可能なサービスがありません。"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"発信者番号の設定は変更できません。"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"データが <xliff:g id="CARRIERDISPLAY">%s</xliff:g> に切り替わりました"</string>
@@ -1947,6 +1950,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"、 "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>~<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>~<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>、<xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"すべてのカレンダー"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> により一部の音はミュートに設定"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"デバイスで内部的な問題が発生しました。データが初期化されるまで不安定になる可能性があります。"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 4ffa312..1a0ba5a 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -71,6 +71,9 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ნაგულისხმებად დაყენებულია ნომრის დაფარვა. შემდეგი ზარი: არ არის დაფარული."</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ნაგულისხმებად დაყენებულია ნომრის დაფარვის გამორთვა. შემდეგი ზარი: დაფარულია."</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ნაგულისხმებად დაყენებულია ნომრის დაფარვის გამორთვა. შემდეგი ზარი: არ არის დაფარული."</string>
+    <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"ეს აპი არ არის თავსებადი 16 კბაიტისთვის. APK გასწორების შემოწმება ვერ მოხერხდა. ეს აპი გაიშვება გვერდის ზომის თავსებადი რეჟიმის გამოყენებით. საუკეთესო თავსებადობისთვის, ხელახლა შექმენით აპლიკაცია 16 კბაიტი მხარდაჭერით. დამატებითი ინფორმაციისთვის იხილეთ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"ეს აპი არ არის თავსებადი 16 კბაიტისთვის. ELF გასწორების შემოწმება ვერ მოხერხდა. ეს აპი გაიშვება გვერდის ზომის თავსებადი რეჟიმის გამოყენებით. საუკეთესო თავსებადობისთვის, ხელახლა შექმენით აპლიკაცია 16 კბაიტი მხარდაჭერით. დამატებითი ინფორმაციისთვის იხილეთ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"ეს აპი არ არის თავსებადი 16 კბაიტისთვის. APK და ELF გასწორების შემოწმება ვერ მოხერხდა. ეს აპი გაიშვება გვერდის ზომის თავსებადი რეჟიმის გამოყენებით. საუკეთესო თავსებადობისთვის, ხელახლა შექმენით აპლიკაცია 16 კბაიტი მხარდაჭერით. დამატებითი ინფორმაციისთვის იხილეთ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"სერვისი არ არის მიწოდებული."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"არ შეგიძლიათ აბონენტის ID პარამეტრების შეცვლა."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"მონაცემები გადართულია <xliff:g id="CARRIERDISPLAY">%s</xliff:g>-ზე"</string>
@@ -1947,6 +1950,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ნებისმიერი კალენდარი"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ზოგიერთ ხმას ადუმებს"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"ფიქსირდება თქვენი მ ოწყობილობის შიდა პრობლემა და შეიძლება არასტაბილური იყოს, სანამ ქარხნულ მონაცემების არ განაახლებთ."</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 6355d86..fd235c2 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Қоңырау шалушының жеке анықтағышы бастапқы бойынша шектелген. Келесі қоңырау: Шектелмеген"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Қоңырау шалушының жеке анықтағышы бастапқы бойынша шектелмеген. Келесі қоңырау: Шектелген"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Қоңырау шалушының жеке анықтағышы бастапқы бойынша шектелмеген. Келесі қоңырау: Шектелмеген"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Қызмет ұсынылмаған."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Қоңырау шалушы идентификаторы параметрін өзгерту мүмкін емес."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Деректер <xliff:g id="CARRIERDISPLAY">%s</xliff:g> операторына ауыстырылды"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g> <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Кез келген күнтізбе"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> кейбір дыбыстарды өшіруде"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
@@ -2442,9 +2449,9 @@
     <string name="satellite_sos_not_in_allowed_region_notification_title" msgid="3164093193467075926">"Satellite SOS функциясы қолжетімді емес"</string>
     <string name="satellite_sos_not_in_allowed_region_notification_summary" msgid="7686947667515679672">"Satellite SOS функциясы бұл елде немесе аймақта қолжетімді емес."</string>
     <string name="satellite_sos_unsupported_default_sms_app_notification_title" msgid="292528603128702080">"Satellite SOS функциясы реттелмеген"</string>
-    <string name="satellite_sos_unsupported_default_sms_app_notification_summary" msgid="3165168393504548437">"Жерсерік арқылы хабар алмасу үшін, Google Messages сіздің әдепкі хабар алмасу қолданбаңыз болуы керек."</string>
+    <string name="satellite_sos_unsupported_default_sms_app_notification_summary" msgid="3165168393504548437">"Жерсерік арқылы хабар алмасу үшін Google Messages сіздің әдепкі хабар алмасу қолданбаңыз болуы керек."</string>
     <string name="satellite_sos_location_disabled_notification_title" msgid="5427987916850950591">"Satellite SOS функциясы қолжетімді емес"</string>
-    <string name="satellite_sos_location_disabled_notification_summary" msgid="1544937460641058567">"Satellite SOS функциясының бұл елде немесе аймақта қолжетімді екенін тексеру үшін, локация параметрлерін қосыңыз."</string>
+    <string name="satellite_sos_location_disabled_notification_summary" msgid="1544937460641058567">"Satellite SOS функциясының бұл елде немесе аймақта қолжетімді екенін тексеру үшін локация параметрлерін қосыңыз."</string>
     <string name="satellite_messaging_available_notification_title" msgid="3366657987618784706">"Жерсерік арқылы хабар алмасу функциясы қолжетімді"</string>
     <string name="satellite_messaging_available_notification_summary" msgid="7573949038500243670">"Мобильдік немесе Wi-Fi желісі жоқ болған жағдайда, жерсерік арқылы хабар алмаса аласыз. Google Messages сіздің әдепкі хабар алмасу қолданбаңыз болуы керек."</string>
     <string name="satellite_messaging_not_supported_notification_title" msgid="8202139632766878610">"Жерсерік арқылы хабар алмасу функциясына қолдау көрсетілмейді"</string>
@@ -2454,9 +2461,9 @@
     <string name="satellite_messaging_not_in_allowed_region_notification_title" msgid="2035303593479031655">"Жерсерік арқылы хабар алмасу функциясы қолжетімді емес"</string>
     <string name="satellite_messaging_not_in_allowed_region_notification_summary" msgid="5270294879531815854">"Жерсерік арқылы хабар алмасу функциясы бұл елде немесе аймақта қолжетімді емес."</string>
     <string name="satellite_messaging_unsupported_default_sms_app_notification_title" msgid="1004808759472360189">"Жерсерік арқылы хабар алмасу функциясы реттелмеген"</string>
-    <string name="satellite_messaging_unsupported_default_sms_app_notification_summary" msgid="17084124893763593">"Жерсерік арқылы хабар алмасу үшін, Google Messages сіздің әдепкі хабар алмасу қолданбаңыз болуы керек."</string>
+    <string name="satellite_messaging_unsupported_default_sms_app_notification_summary" msgid="17084124893763593">"Жерсерік арқылы хабар алмасу үшін Google Messages сіздің әдепкі хабар алмасу қолданбаңыз болуы керек."</string>
     <string name="satellite_messaging_location_disabled_notification_title" msgid="7270641894250928494">"Жерсерік арқылы хабар алмасу функциясы қолжетімді емес"</string>
-    <string name="satellite_messaging_location_disabled_notification_summary" msgid="1450824950686221810">"Жерсерік арқылы хабар алмасу функциясының бұл елде немесе аймақта қолжетімді екенін тексеру үшін, локация параметрлерін қосыңыз."</string>
+    <string name="satellite_messaging_location_disabled_notification_summary" msgid="1450824950686221810">"Жерсерік арқылы хабар алмасу функциясының бұл елде немесе аймақта қолжетімді екенін тексеру үшін локация параметрлерін қосыңыз."</string>
     <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Саусақ ізімен ашу функциясын қайта реттеу"</string>
     <string name="fingerprint_dangling_notification_msg_1" msgid="5851784577768803510">"<xliff:g id="FINGERPRINT">%s</xliff:g> бұдан былай танылмайды."</string>
     <string name="fingerprint_dangling_notification_msg_2" msgid="7925203589860744456">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> және <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> бұдан былай танылмайды."</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 4163be4..2fddb5f 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"មិន​បាន​ដាក់កម្រិត​លំនាំដើម​លេខ​សម្គាល់​អ្នក​ហៅ។ ការ​ហៅ​បន្ទាប់៖ មិន​បាន​ដាក់​កម្រិត។"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"មិន​បាន​ដាក់​កម្រិត​លេខ​សម្គាល់​អ្នក​ហៅ​លំនាំ​ដើម។ ការ​ហៅ​បន្ទាប់៖​ បាន​ដាក់កម្រិត"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"មិន​បាន​ដាក់កម្រិត​លំនាំដើម​លេខ​សម្គាល់​អ្នក​ហៅ។ ការ​ហៅ​បន្ទាប់៖ មិន​បាន​ដាក់​កម្រិត។"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"មិន​បាន​ផ្ដល់​សេវាកម្ម។"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"អ្នក​មិន​អាច​ប្ដូរ​ការ​កំណត់​លេខ​សម្គាល់​អ្នក​ហៅ​បានទេ។"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"បានប្ដូរទិន្នន័យទៅ <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> ដល់ <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ប្រតិទិនណាមួយ"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> កំពុង​បិទសំឡេង​មួយចំនួន"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"មានបញ្ហាខាងក្នុងឧបករណ៍របស់អ្នក ហើយវាអ្នកមិនមានស្ថេរភាព រហូតទាល់តែអ្នកកំណត់ដូចដើមវិញទាំងស្រុង។"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 234ba66..4264717 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -71,6 +71,9 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ಕರೆಮಾಡುವವರ ID ಅನ್ನು ನಿರ್ಬಂಧಿಸುವಂತೆ ಡಿಫಾಲ್ಟ್ ಮಾಡಲಾಗಿದೆ. ಮುಂದಿನ ಕರೆ: ನಿರ್ಬಂಧಿಸಿಲ್ಲ"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ಕರೆಮಾಡುವವರ ID ಅನ್ನು ನಿರ್ಬಂಧಿಸದಿರುವಂತೆ ಡಿಫಾಲ್ಟ್ ಮಾಡಲಾಗಿದೆ. ಮುಂದಿನ ಕರೆ: ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ಕರೆಮಾಡುವವರ ID ಅನ್ನು ನಿರ್ಬಂಧಿಸದಿರುವಂತೆ ಡಿಫಾಲ್ಟ್ ಮಾಡಲಾಗಿದೆ. ಮುಂದಿನ ಕರೆ: ನಿರ್ಬಂಧಿಸಲಾಗಿಲ್ಲ"</string>
+    <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"ಈ ಆ್ಯಪ್‌ 16 KB ಗೆ ಹೊಂದಿಕೆಯಾಗುವುದಿಲ್ಲ. APK ಮತ್ತು ELF ಅಲೈನ್‌ಮೆಂಟ್ ಪರಿಶೀಲನೆಗಳು ವಿಫಲವಾಗಿವೆ ಪುಟದ ಗಾತ್ರಕ್ಕೆ ಹೊಂದಿಕೆಯಾಗುವ ಮೋಡ್ ಅನ್ನು ಬಳಸಿಕೊಂಡು ಆ್ಯಪ್ ಅನ್ನು ರನ್ ಮಾಡಲಾಗುತ್ತದೆ. ಉತ್ತಮ ಹೊಂದಾಣಿಕೆಗಾಗಿ, 16 KB ಗೆ ಬೆಂಬಲದೊಂದಿಗೆ ಆ್ಯಪ್ ಅನ್ನು ಮರುಕಂಪೈಲ್ ಮಾಡಿ. ಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; ಅನ್ನು ನೋಡಿ"</string>
+    <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"ಈ ಆ್ಯಪ್‌ 16 KB ಗೆ ಹೊಂದಿಕೆಯಾಗುವುದಿಲ್ಲ. ELF ಅಲೈನ್‌ಮೆಂಟ್ ಪರಿಶೀಲನೆಗಳು ವಿಫಲವಾಗಿದೆ. ಪುಟದ ಗಾತ್ರಕ್ಕೆ ಹೊಂದಿಕೆಯಾಗುವ ಮೋಡ್ ಅನ್ನು ಬಳಸಿಕೊಂಡು ಆ್ಯಪ್ ಅನ್ನು ರನ್ ಮಾಡಲಾಗುತ್ತದೆ. ಉತ್ತಮ ಹೊಂದಾಣಿಕೆಗಾಗಿ, 16 KB ಗೆ ಬೆಂಬಲದೊಂದಿಗೆ ಆ್ಯಪ್ ಅನ್ನು ಮರುಕಂಪೈಲ್ ಮಾಡಿ. ಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; ಅನ್ನು ನೋಡಿ"</string>
+    <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"ಈ ಆ್ಯಪ್‌ 16 KB ಗೆ ಹೊಂದಿಕೆಯಾಗುವುದಿಲ್ಲ. APK ಮತ್ತು ELF ಅಲೈನ್‌ಮೆಂಟ್ ಪರಿಶೀಲನೆಗಳು ವಿಫಲವಾಗಿವೆ ಪುಟದ ಗಾತ್ರಕ್ಕೆ ಹೊಂದಿಕೆಯಾಗುವ ಮೋಡ್ ಅನ್ನು ಬಳಸಿಕೊಂಡು ಆ್ಯಪ್ ಅನ್ನು ರನ್ ಮಾಡಲಾಗುತ್ತದೆ. ಉತ್ತಮ ಹೊಂದಾಣಿಕೆಗಾಗಿ, 16 KB ಗೆ ಬೆಂಬಲದೊಂದಿಗೆ ಆ್ಯಪ್ ಅನ್ನು ಮರುಕಂಪೈಲ್ ಮಾಡಿ. ಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; ಅನ್ನು ನೋಡಿ"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ಸೇವೆಯನ್ನು ಪೂರೈಸಲಾಗಿಲ್ಲ."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"ನೀವು ಕಾಲರ್‌ ID ಸೆಟ್ಟಿಂಗ್‌ ಬದಲಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> ಗೆ ಡೇಟಾವನ್ನು ಬದಲಾಯಿಸಲಾಗಿದೆ"</string>
@@ -1387,7 +1390,7 @@
     <string name="carrier_app_notification_title" msgid="5815477368072060250">"ಹೊಸ ಸಿಮ್ ಸೇರಿಸಲಾಗಿದೆ"</string>
     <string name="carrier_app_notification_text" msgid="6567057546341958637">"ಇದನ್ನು ಸ್ಥಾಪಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="time_picker_dialog_title" msgid="9053376764985220821">"ಸಮಯವನ್ನು ಹೊಂದಿಸಿ"</string>
-    <string name="date_picker_dialog_title" msgid="5030520449243071926">"ದಿನಾಂಕವನ್ನು ಹೊಂದಿಸಿ"</string>
+    <string name="date_picker_dialog_title" msgid="5030520449243071926">"ದಿನಾಂಕವನ್ನು ಸೆಟ್ ಮಾಡಿ"</string>
     <string name="date_time_set" msgid="4603445265164486816">"ಹೊಂದಿಸು"</string>
     <string name="date_time_done" msgid="8363155889402873463">"ಆಯಿತು"</string>
     <string name="perms_new_perm_prefix" msgid="6984556020395757087"><font size="12" fgcolor="#ff33b5e5">"ಹೊಸ: "</font></string>
@@ -1947,6 +1950,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> ನಿಂದ <xliff:g id="END">%2$s</xliff:g> ವರೆಗೆ"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ಯಾವುದೇ ಕ್ಯಾಲೆಂಡರ್"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ಧ್ವನಿ ಮ್ಯೂಟ್ ಮಾಡುತ್ತಿದ್ದಾರೆ"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಆಂತರಿಕ ಸಮಸ್ಯೆಯಿದೆ ಹಾಗೂ ನೀವು ಫ್ಯಾಕ್ಟರಿ ಡೇಟಾವನ್ನು ರೀಸೆಟ್ ಮಾಡುವವರೆಗೂ ಅದು ಅಸ್ಥಿರವಾಗಬಹುದು."</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 59af8fb..b8bbacb 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"발신자 번호가 기본적으로 제한됨으로 설정됩니다. 다음 통화: 제한되지 않음"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"발신자 번호가 기본적으로 제한되지 않음으로 설정됩니다. 다음 통화: 제한됨"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"발신자 번호가 기본적으로 제한되지 않음으로 설정됩니다. 다음 통화: 제한되지 않음"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"서비스가 준비되지 않았습니다."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"발신자 번호 설정을 변경할 수 없습니다."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> 이동통신사로 데이터가 변경됨"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>~<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>~<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"모든 캘린더"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>(이)가 일부 소리를 음소거함"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"사용 중인 기기 내부에 문제가 발생했습니다. 초기화할 때까지 불안정할 수 있습니다."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index cbb0a13..70c4d8e 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -71,6 +71,9 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Номурду аныктоонун демейки абалы \"чектелген\" деп коюлган. Кийинки чалуу: Чектелбейт"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Номурду аныктоонун демейки абалы \"чектелбейт\" деп коюлган. Кийинки чалуу: Чектелген"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Номурду аныктоонун демейки абалы \"чектелбейт\" деп коюлган. Кийинки чалуу: Чектелбейт"</string>
+    <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Бул колдонмо 16 Кб өлчөмүнө туура келбейт. APK\'дин тегизделиши тейшерилбей калды. Колдонмо беттин өлчөмүнө туура келген режимде иштейт. Эң жакшы шайкештик үчүн колдонмону 16 Кб колдоосу менен кайра түзүңүз. Кеңири маалымат: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Бул колдонмо 16 Кб өлчөмүнө туура келбейт. ELF\'тин тегизделиши тейшерилбей калды. Колдонмо беттин өлчөмүнө туура келген режимде иштейт. Эң жакшы шайкештик үчүн колдонмону 16 Кб колдоосу менен кайра түзүңүз. Кеңири маалымат: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Бул колдонмо 16 Кб өлчөмүнө туура келбейт. APK менен ELF\'тин тегизделиши тейшерилбей калды. Колдонмо беттин өлчөмүнө туура келген режимде иштейт. Эң жакшы шайкештик үчүн колдонмону 16 Кб колдоосу менен кайра түзүңүз. Кеңири маалымат: &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Кызмат камсыздалган эмес."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Чалуучунун далдаштырма дайындары параметрлерин өзгөртө албайсыз."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Мобилдик Интернет <xliff:g id="CARRIERDISPLAY">%s</xliff:g> которулду"</string>
@@ -1947,6 +1950,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>, <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Бардык жылнаамалар"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> айрым үндөрдү өчүрүүдө"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Түзмөгүңүздө ички көйгөй бар жана ал баштапкы абалга кайтарылмайынча туруктуу иштебей коюшу мүмкүн."</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 5af4a84..837cfc3 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -71,6 +71,9 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ໝາຍເລກຜູ່ໂທ ໄດ້ຮັບການຕັ້ງຄ່າເລີ່ມຕົ້ນເປັນ ຖືກຈຳກັດ. ການໂທຄັ້ງຕໍ່ໄປ: ບໍ່ຖືກຈຳກັດ."</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Caller ID ໂດຍເລີ່ມຕົ້ນຖືກປັບໃຫ້ບໍ່ມີການປິດກັ້ນ. ການໂທຕໍ່ໄປ:ປິດກັ້ນ"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ໝາຍເລກຜູ່ໂທ ໄດ້ຮັບການຕັ້ງຄ່າເລີ່ມຕົ້ນເປັນ ບໍ່ຖືກຈຳກັດ. ການໂທຄັ້ງຕໍ່ໄປ: ບໍ່ຖືກຈຳກັດ."</string>
+    <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"ແອັບນີ້ເຂົ້າກັນບໍ່ໄດ້ກັບ 16 KB. ກວດສອບການຈັດຕຳແໜ່ງ APK ບໍ່ສຳເລັດ. ແອັບນີ້ຈະເຮັດວຽກໂດຍໃຊ້ໂໝດທີ່ເຂົ້າກັນໄດ້ກັບຂະໜາດໜ້າ. ເພື່ອຄວາມເຂົ້າກັນໄດ້ດີທີ່ສຸດ, ກະລຸນາຮວມແອັບພລິເຄຊັນຄືນດ້ວຍການຮອງຮັບ 16 KB. ສຳລັບຂໍ້ມູນເພີ່ມເຕີມ, ໃຫ້ເບິ່ງ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"ແອັບນີ້ເຂົ້າກັນບໍ່ໄດ້ກັບ 16 KB. ກວດສອບການຈັດຕຳແໜ່ງ ELF ບໍ່ສຳເລັດ. ແອັບນີ້ຈະເຮັດວຽກໂດຍໃຊ້ໂໝດທີ່ເຂົ້າກັນໄດ້ກັບຂະໜາດໜ້າ. ເພື່ອຄວາມເຂົ້າກັນໄດ້ດີທີ່ສຸດ, ກະລຸນາຮວມແອັບພລິເຄຊັນຄືນດ້ວຍການຮອງຮັບ 16 KB. ສຳລັບຂໍ້ມູນເພີ່ມເຕີມ, ໃຫ້ເບິ່ງ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"ແອັບນີ້ເຂົ້າກັນບໍ່ໄດ້ກັບ 16 KB. ກວດສອບການຈັດຕຳແໜ່ງ APK ແລະ ELF ບໍ່ສຳເລັດ. ແອັບນີ້ຈະເຮັດວຽກໂດຍໃຊ້ໂໝດທີ່ເຂົ້າກັນໄດ້ກັບຂະໜາດໜ້າ. ເພື່ອຄວາມເຂົ້າກັນໄດ້ດີທີ່ສຸດ, ກະລຸນາຮວມແອັບພລິເຄຊັນຄືນດ້ວຍການຮອງຮັບ 16 KB. ສຳລັບຂໍ້ມູນເພີ່ມເຕີມ, ໃຫ້ເບິ່ງ &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ບໍ່ໄດ້ເປີດໃຊ້ບໍລິການ."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"ທ່ານບໍ່ສາມາດປ່ຽນແປງການຕັ້ງຄ່າ Caller ID"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"ປ່ຽນໄປໃຊ້ອິນເຕີເນັດມືຖືຂອງ <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ແລ້ວ"</string>
@@ -1947,6 +1950,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> ຫາ <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ປະ​ຕິ​ທິນ​ໃດ​ກໍໄດ້"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ປິດສຽງບາງຢ່າງໄວ້"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"ມີ​ບັນ​ຫາ​ພາຍ​ໃນ​ກັບ​ອຸ​ປະ​ກອນ​ຂອງ​ທ່ານ, ແລະ​ມັນ​ອາດ​ຈະ​ບໍ່​ສະ​ຖຽນ​ຈົນ​ກວ່າ​ທ່ານ​ຕັ້ງ​ເປັນ​ຂໍ້​ມູນ​ໂຮງ​ງານ​ຄືນ​ແລ້ວ."</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index cb7cc89..a281355 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -73,6 +73,9 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Skambintojo ID pagal numatytuosius nustatymus yra apribotas. Kitas skambutis: neapribotas"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Skambintojo ID pagal numatytuosius nustatymus nustatomas į neapribotą. Kitas skambutis: apribotas"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Skambintojo ID pagal numatytuosius nustatymus yra neapribotas. Kitas skambutis: neapribotas"</string>
+    <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Ši programa nesuderinama su 16 KB. APK lygiavimo patikrinimas nepavyko. Ši programa bus paleista naudojant su puslapio dydžiu suderintą režimą. Kad užtikrintumėte geriausią suderinamumą, iš naujo sukompiliuokite programą su 16 KB palaikymu. Jei reikia daugiau informacijos, žr. &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Ši programa nesuderinama su 16 KB. ELF lygiavimo patikrinimas nepavyko. Ši programa bus paleista naudojant su puslapio dydžiu suderintą režimą. Kad užtikrintumėte geriausią suderinamumą, iš naujo sukompiliuokite programą su 16 KB palaikymu. Jei reikia daugiau informacijos, žr. &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Ši programa nesuderinama su 16 KB. APK ir ELF lygiavimo patikrinimai nepavyko. Ši programa bus paleista naudojant su puslapio dydžiu suderintą režimą. Kad užtikrintumėte geriausią suderinamumą, iš naujo sukompiliuokite programą su 16 KB palaikymu. Jei reikia daugiau informacijos, žr. &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Paslauga neteikiama."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Negalima pakeisti skambinančiojo ID nustatymo."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Duomenys perjungti į „<xliff:g id="CARRIERDISPLAY">%s</xliff:g>“"</string>
@@ -1949,6 +1952,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Bet kuris kalendorius"</string>
     <string name="muted_by" msgid="91464083490094950">"„<xliff:g id="THIRD_PARTY">%1$s</xliff:g>“ nutildo kai kuriuos garsus"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Iškilo vidinė su jūsų įrenginiu susijusi problema, todėl įrenginys gali veikti nestabiliai, kol neatkursite gamyklinių duomenų."</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index b72fb07..a1c134b 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -72,6 +72,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Zvanītāja ID noklusējumi ir iestatīti uz Ierobežots. Nākamais zvans: nav ierobežots"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Zvanītāja ID noklusējumi ir iestatīti uz Nav ierobežots. Nākamais zvans: ierobežots"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Zvanītāja ID noklusējumi ir iestatīti uz Nav ierobežots. Nākamais zvans: nav ierobežots"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Pakalpojums netiek nodrošināts."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Zvanītāja ID iestatījumu nevar mainīt."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Tiek izmantots operatora <xliff:g id="CARRIERDISPLAY">%s</xliff:g> datu savienojums"</string>
@@ -1948,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"no <xliff:g id="START">%1$s</xliff:g> līdz <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Jebkurš kalendārs"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> izslēdz noteiktas skaņas"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Jūsu ierīcē ir radusies iekšēja problēma, un ierīce var darboties nestabili. Lai to labotu, veiciet rūpnīcas datu atiestatīšanu."</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 0b0e3ef..74e5bf0 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Стандардно, ID на повикувач е скриен. Следен повик: не е скриен"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Стандардно, ID на повикувач не е скриен. Следен повик: скриен"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Стандардно, ID на повикувач не е скриен. Следен повик: не е скриен"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Услугата не е предвидена."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Не може да го промените поставувањето за ID на повикувач."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Мобилниот интернет се префрли на <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> до <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Кој било календар"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> исклучи некои звуци"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Настана внатрешен проблем со уредот и може да биде нестабилен сè додека не ресетирате на фабричките податоци."</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index fe5974b..37f51c5 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"നിയന്ത്രിക്കേണ്ട സ്ഥിര കോളർ ഐഡികൾ. അടുത്ത കോൾ: നിയന്ത്രിച്ചിട്ടില്ല"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"നിയന്ത്രിക്കേണ്ടതല്ലാത്ത സ്ഥിര കോളർ ഐഡികൾ. അടുത്ത കോൾ: നിയന്ത്രിച്ചിട്ടുണ്ട്"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"നിയന്ത്രിക്കേണ്ടതല്ലാത്ത സ്ഥിര കോളർ ഐഡികൾ. അടുത്ത കോൾ: നിയന്ത്രിച്ചിട്ടില്ല"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"സേവനം വ്യവസ്ഥ ചെയ്‌തിട്ടില്ല."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"വിളിച്ച നമ്പർ ക്രമീകരണം നിങ്ങൾക്ക് മാറ്റാനാവില്ല."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> എന്നതിലേക്ക് ഡാറ്റ മാറ്റി"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> മുതൽ <xliff:g id="END">%2$s</xliff:g> വരെ"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"എല്ലാ കലണ്ടറിലും"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ചില ശബ്‌ദങ്ങൾ മ്യൂട്ട് ചെയ്യുന്നു"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"നിങ്ങളുടെ ഉപകരണത്തിൽ ഒരു ആന്തരിക പ്രശ്‌നമുണ്ട്, ഫാക്‌ടറി വിവര പുനഃസജ്ജീകരണം ചെയ്യുന്നതുവരെ ഇതു അസ്ഥിരമായിരിക്കാനിടയുണ്ട്."</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 376a033..fce948e 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Дуудлага хийгчийн ID хязгаарлагдсан. Дараагийн дуудлага: Хязгаарлагдаагүй"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Дуудлага хийгчийн ID хязгаарлагдаагүй. Дараагийн дуудлага: Хязгаарлагдсан"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Дуудлага хийгчийн ID хязгаарлагдсан. Дараагийн дуудлага: Хязгаарлагдсан"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Үйлчилгээ провишн хийгдээгүй ."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Та дуудлага хийгчийн ID тохиргоог солиж чадахгүй."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Өгөгдлийг <xliff:g id="CARRIERDISPLAY">%s</xliff:g> руу шилжүүлсэн"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>-с <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Дурын календарь"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> зарим дууны дууг хааж байна"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Таны төхөөрөмжид дотоод алдаа байна.Та төхөөрөмжөө үйлдвэрээс гарсан төлөвт шилжүүлэх хүртэл таны төхөөрөмж чинь тогтворгүй байж болох юм."</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index e640f72..cf76311 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -71,6 +71,9 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"कॉलर आयडी डीफॉल्‍ट रूपात प्रतिबंधित वर सेट असतो. पुढील कॉल: प्रतिबंधित नाही"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"कॉलर आयडी डीफॉल्‍ट रूपात प्रतिबंधित नाही वर सेट असतो. पुढील कॉल: प्रतिबंधित"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कॉलर आयडी डीफॉल्‍ट रूपात प्रतिबंधित नाही वर सेट असतो. पुढील कॉल: प्रतिबंधित नाही"</string>
+    <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"हे अ‍ॅप १६ KB कंपॅटिबल नाही. APK अलाइनमेंटची तपासणी करता आली नाही. हे ॲप पेजच्या आकाराशी कंपॅटिबल असलेला मोड वापरून रन केले जाईल. सर्वोत्तम कंपॅटिबिलिटीसाठी, कृपया १६ KB च्या सपोर्टसह अ‍ॅप्लिकेशन पुन्हा कंपाइल करा. अधिक माहितीसाठी, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; पहा"</string>
+    <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"हे अ‍ॅप १६ KB कंपॅटिबल नाही. ELF अलाइनमेंटची तपासणी करता आली नाही. हे ॲप पेजच्या आकाराशी कंपॅटिबल असलेला मोड वापरून रन केले जाईल. सर्वोत्तम कंपॅटिबिलिटीसाठी, कृपया १६ KB च्या सपोर्टसह अ‍ॅप्लिकेशन पुन्हा कंपाइल करा. अधिक माहितीसाठी, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; पहा"</string>
+    <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"हे अ‍ॅप १६ KB कंपॅटिबल नाही. APK आणि ELF अलाइनमेंटची तपासणी करता आली नाही. हे ॲप पेजच्या आकाराशी कंपॅटिबल असलेला मोड वापरून रन केले जाईल. सर्वोत्तम कंपॅटिबिलिटीसाठी, कृपया १६ KB च्या सपोर्टसह अ‍ॅप्लिकेशन पुन्हा कंपाइल करा. अधिक माहितीसाठी, &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; पहा"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवेची तरतूद केलेली नाही."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"तुम्ही कॉलर आयडी सेटिंग बदलू शकत नाही."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"डेटा <xliff:g id="CARRIERDISPLAY">%s</xliff:g> वर स्विच केला"</string>
@@ -1947,6 +1950,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> ते <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"कोणतेही कॅलेंडर"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> काही ध्‍वनी म्‍यूट करत आहे"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"आपल्‍या डिव्‍हाइसमध्‍ये अंतर्गत समस्‍या आहे आणि तुमचा फॅक्‍टरी डेटा रीसेट होईपर्यंत ती अस्‍थिर असू शकते."</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 3946ac1..34d7654 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID pemanggil secara lalainya ditetapkan kepada terhad. Panggilan seterusnya: Tidak terhad"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID pemanggil secara lalainya ditetapkan kepada tidak terhad. Panggilan seterusnya: Terhad"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID pemanggil secara lalainya ditetapkan kepada tidak dihadkan. Panggilan seterusnya: Tidak terhad"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Perkhidmatan yang tidak diuntukkan."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Anda tidak boleh mengubah tetapan ID pemanggil."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Data ditukar kepada <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> hingga <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Sebarang kalendar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> meredamkan sesetengah bunyi"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Terdapat masalah dalaman dengan peranti anda. Peranti mungkin tidak stabil sehingga anda membuat tetapan semula data kilang."</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index fc00725..45be6f5 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ပုံသေအားဖြင့် ခေါ်ဆိုသူအိုင်ဒီ(Caller ID)အား ကန့်သတ်ထားသည်။ နောက်ထပ်အဝင်ခေါ်ဆိုမှု-ကန့်သတ်မထားပါ။"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ပုံသေအားဖြင့် ခေါ်ဆိုသူအိုင်ဒီ(Caller ID)အား ကန့်သတ်မထားပါ။ နောက်ထပ်အဝင်ခေါ်ဆိုမှု-ကန့်သတ်ထားသည်။"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ပုံသေအားဖြင့် ခေါ်ဆိုသူအိုင်ဒီ(Caller ID)အား ကန့်သတ်မထားပါ။ နောက်ထပ်အဝင်ခေါ်ဆိုမှု-ကန့်သတ်မထားပါ။"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ဝန်ဆောင်မှုအား ကန့်သတ်မထားပါ"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"သင်သည် ခေါ်ဆိုသူ ID ဆက်တင်ကို မပြောင်းလဲနိုင်ပါ။"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"ဒေတာကို <xliff:g id="CARRIERDISPLAY">%s</xliff:g> သို့ ပြောင်းထားသည်"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"၊ "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> မှ <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>၊ <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"မည်သည့်ပြက္ခဒိန်မဆို"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> သည် အချို့အသံကို ပိတ်နေသည်"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"သင့်ကိရိယာအတွင်းပိုင်းတွင် ပြဿနာရှိနေပြီး၊ မူလစက်ရုံထုတ်အခြေအနေအဖြစ် ပြန်လည်ရယူနိုင်သည်အထိ အခြေအနေမတည်ငြိမ်နိုင်ပါ။"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index c1095d3..910d70d 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Nummervisning er begrenset som standard. Neste anrop: Ikke begrenset"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Nummervisning er ikke begrenset som standard. Neste anrop: Begrenset"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Nummervisning er ikke begrenset som standard. Neste anrop: Ikke begrenset"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"SIM-kortet er ikke tilrettelagt for tjenesten."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Du kan ikke endre innstillingen for anrops-ID."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Byttet data til <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> til <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Hvilken som helst kalender"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> slår av noen lyder"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Det har oppstått et internt problem på enheten din, og den kan være ustabil til du tilbakestiller den til fabrikkdata."</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index beda131..5e6d850 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"कलर ID पूर्वनिर्धारितको लागि रोकावट छ। अर्को कल: रोकावट छैन"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"कलर ID पूर्वनिर्धारितदेखि प्रतिबन्धित छैन। अर्को कल: प्रतिबन्धित छ"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कलर ID पूर्वनिर्धारितको लागि रोकावट छैन। अर्को कल: रोकावट छैन"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवाको व्यवस्था छैन।"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"तपाईं कलर ID सेटिङ परिवर्तन गर्न सक्नुहुन्न।"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> को डेटा प्रयोग गर्न थालिएको छ"</string>
@@ -1105,7 +1111,7 @@
     <string name="permlab_addVoicemail" msgid="4770245808840814471">"भ्वाइसमेल थप गर्नुहोस्"</string>
     <string name="permdesc_addVoicemail" msgid="5470312139820074324">"तपाईँको भ्वाइसमेल इनबक्समा सन्देश थप्नको लागि एपलाई अनुमति दिन्छ।"</string>
     <string name="pasted_from_clipboard" msgid="7355790625710831847">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> ले तपाईंको क्लिपबोर्डमा रहेको जानकारी पेस्ट गरेको छ"</string>
-    <string name="more_item_label" msgid="7419249600215749115">"बढी"</string>
+    <string name="more_item_label" msgid="7419249600215749115">"थप"</string>
     <string name="prepend_shortcut_label" msgid="1743716737502867951">"मेनु+"</string>
     <string name="menu_meta_shortcut_label" msgid="1623390163674762478">"Meta+"</string>
     <string name="menu_ctrl_shortcut_label" msgid="131911133027196485">"Ctrl+"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> देखि <xliff:g id="END">%2$s</xliff:g> सम्म"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"कुनै पनि पात्रो"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ले केही ध्वनिहरू म्युट गर्दै छ"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"तपाईंको यन्त्रसँग आन्तरिक समस्या छ, र तपाईंले फ्याक्ट्री डाटा रिसेट नगर्दासम्म यो अस्थिर रहन्छ।"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 48c8be1..b45f6cd 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Beller-ID standaard ingesteld op \'beperkt\'. Volgend gesprek: onbeperkt."</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Beller-ID standaard ingesteld op \'onbeperkt\'. Volgend gesprek: beperkt."</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Beller-ID standaard ingesteld op \'onbeperkt\'. Volgend gesprek: onbeperkt."</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service niet voorzien."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"U kunt de instelling voor de beller-ID niet wijzigen."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Mobiele data overgeschakeld naar <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> tot <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Elke agenda"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> zet sommige geluiden uit"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Er is een intern probleem met je apparaat. Het apparaat kan instabiel zijn totdat u het apparaat terugzet naar de fabrieksinstellingen."</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index a99afa5..9e57ca3 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"କଲର୍ ଆଇଡି ଡିଫଲ୍ଟ ଭାବରେ ପ୍ରତିବନ୍ଧିତ। ପରବର୍ତ୍ତୀ କଲ୍: ପ୍ରତିବନ୍ଧିତ ନୁହେଁ"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"କଲର୍ ଆଇଡି ଡିଫଲ୍ଟ ଭାବରେ ପ୍ରତିବନ୍ଧିତ ନୁହେଁ। ପରବର୍ତ୍ତୀ କଲ୍: ପ୍ରତିବନ୍ଧିତ"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"କଲର୍ ଆଇଡି ଡିଫଲ୍ଟ ଭାବରେ ପ୍ରତିବନ୍ଧିତ ନୁହେଁ। ପରବର୍ତ୍ତୀ କଲ୍: ପ୍ରତିବନ୍ଧିତ ନୁହେଁ"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ସେବାର ସୁବିଧା ନାହିଁ।"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"ଆପଣ କଲର୍‍ ID ସେଟିଙ୍ଗ ବଦଳାଇପାରିବେ ନାହିଁ।"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g>କୁ ଡାଟା ସ୍ୱିଚ କରାଯାଇଛି"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>ରୁ <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ଯେକୌଣସି କ୍ୟାଲେଣ୍ଡର୍‌"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> କିଛି ସାଉଣ୍ଡକୁ ମ୍ୟୁଟ୍ କରୁଛି"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"ଆପଣଙ୍କ ଡିଭାଇସ୍‍ରେ ଏକ ସମସ୍ୟା ରହିଛି ଏବଂ ଆପଣ ଫ୍ୟାକ୍ଟୋରୀ ଡାଟା ରିସେଟ୍‍ ନକରିବା ପର୍ଯ୍ୟନ୍ତ ଏହା ଅସ୍ଥିର ରହିପାରେ।"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index b4630174..c7b11e5 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ਪ੍ਰਤਿਬੰਧਿਤ ਕਰਨ ਲਈ ਕਾਲਰ ਆਈ.ਡੀ. ਪੂਰਵ-ਨਿਰਧਾਰਤ। ਅਗਲੀ ਕਾਲ: ਪ੍ਰਤਿਬੰਧਿਤ ਨਹੀਂ"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ਪ੍ਰਤਿਬੰਧਿਤ ਨਾ ਕਰਨ ਲਈ ਕਾਲਰ ਆਈ.ਡੀ. ਪੂਰਵ-ਨਿਰਧਾਰਤ। ਅਗਲੀ ਕਾਲ: ਪ੍ਰਤਿਬੰਧਿਤ"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ਪ੍ਰਤਿਬੰਧਿਤ ਨਾ ਕਰਨ ਲਈ ਕਾਲਰ ਆਈ.ਡੀ. ਪੂਰਵ-ਨਿਰਧਾਰਤ। ਅਗਲੀ ਕਾਲ: ਪ੍ਰਤਿਬੰਧਿਤ ਨਹੀਂ"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ਸੇਵਾ ਪ੍ਰਬੰਧਿਤ ਨਹੀਂ ਹੈ।"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"ਤੁਸੀਂ ਕਾਲਰ ਆਈ.ਡੀ. ਸੈਟਿੰਗ ਨਹੀਂ ਬਦਲ ਸਕਦੇ।"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"ਡਾਟੇ ਨੂੰ <xliff:g id="CARRIERDISPLAY">%s</xliff:g> \'ਤੇ ਸਵਿੱਚ ਕੀਤਾ ਗਿਆ"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> ਤੋਂ <xliff:g id="END">%2$s</xliff:g> ਤੱਕ"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ਕੋਈ ਵੀ ਕੈਲੰਡਰ"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ਕੁਝ ਧੁਨੀਆਂ ਨੂੰ ਮਿਊਟ ਕਰ ਰਹੀ ਹੈ"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨਾਲ ਇੱਕ ਅੰਦਰੂਨੀ ਸਮੱਸਿਆ ਹੈ ਅਤੇ ਇਹ ਅਸਥਿਰ ਹੋ ਸਕਦੀ ਹੈ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਨਹੀਂ ਕਰਦੇ।"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 4f34fc5..7a7e159 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -73,6 +73,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID rozmówcy ustawiony jest domyślnie na „zastrzeżony”. Następne połączenie: nie zastrzeżony"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID rozmówcy ustawiony jest domyślnie na „nie zastrzeżony”. Następne połączenie: zastrzeżony"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID rozmówcy ustawiony jest domyślnie na „nie zastrzeżony”. Następne połączenie: nie zastrzeżony"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Usługa nie jest świadczona."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Nie możesz zmienić ustawienia ID rozmówcy."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Przełączono mobilną transmisję danych na: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1949,6 +1955,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"Od <xliff:g id="START">%1$s</xliff:g> do <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Dowolny kalendarz"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> wycisza niektóre dźwięki"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"W Twoim urządzeniu wystąpił problem wewnętrzny. Może być ono niestabilne, dopóki nie przywrócisz danych fabrycznych."</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index a219276..d332777 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -72,6 +72,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"O identificador de chamadas assume o padrão de restrito. Próxima chamada: Não restrita"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"O identificador de chamadas assume o padrão de não restrito. Próxima chamada: Restrita"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O identificador de chamadas assume o padrão de não restrito. Próxima chamada: Não restrita"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"O serviço não foi habilitado."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Não é possível alterar a configuração do identificador de chamadas."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Dados da <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ativados"</string>
@@ -1948,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Qualquer agenda"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está silenciando alguns sons"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Há um problema interno com seu dispositivo. Ele pode ficar instável até que você faça a redefinição para configuração original."</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index a898121..8b1d408 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -72,6 +72,9 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID do autor da chamada é predefinido como restrito. Chamada seguinte: Não restrita"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID do autor da chamada é predefinido como não restrito. Chamada seguinte: Restrita"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID do autor da chamada é predefinido com não restrito. Chamada seguinte: Não restrita"</string>
+    <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Esta app não é compatível com 16 KB. Falha na verificação do alinhamento do APK. Esta app vai ser executada através do modo compatível com o tamanho da página. Para uma melhor compatibilidade, recompile a aplicação de forma a ser compatível com 16 KB. Para ver mais informações, consulte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Esta app não é compatível com 16 KB. Falha na verificação do alinhamento do ELF. Esta app vai ser executada através do modo compatível com o tamanho da página. Para uma melhor compatibilidade, recompile a aplicação de forma a ser compatível com 16 KB. Para ver mais informações, consulte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
+    <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Esta app não é compatível com 16 KB. As verificações de alinhamento do APK e do ELF falharam. Esta app vai ser executada através do modo compatível com o tamanho da página. Para uma melhor compatibilidade, recompile a aplicação de forma a ser compatível com 16 KB. Para ver mais informações, consulte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Serviço não fornecido."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Não pode alterar a definição da identificação de chamadas."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Os dados móveis foram alterados para o operador <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1948,6 +1951,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Qualquer calendário"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está a desativar alguns sons."</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Existe um problema interno no seu dispositivo e pode ficar instável até efetuar uma reposição de dados de fábrica."</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index a219276..d332777 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -72,6 +72,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"O identificador de chamadas assume o padrão de restrito. Próxima chamada: Não restrita"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"O identificador de chamadas assume o padrão de não restrito. Próxima chamada: Restrita"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O identificador de chamadas assume o padrão de não restrito. Próxima chamada: Não restrita"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"O serviço não foi habilitado."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Não é possível alterar a configuração do identificador de chamadas."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Dados da <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ativados"</string>
@@ -1948,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Qualquer agenda"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está silenciando alguns sons"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Há um problema interno com seu dispositivo. Ele pode ficar instável até que você faça a redefinição para configuração original."</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 8b7a296..ed97431 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -72,6 +72,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID-ul apelantului este restricționat în mod prestabilit. Apelul următor: nerestricționat"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID-ul apelantului este nerestricționat în mod prestabilit. Apelul următor: Restricționat."</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID-ul apelantului este nerestricționat în mod prestabilit. Apelul următor: nerestricționat"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Nu se asigură accesul la acest serviciu."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Nu poți modifica setarea pentru ID-ul apelantului."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"S-a trecut la datele mobile <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1948,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Orice calendar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> dezactivează anumite sunete"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"A apărut o problemă internă pe dispozitiv, iar acesta poate fi instabil până la revenirea la setările din fabrică."</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 386830e..9b3a719 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -73,6 +73,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Идентификация абонента по умолчанию запрещена. След. вызов: разрешена"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Идентификация абонента по умолчанию не запрещена. След. вызов: запрещена"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Идентификация абонента по умолчанию не запрещена. След. вызов: разрешена"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Услуга не предоставляется."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Невозможно изменить параметр идентификатора вызывающего абонента."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Используется мобильный интернет <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1949,6 +1955,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Любой календарь"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> приглушает некоторые звуки."</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Произошла внутренняя ошибка, и устройство может работать нестабильно, пока вы не выполните сброс настроек."</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index e969a8e..a02cf02 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"අමතන්නාගේ ID සුපුරුදු අනුව සීමා වී ඇත. මීළඟ ඇමතුම: සීමා කර නැත"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"අමතන්නාගේ ID සුපුරුදු අනුව සීමා වී නැත. මීළඟ ඇමතුම: සීමා කර ඇත"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"අමතන්නාගේ ID සුපුරුදු අනුව සීමා වී නැත. මීළඟ ඇමතුම: සීමා කර ඇත"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"සේවාවන් සපයා නැත."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"අමතන්නාගේ ID සැකසීම ඔබට වෙනස්කල නොහැක."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"දත්ත <xliff:g id="CARRIERDISPLAY">%s</xliff:g> වෙත මාරු කරන ලදි"</string>
@@ -1059,7 +1065,7 @@
     <string name="lockscreen_access_pattern_cell_added_verbose" msgid="2931364927622563465">"<xliff:g id="CELL_INDEX">%1$s</xliff:g> කොටුව එකතු කරන ලදි"</string>
     <string name="lockscreen_access_pattern_detected" msgid="3931150554035194012">"රටාව සම්පූර්ණයි"</string>
     <string name="lockscreen_access_pattern_area" msgid="1288780416685002841">"රටා ප්‍රදේශය."</string>
-    <string name="keyguard_accessibility_widget_changed" msgid="7298011259508200234">"%%1$s. %%3$d න් %%2$d විජටය."</string>
+    <string name="keyguard_accessibility_widget_changed" msgid="7298011259508200234">"%1$s. %3$d න් %2$d විජටය."</string>
     <string name="keyguard_accessibility_add_widget" msgid="8245795023551343672">"විජටය එක් කරන්න."</string>
     <string name="keyguard_accessibility_widget_empty_slot" msgid="544239307077644480">"හිස්"</string>
     <string name="keyguard_accessibility_unlock_area_expanded" msgid="7768634718706488951">"අගුළු අරින ප්‍රදේශය විදහා ඇත."</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="END">%2$s</xliff:g> සිට <xliff:g id="START">%1$s</xliff:g> දක්වා"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ඕනෑම දින දර්ශනයක්"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> සමහර ශබ්ද නිහඬ කරමින්"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"ඔබේ උපාංගය සමගින් ගැටලුවක් ඇති අතර, ඔබේ කර්මාන්තශාලා දත්ත යළි සකසන තෙක් එය අස්ථායි විය හැකිය."</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index d211d8c..fd2c727 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -73,6 +73,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"V predvolenom nastavení je identifikácia volajúceho obmedzená. Ďalší hovor: Bez obmedzenia"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"V predvolenom nastavení nie je identifikácia volajúceho obmedzená. Ďalší hovor: Obmedzené"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"V predvolenom nastavení nie je identifikácia volajúceho obmedzená. Ďalší hovor: Bez obmedzenia"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Služba nie je poskytovaná."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Nemôžete meniť nastavenie identifikácie volajúcich."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Dátové pripojenie bolo prepnuté na operátora <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1949,6 +1955,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Ľubovoľný kalendár"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> vypína niektoré zvuky"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Vo vašom zariadení došlo k internému problému. Môže byť nestabilné, kým neobnovíte jeho výrobné nastavenia."</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index ca85434..51803b0 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -73,6 +73,9 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID klicatelja je ponastavljen na omejeno. Naslednji klic: ni omejeno"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID klicatelja je ponastavljen na neomejeno. Naslednji klic: omejeno"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID klicatelja je ponastavljen na neomejeno. Naslednji klic: ni omejeno"</string>
+    <string name="page_size_compat_apk_warning" msgid="2982396798449041224">"Ta aplikacija ni združljiva s pomnilniško stranjo velikosti 16 KB. Preverjanje usklajenosti oblike zapisa APK ni uspelo. Ta aplikacija bo delovala v načinu, združljivem z velikostjo pomnilniške strani. Za najboljšo združljivost znova prevedite aplikacijo s podporo za pomnilniško stran velikosti 16 KB. Za več informacij si oglejte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+    <string name="page_size_compat_elf_warning" msgid="6753874059564812651">"Ta aplikacija ni združljiva s pomnilniško stranjo velikosti 16 KB. Preverjanje usklajenosti oblike zapisa ELF ni uspelo. Ta aplikacija bo delovala v načinu, združljivem z velikostjo pomnilniške strani. Za najboljšo združljivost znova prevedite aplikacijo s podporo za pomnilniško stran velikosti 16 KB. Za več informacij si oglejte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
+    <string name="page_size_compat_apk_and_elf_warning" msgid="7628675779500605390">"Ta aplikacija ni združljiva s pomnilniško stranjo velikosti 16 KB. Preverjanja usklajenosti oblik zapisov APK in ELF niso uspela. Ta aplikacija bo delovala v načinu, združljivem z velikostjo pomnilniške strani. Za najboljšo združljivost znova prevedite aplikacijo s podporo za pomnilniško stran velikosti 16 KB. Za več informacij si oglejte &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;."</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Storitev ni nastavljena in omogočena."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ne morete spremeniti nastavitve ID-ja klicatelja."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Prenos podatkov je preklopljen na operaterja <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1949,6 +1952,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> do <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Kateri koli koledar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> izklaplja nekatere zvoke"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Vaša naprava ima notranjo napako in bo morda nestabilna, dokler je ne ponastavite na tovarniške nastavitve."</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 45cf31a..995910a 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ID-ja e telefonuesit kalon me paracaktim në listën e të telefonuesve të kufizuar. Telefonata e radhës: e pakufizuar!"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ID-ja e telefonuesit kalon me paracaktim në listën e të telefonuesve të pakufizuar. Telefonata e radhës: e kufizuar!"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID-ja e telefonuesit kalon me paracaktim në listën e të telefonuesve të pakufizuar. Telefonata e radhës: e pakufizuar!"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Shërbimi nuk është përgatitur."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Nuk mund ta ndryshosh cilësimin e ID-së së telefonuesit."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Të dhënat u kaluan te <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Çdo kalendar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> po çaktivizon disa tinguj"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Ka një problem të brendshëm me pajisjen tënde. Ajo mund të jetë e paqëndrueshme derisa të rivendosësh të dhënat në gjendje fabrike."</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 5a3bae0..7ce7b7b 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -72,6 +72,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"ИД позиваоца је подразумевано ограничен. Следећи позив: Није ограничен."</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"ИД позиваоца подразумевано није ограничен. Следећи позив: ограничен."</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ИД позиваоца подразумевано није ограничен. Следећи позив: Није ограничен."</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Услуга није добављена."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Не можете да промените подешавање ИД-а корисника."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Мобилни подаци су пребачени на оператера <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1948,6 +1954,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Било који календар"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> искључује неке звуке"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Дошло је до интерног проблема у вези са уређајем и можда ће бити нестабилан док не обавите ресетовање на фабричка подешавања."</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index fde9023..a9a6067 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Nummerpresentatörens standardinställning är blockerad. Nästa samtal: Inte blockerad"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Nummerpresentatörens standardinställning är inte blockerad. Nästa samtal: Blockerad"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Nummerpresentatörens standardinställning är inte blockerad. Nästa samtal: Inte blockerad"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Tjänsten är inte etablerad."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Det går inte att ändra inställningen för nummerpresentatör."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Du har bytt mobildata till <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>–<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> till <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Alla kalendrar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> stänger av vissa ljud"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Ett internt problem har uppstått i enheten, och det kan hända att problemet kvarstår tills du återställer standardinställningarna."</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 2a62c87..4d0d7dc 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Chaguomsingi za kitambulisho cha mpigaji simu huwa kuzuiwa. Simu ifuatayo: Haijazuiliwa"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Chaguomsingi za ID ya mpigaji simu za kutozuia. Simu ifuatayo:Imezuiliwa"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Chaguomsingi za ID ya mpigaji simu za kutozuia. Simu ifuatayo: Haijazuiliwa"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Huduma haitathminiwi."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Hauwezi kubadilisha mpangilio wa kitambulisho cha anayepiga."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Sasa unatumia data ya mtandao wa <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> hadi <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Kalenda yoyote"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> inazima baadhi ya sauti"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Kuna hitilafu ya ndani ya kifaa chako, na huenda kisiwe thabiti mpaka urejeshe mipangilio ya kiwandani."</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 6ff96c0..96e62f2 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"அழைப்பாளர் ஐடி ஆனது வரையறுக்கப்பட்டது என்பதற்கு இயல்பாக அமைக்கப்பட்டது. அடுத்த அழைப்பு: வரையறுக்கப்படவில்லை"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"அழைப்பாளர் ஐடி ஆனது வரையறுக்கப்படவில்லை என்பதற்கு இயல்பாக அமைக்கப்பட்டது. அடுத்த அழைப்பு: வரையறுக்கப்பட்டது"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"அழைப்பாளர் ஐடி ஆனது வரையறுக்கப்படவில்லை என்பதற்கு இயல்பாக அமைக்கப்பட்டது. அடுத்த அழைப்பு: வரையறுக்கப்படவில்லை"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"சேவை ஒதுக்கப்படவில்லை."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"அழைப்பாளர் ஐடி அமைப்பை மாற்ற முடியாது."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"டேட்டா <xliff:g id="CARRIERDISPLAY">%s</xliff:g>க்கு மாற்றப்பட்டது"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> முதல் <xliff:g id="END">%2$s</xliff:g> வரை"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ஏதேனும் கேலெண்டர்"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> சில ஒலிகளை முடக்குகிறது"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"சாதனத்தில் அகச் சிக்கல் இருக்கிறது, அதனை ஆரம்பநிலைக்கு மீட்டமைக்கும் வரை நிலையற்று இயங்கலாம்."</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 68fce1f..9495a96 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"కాలర్ ID ఆటోమేటిక్‌లపై పరిమితి ఉంటుంది. తర్వాత కాల్: పరిమితి లేదు"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"కాలర్ ID ఆటోమేటిక్‌లపై పరిమితి లేదు. తర్వాత కాల్: పరిమితి ఉంటుంది"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"కాలర్ ID ఆటోమేటిక్‌లపై పరిమితి లేదు. తర్వాత కాల్: పరిమితి లేదు"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"సేవ కేటాయించబడలేదు."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"మీరు కాలర్ ID సెట్టింగ్‌ను మార్చలేరు."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"డేటాను <xliff:g id="CARRIERDISPLAY">%s</xliff:g>కు స్విచ్ చేశారు"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> నుండి <xliff:g id="END">%2$s</xliff:g> వరకు"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ఏదైనా క్యాలెండర్"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> కొన్ని ధ్వనులను మ్యూట్ చేస్తోంది"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"మీ పరికరంతో అంతర్గత సమస్య ఏర్పడింది మరియు మీరు ఫ్యాక్టరీ డేటా రీసెట్ చేసే వరకు అస్థిరంగా ఉంటుంది."</string>
@@ -2442,7 +2449,7 @@
     <string name="satellite_sos_not_in_allowed_region_notification_title" msgid="3164093193467075926">"ఎమర్జెన్సీ శాటిలైట్ సహాయం అందుబాటులో లేదు"</string>
     <string name="satellite_sos_not_in_allowed_region_notification_summary" msgid="7686947667515679672">"ఈ దేశంలో లేదా ప్రాంతంలో ఎమర్జెన్సీ శాటిలైట్ సహాయం అందుబాటులో లేదు"</string>
     <string name="satellite_sos_unsupported_default_sms_app_notification_title" msgid="292528603128702080">"ఎమర్జెన్సీ శాటిలైట్ సహాయం సెటప్ చేయలేదు"</string>
-    <string name="satellite_sos_unsupported_default_sms_app_notification_summary" msgid="3165168393504548437">"శాటిలైట్ ద్వారా మెసేజ్ చేయడానికి, Google Messagesను మీ ఆటోమేటిక్ సెట్టింగ్ మెసేజింగ్ యాప్‌గా సెట్ చేయండి"</string>
+    <string name="satellite_sos_unsupported_default_sms_app_notification_summary" msgid="3165168393504548437">"శాటిలైట్ ద్వారా మెసేజ్ చేయడానికి, Google Messagesను మీ ఆటోమేటిక్ మెసేజింగ్ యాప్‌గా సెట్ చేయండి"</string>
     <string name="satellite_sos_location_disabled_notification_title" msgid="5427987916850950591">"ఎమర్జెన్సీ శాటిలైట్ సహాయం అందుబాటులో లేదు"</string>
     <string name="satellite_sos_location_disabled_notification_summary" msgid="1544937460641058567">"ఈ దేశంలో లేదా ప్రాంతంలో ఎమర్జెన్సీ శాటిలైట్ సహాయం అందుబాటులో ఉందో లేదో చెక్ చేయడానికి, లొకేషన్ సెట్టింగ్‌లను ఆన్ చేయండి"</string>
     <string name="satellite_messaging_available_notification_title" msgid="3366657987618784706">"శాటిలైట్ మెసేజింగ్ అందుబాటులో ఉంది"</string>
@@ -2454,7 +2461,7 @@
     <string name="satellite_messaging_not_in_allowed_region_notification_title" msgid="2035303593479031655">"శాటిలైట్ మెసేజింగ్ అందుబాటులో లేదు"</string>
     <string name="satellite_messaging_not_in_allowed_region_notification_summary" msgid="5270294879531815854">"ఈ దేశంలో లేదా ప్రాంతంలో శాటిలైట్ మెసేజింగ్ అందుబాటులో లేదు"</string>
     <string name="satellite_messaging_unsupported_default_sms_app_notification_title" msgid="1004808759472360189">"శాటిలైట్ మెసేజింగ్‌ను సెటప్ చేయలేదు"</string>
-    <string name="satellite_messaging_unsupported_default_sms_app_notification_summary" msgid="17084124893763593">"శాటిలైట్ ద్వారా మెసేజ్ చేయడానికి, Google Messagesను మీ ఆటోమేటిక్ సెట్టింగ్ మెసేజింగ్ యాప్‌గా సెట్ చేయండి"</string>
+    <string name="satellite_messaging_unsupported_default_sms_app_notification_summary" msgid="17084124893763593">"శాటిలైట్ ద్వారా మెసేజ్ చేయడానికి, Google Messagesను మీ ఆటోమేటిక్ మెసేజింగ్ యాప్‌గా సెట్ చేయండి"</string>
     <string name="satellite_messaging_location_disabled_notification_title" msgid="7270641894250928494">"శాటిలైట్ మెసేజింగ్ అందుబాటులో లేదు"</string>
     <string name="satellite_messaging_location_disabled_notification_summary" msgid="1450824950686221810">"ఈ దేశంలో లేదా ప్రాంతంలో శాటిలైట్ మెసేజింగ్ అందుబాటులో ఉందో లేదో చెక్ చేయడానికి, లొకేషన్ సెట్టింగ్‌లను ఆన్ చేయండి"</string>
     <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"వేలిముద్ర అన్‌లాక్‌ను మళ్లీ సెటప్ చేయండి"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 178a76d..86931c0 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"หมายเลขผู้โทรได้รับการตั้งค่าเริ่มต้นเป็นถูกจำกัด การโทรครั้งต่อไป: ไม่จำกัด"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"หมายเลขผู้โทรได้รับการตั้งค่าเริ่มต้นเป็นไม่จำกัด การโทรครั้งต่อไป: ถูกจำกัด"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"หมายเลขผู้โทรได้รับการตั้งค่าเริ่มต้นเป็นไม่จำกัด การโทรครั้งต่อไป: ไม่จำกัด"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ไม่มีการนำเสนอบริการ"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"คุณไม่สามารถเปลี่ยนการตั้งค่าหมายเลขผู้โทร"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"เปลี่ยนไปใช้อินเทอร์เน็ตมือถือของ <xliff:g id="CARRIERDISPLAY">%s</xliff:g> แล้ว"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>ถึง<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"ปฏิทินทั้งหมด"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> กำลังปิดเสียงบางรายการ"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"อุปกรณ์ของคุณเกิดปัญหาภายในเครื่อง อุปกรณ์อาจทำงานไม่เสถียรจนกว่าคุณจะรีเซ็ตข้อมูลเป็นค่าเริ่มต้น"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 8e3c5e7..461050e 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Nade-default sa pinaghihigpitan ang Caller ID. Susunod na tawag: hindi pinaghihigpitan"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Naka-default sa hindi pinaghihigpitan ang Caller ID. Susunod na tawag: Pinaghihigpitan"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Naka-default na hindi pinaghihigpitan ang Caller ID. Susunod na tawag: Hindi pinaghihigpitan"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Hindi naprobisyon ang serbisyo."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Hindi mo mababago ang setting ng caller ID."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Nailipat sa <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ang data"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>, <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> patungong <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Anumang kalendaryo"</string>
     <string name="muted_by" msgid="91464083490094950">"Minu-mute ng <xliff:g id="THIRD_PARTY">%1$s</xliff:g> ang ilang tunog"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"May internal na problema sa iyong device, at maaaring hindi ito maging stable hanggang sa i-reset mo ang factory data."</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 98b1dd3..bc90fe1 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Arayan kimliği varsayılanları kısıtlanmıştır. Sonraki çağrı: Kısıtlanmamış"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Arayan kimliği varsayılanları kısıtlanmamıştır. Sonraki çağrı: Kısıtlanmış"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Arayan kimliği varsayılanları kısıtlanmamıştır. Sonraki çağrı: Kısıtlanmamış"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Hizmet sağlanamadı."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Arayanın kimliği ayarını değiştiremezsiniz."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Veriler şuraya aktarıldı: <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>-<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Tüm takvimler"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> bazı sesleri kapatıyor"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Cihazınızla ilgili dahili bir sorun oluştu ve fabrika verilerine sıfırlama işlemi gerçekleştirilene kadar kararsız çalışabilir."</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index d5e6bd8..8b5a321 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -73,6 +73,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Ідентиф. абонента за умовч. обмеж. Наст. дзвінок: не обмеж."</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Ідентиф. абонента за умовч. не обмеж. Наст. дзвінок: обмеж."</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Ідентиф. абонента за умовч. не обмеж. Наст. дзвінок: не обмежений"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Службу не ініціалізовано."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ви не можете змінювати налаштування ідентифікатора абонента."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Мобільний Інтернет переключено на <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1949,6 +1955,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"З усіх календарів"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> вимикає деякі звуки"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Через внутрішню помилку ваш пристрій може працювати нестабільно. Відновіть заводські налаштування."</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index b26b1c1..ae81328 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"‏کالر ID کی ڈیفالٹ ترتیب محدود کردہ ہے۔ اگلی کال: غیر محدود کردہ"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"‏کالر ID کی ڈیفالٹ ترتیب غیر محدود کردہ ہے۔ اگلی کال: محدود کردہ"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"‏کالر ID کی ڈیفالٹ ترتیب غیر محدود کردہ ہے۔ اگلی کال: غیر محدود کردہ"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"سروس فراہم نہیں کی گئی۔"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"‏آپ کالر ID کی ترتیبات تبدیل نہیں کر سکتے ہیں۔"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"ڈیٹا <xliff:g id="CARRIERDISPLAY">%s</xliff:g> پر سوئچ کیا گیا"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"، "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> تا <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>، <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"کوئی بھی کیلنڈر"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> کچھ آوازوں کو خاموش کر رہا ہے"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"آپ کے آلہ میں ایک داخلی مسئلہ ہے اور جب تک آپ فیکٹری ڈیٹا کو دوبارہ ترتیب نہیں دے دیتے ہیں، ہوسکتا ہے کہ یہ غیر مستحکم رہے۔"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 97e0448..54b51d9 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Qo‘ng‘iroq qiluvchi ma’lumotlari cheklangan. Keyingi qo‘ng‘iroq: cheklanmagan"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Qo‘ng‘iroq qiluvchi ma’lumotlari cheklanmagan. Keyingi qo‘ng‘iroq: cheklangan"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Qo‘ng‘iroq qiluvchi ma’lumotlari cheklanmagan. Keyingi qo‘ng‘iroq: cheklanmagan"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Xizmat ishalamaydi."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Qo‘ng‘iroq qiluvchining ID raqami sozlamasini o‘zgartirib bo‘lmaydi."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Internet <xliff:g id="CARRIERDISPLAY">%s</xliff:g> operatoriga almashtirildi"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Har qanday taqvim"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ayrim tovushlarni ovozsiz qilgan"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Qurilmangiz bilan bog‘liq ichki muammo mavjud. U zavod sozlamalari tiklanmaguncha barqaror ishlamasligi mumkin."</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 1ebff32..9dc1a30 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"Số gọi đến mặc định thành bị giới hạn. Cuộc gọi tiếp theo. Không bị giới hạn"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Số gọi đến mặc định thành không bị giới hạn. Cuộc gọi tiếp theo. Bị giới hạn"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Số gọi đến mặc định thành không bị giới hạn. Cuộc gọi tiếp theo. Không bị giới hạn"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Dịch vụ không được cấp phép."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Bạn không thể thay đổi cài đặt ID người gọi."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Đã chuyển dữ liệu sang <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> đến <xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Bất kỳ lịch nào"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> đang tắt một số âm thanh"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Đã xảy ra sự cố nội bộ với thiết bị của bạn và thiết bị có thể sẽ không ổn định cho tới khi bạn thiết lập lại dữ liệu ban đầu."</string>
diff --git a/core/res/res/values-watch-v36/config.xml b/core/res/res/values-watch-v36/config.xml
index 1143ae3..679dc70 100644
--- a/core/res/res/values-watch-v36/config.xml
+++ b/core/res/res/values-watch-v36/config.xml
@@ -16,5 +16,5 @@
 
 <resources>
     <dimen name="config_wearMaterial3_buttonCornerRadius">26dp</dimen>
-    <dimen name="config_bottomDialogCornerRadius">18dp</dimen>
+    <dimen name="config_wearMaterial3_bottomDialogCornerRadius">18dp</dimen>
 </resources>
diff --git a/core/res/res/values-watch-v36/dimens_material.xml b/core/res/res/values-watch-v36/dimens_material.xml
index ffa3b9c..7232786 100644
--- a/core/res/res/values-watch-v36/dimens_material.xml
+++ b/core/res/res/values-watch-v36/dimens_material.xml
@@ -31,4 +31,9 @@
     <!-- Opacity factor for disabled material3 widget -->
     <dimen name="disabled_alpha_device_default">0.12</dimen>
     <dimen name="primary_content_alpha_device_default">0.38</dimen>
+
+    <!--  values for material3 progress bar(progress indicator)  -->
+    <item name="progressbar_inner_radius_ratio" format="float" type="dimen">2.12</item>
+    <dimen name="progressbar_thickness">8dp</dimen>
+    <dimen name="progressbar_elevation">0.1dp</dimen>
 </resources>
diff --git a/core/res/res/values-watch-v36/styles_material.xml b/core/res/res/values-watch-v36/styles_material.xml
index 00f3f09..6e5ef68 100644
--- a/core/res/res/values-watch-v36/styles_material.xml
+++ b/core/res/res/values-watch-v36/styles_material.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2024 The Android Open Source Project
   ~
@@ -17,13 +17,13 @@
 
 <resources>
     <!--  Button Styles  -->
-    <!-- Material Button - Filled  -->
+    <!-- Material Button - Filled (primary colored) -->
     <style name="Widget.DeviceDefault.Button.Filled" parent="Widget.DeviceDefault.Button.WearMaterial3">
         <item name="android:background">@drawable/btn_background_material_filled</item>
         <item name="textAppearance">@style/TextAppearance.Widget.Button.Material.Filled</item>
     </style>
 
-    <!-- Material Button - Filled Tonal(Override system default button styles) -->
+    <!-- Material Button - Filled Tonal (Override system default button styles) -->
     <style name="Widget.DeviceDefault.Button.WearMaterial3">
         <item name="background">@drawable/btn_background_material_filled_tonal</item>
         <item name="textAppearance">@style/TextAppearance.Widget.Button.Material</item>
@@ -41,9 +41,19 @@
         <item name="gravity">center_vertical</item>
     </style>
 
+    <!-- Material Button - Outlined -->
+    <style name="Widget.DeviceDefault.Button.Outlined" parent="Widget.DeviceDefault.Button.WearMaterial3">
+        <item name="android:background">@drawable/btn_background_material_outlined</item>
+    </style>
+
+    <!-- Material Button - Text -->
+    <style name="Widget.DeviceDefault.Button.Text" parent="Widget.DeviceDefault.Button.WearMaterial3">
+        <item name="android:background">@drawable/btn_background_material_text</item>
+    </style>
+
     <!--  Text Styles  -->
     <!-- TextAppearance for Material Button - Filled  -->
-    <style name="TextAppearance.Widget.Button.Material.Filled" parent="TextAppearance.Widget.Button.Material">
+    <style name="TextAppearance.Widget.Button.Material.Filled">
         <item name="textColor">@color/btn_material_filled_content_color</item>
     </style>
 
@@ -57,7 +67,11 @@
     </style>
 
     <!--  AlertDialog Styles  -->
-    <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog" parent="Widget.DeviceDefault.Button">
+    <style name="AlertDialog.DeviceDefault.WearMaterial3">
+        <item name="layout">@layout/alert_dialog_wear_material3</item>
+    </style>
+
+    <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog.WearMaterial3" parent="Widget.DeviceDefault.Button">
         <item name="android:textSize">0sp</item>
         <item name="android:gravity">center</item>
         <item name="android:paddingStart">0dp</item>
@@ -65,18 +79,27 @@
         <item name="android:drawablePadding">0dp</item>
     </style>
 
-    <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog.Confirm" parent="Widget.DeviceDefault.Button.ButtonBar.AlertDialog">
+    <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog.WearMaterial3.Confirm">
         <!-- Use a ImageView as background -->
         <item name="background">@android:color/transparent</item>
         <item name="minWidth">@dimen/dialog_btn_confirm_width</item>
         <item name="minHeight">@dimen/dialog_btn_confirm_height</item>
     </style>
 
-    <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog.Negative" parent="Widget.DeviceDefault.Button.ButtonBar.AlertDialog">
+    <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog.WearMaterial3.Negative">
         <item name="background">@drawable/dialog_alert_button_negative</item>
         <item name="minWidth">@dimen/dialog_btn_negative_width</item>
         <item name="minHeight">@dimen/dialog_btn_negative_height</item>
         <item name="maxWidth">@dimen/dialog_btn_negative_width</item>
         <item name="maxHeight">@dimen/dialog_btn_negative_height</item>
     </style>
+
+    <!-- Wear Material3 Progress Bar style: progressed ring.-->
+    <style name="Widget.DeviceDefault.ProgressBar.WearMaterial3">
+        <item name="indeterminateOnly">false</item>
+        <item name="progressDrawable">@drawable/progress_ring_wear_material3</item>
+        <item name="minHeight">@dimen/progress_bar_height</item>
+        <item name="maxHeight">@dimen/progress_bar_height</item>
+        <item name="mirrorForRtl">true</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/core/res/res/values-watch/config_material.xml b/core/res/res/values-watch/config_material.xml
index 529f18b..8e9693a 100644
--- a/core/res/res/values-watch/config_material.xml
+++ b/core/res/res/values-watch/config_material.xml
@@ -33,4 +33,40 @@
     <!-- Style the scrollbars accoridngly. -->
     <drawable name="config_scrollbarThumbVertical">@drawable/scrollbar_vertical_thumb</drawable>
     <drawable name="config_scrollbarTrackVertical">@drawable/scrollbar_vertical_track</drawable>
+
+    <!--
+         Material motion physics configs
+         values from https://carbon.googleplex.com/wear-m3/pages/motion/tokens-and-specs/40358758-8b8c-4d46-9391-a8fff2d91197#15087d76-8a5a-4d52-a210-efc2cd479a66
+     -->
+    <!-- standard -->
+    <item name="config_motionStandardFastSpatialDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionStandardFastSpatialStiffness">1400</integer>
+    <item name="config_motionStandardFastEffectDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionStandardFastEffectStiffness">1400</integer>
+
+    <item name="config_motionStandardDefaultSpatialDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionStandardDefaultSpatialStiffness">500</integer>
+    <item name="config_motionStandardDefaultEffectDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionStandardDefaultEffectStiffness">500</integer>
+
+    <item name="config_motionStandardSlowSpatialDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionStandardSlowSpatialStiffness">260</integer>
+    <item name="config_motionStandardSlowEffectDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionStandardSlowEffectStiffness">260</integer>
+
+    <!-- expressive -->
+    <item name="config_motionExpressiveFastSpatialDamping" format="float" type="dimen">0.7</item>
+    <integer name="config_motionExpressiveFastSpatialStiffness">800</integer>
+    <item name="config_motionExpressiveFastEffectDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionExpressiveFastEffectStiffness">1400</integer>
+
+    <item name="config_motionExpressiveDefaultSpatialDamping" format="float" type="dimen">0.75</item>
+    <integer name="config_motionExpressiveDefaultSpatialStiffness">350</integer>
+    <item name="config_motionExpressiveDefaultEffectDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionExpressiveDefaultEffectStiffness">500</integer>
+
+    <item name="config_motionExpressiveSlowSpatialDamping" format="float" type="dimen">0.8</item>
+    <integer name="config_motionExpressiveSlowSpatialStiffness">200</integer>
+    <item name="config_motionExpressiveSlowEffectDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionExpressiveSlowEffectStiffness">260</integer>
 </resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 64c2f70..98f1898 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"默认不显示本机号码,但在下一次通话中显示"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"默认显示本机号码,但在下一次通话中不显示"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"默认显示本机号码,在下一次通话中也显示"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"未提供服务。"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"您无法更改来电显示设置。"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"移动数据网络已切换至“<xliff:g id="CARRIERDISPLAY">%s</xliff:g>”"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"、 "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>到<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g><xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"所有日历"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>正在将某些音效设为静音"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"您的设备内部出现了问题。如果不将设备恢复出厂设置,设备运行可能会不稳定。"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 53c5d15..a544b44 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"預設不顯示來電號碼,但下一通電話則顯示。"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"預設顯示來電號碼,但下一通電話不顯示。"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"預設顯示來電號碼,下一通電話也繼續顯示。"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"未提供此服務。"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"你無法更改來電顯示設定。"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"流動數據已切換至「<xliff:g id="CARRIERDISPLAY">%s</xliff:g>」"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"、 "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>至<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>,<xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"任何日曆"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>正將某些音效設為靜音"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"你裝置的系統發生問題,回復原廠設定後即可解決該問題。"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index cbd6bd2..68158ce 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"預設不顯示本機號碼,但下一通電話顯示。"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"預設顯示本機號碼,但下一通電話不顯示。"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"預設顯示本機號碼,下一通電話也繼續顯示。"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"無法提供此服務。"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"你無法變更來電顯示設定。"</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"行動數據已切換至「<xliff:g id="CARRIERDISPLAY">%s</xliff:g>」"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">"、 "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g>到<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>、<xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"任何日曆"</string>
     <string name="muted_by" msgid="91464083490094950">"「<xliff:g id="THIRD_PARTY">%1$s</xliff:g>」正在關閉部分音效"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"你的裝置發生內部問題,必須將裝置恢復原廠設定才能解除不穩定狀態。"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index f498fe2..9047a98 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -71,6 +71,12 @@
     <string name="CLIRDefaultOnNextCallOff" msgid="5036749051007098105">"i-ID yomshayeli ishintshela kokuvinjiwe. Ucingo olulandelayo: Aluvinjelwe"</string>
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"I-ID Yomshayeli ishintshela kokungavinjelwe. Ucingo olulandelayo: Luvinjelwe"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"I-ID Yomshayeli ishintshela kokungavinjelwe. Ucingo olulandelayo: Aluvinjelwe"</string>
+    <!-- no translation found for page_size_compat_apk_warning (2982396798449041224) -->
+    <skip />
+    <!-- no translation found for page_size_compat_elf_warning (6753874059564812651) -->
+    <skip />
+    <!-- no translation found for page_size_compat_apk_and_elf_warning (7628675779500605390) -->
+    <skip />
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Isevisi ayilungiselelwe."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ngeke ukwazi ukuguqul izilungiselelo zemininingwane yoshayayo."</string>
     <string name="auto_data_switch_title" msgid="3286350716870518297">"Ushintshele idatha ku-<xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
@@ -1947,6 +1953,7 @@
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g>, <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"U-<xliff:g id="START">%1$s</xliff:g> ukuya ku-<xliff:g id="END">%2$s</xliff:g>"</string>
+    <string name="zen_mode_trigger_summary_combined" msgid="6492381546327807669">"<xliff:g id="DAYS">%1$s</xliff:g>, <xliff:g id="TIMES">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Noma iyiphi ikhalenda"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> ithulisa eminye imisindo"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Kukhona inkinga yangaphakathi ngedivayisi yakho, futhi ingase ibe engazinzile kuze kube yilapho usetha kabusha yonke idatha."</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index f6590b1..238aca5 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4808,6 +4808,12 @@
         <!-- Whether the device should default to observe mode when this service is
              default or in the foreground. -->
         <attr name="shouldDefaultToObserveMode" format="boolean"/>
+        <!-- Whether this service should share the same AID routing priority as the role
+             owner. This package and the role owner must have the same signature, and the
+             role owner must opt into this behavior by using the property named by
+             {@link android.nfc.cardemulation.CardEmulation.PROPERTY_ALLOW_SHARED_ROLE_PRIORITY }
+             in the <code>&lt;application&rt;</code> tag. -->
+        <attr name="shareRolePriority" format="boolean"/>
     </declare-styleable>
 
     <!-- Use <code>offhost-apdu-service</code> as the root tag of the XML resource that
@@ -4835,6 +4841,7 @@
         <!-- Whether the device should default to observe mode when this service is
              default or in the foreground. -->
         <attr name="shouldDefaultToObserveMode"/>
+        <attr name="shareRolePriority"/>
     </declare-styleable>
 
     <!-- Specify one or more <code>aid-group</code> elements inside a
@@ -10563,6 +10570,32 @@
 
     <declare-styleable name="DateTimeView">
         <attr name="showRelative" format="boolean" />
+        <!-- For relative times, controls what kinds of times get disambiguation text.
+
+             The default value is "future".
+
+             Does nothing if showRelative=false.
+             -->
+        <attr name="relativeTimeDisambiguationText">
+            <!-- Times in the past will have extra clarifying text indicating that the time is in
+                 the past. For example, 1 minute ago is represented as "1m ago". If this flag is not
+                 set, times in the past are represented as just "1m". -->
+            <flag name="past" value="0x01" />
+            <!-- Times in the future will have extra clarifying text indicating that the time is in
+                 the future. For example, 1 minute in the future is represented as "in 1m". If this
+                 flag is not set, times in the future are represented as just "1m". -->
+            <flag name="future" value="0x02" />
+        </attr>
+        <!-- For relative times, sets the length of the time unit displayed (minutes, hours, etc.).
+
+             Does nothing if showRelative=false.
+             -->
+        <attr name="relativeTimeUnitDisplayLength">
+            <!-- The time unit will be shown as a short as possible (1 character if possible). -->
+            <enum name="shortest" value="0" />
+            <!-- The time unit will be shortened to a medium length (2-3 characters in general). -->
+            <enum name="medium" value="1" />
+        </attr>
     </declare-styleable>
 
     <declare-styleable name="ResolverDrawerLayout_LayoutParams">
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 7ef5394..00c59c6 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1867,8 +1867,12 @@
          16 KB device. 4 KB natives libs will be loaded app-compat mode if they are eligible.
          @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) -->
     <attr name="pageSizeCompat">
-        <enum name="enabled" value="5" />
-        <enum name="disabled" value="6" />
+        <!-- value for enabled must match with
+        ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED -->
+        <enum name="enabled" value="32" />
+        <!-- value for disabled must match with
+        ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED -->
+        <enum name="disabled" value="64" />
     </attr>
 
 
@@ -2568,6 +2572,8 @@
              against a development branch, in which case it will only work against
              the development builds. -->
         <attr name="minSdkVersion" format="integer|string" />
+        <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) -->
+        <attr name="minSdkVersionFull" format="string" />
         <!-- This is the SDK version number that the application is targeting.
              It is able to run on older versions (down to minSdkVersion), but
              was explicitly tested to work with the version specified here.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 38ebda7..45d0c90 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -815,6 +815,11 @@
          we rely on gravity to determine the effective orientation. -->
     <bool name="config_deskDockEnablesAccelerometer">true</bool>
 
+    <!-- Control for allowing the dock rotation functionality before provision like
+         when the SetupWizard is being shown to the user. This defaults to false to
+         preserve existing behavior. -->
+    <bool name="config_allowDockBeforeProvision">false</bool>
+
     <!-- Car dock behavior -->
 
     <!-- The number of degrees to rotate the display when the device is in a car dock.
@@ -2174,6 +2179,16 @@
         <item>com.android.location.fused</item>
     </string-array>
 
+    <!-- Package name providing population density location support. -->
+    <string name="config_populationDensityProviderPackageName" translatable="false">com.android.location.populationdensity</string>
+
+    <!-- Whether to enable population density provider overlay, which allows the population density provider to
+         be replaced by an app at run-time. When disabled, only the
+         config_populationDensityProviderPackageName package will be searched for a population density
+         provider, otherwise any system package is eligible. Anyone who wants to disable the overlay
+         mechanism can set it to false. -->
+    <bool name="config_enablePopulationDensityProviderOverlay" translatable="false">true</bool>
+
    <!-- Package name of the extension software fallback. -->
     <string name="config_extensionFallbackPackageName" translatable="false"></string>
 
@@ -2451,6 +2466,9 @@
     <string name="config_systemCallStreaming" translatable="false"></string>
     <!-- The name of the package that will hold the default retail demo role. -->
     <string name="config_defaultRetailDemo" translatable="false"></string>
+    <!-- The name of the package that will hold the default reserved for testing profile group
+         exclusivity role. -->
+    <string name="config_defaultReservedForTestingProfileGroupExclusivity" translatable="false">android.app.rolemultiuser.cts.app</string>
 
     <!-- The component name of the wear service class that will be started by the system server. -->
     <string name="config_wearServiceComponent" translatable="false"></string>
@@ -2779,6 +2797,9 @@
          If empty, logs "other" for all. -->
     <string-array name="config_loggable_dream_prefixes"></string-array>
 
+    <!-- Whether to enable glanceable hub features on this device. -->
+    <bool name="config_glanceableHubEnabled">false</bool>
+
     <!-- ComponentName of a dream to show whenever the system would otherwise have
          gone to sleep.  When the PowerManager is asked to go to sleep, it will instead
          try to start this dream if possible.  The dream should typically call startDozing()
@@ -6003,6 +6024,12 @@
     <!-- If true, show multiuser switcher by default unless the user specifically disables it. -->
     <bool name="config_showUserSwitcherByDefault">false</bool>
 
+    <!-- If true, user can change state of multiuser switcher. -->
+    <bool name="config_allowChangeUserSwitcherEnabled">true</bool>
+
+    <!-- If true, multiuser switcher would be automatically enabled when second user is created on the device. -->
+    <bool name="config_enableUserSwitcherUponUserCreation">true</bool>
+
     <!-- Set to true to make assistant show in front of the dream/screensaver. -->
     <bool name="config_assistantOnTopOfDream">false</bool>
 
@@ -7186,10 +7213,6 @@
          screen. -->
     <bool name="config_dragToMaximizeInDesktopMode">false</bool>
 
-    <!-- Whether showing the app handle is supported on this device.
-         If config_isDesktopModeSupported, then this has no effect -->
-    <bool name="config_enableAppHandle">false</bool>
-
     <!-- Frame rate compatibility value for Wallpaper
          FRAME_RATE_COMPATIBILITY_MIN (102) is used by default for lower power consumption -->
     <integer name="config_wallpaperFrameRateCompatibility">102</integer>
@@ -7245,4 +7268,8 @@
         POLICY_DOZE can also dim the screen unless parameter useNormalBrightnessForDoze of
         DreamService#setDozeScreenState requests an exception. -->
     <bool name="config_allowNormalBrightnessForDozePolicy">false</bool>
+
+    <!-- List of protected packages that require biometric authentication for modification
+         (Disable, force-stop or uninstalling updates). -->
+    <string-array name="config_biometric_protected_package_names" translatable="false" />
 </resources>
diff --git a/core/res/res/values/config_material.xml b/core/res/res/values/config_material.xml
index 64483f1..6034f9c 100644
--- a/core/res/res/values/config_material.xml
+++ b/core/res/res/values/config_material.xml
@@ -38,4 +38,41 @@
     <!-- Style the scrollbars accoridngly. -->
     <drawable name="config_scrollbarThumbVertical">@drawable/scrollbar_handle_material</drawable>
     <drawable name="config_scrollbarTrackVertical">@null</drawable>
+
+    <!--
+         Material motion physics configs
+         values from https://carbon.googleplex.com/google-material-3/pages/motion/how-it-works/1d566b15-2923-4e40-bd1e-25a867b96cbb#7520e861-2251-4ddb-af33-59df0d233d21
+    -->
+    <!-- standard -->
+    <item name="config_motionStandardFastSpatialDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionStandardFastSpatialStiffness">1400</integer>
+    <item name="config_motionStandardFastEffectDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionStandardFastEffectStiffness">3800</integer>
+
+    <item name="config_motionStandardDefaultSpatialDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionStandardDefaultSpatialStiffness">700</integer>
+    <item name="config_motionStandardDefaultEffectDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionStandardDefaultEffectStiffness">1600</integer>
+
+    <item name="config_motionStandardSlowSpatialDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionStandardSlowSpatialStiffness">300</integer>
+    <item name="config_motionStandardSlowEffectDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionStandardSlowEffectStiffness">800</integer>
+
+
+    <!-- expressive -->
+    <item name="config_motionExpressiveFastSpatialDamping" format="float" type="dimen">0.6</item>
+    <integer name="config_motionExpressiveFastSpatialStiffness">800</integer>
+    <item name="config_motionExpressiveFastEffectDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionExpressiveFastEffectStiffness">3800</integer>
+
+    <item name="config_motionExpressiveDefaultSpatialDamping" format="float" type="dimen">0.8</item>
+    <integer name="config_motionExpressiveDefaultSpatialStiffness">380</integer>
+    <item name="config_motionExpressiveDefaultEffectDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionExpressiveDefaultEffectStiffness">1600</integer>
+
+    <item name="config_motionExpressiveSlowSpatialDamping" format="float" type="dimen">0.8</item>
+    <integer name="config_motionExpressiveSlowSpatialStiffness">200</integer>
+    <item name="config_motionExpressiveSlowEffectDamping" format="float" type="dimen">1.0</item>
+    <integer name="config_motionExpressiveSlowEffectStiffness">800</integer>
 </resources>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index e8063a2..bb76b9f 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -488,8 +488,12 @@
     <java-symbol type="string" name="config_satellite_carrier_roaming_non_emergency_session_class" />
 
     <!-- Whether to show the system notification to users whenever there is a change
-     in the satellite availability state at the current location. -->
+         in the satellite availability state at the current location. -->
     <bool name="config_satellite_should_notify_availability">true</bool>
     <java-symbol type="bool" name="config_satellite_should_notify_availability" />
 
+    <!-- Whether to allow check message datagrams to be sent even when the satellite modem is in
+         not connected state. -->
+    <bool name="config_satellite_allow_check_message_in_not_connected">false</bool>
+    <java-symbol type="bool" name="config_satellite_allow_check_message_in_not_connected" />
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index c8df662..f53acbf 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -255,6 +255,19 @@
      This represents 16dp for the left margin + 40dp for the icon + 16dp for the right margin -->
     <dimen name="notification_2025_content_margin_start">72dp</dimen>
 
+    <!-- The margin on the start of the media actions, selected to ensure that action icons which
+     are visually 12x12 in a 24x24 drawable will align correctly with the text.  This means that
+     stock media action icons will align, but icons may be visually up to 20x20 and remain in-spec,
+     in which case they will protrude into the start column slightly.
+     72dp (content margin) - 8dp (media action padding) - 6dp (visual padding within drawable) -->
+    <dimen name="notification_2025_media_actions_margin_start">58dp</dimen>
+
+    <!-- The margin on the start of notification actions (2025 redesign version), to align them to
+     the rest of the notification content. Note that this can be set to 0 if the actions would not
+     fit with it included.
+     72dp (content margin) - 12dp (action padding) - 4dp (button inset) -->
+    <dimen name="notification_2025_actions_margin_start">56dp</dimen>
+
     <!-- The margin on the end of most content views (ignores the expander) -->
     <dimen name="notification_content_margin_end">16dp</dimen>
 
@@ -377,6 +390,9 @@
     <!-- the size of the notification close button -->
     <dimen name="notification_close_button_size">16dp</dimen>
 
+    <!-- Margin for all notification content -->
+    <dimen name="notification_2025_margin">16dp</dimen>
+
     <!-- Vertical margin for the headerless notification content, when content has 1 line -->
     <!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) -->
     <dimen name="notification_headerless_margin_oneline">16dp</dimen>
@@ -388,10 +404,19 @@
     <!-- The height of each of the 1 or 2 lines in the headerless notification template -->
     <dimen name="notification_headerless_line_height">24dp</dimen>
 
-    <!-- vertical margin for the headerless notification content -->
+    <!-- The minimum height of the notification content (even when there's only one line of text) -->
+    <dimen name="notification_2025_content_min_height">40dp</dimen>
+
+    <!-- Height of a headerless notification with one or two lines -->
+    <!-- 16 * 2 (margins) + 40 (min content height) = 72 (notification) -->
+    <dimen name="notification_2025_min_height">72dp</dimen>
+
+    <!-- Height of a headerless notification with one line -->
+    <!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) -->
     <dimen name="notification_headerless_min_height">56dp</dimen>
 
-    <!-- Height of a small notification in the status bar -->
+    <!-- Height of a small two-line notification -->
+    <!-- 20 * 2 (margins) + 24 * 2 (2 lines) = 88 (notification) -->
     <dimen name="notification_min_height">88dp</dimen>
 
     <!-- The width of the big icons in notifications. -->
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index e71277d..3b39a65 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -290,4 +290,7 @@
 
   <!-- View tag associating a view with its overridden id, to ensure valid recycling only. -->
   <item type="id" name="remote_views_override_id" />
+
+  <!-- View tag associating a view with its id for widget metrics. -->
+  <item type="id" name="remoteViewsMetricsId" />
 </resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index b0b87d1d..90f1b8a 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -133,9 +133,15 @@
     <public name="alternateLauncherLabels"/>
     <!-- @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) -->
     <public name="pageSizeCompat" />
+    <!-- @FlaggedApi(android.nfc.Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES) -->
+    <public name="shareRolePriority"/>
+    <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) -->
+    <public name="minSdkVersionFull"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01b60000">
+    <!-- @FlaggedApi(android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS) -->
+    <public name="remoteViewsMetricsId"/>
   </staging-public-group>
 
   <staging-public-group type="style" first-id="0x01b50000">
@@ -145,9 +151,36 @@
     <!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
          @hide @SystemApi -->
     <public name="config_systemDependencyInstaller" />
+    <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_PLATFORM_API_ENABLED)
+         @hide @SystemApi -->
+    <public name="config_defaultReservedForTestingProfileGroupExclusivity" />
   </staging-public-group>
 
   <staging-public-group type="dimen" first-id="0x01b30000">
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionStandardFastSpatialDamping"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionStandardFastEffectDamping"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionStandardDefaultSpatialDamping"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionStandardDefaultEffectDamping"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionStandardSlowSpatialDamping"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionStandardSlowEffectDamping"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionExpressiveFastSpatialDamping"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionExpressiveFastEffectDamping"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionExpressiveDefaultSpatialDamping"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionExpressiveDefaultEffectDamping"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionExpressiveSlowSpatialDamping"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionExpressiveSlowEffectDamping"/>
   </staging-public-group>
 
   <staging-public-group type="color" first-id="0x01b20000">
@@ -199,6 +232,30 @@
   </staging-public-group>
 
   <staging-public-group type="integer" first-id="0x01aa0000">
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionStandardFastSpatialStiffness"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionStandardFastEffectStiffness"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionStandardDefaultSpatialStiffness"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionStandardDefaultEffectStiffness"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionStandardSlowSpatialStiffness"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionStandardSlowEffectStiffness"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionExpressiveFastSpatialStiffness"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionExpressiveFastEffectStiffness"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionExpressiveDefaultSpatialStiffness"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionExpressiveDefaultEffectStiffness"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionExpressiveSlowSpatialStiffness"/>
+    <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
+    <public name="config_motionExpressiveSlowEffectStiffness"/>
   </staging-public-group>
 
   <staging-public-group type="transition" first-id="0x01a90000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 413f0c3..cfc3ddc 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -199,6 +199,21 @@
     <!-- Displayed to confirm to the user that caller ID will not be restricted on the next call or in general. -->
     <string name="CLIRDefaultOffNextCallOff">Caller ID defaults to not restricted. Next call: Not restricted</string>
 
+    <!-- Message displayed in dialog when APK is not 16 KB aligned. [CHAR LIMIT=NONE] -->
+    <string name="page_size_compat_apk_warning">This app isn’t 16 KB compatible. APK alignment check failed.
+        This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support.
+        For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; </string>
+
+    <!-- Message displayed in dialog when ELF is not 16 KB aligned. [CHAR LIMIT=NONE] -->
+    <string name="page_size_compat_elf_warning">This app isn’t 16 KB compatible. ELF alignment check failed.
+        This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support.
+        For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;</string>
+
+    <!-- Message displayed in dialog when APK and ELF are not 16 KB aligned. [CHAR LIMIT=NONE] -->
+    <string name="page_size_compat_apk_and_elf_warning">This app isn’t 16 KB compatible. APK and ELF alignment checks failed.
+        This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support.
+        For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;</string>
+
 
     <!-- Displayed to tell the user that caller ID is not provisioned for their SIM. -->
     <string name="serviceNotProvisioned">Service not provisioned.</string>
@@ -3120,6 +3135,86 @@
         in <xliff:g id="count">%d</xliff:g>y
     </string>
 
+    <!-- Phrase describing a time duration using minutes that is as short as possible, preferrably one character. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
+    <string name="duration_minutes_shortest_past">
+        <xliff:g id="count">%d</xliff:g>m ago
+    </string>
+
+    <!-- Phrase describing a time duration using hours that is as short as possible, preferrably one character. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
+    <string name="duration_hours_shortest_past">
+        <xliff:g id="count">%d</xliff:g>h ago
+    </string>
+
+    <!-- Phrase describing a time duration using days that is as short as possible, preferrably one character. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
+    <string name="duration_days_shortest_past">
+        <xliff:g example="1" id="count">%d</xliff:g>d ago
+    </string>
+
+    <!-- Phrase describing a time duration using years that is as short as possible, preferrably one character. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=14] -->
+    <string name="duration_years_shortest_past">
+        <xliff:g id="count">%d</xliff:g>y ago
+    </string>
+
+    <!-- Phrase describing a time duration using minutes that is a medium length, preferrably two or three characters. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=8] -->
+    <string name="duration_minutes_medium">
+        <xliff:g id="count">%d</xliff:g>min
+    </string>
+
+    <!-- Phrase describing a time duration using hours that is a medium length, preferrably two or three characters. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=8] -->
+    <string name="duration_hours_medium">
+        <xliff:g id="count">%d</xliff:g>hr
+    </string>
+
+    <!-- Phrase describing a time duration using days that is a medium length, preferrably two or three characters. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=8] -->
+    <string name="duration_days_medium">
+       <xliff:g id="count">%d</xliff:g>d
+    </string>
+
+    <!-- Phrase describing a time duration using years that is a medium length, preferrably two or three characters. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=8] -->
+    <string name="duration_years_medium">
+        <xliff:g id="count">%d</xliff:g>yr
+    </string>
+
+    <!-- Phrase describing a time duration using minutes that is a medium length, preferrably two or three characters. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+    <string name="duration_minutes_medium_future">
+        in <xliff:g id="count">%d</xliff:g>min
+    </string>
+
+    <!-- Phrase describing a time duration using hours that is a medium length, preferrably two or three characters. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+    <string name="duration_hours_medium_future">
+        in <xliff:g id="count">%d</xliff:g>hr
+    </string>
+
+    <!-- Phrase describing a time duration using days that is a medium length, preferrably two or three characters. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+    <string name="duration_days_medium_future">
+        in <xliff:g example="1" id="count">%d</xliff:g>d
+    </string>
+
+    <!-- Phrase describing a time duration using years that is a medium length, preferrably two or three characters. This version should be a future point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+    <string name="duration_years_medium_future">
+        in <xliff:g id="count">%d</xliff:g>yr
+    </string>
+
+    <!-- Phrase describing a time duration using minutes that is a medium length, preferrably two or three characters. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+    <string name="duration_minutes_medium_past">
+        <xliff:g id="count">%d</xliff:g>min ago
+    </string>
+
+    <!-- Phrase describing a time duration using hours that is a medium length, preferrably two or three characters. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+    <string name="duration_hours_medium_past">
+        <xliff:g id="count">%d</xliff:g>hr ago
+    </string>
+
+    <!-- Phrase describing a time duration using days that is a medium length, preferrably two or three characters. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+    <string name="duration_days_medium_past">
+        <xliff:g example="1" id="count">%d</xliff:g>d ago
+    </string>
+
+    <!-- Phrase describing a time duration using years that is a medium length, preferrably two or three characters. This version should be a past point in time. If the language needs a space in between the integer and the unit, please also integrate it in the string, but preferably it should not have a space in between.[CHAR LIMIT=18] -->
+    <string name="duration_years_medium_past">
+        <xliff:g id="count">%d</xliff:g>yr ago
+    </string>
+
     <!-- Phrase describing a relative time using minutes in the past that is not shown on the screen but used for accessibility. [CHAR LIMIT=NONE] -->
     <string name="duration_minutes_relative">{count, plural,
         =1 {# minute ago}
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index de71af0..bdf579d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1797,6 +1797,7 @@
   <java-symbol type="bool" name="config_customUserSwitchUi" />
   <java-symbol type="bool" name="config_canRemoveFirstAccount" />
   <java-symbol type="string" name="config_accountTypeToKeepFirstAccount" />
+  <java-symbol type="bool" name="config_allowDockBeforeProvision" />
   <java-symbol type="bool" name="config_deskDockEnablesAccelerometer" />
   <java-symbol type="bool" name="config_disableMenuKeyInLockScreen" />
   <java-symbol type="bool" name="config_enableCarDockHomeLaunch" />
@@ -2012,6 +2013,8 @@
   <java-symbol type="array" name="config_locationProviderPackageNames" />
   <java-symbol type="array" name="config_locationDriverAssistancePackageNames" />
   <java-symbol type="array" name="config_locationExtraPackageNames" />
+  <java-symbol type="string" name="config_populationDensityProviderPackageName" />
+  <java-symbol type="bool" name="config_enablePopulationDensityProviderOverlay" />
   <java-symbol type="array" name="config_testLocationProviders" />
   <java-symbol type="array" name="config_defaultNotificationVibePattern" />
   <java-symbol type="array" name="config_defaultNotificationVibeWaveform" />
@@ -2054,6 +2057,7 @@
   <java-symbol type="bool" name="config_allowTheaterModeWakeFromDock" />
   <java-symbol type="bool" name="config_allowTheaterModeWakeFromWindowLayout" />
   <java-symbol type="bool" name="config_keepDreamingWhenUnplugging" />
+  <java-symbol type="bool" name="config_glanceableHubEnabled" />
   <java-symbol type="integer" name="config_keyguardDrawnTimeout" />
   <java-symbol type="bool" name="config_goToSleepOnButtonPressTheaterMode" />
   <java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" />
@@ -2390,8 +2394,17 @@
   <java-symbol type="layout" name="notification_material_action_list" />
   <java-symbol type="layout" name="notification_material_action_tombstone" />
   <java-symbol type="layout" name="notification_2025_template_collapsed_base" />
+  <java-symbol type="layout" name="notification_2025_template_expanded_base" />
   <java-symbol type="layout" name="notification_2025_template_heads_up_base" />
   <java-symbol type="layout" name="notification_2025_template_header" />
+  <java-symbol type="layout" name="notification_2025_template_collapsed_messaging" />
+  <java-symbol type="layout" name="notification_2025_template_collapsed_media" />
+  <java-symbol type="layout" name="notification_2025_template_expanded_big_picture" />
+  <java-symbol type="layout" name="notification_2025_template_expanded_inbox" />
+  <java-symbol type="layout" name="notification_2025_template_expanded_media" />
+  <java-symbol type="layout" name="notification_2025_template_expanded_big_text" />
+  <java-symbol type="layout" name="notification_2025_template_expanded_messaging" />
+  <java-symbol type="layout" name="notification_2025_template_expanded_progress" />
   <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" />
@@ -2403,6 +2416,8 @@
   <java-symbol type="layout" name="notification_template_material_big_media" />
   <java-symbol type="layout" name="notification_template_material_big_text" />
   <java-symbol type="layout" name="notification_template_material_progress" />
+  <java-symbol type="layout" name="notification_template_material_messaging" />
+  <java-symbol type="layout" name="notification_template_material_big_messaging" />
   <java-symbol type="layout" name="notification_template_header" />
   <java-symbol type="layout" name="notification_material_media_action" />
   <java-symbol type="color" name="notification_progress_background_color" />
@@ -3309,6 +3324,11 @@
   <java-symbol type="string" name="language_selection_title" />
   <java-symbol type="string" name="search_language_hint" />
 
+  <!-- Strings for page size app compat dialog -->
+  <java-symbol type="string" name="page_size_compat_apk_warning" />
+  <java-symbol type="string" name="page_size_compat_elf_warning" />
+  <java-symbol type="string" name="page_size_compat_apk_and_elf_warning" />
+
   <!--  Work profile unlaunchable app alert dialog-->
   <java-symbol type="style" name="AlertDialogWithEmergencyButton"/>
   <java-symbol type="string" name="work_mode_emergency_call_button" />
@@ -3337,8 +3357,6 @@
   <java-symbol type="bool" name="config_strongAuthRequiredOnBoot" />
 
   <java-symbol type="layout" name="app_anr_dialog" />
-  <java-symbol type="layout" name="notification_template_material_messaging" />
-  <java-symbol type="layout" name="notification_template_material_big_messaging" />
 
   <java-symbol type="id" name="aerr_wait" />
 
@@ -3350,6 +3368,23 @@
   <java-symbol type="string" name="duration_hours_shortest_future" />
   <java-symbol type="string" name="duration_days_shortest_future" />
   <java-symbol type="string" name="duration_years_shortest_future" />
+  <java-symbol type="string" name="duration_minutes_shortest_past" />
+  <java-symbol type="string" name="duration_hours_shortest_past" />
+  <java-symbol type="string" name="duration_days_shortest_past" />
+  <java-symbol type="string" name="duration_years_shortest_past" />
+
+  <java-symbol type="string" name="duration_minutes_medium" />
+  <java-symbol type="string" name="duration_hours_medium" />
+  <java-symbol type="string" name="duration_days_medium" />
+  <java-symbol type="string" name="duration_years_medium" />
+  <java-symbol type="string" name="duration_minutes_medium_future" />
+  <java-symbol type="string" name="duration_hours_medium_future" />
+  <java-symbol type="string" name="duration_days_medium_future" />
+  <java-symbol type="string" name="duration_years_medium_future" />
+  <java-symbol type="string" name="duration_minutes_medium_past" />
+  <java-symbol type="string" name="duration_hours_medium_past" />
+  <java-symbol type="string" name="duration_days_medium_past" />
+  <java-symbol type="string" name="duration_years_medium_past" />
 
   <java-symbol type="string" name="duration_minutes_relative" />
   <java-symbol type="string" name="duration_hours_relative" />
@@ -3440,6 +3475,8 @@
   <!-- Notifications: CallStyle -->
   <java-symbol type="layout" name="notification_template_material_call" />
   <java-symbol type="layout" name="notification_template_material_big_call" />
+  <java-symbol type="layout" name="notification_2025_template_collapsed_call" />
+  <java-symbol type="layout" name="notification_2025_template_expanded_call" />
   <java-symbol type="string" name="call_notification_answer_action" />
   <java-symbol type="string" name="call_notification_answer_video_action" />
   <java-symbol type="string" name="call_notification_decline_action" />
@@ -3464,6 +3501,7 @@
 
   <java-symbol type="bool" name="config_supportPreRebootSecurityLogs" />
 
+  <java-symbol type="dimen" name="notification_2025_actions_margin_start"/>
   <java-symbol type="id" name="notification_action_list_margin_target" />
   <java-symbol type="dimen" name="notification_actions_padding_start"/>
   <java-symbol type="dimen" name="notification_actions_collapsed_priority_width"/>
@@ -4091,6 +4129,7 @@
   <java-symbol type="layout" name="notification_template_messaging_text_message" />
   <java-symbol type="layout" name="notification_template_messaging_image_message" />
   <java-symbol type="layout" name="notification_template_messaging_group" />
+  <java-symbol type="layout" name="notification_2025_messaging_group" />
   <java-symbol type="id" name="message_text" />
   <java-symbol type="id" name="message_name" />
   <java-symbol type="id" name="message_icon" />
@@ -4621,9 +4660,11 @@
   <java-symbol type="dimen" name="conversation_icon_container_top_padding" />
   <java-symbol type="dimen" name="conversation_icon_container_top_padding_small_avatar" />
   <java-symbol type="layout" name="notification_template_material_conversation" />
+  <java-symbol type="layout" name="notification_2025_template_conversation" />
   <java-symbol type="dimen" name="button_padding_horizontal_material" />
   <java-symbol type="dimen" name="button_inset_horizontal_material" />
   <java-symbol type="layout" name="conversation_face_pile_layout" />
+  <java-symbol type="layout" name="notification_2025_conversation_face_pile_layout" />
   <java-symbol type="string" name="unread_convo_overflow" />
   <java-symbol type="drawable" name="conversation_badge_background" />
   <java-symbol type="drawable" name="conversation_badge_ring" />
@@ -4694,6 +4735,8 @@
 
   <!-- If true, show multiuser switcher by default unless the user specifically disables it. -->
   <java-symbol type="bool" name="config_showUserSwitcherByDefault" />
+  <java-symbol type="bool" name="config_allowChangeUserSwitcherEnabled" />
+  <java-symbol type="bool" name="config_enableUserSwitcherUponUserCreation" />
 
   <!-- Set to true to make assistant show in front of the dream/screensaver. -->
   <java-symbol type="bool" name="config_assistantOnTopOfDream"/>
@@ -5110,7 +5153,6 @@
   <java-symbol type="layout" name="notification_expand_button"/>
   <java-symbol type="id" name="close_button" />
   <java-symbol type="layout" name="notification_close_button"/>
-  <java-symbol type="id" name="notification_buttons_column" />
 
   <java-symbol type="bool" name="config_supportsMicToggle" />
   <java-symbol type="bool" name="config_supportsCamToggle" />
@@ -5722,9 +5764,6 @@
        screen. -->
   <java-symbol type="bool" name="config_dragToMaximizeInDesktopMode" />
 
-  <!-- Whether showing the app handle is supported on this device -->
-  <java-symbol type="bool" name="config_enableAppHandle" />
-
   <!-- Frame rate compatibility value for Wallpaper -->
   <java-symbol type="integer" name="config_wallpaperFrameRateCompatibility" />
 
@@ -5802,5 +5841,38 @@
   <!-- Style for Wear Material3 Button. Will only be used for sdk 36 or above. -->
   <java-symbol type="style" name="Widget.DeviceDefault.Button.WearMaterial3" />
 
+  <!-- Style for Wear Material3 AlertDialog. Will only be used for sdk 36 or above. -->
+  <java-symbol type="style" name="AlertDialog.DeviceDefault.WearMaterial3" />
+
   <java-symbol type="bool" name="config_allowNormalBrightnessForDozePolicy" />
+
+  <!-- Material motion spec config tokens -->
+  <java-symbol type="integer" name="config_motionStandardFastSpatialStiffness"/>
+  <java-symbol type="integer" name="config_motionStandardFastEffectStiffness"/>
+  <java-symbol type="integer" name="config_motionStandardDefaultSpatialStiffness"/>
+  <java-symbol type="integer" name="config_motionStandardDefaultEffectStiffness"/>
+  <java-symbol type="integer" name="config_motionStandardSlowSpatialStiffness"/>
+  <java-symbol type="integer" name="config_motionStandardSlowEffectStiffness"/>
+  <java-symbol type="integer" name="config_motionExpressiveFastSpatialStiffness"/>
+  <java-symbol type="integer" name="config_motionExpressiveFastEffectStiffness"/>
+  <java-symbol type="integer" name="config_motionExpressiveDefaultSpatialStiffness"/>
+  <java-symbol type="integer" name="config_motionExpressiveDefaultEffectStiffness"/>
+  <java-symbol type="integer" name="config_motionExpressiveSlowSpatialStiffness"/>
+  <java-symbol type="integer" name="config_motionExpressiveSlowEffectStiffness"/>
+  <java-symbol type="dimen" name="config_motionStandardFastSpatialDamping"/>
+  <java-symbol type="dimen" name="config_motionStandardFastEffectDamping"/>
+  <java-symbol type="dimen" name="config_motionStandardDefaultSpatialDamping"/>
+  <java-symbol type="dimen" name="config_motionStandardDefaultEffectDamping"/>
+  <java-symbol type="dimen" name="config_motionStandardSlowSpatialDamping"/>
+  <java-symbol type="dimen" name="config_motionStandardSlowEffectDamping"/>
+  <java-symbol type="dimen" name="config_motionExpressiveFastSpatialDamping"/>
+  <java-symbol type="dimen" name="config_motionExpressiveFastEffectDamping"/>
+  <java-symbol type="dimen" name="config_motionExpressiveDefaultSpatialDamping"/>
+  <java-symbol type="dimen" name="config_motionExpressiveDefaultEffectDamping"/>
+  <java-symbol type="dimen" name="config_motionExpressiveSlowSpatialDamping"/>
+  <java-symbol type="dimen" name="config_motionExpressiveSlowEffectDamping"/>
+
+  <!-- List of protected packages that require biometric authentication for modification -->
+  <java-symbol type="array" name="config_biometric_protected_package_names" />
+
 </resources>
diff --git a/core/tests/FileSystemUtilsTest/Android.bp b/core/tests/FileSystemUtilsTest/Android.bp
index ae04aa4..962ff3c 100644
--- a/core/tests/FileSystemUtilsTest/Android.bp
+++ b/core/tests/FileSystemUtilsTest/Android.bp
@@ -17,14 +17,40 @@
     default_team: "trendy_team_android_kernel",
 }
 
-cc_library {
-    name: "libpunchtest",
+cc_defaults {
+    name: "libpunch_defaults",
     stl: "none",
     host_supported: true,
     srcs: ["jni/android_test_jni_source.cpp"],
     header_libs: ["jni_headers"],
 }
 
+cc_library {
+    name: "libpunchtest",
+    defaults: ["libpunch_defaults"],
+}
+
+cc_library {
+    name: "libpunchtest_4kb",
+    defaults: ["libpunch_defaults"],
+    ldflags: ["-z max-page-size=0x1000"],
+}
+
+android_test_helper_app {
+    name: "app_with_4kb_elf",
+    srcs: ["app_with_4kb_elf/src/**/*.java"],
+    manifest: "app_with_4kb_elf/app_with_4kb_elf.xml",
+    compile_multilib: "64",
+    jni_libs: [
+        "libpunchtest_4kb",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "platform-test-annotations",
+    ],
+    use_embedded_native_libs: true,
+}
+
 android_test_helper_app {
     name: "embedded_native_libs_test_app",
     srcs: ["apk_embedded_native_libs/src/**/*.java"],
@@ -72,6 +98,7 @@
     device_common_data: [
         ":embedded_native_libs_test_app",
         ":extract_native_libs_test_app",
+        ":app_with_4kb_elf",
     ],
     test_suites: ["general-tests"],
     test_config: "AndroidTest.xml",
diff --git a/core/tests/FileSystemUtilsTest/AndroidTest.xml b/core/tests/FileSystemUtilsTest/AndroidTest.xml
index 27f49b2..651a7ca 100644
--- a/core/tests/FileSystemUtilsTest/AndroidTest.xml
+++ b/core/tests/FileSystemUtilsTest/AndroidTest.xml
@@ -22,6 +22,7 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="embedded_native_libs_test_app.apk" />
         <option name="test-file-name" value="extract_native_libs_test_app.apk" />
+        <option name="test-file-name" value="app_with_4kb_elf.apk" />
     </target_preparer>
 
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml
new file mode 100644
index 0000000..b9d6d4d
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_elf.xml
@@ -0,0 +1,37 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.test.pagesizecompat">
+    <application
+        android:extractNativeLibs="false"
+        android:pageSizeCompat="enabled">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".MainActivity"
+                  android:exported="true"
+                  android:process=":NewProcess">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.test.pagesizecompat"/>
+</manifest>
\ No newline at end of file
diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java
new file mode 100644
index 0000000..893f9cd
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/MainActivity.java
@@ -0,0 +1,60 @@
+/*
+ * 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.test.pagesizecompat;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.VisibleForTesting;
+
+public class MainActivity extends Activity {
+
+    static {
+        System.loadLibrary("punchtest_4kb");
+    }
+
+    @VisibleForTesting
+    static final String INTENT_TYPE = "android.test.pagesizecompat.EMBEDDED_4KB_LIB_LOADED";
+
+    @VisibleForTesting
+    static final String KEY_OPERAND_1 = "OP1";
+
+    @VisibleForTesting
+    static final String KEY_OPERAND_2 = "OP2";
+
+    @VisibleForTesting
+    static final String KEY_RESULT = "RESULT";
+
+    @Override
+    public void onCreate(Bundle savedOnstanceState) {
+        super.onCreate(savedOnstanceState);
+
+        Intent received =  getIntent();
+        int op1 = received.getIntExtra(KEY_OPERAND_1, -1);
+        int op2 = received.getIntExtra(KEY_OPERAND_2, -1);
+        int result = add(op1, op2);
+
+        // Send broadcast so that test can know app has launched and lib is loaded
+        // attach result which has been fetched from JNI lib
+        Intent intent = new Intent(INTENT_TYPE);
+        intent.putExtra(KEY_RESULT, result);
+        sendBroadcast(intent);
+    }
+
+    private native int add(int op1, int op2);
+}
diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java
new file mode 100644
index 0000000..9cbe414a
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/src/android/test/pagesizecompat/PageSizeCompatTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.test.pagesizecompat;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class PageSizeCompatTest {
+
+    @Test
+    public void testPageSizeCompat_embedded4KbLib() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        CountDownLatch receivedSignal = new CountDownLatch(1);
+
+        // Test app is expected to receive this and perform addition of operands using ELF
+        // loaded in compat mode on 16 KB device
+        int op1 = 48;
+        int op2 = 75;
+        IntentFilter intentFilter = new IntentFilter(MainActivity.INTENT_TYPE);
+        BroadcastReceiver broadcastReceiver =
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        receivedSignal.countDown();
+                        int result = intent.getIntExtra(MainActivity.KEY_RESULT, 1000);
+                        Assert.assertEquals(result, op1 + op2);
+
+                    }
+                };
+        context.registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED);
+
+        Intent launchIntent =
+                context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
+        launchIntent.putExtra(MainActivity.KEY_OPERAND_1, op1);
+        launchIntent.putExtra(MainActivity.KEY_OPERAND_2, op2);
+        context.startActivity(launchIntent);
+
+        Assert.assertTrue("Failed to launch app", receivedSignal.await(10, TimeUnit.SECONDS));
+    }
+}
diff --git a/core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp b/core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp
index 2a5ba81..5bcd0b6 100644
--- a/core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp
+++ b/core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp
@@ -22,6 +22,12 @@
     return op1 + op2;
 }
 
+extern "C" JNIEXPORT
+jint JNICALL Java_android_test_pagesizecompat_MainActivity_add(JNIEnv*, jclass, jint op1, jint op2)
+{
+    return op1 + op2;
+}
+
 // This will be called from extract_native_libs_test_app
 extern "C" JNIEXPORT
 jint JNICALL Java_android_test_extract_MainActivity_subtract(JNIEnv*, jclass, jint op1, jint op2) {
diff --git a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
index 77802e5..aed907a 100644
--- a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
+++ b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
@@ -47,4 +47,13 @@
         assertTrue(isPackageInstalled(appPackage));
         runDeviceTests(appPackage, appPackage + "." + testName);
     }
+
+    @Test
+    @AppModeFull
+    public void runAppWith4KbLib_overrideCompatMode() throws DeviceNotAvailableException {
+        String appPackage = "android.test.pagesizecompat";
+        String testName = "PageSizeCompatTest";
+        assertTrue(isPackageInstalled(appPackage));
+        runDeviceTests(appPackage, appPackage + "." + testName);
+    }
 }
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_ltr.9.png b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_ltr.9.png
new file mode 100644
index 0000000..c7e937c
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_ltr.9.png
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_tr.9.png b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_tr.9.png
new file mode 100644
index 0000000..a3cff98
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_tr.9.png
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/power_other_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/power_other_24.xml
new file mode 100644
index 0000000..c9fb53e
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/power_other_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M720,600L720,520L800,520Q817,520 828.5,531.5Q840,543 840,560Q840,577 828.5,588.5Q817,600 800,600L720,600ZM720,760L720,680L800,680Q817,680 828.5,691.5Q840,703 840,720Q840,737 828.5,748.5Q817,760 800,760L720,760ZM560,800Q527,800 503.5,776.5Q480,753 480,720L400,720L400,560L480,560Q480,527 503.5,503.5Q527,480 560,480L680,480L680,800L560,800ZM280,680Q214,680 167,633Q120,586 120,520Q120,454 167,407Q214,360 280,360L340,360Q365,360 382.5,342.5Q400,325 400,300Q400,275 382.5,257.5Q365,240 340,240L200,240Q183,240 171.5,228.5Q160,217 160,200Q160,183 171.5,171.5Q183,160 200,160L340,160Q398,160 439,201Q480,242 480,300Q480,358 439,399Q398,440 340,440L280,440Q247,440 223.5,463.5Q200,487 200,520Q200,553 223.5,576.5Q247,600 280,600L360,600L360,680L280,680Z" />
+</vector>
\ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_off_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_off_24.xml
new file mode 100644
index 0000000..ca94825
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_off_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M280,920Q247,920 223.5,896.5Q200,873 200,840L200,120Q200,87 223.5,63.5Q247,40 280,40L680,40Q713,40 736.5,63.5Q760,87 760,120L760,840Q760,873 736.5,896.5Q713,920 680,920L280,920ZM280,800L280,840Q280,840 280,840Q280,840 280,840L680,840Q680,840 680,840Q680,840 680,840L680,800L280,800ZM280,720L680,240L280,720ZM280,160L680,160L680,120Q680,120 680,120Q680,120 680,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,160L280,120Q280,120 280,120Q280,120 280,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,800L280,800L280,840Q280,840 280,840Q280,840 280,840L280,840Q280,840 280,840Q280,840 280,840L280,800Z" />
+</vector>
\ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_on_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_on_24.xml
new file mode 100644
index 0000000..48f990c
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_on_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M280,920Q247,920 223.5,896.5Q200,873 200,840L200,120Q200,87 223.5,63.5Q247,40 280,40L680,40Q713,40 736.5,63.5Q760,87 760,120L760,840Q760,873 736.5,896.5Q713,920 680,920L280,920ZM280,800L280,840Q280,840 280,840Q280,840 280,840L680,840Q680,840 680,840Q680,840 680,840L680,800L280,800ZM280,720L680,720L680,240L280,240L280,720ZM280,160L680,160L680,120Q680,120 680,120Q680,120 680,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,160L280,120Q280,120 280,120Q280,120 280,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,800L280,800L280,840Q280,840 280,840Q280,840 280,840L280,840Q280,840 280,840Q280,840 280,840L280,800Z" />
+</vector>
\ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
index be0e135..e1f4623 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,46 +13,127 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:minHeight="?android:attr/listPreferredItemHeightSmall"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingTop="8dp"
-    android:paddingBottom="8dp">
+    android:orientation="vertical">
 
-    <ImageView
-        android:id="@+id/icon"
-        android:layout_width="wrap_content"
+    <LinearLayout
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginEnd="8dp"
-        android:paddingBottom="8dp"/>
+        android:minHeight="?android:attr/listPreferredItemHeightSmall"
+        android:orientation="horizontal"
+        android:paddingBottom="8dp"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:paddingTop="8dp">
 
-    <TextView
-        android:id="@+id/title"
-        android:layout_width="0dp"
-        android:layout_weight="1"
-        android:layout_height="wrap_content"
-        android:textAppearance="@style/TextAppearanceBody"/>
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginEnd="8dp"
+            android:paddingBottom="8dp" />
 
-    <TextView
-        android:id="@+id/value1"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="8dp"
-        android:gravity="right"
-        android:maxLines="1"
-        android:textAppearance="@style/TextAppearanceBody"/>
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:textAppearance="@style/TextAppearanceBody" />
 
-    <TextView
-        android:id="@+id/value2"
-        android:layout_width="76dp"
+        <TextView
+            android:id="@+id/value1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:gravity="right"
+            android:maxLines="1"
+            android:textAppearance="@style/TextAppearanceBody" />
+
+        <TextView
+            android:id="@+id/value2"
+            android:layout_width="76dp"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:maxLines="1"
+            android:textAppearance="@style/TextAppearanceBody" />
+    </LinearLayout>
+
+    <TableLayout
+        android:id="@+id/table"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:gravity="right"
-        android:maxLines="1"
-        android:textAppearance="@style/TextAppearanceBody"/>
+        android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:layout_marginStart="50dp"
+        android:stretchColumns="1,2,3,4">
+
+        <TableRow android:background="#EEFFEE">
+            <LinearLayout
+                style="@style/TableCell.Start"
+                android:layout_width="65dp">
+            <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:padding="3dip"
+                    android:text="State"
+                    android:textStyle="bold" />
+            </LinearLayout>
+
+            <RelativeLayout style="@style/TableCell.Inner">
+                <ImageView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerInParent="true"
+                    android:src="@drawable/screen_on_24"
+                    android:tint="@color/battery_consumer_slice_icon" />
+            </RelativeLayout>
+
+            <RelativeLayout style="@style/TableCell.Inner">
+                <ImageView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerInParent="true"
+                    android:src="@drawable/screen_off_24"
+                    android:tint="@color/battery_consumer_slice_icon" />
+            </RelativeLayout>
+
+            <RelativeLayout style="@style/TableCell.Inner">
+                <ImageView
+                    android:id="@+id/screen_on_24_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerInParent="true"
+                    android:src="@drawable/screen_on_24"
+                    android:tint="@color/battery_consumer_slice_icon" />
+                <ImageView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_toRightOf="@id/screen_on_24_icon"
+                    android:src="@drawable/power_other_24"
+                    android:tint="@color/battery_consumer_slice_icon" />
+            </RelativeLayout>
+
+            <RelativeLayout style="@style/TableCell.End">
+                <ImageView
+                    android:id="@+id/screen_off_24_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerInParent="true"
+                    android:src="@drawable/screen_off_24"
+                    android:tint="@color/battery_consumer_slice_icon" />
+                <ImageView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_toRightOf="@id/screen_off_24_icon"
+                    android:src="@drawable/power_other_24"
+                    android:tint="@color/battery_consumer_slice_icon" />
+            </RelativeLayout>
+        </TableRow>
+
+        <View
+            android:layout_height="1dip"
+            android:background="#000000" />
+    </TableLayout>
 </LinearLayout>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml
index 987de6b..b88425a 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml
@@ -14,16 +14,30 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/swipe_refresh"
-    android:paddingTop="?attr/actionBarSize"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:fitsSystemWindows="true">
 
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/list_view"
+    <androidx.appcompat.widget.Toolbar
+        android:id="@+id/toolbar"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"/>
+        android:layout_height="?attr/actionBarSize"
+        android:background="?attr/colorPrimary"
+        android:elevation="4dp"
+        android:theme="@style/ThemeOverlay.AppCompat.ActionBar" />
 
-</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+        android:id="@+id/swipe_refresh"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/list_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+
+    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+</LinearLayout>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_slices_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_slices_layout.xml
new file mode 100644
index 0000000..642c0de
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_slices_layout.xml
@@ -0,0 +1,87 @@
+<?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.
+  -->
+
+<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout style="@style/TableCell.Start">
+        <TextView
+            android:id="@+id/procState"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+
+    <LinearLayout
+        style="@style/TableCell.Inner"
+        android:orientation="vertical">
+        <TextView
+            android:id="@+id/power_b_on"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right" />
+        <TextView
+            android:id="@+id/duration_b_on"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right" />
+    </LinearLayout>
+
+    <LinearLayout
+        style="@style/TableCell.Inner"
+        android:orientation="vertical">
+        <TextView
+            android:id="@+id/power_b_off"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right" />
+        <TextView
+            android:id="@+id/duration_b_off"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right" />
+    </LinearLayout>
+
+    <LinearLayout
+        style="@style/TableCell.Inner"
+        android:orientation="vertical">
+        <TextView
+            android:id="@+id/power_c_on"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right" />
+        <TextView
+            android:id="@+id/duration_c_on"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right" />
+    </LinearLayout>
+
+    <LinearLayout
+        style="@style/TableCell.End"
+        android:orientation="vertical">
+        <TextView
+            android:id="@+id/power_c_off"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right" />
+        <TextView
+            android:id="@+id/duration_c_off"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right" />
+    </LinearLayout>
+</TableRow>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
index 2d276a5..46d8f04 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
@@ -17,13 +17,12 @@
 <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/swipe_refresh"
-    android:paddingTop="?attr/actionBarSize"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
 
     <LinearLayout
         android:orientation="vertical"
-        android:paddingTop="?attr/actionBarSize"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
 
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
index 6cc70bd..1dc288a 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
@@ -18,4 +18,5 @@
 <resources>
     <color name="battery_consumer_bg_power_profile">#ffffff</color>
     <color name="battery_consumer_bg_energy_consumption">#fff5eb</color>
+    <color name="battery_consumer_slice_icon">#aaaaaa</color>
 </resources>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml
index fa30b2c..a298cc9 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml
@@ -17,11 +17,9 @@
   -->
 
 <resources>
-    <style name="Theme" parent="Theme.MaterialComponents.Light">
+    <style name="Theme" parent="Theme.MaterialComponents.Light.NoActionBar">
         <item name="colorPrimary">#34a853</item>
-        <item name="android:windowActionBar">true</item>
-        <item name="android:windowNoTitle">false</item>
-        <item name="android:windowDrawsSystemBarBackgrounds">false</item>
+        <item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
     </style>
 
     <style name="LoadTestCardView" parent="Widget.MaterialComponents.CardView">
@@ -32,4 +30,25 @@
         <item name="android:textColor">#000000</item>
         <item name="android:textSize">18sp</item>
     </style>
-</resources>
\ No newline at end of file
+
+    <style name="TableCell">
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:padding">4dp</item>
+    </style>
+
+    <style name="TableCell.Start" parent="TableCell">
+        <item name="android:background">@drawable/border_ltr</item>
+    </style>
+
+    <style name="TableCell.Inner" parent="TableCell">
+        <item name="android:background">@drawable/border_tr</item>
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_weight">1</item>
+    </style>
+
+    <style name="TableCell.End" parent="TableCell">
+        <item name="android:background">@drawable/border_tr</item>
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_weight">1</item>
+    </style>
+</resources>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
index f691a1b..35175a7 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
@@ -31,22 +31,16 @@
     public static final String UID_BATTERY_CONSUMER_ID_PREFIX = "APP|";
     public static final String AGGREGATE_BATTERY_CONSUMER_ID = "SYS|";
 
-    enum EntryType {
-        UID_TOTAL_POWER,
-        UID_POWER_PROFILE,
-        UID_POWER_PROFILE_PROCESS_STATE,
-        UID_POWER_ENERGY_CONSUMPTION,
-        UID_POWER_ENERGY_PROCESS_STATE,
-        UID_POWER_CUSTOM,
-        UID_DURATION,
+    public enum EntryType {
         DEVICE_TOTAL_POWER,
-        DEVICE_POWER_MODELED,
+        DEVICE_POWER,
         DEVICE_POWER_ENERGY_CONSUMPTION,
         DEVICE_POWER_CUSTOM,
         DEVICE_DURATION,
+        UID,
     }
 
-    enum ConsumerType {
+    public enum ConsumerType {
         UID_BATTERY_CONSUMER,
         DEVICE_POWER_COMPONENT,
     }
@@ -56,34 +50,38 @@
         public String title;
         public double value1;
         public double value2;
+        public List<Slice> slices;
+    }
+
+    public static class Slice {
+        public int powerState;
+        public int screenState;
+        public int processState;
+        public double powerMah;
+        public long durationMs;
     }
 
     private BatteryConsumerInfoHelper.BatteryConsumerInfo mBatteryConsumerInfo;
     private final List<Entry> mEntries = new ArrayList<>();
 
     public BatteryConsumerData(Context context,
-            List<BatteryUsageStats> batteryUsageStatsList, String batteryConsumerId) {
+            BatteryUsageStats batteryUsageStats, String batteryConsumerId) {
         switch (getConsumerType(batteryConsumerId)) {
             case UID_BATTERY_CONSUMER:
-                populateForUidBatteryConsumer(context, batteryUsageStatsList, batteryConsumerId);
+                populateForUidBatteryConsumer(context, batteryUsageStats, batteryConsumerId);
                 break;
             case DEVICE_POWER_COMPONENT:
-                populateForAggregateBatteryConsumer(context, batteryUsageStatsList);
+                populateForAggregateBatteryConsumer(context, batteryUsageStats);
                 break;
         }
     }
 
-    private void populateForUidBatteryConsumer(
-            Context context, List<BatteryUsageStats> batteryUsageStatsList,
+    private void populateForUidBatteryConsumer(Context context, BatteryUsageStats batteryUsageStats,
             String batteryConsumerId) {
-        BatteryUsageStats batteryUsageStats = batteryUsageStatsList.get(0);
-        BatteryUsageStats modeledBatteryUsageStats = batteryUsageStatsList.get(1);
         BatteryConsumer requestedBatteryConsumer = getRequestedBatteryConsumer(batteryUsageStats,
                 batteryConsumerId);
-        BatteryConsumer requestedModeledBatteryConsumer = getRequestedBatteryConsumer(
-                modeledBatteryUsageStats, batteryConsumerId);
 
-        if (requestedBatteryConsumer == null || requestedModeledBatteryConsumer == null) {
+        if (requestedBatteryConsumer == null) {
             mBatteryConsumerInfo = null;
             return;
         }
@@ -92,118 +90,95 @@
                 batteryUsageStats, batteryConsumerId, context.getPackageManager());
 
         double[] totalPowerByComponentMah = new double[BatteryConsumer.POWER_COMPONENT_COUNT];
-        double[] totalModeledPowerByComponentMah =
-                new double[BatteryConsumer.POWER_COMPONENT_COUNT];
         long[] totalDurationByComponentMs = new long[BatteryConsumer.POWER_COMPONENT_COUNT];
-        final int customComponentCount =
-                requestedBatteryConsumer.getCustomPowerComponentCount();
+        final int customComponentCount = requestedBatteryConsumer.getCustomPowerComponentCount();
         double[] totalCustomPowerByComponentMah = new double[customComponentCount];
 
         computeTotalPower(batteryUsageStats, totalPowerByComponentMah);
-        computeTotalPower(modeledBatteryUsageStats, totalModeledPowerByComponentMah);
         computeTotalPowerForCustomComponent(batteryUsageStats, totalCustomPowerByComponentMah);
         computeTotalDuration(batteryUsageStats, totalDurationByComponentMs);
 
-        if (isPowerProfileModelsOnly(requestedBatteryConsumer)) {
-            addEntry("Consumed", EntryType.UID_TOTAL_POWER,
-                    requestedBatteryConsumer.getConsumedPower(),
-                    batteryUsageStats.getAggregateBatteryConsumer(
-                            BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
-                            .getConsumedPower());
-        } else {
-            addEntry("Consumed (PowerStats)", EntryType.UID_TOTAL_POWER,
-                    requestedBatteryConsumer.getConsumedPower(),
-                    batteryUsageStats.getAggregateBatteryConsumer(
-                            BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
-                            .getConsumedPower());
-            addEntry("Consumed (PowerProfile)", EntryType.UID_TOTAL_POWER,
-                    requestedModeledBatteryConsumer.getConsumedPower(),
-                    modeledBatteryUsageStats.getAggregateBatteryConsumer(
-                            BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
-                            .getConsumedPower());
+        Entry totalsEntry = addEntry("Consumed", EntryType.UID,
+                requestedBatteryConsumer.getConsumedPower(),
+                batteryUsageStats.getAggregateBatteryConsumer(
+                                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                        .getConsumedPower());
+        addSlices(totalsEntry, requestedBatteryConsumer, BatteryConsumer.POWER_COMPONENT_BASE);
+        for (Slice slice : totalsEntry.slices) {
+            slice.powerMah = requestedBatteryConsumer.getConsumedPower(
+                    new BatteryConsumer.Dimensions(BatteryConsumer.POWER_COMPONENT_ANY,
+                            slice.processState, slice.screenState, slice.powerState));
         }
 
         for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
+            if (component == BatteryConsumer.POWER_COMPONENT_BASE) {
+                continue;
+            }
             final String metricTitle = getPowerMetricTitle(component);
-            final int powerModel = requestedBatteryConsumer.getPowerModel(component);
-            if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE
-                    || powerModel == BatteryConsumer.POWER_MODEL_UNDEFINED) {
-                addEntry(metricTitle, EntryType.UID_POWER_PROFILE,
-                        requestedBatteryConsumer.getConsumedPower(component),
+            double consumedPower = requestedBatteryConsumer.getConsumedPower(component);
+            if (consumedPower != 0) {
+                Entry entry = addEntry(metricTitle, EntryType.UID, consumedPower,
                         totalPowerByComponentMah[component]);
-                addProcessStateEntries(metricTitle, EntryType.UID_POWER_PROFILE_PROCESS_STATE,
-                        requestedBatteryConsumer, component);
-            } else {
-                addEntry(metricTitle + " (PowerStats)", EntryType.UID_POWER_ENERGY_CONSUMPTION,
-                        requestedBatteryConsumer.getConsumedPower(component),
-                        totalPowerByComponentMah[component]);
-                addProcessStateEntries(metricTitle, EntryType.UID_POWER_ENERGY_PROCESS_STATE,
-                        requestedBatteryConsumer, component);
-                addEntry(metricTitle + " (PowerProfile)", EntryType.UID_POWER_PROFILE,
-                        requestedModeledBatteryConsumer.getConsumedPower(component),
-                        totalModeledPowerByComponentMah[component]);
-                addProcessStateEntries(metricTitle, EntryType.UID_POWER_PROFILE_PROCESS_STATE,
-                        requestedModeledBatteryConsumer, component);
+                addSlices(entry, requestedBatteryConsumer, component);
             }
         }
 
         for (int component = 0; component < customComponentCount; component++) {
-            final String name = requestedBatteryConsumer.getCustomPowerComponentName(
-                    BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
-            addEntry(name + " (PowerStats)", EntryType.UID_POWER_CUSTOM,
-                    requestedBatteryConsumer.getConsumedPowerForCustomComponent(
-                            BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component),
-                    totalCustomPowerByComponentMah[component]
-            );
-        }
-
-        for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
-            final String metricTitle = getTimeMetricTitle(component);
-            addEntry(metricTitle, EntryType.UID_DURATION,
-                    requestedBatteryConsumer.getUsageDurationMillis(component),
-                    totalDurationByComponentMs[component]
-            );
+            int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component;
+            final String name = requestedBatteryConsumer.getCustomPowerComponentName(componentId);
+            double consumedPower = requestedBatteryConsumer.getConsumedPower(componentId);
+            if (consumedPower != 0) {
+                Entry entry = addEntry(name, EntryType.UID, consumedPower,
+                        totalCustomPowerByComponentMah[component]);
+                addSlices(entry, requestedBatteryConsumer, componentId);
+            }
         }
 
         mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo(batteryUsageStats,
                 batteryConsumerId, context.getPackageManager());
     }
 
-    private void addProcessStateEntries(String metricTitle, EntryType entryType,
-            BatteryConsumer batteryConsumer, int component) {
+    private void addSlices(Entry entry, BatteryConsumer batteryConsumer, int component) {
         final BatteryConsumer.Key[] keys = batteryConsumer.getKeys(component);
         if (keys == null || keys.length <= 1) {
             return;
         }
 
+        boolean hasProcStateData = false;
         for (BatteryConsumer.Key key : keys) {
-            String label;
-            switch (key.processState) {
-                case BatteryConsumer.PROCESS_STATE_FOREGROUND:
-                    label = "foreground";
-                    break;
-                case BatteryConsumer.PROCESS_STATE_BACKGROUND:
-                    label = "background";
-                    break;
-                case BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE:
-                    label = "FGS";
-                    break;
-                case BatteryConsumer.PROCESS_STATE_CACHED:
-                    label = "cached";
-                    break;
-                default:
-                    continue;
+            if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                hasProcStateData = true;
+                break;
             }
-            addEntry(metricTitle + " \u2022 " + label, entryType,
-                    batteryConsumer.getConsumedPower(key), 0);
         }
+
+        ArrayList<Slice> slices = new ArrayList<>();
+        for (BatteryConsumer.Key key : keys) {
+            if (hasProcStateData && key.processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                continue;
+            }
+
+            double powerMah = batteryConsumer.getConsumedPower(key);
+            long durationMs = batteryConsumer.getUsageDurationMillis(key);
+
+            if (powerMah == 0 && durationMs == 0) {
+                continue;
+            }
+
+            Slice slice = new Slice();
+            slice.powerState = key.powerState;
+            slice.screenState = key.screenState;
+            slice.processState = key.processState;
+            slice.powerMah = powerMah;
+            slice.durationMs = durationMs;
+
+            slices.add(slice);
+        }
+        entry.slices = slices;
     }
 
     private void populateForAggregateBatteryConsumer(Context context,
-            List<BatteryUsageStats> batteryUsageStatsList) {
-        BatteryUsageStats batteryUsageStats = batteryUsageStatsList.get(0);
-        BatteryUsageStats modeledBatteryUsageStats = batteryUsageStatsList.get(1);
-
+            BatteryUsageStats batteryUsageStats) {
         final BatteryConsumer deviceBatteryConsumer =
                 batteryUsageStats.getAggregateBatteryConsumer(
                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
@@ -211,46 +186,18 @@
                 batteryUsageStats.getAggregateBatteryConsumer(
                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
 
-        BatteryConsumer modeledDeviceBatteryConsumer =
-                modeledBatteryUsageStats.getAggregateBatteryConsumer(
-                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
-        BatteryConsumer modeledAppsBatteryConsumer =
-                modeledBatteryUsageStats.getAggregateBatteryConsumer(
-                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
-
-        if (isPowerProfileModelsOnly(deviceBatteryConsumer)) {
-            addEntry("Consumed", EntryType.DEVICE_TOTAL_POWER,
-                    deviceBatteryConsumer.getConsumedPower(),
-                    appsBatteryConsumer.getConsumedPower());
-        } else {
-            addEntry("Consumed (PowerStats)", EntryType.DEVICE_TOTAL_POWER,
-                    deviceBatteryConsumer.getConsumedPower(),
-                    appsBatteryConsumer.getConsumedPower());
-            addEntry("Consumed (PowerProfile)", EntryType.DEVICE_TOTAL_POWER,
-                    modeledDeviceBatteryConsumer.getConsumedPower(),
-                    modeledAppsBatteryConsumer.getConsumedPower());
-        }
+        addEntry("Consumed", EntryType.DEVICE_TOTAL_POWER,
+                deviceBatteryConsumer.getConsumedPower(),
+                appsBatteryConsumer.getConsumedPower());
 
         mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo(batteryUsageStats,
                 AGGREGATE_BATTERY_CONSUMER_ID, context.getPackageManager());
 
-
         for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
             final String metricTitle = getPowerMetricTitle(component);
-            final int powerModel = deviceBatteryConsumer.getPowerModel(component);
-            if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE
-                    || powerModel == BatteryConsumer.POWER_MODEL_UNDEFINED) {
-                addEntry(metricTitle, EntryType.DEVICE_POWER_MODELED,
-                        deviceBatteryConsumer.getConsumedPower(component),
-                        appsBatteryConsumer.getConsumedPower(component));
-            } else {
-                addEntry(metricTitle + " (PowerStats)", EntryType.DEVICE_POWER_ENERGY_CONSUMPTION,
-                        deviceBatteryConsumer.getConsumedPower(component),
-                        appsBatteryConsumer.getConsumedPower(component));
-                addEntry(metricTitle + " (PowerProfile)", EntryType.DEVICE_POWER_MODELED,
-                        modeledDeviceBatteryConsumer.getConsumedPower(component),
-                        modeledAppsBatteryConsumer.getConsumedPower(component));
-            }
+            addEntry(metricTitle, EntryType.DEVICE_POWER,
+                    deviceBatteryConsumer.getConsumedPower(component),
+                    appsBatteryConsumer.getConsumedPower(component));
         }
 
         final int customComponentCount =
@@ -258,10 +205,10 @@
         for (int component = 0; component < customComponentCount; component++) {
             final String name = deviceBatteryConsumer.getCustomPowerComponentName(
                     BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
-            addEntry(name + " (PowerStats)", EntryType.DEVICE_POWER_CUSTOM,
-                    deviceBatteryConsumer.getConsumedPowerForCustomComponent(
+            addEntry(name, EntryType.DEVICE_POWER_CUSTOM,
+                    deviceBatteryConsumer.getConsumedPower(
                             BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component),
-                    appsBatteryConsumer.getConsumedPowerForCustomComponent(
+                    appsBatteryConsumer.getConsumedPower(
                             BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component));
         }
 
@@ -272,17 +219,6 @@
         }
     }
 
-    private boolean isPowerProfileModelsOnly(BatteryConsumer batteryConsumer) {
-        for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
-            final int powerModel = batteryConsumer.getPowerModel(component);
-            if (powerModel != BatteryConsumer.POWER_MODEL_POWER_PROFILE
-                    && powerModel != BatteryConsumer.POWER_MODEL_UNDEFINED) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     private BatteryConsumer getRequestedBatteryConsumer(BatteryUsageStats batteryUsageStats,
             String batteryConsumerId) {
         for (UidBatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
@@ -352,13 +288,14 @@
         }
     }
 
-    private void addEntry(String title, EntryType entryType, double value1, double value2) {
+    private Entry addEntry(String title, EntryType entryType, double value1, double value2) {
         Entry entry = new Entry();
         entry.title = title;
         entry.entryType = entryType;
         entry.value1 = value1;
         entry.value2 = value2;
         mEntries.add(entry);
+        return entry;
     }
 
     public BatteryConsumerInfoHelper.BatteryConsumerInfo getBatteryConsumerInfo() {
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
index c6d71c3..37d6b17 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
@@ -24,6 +24,8 @@
 
 import androidx.annotation.NonNull;
 
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 import java.util.List;
 
 class BatteryConsumerInfoHelper {
@@ -76,6 +78,8 @@
         String packageWithHighestDrain = uidBatteryConsumer.getPackageWithHighestDrain();
         if (uid == Process.ROOT_UID) {
             info.label = "<root>";
+        } else if (uid < Process.FIRST_APPLICATION_UID) {
+            info.label = makeSystemUidLabel(uid);
         } else {
             String[] packages = packageManager.getPackagesForUid(uid);
             String primaryPackageName = null;
@@ -134,6 +138,23 @@
         return info;
     }
 
+    private static CharSequence makeSystemUidLabel(int uid) {
+        for (Field field : Process.class.getDeclaredFields()) {
+            final int modifiers = field.getModifiers();
+            if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+                    && field.getType().equals(int.class) && field.getName().endsWith("_UID")) {
+                try {
+                    if (uid == field.getInt(null)) {
+                        String label = field.getName();
+                        return label.substring(0, label.lastIndexOf("_UID"));
+                    }
+                } catch (IllegalAccessException ignored) {
+                }
+            }
+        }
+        return null;
+    }
+
     private static BatteryConsumerInfo makeAggregateBatteryConsumerInfo(
             BatteryUsageStats batteryUsageStats) {
         BatteryConsumerInfo info = new BatteryConsumerInfo();
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
index 4469168..3699690 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
@@ -30,8 +30,8 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import androidx.activity.ComponentActivity;
 import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
 import androidx.loader.app.LoaderManager;
 import androidx.loader.content.Loader;
 import androidx.recyclerview.widget.LinearLayoutManager;
@@ -50,7 +50,7 @@
  * Picker, showing a sorted lists of applications and other types of entities consuming power.
  * Opens BatteryStatsViewerActivity upon item selection.
  */
-public class BatteryConsumerPickerActivity extends ComponentActivity {
+public class BatteryConsumerPickerActivity extends AppCompatActivity {
     private static final String PREF_SELECTED_BATTERY_CONSUMER = "batteryConsumerId";
     private static final int BATTERY_STATS_REFRESH_RATE_MILLIS = 60 * 1000;
     private static final String FORCE_FRESH_STATS = "force_fresh_stats";
@@ -68,6 +68,7 @@
         super.onCreate(icicle);
 
         setContentView(R.layout.battery_consumer_picker_layout);
+        setSupportActionBar(findViewById(R.id.toolbar));
 
         mSwipeRefreshLayout = findViewById(R.id.swipe_refresh);
         mSwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_green_light);
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
index e165c49..35021316 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
@@ -17,14 +17,18 @@
 package com.android.frameworks.core.batterystatsviewer;
 
 import android.content.Context;
+import android.os.BatteryConsumer;
 import android.os.BatteryStatsManager;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.Bundle;
+import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
+import android.widget.TableLayout;
+import android.widget.TableRow;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -40,6 +44,7 @@
 
 import com.android.settingslib.utils.AsyncLoaderCompat;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
@@ -63,7 +68,16 @@
     private SwipeRefreshLayout mSwipeRefreshLayout;
     private View mCardView;
     private View mEmptyView;
-    private List<BatteryUsageStats> mBatteryUsageStats;
+    private BatteryUsageStats mBatteryUsageStats;
+
+    private static SparseArray<String> sProcStateNames = new SparseArray<>();
+    static {
+        sProcStateNames.put(BatteryConsumer.PROCESS_STATE_UNSPECIFIED, "-");
+        sProcStateNames.put(BatteryConsumer.PROCESS_STATE_FOREGROUND, "FG");
+        sProcStateNames.put(BatteryConsumer.PROCESS_STATE_BACKGROUND, "BG");
+        sProcStateNames.put(BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, "FGS");
+        sProcStateNames.put(BatteryConsumer.PROCESS_STATE_CACHED, "Cached");
+    }
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -122,7 +136,7 @@
     }
 
     private static class BatteryUsageStatsLoader extends
-            AsyncLoaderCompat<List<BatteryUsageStats>> {
+            AsyncLoaderCompat<BatteryUsageStats> {
         private final BatteryStatsManager mBatteryStatsManager;
         private final boolean mForceFreshStats;
 
@@ -133,51 +147,44 @@
         }
 
         @Override
-        public List<BatteryUsageStats> loadInBackground() {
+        public BatteryUsageStats loadInBackground() {
             final int maxStatsAgeMs = mForceFreshStats ? 0 : BATTERY_STATS_REFRESH_RATE_MILLIS;
             final BatteryUsageStatsQuery queryDefault =
                     new BatteryUsageStatsQuery.Builder()
-                            .includePowerModels()
                             .includeProcessStateData()
+                            .includeScreenStateData()
+                            .includePowerStateData()
                             .setMaxStatsAgeMs(maxStatsAgeMs)
                             .build();
-            final BatteryUsageStatsQuery queryPowerProfileModeledOnly =
-                    new BatteryUsageStatsQuery.Builder()
-                            .powerProfileModeledOnly()
-                            .includePowerModels()
-                            .includeProcessStateData()
-                            .setMaxStatsAgeMs(maxStatsAgeMs)
-                            .build();
-            return mBatteryStatsManager.getBatteryUsageStats(
-                    List.of(queryDefault, queryPowerProfileModeledOnly));
+            return mBatteryStatsManager.getBatteryUsageStats(queryDefault);
         }
 
         @Override
-        protected void onDiscardResult(List<BatteryUsageStats> result) {
+        protected void onDiscardResult(BatteryUsageStats result) {
         }
     }
 
     private class BatteryUsageStatsLoaderCallbacks
-            implements LoaderCallbacks<List<BatteryUsageStats>> {
+            implements LoaderCallbacks<BatteryUsageStats> {
         @NonNull
         @Override
-        public Loader<List<BatteryUsageStats>> onCreateLoader(int id, Bundle args) {
+        public Loader<BatteryUsageStats> onCreateLoader(int id, Bundle args) {
             return new BatteryUsageStatsLoader(BatteryStatsViewerActivity.this,
                     args.getBoolean(FORCE_FRESH_STATS));
         }
 
         @Override
-        public void onLoadFinished(@NonNull Loader<List<BatteryUsageStats>> loader,
-                List<BatteryUsageStats> batteryUsageStats) {
+        public void onLoadFinished(@NonNull Loader<BatteryUsageStats> loader,
+                BatteryUsageStats batteryUsageStats) {
             onBatteryUsageStatsLoaded(batteryUsageStats);
         }
 
         @Override
-        public void onLoaderReset(@NonNull Loader<List<BatteryUsageStats>> loader) {
+        public void onLoaderReset(@NonNull Loader<BatteryUsageStats> loader) {
         }
     }
 
-    private void onBatteryUsageStatsLoaded(List<BatteryUsageStats> batteryUsageStats) {
+    private void onBatteryUsageStatsLoaded(BatteryUsageStats batteryUsageStats) {
         mBatteryUsageStats = batteryUsageStats;
         onBatteryStatsDataLoaded();
     }
@@ -238,10 +245,21 @@
     private static class BatteryStatsDataAdapter extends
             RecyclerView.Adapter<BatteryStatsDataAdapter.ViewHolder> {
         public static class ViewHolder extends RecyclerView.ViewHolder {
+            public static class SliceViewHolder {
+                public TableRow tableRow;
+                public int procState;
+                public int powerState;
+                public int screenState;
+                public TextView powerTextView;
+                public TextView durationTextView;
+            }
+
             public ImageView iconImageView;
             public TextView titleTextView;
             public TextView value1TextView;
             public TextView value2TextView;
+            public TableLayout table;
+            public List<SliceViewHolder> slices = new ArrayList<>();
 
             ViewHolder(View itemView) {
                 super(itemView);
@@ -250,6 +268,40 @@
                 titleTextView = itemView.findViewById(R.id.title);
                 value1TextView = itemView.findViewById(R.id.value1);
                 value2TextView = itemView.findViewById(R.id.value2);
+                table = itemView.findViewById(R.id.table);
+
+                for (int i = 0; i < sProcStateNames.size(); i++) {
+                    int procState = sProcStateNames.keyAt(i);
+                    slices.add(createSliceViewHolder(procState,
+                            BatteryConsumer.POWER_STATE_BATTERY,
+                            BatteryConsumer.SCREEN_STATE_ON,
+                            R.id.power_b_on, R.id.duration_b_on));
+                    slices.add(createSliceViewHolder(procState,
+                            BatteryConsumer.POWER_STATE_BATTERY,
+                            BatteryConsumer.SCREEN_STATE_OTHER,
+                            R.id.power_b_off, R.id.duration_b_off));
+                    slices.add(createSliceViewHolder(procState,
+                            BatteryConsumer.POWER_STATE_OTHER,
+                            BatteryConsumer.SCREEN_STATE_ON,
+                            R.id.power_c_on, R.id.duration_c_on));
+                    slices.add(createSliceViewHolder(procState,
+                            BatteryConsumer.POWER_STATE_OTHER,
+                            BatteryConsumer.SCREEN_STATE_OTHER,
+                            R.id.power_c_off, R.id.duration_c_off));
+                }
+            }
+
+            private SliceViewHolder createSliceViewHolder(int procState, int powerState,
+                    int screenState, int powerTextViewResId, int durationTextViewResId) {
+                TableRow powerRow = table.findViewWithTag("procstate" + procState);
+                SliceViewHolder svh = new SliceViewHolder();
+                svh.tableRow = powerRow;
+                svh.procState = procState;
+                svh.powerState = powerState;
+                svh.screenState = screenState;
+                svh.powerTextView = powerRow.findViewById(powerTextViewResId);
+                svh.durationTextView = powerRow.findViewById(durationTextViewResId);
+                return svh;
             }
         }
 
@@ -269,62 +321,32 @@
         @Override
         public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
             LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
-            View itemView = layoutInflater.inflate(R.layout.battery_consumer_entry_layout, parent,
-                    false);
+            ViewGroup itemView = (ViewGroup) layoutInflater.inflate(
+                    R.layout.battery_consumer_entry_layout, parent, false);
+            TableLayout table = itemView.findViewById(R.id.table);
+            int offset = 1;     // Skip header
+            for (int i = 0; i < sProcStateNames.size(); i++) {
+                View powerRow = layoutInflater.inflate(R.layout.battery_consumer_slices_layout,
+                        itemView, false);
+                ((TextView) powerRow.findViewById(R.id.procState))
+                        .setText(sProcStateNames.valueAt(i));
+                powerRow.setTag("procstate" + sProcStateNames.keyAt(i));
+                table.addView(powerRow, offset++);
+            }
+
             return new ViewHolder(itemView);
         }
 
         @Override
         public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
             BatteryConsumerData.Entry entry = mEntries.get(position);
-
             switch (entry.entryType) {
-                case UID_TOTAL_POWER:
+                case UID:
                     setTitleIconAndBackground(viewHolder, entry.title,
-                            R.drawable.gm_sum_24, 0);
+                            R.drawable.gm_energy_24, 0);
                     setPowerText(viewHolder.value1TextView, entry.value1);
                     setProportionText(viewHolder.value2TextView, entry);
-                    break;
-                case UID_POWER_PROFILE:
-                    setTitleIconAndBackground(viewHolder, entry.title,
-                            R.drawable.gm_calculate_24,
-                            R.color.battery_consumer_bg_power_profile);
-                    setPowerText(viewHolder.value1TextView, entry.value1);
-                    setProportionText(viewHolder.value2TextView, entry);
-                    break;
-                case UID_POWER_PROFILE_PROCESS_STATE:
-                    setTitleIconAndBackground(viewHolder, "    " + entry.title,
-                            R.drawable.gm_calculate_24,
-                            R.color.battery_consumer_bg_power_profile);
-                    setPowerText(viewHolder.value1TextView, entry.value1);
-                    viewHolder.value2TextView.setVisibility(View.INVISIBLE);
-                    break;
-                case UID_POWER_ENERGY_CONSUMPTION:
-                    setTitleIconAndBackground(viewHolder, entry.title,
-                            R.drawable.gm_energy_24,
-                            R.color.battery_consumer_bg_energy_consumption);
-                    setPowerText(viewHolder.value1TextView, entry.value1);
-                    setProportionText(viewHolder.value2TextView, entry);
-                    break;
-                case UID_POWER_ENERGY_PROCESS_STATE:
-                    setTitleIconAndBackground(viewHolder, "    " + entry.title,
-                            R.drawable.gm_energy_24,
-                            R.color.battery_consumer_bg_energy_consumption);
-                    setPowerText(viewHolder.value1TextView, entry.value1);
-                    viewHolder.value2TextView.setVisibility(View.INVISIBLE);
-                    break;
-                case UID_POWER_CUSTOM:
-                    setTitleIconAndBackground(viewHolder, entry.title,
-                            R.drawable.gm_energy_24,
-                            R.color.battery_consumer_bg_energy_consumption);
-                    setPowerText(viewHolder.value1TextView, entry.value1);
-                    setProportionText(viewHolder.value2TextView, entry);
-                    break;
-                case UID_DURATION:
-                    setTitleIconAndBackground(viewHolder, entry.title,
-                            R.drawable.gm_timer_24, 0);
-                    setDurationText(viewHolder.value1TextView, (long) entry.value1);
-                    setProportionText(viewHolder.value2TextView, entry);
+                    bindSlices(viewHolder, entry);
                     break;
                 case DEVICE_TOTAL_POWER:
                     setTitleIconAndBackground(viewHolder, entry.title,
@@ -332,27 +354,13 @@
                     setPowerText(viewHolder.value1TextView, entry.value1);
                     setPowerText(viewHolder.value2TextView, entry.value2);
                     break;
-                case DEVICE_POWER_MODELED:
+                case DEVICE_POWER:
                     setTitleIconAndBackground(viewHolder, entry.title,
                             R.drawable.gm_calculate_24,
                             R.color.battery_consumer_bg_power_profile);
                     setPowerText(viewHolder.value1TextView, entry.value1);
                     setPowerText(viewHolder.value2TextView, entry.value2);
                     break;
-                case DEVICE_POWER_ENERGY_CONSUMPTION:
-                    setTitleIconAndBackground(viewHolder, entry.title,
-                            R.drawable.gm_energy_24,
-                            R.color.battery_consumer_bg_energy_consumption);
-                    setPowerText(viewHolder.value1TextView, entry.value1);
-                    setPowerText(viewHolder.value2TextView, entry.value2);
-                    break;
-                case DEVICE_POWER_CUSTOM:
-                    setTitleIconAndBackground(viewHolder, entry.title,
-                            R.drawable.gm_energy_24,
-                            R.color.battery_consumer_bg_energy_consumption);
-                    setPowerText(viewHolder.value1TextView, entry.value1);
-                    setPowerText(viewHolder.value2TextView, entry.value2);
-                    break;
                 case DEVICE_DURATION:
                     setTitleIconAndBackground(viewHolder, entry.title,
                             R.drawable.gm_timer_24, 0);
@@ -362,6 +370,65 @@
             }
         }
 
+        private void bindSlices(ViewHolder viewHolder, BatteryConsumerData.Entry entry) {
+            if (entry.slices == null || entry.slices.isEmpty()) {
+                viewHolder.table.setVisibility(View.GONE);
+                return;
+            }
+            viewHolder.table.setVisibility(View.VISIBLE);
+
+            boolean[] procStateRowPopulated =
+                    new boolean[BatteryConsumer.PROCESS_STATE_COUNT];
+            for (BatteryConsumerData.Slice s : entry.slices) {
+                if (s.powerMah != 0 || s.durationMs != 0) {
+                    procStateRowPopulated[s.processState] = true;
+                }
+            }
+
+            for (ViewHolder.SliceViewHolder sliceViewHolder : viewHolder.slices) {
+                BatteryConsumerData.Slice slice = null;
+                for (BatteryConsumerData.Slice s : entry.slices) {
+                    if (s.powerState == sliceViewHolder.powerState
+                            && s.screenState == sliceViewHolder.screenState
+                            && s.processState == sliceViewHolder.procState) {
+                        slice = s;
+                        break;
+                    }
+                }
+                if (!procStateRowPopulated[sliceViewHolder.procState]) {
+                    sliceViewHolder.tableRow.setVisibility(View.GONE);
+                } else {
+                    sliceViewHolder.tableRow.setVisibility(View.VISIBLE);
+
+                    if (slice != null && (slice.powerMah != 0 || slice.durationMs != 0)) {
+                        sliceViewHolder.powerTextView.setText(
+                                String.format(Locale.getDefault(), "%.1f", slice.powerMah));
+                    } else {
+                        sliceViewHolder.powerTextView.setText(null);
+                    }
+
+                    if (slice != null && slice.durationMs != 0) {
+                        sliceViewHolder.durationTextView.setVisibility(View.VISIBLE);
+                        String timeString;
+                        if (slice.durationMs < MILLIS_IN_MINUTE) {
+                            timeString = String.format(Locale.getDefault(), "%ds",
+                                    slice.durationMs / 1000);
+                        } else if (slice.durationMs < 60 * MILLIS_IN_MINUTE) {
+                            timeString = String.format(Locale.getDefault(), "%dm %ds",
+                                    slice.durationMs / MILLIS_IN_MINUTE,
+                                    (slice.durationMs % MILLIS_IN_MINUTE) / 1000);
+                        } else {
+                            timeString = String.format(Locale.getDefault(), "%dm",
+                                    slice.durationMs / MILLIS_IN_MINUTE);
+                        }
+                        sliceViewHolder.durationTextView.setText(timeString);
+                    } else {
+                        sliceViewHolder.durationTextView.setVisibility(View.GONE);
+                    }
+                }
+            }
+        }
+
         private void setTitleIconAndBackground(ViewHolder viewHolder, String title, int icon,
                 int background) {
             viewHolder.titleTextView.setText(title);
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java
index b016488..412169e 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java
@@ -42,5 +42,6 @@
 
     private void launchMainActivity() {
         startActivity(new Intent(this, BatteryConsumerPickerActivity.class));
+        finish();
     }
 }
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index f39508d..4942557 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -152,6 +152,7 @@
         ":HelloWorldUsingSdkMalformedNegativeVersion",
         ":CtsStaticSharedLibConsumerApp1",
         ":CtsStaticSharedLibConsumerApp3",
+        ":CtsStaticSharedLibProviderApp1",
     ],
 }
 
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 5d8ff87..bada751 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -43,6 +43,8 @@
             value="/data/local/tmp/tests/coretests/pm/CtsStaticSharedLibConsumerApp1.apk"/>
         <option name="push-file" key="CtsStaticSharedLibConsumerApp3.apk"
             value="/data/local/tmp/tests/coretests/pm/CtsStaticSharedLibConsumerApp3.apk"/>
+        <option name="push-file" key="CtsStaticSharedLibProviderApp1.apk"
+                value="/data/local/tmp/tests/coretests/pm/CtsStaticSharedLibProviderApp1.apk"/>
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 23a0985..63e678d 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -467,7 +467,6 @@
                 .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
                 .setColor(Color.WHITE)
                 .setColorized(true)
-                .setFlag(FLAG_CAN_COLORIZE, true)
                 .build();
         assertThat(n.hasPromotableCharacteristics()).isTrue();
     }
@@ -481,7 +480,6 @@
                 .setContentTitle("TITLE")
                 .setColor(Color.WHITE)
                 .setColorized(true)
-                .setFlag(FLAG_CAN_COLORIZE, true)
                 .build();
         assertThat(n.hasPromotableCharacteristics()).isFalse();
     }
@@ -505,7 +503,20 @@
                 .setStyle(new Notification.BigTextStyle())
                 .setColor(Color.WHITE)
                 .setColorized(true)
-                .setFlag(FLAG_CAN_COLORIZE, true)
+                .build();
+        assertThat(n.hasPromotableCharacteristics()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+    public void testHasPromotableCharacteristics_groupSummary() {
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+                .setColor(Color.WHITE)
+                .setColorized(true)
+                .setGroup("someGroup")
+                .setGroupSummary(true)
                 .build();
         assertThat(n.hasPromotableCharacteristics()).isFalse();
     }
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index 2fc72e1..177c7f0 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -26,6 +26,7 @@
 import static com.android.internal.os.Flags.FLAG_APPLICATION_SHARED_MEMORY_ENABLED;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertSame;
@@ -34,6 +35,8 @@
 
 import android.annotation.SuppressLint;
 import android.app.PropertyInvalidatedCache.Args;
+import android.app.PropertyInvalidatedCache.NonceWatcher;
+import android.app.PropertyInvalidatedCache.NonceStore;
 import android.os.Binder;
 import com.android.internal.os.ApplicationSharedMemory;
 
@@ -45,11 +48,15 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.os.ApplicationSharedMemory;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Test for verifying the behavior of {@link PropertyInvalidatedCache}.  This test does
  * not use any actual binder calls - it is entirely self-contained.  This test also relies
@@ -490,6 +497,62 @@
         }
     }
 
+    // Verify that NonceWatcher change reporting works properly
+    @Test
+    public void testNonceWatcherChanged() {
+        // Create a cache that will write a system nonce.
+        TestCache sysCache = new TestCache(MODULE_SYSTEM, "watcher1");
+        sysCache.testPropertyName();
+
+        try (NonceWatcher watcher1 = sysCache.getNonceWatcher()) {
+
+            // The property has never been invalidated so it is still unset.
+            assertFalse(watcher1.isChanged());
+
+            // Invalidate the cache.  The first call to isChanged will return true but the second
+            // call will return false;
+            sysCache.invalidateCache();
+            assertTrue(watcher1.isChanged());
+            assertFalse(watcher1.isChanged());
+
+            // Invalidate the cache.  The first call to isChanged will return true but the second
+            // call will return false;
+            sysCache.invalidateCache();
+            sysCache.invalidateCache();
+            assertTrue(watcher1.isChanged());
+            assertFalse(watcher1.isChanged());
+
+            NonceWatcher watcher2 = sysCache.getNonceWatcher();
+            // This watcher return isChanged() immediately because the nonce is not UNSET.
+            assertTrue(watcher2.isChanged());
+        }
+    }
+
+    // Verify that NonceWatcher wait-for-change works properly
+    @Test
+    public void testNonceWatcherWait() throws Exception {
+        // Create a cache that will write a system nonce.
+        TestCache sysCache = new TestCache(MODULE_TEST, "watcher1");
+
+        // Use the watcher outside a try-with-resources block.
+        NonceWatcher watcher1 = sysCache.getNonceWatcher();
+
+        // Invalidate the cache and then "wait".
+        sysCache.invalidateCache();
+        assertEquals(watcher1.waitForChange(), 1);
+
+        // Invalidate the cache three times and then "wait".
+        sysCache.invalidateCache();
+        sysCache.invalidateCache();
+        sysCache.invalidateCache();
+        assertEquals(watcher1.waitForChange(), 3);
+
+        // Wait for a change.  It won't happen, but the code will time out after 10ms.
+        assertEquals(watcher1.waitForChange(10, TimeUnit.MILLISECONDS), 0);
+
+        watcher1.close();
+    }
+
     // Verify the behavior of shared memory nonce storage.  This does not directly test the cache
     // storing nonces in shared memory.
     @RequiresFlagsEnabled(FLAG_APPLICATION_SHARED_MEMORY_ENABLED)
@@ -502,10 +565,8 @@
 
         // Create a server-side store and a client-side store.  The server's store is mutable and
         // the client's store is not mutable.
-        PropertyInvalidatedCache.NonceStore server =
-                new PropertyInvalidatedCache.NonceStore(shmem.getSystemNonceBlock(), true);
-        PropertyInvalidatedCache.NonceStore client =
-                new PropertyInvalidatedCache.NonceStore(shmem.getSystemNonceBlock(), false);
+        NonceStore server = new NonceStore(shmem.getSystemNonceBlock(), true);
+        NonceStore client = new NonceStore(shmem.getSystemNonceBlock(), false);
 
         final String name1 = "name1";
         assertEquals(server.getHandleForName(name1), INVALID_NONCE_INDEX);
diff --git a/core/tests/coretests/src/android/app/wallpaper/WallpaperDescriptionTest.java b/core/tests/coretests/src/android/app/wallpaper/WallpaperDescriptionTest.java
index 01c2abf..a22eae3 100644
--- a/core/tests/coretests/src/android/app/wallpaper/WallpaperDescriptionTest.java
+++ b/core/tests/coretests/src/android/app/wallpaper/WallpaperDescriptionTest.java
@@ -15,13 +15,20 @@
  */
 package android.app.wallpaper;
 
+import static android.app.WallpaperManager.ORIENTATION_LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_PORTRAIT;
+import static android.app.WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_SQUARE_PORTRAIT;
+
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.content.ComponentName;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.PersistableBundle;
+import android.util.SparseArray;
 import android.util.Xml;
 
 import com.android.modules.utils.TypedXmlPullParser;
@@ -41,17 +48,20 @@
 
 @RunWith(JUnit4.class)
 public class WallpaperDescriptionTest {
-    private static final String TAG = "WallpaperDescriptionTest";
+    private static final Rect DEFAULT_CROP_PORTRAIT = new Rect(1, 2, 3, 4);
+    private static final Rect DEFAULT_CROP_LANDSCAPE = new Rect(5, 6, 7, 8);
+    private static final Rect DEFAULT_CROP_SQUARE_PORTRAIT = new Rect(9, 10, 11, 12);
+    private static final Rect DEFAULT_CROP_SQUARE_LANDSCAPE = new Rect(13, 14, 15, 16);
 
     private final ComponentName mTestComponent = new ComponentName("fakePackage", "fakeClass");
 
     @Test
     public void equals_ignoresIrrelevantFields() {
         String id = "fakeId";
-        WallpaperDescription desc1 = new WallpaperDescription.Builder().setComponent(
-                mTestComponent).setId(id).setTitle("fake one").build();
-        WallpaperDescription desc2 = new WallpaperDescription.Builder().setComponent(
-                mTestComponent).setId(id).setTitle("fake different").build();
+        WallpaperDescription desc1 = new WallpaperDescription.Builder()
+                .setComponent(mTestComponent).setId(id).setTitle("fake one").build();
+        WallpaperDescription desc2 = new WallpaperDescription.Builder()
+                .setComponent(mTestComponent).setId(id).setTitle("fake different").build();
 
         assertThat(desc1).isEqualTo(desc2);
     }
@@ -72,13 +82,21 @@
         final Uri thumbnail = Uri.parse("http://www.bogus.com/thumbnail");
         final List<CharSequence> description = List.of("line1", "line2");
         final Uri contextUri = Uri.parse("http://www.bogus.com/contextUri");
-        final PersistableBundle content = new PersistableBundle();
-        content.putString("ckey", "cvalue");
+        final PersistableBundle content = makeDefaultContent();
+        final SparseArray<Rect> cropHints = makeDefaultCropHints();
+        final float sampleSize = 0.9f;
         WallpaperDescription source = new WallpaperDescription.Builder()
-                .setComponent(mTestComponent).setId("fakeId").setThumbnail(thumbnail)
-                .setTitle("Fake title").setDescription(description)
-                .setContextUri(contextUri).setContextDescription("Context description")
-                .setContent(content).build();
+                .setComponent(mTestComponent)
+                .setId("fakeId")
+                .setThumbnail(thumbnail)
+                .setTitle("Fake title")
+                .setDescription(description)
+                .setContextUri(contextUri)
+                .setContextDescription("Context description")
+                .setContent(content)
+                .setCropHints(cropHints)
+                .setSampleSize(sampleSize)
+                .build();
 
         ByteArrayOutputStream ostream = new ByteArrayOutputStream();
         TypedXmlSerializer serializer = Xml.newBinarySerializer();
@@ -122,6 +140,57 @@
         assertThat(destination.getContent()).isNotNull();
         assertThat(destination.getContent().getString("ckey")).isEqualTo(
                 source.getContent().getString("ckey"));
+        assertThat(destination.getCropHints()).isNotNull();
+        assertThat(destination.getCropHints().get(ORIENTATION_PORTRAIT)).isEqualTo(
+                DEFAULT_CROP_PORTRAIT);
+        assertThat(destination.getCropHints().get(ORIENTATION_LANDSCAPE)).isEqualTo(
+                DEFAULT_CROP_LANDSCAPE);
+        assertThat(destination.getCropHints().get(ORIENTATION_SQUARE_PORTRAIT)).isEqualTo(
+                DEFAULT_CROP_SQUARE_PORTRAIT);
+        assertThat(destination.getCropHints().get(ORIENTATION_SQUARE_LANDSCAPE)).isEqualTo(
+                DEFAULT_CROP_SQUARE_LANDSCAPE);
+        assertThat(destination.getSampleSize()).isEqualTo(sampleSize);
+    }
+
+    @Test
+    public void xml_roundTripSucceeds_withNulls() throws IOException, XmlPullParserException {
+        WallpaperDescription source = new WallpaperDescription.Builder().build();
+
+        ByteArrayOutputStream ostream = new ByteArrayOutputStream();
+        TypedXmlSerializer serializer = Xml.newBinarySerializer();
+        serializer.setOutput(ostream, StandardCharsets.UTF_8.name());
+        serializer.startDocument(null, true);
+        serializer.startTag(null, "test");
+        source.saveToXml(serializer);
+        serializer.endTag(null, "test");
+        serializer.endDocument();
+        ostream.close();
+
+        WallpaperDescription destination = null;
+        ByteArrayInputStream istream = new ByteArrayInputStream(ostream.toByteArray());
+        TypedXmlPullParser parser = Xml.newBinaryPullParser();
+        parser.setInput(istream, StandardCharsets.UTF_8.name());
+        int type;
+        do {
+            type = parser.next();
+            if (type == XmlPullParser.START_TAG && "test".equals(parser.getName())) {
+                destination = WallpaperDescription.restoreFromXml(parser);
+            }
+        } while (type != XmlPullParser.END_DOCUMENT);
+
+        assertThat(destination).isNotNull();
+        assertThat(destination.getComponent()).isEqualTo(source.getComponent());
+        assertThat(destination.getId()).isEqualTo(source.getId());
+        assertThat(destination.getThumbnail()).isEqualTo(source.getThumbnail());
+        assertThat(destination.getTitle()).isNull();
+        assertThat(destination.getDescription()).hasSize(0);
+        assertThat(destination.getContextUri()).isEqualTo(source.getContextUri());
+        assertThat(destination.getContextDescription()).isNull();
+        assertThat(destination.getContent()).isNotNull();
+        assertThat(destination.getContent().keySet()).isEmpty();
+        assertThat(destination.getCropHints()).isNotNull();
+        assertThat(destination.getCropHints().size()).isEqualTo(0);
+        assertThat(destination.getSampleSize()).isEqualTo(source.getSampleSize());
     }
 
     @Test
@@ -129,13 +198,21 @@
         final Uri thumbnail = Uri.parse("http://www.bogus.com/thumbnail");
         final List<CharSequence> description = List.of("line1", "line2");
         final Uri contextUri = Uri.parse("http://www.bogus.com/contextUri");
-        final PersistableBundle content = new PersistableBundle();
-        content.putString("ckey", "cvalue");
-        WallpaperDescription source = new WallpaperDescription.Builder().setComponent(
-                mTestComponent).setId("fakeId").setThumbnail(thumbnail).setTitle(
-                "Fake title").setDescription(description).setContextUri(
-                contextUri).setContextDescription("Context description").setContent(
-                content).build();
+        final PersistableBundle content = makeDefaultContent();
+        final SparseArray<Rect> cropHints = makeDefaultCropHints();
+        final float sampleSize = 0.9f;
+        WallpaperDescription source = new WallpaperDescription.Builder()
+                .setComponent(mTestComponent)
+                .setId("fakeId")
+                .setThumbnail(thumbnail)
+                .setTitle("Fake title")
+                .setDescription(description)
+                .setContextUri(contextUri)
+                .setContextDescription("Context description")
+                .setContent(content)
+                .setCropHints(cropHints)
+                .setSampleSize(sampleSize)
+                .build();
 
         Parcel parcel = Parcel.obtain();
         source.writeToParcel(parcel, 0);
@@ -162,6 +239,16 @@
         assertThat(destination.getContent()).isNotNull();
         assertThat(destination.getContent().getString("ckey")).isEqualTo(
                 source.getContent().getString("ckey"));
+        assertThat(destination.getCropHints()).isNotNull();
+        assertThat(destination.getCropHints().get(ORIENTATION_PORTRAIT)).isEqualTo(
+                DEFAULT_CROP_PORTRAIT);
+        assertThat(destination.getCropHints().get(ORIENTATION_LANDSCAPE)).isEqualTo(
+                DEFAULT_CROP_LANDSCAPE);
+        assertThat(destination.getCropHints().get(ORIENTATION_SQUARE_PORTRAIT)).isEqualTo(
+                DEFAULT_CROP_SQUARE_PORTRAIT);
+        assertThat(destination.getCropHints().get(ORIENTATION_SQUARE_LANDSCAPE)).isEqualTo(
+                DEFAULT_CROP_SQUARE_LANDSCAPE);
+        assertThat(destination.getSampleSize()).isEqualTo(sampleSize);
     }
 
     @Test
@@ -178,17 +265,14 @@
         assertThat(destination.getId()).isEqualTo(source.getId());
         assertThat(destination.getThumbnail()).isEqualTo(source.getThumbnail());
         assertThat(destination.getTitle()).isNull();
-        assertThat(destination.getDescription()).hasSize(source.getDescription().size());
-        for (int i = 0; i < destination.getDescription().size(); i++) {
-            CharSequence strDest = destination.getDescription().get(i);
-            CharSequence strSrc = source.getDescription().get(i);
-            assertWithMessage("description string mismatch")
-                    .that(CharSequence.compare(strDest, strSrc)).isEqualTo(0);
-        }
+        assertThat(destination.getDescription()).hasSize(0);
         assertThat(destination.getContextUri()).isEqualTo(source.getContextUri());
         assertThat(destination.getContextDescription()).isNull();
         assertThat(destination.getContent()).isNotNull();
         assertThat(destination.getContent().keySet()).isEmpty();
+        assertThat(destination.getCropHints()).isNotNull();
+        assertThat(destination.getCropHints().size()).isEqualTo(0);
+        assertThat(destination.getSampleSize()).isEqualTo(source.getSampleSize());
     }
 
     @Test
@@ -197,14 +281,22 @@
         final Uri thumbnail = Uri.parse("http://www.bogus.com/thumbnail");
         final List<CharSequence> description = List.of("line1", "line2");
         final Uri contextUri = Uri.parse("http://www.bogus.com/contextUri");
-        final PersistableBundle content = new PersistableBundle();
-        content.putString("ckey", "cvalue");
+        final PersistableBundle content = makeDefaultContent();
+        final SparseArray<Rect> cropHints = makeDefaultCropHints();
+        final float sampleSize = 1.1f;
         final String destinationId = "destinationId";
-        WallpaperDescription source = new WallpaperDescription.Builder().setComponent(
-                mTestComponent).setId(sourceId).setThumbnail(thumbnail).setTitle(
-                "Fake title").setDescription(description).setContextUri(
-                contextUri).setContextDescription("Context description").setContent(
-                content).build();
+        WallpaperDescription source = new WallpaperDescription.Builder()
+                .setComponent(mTestComponent)
+                .setId(sourceId)
+                .setThumbnail(thumbnail)
+                .setTitle("Fake title")
+                .setDescription(description)
+                .setContextUri(contextUri)
+                .setContextDescription("Context description")
+                .setContent(content)
+                .setCropHints(cropHints)
+                .setSampleSize(sampleSize)
+                .build();
 
         WallpaperDescription destination = source.toBuilder().setId(destinationId).build();
 
@@ -227,5 +319,29 @@
         assertThat(destination.getContent()).isNotNull();
         assertThat(destination.getContent().getString("ckey")).isEqualTo(
                 source.getContent().getString("ckey"));
+        assertThat(destination.getCropHints().get(ORIENTATION_PORTRAIT)).isEqualTo(
+                DEFAULT_CROP_PORTRAIT);
+        assertThat(destination.getCropHints().get(ORIENTATION_LANDSCAPE)).isEqualTo(
+                DEFAULT_CROP_LANDSCAPE);
+        assertThat(destination.getCropHints().get(ORIENTATION_SQUARE_PORTRAIT)).isEqualTo(
+                DEFAULT_CROP_SQUARE_PORTRAIT);
+        assertThat(destination.getCropHints().get(ORIENTATION_SQUARE_LANDSCAPE)).isEqualTo(
+                DEFAULT_CROP_SQUARE_LANDSCAPE);
+        assertThat(destination.getSampleSize()).isEqualTo(sampleSize);
+    }
+
+    private static PersistableBundle makeDefaultContent() {
+        final PersistableBundle content = new PersistableBundle();
+        content.putString("ckey", "cvalue");
+        return content;
+    }
+
+    private static SparseArray<Rect> makeDefaultCropHints() {
+        final SparseArray<Rect> cropHints = new SparseArray<>();
+        cropHints.put(ORIENTATION_PORTRAIT, DEFAULT_CROP_PORTRAIT);
+        cropHints.put(ORIENTATION_LANDSCAPE, DEFAULT_CROP_LANDSCAPE);
+        cropHints.put(ORIENTATION_SQUARE_PORTRAIT, DEFAULT_CROP_SQUARE_PORTRAIT);
+        cropHints.put(ORIENTATION_SQUARE_LANDSCAPE, DEFAULT_CROP_SQUARE_LANDSCAPE);
+        return cropHints;
     }
 }
diff --git a/core/tests/coretests/src/android/appwidget/AppWidgetEventsTest.kt b/core/tests/coretests/src/android/appwidget/AppWidgetEventsTest.kt
new file mode 100644
index 0000000..ea1158c
--- /dev/null
+++ b/core/tests/coretests/src/android/appwidget/AppWidgetEventsTest.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 android.appwidget
+
+import android.graphics.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppWidgetEventsTest {
+    @Test
+    fun createWidgetInteractionEvent() {
+        val appWidgetId = 1
+        val durationMs = 1000L
+        val position = Rect(1, 2, 3, 4)
+        val clicked = intArrayOf(1, 2, 3)
+        val scrolled = intArrayOf(4, 5, 6)
+        val bundle = AppWidgetManager.createWidgetInteractionEvent(
+            appWidgetId,
+            durationMs,
+            position,
+            clicked,
+            scrolled
+        )
+
+        assertThat(bundle.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID)).isEqualTo(appWidgetId)
+        assertThat(bundle.getLong(AppWidgetManager.EXTRA_EVENT_DURATION_MS)).isEqualTo(durationMs)
+        assertThat(bundle.getIntArray(AppWidgetManager.EXTRA_EVENT_POSITION_RECT))
+            .asList().containsExactly(position.left, position.top, position.right, position.bottom)
+        assertThat(bundle.getIntArray(AppWidgetManager.EXTRA_EVENT_CLICKED_VIEWS))
+            .asList().containsExactly(clicked[0], clicked[1], clicked[2])
+        assertThat(bundle.getIntArray(AppWidgetManager.EXTRA_EVENT_SCROLLED_VIEWS))
+            .asList().containsExactly(scrolled[0], scrolled[1], scrolled[2])
+    }
+}
diff --git a/core/tests/coretests/src/android/appwidget/OWNERS b/core/tests/coretests/src/android/appwidget/OWNERS
new file mode 100644
index 0000000..d724cac
--- /dev/null
+++ b/core/tests/coretests/src/android/appwidget/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/appwidget/OWNERS
diff --git a/core/tests/coretests/src/android/content/IntentTest.java b/core/tests/coretests/src/android/content/IntentTest.java
index 7bc4abd..fdfb0c3 100644
--- a/core/tests/coretests/src/android/content/IntentTest.java
+++ b/core/tests/coretests/src/android/content/IntentTest.java
@@ -19,8 +19,9 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
-import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -29,6 +30,7 @@
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.security.Flags;
+import android.util.ArraySet;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -40,6 +42,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 
 /**
  *  Build/Install/Run:
@@ -51,7 +54,6 @@
 public class IntentTest {
     private static final String TEST_ACTION = "android.content.IntentTest_test";
     private static final String TEST_EXTRA_NAME = "testExtraName";
-    private static final Uri TEST_URI = Uri.parse("content://com.example/people");
 
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -129,4 +131,111 @@
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT)
+    public void testFillInCreatorTokenInfo() {
+        // case 1: intent does not have creatorTokenInfo; fillinIntent contains creatorTokenInfo
+        Intent intent = new Intent();
+        Intent fillInIntent = new Intent();
+        fillInIntent.setCreatorToken(new Binder());
+        fillInIntent.putExtra("extraKey", new Intent());
+
+        fillInIntent.collectExtraIntentKeys();
+        intent.fillIn(fillInIntent, 0);
+
+        // extra intent keys are merged
+        assertThat(intent.getExtraIntentKeys()).isEqualTo(fillInIntent.getExtraIntentKeys());
+        // but creator token is not overwritten.
+        assertThat(intent.getCreatorToken()).isNull();
+
+
+        // case 2: Both intent and fillInIntent contains creatorToken, intent's creatorToken is not
+        // overwritten.
+        intent = new Intent();
+        IBinder creatorToken = new Binder();
+        intent.setCreatorToken(creatorToken);
+        fillInIntent = new Intent();
+        fillInIntent.setCreatorToken(new Binder());
+
+        intent.fillIn(fillInIntent, 0);
+
+        assertThat(intent.getCreatorToken()).isEqualTo(creatorToken);
+
+
+        // case 3: Contains duplicate extra keys
+        intent = new Intent();
+        intent.putExtra("key1", new Intent());
+        intent.putExtra("key2", new Intent());
+        fillInIntent = new Intent();
+        fillInIntent.putExtra("key1", new Intent());
+        fillInIntent.putExtra("key3", new Intent());
+
+        intent.collectExtraIntentKeys();
+        Set originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys());
+
+        fillInIntent.collectExtraIntentKeys();
+        intent.fillIn(fillInIntent, 0);
+
+        assertThat(intent.getExtraIntentKeys()).hasSize(3);
+        assertTrue(intent.getExtraIntentKeys().containsAll(originalIntentKeys));
+        assertTrue(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys()));
+
+
+        // case 4: Both contains a mixture of extras and clip data. NOT force to fill in clip data.
+        intent = new Intent();
+        ClipData clipData = ClipData.newIntent("clip", new Intent());
+        clipData.addItem(new ClipData.Item(new Intent()));
+        intent.setClipData(clipData);
+        intent.putExtra("key1", new Intent());
+        intent.putExtra("key2", new Intent());
+        fillInIntent = new Intent();
+        ClipData fillInClipData = ClipData.newIntent("clip", new Intent());
+        fillInClipData.addItem(new ClipData.Item(new Intent()));
+        fillInClipData.addItem(new ClipData.Item(new Intent()));
+        fillInIntent.setClipData(fillInClipData);
+        fillInIntent.putExtra("key1", new Intent());
+        fillInIntent.putExtra("key3", new Intent());
+
+        intent.collectExtraIntentKeys();
+        originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys());
+        fillInIntent.collectExtraIntentKeys();
+        intent.fillIn(fillInIntent, 0);
+
+        // size is 5 ( 3 extras merged from both + 2 clip data in the original.
+        assertThat(intent.getExtraIntentKeys()).hasSize(5);
+        // all keys from original are kept.
+        assertTrue(intent.getExtraIntentKeys().containsAll(originalIntentKeys));
+        // Not all keys from fillInIntent are kept - clip data keys are dropped.
+        assertFalse(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys()));
+
+
+        // case 5: Both contains a mixture of extras and clip data. Force to fill in clip data.
+        intent = new Intent();
+        clipData = ClipData.newIntent("clip", new Intent());
+        clipData.addItem(new ClipData.Item(new Intent()));
+        clipData.addItem(new ClipData.Item(new Intent()));
+        clipData.addItem(new ClipData.Item(new Intent()));
+        intent.setClipData(clipData);
+        intent.putExtra("key1", new Intent());
+        intent.putExtra("key2", new Intent());
+        fillInIntent = new Intent();
+        fillInClipData = ClipData.newIntent("clip", new Intent());
+        fillInClipData.addItem(new ClipData.Item(new Intent()));
+        fillInClipData.addItem(new ClipData.Item(new Intent()));
+        fillInIntent.setClipData(fillInClipData);
+        fillInIntent.putExtra("key1", new Intent());
+        fillInIntent.putExtra("key3", new Intent());
+
+        intent.collectExtraIntentKeys();
+        originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys());
+        fillInIntent.collectExtraIntentKeys();
+        intent.fillIn(fillInIntent, Intent.FILL_IN_CLIP_DATA);
+
+        // size is 6 ( 3 extras merged from both + 3 clip data in the fillInIntent.
+        assertThat(intent.getExtraIntentKeys()).hasSize(6);
+        // all keys from fillInIntent are kept.
+        assertTrue(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys()));
+        // Not all keys from intent are kept - clip data keys are dropped.
+        assertFalse(intent.getExtraIntentKeys().containsAll(originalIntentKeys));
+    }
 }
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
index b60d614..c55008e 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
@@ -24,6 +24,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class PackageManagerTest {
@@ -46,4 +49,25 @@
     public void testResolveInfoFlags() throws Exception {
         assertThat(PackageManager.ResolveInfoFlags.of(42L).getValue()).isEqualTo(42L);
     }
+
+    @Test
+    public void testSdkFeatureCount() throws Exception {
+        // Check to make sure the system feature `SdkConst` annotation processor yields sensible
+        // results. We don't care about the exactness, just that it's not pathologically wrong.
+        assertThat(PackageManager.SDK_FEATURE_COUNT).isGreaterThan(150);
+        assertThat(PackageManager.SDK_FEATURE_COUNT).isLessThan(500);
+        assertThat(PackageManager.SDK_FEATURE_COUNT)
+                .isWithin(50)
+                .of(getApproximateFeatureCountUsingReflection());
+    }
+
+    /* Return a ballpark estimate of the feature count using FEATURE_ field names. */
+    private static int getApproximateFeatureCountUsingReflection() {
+        return (int)
+                Arrays.stream(PackageManager.class.getFields())
+                        .filter(field -> Modifier.isStatic(field.getModifiers()))
+                        .filter(field -> Modifier.isFinal(field.getModifiers()))
+                        .filter(field -> field.getName().startsWith("FEATURE_"))
+                        .count();
+    }
 }
diff --git a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
index ecacdb2..6d2dd53 100644
--- a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
+++ b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
@@ -73,6 +73,7 @@
     private static final String TEST_APP_USING_SDK1_AND_SDK1 = "HelloWorldUsingSdk1AndSdk1.apk";
     private static final String TEST_APP_USING_SDK_MALFORMED_VERSION =
             "HelloWorldUsingSdkMalformedNegativeVersion.apk";
+    private static final String TEST_STATIC_LIB_APP = "CtsStaticSharedLibProviderApp1.apk";
     private static final String TEST_APP_USING_STATIC_LIB = "CtsStaticSharedLibConsumerApp1.apk";
     private static final String TEST_APP_USING_STATIC_LIB_TWO_CERTS =
             "CtsStaticSharedLibConsumerApp3.apk";
@@ -207,6 +208,17 @@
         assertThat(liteCerts).isEqualTo(pkgCerts);
     }
 
+    @Test
+    public void testParseApkLite_isIsStaticLibrary() throws Exception {
+        File apkFile = copyApkToTmpDir(TEST_STATIC_LIB_APP);
+        ParseResult<ApkLite> result = ApkLiteParseUtils
+                .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+        assertThat(result.isError()).isFalse();
+        ApkLite baseApk = result.getResult();
+
+        assertThat(baseApk.isIsStaticLibrary()).isTrue();
+    }
+
     @SuppressLint("CheckResult")
     @Test
     public void testParseApkLite_malformedUsesSdkLibrary_duplicate() throws Exception {
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index c0a9bc2..f9d449c 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -18,10 +18,7 @@
 
 import android.platform.test.annotations.Presubmit
 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.RavenwoodFlagsValueProvider
-import android.platform.test.ravenwood.RavenwoodRule
 import android.util.SparseArray
 import androidx.core.util.forEach
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -47,15 +44,7 @@
 class FontScaleConverterFactoryTest {
 
     @get:Rule
-    val ravenwoodRule: RavenwoodRule = RavenwoodRule.Builder().build()
-
-    @get:Rule
-    val checkFlagsRule: CheckFlagsRule =
-        if (RavenwoodRule.isOnRavenwood()) {
-            RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
-        } else {
-            DeviceFlagsValueProvider.createCheckFlagsRule()
-        }
+    val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
 
     private var defaultLookupTables: SparseArray<FontScaleConverter>? = null
 
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index be8ecbe..cfcd53e 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -33,8 +33,6 @@
 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.RavenwoodFlagsValueProvider;
-import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
@@ -65,13 +63,7 @@
     private static final String TEST_LIB = "com.android.frameworks.coretests.bdr_helper_app1";
 
     @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().build();
-
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            RavenwoodRule.isOnRavenwood()
-                    ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
-                    : DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     private ResourcesManager mResourcesManager;
     private Map<Integer, DisplayMetrics> mDisplayMetricsMap;
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 6a5224d..7a5b306 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -55,6 +55,9 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
 /**
  * Tests for {@link DisplayManagerGlobal}.
  *
@@ -79,10 +82,13 @@
     private IDisplayManager mDisplayManager;
 
     @Mock
-    private DisplayManager.DisplayListener mListener;
+    private DisplayManager.DisplayListener mDisplayListener;
 
     @Mock
-    private DisplayManager.DisplayListener mListener2;
+    private DisplayManager.DisplayListener mDisplayListener2;
+
+    @Mock
+    private Consumer<DisplayTopology> mTopologyListener;
 
     @Captor
     private ArgumentCaptor<IDisplayManagerCallback> mCallbackCaptor;
@@ -90,6 +96,7 @@
     private Context mContext;
     private DisplayManagerGlobal mDisplayManagerGlobal;
     private Handler mHandler;
+    private Executor mExecutor;
 
     @Before
     public void setUp() throws RemoteException {
@@ -97,13 +104,14 @@
         Mockito.when(mDisplayManager.getPreferredWideGamutColorSpaceId()).thenReturn(0);
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
         mHandler = mContext.getMainThreadHandler();
+        mExecutor = mContext.getMainExecutor();
         mDisplayManagerGlobal = new DisplayManagerGlobal(mDisplayManager);
     }
 
     @Test
     public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException {
-        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS,
-                null);
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+                ALL_DISPLAY_EVENTS, /* packageName= */ null);
         Mockito.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
         IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -111,31 +119,31 @@
         int displayId = 1;
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
         waitForHandler();
-        Mockito.verify(mListener).onDisplayAdded(eq(displayId));
-        Mockito.verifyNoMoreInteractions(mListener);
+        Mockito.verify(mDisplayListener).onDisplayAdded(eq(displayId));
+        Mockito.verifyNoMoreInteractions(mDisplayListener);
 
-        Mockito.reset(mListener);
+        Mockito.reset(mDisplayListener);
         // Mock IDisplayManager to return a different display info to trigger display change.
         final DisplayInfo newDisplayInfo = new DisplayInfo();
         newDisplayInfo.rotation++;
         doReturn(newDisplayInfo).when(mDisplayManager).getDisplayInfo(displayId);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
         waitForHandler();
-        Mockito.verify(mListener).onDisplayChanged(eq(displayId));
-        Mockito.verifyNoMoreInteractions(mListener);
+        Mockito.verify(mDisplayListener).onDisplayChanged(eq(displayId));
+        Mockito.verifyNoMoreInteractions(mDisplayListener);
 
-        Mockito.reset(mListener);
+        Mockito.reset(mDisplayListener);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
         waitForHandler();
-        Mockito.verify(mListener).onDisplayRemoved(eq(displayId));
-        Mockito.verifyNoMoreInteractions(mListener);
+        Mockito.verify(mDisplayListener).onDisplayRemoved(eq(displayId));
+        Mockito.verifyNoMoreInteractions(mDisplayListener);
     }
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS)
     public void testDisplayListenerIsCalled_WhenDisplayPropertyChangeEventOccurs()
             throws RemoteException {
-        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
                 INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
                         | INTERNAL_EVENT_FLAG_DISPLAY_STATE,
                 null);
@@ -145,50 +153,50 @@
 
         int displayId = 1;
 
-        Mockito.reset(mListener);
+        Mockito.reset(mDisplayListener);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED);
         waitForHandler();
-        Mockito.verify(mListener).onDisplayChanged(eq(displayId));
-        Mockito.verifyNoMoreInteractions(mListener);
+        Mockito.verify(mDisplayListener).onDisplayChanged(eq(displayId));
+        Mockito.verifyNoMoreInteractions(mDisplayListener);
 
-        Mockito.reset(mListener);
+        Mockito.reset(mDisplayListener);
         callback.onDisplayEvent(displayId, EVENT_DISPLAY_STATE_CHANGED);
         waitForHandler();
-        Mockito.verify(mListener).onDisplayChanged(eq(displayId));
-        Mockito.verifyNoMoreInteractions(mListener);
+        Mockito.verify(mDisplayListener).onDisplayChanged(eq(displayId));
+        Mockito.verifyNoMoreInteractions(mDisplayListener);
     }
 
     @Test
     public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException {
         // First we subscribe to all events in order to test that the subsequent calls to
         // registerDisplayListener will update the event mask.
-        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS,
-                null);
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
+                ALL_DISPLAY_EVENTS, /* packageName= */ null);
         Mockito.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
         IDisplayManagerCallback callback = mCallbackCaptor.getValue();
 
         int displayId = 1;
-        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
                 ALL_DISPLAY_EVENTS
                         & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, null);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
         waitForHandler();
-        Mockito.verifyZeroInteractions(mListener);
+        Mockito.verifyZeroInteractions(mDisplayListener);
 
-        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
                 ALL_DISPLAY_EVENTS
                         & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, null);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
         waitForHandler();
-        Mockito.verifyZeroInteractions(mListener);
+        Mockito.verifyZeroInteractions(mDisplayListener);
 
-        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
                 ALL_DISPLAY_EVENTS
                         & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
         waitForHandler();
-        Mockito.verifyZeroInteractions(mListener);
+        Mockito.verifyZeroInteractions(mDisplayListener);
     }
 
     @Test
@@ -207,7 +215,7 @@
     @Test
     public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
             throws RemoteException {
-        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
                 DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED,
                 null);
         InOrder inOrder = Mockito.inOrder(mDisplayManager);
@@ -228,7 +236,7 @@
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(),
                         eq(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED));
 
-        mDisplayManagerGlobal.unregisterDisplayListener(mListener);
+        mDisplayManagerGlobal.unregisterDisplayListener(mDisplayListener);
         inOrder.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(0L));
     }
@@ -244,33 +252,49 @@
         mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(123);
 
         // One listener listens on add/remove, and the other one listens on change.
-        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
                 DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
                         | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
                 null /* packageName */);
-        mDisplayManagerGlobal.registerDisplayListener(mListener2, mHandler,
+        mDisplayManagerGlobal.registerDisplayListener(mDisplayListener2, mHandler,
                 DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
                 null /* packageName */);
 
         mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
         waitForHandler();
 
-        verify(mListener, never()).onDisplayChanged(anyInt());
-        verify(mListener2).onDisplayChanged(321);
+        verify(mDisplayListener, never()).onDisplayChanged(anyInt());
+        verify(mDisplayListener2).onDisplayChanged(321);
 
         // Trigger the callback again even if the display info is not changed.
-        clearInvocations(mListener2);
+        clearInvocations(mDisplayListener2);
         mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
         waitForHandler();
 
-        verify(mListener2).onDisplayChanged(321);
+        verify(mDisplayListener2).onDisplayChanged(321);
 
         // No callback for non-existing display (no display info returned from IDisplayManager).
-        clearInvocations(mListener2);
+        clearInvocations(mDisplayListener2);
         mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(456);
         waitForHandler();
 
-        verify(mListener2, never()).onDisplayChanged(anyInt());
+        verify(mDisplayListener2, never()).onDisplayChanged(anyInt());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DISPLAY_TOPOLOGY)
+    public void testTopologyListenerIsCalled_WhenTopologyUpdateOccurs() throws RemoteException {
+        mDisplayManagerGlobal.registerTopologyListener(mExecutor, mTopologyListener,
+                /* packageName= */ null);
+        Mockito.verify(mDisplayManager).registerCallbackWithEventMask(mCallbackCaptor.capture(),
+                eq(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED));
+        IDisplayManagerCallback callback = mCallbackCaptor.getValue();
+
+        DisplayTopology topology = new DisplayTopology();
+        callback.onTopologyChanged(topology);
+        waitForHandler();
+        Mockito.verify(mTopologyListener).accept(topology);
+        Mockito.verifyNoMoreInteractions(mTopologyListener);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
index 8969b2b..18e4fde 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
+++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
@@ -38,12 +38,7 @@
         topology.addDisplay(displayId, width, height)
 
         assertThat(topology.primaryDisplayId).isEqualTo(displayId)
-
-        val display = topology.root!!
-        assertThat(display.displayId).isEqualTo(displayId)
-        assertThat(display.width).isEqualTo(width)
-        assertThat(display.height).isEqualTo(height)
-        assertThat(display.children).isEmpty()
+        verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0)
     }
 
     @Test
@@ -62,18 +57,9 @@
         assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
 
         val display1 = topology.root!!
-        assertThat(display1.displayId).isEqualTo(displayId1)
-        assertThat(display1.width).isEqualTo(width1)
-        assertThat(display1.height).isEqualTo(height1)
-        assertThat(display1.children).hasSize(1)
-
-        val display2 = display1.children[0]
-        assertThat(display2.displayId).isEqualTo(displayId2)
-        assertThat(display2.width).isEqualTo(width2)
-        assertThat(display2.height).isEqualTo(height2)
-        assertThat(display2.children).isEmpty()
-        assertThat(display2.position).isEqualTo(POSITION_TOP)
-        assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+        verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1)
+        verifyDisplay(display1.children[0], displayId2, width2, height2, POSITION_TOP,
+            offset = width1 / 2 - width2 / 2, noOfChildren = 0)
     }
 
     @Test
@@ -97,29 +83,18 @@
         assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
 
         val display1 = topology.root!!
-        assertThat(display1.displayId).isEqualTo(displayId1)
-        assertThat(display1.width).isEqualTo(width1)
-        assertThat(display1.height).isEqualTo(height1)
-        assertThat(display1.children).hasSize(1)
+        verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1)
 
         val display2 = display1.children[0]
-        assertThat(display2.displayId).isEqualTo(displayId2)
-        assertThat(display2.width).isEqualTo(width2)
-        assertThat(display2.height).isEqualTo(height2)
-        assertThat(display2.children).hasSize(1)
-        assertThat(display2.position).isEqualTo(POSITION_TOP)
-        assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+        verifyDisplay(display1.children[0], displayId2, width2, height2, POSITION_TOP,
+            offset = width1 / 2 - width2 / 2, noOfChildren = 1)
 
         var display = display2
         for (i in 3..noOfDisplays) {
             display = display.children[0]
-            assertThat(display.displayId).isEqualTo(i)
-            assertThat(display.width).isEqualTo(width1)
-            assertThat(display.height).isEqualTo(height1)
             // The last display should have no children
-            assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0)
-            assertThat(display.position).isEqualTo(POSITION_RIGHT)
-            assertThat(display.offset).isEqualTo(0)
+            verifyDisplay(display, id = i, width1, height1, POSITION_RIGHT, offset = 0f,
+                noOfChildren = if (i < noOfDisplays) 1 else 0)
         }
     }
 
@@ -147,18 +122,11 @@
         assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
 
         var display1 = topology.root!!
-        assertThat(display1.displayId).isEqualTo(displayId1)
-        assertThat(display1.width).isEqualTo(width1)
-        assertThat(display1.height).isEqualTo(height1)
-        assertThat(display1.children).hasSize(1)
+        verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1)
 
         var display2 = display1.children[0]
-        assertThat(display2.displayId).isEqualTo(displayId2)
-        assertThat(display2.width).isEqualTo(width2)
-        assertThat(display2.height).isEqualTo(height2)
-        assertThat(display2.children).hasSize(1)
-        assertThat(display2.position).isEqualTo(POSITION_TOP)
-        assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+        verifyDisplay(display2, displayId2, width2, height2, POSITION_TOP,
+            offset = width1 / 2 - width2 / 2, noOfChildren = 1)
 
         var display = display2
         for (i in 3..noOfDisplays) {
@@ -166,13 +134,9 @@
                 continue
             }
             display = display.children[0]
-            assertThat(display.displayId).isEqualTo(i)
-            assertThat(display.width).isEqualTo(width1)
-            assertThat(display.height).isEqualTo(height1)
             // The last display should have no children
-            assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0)
-            assertThat(display.position).isEqualTo(POSITION_RIGHT)
-            assertThat(display.offset).isEqualTo(0)
+            verifyDisplay(display, id = i, width1, height1, POSITION_RIGHT, offset = 0f,
+                noOfChildren = if (i < noOfDisplays) 1 else 0)
         }
 
         topology.removeDisplay(22)
@@ -185,18 +149,11 @@
         assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
 
         display1 = topology.root!!
-        assertThat(display1.displayId).isEqualTo(displayId1)
-        assertThat(display1.width).isEqualTo(width1)
-        assertThat(display1.height).isEqualTo(height1)
-        assertThat(display1.children).hasSize(1)
+        verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1)
 
         display2 = display1.children[0]
-        assertThat(display2.displayId).isEqualTo(displayId2)
-        assertThat(display2.width).isEqualTo(width2)
-        assertThat(display2.height).isEqualTo(height2)
-        assertThat(display2.children).hasSize(1)
-        assertThat(display2.position).isEqualTo(POSITION_TOP)
-        assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+        verifyDisplay(display2, displayId2, width2, height2, POSITION_TOP,
+            offset = width1 / 2 - width2 / 2, noOfChildren = 1)
 
         display = display2
         for (i in 3..noOfDisplays) {
@@ -204,13 +161,9 @@
                 continue
             }
             display = display.children[0]
-            assertThat(display.displayId).isEqualTo(i)
-            assertThat(display.width).isEqualTo(width1)
-            assertThat(display.height).isEqualTo(height1)
             // The last display should have no children
-            assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0)
-            assertThat(display.position).isEqualTo(POSITION_RIGHT)
-            assertThat(display.offset).isEqualTo(0)
+            verifyDisplay(display, id = i, width1, height1, POSITION_RIGHT, offset = 0f,
+                noOfChildren = if (i < noOfDisplays) 1 else 0)
         }
     }
 
@@ -237,12 +190,7 @@
         topology.removeDisplay(3)
 
         assertThat(topology.primaryDisplayId).isEqualTo(displayId)
-
-        val display = topology.root!!
-        assertThat(display.displayId).isEqualTo(displayId)
-        assertThat(display.width).isEqualTo(width)
-        assertThat(display.height).isEqualTo(height)
-        assertThat(display.children).isEmpty()
+        verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0)
     }
 
     @Test
@@ -258,11 +206,46 @@
         topology.removeDisplay(displayId2)
 
         assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
-        val display = topology.root!!
-        assertThat(display.displayId).isEqualTo(displayId1)
-        assertThat(display.width).isEqualTo(width)
-        assertThat(display.height).isEqualTo(height)
-        assertThat(display.children).isEmpty()
+        verifyDisplay(topology.root!!, displayId1, width, height, noOfChildren = 0)
+    }
+
+    @Test
+    fun normalization_clampsOffsets() {
+        val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+            /* height= */ 600f, /* position= */ 0, /* offset= */ 0f)
+
+        val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 800f)
+        display1.addChild(display2)
+
+        val primaryDisplayId = 3
+        val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+            /* height= */ 200f, POSITION_LEFT, /* offset= */ -300f)
+        display1.addChild(display3)
+
+        val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+            /* height= */ 600f, POSITION_TOP, /* offset= */ 1000f)
+        display2.addChild(display4)
+
+        topology = DisplayTopology(display1, primaryDisplayId)
+        topology.normalize()
+
+        assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
+
+        val actualDisplay1 = topology.root!!
+        verifyDisplay(actualDisplay1, id = 1, width = 200f, height = 600f, noOfChildren = 2)
+
+        val actualDisplay2 = actualDisplay1.children[0]
+        verifyDisplay(actualDisplay2, id = 2, width = 600f, height = 200f, POSITION_RIGHT,
+            offset = 600f, noOfChildren = 1)
+
+        val actualDisplay3 = actualDisplay1.children[1]
+        verifyDisplay(actualDisplay3, id = 3, width = 600f, height = 200f, POSITION_LEFT,
+            offset = -200f, noOfChildren = 0)
+
+        val actualDisplay4 = actualDisplay2.children[0]
+        verifyDisplay(actualDisplay4, id = 4, width = 200f, height = 600f, POSITION_TOP,
+            offset = 600f, noOfChildren = 0)
     }
 
     @Test
@@ -289,34 +272,19 @@
         assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
 
         val actualDisplay1 = topology.root!!
-        assertThat(actualDisplay1.displayId).isEqualTo(1)
-        assertThat(actualDisplay1.width).isEqualTo(200f)
-        assertThat(actualDisplay1.height).isEqualTo(600f)
-        assertThat(actualDisplay1.children).hasSize(2)
+        verifyDisplay(actualDisplay1, id = 1, width = 200f, height = 600f, noOfChildren = 2)
 
         val actualDisplay2 = actualDisplay1.children[0]
-        assertThat(actualDisplay2.displayId).isEqualTo(2)
-        assertThat(actualDisplay2.width).isEqualTo(600f)
-        assertThat(actualDisplay2.height).isEqualTo(200f)
-        assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay2.offset).isEqualTo(0f)
-        assertThat(actualDisplay2.children).hasSize(1)
+        verifyDisplay(actualDisplay2, id = 2, width = 600f, height = 200f, POSITION_RIGHT,
+            offset = 0f, noOfChildren = 1)
 
         val actualDisplay3 = actualDisplay1.children[1]
-        assertThat(actualDisplay3.displayId).isEqualTo(3)
-        assertThat(actualDisplay3.width).isEqualTo(600f)
-        assertThat(actualDisplay3.height).isEqualTo(200f)
-        assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay3.offset).isEqualTo(400f)
-        assertThat(actualDisplay3.children).isEmpty()
+        verifyDisplay(actualDisplay3, id = 3, width = 600f, height = 200f, POSITION_RIGHT,
+            offset = 400f, noOfChildren = 0)
 
         val actualDisplay4 = actualDisplay2.children[0]
-        assertThat(actualDisplay4.displayId).isEqualTo(4)
-        assertThat(actualDisplay4.width).isEqualTo(200f)
-        assertThat(actualDisplay4.height).isEqualTo(600f)
-        assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay4.offset).isEqualTo(0f)
-        assertThat(actualDisplay4.children).isEmpty()
+        verifyDisplay(actualDisplay4, id = 4, width = 200f, height = 600f, POSITION_RIGHT,
+            offset = 0f, noOfChildren = 0)
     }
 
     @Test
@@ -344,34 +312,19 @@
         assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
 
         val actualDisplay1 = topology.root!!
-        assertThat(actualDisplay1.displayId).isEqualTo(1)
-        assertThat(actualDisplay1.width).isEqualTo(200f)
-        assertThat(actualDisplay1.height).isEqualTo(600f)
-        assertThat(actualDisplay1.children).hasSize(1)
+        verifyDisplay(actualDisplay1, id = 1, width = 200f, height = 600f, noOfChildren = 1)
 
         val actualDisplay2 = actualDisplay1.children[0]
-        assertThat(actualDisplay2.displayId).isEqualTo(2)
-        assertThat(actualDisplay2.width).isEqualTo(200f)
-        assertThat(actualDisplay2.height).isEqualTo(600f)
-        assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay2.offset).isEqualTo(0f)
-        assertThat(actualDisplay2.children).hasSize(2)
+        verifyDisplay(actualDisplay2, id = 2, width = 200f, height = 600f, POSITION_RIGHT,
+            offset = 0f, noOfChildren = 2)
 
         val actualDisplay3 = actualDisplay2.children[1]
-        assertThat(actualDisplay3.displayId).isEqualTo(3)
-        assertThat(actualDisplay3.width).isEqualTo(600f)
-        assertThat(actualDisplay3.height).isEqualTo(200f)
-        assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay3.offset).isEqualTo(10f)
-        assertThat(actualDisplay3.children).isEmpty()
+        verifyDisplay(actualDisplay3, id = 3, width = 600f, height = 200f, POSITION_RIGHT,
+            offset = 10f, noOfChildren = 0)
 
         val actualDisplay4 = actualDisplay2.children[0]
-        assertThat(actualDisplay4.displayId).isEqualTo(4)
-        assertThat(actualDisplay4.width).isEqualTo(200f)
-        assertThat(actualDisplay4.height).isEqualTo(600f)
-        assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay4.offset).isEqualTo(210f)
-        assertThat(actualDisplay4.children).isEmpty()
+        verifyDisplay(actualDisplay4, id = 4, width = 200f, height = 600f, POSITION_RIGHT,
+            offset = 210f, noOfChildren = 0)
     }
 
     @Test
@@ -397,26 +350,15 @@
         assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
 
         val actualDisplay1 = topology.root!!
-        assertThat(actualDisplay1.displayId).isEqualTo(1)
-        assertThat(actualDisplay1.width).isEqualTo(200f)
-        assertThat(actualDisplay1.height).isEqualTo(50f)
-        assertThat(actualDisplay1.children).hasSize(1)
+        verifyDisplay(actualDisplay1, id = 1, width = 200f, height = 50f, noOfChildren = 1)
 
         val actualDisplay2 = actualDisplay1.children[0]
-        assertThat(actualDisplay2.displayId).isEqualTo(2)
-        assertThat(actualDisplay2.width).isEqualTo(600f)
-        assertThat(actualDisplay2.height).isEqualTo(200f)
-        assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay2.offset).isEqualTo(0f)
-        assertThat(actualDisplay2.children).hasSize(1)
+        verifyDisplay(actualDisplay2, id = 2, width = 600f, height = 200f, POSITION_RIGHT,
+            offset = 0f, noOfChildren = 1)
 
         val actualDisplay3 = actualDisplay2.children[0]
-        assertThat(actualDisplay3.displayId).isEqualTo(3)
-        assertThat(actualDisplay3.width).isEqualTo(600f)
-        assertThat(actualDisplay3.height).isEqualTo(200f)
-        assertThat(actualDisplay3.position).isEqualTo(POSITION_BOTTOM)
-        assertThat(actualDisplay3.offset).isEqualTo(0f)
-        assertThat(actualDisplay3.children).isEmpty()
+        verifyDisplay(actualDisplay3, id = 3, width = 600f, height = 200f, POSITION_BOTTOM,
+            offset = 0f, noOfChildren = 0)
     }
 
     @Test
@@ -443,34 +385,19 @@
         assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
 
         val actualDisplay1 = topology.root!!
-        assertThat(actualDisplay1.displayId).isEqualTo(1)
-        assertThat(actualDisplay1.width).isEqualTo(200f)
-        assertThat(actualDisplay1.height).isEqualTo(600f)
-        assertThat(actualDisplay1.children).hasSize(1)
+        verifyDisplay(actualDisplay1, id = 1, width = 200f, height = 600f, noOfChildren = 1)
 
         val actualDisplay2 = actualDisplay1.children[0]
-        assertThat(actualDisplay2.displayId).isEqualTo(2)
-        assertThat(actualDisplay2.width).isEqualTo(200f)
-        assertThat(actualDisplay2.height).isEqualTo(600f)
-        assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay2.offset).isEqualTo(0f)
-        assertThat(actualDisplay2.children).hasSize(1)
+        verifyDisplay(actualDisplay2, id = 2, width = 200f, height = 600f, POSITION_RIGHT,
+            offset = 0f, noOfChildren = 1)
 
         val actualDisplay3 = actualDisplay2.children[0]
-        assertThat(actualDisplay3.displayId).isEqualTo(3)
-        assertThat(actualDisplay3.width).isEqualTo(600f)
-        assertThat(actualDisplay3.height).isEqualTo(200f)
-        assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay3.offset).isEqualTo(400f)
-        assertThat(actualDisplay3.children).hasSize(1)
+        verifyDisplay(actualDisplay3, id = 3, width = 600f, height = 200f, POSITION_RIGHT,
+            offset = 400f, noOfChildren = 1)
 
         val actualDisplay4 = actualDisplay3.children[0]
-        assertThat(actualDisplay4.displayId).isEqualTo(4)
-        assertThat(actualDisplay4.width).isEqualTo(200f)
-        assertThat(actualDisplay4.height).isEqualTo(600f)
-        assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay4.offset).isEqualTo(-400f)
-        assertThat(actualDisplay4.children).isEmpty()
+        verifyDisplay(actualDisplay4, id = 4, width = 200f, height = 600f, POSITION_RIGHT,
+            offset = -400f, noOfChildren = 0)
     }
 
     @Test
@@ -513,7 +440,7 @@
         val nodes = rearrangeRects(
             RectF(0f, 0f, 150f, 100f),
             RectF(-150f, 0f, 0f, 100f),
-            RectF(0f,-100f, 150f, 0f),
+            RectF(0f, -100f, 150f, 0f),
             RectF(150f, 0f, 300f, 100f),
             RectF(0f, 100f, 150f, 200f),
         )
@@ -584,15 +511,15 @@
     @Test
     fun rearrange_useLargerEdge() {
         val nodes = rearrangeRects(
-            //444111
-            //444111
-            //444111
-            //  000222
-            //  000222
-            //  000222
-            //    333
-            //    333
-            //    333
+            // 444111
+            // 444111
+            // 444111
+            //   000222
+            //   000222
+            //   000222
+            //     333
+            //     333
+            //     333
             RectF(20f, 30f, 50f, 60f),
             RectF(30f, 0f, 60f, 30f),
             RectF(50f, 30f, 80f, 60f),
@@ -617,24 +544,25 @@
     @Test
     fun rearrange_closeGaps() {
         val nodes = rearrangeRects(
-            //000
-            //000 111
-            //000 111
-            //    111
+            // 000
+            // 000 111
+            // 000 111
+            //     111
             //
-            //        222
-            //        222
-            //        222
+            //         222
+            //         222
+            //         222
             RectF(0f, 0f, 30f, 30f),
             RectF(40f, 10f, 70f, 40f),
-            RectF(80.5f, 50f, 110f, 80f),  // left+=0.5 to cause a preference for TOP/BOTTOM attach
+            RectF(80.5f, 50f, 110f, 80f), // left+=0.5 to cause a preference for
+                                                            // TOP/BOTTOM attach
         )
 
         assertPositioning(
             nodes,
             // In the case of corner adjacency, we prefer a left/right attachment.
             Pair(POSITION_RIGHT, 10f),
-            Pair(POSITION_BOTTOM, 40.5f),  // TODO: fix implementation to remove this gap
+            Pair(POSITION_BOTTOM, 30f),
         )
 
         assertThat(nodes[0].children).containsExactly(nodes[1])
@@ -642,11 +570,50 @@
         assertThat(nodes[2].children).isEmpty()
     }
 
+    @Test
+    fun copy() {
+        val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+            /* height= */ 600f, /* position= */ 0, /* offset= */ 0f)
+
+        val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
+        display1.addChild(display2)
+
+        val primaryDisplayId = 3
+        val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
+        display1.addChild(display3)
+
+        val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+        display2.addChild(display4)
+
+        topology = DisplayTopology(display1, primaryDisplayId)
+        val copy = topology.copy()
+
+        assertThat(copy.primaryDisplayId).isEqualTo(primaryDisplayId)
+
+        val actualDisplay1 = copy.root!!
+        verifyDisplay(actualDisplay1, id = 1, width = 200f, height = 600f, noOfChildren = 2)
+
+        val actualDisplay2 = actualDisplay1.children[0]
+        verifyDisplay(actualDisplay2, id = 2, width = 600f, height = 200f, POSITION_RIGHT,
+            offset = 0f, noOfChildren = 1)
+
+        val actualDisplay3 = actualDisplay1.children[1]
+        verifyDisplay(actualDisplay3, id = 3, width = 600f, height = 200f, POSITION_RIGHT,
+            offset = 400f, noOfChildren = 0)
+
+        val actualDisplay4 = actualDisplay2.children[0]
+        verifyDisplay(actualDisplay4, id = 4, width = 200f, height = 600f, POSITION_RIGHT,
+            offset = 0f, noOfChildren = 0)
+    }
+
     /**
      * Runs the rearrange algorithm and returns the resulting tree as a list of nodes, with the
      * root at index 0. The number of nodes is inferred from the number of positions passed.
      */
-    private fun rearrangeRects(vararg pos : RectF) : List<DisplayTopology.TreeNode> {
+    private fun rearrangeRects(vararg pos: RectF): List<DisplayTopology.TreeNode> {
         // Generates a linear sequence of nodes in order in the List from root to leaf,
         // left-to-right. IDs are ascending from 0 to count - 1.
 
@@ -667,9 +634,20 @@
         return nodes
     }
 
+    private fun verifyDisplay(display: DisplayTopology.TreeNode, id: Int, width: Float,
+                              height: Float, @DisplayTopology.TreeNode.Position position: Int = 0,
+                              offset: Float = 0f, noOfChildren: Int) {
+        assertThat(display.displayId).isEqualTo(id)
+        assertThat(display.width).isEqualTo(width)
+        assertThat(display.height).isEqualTo(height)
+        assertThat(display.position).isEqualTo(position)
+        assertThat(display.offset).isEqualTo(offset)
+        assertThat(display.children).hasSize(noOfChildren)
+    }
+
     private fun assertPositioning(
-            nodes : List<DisplayTopology.TreeNode>, vararg positions : Pair<Int, Float>) {
-        assertThat(nodes.drop(1).map { Pair(it.position, it.offset )})
+            nodes: List<DisplayTopology.TreeNode>, vararg positions: Pair<Int, Float>) {
+        assertThat(nodes.drop(1).map { Pair(it.position, it.offset) })
             .containsExactly(*positions)
             .inOrder()
     }
diff --git a/core/tests/coretests/src/android/os/BinderProxyTest.java b/core/tests/coretests/src/android/os/BinderProxyTest.java
index a903ed9..335791c 100644
--- a/core/tests/coretests/src/android/os/BinderProxyTest.java
+++ b/core/tests/coretests/src/android/os/BinderProxyTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.fail;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -42,7 +43,7 @@
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = PowerManager.class)
+@IgnoreUnderRavenwood(blockedBy = ActivityManager.class)
 public class BinderProxyTest {
     private static class CountingListener implements Binder.ProxyTransactListener {
         int mStartedCount;
@@ -62,7 +63,7 @@
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     private Context mContext;
-    private PowerManager mPowerManager;
+    private ActivityManager mActivityManager;
 
     /**
      * Setup any common data for the upcoming tests.
@@ -70,7 +71,7 @@
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
     }
 
     @Test
@@ -80,7 +81,7 @@
         Binder.setProxyTransactListener(listener);
         Binder.setProxyTransactListener(null);
 
-        mPowerManager.isInteractive();
+        mActivityManager.isUserRunning(7); // something which does a binder call
 
         assertEquals(0, listener.mStartedCount);
         assertEquals(0, listener.mEndedCount);
@@ -92,7 +93,7 @@
         CountingListener listener = new CountingListener();
         Binder.setProxyTransactListener(listener);
 
-        mPowerManager.isInteractive();
+        mActivityManager.isUserRunning(27); // something which does a binder call
 
         assertEquals(1, listener.mStartedCount);
         assertEquals(1, listener.mEndedCount);
@@ -112,7 +113,7 @@
         });
 
         // Check it does not throw..
-        mPowerManager.isInteractive();
+        mActivityManager.isUserRunning(47); // something which does a binder call
     }
 
     private IBinder mRemoteBinder = null;
diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java
index e8c701e..2a67716a 100644
--- a/core/tests/coretests/src/android/os/BuildTest.java
+++ b/core/tests/coretests/src/android/os/BuildTest.java
@@ -16,8 +16,10 @@
 
 package android.os;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -103,4 +105,99 @@
         mSetFlagsRule.disableFlags(Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM);
         assertFalse(Flags.androidOsBuildVanillaIceCream());
     }
+
+    @Test
+    public void testParseFullVersionCorrectInputMajorDotMinor() throws Exception {
+        int version = Build.parseFullVersion("12.34");
+        assertEquals(12, Build.getMajorSdkVersion(version));
+        assertEquals(34, Build.getMinorSdkVersion(version));
+    }
+
+    @Test
+    public void testParseFullVersionCorrectInputOmitDotMinor() throws Exception {
+        int version = Build.parseFullVersion("1234");
+        assertEquals(1234, Build.getMajorSdkVersion(version));
+        assertEquals(0, Build.getMinorSdkVersion(version));
+    }
+
+    @Test
+    public void testParseFullVersionCorrectInputCurDevelopment() throws Exception {
+        int version = Build.parseFullVersion(Integer.toString(Build.VERSION_CODES.CUR_DEVELOPMENT));
+        assertEquals(Build.VERSION_CODES.CUR_DEVELOPMENT, Build.getMajorSdkVersion(version));
+        assertEquals(0, Build.getMinorSdkVersion(version));
+    }
+
+    @Test
+    public void testParseFullVersionIncorrectInputEmptyString() throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> {
+            Build.parseFullVersion("");
+        });
+    }
+
+    @Test
+    public void testParseFullVersionIncorrectInputNoNumbersInString() throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> {
+            Build.parseFullVersion("foobar");
+        });
+    }
+
+    @Test
+    public void testParseFullVersionIncorrectInputUnexpectedPatchVersion() throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> {
+            Build.parseFullVersion("1.2.3");
+        });
+    }
+
+    @Test
+    public void testParseFullVersionIncorrectInputLeadingDotMissingMajorVersion() throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> {
+            Build.parseFullVersion(".1234");
+        });
+    }
+
+    @Test
+    public void testParseFullVersionIncorrectInputTrailingDotMissingMinorVersion()
+            throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> {
+            Build.parseFullVersion("1234.");
+        });
+    }
+
+    @Test
+    public void testParseFullVersionIncorrectInputNegativeMajorVersion() throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> {
+            Build.parseFullVersion("-12.34");
+        });
+    }
+
+    @Test
+    public void testParseFullVersionIncorrectInputNegativeMinorVersion() throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> {
+            Build.parseFullVersion("12.-34");
+        });
+    }
+
+    @Test
+    public void testFullVersionToStringCorrectInput() throws Exception {
+        assertEquals("0.0", Build.fullVersionToString(0));
+        assertEquals("1.0", Build.fullVersionToString(1 * 100000 + 0));
+        assertEquals("1.1", Build.fullVersionToString(1 * 100000 + 1));
+        assertEquals("12.34", Build.fullVersionToString(12 * 100000 + 34));
+    }
+
+    @Test
+    public void testFullVersionToStringSameStringAfterRoundTripViaParseFullVersion()
+            throws Exception {
+        String s = "12.34";
+        int major = Build.getMajorSdkVersion(Build.parseFullVersion(s));
+        int minor = Build.getMinorSdkVersion(Build.parseFullVersion(s));
+        assertEquals(s, Build.fullVersionToString(major * 100000 + minor));
+    }
+
+    @Test
+    public void testFullVersionToStringIncorrectInputNegativeVersion() throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> {
+            Build.fullVersionToString(-1);
+        });
+    }
 }
diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java
index 549e666..30f6636 100644
--- a/core/tests/coretests/src/android/os/MessageQueueTest.java
+++ b/core/tests/coretests/src/android/os/MessageQueueTest.java
@@ -26,262 +26,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@Suppress  // Failing.
 @RunWith(AndroidJUnit4.class)
 public class MessageQueueTest {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
-    private static class BaseTestHandler extends TestHandlerThread {
-        Handler mHandler;
-        int mLastMessage;
-        int mCount;
-
-        public BaseTestHandler() {
-        }
-
-        public void go() {
-            mHandler = new Handler() {
-                public void handleMessage(Message msg) {
-                    BaseTestHandler.this.handleMessage(msg);
-                }
-            };
-        }
-
-        public void handleMessage(Message msg) {
-            if (!msg.isInUse()) {
-                failure(new RuntimeException(
-                        "msg.isInuse is false, should always be true, #" + msg.what));
-            }
-            if (mCount <= mLastMessage) {
-                if (msg.what != mCount) {
-                    failure(new RuntimeException(
-                            "Expected message #" + mCount
-                                    + ", received #" + msg.what));
-                } else if (mCount == mLastMessage) {
-                    success();
-                }
-                mCount++;
-            } else {
-                failure(new RuntimeException(
-                        "Message received after done, #" + msg.what));
-            }
-        }
-    }
-
-    @Test
-    @MediumTest
-    public void testMessageOrder() throws Exception {
-        TestHandlerThread tester = new BaseTestHandler() {
-            public void go() {
-                super.go();
-                long now = SystemClock.uptimeMillis() + 200;
-                mLastMessage = 4;
-                mCount = 0;
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(2), now + 1);
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(3), now + 2);
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(4), now + 2);
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(0), now + 0);
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(1), now + 0);
-            }
-        };
-
-        tester.doTest(1000);
-    }
-
-    @Test
-    @MediumTest
-    public void testAtFrontOfQueue() throws Exception {
-        TestHandlerThread tester = new BaseTestHandler() {
-            public void go() {
-                super.go();
-                long now = SystemClock.uptimeMillis() + 200;
-                mLastMessage = 3;
-                mCount = 0;
-                mHandler.sendMessageAtTime(mHandler.obtainMessage(3), now);
-                mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(2));
-                mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(0));
-            }
-
-            public void handleMessage(Message msg) {
-                super.handleMessage(msg);
-                if (msg.what == 0) {
-                    mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(1));
-                }
-            }
-        };
-
-        tester.doTest(1000);
-    }
-
-    private static class TestFieldIntegrityHandler extends TestHandlerThread {
-        Handler mHandler;
-        int mLastMessage;
-        int mCount;
-
-        public TestFieldIntegrityHandler() {
-        }
-
-        public void go() {
-            mHandler = new Handler() {
-                public void handleMessage(Message msg) {
-                    TestFieldIntegrityHandler.this.handleMessage(msg);
-                }
-            };
-        }
-
-        public void handleMessage(Message msg) {
-            if (!msg.isInUse()) {
-                failure(new RuntimeException(
-                        "msg.isInuse is false, should always be true, #" + msg.what));
-            }
-            if (mCount <= mLastMessage) {
-                if (msg.what != mCount) {
-                    failure(new RuntimeException(
-                            "Expected message #" + mCount
-                                    + ", received #" + msg.what));
-                } else if (mCount == mLastMessage) {
-                    success();
-                }
-                mCount++;
-            } else {
-                failure(new RuntimeException(
-                        "Message received after done, #" + msg.what));
-            }
-        }
-    }
-
-    @Test
-    @MediumTest
-    public void testFieldIntegrity() throws Exception {
-
-        TestHandlerThread tester = new TestFieldIntegrityHandler() {
-            Bundle mBundle;
-
-            public void go() {
-                super.go();
-                mLastMessage = 1;
-                mCount = 0;
-                mHandler.sendMessage(mHandler.obtainMessage(0));
-            }
-
-            public void handleMessage(Message msg) {
-                super.handleMessage(msg);
-                if (msg.what == 0) {
-                    msg.flags = Message.FLAGS_TO_CLEAR_ON_COPY_FROM;
-                    msg.what = 1;
-                    msg.arg1 = 456;
-                    msg.arg2 = 789;
-                    msg.obj = this;
-                    msg.replyTo = null;
-                    mBundle = new Bundle();
-                    msg.data = mBundle;
-                    msg.data.putString("key", "value");
-
-                    Message newMsg = mHandler.obtainMessage();
-                    newMsg.copyFrom(msg);
-                    if (newMsg.isInUse() != false) {
-                        failure(new RuntimeException(
-                                "newMsg.isInUse is true should be false after copyFrom"));
-                    }
-                    if (newMsg.flags != 0) {
-                        failure(new RuntimeException(String.format(
-                        "newMsg.flags is %d should be 0 after copyFrom", newMsg.flags)));
-                    }
-                    if (newMsg.what != 1) {
-                        failure(new RuntimeException(String.format(
-                                "newMsg.what is %d should be %d after copyFrom", newMsg.what, 1)));
-                    }
-                    if (newMsg.arg1 != 456) {
-                        failure(new RuntimeException(String.format(
-                                "newMsg.arg1 is %d should be %d after copyFrom", msg.arg1, 456)));
-                    }
-                    if (newMsg.arg2 != 789) {
-                        failure(new RuntimeException(String.format(
-                                "newMsg.arg2 is %d should be %d after copyFrom", msg.arg2, 789)));
-                    }
-                    if (newMsg.obj != this) {
-                        failure(new RuntimeException(
-                                "newMsg.obj should be 'this' after copyFrom"));
-                    }
-                    if (newMsg.replyTo != null) {
-                        failure(new RuntimeException(
-                                "newMsg.replyTo should be null after copyFrom"));
-                    }
-                    if (newMsg.data == mBundle) {
-                        failure(new RuntimeException(
-                                "newMsg.data should NOT be mBundle after copyFrom"));
-                    }
-                    if (!newMsg.data.getString("key").equals(mBundle.getString("key"))) {
-                        failure(new RuntimeException(String.format(
-                                "newMsg.data.getString(\"key\") is %s and does not equal" +
-                                " mBundle.getString(\"key\") which is %s after copyFrom",
-                                newMsg.data.getString("key"),  mBundle.getString("key"))));
-                    }
-                    if (newMsg.when != 0) {
-                        failure(new RuntimeException(String.format(
-                                "newMsg.when is %d should be 0 after copyFrom", newMsg.when)));
-                    }
-                    if (newMsg.target != mHandler) {
-                        failure(new RuntimeException(
-                                "newMsg.target is NOT mHandler after copyFrom"));
-                    }
-                    if (newMsg.callback != null) {
-                        failure(new RuntimeException(
-                                "newMsg.callback is NOT null after copyFrom"));
-                    }
-
-                    mHandler.sendMessage(newMsg);
-                } else if (msg.what == 1) {
-                    if (msg.isInUse() != true) {
-                        failure(new RuntimeException(String.format(
-                                "msg.isInUse is false should be true after when processing %d",
-                                msg.what)));
-                    }
-                    if (msg.arg1 != 456) {
-                        failure(new RuntimeException(String.format(
-                                "msg.arg1 is %d should be %d when processing # %d",
-                                msg.arg1, 456, msg.what)));
-                    }
-                    if (msg.arg2 != 789) {
-                        failure(new RuntimeException(String.format(
-                                "msg.arg2 is %d should be %d when processing # %d",
-                                msg.arg2, 789, msg.what)));
-                    }
-                    if (msg.obj != this) {
-                        failure(new RuntimeException(String.format(
-                                "msg.obj should be 'this' when processing # %d", msg.what)));
-                    }
-                    if (msg.replyTo != null) {
-                        failure(new RuntimeException(String.format(
-                                "msg.replyTo should be null when processing # %d", msg.what)));
-                    }
-                    if (!msg.data.getString("key").equals(mBundle.getString("key"))) {
-                        failure(new RuntimeException(String.format(
-                                "msg.data.getString(\"key\") is %s and does not equal" +
-                                " mBundle.getString(\"key\") which is %s when processing # %d",
-                                msg.data.getString("key"),  mBundle.getString("key"), msg.what)));
-                    }
-                    if (msg.when != 0) {
-                        failure(new RuntimeException(String.format(
-                                "msg.when is %d should be 0 when processing # %d",
-                                msg.when, msg.what)));
-                    }
-                    if (msg.target != null) {
-                        failure(new RuntimeException(String.format(
-                                "msg.target is NOT null when processing # %d", msg.what)));
-                    }
-                    if (msg.callback != null) {
-                        failure(new RuntimeException(String.format(
-                                "msg.callback is NOT null when processing # %d", msg.what)));
-                    }
-                } else {
-                    failure(new RuntimeException(String.format(
-                            "Unexpected msg.what is %d" + msg.what)));
-                }
-            }
-        };
-
-        tester.doTest(1000);
-    }
 }
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index 4620cb8..c45080f 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -15,3 +15,6 @@
 
 # RemoteCallbackList
 per-file RemoteCallbackListTest.java = shayba@google.com
+
+# MessageQueue
+per-file MessageQueueTest.java = mfasheh@google.com, shayba@google.com
diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
index e4e965f..b8d1979 100644
--- a/core/tests/coretests/src/android/os/PowerManagerTest.java
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -30,11 +30,10 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
 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.RavenwoodFlagsValueProvider;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.InstrumentationRegistry;
@@ -54,7 +53,7 @@
 import java.util.concurrent.Executors;
 
 @RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = PowerManager.class)
+@DisabledOnRavenwood(blockedBy = PowerManager.class)
 public class PowerManagerTest {
 
     private static final String TAG = "PowerManagerTest";
@@ -83,19 +82,14 @@
             String[] keys, String[] values);
 
     static {
-        if (!RavenwoodRule.isUnderRavenwood()) {
+        if (!RavenwoodRule.isOnRavenwood()) {
             System.loadLibrary("powermanagertest_jni");
         }
     }
 
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
     // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect.
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood()
-            ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
-            : DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     /**
      * Setup any common data for the upcoming tests.
diff --git a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
index 58a434a..a04a662 100644
--- a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
+++ b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
@@ -18,12 +18,10 @@
 
 import static org.junit.Assert.assertThrows;
 
-import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
 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.RavenwoodFlagsValueProvider;
-import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
@@ -34,16 +32,11 @@
 import org.mockito.MockitoAnnotations;
 
 @RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = WorkDuration.class)
+@DisabledOnRavenwood(blockedBy = WorkDuration.class)
 public class WorkDurationUnitTest {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
     // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect.
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood()
-            ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
-            : DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Before
     public void setUp() {
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
index 726ee85..915ace0 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
@@ -16,9 +16,9 @@
 
 package android.view;
 
-import static androidx.test.InstrumentationRegistry.getTargetContext;
+import static android.view.flags.Flags.FLAG_SCROLL_CAPTURE_TARGET_Z_ORDER_FIX;
 
-import static com.google.common.truth.Truth.assertThat;
+import static androidx.test.InstrumentationRegistry.getTargetContext;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -32,13 +32,16 @@
 import android.graphics.Rect;
 import android.os.CancellationSignal;
 import android.os.SystemClock;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.annotation.NonNull;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -56,6 +59,8 @@
 @RunWith(AndroidJUnit4.class)
 public class ScrollCaptureSearchResultsTest {
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     private static final Rect EMPTY_RECT = new Rect();
     private static final String TAG = "Test";
@@ -98,6 +103,45 @@
         assertNull("Expected null due to no valid targets", results.getTopResult());
     }
 
+    /**
+     * A scrolling target should be excluded even when larger if it will be drawn over by another
+     * scrolling target.
+     */
+    @EnableFlags(FLAG_SCROLL_CAPTURE_TARGET_Z_ORDER_FIX)
+    @Test
+    public void testCoveredTargetsAreExcluded() {
+        ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec);
+
+        FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(mDirectExec);
+        callback1.setScrollBounds(new Rect(0, 0, 200, 200)); // 200 tall
+        View view1 = new FakeView(getTargetContext(), 0, 0, 200, 200, 1);
+        ScrollCaptureTarget target1 = createTargetWithView(view1, callback1,
+                new Rect(0, 0, 200, 200), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
+
+        FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(mDirectExec);
+        callback2.setScrollBounds(new Rect(0, 0, 200, 180)); // 180 tall
+        View view2 = new FakeView(getTargetContext(), 0, 20, 200, 200, 2);
+        ScrollCaptureTarget target2 = createTargetWithView(view2, callback2,
+                new Rect(0, 0, 200, 180), new Point(0, 20), View.SCROLL_CAPTURE_HINT_AUTO);
+
+        // Top z-order but smaller, and non-intersecting. (positioned further Y than the first two)
+        FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(mDirectExec);
+        callback3.setScrollBounds(new Rect(0, 0, 50, 50));
+        View view3 = new FakeView(getTargetContext(), 75, 250, 125, 300, 3);
+        ScrollCaptureTarget target3 = createTargetWithView(view3, callback3,
+                new Rect(0, 0, 50, 50), new Point(75, 250), View.SCROLL_CAPTURE_HINT_AUTO);
+
+        results.addTarget(target1);
+        results.addTarget(target2);
+        results.addTarget(target3);
+
+        assertTrue(results.isComplete());
+        ScrollCaptureTarget result = results.getTopResult();
+        assertSame("Expected the second target because of higher z-Index", target2, result);
+        assertEquals("result has wrong scroll bounds",
+                new Rect(0, 0, 200, 180), result.getScrollBounds());
+    }
+
     @Test
     public void testSingleTarget() {
         ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec);
@@ -152,29 +196,29 @@
 
         // 2 - 10x10 + HINT_INCLUDE
         FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(mDirectExec);
-        callback2.setScrollBounds(new Rect(0, 0, 10, 10));
-        ViewGroup targetView2 = new FakeView(getTargetContext(), 0, 0, 60, 60, 2);
+        callback2.setScrollBounds(new Rect(25, 25, 35, 35)); // 10x10
+        ViewGroup targetView2 = new FakeView(getTargetContext(), 0, 60, 60, 120, 2);
         ScrollCaptureTarget target2 = createTargetWithView(targetView2, callback2,
                  new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_INCLUDE);
 
         // 3 - 20x20 + AUTO
         FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(mDirectExec);
         callback3.setScrollBounds(new Rect(0, 0, 20, 20));
-        ViewGroup targetView3 = new FakeView(getTargetContext(), 0, 0, 60, 60, 3);
+        ViewGroup targetView3 = new FakeView(getTargetContext(), 0, 120, 60, 180, 3);
         ScrollCaptureTarget target3 = createTargetWithView(targetView3, callback3,
                 new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
 
         // 4 - 30x30 + AUTO
         FakeScrollCaptureCallback callback4 = new FakeScrollCaptureCallback(mDirectExec);
         callback4.setScrollBounds(new Rect(0, 0, 10, 10));
-        ViewGroup targetView4 = new FakeView(getTargetContext(), 0, 0, 60, 60, 4);
+        ViewGroup targetView4 = new FakeView(getTargetContext(), 0, 180, 60, 240, 4);
         ScrollCaptureTarget target4 = createTargetWithView(targetView4, callback4,
                 new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
 
         // 5 - 10x10 + child of #4
         FakeScrollCaptureCallback callback5 = new FakeScrollCaptureCallback(mDirectExec);
         callback5.setScrollBounds(new Rect(0, 0, 10, 10));
-        ViewGroup targetView5 = new FakeView(getTargetContext(), 0, 0, 60, 60, 5);
+        ViewGroup targetView5 = new FakeView(getTargetContext(), 0, 0, 60, 30, 5);
         ScrollCaptureTarget target5 = createTargetWithView(targetView5, callback5,
                 new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
         targetView4.addView(targetView5);
@@ -182,7 +226,7 @@
         // 6 - 20x20 + child of #4
         FakeScrollCaptureCallback callback6 = new FakeScrollCaptureCallback(mDirectExec);
         callback6.setScrollBounds(new Rect(0, 0, 20, 20));
-        ViewGroup targetView6 = new FakeView(getTargetContext(), 0, 0, 60, 60, 6);
+        ViewGroup targetView6 = new FakeView(getTargetContext(), 0, 30, 30, 60, 6);
         ScrollCaptureTarget target6 = createTargetWithView(targetView6, callback6,
                 new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
         targetView4.addView(targetView6);
@@ -194,20 +238,10 @@
         results.addTarget(target4);
         results.addTarget(target5);
         results.addTarget(target6);
-        assertTrue(results.isComplete());
+        assertTrue("results.isComplete()", results.isComplete());
 
         // Verify "top" result
-        assertEquals(target2, results.getTopResult());
-
-        // Verify priority ("best" first)
-        assertThat(results.getTargets())
-                .containsExactly(
-                        target2,
-                        target6,
-                        target5,
-                        target4,
-                        target3,
-                        target1);
+        assertEquals("top result", target2, results.getTopResult());
     }
 
     /**
@@ -291,27 +325,22 @@
                 new Rect(1, 2, 3, 4), result.getScrollBounds());
     }
 
-    private void setupTargetView(View view, Rect localVisibleRect, int scrollCaptureHint) {
-        view.setScrollCaptureHint(scrollCaptureHint);
-        view.onVisibilityAggregated(true);
-        // Treat any offset as padding, outset localVisibleRect on all sides and use this as
-        // child bounds
-        Rect bounds = new Rect(localVisibleRect);
-        bounds.inset(-bounds.left, -bounds.top, bounds.left, bounds.top);
-        view.layout(bounds.left, bounds.top, bounds.right, bounds.bottom);
-        view.onVisibilityAggregated(true);
-    }
-
     private ScrollCaptureTarget createTarget(ScrollCaptureCallback callback, Rect localVisibleRect,
             Point positionInWindow, int scrollCaptureHint) {
-        View mockView = new View(getTargetContext());
+        Rect bounds = new Rect(localVisibleRect);
+        // Use localVisibleRect as position, treat left/top offset as padding
+        bounds.left = 0;
+        bounds.top = 0;
+        View mockView = new FakeView(getTargetContext(), bounds.left, bounds.top, bounds.right,
+                bounds.bottom, View.NO_ID);
         return createTargetWithView(mockView, callback, localVisibleRect, positionInWindow,
                 scrollCaptureHint);
     }
 
     private ScrollCaptureTarget createTargetWithView(View view, ScrollCaptureCallback callback,
             Rect localVisibleRect, Point positionInWindow, int scrollCaptureHint) {
-        setupTargetView(view, localVisibleRect, scrollCaptureHint);
+        view.setScrollCaptureHint(scrollCaptureHint);
+        view.onVisibilityAggregated(true);
         return new ScrollCaptureTarget(view, localVisibleRect, positionInWindow, callback);
     }
 
@@ -326,6 +355,32 @@
         @Override
         protected void onLayout(boolean changed, int l, int t, int r, int b) {
         }
+
+        /** Ignores window attachment state. The standard impl always returns [0,0] if the view is
+         *  not attached. This override allows testing without dealing with AttachInfo.
+         */
+        @Override
+        public void getLocationInWindow(int[] outLocation) {
+            outLocation[0] = mLeft;
+            outLocation[1] = mTop;
+            ViewParent viewParent = getParent();
+            while (viewParent instanceof View) {
+                final View view = (View) viewParent;
+
+                outLocation[0] -= view.mScrollX;
+                outLocation[1] -= view.mScrollY;
+
+                // Explicitly do not handle matrix/transforms, not needed for testing
+                if (!view.hasIdentityMatrix()) {
+                    throw new IllegalStateException("This mock does not handle transforms!");
+                }
+
+                outLocation[0] += view.mLeft;
+                outLocation[1] += view.mTop;
+
+                viewParent = view.mParent;
+            }
+        }
     }
 
     static class FakeScrollCaptureCallback implements ScrollCaptureCallback {
diff --git a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
index 25608c3..adf7a72 100644
--- a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.flags.Flags.FLAG_SCROLL_CAPTURE_TARGET_Z_ORDER_FIX;
+
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertEquals;
@@ -30,16 +32,20 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.CancellationSignal;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.annotation.NonNull;
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -51,6 +57,9 @@
 @RunWith(MockitoJUnitRunner.class)
 public class ViewGroupScrollCaptureTest {
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final Executor DIRECT_EXECUTOR = Runnable::run;
 
     /** Make sure the hint flags are saved and loaded correctly. */
@@ -239,6 +248,56 @@
         assertNull(results.getTopResult());
     }
 
+    @EnableFlags(FLAG_SCROLL_CAPTURE_TARGET_Z_ORDER_FIX)
+    @MediumTest
+    @Test
+    public void testDispatchScrollCaptureSearch_traversesInDrawingOrder() throws Exception {
+        final Context context = getInstrumentation().getContext();
+        // Uses childDrawingOrder to reverse drawing order of children.
+        final MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200);
+
+        // w=200, h=180, z=10, drawn on top
+        final MockView view1 = new MockView(context, 0, 20, 200, 200);
+        TestScrollCaptureCallback callback1 = new TestScrollCaptureCallback();
+        view1.setScrollCaptureCallback(callback1);
+        view1.setZ(10f);
+
+        // w=200, h=200, z=0, drawn first, under view1
+        final MockView view2 = new MockView(context, 0, 0, 200, 200);
+        TestScrollCaptureCallback callback2 = new TestScrollCaptureCallback();
+        view2.setScrollCaptureCallback(callback2);
+
+        viewGroup.addView(view1); // test order is dependent on draw order by adding z=10 first
+        viewGroup.addView(view2);
+
+        Rect localVisibleRect = new Rect(0, 0, 200, 200);
+        Point windowOffset = new Point(0, 0);
+
+        // Where targets are added
+        final ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR);
+
+        viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget);
+        callback1.completeSearchRequest(new Rect(0, 0, 200, 180));
+        callback2.completeSearchRequest(new Rect(0, 0, 200, 200));
+        assertTrue(results.isComplete());
+
+        List<ScrollCaptureTarget> targets = results.getTargets();
+        List<View> targetViews =
+                targets.stream().map(ScrollCaptureTarget::getContainingView).toList();
+        assertEquals(List.of(view2,  view1), targetViews);
+    }
+
+    static final class ReverseDrawingViewGroup extends MockViewGroup {
+        ReverseDrawingViewGroup(Context context, int left, int top, int right, int bottom) {
+            super(context, left, top, right, bottom, View.SCROLL_CAPTURE_HINT_AUTO);
+        }
+
+        @Override
+        protected int getChildDrawingOrder(int childCount, int drawingPosition) {
+            return childCount == 0 ? 0 : childCount - (drawingPosition + 1);
+        }
+    }
+
     /**
      * Test scroll capture search dispatch to child views.
      * <p>
@@ -511,7 +570,7 @@
         }
     };
 
-    public static final class MockViewGroup extends ViewGroup {
+    public static class MockViewGroup extends ViewGroup {
         private ScrollCaptureCallback mInternalCallback;
         private Rect mOnScrollCaptureSearchLastLocalVisibleRect;
         private Point mOnScrollCaptureSearchLastWindowOffset;
diff --git a/core/tests/coretests/src/android/widget/DateTimeViewTest.java b/core/tests/coretests/src/android/widget/DateTimeViewTest.java
index a8fd913..be65277 100644
--- a/core/tests/coretests/src/android/widget/DateTimeViewTest.java
+++ b/core/tests/coretests/src/android/widget/DateTimeViewTest.java
@@ -69,6 +69,141 @@
         Assert.assertFalse(dateTimeView.wasLayoutRequested());
     }
 
+    @UiThreadTest
+    @Test
+    public void disambiguationTextMask_none_noPastOrFutureDisambiguationText() {
+        final TestDateTimeView dateTimeView = new TestDateTimeView();
+        dateTimeView.setShowRelativeTime(true);
+        dateTimeView.setRelativeTimeDisambiguationTextMask(0);
+
+        // Minutes
+        dateTimeView.setTime(System.currentTimeMillis() + Duration.ofMinutes(8).toMillis());
+        Assert.assertFalse(dateTimeView.getText().toString().contains("in"));
+
+        dateTimeView.setTime(System.currentTimeMillis() - Duration.ofMinutes(8).toMillis());
+        Assert.assertFalse(dateTimeView.getText().toString().contains("ago"));
+
+        // Hours
+        dateTimeView.setTime(System.currentTimeMillis() + Duration.ofHours(4).toMillis());
+        Assert.assertFalse(dateTimeView.getText().toString().contains("in"));
+
+        dateTimeView.setTime(System.currentTimeMillis() - Duration.ofHours(4).toMillis());
+        Assert.assertFalse(dateTimeView.getText().toString().contains("ago"));
+
+        // Days
+        dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(14).toMillis());
+        Assert.assertFalse(dateTimeView.getText().toString().contains("in"));
+
+        dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(14).toMillis());
+        Assert.assertFalse(dateTimeView.getText().toString().contains("ago"));
+
+        // Years
+        dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(400).toMillis());
+        Assert.assertFalse(dateTimeView.getText().toString().contains("in"));
+
+        dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(400).toMillis());
+        Assert.assertFalse(dateTimeView.getText().toString().contains("ago"));
+    }
+
+    @UiThreadTest
+    @Test
+    public void disambiguationTextMask_bothPastAndFuture_usesPastAndFutureDisambiguationText() {
+        final TestDateTimeView dateTimeView = new TestDateTimeView();
+        dateTimeView.setShowRelativeTime(true);
+        dateTimeView.setRelativeTimeDisambiguationTextMask(
+                DateTimeView.DISAMBIGUATION_TEXT_PAST | DateTimeView.DISAMBIGUATION_TEXT_FUTURE);
+
+        // Minutes
+        dateTimeView.setTime(System.currentTimeMillis() + Duration.ofMinutes(8).toMillis());
+        Assert.assertTrue(dateTimeView.getText().toString().contains("in"));
+
+        dateTimeView.setTime(System.currentTimeMillis() - Duration.ofMinutes(8).toMillis());
+        Assert.assertTrue(dateTimeView.getText().toString().contains("ago"));
+
+        // Hours
+        dateTimeView.setTime(System.currentTimeMillis() + Duration.ofHours(4).toMillis());
+        Assert.assertTrue(dateTimeView.getText().toString().contains("in"));
+
+        dateTimeView.setTime(System.currentTimeMillis() - Duration.ofHours(4).toMillis());
+        Assert.assertTrue(dateTimeView.getText().toString().contains("ago"));
+
+        // Days
+        dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(14).toMillis());
+        Assert.assertTrue(dateTimeView.getText().toString().contains("in"));
+
+        dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(14).toMillis());
+        Assert.assertTrue(dateTimeView.getText().toString().contains("ago"));
+
+        // Years
+        dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(400).toMillis());
+        Assert.assertTrue(dateTimeView.getText().toString().contains("in"));
+
+        dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(400).toMillis());
+        Assert.assertTrue(dateTimeView.getText().toString().contains("ago"));
+    }
+
+    @UiThreadTest
+    @Test
+    public void unitDisplayLength_shortest_noMediumText() {
+        final TestDateTimeView dateTimeView = new TestDateTimeView();
+        dateTimeView.setShowRelativeTime(true);
+        dateTimeView.setRelativeTimeUnitDisplayLength(DateTimeView.UNIT_DISPLAY_LENGTH_SHORTEST);
+
+        // Minutes
+        dateTimeView.setTime(System.currentTimeMillis() + Duration.ofMinutes(8).toMillis());
+        Assert.assertFalse(dateTimeView.getText().toString().contains("min"));
+
+        dateTimeView.setTime(System.currentTimeMillis() - Duration.ofMinutes(8).toMillis());
+        Assert.assertFalse(dateTimeView.getText().toString().contains("min"));
+
+        // Hours
+        dateTimeView.setTime(System.currentTimeMillis() + Duration.ofHours(4).toMillis());
+        Assert.assertFalse(dateTimeView.getText().toString().contains("hr"));
+
+        dateTimeView.setTime(System.currentTimeMillis() - Duration.ofHours(4).toMillis());
+        Assert.assertFalse(dateTimeView.getText().toString().contains("hr"));
+
+        // Days excluded because the string is the same for both shortest length and medium length
+
+        // Years
+        dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(400).toMillis());
+        Assert.assertFalse(dateTimeView.getText().toString().contains("yr"));
+
+        dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(400).toMillis());
+        Assert.assertFalse(dateTimeView.getText().toString().contains("yr"));
+    }
+
+    @UiThreadTest
+    @Test
+    public void unitDisplayLength_medium_usesMediumText() {
+        final TestDateTimeView dateTimeView = new TestDateTimeView();
+        dateTimeView.setShowRelativeTime(true);
+        dateTimeView.setRelativeTimeUnitDisplayLength(DateTimeView.UNIT_DISPLAY_LENGTH_MEDIUM);
+
+        // Minutes
+        dateTimeView.setTime(System.currentTimeMillis() + Duration.ofMinutes(8).toMillis());
+        Assert.assertTrue(dateTimeView.getText().toString().contains("min"));
+
+        dateTimeView.setTime(System.currentTimeMillis() - Duration.ofMinutes(8).toMillis());
+        Assert.assertTrue(dateTimeView.getText().toString().contains("min"));
+
+        // Hours
+        dateTimeView.setTime(System.currentTimeMillis() + Duration.ofHours(4).toMillis());
+        Assert.assertTrue(dateTimeView.getText().toString().contains("hr"));
+
+        dateTimeView.setTime(System.currentTimeMillis() - Duration.ofHours(4).toMillis());
+        Assert.assertTrue(dateTimeView.getText().toString().contains("hr"));
+
+        // Days excluded because the string is the same for both shortest length and medium length
+
+        // Years
+        dateTimeView.setTime(System.currentTimeMillis() + Duration.ofDays(400).toMillis());
+        Assert.assertTrue(dateTimeView.getText().toString().contains("yr"));
+
+        dateTimeView.setTime(System.currentTimeMillis() - Duration.ofDays(400).toMillis());
+        Assert.assertTrue(dateTimeView.getText().toString().contains("yr"));
+    }
+
     private static class TestDateTimeView extends DateTimeView {
         private boolean mRequestedLayout = false;
 
diff --git a/core/tests/coretests/src/android/widget/ProgressBarTest.java b/core/tests/coretests/src/android/widget/ProgressBarTest.java
index fa6dd31..0cbfaa9 100644
--- a/core/tests/coretests/src/android/widget/ProgressBarTest.java
+++ b/core/tests/coretests/src/android/widget/ProgressBarTest.java
@@ -23,8 +23,12 @@
 
 import android.app.Instrumentation;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.Flags;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -32,6 +36,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,6 +45,10 @@
 @Presubmit
 public class ProgressBarTest {
     private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
     private ProgressBar mBar;
     private AccessibilityNodeInfo mInfo;
 
@@ -181,4 +190,16 @@
         mBar.onInitializeAccessibilityNodeInfo(mInfo);
         assertEquals("custom state", mInfo.getStateDescription().toString());
     }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_INDETERMINATE_RANGE_INFO)
+    public void testRangeInfo_indeterminateProgressBar_usesTypeIndeterminate() {
+        mBar.setIndeterminate(true);
+        assertTrue(mBar.isIndeterminate());
+
+        mBar.onInitializeAccessibilityNodeInfo(mInfo);
+
+        assertEquals(mInfo.getRangeInfo().getType(),
+                AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INDETERMINATE);
+    }
 }
diff --git a/core/tests/coretests/src/android/window/OWNERS b/core/tests/coretests/src/android/window/OWNERS
index 6c80cf9..b3fcea2 100644
--- a/core/tests/coretests/src/android/window/OWNERS
+++ b/core/tests/coretests/src/android/window/OWNERS
@@ -1,2 +1,3 @@
 include /services/core/java/com/android/server/wm/OWNERS
-charlesccchen@google.com
+
+# Bug component: 1519745 = per-file WindowContext*,WindowMetrics*,WindowProvider*,WindowTokenClient*
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java
index f1fbd55..21930d1 100644
--- a/core/tests/coretests/src/android/window/WindowContextTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextTest.java
@@ -29,6 +29,11 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
 import android.app.Activity;
 import android.app.EmptyActivity;
@@ -60,6 +65,8 @@
 
 import com.android.frameworks.coretests.R;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -88,9 +95,21 @@
     private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
     private final WindowContext mWindowContext = createWindowContext();
     private final IWindowManager mWms = WindowManagerGlobal.getWindowManagerService();
+    private WindowTokenClientController mOriginalWindowTokenClientController;
 
     private static final int TIMEOUT_IN_SECONDS = 4;
 
+    @Before
+    public void setUp() {
+        // Keeping the original to set it back after each test, in case they applied any override.
+        mOriginalWindowTokenClientController = WindowTokenClientController.getInstance();
+    }
+
+    @After
+    public void tearDown() {
+        WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController);
+    }
+
     @Test
     public void testCreateWindowContextWindowManagerAttachClientToken() {
         final WindowManager windowContextWm = WindowManagerImpl
@@ -320,6 +339,20 @@
         }
     }
 
+    @Test
+    public void updateDisplay_wasAttached_detachThenAttachedPropagatedToTokenController() {
+        final WindowTokenClientController mockWindowTokenClientController =
+                mock(WindowTokenClientController.class);
+        WindowTokenClientController.overrideForTesting(mockWindowTokenClientController);
+
+        mWindowContext.updateDisplay(DEFAULT_DISPLAY + 1);
+
+        verify(mockWindowTokenClientController).detachIfNeeded(any());
+        verify(mockWindowTokenClientController).attachToDisplayArea(any(),
+                anyInt(), /* displayId= */ eq(DEFAULT_DISPLAY + 1),
+                any());
+    }
+
     private WindowContext createWindowContext() {
         return createWindowContext(TYPE_APPLICATION_OVERLAY);
     }
diff --git a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
index a3725af..bb2fe1b 100644
--- a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
@@ -162,6 +162,22 @@
     }
 
     @Test
+    public void testAttachToDisplayContent_keepTrackWithoutWMS() {
+        // WMS is not initialized
+        doReturn(null).when(mController).getWindowManagerService();
+
+        assertFalse(mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY));
+
+        // Can report config change
+        mController.onWindowContextInfoChanged(mWindowTokenClient, mWindowContextInfo);
+
+        verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY);
+
+        // No crash to detach even if WMS is not initialized.
+        mController.detachIfNeeded(mWindowTokenClient);
+    }
+
+    @Test
     public void testAttachToWindowToken() throws RemoteException {
         doReturn(null).when(mWindowManagerService).attachWindowContextToWindowToken(
                 any(), any(), any());
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index f78bc92..74b4de1 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.accessibility;
 
-import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
@@ -64,9 +63,6 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.Vibrator;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.speech.tts.TextToSpeech;
 import android.speech.tts.Voice;
@@ -75,7 +71,6 @@
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
 import android.view.accessibility.IAccessibilityManager;
 import android.widget.Toast;
 
@@ -88,7 +83,6 @@
 
 import org.junit.AfterClass;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -106,8 +100,6 @@
 
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityShortcutControllerTest {
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name";
     private static final CharSequence PACKAGE_NAME_STRING = "Service name";
     private static final String SERVICE_NAME_SUMMARY = "Summary";
@@ -423,7 +415,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
     public void testClickingDisableButtonInDialog_shouldClearShortcutId() throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
@@ -446,58 +437,6 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
-    public void testClickingDisableButtonInDialog_shouldClearShortcutId_old() throws Exception {
-        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
-        configureValidShortcutService();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
-                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
-        getController().performAccessibilityShortcut();
-
-        ArgumentCaptor<DialogInterface.OnClickListener> captor =
-                ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
-        verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
-                captor.capture());
-        captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
-
-        assertThat(
-                Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)
-        ).isEmpty();
-        assertThat(Settings.Secure.getInt(
-                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
-                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
-    @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
-    public void turnOffVolumeShortcutForAlwaysOnA11yService_shouldTurnOffA11yService()
-            throws Exception {
-        configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
-        turnOffVolumeKeyShortcutForA11yService(/* alwaysOnService= */ true);
-
-        assertThat(
-                Settings.Secure.getString(mContentResolver, ENABLED_ACCESSIBILITY_SERVICES)
-        ).isEmpty();
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
-    @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
-    public void turnOffVolumeShortcutForAlwaysOnA11yService_hasOtherTypesShortcut_shouldNotTurnOffA11yService()
-            throws Exception {
-        configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
-        Settings.Secure.putString(
-                mContentResolver, ACCESSIBILITY_BUTTON_TARGETS, SERVICE_NAME_STRING);
-
-        turnOffVolumeKeyShortcutForA11yService(/* alwaysOnService= */ true);
-
-        assertThat(
-                Settings.Secure.getString(mContentResolver, ENABLED_ACCESSIBILITY_SERVICES)
-        ).isEqualTo(SERVICE_NAME_STRING);
-    }
-
-    @Test
     public void turnOffVolumeShortcutForStandardA11yService_shouldNotTurnOffA11yService()
             throws Exception {
         turnOffVolumeKeyShortcutForA11yService(/* alwaysOnService= */ false);
@@ -530,7 +469,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
     public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn()
             throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
@@ -554,30 +492,6 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
-    public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn_old()
-            throws Exception {
-        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
-        configureDefaultAccessibilityService();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
-                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
-        getController().performAccessibilityShortcut();
-
-        ArgumentCaptor<DialogInterface.OnClickListener> captor =
-                ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
-        verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.accessibility_shortcut_on),
-                captor.capture());
-        captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE);
-
-        assertThat(Settings.Secure.getString(mContentResolver,
-                ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
-        assertThat(Settings.Secure.getInt(
-                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
-                AccessibilityShortcutController.DialogStatus.SHOWN);
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
     public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff()
             throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
@@ -601,29 +515,6 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
-    public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff_old()
-            throws Exception {
-        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
-        configureDefaultAccessibilityService();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
-                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
-        getController().performAccessibilityShortcut();
-
-        ArgumentCaptor<DialogInterface.OnClickListener> captor =
-                ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
-        verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
-                captor.capture());
-        captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
-
-        assertThat(Settings.Secure.getString(mContentResolver,
-                ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEmpty();
-        assertThat(Settings.Secure.getInt(
-                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
-                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
-    }
-
-    @Test
     public void testOnAccessibilityShortcut_afterDialogShown_shouldCallServer() throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
@@ -638,9 +529,6 @@
     }
 
     @Test
-    @EnableFlags({
-            Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS,
-            Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE})
     public void testOnAccessibilityShortcut_settingNull_dialogShown_enablesDefaultShortcut()
             throws Exception {
         configureDefaultAccessibilityService();
@@ -658,24 +546,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE)
-    @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
-    public void testOnAccessibilityShortcut_settingNull_dialogShown_writesDefaultSetting()
-            throws Exception {
-        configureDefaultAccessibilityService();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
-                AccessibilityShortcutController.DialogStatus.SHOWN);
-        // Setting is only `null` during SUW.
-        Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null);
-        getController().performAccessibilityShortcut();
-
-        assertThat(Settings.Secure.getString(mContentResolver,
-                ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
-        verify(mAccessibilityManagerService).performAccessibilityShortcut(
-                Display.DEFAULT_DISPLAY, HARDWARE, null);
-    }
-
-    @Test
     public void getFrameworkFeatureMap_shouldBeUnmodifiable() {
         final Map<ComponentName, AccessibilityShortcutController.FrameworkFeatureInfo>
                 frameworkFeatureMap =
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
index 8e906fd..478cef8 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
@@ -25,8 +25,6 @@
 import android.app.AlertDialog;
 import android.content.Context;
 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.testing.AndroidTestingRunner;
@@ -35,7 +33,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.Window;
-import android.view.accessibility.Flags;
 import android.widget.TextView;
 
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -92,19 +89,7 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_WARNING_USE_DEFAULT_DIALOG_TYPE)
-    public void createAccessibilityServiceWarningDialog_hasExpectedWindowParams_isSystemDialog() {
-        createAccessibilityServiceWarningDialog_hasExpectedWindowParams(true);
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_WARNING_USE_DEFAULT_DIALOG_TYPE)
     public void createAccessibilityServiceWarningDialog_hasExpectedWindowParams_notSystemDialog() {
-        createAccessibilityServiceWarningDialog_hasExpectedWindowParams(false);
-    }
-
-    private void createAccessibilityServiceWarningDialog_hasExpectedWindowParams(
-            boolean expectSystemDialog) {
         final AlertDialog dialog =
                 AccessibilityServiceWarning.createAccessibilityServiceWarningDialog(
                         mContext,
@@ -116,11 +101,7 @@
         expect.that(dialogWindow.getAttributes().privateFlags
                 & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS).isEqualTo(
                 SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
-        if (expectSystemDialog) {
-            expect.that(dialogWindow.getAttributes().type).isEqualTo(TYPE_SYSTEM_DIALOG);
-        } else {
-            expect.that(dialogWindow.getAttributes().type).isNotEqualTo(TYPE_SYSTEM_DIALOG);
-        }
+        expect.that(dialogWindow.getAttributes().type).isNotEqualTo(TYPE_SYSTEM_DIALOG);
     }
 
     @Test
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
index 5339d91..acf7dbc 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
@@ -33,12 +33,7 @@
 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.SetFlagsRule;
-import android.provider.Settings;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
 import android.view.accessibility.IAccessibilityManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -68,8 +63,6 @@
 @RunWith(AndroidJUnit4.class)
 public class InvisibleToggleAccessibilityServiceTargetTest {
     @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-    @Rule
     public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
     @Mock
     private IAccessibilityManager mAccessibilityManagerService;
@@ -117,7 +110,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
     public void onCheckedChanged_true_callA11yManagerToUpdateShortcuts() throws Exception {
         mSut.onCheckedChanged(true);
 
@@ -130,7 +122,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
     public void onCheckedChanged_false_callA11yManagerToUpdateShortcuts() throws Exception {
         mSut.onCheckedChanged(false);
         verify(mAccessibilityManagerService).enableShortcutsForTargets(
@@ -140,86 +131,4 @@
                 anyInt());
         assertThat(mListCaptor.getValue()).containsExactly(ALWAYS_ON_SERVICE_COMPONENT_NAME);
     }
-
-    @Test
-    @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
-    public void onCheckedChanged_turnOnShortcut_hasOtherShortcut_serviceKeepsOn() {
-        enableA11yService(/* enable= */ true);
-        addShortcutForA11yService(
-                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, /* add= */ false);
-        addShortcutForA11yService(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* add= */ true);
-
-        mSut.onCheckedChanged(/* isChecked= */ true);
-
-        assertA11yServiceState(/* enabled= */ true);
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
-    public void onCheckedChanged_turnOnShortcut_noOtherShortcut_shouldTurnOnService() {
-        enableA11yService(/* enable= */ false);
-        addShortcutForA11yService(
-                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, /* add= */ false);
-        addShortcutForA11yService(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* add= */ false);
-
-        mSut.onCheckedChanged(/* isChecked= */ true);
-
-        assertA11yServiceState(/* enabled= */ true);
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
-    public void onCheckedChanged_turnOffShortcut_hasOtherShortcut_serviceKeepsOn() {
-        enableA11yService(/* enable= */ true);
-        addShortcutForA11yService(
-                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, /* add= */ true);
-        addShortcutForA11yService(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* add= */ true);
-
-        mSut.onCheckedChanged(/* isChecked= */ false);
-
-        assertA11yServiceState(/* enabled= */ true);
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS)
-    public void onCheckedChanged_turnOffShortcut_noOtherShortcut_shouldTurnOffService() {
-        enableA11yService(/* enable= */ true);
-        addShortcutForA11yService(
-                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, /* add= */ true);
-        addShortcutForA11yService(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* add= */ false);
-
-        mSut.onCheckedChanged(/* isChecked= */ false);
-
-        assertA11yServiceState(/* enabled= */ false);
-    }
-
-    private void enableA11yService(boolean enable) {
-        Settings.Secure.putString(
-                mContextSpy.getContentResolver(),
-                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                enable ? ALWAYS_ON_SERVICE_COMPONENT_NAME : "");
-    }
-
-    private void addShortcutForA11yService(String shortcutKey, boolean add) {
-        Settings.Secure.putString(
-                mContextSpy.getContentResolver(),
-                shortcutKey,
-                add ? ALWAYS_ON_SERVICE_COMPONENT_NAME : "");
-    }
-
-    private void assertA11yServiceState(boolean enabled) {
-        if (enabled) {
-            assertThat(
-                    Settings.Secure.getString(
-                            mContextSpy.getContentResolver(),
-                            Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
-            ).contains(ALWAYS_ON_SERVICE_COMPONENT_NAME);
-        } else {
-            assertThat(
-                    Settings.Secure.getString(
-                            mContextSpy.getContentResolver(),
-                            Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
-            ).doesNotContain(ALWAYS_ON_SERVICE_COMPONENT_NAME);
-        }
-    }
 }
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index e640ce5..17fe15c 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -16,13 +16,17 @@
 
 package android.hardware.devicestate;
 
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+
 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.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
@@ -40,7 +44,6 @@
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.util.ConcurrentUtils;
 import com.android.window.flags.Flags;
 
 import org.junit.Before;
@@ -52,6 +55,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
 import platform.test.runner.parameterized.Parameters;
@@ -103,7 +107,7 @@
                 permissionEnforcer, true /* simulatePostCallback */);
         final DeviceStateManagerGlobal dsmGlobal = new DeviceStateManagerGlobal(service);
         final DeviceStateCallback callback = mock(DeviceStateCallback.class);
-        dsmGlobal.registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
+        dsmGlobal.registerDeviceStateCallback(callback, DIRECT_EXECUTOR);
 
         verify(callback, never()).onDeviceStateChanged(any());
 
@@ -120,49 +124,68 @@
         final IDeviceStateManager service = new TestDeviceStateManagerService(permissionEnforcer);
         final DeviceStateManagerGlobal dsmGlobal = new DeviceStateManagerGlobal(service);
         final DeviceStateCallback callback = mock(DeviceStateCallback.class);
-        dsmGlobal.registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
+        dsmGlobal.registerDeviceStateCallback(callback, DIRECT_EXECUTOR);
 
         verify(callback).onDeviceStateChanged(eq(DEFAULT_DEVICE_STATE));
     }
 
     @Test
-    public void registerCallback() {
+    public void registerCallback_usesExecutorForCallbacks() {
+        final DeviceStateCallback callback = mock(DeviceStateCallback.class);
+        final Executor executor = mock(Executor.class);
+        doAnswer(invocation -> {
+            Runnable runnable = (Runnable) invocation.getArguments()[0];
+            runnable.run();
+            return null;
+        }).when(executor).execute(any(Runnable.class));
+
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, executor);
+        mService.setBaseState(OTHER_DEVICE_STATE);
+        mService.setSupportedStates(List.of(OTHER_DEVICE_STATE));
+
+        // Verify that the given executor is used for both initial and subsequent callbacks.
+        verify(executor, times(4)).execute(any(Runnable.class));
+    }
+
+    @Test
+    public void registerCallback_supportedStatesChanged() {
         final DeviceStateCallback callback1 = mock(DeviceStateCallback.class);
         final DeviceStateCallback callback2 = mock(DeviceStateCallback.class);
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback1, DIRECT_EXECUTOR);
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback2, DIRECT_EXECUTOR);
 
-        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback1,
-                ConcurrentUtils.DIRECT_EXECUTOR);
-        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback2,
-                ConcurrentUtils.DIRECT_EXECUTOR);
-
-        // Verify initial callbacks
-        verify(callback1).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
-        verify(callback1).onDeviceStateChanged(eq(mService.getMergedState()));
-        verify(callback2).onDeviceStateChanged(eq(mService.getMergedState()));
-
-        reset(callback1);
-        reset(callback2);
-
-        // Change the supported states and verify callback
+        // Change the supported states and verify callback.
         mService.setSupportedStates(List.of(DEFAULT_DEVICE_STATE));
+
         verify(callback1).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
         verify(callback2).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
+    }
+
+    @Test
+    public void registerCallback_baseStateChanged() {
+        final DeviceStateCallback callback1 = mock(DeviceStateCallback.class);
+        final DeviceStateCallback callback2 = mock(DeviceStateCallback.class);
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback1, DIRECT_EXECUTOR);
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback2, DIRECT_EXECUTOR);
         mService.setSupportedStates(List.of(DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE));
 
-        reset(callback1);
-        reset(callback2);
-
-        // Change the base state and verify callback
+        // Change the base state and verify callback.
         mService.setBaseState(OTHER_DEVICE_STATE);
+
         verify(callback1).onDeviceStateChanged(eq(mService.getMergedState()));
         verify(callback2).onDeviceStateChanged(eq(mService.getMergedState()));
+    }
 
-        reset(callback1);
-        reset(callback2);
-
-        // Change the requested state and verify callback
+    @Test
+    public void registerCallback_requestedStateChanged() {
+        final DeviceStateCallback callback1 = mock(DeviceStateCallback.class);
+        final DeviceStateCallback callback2 = mock(DeviceStateCallback.class);
         final DeviceStateRequest request =
                 DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE.getIdentifier()).build();
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback1, DIRECT_EXECUTOR);
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback2, DIRECT_EXECUTOR);
+
+        // Change the requested state and verify callback.
         mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
 
         verify(callback1).onDeviceStateChanged(eq(mService.getMergedState()));
@@ -173,8 +196,7 @@
     public void unregisterCallback() {
         final DeviceStateCallback callback = mock(DeviceStateCallback.class);
 
-        mDeviceStateManagerGlobal
-                .registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, DIRECT_EXECUTOR);
 
         // Verify initial callbacks
         verify(callback).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
@@ -191,8 +213,7 @@
     @Test
     public void submitRequest() {
         final DeviceStateCallback callback = mock(DeviceStateCallback.class);
-        mDeviceStateManagerGlobal
-                .registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, DIRECT_EXECUTOR);
 
         verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
         reset(callback);
@@ -212,8 +233,7 @@
     @Test
     public void submitBaseStateOverrideRequest() {
         final DeviceStateCallback callback = mock(DeviceStateCallback.class);
-        mDeviceStateManagerGlobal
-                .registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, DIRECT_EXECUTOR);
 
         verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
         reset(callback);
@@ -234,8 +254,7 @@
     @Test
     public void submitBaseAndEmulatedStateOverride() {
         final DeviceStateCallback callback = mock(DeviceStateCallback.class);
-        mDeviceStateManagerGlobal
-                .registerDeviceStateCallback(callback, ConcurrentUtils.DIRECT_EXECUTOR);
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback, DIRECT_EXECUTOR);
 
         verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
         reset(callback);
@@ -275,7 +294,7 @@
         final DeviceStateRequest request =
                 DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE.getIdentifier()).build();
         mDeviceStateManagerGlobal.requestState(request,
-                ConcurrentUtils.DIRECT_EXECUTOR /* executor */,
+                DIRECT_EXECUTOR /* executor */,
                 callback /* callback */);
 
         verify(callback).onRequestActivated(eq(request));
diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
index 1cc38de..5f25e93 100644
--- a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
@@ -16,6 +16,8 @@
 
 package android.os.vibrator.persistence;
 
+import static android.os.VibrationEffect.Composition.DELAY_TYPE_PAUSE;
+import static android.os.VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET;
 import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
 import static android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
 import static android.os.VibrationEffect.Composition.PRIMITIVE_SPIN;
@@ -31,6 +33,7 @@
 import android.os.VibrationEffect;
 import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -436,8 +439,500 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testWaveformEnvelopeEffect_allSucceed() throws Exception {
+        VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
+                .addControlPoint(0.2f, 80f, 10)
+                .addControlPoint(0.5f, 150f, 10)
+                .build();
+
+        String xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+                        <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+
+        assertPublicApisParserSucceeds(xml, effect);
+        assertPublicApisSerializerSucceeds(effect, xml);
+        assertPublicApisRoundTrip(effect);
+        assertHiddenApisParserSucceeds(xml, effect);
+        assertHiddenApisSerializerSucceeds(effect, xml);
+        assertHiddenApisRoundTrip(effect);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testWaveformEnvelopeEffectWithInitialFrequency_allSucceed() throws Exception {
+        VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
+                .setInitialFrequencyHz(20)
+                .addControlPoint(0.2f, 80f, 10)
+                .addControlPoint(0.5f, 150f, 10)
+                .build();
+
+        String xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect initialFrequencyHz="20.0">
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+                        <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+
+        assertPublicApisParserSucceeds(xml, effect);
+        assertPublicApisSerializerSucceeds(effect, xml);
+        assertPublicApisRoundTrip(effect);
+        assertHiddenApisParserSucceeds(xml, effect);
+        assertHiddenApisSerializerSucceeds(effect, xml);
+        assertHiddenApisRoundTrip(effect);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testWaveformEnvelopeEffect_badXml_throwsException() throws IOException {
+        // Incomplete XML
+        assertParseElementFails("""
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+                </vibration-effect>
+                """);
+        assertParseElementFails("""
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10">
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """);
+        assertParseElementFails("""
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+                    </waveform-envelope-effect>
+                """);
+
+        // Bad vibration XML
+        assertParseElementFails("""
+                <vibration-effect>
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """);
+
+        // "waveform-envelope-effect" tag with invalid attributes
+        assertParseElementFails("""
+                <vibration-effect>
+                    <waveform-envelope-effect init_freq="20.0">
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testWaveformEnvelopeEffect_noControlPoints_allFail() throws IOException {
+        String xml = "<vibration-effect><waveform-envelope-effect/></vibration-effect>";
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = "<vibration-effect><waveform-envelope-effect> \n "
+                + "</waveform-envelope-effect></vibration-effect>";
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = "<vibration-effect><waveform-envelope-effect>invalid</waveform-envelope-effect"
+                + "></vibration-effect>";
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                    <control-point />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect initialFrequencyHz="20.0" />
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                <waveform-envelope-effect initialFrequencyHz="20.0"> \n </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect initialFrequencyHz="20.0">
+                    invalid
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect initialFrequencyHz="20.0">
+                    <control-point />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testWaveformEnvelopeEffect_badControlPointData_allFail() throws IOException {
+        String xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                    <control-point amplitude="-1" frequencyHz="80.0" durationMs="100" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                    <control-point amplitude="0.2" frequencyHz="0" durationMs="100" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect initialFrequencyHz="0">
+                    <control-point amplitude="0.2" frequencyHz="30" durationMs="100" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                    <control-point amplitude="0.2" frequencyHz="80.0" durationMs="0" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect>
+                    <control-point amplitude="0.2" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testWaveformEnvelopeEffect_featureFlagDisabled_allFail() throws Exception {
+        VibrationEffect effect = new VibrationEffect.WaveformEnvelopeBuilder()
+                .setInitialFrequencyHz(20)
+                .addControlPoint(0.2f, 80f, 10)
+                .addControlPoint(0.5f, 150f, 10)
+                .build();
+
+        String xml = """
+                <vibration-effect>
+                    <waveform-envelope-effect initialFrequencyHz="20.0">
+                        <control-point amplitude="0.2" frequencyHz="80.0" durationMs="10" />
+                        <control-point amplitude="0.5" frequencyHz="150.0" durationMs="10" />
+                    </waveform-envelope-effect>
+                </vibration-effect>
+                """;
+
+        assertPublicApisParserFails(xml);
+        assertPublicApisSerializerFails(effect);
+        assertHiddenApisParserFails(xml);
+        assertHiddenApisSerializerFails(effect);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicEnvelopeEffect_allSucceed() throws Exception {
+        VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder()
+                .addControlPoint(0.2f, 0.5f, 10)
+                .addControlPoint(0.0f, 1f, 10)
+                .build();
+
+        String xml = """
+                <vibration-effect>
+                    <basic-envelope-effect>
+                        <control-point intensity="0.2" sharpness="0.5" durationMs="10" />
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+
+        assertPublicApisParserSucceeds(xml, effect);
+        assertPublicApisSerializerSucceeds(effect, xml);
+        assertPublicApisRoundTrip(effect);
+        assertHiddenApisParserSucceeds(xml, effect);
+        assertHiddenApisSerializerSucceeds(effect, xml);
+        assertHiddenApisRoundTrip(effect);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicEnvelopeEffectWithInitialSharpness_allSucceed() throws Exception {
+        VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder()
+                .setInitialSharpness(0.3f)
+                .addControlPoint(0.2f, 0.5f, 10)
+                .addControlPoint(0.0f, 1f, 10)
+                .build();
+
+        String xml = """
+                <vibration-effect>
+                    <basic-envelope-effect initialSharpness="0.3">
+                        <control-point intensity="0.2" sharpness="0.5" durationMs="10" />
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+
+        assertPublicApisParserSucceeds(xml, effect);
+        assertPublicApisSerializerSucceeds(effect, xml);
+        assertPublicApisRoundTrip(effect);
+        assertHiddenApisParserSucceeds(xml, effect);
+        assertHiddenApisSerializerSucceeds(effect, xml);
+        assertHiddenApisRoundTrip(effect);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicEnvelopeEffect_badXml_throwsException() throws IOException {
+        // Incomplete XML
+        assertParseElementFails("""
+                <vibration-effect>
+                    <basic-envelope-effect>
+                        <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                </vibration-effect>
+                """);
+        assertParseElementFails("""
+                <vibration-effect>
+                    <basic-envelope-effect>
+                        <control-point intensity="0.2" sharpness="0.8" durationMs="10">
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """);
+        assertParseElementFails("""
+                <vibration-effect>
+                    <basic-envelope-effect>
+                        <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                """);
+
+        // Bad vibration XML
+        assertParseElementFails("""
+                <vibration-effect>
+                        <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """);
+
+        // "basic-envelope-effect" tag with invalid attributes
+        assertParseElementFails("""
+                <vibration-effect>
+                    <basic-envelope-effect init_sharp="20.0">
+                        <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicEnvelopeEffect_noControlPoints_allFail() throws IOException {
+        String xml = "<vibration-effect><basic-envelope-effect/></vibration-effect>";
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = "<vibration-effect><basic-envelope-effect> \n "
+                + "</basic-envelope-effect></vibration-effect>";
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = "<vibration-effect><basic-envelope-effect>invalid</basic-envelope-effect"
+                + "></vibration-effect>";
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect>
+                    <control-point />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect initialSharpness="0.2" />
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                <basic-envelope-effect initialSharpness="0.2"> \n </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect initialSharpness="0.2">
+                    invalid
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect initialSharpness="0.2">
+                    <control-point />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicEnvelopeEffect_badControlPointData_allFail() throws IOException {
+        String xml = """
+                <vibration-effect>
+                    <basic-envelope-effect>
+                    <control-point intensity="-1" sharpness="0.8" durationMs="100" />
+                    <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect>
+                    <control-point intensity="0.2" sharpness="-1" durationMs="100" />
+                    <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect initialSharpness="-1.0">
+                    <control-point intensity="0.2" sharpness="0.8" durationMs="0" />
+                    <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect initialSharpness="2.0">
+                    <control-point intensity="0.2" sharpness="0.8" durationMs="0" />
+                    <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect>
+                    <control-point intensity="0.2" sharpness="0.8" durationMs="10" />
+                    <control-point intensity="0.5" sharpness="0.8" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+
+        xml = """
+                <vibration-effect>
+                    <basic-envelope-effect>
+                    <control-point intensity="0.2" />
+                    <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(xml);
+        assertHiddenApisParserFails(xml);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicEnvelopeEffect_featureFlagDisabled_allFail() throws Exception {
+        VibrationEffect effect = new VibrationEffect.BasicEnvelopeBuilder()
+                .setInitialSharpness(0.3f)
+                .addControlPoint(0.2f, 0.5f, 10)
+                .addControlPoint(0.0f, 1f, 10)
+                .build();
+
+        String xml = """
+                <vibration-effect>
+                    <basic-envelope-effect initialSharpness="0.3">
+                        <control-point intensity="0.2" sharpness="0.5" durationMs="10" />
+                        <control-point intensity="0.0" sharpness="1.0" durationMs="10" />
+                    </basic-envelope-effect>
+                </vibration-effect>
+                """;
+
+        assertPublicApisParserFails(xml);
+        assertPublicApisSerializerFails(effect);
+
+        assertHiddenApisParserFails(xml);
+        assertHiddenApisSerializerFails(effect);
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
-    public void testVendorEffect_featureFlagEnabled_allSucceed() throws Exception {
+    public void testVendorEffect_allSucceed() throws Exception {
         PersistableBundle vendorData = new PersistableBundle();
         vendorData.putInt("id", 1);
         vendorData.putDouble("scale", 0.5);
@@ -476,7 +971,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
-    public void testInvalidVendorEffect_featureFlagEnabled_allFail() throws IOException {
+    public void testInvalidVendorEffect_allFail() throws IOException {
         String emptyTag = "<vibration-effect><vendor-effect/></vibration-effect>";
         assertPublicApisParserFails(emptyTag);
         assertHiddenApisParserFails(emptyTag);
@@ -526,6 +1021,81 @@
         assertHiddenApisSerializerFails(vendorEffect);
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+    public void testPrimitiveDelayType_allSucceed() throws Exception {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(PRIMITIVE_TICK, 1.0f, 0, DELAY_TYPE_RELATIVE_START_OFFSET)
+                .addPrimitive(PRIMITIVE_CLICK, 0.123f, 10, DELAY_TYPE_PAUSE)
+                .compose();
+        String xml = """
+                <vibration-effect>
+                    <primitive-effect name="tick" delayType="relative_start_offset"/>
+                    <primitive-effect name="click" scale="0.123" delayMs="10"/>
+                </vibration-effect>
+                """;
+
+        assertPublicApisParserSucceeds(xml, effect);
+        assertPublicApisSerializerSucceeds(effect, "tick", "click");
+        // Delay type pause is not serialized, as it's the default one
+        assertPublicApisSerializerSucceeds(effect, "relative_start_offset", "click");
+        assertPublicApisRoundTrip(effect);
+
+        assertHiddenApisParserSucceeds(xml, effect);
+        assertHiddenApisSerializerSucceeds(effect, "tick", "click");
+        assertHiddenApisRoundTrip(effect);
+
+        // Check PersistableBundle from round-trip
+        VibrationEffect.Composed parsedEffect = ((VibrationEffect.Composed) parseVibrationEffect(
+                serialize(effect), /* flags= */ 0));
+        assertThat(parsedEffect.getRepeatIndex()).isEqualTo(-1);
+        assertThat(parsedEffect.getSegments()).containsExactly(
+                new PrimitiveSegment(PRIMITIVE_TICK, 1.0f, 0, DELAY_TYPE_RELATIVE_START_OFFSET),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.123f, 10, DELAY_TYPE_PAUSE))
+                .inOrder();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+    public void testPrimitiveInvalidDelayType_allFail() {
+        String emptyAttribute = """
+                <vibration-effect>
+                    <primitive-effect name="tick" delayType=""/>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(emptyAttribute);
+        assertHiddenApisParserFails(emptyAttribute);
+
+        String invalidString = """
+                <vibration-effect>
+                    <primitive-effect name="tick" delayType="invalid"/>
+                </vibration-effect>
+                """;
+        assertPublicApisParserFails(invalidString);
+        assertHiddenApisParserFails(invalidString);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+    public void testPrimitiveDelayType_featureFlagDisabled_allFail() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(PRIMITIVE_TICK, 1.0f, 0, DELAY_TYPE_RELATIVE_START_OFFSET)
+                .addPrimitive(PRIMITIVE_CLICK, 0.123f, 10, DELAY_TYPE_PAUSE)
+                .compose();
+        String xml = """
+                <vibration-effect>
+                    <primitive-effect name="tick" delayType="relative_start_offset"/>
+                    <primitive-effect name="click" scale="0.123" delayMs="10" delayType="pause"/>
+                </vibration-effect>
+                """;
+
+        assertPublicApisParserFails(xml);
+        assertPublicApisSerializerFails(effect);
+
+        assertHiddenApisParserFails(xml);
+        assertHiddenApisSerializerFails(effect);
+    }
+
     private void assertPublicApisParserFails(String xml) {
         assertThrows("Expected parseVibrationEffect to fail for " + xml,
                 VibrationXmlParser.ParseFailedException.class,
diff --git a/core/xsd/permission.xsd b/core/xsd/permission.xsd
index 0ec8f7d..0a0ca7c 100644
--- a/core/xsd/permission.xsd
+++ b/core/xsd/permission.xsd
@@ -43,6 +43,7 @@
                 <xs:element name="disabled-until-used-preinstalled-carrier-app" type="disabled-until-used-preinstalled-carrier-app"/>
                 <xs:element name="privapp-permissions" type="privapp-permissions"/>
                 <xs:element name="oem-permissions" type="oem-permissions"/>
+                <xs:element name="signature-permissions" type="signature-permissions"/>
                 <xs:element name="hidden-api-whitelisted-app" type="hidden-api-whitelisted-app"/>
                 <xs:element name="allow-association" type="allow-association"/>
                 <xs:element name="bugreport-whitelisted" type="bugreport-whitelisted"/>
@@ -156,6 +157,21 @@
         </xs:sequence>
         <xs:attribute name="package" type="xs:string"/>
     </xs:complexType>
+    <xs:complexType name="signature-permissions">
+        <xs:sequence>
+            <xs:element name="permission" minOccurs="0" maxOccurs="unbounded">
+                <xs:complexType>
+                    <xs:attribute name="name" type="xs:string"/>
+                </xs:complexType>
+            </xs:element>
+            <xs:element name="deny-permission" minOccurs="0" maxOccurs="unbounded">
+                <xs:complexType>
+                    <xs:attribute name="name" type="xs:string"/>
+                </xs:complexType>
+            </xs:element>
+        </xs:sequence>
+        <xs:attribute name="package" type="xs:string"/>
+    </xs:complexType>
     <xs:complexType name="hidden-api-whitelisted-app">
         <xs:attribute name="package" type="xs:string"/>
     </xs:complexType>
diff --git a/core/xsd/schema/current.txt b/core/xsd/schema/current.txt
index f3beea1..cdec6ab 100644
--- a/core/xsd/schema/current.txt
+++ b/core/xsd/schema/current.txt
@@ -183,6 +183,7 @@
     method public java.util.List<com.android.xml.permission.configfile.OemPermissions> getOemPermissions_optional();
     method public java.util.List<com.android.xml.permission.configfile.Permission> getPermission_optional();
     method public java.util.List<com.android.xml.permission.configfile.PrivappPermissions> getPrivappPermissions_optional();
+    method public java.util.List<com.android.xml.permission.configfile.SignaturePermissions> getSignaturePermissions_optional();
     method public java.util.List<com.android.xml.permission.configfile.SplitPermission> getSplitPermission_optional();
     method public java.util.List<com.android.xml.permission.configfile.SystemUserBlacklistedApp> getSystemUserBlacklistedApp_optional();
     method public java.util.List<com.android.xml.permission.configfile.SystemUserWhitelistedApp> getSystemUserWhitelistedApp_optional();
@@ -209,6 +210,26 @@
     method public void setName(String);
   }
 
+  public class SignaturePermissions {
+    ctor public SignaturePermissions();
+    method public java.util.List<com.android.xml.permission.configfile.SignaturePermissions.DenyPermission> getDenyPermission();
+    method public java.util.List<com.android.xml.permission.configfile.SignaturePermissions.Permission> getPermission();
+    method public String get_package();
+    method public void set_package(String);
+  }
+
+  public static class SignaturePermissions.DenyPermission {
+    ctor public SignaturePermissions.DenyPermission();
+    method public String getName();
+    method public void setName(String);
+  }
+
+  public static class SignaturePermissions.Permission {
+    ctor public SignaturePermissions.Permission();
+    method public String getName();
+    method public void setName(String);
+  }
+
   public class SplitPermission {
     ctor public SplitPermission();
     method public java.util.List<com.android.xml.permission.configfile.SplitPermission.Library> getLibrary();
diff --git a/core/xsd/vibrator/vibration/schema/current.txt b/core/xsd/vibrator/vibration/schema/current.txt
index 280b405..29f8d19 100644
--- a/core/xsd/vibrator/vibration/schema/current.txt
+++ b/core/xsd/vibrator/vibration/schema/current.txt
@@ -1,6 +1,23 @@
 // Signature format: 2.0
 package com.android.internal.vibrator.persistence {
 
+  public class BasicControlPoint {
+    ctor public BasicControlPoint();
+    method public long getDurationMs();
+    method public float getIntensity();
+    method public float getSharpness();
+    method public void setDurationMs(long);
+    method public void setIntensity(float);
+    method public void setSharpness(float);
+  }
+
+  public class BasicEnvelopeEffect {
+    ctor public BasicEnvelopeEffect();
+    method public java.util.List<com.android.internal.vibrator.persistence.BasicControlPoint> getControlPoint();
+    method public float getInitialSharpness();
+    method public void setInitialSharpness(float);
+  }
+
   public class PredefinedEffect {
     ctor public PredefinedEffect();
     method public com.android.internal.vibrator.persistence.PredefinedEffectName getName();
@@ -15,12 +32,20 @@
     enum_constant public static final com.android.internal.vibrator.persistence.PredefinedEffectName tick;
   }
 
+  public enum PrimitiveDelayType {
+    method public String getRawName();
+    enum_constant public static final com.android.internal.vibrator.persistence.PrimitiveDelayType pause;
+    enum_constant public static final com.android.internal.vibrator.persistence.PrimitiveDelayType relative_start_offset;
+  }
+
   public class PrimitiveEffect {
     ctor public PrimitiveEffect();
     method public java.math.BigInteger getDelayMs();
+    method public com.android.internal.vibrator.persistence.PrimitiveDelayType getDelayType();
     method public com.android.internal.vibrator.persistence.PrimitiveEffectName getName();
     method public float getScale();
     method public void setDelayMs(java.math.BigInteger);
+    method public void setDelayType(com.android.internal.vibrator.persistence.PrimitiveDelayType);
     method public void setName(com.android.internal.vibrator.persistence.PrimitiveEffectName);
     method public void setScale(float);
   }
@@ -39,14 +64,18 @@
 
   public class VibrationEffect {
     ctor public VibrationEffect();
+    method public com.android.internal.vibrator.persistence.BasicEnvelopeEffect getBasicEnvelopeEffect_optional();
     method public com.android.internal.vibrator.persistence.PredefinedEffect getPredefinedEffect_optional();
     method public com.android.internal.vibrator.persistence.PrimitiveEffect getPrimitiveEffect_optional();
     method public byte[] getVendorEffect_optional();
     method public com.android.internal.vibrator.persistence.WaveformEffect getWaveformEffect_optional();
+    method public com.android.internal.vibrator.persistence.WaveformEnvelopeEffect getWaveformEnvelopeEffect_optional();
+    method public void setBasicEnvelopeEffect_optional(com.android.internal.vibrator.persistence.BasicEnvelopeEffect);
     method public void setPredefinedEffect_optional(com.android.internal.vibrator.persistence.PredefinedEffect);
     method public void setPrimitiveEffect_optional(com.android.internal.vibrator.persistence.PrimitiveEffect);
     method public void setVendorEffect_optional(byte[]);
     method public void setWaveformEffect_optional(com.android.internal.vibrator.persistence.WaveformEffect);
+    method public void setWaveformEnvelopeEffect_optional(com.android.internal.vibrator.persistence.WaveformEnvelopeEffect);
   }
 
   public class VibrationSelect {
@@ -59,6 +88,16 @@
     enum_constant public static final com.android.internal.vibrator.persistence.WaveformAmplitudeDefault _default;
   }
 
+  public class WaveformControlPoint {
+    ctor public WaveformControlPoint();
+    method public float getAmplitude();
+    method public long getDurationMs();
+    method public float getFrequencyHz();
+    method public void setAmplitude(float);
+    method public void setDurationMs(long);
+    method public void setFrequencyHz(float);
+  }
+
   public class WaveformEffect {
     ctor public WaveformEffect();
     method public com.android.internal.vibrator.persistence.WaveformEffect.Repeating getRepeating();
@@ -79,6 +118,13 @@
     method public void setDurationMs(java.math.BigInteger);
   }
 
+  public class WaveformEnvelopeEffect {
+    ctor public WaveformEnvelopeEffect();
+    method public java.util.List<com.android.internal.vibrator.persistence.WaveformControlPoint> getControlPoint();
+    method public float getInitialFrequencyHz();
+    method public void setInitialFrequencyHz(float);
+  }
+
   public class XmlParser {
     ctor public XmlParser();
     method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
diff --git a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
index 21a6fac..b4df2d1 100644
--- a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
+++ b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
@@ -54,6 +54,12 @@
                 <xs:element name="primitive-effect" type="PrimitiveEffect"/>
             </xs:sequence>
 
+            <!-- Waveform envelope effect -->
+            <xs:element name="waveform-envelope-effect" type="WaveformEnvelopeEffect"/>
+
+            <!-- Basic envelope effect -->
+            <xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect"/>
+
         </xs:choice>
     </xs:complexType>
 
@@ -147,6 +153,7 @@
         <xs:attribute name="name" type="PrimitiveEffectName" use="required"/>
         <xs:attribute name="scale" type="PrimitiveScale"/>
         <xs:attribute name="delayMs" type="xs:nonNegativeInteger"/>
+        <xs:attribute name="delayType" type="PrimitiveDelayType"/>
     </xs:complexType>
 
     <!-- Primitive names as defined by VibrationEffect.Composition.PRIMITIVE_* -->
@@ -171,4 +178,62 @@
         </xs:restriction>
     </xs:simpleType>
 
+    <!-- Primitive delay types VibrationEffect.Composition.DELAY_TYPE_* -->
+    <xs:simpleType  name="PrimitiveDelayType">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="pause"/>
+            <xs:enumeration value="relative_start_offset"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <!-- Definition of a waveform envelope effect -->
+    <xs:complexType name="WaveformEnvelopeEffect">
+        <xs:sequence>
+            <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1"
+                type="WaveformControlPoint" />
+        </xs:sequence>
+        <xs:attribute name="initialFrequencyHz" type="ControlPointFrequency" />
+    </xs:complexType>
+
+    <!-- Definition of a basic envelope effect -->
+    <xs:complexType name="BasicEnvelopeEffect">
+        <xs:sequence>
+            <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1"
+                type="BasicControlPoint" />
+        </xs:sequence>
+        <xs:attribute name="initialSharpness" type="NormalizedControlPointUnit" />
+    </xs:complexType>
+
+    <xs:complexType name="WaveformControlPoint">
+        <xs:attribute name="amplitude" type="NormalizedControlPointUnit" use="required"/>
+        <xs:attribute name="frequencyHz" type="ControlPointFrequency" use="required"/>
+        <xs:attribute name="durationMs" type="PositiveLong" use="required"/>
+    </xs:complexType>
+
+    <xs:complexType name="BasicControlPoint">
+        <xs:attribute name="intensity" type="NormalizedControlPointUnit" use="required"/>
+        <xs:attribute name="sharpness" type="NormalizedControlPointUnit" use="required"/>
+        <xs:attribute name="durationMs" type="PositiveLong" use="required"/>
+    </xs:complexType>
+
+    <xs:simpleType name="ControlPointFrequency">
+        <xs:restriction base="xs:float">
+            <xs:minExclusive value="0"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:simpleType name="PositiveLong">
+        <xs:restriction base="xs:long">
+            <xs:minExclusive value="0"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <!-- Normalized control point unit float in [0,1] -->
+    <xs:simpleType name="NormalizedControlPointUnit">
+        <xs:restriction base="xs:float">
+            <xs:minInclusive value="0"/>
+            <xs:maxInclusive value="1"/>
+        </xs:restriction>
+    </xs:simpleType>
+
 </xs:schema>
diff --git a/core/xsd/vibrator/vibration/vibration.xsd b/core/xsd/vibrator/vibration/vibration.xsd
index d35d777..fba966f 100644
--- a/core/xsd/vibrator/vibration/vibration.xsd
+++ b/core/xsd/vibrator/vibration/vibration.xsd
@@ -52,6 +52,12 @@
                 <xs:element name="primitive-effect" type="PrimitiveEffect"/>
             </xs:sequence>
 
+            <!-- Waveform envelope effect -->
+            <xs:element name="waveform-envelope-effect" type="WaveformEnvelopeEffect"/>
+
+            <!-- Basic envelope effect -->
+            <xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect"/>
+
         </xs:choice>
     </xs:complexType>
 
@@ -124,6 +130,7 @@
         <xs:attribute name="name" type="PrimitiveEffectName" use="required"/>
         <xs:attribute name="scale" type="PrimitiveScale"/>
         <xs:attribute name="delayMs" type="xs:nonNegativeInteger"/>
+        <xs:attribute name="delayType" type="PrimitiveDelayType"/>
     </xs:complexType>
 
     <!-- Primitive names as defined by VibrationEffect.Composition.PRIMITIVE_* -->
@@ -148,4 +155,62 @@
         </xs:restriction>
     </xs:simpleType>
 
+    <!-- Primitive delay types VibrationEffect.Composition.DELAY_TYPE_* -->
+    <xs:simpleType  name="PrimitiveDelayType">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="pause"/>
+            <xs:enumeration value="relative_start_offset"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <!-- Definition of a waveform envelope effect -->
+    <xs:complexType name="WaveformEnvelopeEffect">
+        <xs:sequence>
+            <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1"
+                type="WaveformControlPoint" />
+        </xs:sequence>
+        <xs:attribute name="initialFrequencyHz" type="ControlPointFrequency" />
+    </xs:complexType>
+
+    <!-- Definition of a basic envelope effect -->
+    <xs:complexType name="BasicEnvelopeEffect">
+        <xs:sequence>
+            <xs:element name="control-point" maxOccurs="unbounded" minOccurs="1"
+                type="BasicControlPoint" />
+        </xs:sequence>
+        <xs:attribute name="initialSharpness" type="NormalizedControlPointUnit" />
+    </xs:complexType>
+
+    <xs:complexType name="WaveformControlPoint">
+        <xs:attribute name="amplitude" type="NormalizedControlPointUnit" use="required"/>
+        <xs:attribute name="frequencyHz" type="ControlPointFrequency" use="required"/>
+        <xs:attribute name="durationMs" type="PositiveLong" use="required"/>
+    </xs:complexType>
+
+    <xs:complexType name="BasicControlPoint">
+        <xs:attribute name="intensity" type="NormalizedControlPointUnit" use="required"/>
+        <xs:attribute name="sharpness" type="NormalizedControlPointUnit" use="required"/>
+        <xs:attribute name="durationMs" type="PositiveLong" use="required"/>
+    </xs:complexType>
+
+    <xs:simpleType name="ControlPointFrequency">
+        <xs:restriction base="xs:float">
+            <xs:minExclusive value="0"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:simpleType name="PositiveLong">
+        <xs:restriction base="xs:long">
+            <xs:minExclusive value="0"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <!-- Normalized control point unit float in [0,1] -->
+    <xs:simpleType name="NormalizedControlPointUnit">
+        <xs:restriction base="xs:float">
+            <xs:minInclusive value="0"/>
+            <xs:maxInclusive value="1"/>
+        </xs:restriction>
+    </xs:simpleType>
+
 </xs:schema>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index af690f4..a26f5e3 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -514,7 +514,6 @@
         <permission name="android.permission.RENOUNCE_PERMISSIONS" />
         <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
         <permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
-        <permission name="android.permission.READ_DROPBOX_DATA" />
         <permission name="android.permission.READ_LOGS" />
         <permission name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
         <permission name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
@@ -677,8 +676,4 @@
         <permission name="android.permission.BATTERY_STATS"/>
         <permission name="android.permission.ENTER_TRADE_IN_MODE"/>
     </privapp-permissions>
-
-    <privapp-permissions package="com.android.multiuser">
-        <permission name="android.permission.MANAGE_USERS"/>
-    </privapp-permissions>
 </permissions>
diff --git a/errorprone/Android.bp b/errorprone/Android.bp
index b559a15..1428b89 100644
--- a/errorprone/Android.bp
+++ b/errorprone/Android.bp
@@ -31,6 +31,14 @@
         "//external/auto:auto_service_annotations",
     ],
 
+    javacflags: [
+        // These exports are needed because this errorprone plugin access some private classes
+        // of the java compiler.
+        "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+        "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+        "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+    ],
+
     plugins: [
         "//external/auto:auto_service_plugin",
     ],
diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt
index 6339a87..087378b 100644
--- a/framework-jarjar-rules.txt
+++ b/framework-jarjar-rules.txt
@@ -4,7 +4,6 @@
 
 # Framework-specific renames.
 rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1
-rule com.android.server.vcn.util.** com.android.server.vcn.repackaged.util.@1
 
 # for modules-utils-build dependency
 rule com.android.modules.utils.build.** android.internal.modules.utils.build.@1
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index 073307c..d010c52 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -301,10 +301,7 @@
      *
      * @param bounds Returns the computed bounds of the path's control points.
      * @param exact This parameter is no longer used.
-     *
-     * @deprecated use computeBounds(RectF) instead
      */
-    @Deprecated
     @SuppressWarnings({"UnusedDeclaration"})
     public void computeBounds(@NonNull RectF bounds, boolean exact) {
         computeBounds(bounds);
diff --git a/graphics/java/android/graphics/RuntimeColorFilter.java b/graphics/java/android/graphics/RuntimeColorFilter.java
index d112f71..a64acfe 100644
--- a/graphics/java/android/graphics/RuntimeColorFilter.java
+++ b/graphics/java/android/graphics/RuntimeColorFilter.java
@@ -280,7 +280,8 @@
         if (colorFilter == null) {
             throw new NullPointerException("The colorFilter parameter must not be null");
         }
-        nativeUpdateChild(getNativeInstance(), filterName, colorFilter.getNativeInstance());
+        nativeUpdateInputColorFilter(getNativeInstance(), filterName,
+                colorFilter.getNativeInstance());
     }
 
     /**
@@ -318,5 +319,6 @@
             long colorFilter, String uniformName, int value1, int value2, int value3,
             int value4, int count);
     private static native void nativeUpdateChild(long colorFilter, String childName, long child);
-
+    private static native void nativeUpdateInputColorFilter(long colorFilter, String childName,
+            long inputFilter);
 }
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index 6316c1f..3543e99 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -264,6 +264,9 @@
      * enable better heap tracking & tooling support
      */
     private ArrayMap<String, Shader> mShaderUniforms = new ArrayMap<>();
+    private ArrayMap<String, ColorFilter> mColorFilterUniforms = new ArrayMap<>();
+    private ArrayMap<String, RuntimeXfermode> mXfermodeUniforms = new ArrayMap<>();
+
 
     /**
      * Creates a new RuntimeShader.
@@ -544,8 +547,10 @@
         if (colorFilter == null) {
             throw new NullPointerException("The colorFilter parameter must not be null");
         }
-        nativeUpdateChild(mNativeInstanceRuntimeShaderBuilder, filterName,
+        mColorFilterUniforms.put(filterName, colorFilter);
+        nativeUpdateColorFilter(mNativeInstanceRuntimeShaderBuilder, filterName,
                 colorFilter.getNativeInstance());
+        discardNativeInstance();
     }
 
     /**
@@ -563,8 +568,10 @@
         if (xfermode == null) {
             throw new NullPointerException("The xfermode parameter must not be null");
         }
+        mXfermodeUniforms.put(xfermodeName, xfermode);
         nativeUpdateChild(mNativeInstanceRuntimeShaderBuilder, xfermodeName,
                 xfermode.createNativeInstance());
+        discardNativeInstance();
     }
 
 
@@ -594,6 +601,8 @@
             int value4, int count);
     private static native void nativeUpdateShader(
             long shaderBuilder, String shaderName, long shader);
+    private static native void nativeUpdateColorFilter(
+            long shaderBuilder, String colorFilterName, long colorFilter);
     private static native void nativeUpdateChild(
             long shaderBuilder, String childName, long child);
 }
diff --git a/graphics/java/android/graphics/RuntimeXfermode.java b/graphics/java/android/graphics/RuntimeXfermode.java
index 51d97a4..c8a0b1a 100644
--- a/graphics/java/android/graphics/RuntimeXfermode.java
+++ b/graphics/java/android/graphics/RuntimeXfermode.java
@@ -285,7 +285,8 @@
         if (colorFilter == null) {
             throw new NullPointerException("The colorFilter parameter must not be null");
         }
-        nativeUpdateChild(mBuilderNativeInstance, filterName, colorFilter.getNativeInstance());
+        nativeUpdateColorFilter(mBuilderNativeInstance, filterName,
+                colorFilter.getNativeInstance());
     }
 
     /**
@@ -325,5 +326,6 @@
             long builder, String uniformName, int value1, int value2, int value3,
             int value4, int count);
     private static native void nativeUpdateChild(long builder, String childName, long child);
+    private static native void nativeUpdateColorFilter(long builder, String childName, long filter);
 
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index 0896138..223f9f6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -26,12 +26,12 @@
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
 import android.hardware.devicestate.DeviceStateUtil;
+import android.os.Looper;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseIntArray;
 
 import androidx.annotation.BinderThread;
-import androidx.annotation.GuardedBy;
 import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -39,7 +39,6 @@
 import androidx.window.common.layout.DisplayFoldFeatureCommon;
 
 import com.android.internal.R;
-import com.android.window.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -70,19 +69,10 @@
      * "rear display". Concurrent mode for example is activated via public API and can be active in
      * both the "open" and "half folded" device states.
      */
-    // TODO: b/337820752 - Add @GuardedBy("mCurrentDeviceStateLock") after flag cleanup.
     private DeviceState mCurrentDeviceState = INVALID_DEVICE_STATE;
 
-    /**
-     * Lock to synchronize access to {@link #mCurrentDeviceState}.
-     *
-     * <p>This lock is used to ensure thread-safety when accessing and modifying the
-     * {@link #mCurrentDeviceState} field. It is acquired by both the binder thread (if
-     * {@link Flags#wlinfoOncreate()} is enabled) and the main thread (if
-     * {@link Flags#wlinfoOncreate()} is disabled) to prevent race conditions and
-     * ensure data consistency.
-     */
-    private final Object mCurrentDeviceStateLock = new Object();
+    @NonNull
+    private final Context mContext;
 
     @NonNull
     private final RawFoldingFeatureProducer mRawFoldSupplier;
@@ -90,38 +80,40 @@
     @NonNull
     private final DeviceStateMapper mDeviceStateMapper;
 
-    @VisibleForTesting
-    final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() {
-        // The GuardedBy analysis is intra-procedural, meaning it doesn’t consider the getData()
-        // implementation. See https://errorprone.info/bugpattern/GuardedBy for limitations.
-        @SuppressWarnings("GuardedBy")
-        @BinderThread // When Flags.wlinfoOncreate() is enabled.
-        @MainThread // When Flags.wlinfoOncreate() is disabled.
+    private final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() {
+        @BinderThread // Subsequent callback after registered.
+        @MainThread // Initial callback if registration is on the main thread.
         @Override
         public void onDeviceStateChanged(@NonNull DeviceState state) {
-            synchronized (mCurrentDeviceStateLock) {
-                mCurrentDeviceState = state;
-                mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer.this
-                        ::notifyFoldingFeatureChangeLocked);
-            }
+            final boolean isMainThread = Looper.myLooper() == Looper.getMainLooper();
+            final Executor executor = isMainThread ? Runnable::run : mContext.getMainExecutor();
+            executor.execute(() -> {
+                DeviceStateManagerFoldingFeatureProducer.this.onDeviceStateChanged(state);
+            });
         }
     };
 
     public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context,
             @NonNull RawFoldingFeatureProducer rawFoldSupplier,
             @NonNull DeviceStateManager deviceStateManager) {
+        mContext = context;
         mRawFoldSupplier = rawFoldSupplier;
         mDeviceStateMapper =
                 new DeviceStateMapper(context, deviceStateManager.getSupportedDeviceStates());
 
         if (!mDeviceStateMapper.isDeviceStateToPostureMapEmpty()) {
-            final Executor executor =
-                    Flags.wlinfoOncreate() ? Runnable::run : context.getMainExecutor();
             Objects.requireNonNull(deviceStateManager)
-                    .registerCallback(executor, mDeviceStateCallback);
+                    .registerCallback(Runnable::run, mDeviceStateCallback);
         }
     }
 
+    @MainThread
+    @VisibleForTesting
+    void onDeviceStateChanged(@NonNull DeviceState state) {
+        mCurrentDeviceState = state;
+        mRawFoldSupplier.getData(this::notifyFoldingFeatureChangeLocked);
+    }
+
     /**
      * Add a callback to mCallbacks if there is no device state. This callback will be run
      * once a device state is set. Otherwise,run the callback immediately.
@@ -139,27 +131,20 @@
         }
     }
 
-    // The GuardedBy analysis is intra-procedural, meaning it doesn’t consider the implementation of
-    // addDataChangedCallback(). See https://errorprone.info/bugpattern/GuardedBy for limitations.
-    @SuppressWarnings("GuardedBy")
     @Override
     protected void onListenersChanged() {
         super.onListenersChanged();
-        synchronized (mCurrentDeviceStateLock) {
-            if (hasListeners()) {
-                mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChangeLocked);
-            } else {
-                mCurrentDeviceState = INVALID_DEVICE_STATE;
-                mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChangeLocked);
-            }
+        if (hasListeners()) {
+            mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChangeLocked);
+        } else {
+            mCurrentDeviceState = INVALID_DEVICE_STATE;
+            mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChangeLocked);
         }
     }
 
     @NonNull
     private DeviceState getCurrentDeviceState() {
-        synchronized (mCurrentDeviceStateLock) {
-            return mCurrentDeviceState;
-        }
+        return mCurrentDeviceState;
     }
 
     @NonNull
@@ -231,7 +216,6 @@
         });
     }
 
-    @GuardedBy("mCurrentDeviceStateLock")
     private void notifyFoldingFeatureChangeLocked(String displayFeaturesString) {
         final DeviceState state = mCurrentDeviceState;
         if (!mDeviceStateMapper.isDeviceStateValid(state)) {
@@ -252,29 +236,16 @@
         return parseListFromString(displayFeaturesString, hingeState);
     }
 
-    /**
-     * Internal class to map device states to corresponding postures.
-     *
-     * <p>This class encapsulates the logic for mapping device states to postures. The mapping is
-     * immutable after initialization to ensure thread safety.
-     */
+    /** Internal class to map device states to corresponding postures. */
     private static class DeviceStateMapper {
         /**
          * Emulated device state
          * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)} to
          * {@link CommonFoldingFeature.State} map.
-         *
-         * <p>This map must be immutable after initialization to ensure thread safety, as it may be
-         * accessed from multiple threads. Modifications should only occur during object
-         * construction.
          */
         private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
 
-        /**
-         * The list of device states that are supported.
-         *
-         * <p>This list must be immutable after initialization to ensure thread safety.
-         */
+        /** The list of device states that are supported. */
         @NonNull
         private final List<DeviceState> mSupportedStates;
 
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 6928409..a5a84db 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -182,8 +182,15 @@
 
     @NonNull
     List<ParcelableSplitContainerData> getParcelableSplitContainerDataList() {
-        final List<ParcelableSplitContainerData> data = new ArrayList<>(mSplitContainers.size());
+        final int size =
+                mSplitPinContainer != null ? mSplitContainers.size() - 1 : mSplitContainers.size();
+        final List<ParcelableSplitContainerData> data = new ArrayList<>(size);
         for (SplitContainer splitContainer : mSplitContainers) {
+            if (splitContainer == mSplitPinContainer) {
+                // Skip SplitPinContainer as it cannot be restored because the SplitPinRule is
+                // set while pinning the container in runtime.
+                continue;
+            }
             data.add(splitContainer.getParcelableData());
         }
         return data;
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducerTest.kt b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducerTest.kt
index 90887a7..ad29bf6 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducerTest.kt
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducerTest.kt
@@ -20,10 +20,8 @@
 import android.content.res.Resources
 import android.hardware.devicestate.DeviceState
 import android.hardware.devicestate.DeviceStateManager
-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.platform.app.InstrumentationRegistry
 import androidx.window.common.layout.CommonFoldingFeature
 import androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_FLAT
 import androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_HALF_OPENED
@@ -34,19 +32,16 @@
 import androidx.window.common.layout.DisplayFoldFeatureCommon.DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED
 import androidx.window.common.layout.DisplayFoldFeatureCommon.DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN
 import com.android.internal.R
-import com.android.window.flags.Flags
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import java.util.concurrent.Executor
 import java.util.function.Consumer
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
 import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.doAnswer
 import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
 import org.mockito.kotlin.stub
@@ -60,9 +55,6 @@
  */
 @RunWith(AndroidJUnit4::class)
 class DeviceStateManagerFoldingFeatureProducerTest {
-    @get:Rule
-    val setFlagsRule: SetFlagsRule = SetFlagsRule()
-
     private val mMockDeviceStateManager = mock<DeviceStateManager>()
     private val mMockResources = mock<Resources> {
         on { getStringArray(R.array.config_device_state_postures) } doReturn DEVICE_STATE_POSTURES
@@ -79,32 +71,39 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_WLINFO_ONCREATE)
-    fun testRegisterCallback_whenWlinfoOncreateIsDisabled_usesMainExecutor() {
+    fun testRegisterCallback_initialCallbackOnMainThread_executesDirectly() {
         DeviceStateManagerFoldingFeatureProducer(
             mMockContext,
             mRawFoldSupplier,
             mMockDeviceStateManager,
         )
+        val callbackCaptor = argumentCaptor<DeviceStateManager.DeviceStateCallback>()
+        verify(mMockDeviceStateManager).registerCallback(any(), callbackCaptor.capture())
 
-        verify(mMockDeviceStateManager).registerCallback(eq(mMockContext.mainExecutor), any())
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            callbackCaptor.firstValue.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
+        }
+
+        verify(mMockContext, never()).getMainExecutor()
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_WLINFO_ONCREATE)
-    fun testRegisterCallback_whenWlinfoOncreateIsEnabled_usesRunnableRun() {
-        val executorCaptor = ArgumentCaptor.forClass(Executor::class.java)
-        val runnable = mock<Runnable>()
-
+    fun testRegisterCallback_subsequentCallbacks_postsToMainThread() {
+        val mockMainExecutor = mock<Executor>()
+        mMockContext.stub {
+            on { getMainExecutor() } doReturn mockMainExecutor
+        }
         DeviceStateManagerFoldingFeatureProducer(
             mMockContext,
             mRawFoldSupplier,
             mMockDeviceStateManager,
         )
+        val callbackCaptor = argumentCaptor<DeviceStateManager.DeviceStateCallback>()
+        verify(mMockDeviceStateManager).registerCallback(any(), callbackCaptor.capture())
 
-        verify(mMockDeviceStateManager).registerCallback(executorCaptor.capture(), any())
-        executorCaptor.value.execute(runnable)
-        verify(runnable).run()
+        callbackCaptor.firstValue.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
+
+        verify(mockMainExecutor).execute(any())
     }
 
     @Test
@@ -114,7 +113,7 @@
             mRawFoldSupplier,
             mMockDeviceStateManager,
         )
-        ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
+        ffp.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
 
         val currentData = ffp.getCurrentData()
 
@@ -237,7 +236,7 @@
             mRawFoldSupplier,
             mMockDeviceStateManager,
         )
-        ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
+        ffp.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
         val storeFeaturesConsumer = mock<Consumer<List<CommonFoldingFeature>>>()
 
         ffp.getData(storeFeaturesConsumer)
@@ -257,8 +256,8 @@
         ffp.getData(storeFeaturesConsumer)
 
         verify(storeFeaturesConsumer, never()).accept(any())
-        ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
-        ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_OPENED)
+        ffp.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
+        ffp.onDeviceStateChanged(DEVICE_STATE_OPENED)
         verify(storeFeaturesConsumer).accept(HALF_OPENED_FOLDING_FEATURES)
     }
 
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index a354bf7..4c75ea4 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -182,6 +182,7 @@
         "kotlinx-coroutines-core",
         "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
         "//frameworks/libs/systemui:iconloader_base",
+        "com_android_launcher3_flags_lib",
         "com_android_wm_shell_flags_lib",
         "PlatformAnimationLib",
         "WindowManager-Shell-proto",
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
new file mode 100644
index 0000000..f535fbd
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
@@ -0,0 +1,320 @@
+/*
+ * 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.bubbles
+
+import android.content.Context
+import android.content.pm.LauncherApps
+import android.graphics.Insets
+import android.graphics.Rect
+import android.os.Handler
+import android.os.UserManager
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.IWindowManager
+import android.view.WindowManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.protolog.ProtoLog
+import com.android.internal.statusbar.IStatusBarService
+import com.android.wm.shell.Flags
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.WindowManagerShellWrapper
+import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
+import com.android.wm.shell.bubbles.properties.ProdBubbleProperties
+import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
+import com.android.wm.shell.common.FloatingContentCoordinator
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.common.TaskStackListenerImpl
+import com.android.wm.shell.draganddrop.DragAndDropController
+import com.android.wm.shell.shared.TransactionPool
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.BubbleBarUpdate
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskViewTransitions
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import java.util.Optional
+
+/** Tests for [BubbleController] when using bubble bar */
+@SmallTest
+@EnableFlags(Flags.FLAG_ENABLE_BUBBLE_BAR)
+@RunWith(AndroidJUnit4::class)
+class BubbleControllerBubbleBarTest {
+
+    companion object {
+        private const val SCREEN_WIDTH = 2000
+        private const val SCREEN_HEIGHT = 1000
+    }
+
+    @get:Rule val setFlagsRule = SetFlagsRule()
+
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+
+    private lateinit var bubbleController: BubbleController
+    private lateinit var uiEventLoggerFake: UiEventLoggerFake
+    private lateinit var bubblePositioner: BubblePositioner
+    private lateinit var bubbleData: BubbleData
+    private lateinit var mainExecutor: TestExecutor
+    private lateinit var bgExecutor: TestExecutor
+
+    @Before
+    fun setUp() {
+        ProtoLog.REQUIRE_PROTOLOGTOOL = false
+        ProtoLog.init()
+
+        mainExecutor = TestExecutor()
+        bgExecutor = TestExecutor()
+
+        uiEventLoggerFake = UiEventLoggerFake()
+        val bubbleLogger = BubbleLogger(uiEventLoggerFake)
+
+        val deviceConfig =
+            DeviceConfig(
+                windowBounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),
+                isLargeScreen = true,
+                isSmallTablet = false,
+                isLandscape = true,
+                isRtl = false,
+                insets = Insets.of(10, 20, 30, 40),
+            )
+
+        bubblePositioner = BubblePositioner(context, deviceConfig)
+        bubblePositioner.isShowingInBubbleBar = true
+
+        bubbleData =
+            BubbleData(
+                context,
+                bubbleLogger,
+                bubblePositioner,
+                BubbleEducationController(context),
+                mainExecutor,
+                bgExecutor,
+            )
+
+        val shellInit = ShellInit(mainExecutor)
+
+        bubbleController =
+            createBubbleController(
+                shellInit,
+                bubbleData,
+                bubbleLogger,
+                bubblePositioner,
+                mainExecutor,
+                bgExecutor,
+            )
+        bubbleController.asBubbles().setSysuiProxy(Mockito.mock(SysuiProxy::class.java))
+
+        shellInit.init()
+
+        mainExecutor.flushAll()
+        bgExecutor.flushAll()
+
+        bubbleController.registerBubbleStateListener(FakeBubblesStateListener())
+    }
+
+    @After
+    fun tearDown() {
+        mainExecutor.flushAll()
+        bgExecutor.flushAll()
+    }
+
+    @Test
+    fun testEventLogging_bubbleBar_dragBarLeft() {
+        addBubble()
+
+        bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+
+        bubbleController.setBubbleBarLocation(
+            BubbleBarLocation.LEFT,
+            BubbleBarLocation.UpdateSource.DRAG_BAR,
+        )
+
+        // 2 events: add bubble + drag event
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
+        assertThat(uiEventLoggerFake.eventId(1))
+            .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR.id)
+    }
+
+    @Test
+    fun testEventLogging_bubbleBar_dragBarRight() {
+        addBubble()
+
+        bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT
+
+        bubbleController.setBubbleBarLocation(
+            BubbleBarLocation.RIGHT,
+            BubbleBarLocation.UpdateSource.DRAG_BAR,
+        )
+
+        // 2 events: add bubble + drag event
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
+        assertThat(uiEventLoggerFake.eventId(1))
+            .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR.id)
+    }
+
+    @Test
+    fun testEventLogging_bubbleBar_dragBubbleLeft() {
+        addBubble()
+
+        bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+
+        bubbleController.setBubbleBarLocation(
+            BubbleBarLocation.LEFT,
+            BubbleBarLocation.UpdateSource.DRAG_BUBBLE,
+        )
+
+        // 2 events: add bubble + drag event
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
+        assertThat(uiEventLoggerFake.eventId(1))
+            .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE.id)
+    }
+
+    @Test
+    fun testEventLogging_bubbleBar_dragBubbleRight() {
+        addBubble()
+
+        bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT
+
+        bubbleController.setBubbleBarLocation(
+            BubbleBarLocation.RIGHT,
+            BubbleBarLocation.UpdateSource.DRAG_BUBBLE,
+        )
+
+        // 2 events: add bubble + drag event
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
+        assertThat(uiEventLoggerFake.eventId(1))
+            .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE.id)
+    }
+
+    private fun addBubble(): Bubble {
+        val bubble = FakeBubbleFactory.createChatBubble(context)
+        bubble.setInflateSynchronously(true)
+        bubbleData.notificationEntryUpdated(
+            bubble,
+            /* suppressFlyout= */ true,
+            /* showInShade= */ true,
+        )
+        return bubble
+    }
+
+    private fun createBubbleController(
+        shellInit: ShellInit,
+        bubbleData: BubbleData,
+        bubbleLogger: BubbleLogger,
+        bubblePositioner: BubblePositioner,
+        mainExecutor: TestExecutor,
+        bgExecutor: TestExecutor,
+    ): BubbleController {
+        val shellCommandHandler = ShellCommandHandler()
+        val shellController =
+            ShellController(
+                context,
+                shellInit,
+                shellCommandHandler,
+                mock<DisplayInsetsController>(),
+                mainExecutor,
+            )
+        val surfaceSynchronizer = { obj: Runnable -> obj.run() }
+
+        val bubbleDataRepository =
+            BubbleDataRepository(
+                mock<LauncherApps>(),
+                mainExecutor,
+                bgExecutor,
+                BubblePersistentRepository(context),
+            )
+
+        val shellTaskOrganizer = mock<ShellTaskOrganizer>()
+        whenever(shellTaskOrganizer.executor).thenReturn(directExecutor())
+
+        return BubbleController(
+            context,
+            shellInit,
+            shellCommandHandler,
+            shellController,
+            bubbleData,
+            surfaceSynchronizer,
+            FloatingContentCoordinator(),
+            bubbleDataRepository,
+            mock<IStatusBarService>(),
+            mock<WindowManager>(),
+            WindowManagerShellWrapper(mainExecutor),
+            mock<UserManager>(),
+            mock<LauncherApps>(),
+            bubbleLogger,
+            mock<TaskStackListenerImpl>(),
+            shellTaskOrganizer,
+            bubblePositioner,
+            mock<DisplayController>(),
+            /* oneHandedOptional= */ Optional.empty(),
+            mock<DragAndDropController>(),
+            mainExecutor,
+            mock<Handler>(),
+            bgExecutor,
+            mock<TaskViewTransitions>(),
+            mock<Transitions>(),
+            SyncTransactionQueue(TransactionPool(), mainExecutor),
+            mock<IWindowManager>(),
+            ProdBubbleProperties,
+        )
+    }
+
+    private class TestExecutor : ShellExecutor {
+
+        private val runnables: MutableList<Runnable> = mutableListOf()
+
+        override fun execute(runnable: Runnable) {
+            runnables.add(runnable)
+        }
+
+        override fun executeDelayed(runnable: Runnable, delayMillis: Long) {
+            execute(runnable)
+        }
+
+        override fun removeCallbacks(runnable: Runnable?) {}
+
+        override fun hasCallback(runnable: Runnable?): Boolean = false
+
+        fun flushAll() {
+            while (runnables.isNotEmpty()) {
+                runnables.removeAt(0).run()
+            }
+        }
+    }
+
+    private class FakeBubblesStateListener : Bubbles.BubbleStateListener {
+        override fun onBubbleStateChange(update: BubbleBarUpdate?) {}
+
+        override fun animateBubbleBarLocation(location: BubbleBarLocation?) {}
+    }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 5f42bb1..36acf66 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -31,7 +31,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.internal.protolog.ProtoLog
 import com.android.launcher3.icons.BubbleIconFactory
@@ -52,6 +51,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import java.util.concurrent.Semaphore
@@ -355,7 +355,7 @@
 
     @Test
     fun removeFromWindow_stopMonitoringSwipeUpGesture() {
-        spyOn(bubbleStackView)
+        bubbleStackView = Mockito.spy(bubbleStackView)
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
             // No way to add to window in the test environment right now so just pretend
             bubbleStackView.onDetachedFromWindow()
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
index cb6fb62..3279d56 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
@@ -32,10 +32,10 @@
             return BubbleViewInfo().apply { bubbleBarExpandedView = bubbleExpandedView }
         }
 
-        fun createChatBubbleWithViewInfo(
+        fun createChatBubble(
             context: Context,
             key: String = "key",
-            viewInfo: BubbleViewInfo,
+            viewInfo: BubbleViewInfo? = null,
         ): Bubble {
             val bubble =
                 Bubble(
@@ -50,7 +50,9 @@
                     directExecutor(),
                     directExecutor(),
                 ) {}
-            bubble.setViewInfo(viewInfo)
+            if (viewInfo != null) {
+                bubble.setViewInfo(viewInfo)
+            }
             return bubble
         }
     }
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 3d34cba..7280f8a 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -165,7 +165,7 @@
                 }
 
         val viewInfo = FakeBubbleFactory.createViewInfo(bubbleBarExpandedView)
-        bubble = FakeBubbleFactory.createChatBubbleWithViewInfo(context, viewInfo = viewInfo)
+        bubble = FakeBubbleFactory.createChatBubble(context, viewInfo = viewInfo)
     }
 
     @After
diff --git a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
index e7ead63..62782a7 100644
--- a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
@@ -22,14 +22,14 @@
     android:gravity="bottom|end">
 
     <include android:id="@+id/size_compat_hint"
-        android:visibility="invisible"
+        android:visibility="gone"
         android:layout_width="@dimen/compat_hint_width"
         android:layout_height="wrap_content"
         layout="@layout/compat_mode_hint"/>
 
     <ImageButton
         android:id="@+id/size_compat_restart_button"
-        android:visibility="invisible"
+        android:visibility="gone"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginEnd="@dimen/compat_button_margin"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml
index 3dbf754..fcf74e3 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml
@@ -46,15 +46,19 @@
         <TextView
             android:id="@+id/application_name"
             android:layout_width="0dp"
-            android:layout_height="20dp"
-            android:maxWidth="86dp"
+            android:layout_height="wrap_content"
+            android:maxWidth="130dp"
             android:textAppearance="@android:style/TextAppearance.Material.Title"
             android:textSize="14sp"
             android:textFontWeight="500"
-            android:lineHeight="20dp"
+            android:lineHeight="20sp"
             android:layout_gravity="center_vertical"
             android:layout_weight="1"
             android:layout_marginStart="8dp"
+            android:singleLine="true"
+            android:ellipsize="none"
+            android:requiresFadingEdge="horizontal"
+            android:fadingEdgeLength="28dp"
             android:clickable="false"
             android:focusable="false"
             tools:text="Gmail"/>
diff --git a/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
index b5f04c3..433d854 100644
--- a/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
@@ -22,14 +22,14 @@
     android:gravity="bottom|end">
 
     <include android:id="@+id/user_aspect_ratio_settings_hint"
-        android:visibility="invisible"
+        android:visibility="gone"
         android:layout_width="@dimen/compat_hint_width"
         android:layout_height="wrap_content"
         layout="@layout/compat_mode_hint"/>
 
     <ImageButton
         android:id="@+id/user_aspect_ratio_settings_button"
-        android:visibility="invisible"
+        android:visibility="gone"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginEnd="@dimen/compat_button_margin"
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 9a1a3da..07cc0e7 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -102,7 +102,7 @@
     <string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Keer enige tyd terug na volskerm vanaf die appkieslys"</string>
     <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sien en doen meer"</string>
     <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Sleep ’n ander app in vir verdeelde skerm"</string>
-    <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik buite ’n program om dit te herposisioneer"</string>
+    <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik buite ’n app om dit te herposisioneer"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Het dit"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vou uit vir meer inligting."</string>
     <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Herbegin vir ’n beter aansig?"</string>
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Maak kieslys toe"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Maak kieslys oop"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimeer skerm"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Verander grootte"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App kan nie hierheen geskuif word nie"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Meesleurend"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Stel terug"</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 9d22fef..a6921b9 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"ምናሌ ዝጋ"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ምናሌን ክፈት"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"የማያ ገጹ መጠን አሳድግ"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"መጠን ቀይር"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"መተግበሪያ ወደዚህ መንቀሳቀስ አይችልም"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"አስማጭ"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ወደነበረበት መልስ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 46ab090..b72d255 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"إغلاق القائمة"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"فتح القائمة"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"تكبير الشاشة إلى أقصى حدّ"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"تغيير الحجم"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"لا يمكن نقل التطبيق إلى هنا"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"مجسَّم"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"استعادة"</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 1e35d6e..632d126 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"মেনু বন্ধ কৰক"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"মেনু খোলক"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্ৰীন মেক্সিমাইজ কৰক"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"আকাৰ সলনি কৰক"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ইয়ালৈ এপ্‌টো আনিব নোৱাৰি"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ইমাৰ্ছিভ"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"পুনঃস্থাপন কৰক"</string>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 136d4c1..cf9f1b2 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Menyunu bağlayın"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menyunu açın"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı maksimum böyüdün"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ölçüsünü dəyişin"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Tətbiqi bura köçürmək mümkün deyil"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"İmmersiv"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Bərpa edin"</string>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index 10a33bb..c2d4d8b 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite meni"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvorite meni"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Povećaj ekran"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Promeni veličinu"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija ne može da se premesti ovde"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imerzivne"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vrati"</string>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 163fbdd..dde2374 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Закрыць меню"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Адкрыць меню"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Разгарнуць на ўвесь экран"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Змяніць памер"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Нельга перамясціць сюды праграму"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"З эфектам прысутнасці"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Аднавіць"</string>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index d7da3ae..7e80484 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Затваряне на менюто"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отваряне на менюто"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Увеличаване на екрана"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Преоразмеряване"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Приложението не може да бъде преместено тук"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалистично"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Възстановяване"</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 9c2fc6e..4c6e6c1 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"\'মেনু\' বন্ধ করুন"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"মেনু খুলুন"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্রিন বড় করুন"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ছোট বড় করুন"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"অ্যাপটি এখানে সরানো যাবে না"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ইমারসিভ"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ফিরিয়ে আনুন"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 911285d..bb53adf 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvaranje menija"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvaranje menija"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiziraj ekran"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Promijeni veličinu"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ne možete premjestiti aplikaciju ovdje"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Uvjerljivo"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vraćanje"</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 4249373..786ed76 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Tanca el menú"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Obre el menú"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximitza la pantalla"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Canvia la mida"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"L\'aplicació no es pot moure aquí"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiu"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaura"</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index a125343..99e9a83 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Zavřít nabídku"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otevřít nabídku"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovat obrazovku"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Změnit velikost"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikaci sem nelze přesunout"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Pohlcující"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnovit"</string>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 5b657f4..6021a96 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -72,9 +72,9 @@
     <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"udvid <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
     <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"skjul <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string>
     <string name="bubbles_app_settings" msgid="3617224938701566416">"Indstillinger for <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
-    <string name="bubble_dismiss_text" msgid="8816558050659478158">"Afvis boble"</string>
+    <string name="bubble_dismiss_text" msgid="8816558050659478158">"Luk boble"</string>
     <string name="bubble_fullscreen_text" msgid="1006758103218086231">"Flyt til fuld skærm"</string>
-    <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Vis ikke samtaler i bobler"</string>
+    <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Vis ikke samtale i boble"</string>
     <string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat ved hjælp af bobler"</string>
     <string name="bubbles_user_education_description" msgid="4215862563054175407">"Nye samtaler vises som svævende ikoner eller bobler. Tryk for at åbne boblen. Træk for at flytte den."</string>
     <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Styr bobler når som helst"</string>
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Luk menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Åbn menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimér skærm"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Tilpas størrelse"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apps kan ikke flyttes hertil"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Opslugende"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Gendan"</string>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 6d360e8..7b29662 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Menü schließen"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menü öffnen"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Bildschirm maximieren"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Größe ändern"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Die App kann nicht hierher verschoben werden"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiv"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Wiederherstellen"</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 85a44f6..879347ad 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Κλείσιμο μενού"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Άνοιγμα μενού"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Μεγιστοποίηση οθόνης"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Αλλαγή μεγέθους"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Δεν είναι δυνατή η μετακίνηση της εφαρμογής εδώ"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Καθηλωτικό"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Επαναφορά"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 3e30ff0..358e314 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 0d7189b..923f30b 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Close Menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open Menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximize Screen"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 3e30ff0..358e314 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 3e30ff0..358e314 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 6a1a2e5..7a2e8cf 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir el menú"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Cambiar el tamaño"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"No se puede mover la app aquí"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Inmersivo"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restablecer"</string>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index f93cf5a..2a30bfb 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir menú"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Cambiar tamaño"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"La aplicación no se puede mover aquí"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Inmersivo"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index f0d1d4e..9a15f90 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Sule menüü"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ava menüü"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Kuva täisekraanil"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Suuruse muutmine"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Rakendust ei saa siia teisaldada"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Kaasahaarav"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Taasta"</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index c6a7f2e..7c03b24 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Itxi menua"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ireki menua"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Handitu pantaila"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Aldatu tamaina"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikazioa ezin da hona ekarri"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Murgiltzailea"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Leheneratu"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index d10a02d..eb50ba7 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"بستن منو"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"باز کردن منو"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"بزرگ کردن صفحه"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"تغییر اندازه"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"برنامه را نمی‌توان به اینجا منتقل کرد"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"فراگیر"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"بازیابی"</string>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 0655f9a..d89e36a 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Sulje valikko"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Avaa valikko"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Suurenna näyttö"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Muuta kokoa"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Sovellusta ei voi siirtää tänne"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiivinen"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Palauta"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index b9bdbd7..e2730d4 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -74,7 +74,7 @@
     <string name="bubbles_app_settings" msgid="3617224938701566416">"Paramètres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
     <string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorer la bulle"</string>
     <string name="bubble_fullscreen_text" msgid="1006758103218086231">"Passez en plein écran"</string>
-    <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher les conversations dans des bulles"</string>
+    <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher la conversation dans une bulle"</string>
     <string name="bubbles_user_education_title" msgid="2112319053732691899">"Clavarder en utilisant des bulles"</string>
     <string name="bubbles_user_education_description" msgid="4215862563054175407">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes (de bulles). Touchez une bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string>
     <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Paramètres des bulles"</string>
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ouvrir le menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Agrandir l\'écran"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionner"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossible de déplacer l\'appli ici"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersif"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurer"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index a1eb028..a97a48cd 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ouvrir le menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mettre en plein écran"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionner"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossible de déplacer l\'appli ici"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersif"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurer"</string>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 22a7f7f..445cc70 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Pechar o menú"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menú"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Cambiar tamaño"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Non se pode mover aquí a aplicación"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Envolvente"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 06c21b4..6bef1bb 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"મેનૂ બંધ કરો"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"મેનૂ ખોલો"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"સ્ક્રીન કરો મોટી કરો"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"કદ બદલો"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ઍપ અહીં ખસેડી શકાતી નથી"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ઇમર્સિવ"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"રિસ્ટોર કરો"</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 0eab10c..95b3fc0 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -84,7 +84,7 @@
     <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"हाल ही के बबल्स और हटाए गए बबल्स यहां दिखेंगे"</string>
     <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"बबल्स का इस्तेमाल करके चैट करें"</string>
     <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"नई बातचीत, आपकी स्क्रीन पर सबसे नीचे आइकॉन के तौर पर दिखती हैं. किसी आइकॉन को बड़ा करने के लिए उस पर टैप करें या खारिज करने के लिए उसे खींचें और छोड़ें."</string>
-    <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"जब चाहें, बबल्स की सुविधा को कंट्रोल करें"</string>
+    <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"जब चाहें, बबल्स को कंट्रोल करें"</string>
     <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"किसी ऐप्लिकेशन और बातचीत के लिए बबल की सुविधा को मैनेज करने के लिए यहां टैप करें"</string>
     <string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"मैनेज करें"</string>
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"मेन्यू बंद करें"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेन्यू खोलें"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन को बड़ा करें"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"साइज़ बदलें"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ऐप्लिकेशन को यहां मूव नहीं किया जा सकता"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिव"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"वापस लाएं"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index bf756f63..28bab79 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite izbornik"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvaranje izbornika"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimalno povećaj zaslon"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Promijeni veličinu"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija se ne može premjestiti ovdje"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Interaktivno"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vrati"</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index b02be18..1afb57d 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Menü bezárása"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menü megnyitása"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Képernyő méretének maximalizálása"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Átméretezés"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Az alkalmazás nem helyezhető át ide"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Magával ragadó"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Visszaállítás"</string>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 59a95f0..72669424 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Փակել ընտրացանկը"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Բացել ընտրացանկը"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ծավալել էկրանը"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Փոխել չափը"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Հավելվածը հնարավոր չէ տեղափոխել այստեղ"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Ներկայության էֆեկտով"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Վերականգնել"</string>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index baa1d0e..1197413 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buka Menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Perbesar Layar"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ubah ukuran"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikasi tidak dapat dipindahkan ke sini"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersif"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Pulihkan"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index c3ad5d6..9646cb3 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Loka valmynd"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Opna valmynd"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Stækka skjá"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Breyta stærð"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ekki er hægt að færa forritið hingað"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Umlykjandi"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Endurheimta"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index b75c041..c3f6b3b 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -79,12 +79,12 @@
     <string name="bubbles_user_education_description" msgid="4215862563054175407">"Le nuove conversazioni vengono mostrate come icone mobili o bolle. Tocca per aprire la bolla. Trascinala per spostarla."</string>
     <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controlla le bolle quando vuoi"</string>
     <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Tocca Gestisci per disattivare le bolle dall\'app"</string>
-    <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
+    <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ok"</string>
     <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nessuna bolla recente"</string>
     <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Le bolle recenti e ignorate appariranno qui"</string>
     <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatta utilizzando le bolle"</string>
     <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Le nuove conversazioni vengono visualizzate sotto forma di icone in un angolo inferiore dello schermo. Tocca per espanderle o trascina per chiuderle."</string>
-    <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Gestisci le bolle in qualsiasi momento"</string>
+    <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controlla le bolle quando vuoi"</string>
     <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tocca qui per gestire le app e le conversazioni per cui mostrare le bolle"</string>
     <string name="notification_bubble_title" msgid="6082910224488253378">"Fumetto"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestisci"</string>
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Chiudi il menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Apri il menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Massimizza schermo"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ridimensiona"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossibile spostare l\'app qui"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersivo"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Ripristina"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 5f37590..6f18eda 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"סגירת התפריט"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"פתיחת התפריט"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"הגדלת המסך"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"שינוי הגודל"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"לא ניתן להעביר את האפליקציה לכאן"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"סוחף"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"שחזור"</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 2960a19..c955ecb 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -80,7 +80,7 @@
     <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"バブルはいつでも管理可能"</string>
     <string name="bubbles_user_education_manage" msgid="3460756219946517198">"このアプリからのバブルを OFF にするには、[管理] をタップしてください"</string>
     <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
-    <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近閉じたバブルはありません"</string>
+    <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近のバブルはありません"</string>
     <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"最近表示されたバブルや閉じたバブルが、ここに表示されます"</string>
     <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"チャットでバブルを使う"</string>
     <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"新しい会話がアイコンとして画面下部に表示されます。タップすると開き、ドラッグして閉じることができます。"</string>
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"メニューを閉じる"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"メニューを開く"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"画面の最大化"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"サイズ変更"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"アプリはここに移動できません"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"没入モード"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"復元"</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 6420bf5..2c286d2 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"მენიუს დახურვა"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"მენიუს გახსნა"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"აპლიკაციის გაშლა სრულ ეკრანზე"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ზომის შეცვლა"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"აპის აქ გადატანა შეუძლებელია"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"იმერსიული"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"აღდგენა"</string>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index ef16953..58afb7f 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Мәзірді жабу"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Мәзірді ашу"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды ұлғайту"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Өлшемін өзгерту"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Қолданба бұл жерге қойылмайды."</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Әсерлі"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Қалпына келтіру"</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index a625201..6abb66d 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"បិទ​ម៉ឺនុយ"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"បើកម៉ឺនុយ"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ពង្រីកអេក្រង់"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ប្ដូរ​ទំហំ"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"មិនអាចផ្លាស់ទីកម្មវិធីមកទីនេះបានទេ"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ជក់ចិត្ត"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ស្ដារ"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index b2bf3a5..1da093d 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"ಮೆನು ಮುಚ್ಚಿ"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ಮೆನು ತೆರೆಯಿರಿ"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ಸ್ಕ್ರೀನ್ ಅನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ಮರುಗಾತ್ರಗೊಳಿಸಿ"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ಆ್ಯಪ್ ಅನ್ನು ಇಲ್ಲಿಗೆ ಸರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ಇಮ್ಮರ್ಸಿವ್"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ಮರುಸ್ಥಾಪಿಸಿ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index ad0368a..22f2e06 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"메뉴 닫기"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"메뉴 열기"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"화면 최대화"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"크기 조절"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"앱을 여기로 이동할 수 없음"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"몰입형"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"복원"</string>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 0b4eb93..86529a2 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Менюну жабуу"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Менюну ачуу"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды чоңойтуу"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Өлчөмүн өзгөртүү"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Колдонмону бул жерге жылдырууга болбойт"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Сүңгүтүүчү"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Калыбына келтирүү"</string>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 9710e69..fab0cb2 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"ປິດເມນູ"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ເປີດເມນູ"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ປັບຈໍໃຫຍ່ສຸດ"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ປັບຂະໜາດ"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ບໍ່ສາມາດຍ້າຍແອັບມາບ່ອນນີ້ໄດ້"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ສົມຈິງ"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ກູ້ຄືນ"</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 5bfb8e3..d036e35 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Uždaryti meniu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Atidaryti meniu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Išskleisti ekraną"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Pakeisti dydį"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Programos negalima perkelti čia"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Įtraukiantis"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Atkurti"</string>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index 0734200..dc1f7b0 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Aizvērt izvēlni"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Atvērt izvēlni"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizēt ekrānu"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Mainīt lielumu"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Lietotni nevar pārvietot šeit."</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Iekļaujoši"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Atjaunot"</string>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 2c4503c..3da196b 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Затворете го менито"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отвори го менито"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Максимизирај го екранот"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Промени ја големината"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Апликацијата не може да се премести овде"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалистично"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Врати"</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 7e20ee1..c2e747c 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"മെനു അടയ്ക്കുക"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"മെനു തുറക്കുക"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"സ്‌ക്രീൻ വലുതാക്കുക"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"വലുപ്പം മാറ്റുക"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ആപ്പ് ഇവിടേക്ക് നീക്കാനാകില്ല"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ഇമേഴ്‌സീവ്"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"പുനഃസ്ഥാപിക്കുക"</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index ef24222c..045fc21 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Цэсийг хаах"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Цэсийг нээх"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Дэлгэцийг томруулах"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Хэмжээг өөрчлөх"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Аппыг ийш зөөх боломжгүй"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Бодит мэт"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Сэргээх"</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index e665639..01398d5 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"मेनू बंद करा"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेनू उघडा"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन मोठी करा"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"आकार बदला"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"अ‍ॅप इथे हलवू शकत नाही"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिव्ह"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"रिस्टोअर करा"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 5de79c2..3d687dc 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buka Menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimumkan Skrin"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ubah saiz"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apl tidak boleh dialihkan ke sini"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Mengasyikkan"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Pulihkan"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index e6d3553..08a935f 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"မီနူး ပိတ်ရန်"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"မီနူး ဖွင့်ရန်"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"စခရင်ကို ချဲ့မည်"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"အရွယ်ပြင်ရန်"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"အက်ပ်ကို ဤနေရာသို့ ရွှေ့၍မရပါ"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"သုံးဘက်မြင်"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ပြန်ပြောင်းရန်"</string>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index bde7ec6..1965078 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Lukk menyen"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Åpne menyen"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimer skjermen"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Endre størrelse"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Appen kan ikke flyttes hit"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Oppslukende"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Gjenopprett"</string>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index a40e3ad..10e9332 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"मेनु बन्द गर्नुहोस्"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेनु खोल्नुहोस्"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रिन ठुलो बनाउनुहोस्"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"आकार बदल्नुहोस्"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"एप सारेर यहाँ ल्याउन सकिएन"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिभ"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"रिस्टोर गर्नुहोस्"</string>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index b28b690..fc84515 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -84,7 +84,7 @@
     <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recente bubbels en gesloten bubbels zie je hier"</string>
     <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatten met bubbels"</string>
     <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nieuwe gesprekken verschijnen als iconen in een benedenhoek van je scherm. Tik om ze uit te vouwen of sleep om ze te sluiten."</string>
-    <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bubbels beheren wanneer je wilt"</string>
+    <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bubbels beheren"</string>
     <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tik hier om te beheren welke apps en gesprekken als bubbel kunnen worden getoond"</string>
     <string name="notification_bubble_title" msgid="6082910224488253378">"Bubbel"</string>
     <string name="manage_bubbles_text" msgid="7730624269650594419">"Beheren"</string>
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Menu sluiten"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menu openen"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Scherm maximaliseren"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Formaat aanpassen"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Kan de app niet hierheen verplaatsen"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersief"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Herstellen"</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 842e3de..be01593 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"ମେନୁ ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ମେନୁ ଖୋଲନ୍ତୁ"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ସ୍କ୍ରିନକୁ ବଡ଼ କରନ୍ତୁ"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ରିସାଇଜ କରନ୍ତୁ"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ଆପକୁ ଏଠାକୁ ମୁଭ କରାଯାଇପାରିବ ନାହିଁ"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ଇମର୍ସିଭ"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ରିଷ୍ଟୋର କରନ୍ତୁ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index e1c804a..fb4c83e 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"ਮੀਨੂ ਬੰਦ ਕਰੋ"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ਮੀਨੂ ਖੋਲ੍ਹੋ"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ਸਕ੍ਰੀਨ ਦਾ ਆਕਾਰ ਵਧਾਓ"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ਆਕਾਰ ਬਦਲੋ"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ਐਪ ਨੂੰ ਇੱਥੇ ਨਹੀਂ ਲਿਜਾਇਆ ਜਾ ਸਕਦਾ"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ਇਮਰਸਿਵ"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ਮੁੜ-ਬਹਾਲ ਕਰੋ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index e82916b..fa0e7c3 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Zamknij menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otwórz menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksymalizuj ekran"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Zmień rozmiar"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Nie można przenieść aplikacji tutaj"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Tryb immersyjny"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Przywróć"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 6d8c9ce..d9e5f8c 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionar"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover o app para cá"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersivo"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index e0e91e7..28dc7b0 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar ecrã"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionar"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover a app para aqui"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Envolvente"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 6d8c9ce..d9e5f8c 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionar"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover o app para cá"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersivo"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 5a38155..b63a8b3 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Închide meniul"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Deschide meniul"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizează fereastra"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionează"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplicația nu poate fi mutată aici"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Captivant"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restabilește"</string>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index d26eb53..709e90e 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Закрыть меню"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Открыть меню"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Развернуть на весь экран"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Изменить размер"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Приложение нельзя сюда переместить"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Погружение"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Восстановить"</string>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 5bfa4c9..da1aa9d 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"මෙනුව වසන්න"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"මෙනුව විවෘත කරන්න"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"තිරය උපරිම කරන්න"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ප්‍රතිප්‍රමාණය කරන්න"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"යෙදුම මෙතැනට ගෙන යා නොහැක"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ගිලෙන සුළු"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ප්‍රතිසාධනය කරන්න"</string>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index cd20df5..aa77997 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Zavrieť ponuku"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvoriť ponuku"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovať obrazovku"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Zmeniť veľkosť"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikácia sa sem nedá presunúť"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Pútavé"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnoviť"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 063e47c..55452bd 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Zapri meni"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Odpri meni"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiraj zaslon"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Spremeni velikost"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacije ni mogoče premakniti sem"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Poglobljeno"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnovi"</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index f2fb7da..0492b2f9 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Mbyll menynë"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Hap menynë"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizo ekranin"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ndrysho përmasat"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacioni nuk mund të zhvendoset këtu"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Përfshirës"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restauro"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index f74e460..af8ac68 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Затворите мени"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отворите мени"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Повећај екран"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Промени величину"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Апликација не може да се премести овде"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Имерзивне"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Врати"</string>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 7a5549e..0c3c18c 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Stäng menyn"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Öppna menyn"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximera skärmen"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ändra storlek"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Det går inte att flytta appen hit"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Uppslukande"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Återställ"</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 128ba74..4f0a6ac 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Funga Menyu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Fungua Menyu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Panua Dirisha kwenye Skrini"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Badilisha ukubwa"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Imeshindwa kuhamishia programu hapa"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Shirikishi"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Rejesha"</string>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 668efd5..5fca404 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"மெனுவை மூடும்"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"மெனுவைத் திறக்கும்"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"திரையைப் பெரிதாக்கு"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"அளவை மாற்று"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ஆப்ஸை இங்கே நகர்த்த முடியாது"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ஈடுபட வைக்கும்"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"மீட்டெடுக்கும்"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index a3827bb..9e0f107 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"మెనూను మూసివేయండి"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"మెనూను తెరవండి"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"స్క్రీన్ సైజ్‌ను పెంచండి"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"సైజ్ మార్చండి"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"యాప్‌ను ఇక్కడకి తరలించడం సాధ్యం కాదు"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"లీనమయ్యే"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"రీస్టోర్ చేయండి"</string>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index fe75561..7be7373 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"ปิดเมนู"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"เปิดเมนู"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ขยายหน้าจอให้ใหญ่สุด"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ปรับขนาด"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ย้ายแอปมาที่นี่ไม่ได้"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"สมจริง"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"คืนค่า"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index d9fb13b..22b0174 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Isara ang Menu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buksan ang Menu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"I-maximize ang Screen"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"I-resize"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Hindi mailipat dito ang app"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"I-restore"</string>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index e6d900a..79d64ba1 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Menüyü kapat"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menüyü aç"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı Büyüt"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Yeniden boyutlandır"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Uygulama buraya taşınamıyor"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Etkileyici"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Geri yükle"</string>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index ea59995..aeba982 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Закрити меню"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Відкрити меню"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Розгорнути екран"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Змінити розмір"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Сюди не можна перемістити додаток"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалістичність"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Відновити"</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 6749629f..cf6fb89 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"مینیو بند کریں"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"مینو کھولیں"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"اسکرین کو بڑا کریں"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"سائز تبدیل کریں"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ایپ کو یہاں منتقل نہیں کیا جا سکتا"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"عمیق"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"بحال کریں"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 3417fef..c64b843 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Menyuni yopish"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menyuni ochish"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranni yoyish"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Oʻlchamini oʻzgartirish"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ilova bu yerga surilmaydi"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiv"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Tiklash"</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 559bff8..2a7dae4 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Đóng trình đơn"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Mở Trình đơn"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mở rộng màn hình"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Đổi kích thước"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Không di chuyển được ứng dụng đến đây"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Hiển thị tối đa"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Khôi phục"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index f327653..e45fbba 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"关闭菜单"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"打开菜单"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"最大化屏幕"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"调整大小"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"无法将应用移至此处"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"沉浸式"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"恢复"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index e1ecd44..d5e1063 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -125,7 +125,7 @@
     <string name="select_text" msgid="5139083974039906583">"選取"</string>
     <string name="screenshot_text" msgid="1477704010087786671">"螢幕截圖"</string>
     <string name="open_in_browser_text" msgid="9181692926376072904">"在瀏覽器中開啟"</string>
-    <string name="open_in_app_text" msgid="2874590745116268525">"在應用程式中開啟"</string>
+    <string name="open_in_app_text" msgid="2874590745116268525">"喺應用程式入面打開"</string>
     <string name="new_window_text" msgid="6318648868380652280">"新視窗"</string>
     <string name="manage_windows_text" msgid="5567366688493093920">"管理視窗"</string>
     <string name="change_aspect_ratio_text" msgid="9104456064548212806">"變更長寬比"</string>
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"打開選單"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"調整大小"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"應用程式無法移至這裡"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"身歷其境"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"還原"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 1b8f704..a0357e12 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"開啟選單"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"調整大小"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"應用程式無法移至此處"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"沉浸"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"還原"</string>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 6040317..810b6c8 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -133,8 +133,7 @@
     <string name="collapse_menu_text" msgid="7515008122450342029">"Vala Imenyu"</string>
     <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Vula Imenyu"</string>
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Khulisa Isikrini Sifike Ekugcineni"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Shintsha usayizi"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"I-app ayikwazi ukuhanjiswa lapha"</string>
     <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Okugxilile"</string>
     <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Buyisela"</string>
diff --git a/libs/WindowManager/Shell/shared/Android.bp b/libs/WindowManager/Shell/shared/Android.bp
index 5113d98..5bbda95 100644
--- a/libs/WindowManager/Shell/shared/Android.bp
+++ b/libs/WindowManager/Shell/shared/Android.bp
@@ -26,6 +26,7 @@
     name: "wm_shell-shared-utils",
     srcs: [
         "src/com/android/wm/shell/shared/TransitionUtil.java",
+        "src/com/android/wm/shell/shared/Utils.java",
     ],
 }
 
@@ -71,6 +72,7 @@
     srcs: [
         "**/desktopmode/*.java",
         "**/desktopmode/*.kt",
+        ":wm_shell-shared-utils",
     ],
     static_libs: [
         "com.android.window.flags.window-aconfig-java",
diff --git a/libs/WindowManager/Shell/shared/res/values/strings.xml b/libs/WindowManager/Shell/shared/res/values/strings.xml
new file mode 100644
index 0000000..d1804c0
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?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>
+  <!-- Accessibility text for the icons generated by the Manage Windows submenu
+  in desktop mode and taskbar. [CHAR LIMIT=NONE] -->
+  <string name="manage_windows_icon_text">Open Window %1$d</string>
+</resources>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java
index 8f7a2e5..01d2201 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java
@@ -20,29 +20,6 @@
  * General shell-related constants that are shared with users of the library.
  */
 public class ShellSharedConstants {
-    // See IPip.aidl
-    public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
-    // See IBubbles.aidl
-    public static final String KEY_EXTRA_SHELL_BUBBLES = "extra_shell_bubbles";
-    // See ISplitScreen.aidl
-    public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
-    // See IOneHanded.aidl
-    public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
-    // See IShellTransitions.aidl
-    public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
-            "extra_shell_shell_transitions";
-    // See IStartingWindow.aidl
-    public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
-            "extra_shell_starting_window";
-    // See IRecentTasks.aidl
-    public static final String KEY_EXTRA_SHELL_RECENT_TASKS = "extra_shell_recent_tasks";
-    // See IBackAnimation.aidl
-    public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
-    // See IDesktopMode.aidl
-    public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
-    // See IDragAndDrop.aidl
-    public static final String KEY_EXTRA_SHELL_DRAG_AND_DROP = "extra_shell_drag_and_drop";
-    // See IRecentsAnimationController.aidl
     public static final String KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION =
             "extra_shell_can_hand_off_animation";
 }
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/Utils.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/Utils.java
new file mode 100644
index 0000000..f5e6ddd
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/Utils.java
@@ -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.wm.shell.shared;
+
+import android.annotation.NonNull;
+
+import java.util.function.Supplier;
+
+/**
+ * This class provides generic utility methods and classes for shell
+ */
+public class Utils {
+
+    /**
+     * Lazily returns object from a supplier with caching
+     * @param <T> type of object to get
+     */
+    public static class Lazy<T> {
+        private T mInstance;
+
+        /**
+         * @param supplier the supplier that defines the return value if not defined already
+         * @return the cached value or the value from the supplier
+         */
+        public final T get(@NonNull Supplier<T> supplier) {
+            if (mInstance == null) {
+                mInstance = supplier.get();
+            }
+            return mInstance;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 04c17e5..4c6ef93 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -16,14 +16,19 @@
 
 package com.android.wm.shell.shared.desktopmode;
 
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+
 import android.annotation.NonNull;
 import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
 import android.os.SystemProperties;
 import android.window.DesktopModeFlags;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.window.flags.Flags;
+import com.android.wm.shell.shared.Utils.Lazy;
 
 import java.io.PrintWriter;
 
@@ -35,6 +40,8 @@
 
     private static final String TAG = "DesktopModeStatus";
 
+    private static Lazy<Boolean> sIsFoldableDevice = new Lazy<>();
+
     /**
      * Flag to indicate whether task resizing is veiled.
      */
@@ -195,8 +202,7 @@
      * necessarily enabling desktop mode
      */
     public static boolean overridesShowAppHandle(@NonNull Context context) {
-        return Flags.showAppHandleLargeScreens()
-                && context.getResources().getBoolean(R.bool.config_enableAppHandle);
+        return Flags.showAppHandleLargeScreens() && isDeviceFoldable(context);
     }
 
     /**
@@ -244,6 +250,17 @@
     }
 
     /**
+     * @return {@code true} if this is a foldable device
+     */
+    private static boolean isDeviceFoldable(@NonNull Context context) {
+        return sIsFoldableDevice.get(() -> context.getSystemService(DeviceStateManager.class)
+                .getSupportedDeviceStates().stream().anyMatch(state ->
+                        state.hasProperty(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY)
+                                || state.hasProperty(
+                                        PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY)));
+    }
+
+    /**
      * Return {@code true} if a display should enter desktop mode by default when the windowing mode
      * of the display's root [TaskDisplayArea] is set to WINDOWING_MODE_FREEFORM.
      */
@@ -283,6 +300,6 @@
         pw.println(maxTaskLimitHandle == null ? "null" : maxTaskLimitHandle.getInt(/* def= */ -1));
 
         pw.print(innerPrefix); pw.print("showAppHandle config override=");
-        pw.print(context.getResources().getBoolean(R.bool.config_enableAppHandle));
+        pw.print(overridesShowAppHandle(context));
     }
 }
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
deleted file mode 100644
index f14dfdb..0000000
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
+++ /dev/null
@@ -1,319 +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.wm.shell.shared.desktopmode
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.AnimatorSet
-import android.animation.ObjectAnimator
-import android.annotation.ColorInt
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.drawable.ShapeDrawable
-import android.graphics.drawable.shapes.RoundRectShape
-import android.util.TypedValue
-import android.view.MotionEvent.ACTION_OUTSIDE
-import android.view.SurfaceView
-import android.view.View
-import android.view.View.ALPHA
-import android.view.View.SCALE_X
-import android.view.View.SCALE_Y
-import android.view.ViewGroup.MarginLayoutParams
-import android.widget.LinearLayout
-import android.window.TaskSnapshot
-
-/**
- * View for the All Windows menu option, used by both Desktop Windowing and Taskbar.
- * The menu displays icons of all open instances of an app. Clicking the icon should launch
- * the instance, which will be performed by the child class.
- */
-abstract class ManageWindowsViewContainer(
-    val context: Context,
-    @ColorInt private val menuBackgroundColor: Int
-) {
-    lateinit var menuView: ManageWindowsView
-
-    /** Creates the base menu view and fills it with icon views. */
-    fun createMenu(snapshotList: List<Pair<Int, TaskSnapshot>>,
-             onIconClickListener: ((Int) -> Unit),
-             onOutsideClickListener: (() -> Unit)): ManageWindowsView {
-        val bitmapList = snapshotList.map { (index, snapshot) ->
-            index to Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace)
-        }
-        return createAndShowMenuView(
-            bitmapList,
-            onIconClickListener,
-            onOutsideClickListener
-        )
-    }
-
-    /** Creates the menu view with the given bitmaps, and displays it. */
-    fun createAndShowMenuView(
-        snapshotList: List<Pair<Int, Bitmap?>>,
-        onIconClickListener: ((Int) -> Unit),
-        onOutsideClickListener: (() -> Unit)
-    ): ManageWindowsView {
-        menuView = ManageWindowsView(context, menuBackgroundColor).apply {
-            this.onOutsideClickListener = onOutsideClickListener
-            this.onIconClickListener = onIconClickListener
-            this.generateIconViews(snapshotList)
-        }
-        addToContainer(menuView)
-        return menuView
-    }
-
-    /** Play the animation for opening the menu. */
-    fun animateOpen() {
-        menuView.animateOpen()
-    }
-
-    /**
-     * Play the animation for closing the menu. On finish, will run the provided callback,
-     * which will be responsible for removing the view from the container used in [addToContainer].
-     */
-    fun animateClose() {
-        menuView.animateClose { removeFromContainer() }
-    }
-
-    /** Adds the menu view to the container responsible for displaying it. */
-    abstract fun addToContainer(menuView: ManageWindowsView)
-
-    /** Removes the menu view from the container used in the method above */
-    abstract fun removeFromContainer()
-
-    companion object {
-        const val MANAGE_WINDOWS_MINIMUM_INSTANCES = 2
-    }
-
-    class ManageWindowsView(
-        private val context: Context,
-        menuBackgroundColor: Int
-    ) {
-        private val animators = mutableListOf<Animator>()
-        private val iconViews = mutableListOf<SurfaceView>()
-        val rootView: LinearLayout = LinearLayout(context)
-        var menuHeight = 0
-        var menuWidth = 0
-        var onIconClickListener: ((Int) -> Unit)? = null
-        var onOutsideClickListener: (() -> Unit)? = null
-
-        init {
-            rootView.orientation = LinearLayout.VERTICAL
-            val menuBackground = ShapeDrawable()
-            val menuRadius = getDimensionPixelSize(MENU_RADIUS_DP)
-            menuBackground.shape = RoundRectShape(
-                FloatArray(8) { menuRadius },
-                null,
-                null
-            )
-            menuBackground.paint.color = menuBackgroundColor
-            rootView.background = menuBackground
-            rootView.elevation = getDimensionPixelSize(MENU_ELEVATION_DP)
-            rootView.setOnTouchListener { _, event ->
-                if (event.actionMasked == ACTION_OUTSIDE) {
-                    onOutsideClickListener?.invoke()
-                }
-                return@setOnTouchListener true
-            }
-        }
-
-        private fun getDimensionPixelSize(sizeDp: Float): Float {
-            return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                sizeDp, context.resources.displayMetrics)
-        }
-
-        fun generateIconViews(
-            snapshotList: List<Pair<Int, Bitmap?>>
-        ) {
-            menuWidth = 0
-            menuHeight = 0
-            rootView.removeAllViews()
-            val instanceIconHeight = getDimensionPixelSize(ICON_HEIGHT_DP)
-            val instanceIconWidth = getDimensionPixelSize(ICON_WIDTH_DP)
-            val iconRadius = getDimensionPixelSize(ICON_RADIUS_DP)
-            val iconMargin = getDimensionPixelSize(ICON_MARGIN_DP)
-            var rowLayout: LinearLayout? = null
-            // Add each icon to the menu, adding a new row when needed.
-            for ((iconCount, taskInfoSnapshotPair) in snapshotList.withIndex()) {
-                val taskId = taskInfoSnapshotPair.first
-                val snapshotBitmap = taskInfoSnapshotPair.second
-                // Once a row is filled, make a new row and increase the menu height.
-                if (iconCount % MENU_MAX_ICONS_PER_ROW == 0) {
-                    rowLayout = LinearLayout(context)
-                    rowLayout.orientation = LinearLayout.HORIZONTAL
-                    rootView.addView(rowLayout)
-                    menuHeight += (instanceIconHeight + iconMargin).toInt()
-                }
-
-                val croppedBitmap = snapshotBitmap?.let { cropBitmap(it) }
-                val scaledSnapshotBitmap = croppedBitmap?.let {
-                    Bitmap.createScaledBitmap(
-                        it, instanceIconWidth.toInt(), instanceIconHeight.toInt(), true /* filter */
-                    )
-                }
-                val appSnapshotButton = SurfaceView(context)
-                appSnapshotButton.cornerRadius = iconRadius
-                appSnapshotButton.setZOrderOnTop(true)
-                appSnapshotButton.setOnClickListener {
-                    onIconClickListener?.invoke(taskId)
-                }
-                val lp = MarginLayoutParams(
-                    instanceIconWidth.toInt(), instanceIconHeight.toInt()
-                )
-                lp.apply {
-                    marginStart = iconMargin.toInt()
-                    topMargin = iconMargin.toInt()
-                }
-                appSnapshotButton.layoutParams = lp
-                // If we haven't already reached one full row, increment width.
-                if (iconCount < MENU_MAX_ICONS_PER_ROW) {
-                    menuWidth += (instanceIconWidth + iconMargin).toInt()
-                }
-                rowLayout?.addView(appSnapshotButton)
-                iconViews += appSnapshotButton
-                appSnapshotButton.requestLayout()
-                rowLayout?.post {
-                    appSnapshotButton.holder.surface
-                        .attachAndQueueBufferWithColorSpace(
-                            scaledSnapshotBitmap?.hardwareBuffer,
-                            scaledSnapshotBitmap?.colorSpace
-                        )
-                }
-            }
-            // Add margin again for the right/bottom of the menu.
-            menuWidth += iconMargin.toInt()
-            menuHeight += iconMargin.toInt()
-        }
-
-        private fun cropBitmap(
-            bitmapToCrop: Bitmap
-        ): Bitmap {
-            val ratioToMatch = ICON_WIDTH_DP / ICON_HEIGHT_DP
-            val bitmapWidth = bitmapToCrop.width
-            val bitmapHeight = bitmapToCrop.height
-            if (bitmapWidth > bitmapHeight * ratioToMatch) {
-                // Crop based on height
-                val newWidth = bitmapHeight * ratioToMatch
-                return Bitmap.createBitmap(
-                    bitmapToCrop,
-                    ((bitmapWidth - newWidth) / 2).toInt(),
-                    0,
-                    newWidth.toInt(),
-                    bitmapHeight
-                )
-            } else {
-                // Crop based on width
-                val newHeight = bitmapWidth / ratioToMatch
-                return Bitmap.createBitmap(
-                    bitmapToCrop,
-                    0,
-                    ((bitmapHeight - newHeight) / 2).toInt(),
-                    bitmapWidth,
-                    newHeight.toInt()
-                )
-            }
-        }
-
-        /** Play the animation for opening the menu. */
-        fun animateOpen() {
-            animateView(rootView, MENU_BOUNDS_SHRUNK_SCALE, MENU_BOUNDS_FULL_SCALE,
-                MENU_START_ALPHA, MENU_FULL_ALPHA)
-            for (view in iconViews) {
-                animateView(view, MENU_BOUNDS_SHRUNK_SCALE, MENU_BOUNDS_FULL_SCALE,
-                    MENU_START_ALPHA, MENU_FULL_ALPHA)
-            }
-            createAnimatorSet().start()
-        }
-
-        /** Play the animation for closing the menu. */
-        fun animateClose(callback: () -> Unit) {
-            animateView(rootView, MENU_BOUNDS_FULL_SCALE, MENU_BOUNDS_SHRUNK_SCALE,
-                MENU_FULL_ALPHA, MENU_START_ALPHA)
-            for (view in iconViews) {
-                animateView(view, MENU_BOUNDS_FULL_SCALE, MENU_BOUNDS_SHRUNK_SCALE,
-                    MENU_FULL_ALPHA, MENU_START_ALPHA)
-            }
-            createAnimatorSet().apply {
-                addListener(
-                    object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator) {
-                            callback.invoke()
-                        }
-                    }
-                )
-                start()
-            }
-        }
-
-        private fun animateView(
-            view: View,
-            startBoundsScale: Float,
-            endBoundsScale: Float,
-            startAlpha: Float,
-            endAlpha: Float) {
-            animators += ObjectAnimator.ofFloat(
-                view,
-                SCALE_X,
-                startBoundsScale,
-                endBoundsScale
-            ).apply {
-                duration = MENU_BOUNDS_ANIM_DURATION
-            }
-            animators += ObjectAnimator.ofFloat(
-                view,
-                SCALE_Y,
-                startBoundsScale,
-                endBoundsScale
-            ).apply {
-                duration = MENU_BOUNDS_ANIM_DURATION
-            }
-            animators += ObjectAnimator.ofFloat(
-                view,
-                ALPHA,
-                startAlpha,
-                endAlpha
-            ).apply {
-                duration = MENU_ALPHA_ANIM_DURATION
-                startDelay = MENU_ALPHA_ANIM_DELAY
-            }
-        }
-
-        private fun createAnimatorSet(): AnimatorSet {
-            val animatorSet = AnimatorSet().apply {
-                playTogether(animators)
-            }
-            animators.clear()
-            return animatorSet
-        }
-
-        companion object {
-            private const val MENU_RADIUS_DP = 26f
-            private const val ICON_WIDTH_DP = 204f
-            private const val ICON_HEIGHT_DP = 127.5f
-            private const val ICON_RADIUS_DP = 16f
-            private const val ICON_MARGIN_DP = 16f
-            private const val MENU_ELEVATION_DP = 1f
-            private const val MENU_MAX_ICONS_PER_ROW = 3
-            private const val MENU_BOUNDS_ANIM_DURATION = 200L
-            private const val MENU_BOUNDS_SHRUNK_SCALE = 0.8f
-            private const val MENU_BOUNDS_FULL_SCALE = 1f
-            private const val MENU_ALPHA_ANIM_DURATION = 100L
-            private const val MENU_ALPHA_ANIM_DELAY = 50L
-            private const val MENU_START_ALPHA = 0f
-            private const val MENU_FULL_ALPHA = 1f
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt
new file mode 100644
index 0000000..0954b52
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt
@@ -0,0 +1,327 @@
+/*
+ * 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.shared.multiinstance
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.annotation.ColorInt
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
+import android.util.TypedValue
+import android.view.MotionEvent.ACTION_OUTSIDE
+import android.view.SurfaceView
+import android.view.View
+import android.view.View.ALPHA
+import android.view.View.SCALE_X
+import android.view.View.SCALE_Y
+import android.view.ViewGroup.MarginLayoutParams
+import android.widget.LinearLayout
+import android.window.TaskSnapshot
+import com.android.wm.shell.shared.R
+
+/**
+ * View for the All Windows menu option, used by both Desktop Windowing and Taskbar.
+ * The menu displays icons of all open instances of an app. Clicking the icon should launch
+ * the instance, which will be performed by the child class.
+ */
+abstract class ManageWindowsViewContainer(
+    val context: Context,
+    @ColorInt private val menuBackgroundColor: Int
+) {
+    lateinit var menuView: ManageWindowsView
+
+    /** Creates the base menu view and fills it with icon views. */
+    fun createMenu(snapshotList: List<Pair<Int, TaskSnapshot>>,
+             onIconClickListener: ((Int) -> Unit),
+             onOutsideClickListener: (() -> Unit)): ManageWindowsView {
+        val bitmapList = snapshotList.map { (index, snapshot) ->
+            index to Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace)
+        }
+        return createAndShowMenuView(
+            bitmapList,
+            onIconClickListener,
+            onOutsideClickListener
+        )
+    }
+
+    /** Creates the menu view with the given bitmaps, and displays it. */
+    fun createAndShowMenuView(
+        snapshotList: List<Pair<Int, Bitmap?>>,
+        onIconClickListener: ((Int) -> Unit),
+        onOutsideClickListener: (() -> Unit)
+    ): ManageWindowsView {
+        menuView = ManageWindowsView(context, menuBackgroundColor).apply {
+            this.onOutsideClickListener = onOutsideClickListener
+            this.onIconClickListener = onIconClickListener
+            this.generateIconViews(snapshotList)
+        }
+        addToContainer(menuView)
+        return menuView
+    }
+
+    /** Play the animation for opening the menu. */
+    fun animateOpen() {
+        menuView.animateOpen()
+    }
+
+    /**
+     * Play the animation for closing the menu. On finish, will run the provided callback,
+     * which will be responsible for removing the view from the container used in [addToContainer].
+     */
+    fun animateClose() {
+        menuView.animateClose { removeFromContainer() }
+    }
+
+    /** Adds the menu view to the container responsible for displaying it. */
+    abstract fun addToContainer(menuView: ManageWindowsView)
+
+    /** Removes the menu view from the container used in the method above */
+    abstract fun removeFromContainer()
+
+    companion object {
+        const val MANAGE_WINDOWS_MINIMUM_INSTANCES = 2
+    }
+
+    class ManageWindowsView(
+        private val context: Context,
+        menuBackgroundColor: Int
+    ) {
+        private val animators = mutableListOf<Animator>()
+        private val iconViews = mutableListOf<SurfaceView>()
+        val rootView: LinearLayout = LinearLayout(context)
+        var menuHeight = 0
+        var menuWidth = 0
+        var onIconClickListener: ((Int) -> Unit)? = null
+        var onOutsideClickListener: (() -> Unit)? = null
+
+        init {
+            rootView.orientation = LinearLayout.VERTICAL
+            val menuBackground = ShapeDrawable()
+            val menuRadius = getDimensionPixelSize(MENU_RADIUS_DP)
+            menuBackground.shape = RoundRectShape(
+                FloatArray(8) { menuRadius },
+                null,
+                null
+            )
+            menuBackground.paint.color = menuBackgroundColor
+            rootView.background = menuBackground
+            rootView.elevation = getDimensionPixelSize(MENU_ELEVATION_DP)
+            rootView.setOnTouchListener { _, event ->
+                if (event.actionMasked == ACTION_OUTSIDE) {
+                    onOutsideClickListener?.invoke()
+                }
+                return@setOnTouchListener true
+            }
+        }
+
+        private fun getDimensionPixelSize(sizeDp: Float): Float {
+            return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                sizeDp, context.resources.displayMetrics)
+        }
+
+        fun generateIconViews(
+            snapshotList: List<Pair<Int, Bitmap?>>
+        ) {
+            menuWidth = 0
+            menuHeight = 0
+            rootView.removeAllViews()
+            val instanceIconHeight = getDimensionPixelSize(ICON_HEIGHT_DP)
+            val instanceIconWidth = getDimensionPixelSize(ICON_WIDTH_DP)
+            val iconRadius = getDimensionPixelSize(ICON_RADIUS_DP)
+            val iconMargin = getDimensionPixelSize(ICON_MARGIN_DP)
+            var rowLayout: LinearLayout? = null
+            // Add each icon to the menu, adding a new row when needed.
+            for ((iconCount, taskInfoSnapshotPair) in snapshotList.withIndex()) {
+                val taskId = taskInfoSnapshotPair.first
+                val snapshotBitmap = taskInfoSnapshotPair.second
+                // Once a row is filled, make a new row and increase the menu height.
+                if (iconCount % MENU_MAX_ICONS_PER_ROW == 0) {
+                    rowLayout = LinearLayout(context)
+                    rowLayout.orientation = LinearLayout.HORIZONTAL
+                    rootView.addView(rowLayout)
+                    menuHeight += (instanceIconHeight + iconMargin).toInt()
+                }
+
+                val croppedBitmap = snapshotBitmap?.let { cropBitmap(it) }
+                val scaledSnapshotBitmap = croppedBitmap?.let {
+                    Bitmap.createScaledBitmap(
+                        it, instanceIconWidth.toInt(), instanceIconHeight.toInt(), true /* filter */
+                    )
+                }
+                val appSnapshotButton = SurfaceView(context)
+                appSnapshotButton.cornerRadius = iconRadius
+                appSnapshotButton.setZOrderOnTop(true)
+                appSnapshotButton.contentDescription = context.resources.getString(
+                    R.string.manage_windows_icon_text, iconCount + 1
+                )
+                appSnapshotButton.setOnClickListener {
+                    onIconClickListener?.invoke(taskId)
+                }
+                val lp = MarginLayoutParams(
+                    instanceIconWidth.toInt(), instanceIconHeight.toInt()
+                )
+                lp.apply {
+                    marginStart = iconMargin.toInt()
+                    topMargin = iconMargin.toInt()
+                }
+                appSnapshotButton.layoutParams = lp
+                // If we haven't already reached one full row, increment width.
+                if (iconCount < MENU_MAX_ICONS_PER_ROW) {
+                    menuWidth += (instanceIconWidth + iconMargin).toInt()
+                }
+                rowLayout?.addView(appSnapshotButton)
+                iconViews += appSnapshotButton
+                appSnapshotButton.requestLayout()
+                rowLayout?.post {
+                    appSnapshotButton.holder.surface
+                        .attachAndQueueBufferWithColorSpace(
+                            scaledSnapshotBitmap?.hardwareBuffer,
+                            scaledSnapshotBitmap?.colorSpace
+                        )
+                }
+            }
+            // Add margin again for the right/bottom of the menu.
+            menuWidth += iconMargin.toInt()
+            menuHeight += iconMargin.toInt()
+        }
+
+        private fun cropBitmap(
+            bitmapToCrop: Bitmap
+        ): Bitmap {
+            val ratioToMatch = ICON_WIDTH_DP / ICON_HEIGHT_DP
+            val bitmapWidth = bitmapToCrop.width
+            val bitmapHeight = bitmapToCrop.height
+            if (bitmapWidth > bitmapHeight * ratioToMatch) {
+                // Crop based on height
+                val newWidth = bitmapHeight * ratioToMatch
+                return Bitmap.createBitmap(
+                    bitmapToCrop,
+                    ((bitmapWidth - newWidth) / 2).toInt(),
+                    0,
+                    newWidth.toInt(),
+                    bitmapHeight
+                )
+            } else {
+                // Crop based on width
+                val newHeight = bitmapWidth / ratioToMatch
+                return Bitmap.createBitmap(
+                    bitmapToCrop,
+                    0,
+                    ((bitmapHeight - newHeight) / 2).toInt(),
+                    bitmapWidth,
+                    newHeight.toInt()
+                )
+            }
+        }
+
+        /** Play the animation for opening the menu. */
+        fun animateOpen() {
+            animateView(rootView, MENU_BOUNDS_SHRUNK_SCALE, MENU_BOUNDS_FULL_SCALE,
+                MENU_START_ALPHA, MENU_FULL_ALPHA
+            )
+            for (view in iconViews) {
+                animateView(view, MENU_BOUNDS_SHRUNK_SCALE, MENU_BOUNDS_FULL_SCALE,
+                    MENU_START_ALPHA, MENU_FULL_ALPHA
+                )
+            }
+            createAnimatorSet().start()
+        }
+
+        /** Play the animation for closing the menu. */
+        fun animateClose(callback: () -> Unit) {
+            animateView(rootView, MENU_BOUNDS_FULL_SCALE, MENU_BOUNDS_SHRUNK_SCALE,
+                MENU_FULL_ALPHA, MENU_START_ALPHA
+            )
+            for (view in iconViews) {
+                animateView(view, MENU_BOUNDS_FULL_SCALE, MENU_BOUNDS_SHRUNK_SCALE,
+                    MENU_FULL_ALPHA, MENU_START_ALPHA
+                )
+            }
+            createAnimatorSet().apply {
+                addListener(
+                    object : AnimatorListenerAdapter() {
+                        override fun onAnimationEnd(animation: Animator) {
+                            callback.invoke()
+                        }
+                    }
+                )
+                start()
+            }
+        }
+
+        private fun animateView(
+            view: View,
+            startBoundsScale: Float,
+            endBoundsScale: Float,
+            startAlpha: Float,
+            endAlpha: Float) {
+            animators += ObjectAnimator.ofFloat(
+                view,
+                SCALE_X,
+                startBoundsScale,
+                endBoundsScale
+            ).apply {
+                duration = MENU_BOUNDS_ANIM_DURATION
+            }
+            animators += ObjectAnimator.ofFloat(
+                view,
+                SCALE_Y,
+                startBoundsScale,
+                endBoundsScale
+            ).apply {
+                duration = MENU_BOUNDS_ANIM_DURATION
+            }
+            animators += ObjectAnimator.ofFloat(
+                view,
+                ALPHA,
+                startAlpha,
+                endAlpha
+            ).apply {
+                duration = MENU_ALPHA_ANIM_DURATION
+                startDelay = MENU_ALPHA_ANIM_DELAY
+            }
+        }
+
+        private fun createAnimatorSet(): AnimatorSet {
+            val animatorSet = AnimatorSet().apply {
+                playTogether(animators)
+            }
+            animators.clear()
+            return animatorSet
+        }
+
+        companion object {
+            private const val MENU_RADIUS_DP = 26f
+            private const val ICON_WIDTH_DP = 204f
+            private const val ICON_HEIGHT_DP = 127.5f
+            private const val ICON_RADIUS_DP = 16f
+            private const val ICON_MARGIN_DP = 16f
+            private const val MENU_ELEVATION_DP = 1f
+            private const val MENU_MAX_ICONS_PER_ROW = 3
+            private const val MENU_BOUNDS_ANIM_DURATION = 200L
+            private const val MENU_BOUNDS_SHRUNK_SCALE = 0.8f
+            private const val MENU_BOUNDS_FULL_SCALE = 1f
+            private const val MENU_ALPHA_ANIM_DURATION = 100L
+            private const val MENU_ALPHA_ANIM_DELAY = 50L
+            private const val MENU_START_ALPHA = 0f
+            private const val MENU_FULL_ALPHA = 1f
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
index 5a2a723..f9f43bc 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
@@ -85,6 +85,9 @@
     public @interface SplitIndex {
     }
 
+    /** Signifies that user is currently not in split screen. */
+    public static final int NOT_IN_SPLIT = -1;
+
     /**
      * A snap target for two apps, where the split is 33-66. With FLAG_ENABLE_FLEXIBLE_SPLIT,
      * only used on tablets.
@@ -152,6 +155,23 @@
     public @interface PersistentSnapPosition {}
 
     /**
+     * These are all the valid "states" that split screen can be in. It's the set of
+     * {@link PersistentSnapPosition} + {@link #NOT_IN_SPLIT}.
+     */
+    @IntDef(value = {
+            NOT_IN_SPLIT,
+            SNAP_TO_2_33_66,
+            SNAP_TO_2_50_50,
+            SNAP_TO_2_66_33,
+            SNAP_TO_2_90_10,
+            SNAP_TO_2_10_90,
+            SNAP_TO_3_33_33_33,
+            SNAP_TO_3_45_45_10,
+            SNAP_TO_3_10_45_45,
+    })
+    public @interface SplitScreenState {}
+
+    /**
      * Checks if the snapPosition in question is a {@link PersistentSnapPosition}.
      */
     public static boolean isPersistentSnapPosition(@SnapPosition int snapPosition) {
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 e9cfd9b..c024840 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
@@ -32,7 +32,6 @@
 import static com.android.window.flags.Flags.predictiveBackSystemAnims;
 import static com.android.window.flags.Flags.unifyBackNavigationTransition;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -308,7 +307,7 @@
         setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
         updateEnableAnimationFromFlags();
         createAdapter();
-        mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
+        mShellController.addExternalInterface(IBackAnimation.DESCRIPTOR,
                 this::createExternalInterface, this);
         mShellCommandHandler.addDumpCallback(this::dump, this);
         mShellController.addConfigurationChangeListener(this);
@@ -552,6 +551,7 @@
                     // start animation immediately for non-gestural sources (without ACTION_MOVE
                     // events)
                     mThresholdCrossed = true;
+                    mPointersPilfered = true;
                     onGestureStarted(touchX, touchY, swipeEdge);
                     mShouldStartOnNextMoveEvent = false;
                 } else {
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 3733930..7e5a82e 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
@@ -39,6 +39,8 @@
 import android.view.animation.Interpolator
 import android.view.animation.Transformation
 import android.window.BackEvent
+import android.window.BackEvent.EDGE_LEFT
+import android.window.BackEvent.EDGE_RIGHT
 import android.window.BackMotionEvent
 import android.window.BackNavigationInfo
 import android.window.BackProgressAnimator
@@ -50,6 +52,7 @@
 import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.internal.policy.SystemBarUtils
 import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags.predictiveBackTimestampApi
 import com.android.wm.shell.R
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.protolog.ShellProtoLogGroup
@@ -118,7 +121,9 @@
     private val postCommitFlingSpring = SpringForce(SPRING_SCALE)
             .setStiffness(SpringForce.STIFFNESS_LOW)
             .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+    private var swipeEdge = EDGE_LEFT
     protected var gestureProgress = 0f
+    private val velocityTracker = ProgressVelocityTracker()
 
     /** Background color to be used during the animation, also see [getBackgroundColor] */
     protected var customizedBackgroundColor = 0
@@ -175,6 +180,7 @@
             )
             return
         }
+        swipeEdge = backMotionEvent.swipeEdge
         triggerBack = backMotionEvent.triggerBack
         initialTouchPos.set(backMotionEvent.touchX, backMotionEvent.touchY)
 
@@ -241,6 +247,9 @@
         )
         applyTransaction()
         background.customizeStatusBarAppearance(currentClosingRect.top.toInt())
+        if (predictiveBackTimestampApi()) {
+            velocityTracker.addPosition(backEvent.frameTimeMillis, progress)
+        }
     }
 
     private fun getYOffset(centeredRect: RectF, touchY: Float): Float {
@@ -272,10 +281,19 @@
 
         // kick off spring animation with the current velocity from the pre-commit phase, this
         // affects the scaling of the closing and/or opening activity during post-commit
-        val startVelocity =
-            if (gestureProgress < 0.1f) -DEFAULT_FLING_VELOCITY else -velocity * SPRING_SCALE
+
+        var startVelocity = if (predictiveBackTimestampApi()) {
+            // pronounce fling animation more for gestures
+            val velocityFactor = if (swipeEdge == EDGE_LEFT || swipeEdge == EDGE_RIGHT) 2f else 1f
+            velocity * SPRING_SCALE * (1f - MAX_SCALE) * velocityFactor
+        } else {
+            velocity * SPRING_SCALE
+        }
+        if (gestureProgress < 0.1f) {
+            startVelocity = startVelocity.coerceAtLeast(DEFAULT_FLING_VELOCITY)
+        }
         val flingAnimation = SpringAnimation(postCommitFlingScale, SPRING_SCALE)
-            .setStartVelocity(startVelocity.coerceIn(-MAX_FLING_VELOCITY, 0f))
+            .setStartVelocity(-startVelocity.coerceIn(0f, MAX_FLING_VELOCITY))
             .setStartValue(SPRING_SCALE)
             .setSpring(postCommitFlingSpring)
         flingAnimation.start()
@@ -338,6 +356,7 @@
         lastPostCommitFlingScale = SPRING_SCALE
         gestureProgress = 0f
         triggerBack = false
+        velocityTracker.resetTracking()
     }
 
     protected fun applyTransform(
@@ -520,7 +539,11 @@
         override fun onBackInvoked() {
             triggerBack = true
             progressAnimator.reset()
-            onGestureCommitted(progressAnimator.velocity)
+            if (predictiveBackTimestampApi()) {
+                onGestureCommitted(velocityTracker.calculateVelocity())
+            } else {
+                onGestureCommitted(progressAnimator.velocity)
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index dc50fdb..f48b3ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.back;
 
-import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.window.BackEvent.EDGE_RIGHT;
@@ -43,10 +42,8 @@
 import android.view.Choreographer;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
-import android.view.MotionEvent;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
-import android.view.VelocityTracker;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.window.BackEvent;
@@ -132,9 +129,8 @@
     private final SpringForce mPostCommitFlingSpring = new SpringForce(SPRING_SCALE)
             .setStiffness(FLING_SPRING_STIFFNESS)
             .setDampingRatio(1f);
-    private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+    private final ProgressVelocityTracker mVelocityTracker = new ProgressVelocityTracker();
     private float mGestureProgress = 0f;
-    private long mDownTime = 0L;
 
     @Inject
     public CrossTaskBackAnimation(Context context, BackAnimationBackground background,
@@ -316,8 +312,7 @@
         mClosingCurrentRect.setEmpty();
         mInitialTouchPos.set(0, 0);
         mGestureProgress = 0;
-        mDownTime = 0;
-        mVelocityTracker.clear();
+        mVelocityTracker.resetTracking();
 
         if (mFinishCallback != null) {
             try {
@@ -333,22 +328,13 @@
     private void onGestureProgress(@NonNull BackEvent backEvent) {
         if (!mBackInProgress) {
             mBackInProgress = true;
-            mDownTime = backEvent.getFrameTimeMillis();
         }
         float progress = backEvent.getProgress();
         mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
         float interpolatedProgress = getInterpolatedProgress(progress);
         if (predictiveBackTimestampApi()) {
-            mVelocityTracker.addMovement(
-                    MotionEvent.obtain(
-                            /* downTime */ mDownTime,
-                            /* eventTime */ backEvent.getFrameTimeMillis(),
-                            /* action */ ACTION_MOVE,
-                            /* x */ interpolatedProgress * SPRING_SCALE,
-                            /* y */ 0f,
-                            /* metaState */ 0
-                    )
-            );
+            mVelocityTracker.addPosition(backEvent.getFrameTimeMillis(),
+                    interpolatedProgress * SPRING_SCALE);
         }
         updateGestureBackProgress(interpolatedProgress, backEvent);
     }
@@ -362,9 +348,8 @@
         if (predictiveBackTimestampApi()) {
             // kick off spring animation with the current velocity from the pre-commit phase, this
             // affects the scaling of the closing and/or opening task during post-commit
-            mVelocityTracker.computeCurrentVelocity(1000);
             float startVelocity = mGestureProgress < 0.1f
-                    ? -DEFAULT_FLING_VELOCITY : -mVelocityTracker.getXVelocity();
+                    ? -DEFAULT_FLING_VELOCITY : -mVelocityTracker.calculateVelocity();
             SpringAnimation flingAnimation =
                     new SpringAnimation(mPostCommitFlingScale, SPRING_SCALE)
                     .setStartVelocity(Math.max(-MAX_FLING_VELOCITY, Math.min(0f, startVelocity)))
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ProgressVelocityTracker.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ProgressVelocityTracker.kt
new file mode 100644
index 0000000..6bbda0f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ProgressVelocityTracker.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.wm.shell.back
+
+import android.view.MotionEvent
+import android.view.VelocityTracker
+
+internal class ProgressVelocityTracker {
+    private val velocityTracker: VelocityTracker = VelocityTracker.obtain()
+    private var downTime = -1L
+
+    fun addPosition(timeMillis: Long, position: Float) {
+        if (downTime == -1L) downTime = timeMillis
+        velocityTracker.addMovement(
+            MotionEvent.obtain(
+                /* downTime */ downTime,
+                /* eventTime */ timeMillis,
+                /* action */ MotionEvent.ACTION_MOVE,
+                /* x */ position,
+                /* y */ 0f,
+                /* metaState */0
+            )
+        )
+    }
+
+    /** calculates current velocity (unit: progress per second) */
+    fun calculateVelocity(): Float {
+        velocityTracker.computeCurrentVelocity(1000)
+        return velocityTracker.xVelocity
+    }
+
+    fun resetTracking() {
+        velocityTracker.clear()
+        downTime = -1L
+    }
+}
\ No newline at end of file
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 b82496e..bec73a1 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
@@ -35,7 +35,6 @@
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
 
 import android.annotation.BinderThread;
 import android.annotation.NonNull;
@@ -274,8 +273,11 @@
     private final DragAndDropController mDragAndDropController;
     /** Used to send bubble events to launcher. */
     private Bubbles.BubbleStateListener mBubbleStateListener;
-    /** Used to track previous navigation mode to detect switch to buttons navigation. */
-    private boolean mIsPrevNavModeGestures;
+    /**
+     * Used to track previous navigation mode to detect switch to buttons navigation. Set to
+     * true to switch the bubble bar to the opposite side for 3 nav buttons mode on device boot.
+     */
+    private boolean mIsPrevNavModeGestures = true;
     /** Used to send updates to the views from {@link #mBubbleDataListener}. */
     private BubbleViewCallback mBubbleViewCallback;
 
@@ -357,7 +359,6 @@
             }
         };
         mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this);
-        mIsPrevNavModeGestures = ContextUtils.isGestureNavigationMode(mContext);
     }
 
     private void registerOneHandedState(OneHandedController oneHanded) {
@@ -520,7 +521,7 @@
         }
 
         mShellController.addConfigurationChangeListener(this);
-        mShellController.addExternalInterface(KEY_EXTRA_SHELL_BUBBLES,
+        mShellController.addExternalInterface(IBubbles.DESCRIPTOR,
                 this::createExternalInterface, this);
         mShellCommandHandler.addDumpCallback(this::dump, this);
     }
@@ -593,9 +594,9 @@
         if (mBubbleStateListener != null) {
             boolean isCurrentNavModeGestures = ContextUtils.isGestureNavigationMode(mContext);
             if (mIsPrevNavModeGestures && !isCurrentNavModeGestures) {
-                BubbleBarLocation navButtonsLocation = ContextUtils.isRtl(mContext)
+                BubbleBarLocation bubbleBarLocation = ContextUtils.isRtl(mContext)
                         ? BubbleBarLocation.RIGHT : BubbleBarLocation.LEFT;
-                mBubblePositioner.setBubbleBarLocation(navButtonsLocation);
+                mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
             }
             mIsPrevNavModeGestures = isCurrentNavModeGestures;
             BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
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 068b2d2..0fd4206 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
@@ -101,9 +101,13 @@
     private int mBubbleBarTopOnScreen;
 
     public BubblePositioner(Context context, WindowManager windowManager) {
+        this(context, DeviceConfig.create(context, windowManager));
+    }
+
+    public BubblePositioner(Context context, DeviceConfig deviceConfig) {
         mContext = context;
-        mDeviceConfig = DeviceConfig.create(context, windowManager);
-        update(mDeviceConfig);
+        mDeviceConfig = deviceConfig;
+        update(deviceConfig);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 9fe7a97..ed49417 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -402,7 +402,7 @@
     }
 
     void animateExpansionWhenTaskViewVisible(Runnable animateExpansion) {
-        if (mVisibilityState == TaskViewVisibilityState.VISIBLE) {
+        if (mVisibilityState == TaskViewVisibilityState.VISIBLE || mIsOverflow) {
             animateExpansion.run();
         } else {
             mAnimateExpansion = animateExpansion;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 12d20bf..f532be6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -96,14 +96,6 @@
     }
 
     /**
-     * Get all the displays from DisplayManager.
-     */
-    public Display[] getDisplays() {
-        final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
-        return displayManager.getDisplays();
-    }
-
-    /**
      * Gets the DisplayLayout associated with a display.
      */
     public @Nullable DisplayLayout getDisplayLayout(int displayId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 38087c0..38b8592 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -403,8 +403,11 @@
 
         @Override
         // TODO(b/335404678): pass control target
-        public void setImeInputTargetRequestedVisibility(boolean visible) {
+        public void setImeInputTargetRequestedVisibility(boolean visible,
+                @NonNull ImeTracker.Token statsToken) {
             if (android.view.inputmethod.Flags.refactorInsetsController()) {
+                ImeTracker.forLogging().onProgress(statsToken,
+                        ImeTracker.PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE);
                 mImeRequestedVisible = visible;
                 dispatchImeRequested(mDisplayId, mImeRequestedVisible);
 
@@ -414,21 +417,21 @@
                 // therefore have to start the show animation from here
                 startAnimation(mImeRequestedVisible /* show */, false /* forceRestart */);
 
-                setVisibleDirectly(mImeRequestedVisible || mAnimation != null);
+                setVisibleDirectly(mImeRequestedVisible || mAnimation != null, statsToken);
             }
         }
 
         /**
          * Sends the local visibility state back to window manager. Needed for legacy adjustForIme.
          */
-        private void setVisibleDirectly(boolean visible) {
+        private void setVisibleDirectly(boolean visible, @Nullable ImeTracker.Token statsToken) {
             mInsetsState.setSourceVisible(InsetsSource.ID_IME, visible);
             mRequestedVisibleTypes = visible
                     ? mRequestedVisibleTypes | WindowInsets.Type.ime()
                     : mRequestedVisibleTypes & ~WindowInsets.Type.ime();
             try {
                 mWmService.updateDisplayWindowRequestedVisibleTypes(mDisplayId,
-                        mRequestedVisibleTypes);
+                        mRequestedVisibleTypes, statsToken);
             } catch (RemoteException e) {
             }
         }
@@ -640,7 +643,7 @@
                         t.hide(animatingLeash);
                         removeImeSurface(mDisplayId);
                         if (android.view.inputmethod.Flags.refactorInsetsController()) {
-                            setVisibleDirectly(false /* visible */);
+                            setVisibleDirectly(false /* visible */, statsToken);
                         }
                         ImeTracker.forLogging().onHidden(mStatsToken);
                     } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) {
@@ -669,13 +672,13 @@
             if (!android.view.inputmethod.Flags.refactorInsetsController() && !show) {
                 // When going away, queue up insets change first, otherwise any bounds changes
                 // can have a "flicker" of ime-provided insets.
-                setVisibleDirectly(false /* visible */);
+                setVisibleDirectly(false /* visible */, null /* statsToken */);
             }
             mAnimation.start();
             if (!android.view.inputmethod.Flags.refactorInsetsController() && show) {
                 // When showing away, queue up insets change last, otherwise any bounds changes
                 // can have a "flicker" of ime-provided insets.
-                setVisibleDirectly(true /* visible */);
+                setVisibleDirectly(true /* visible */, null /* statsToken */);
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index c4c177c..c45f09b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.common;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.os.RemoteException;
@@ -223,13 +224,14 @@
             }
         }
 
-        private void setImeInputTargetRequestedVisibility(boolean visible) {
+        private void setImeInputTargetRequestedVisibility(boolean visible,
+                @NonNull ImeTracker.Token statsToken) {
             CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
             if (listeners == null) {
                 return;
             }
             for (OnInsetsChangedListener listener : listeners) {
-                listener.setImeInputTargetRequestedVisibility(visible);
+                listener.setImeInputTargetRequestedVisibility(visible, statsToken);
             }
         }
 
@@ -276,10 +278,11 @@
             }
 
             @Override
-            public void setImeInputTargetRequestedVisibility(boolean visible)
+            public void setImeInputTargetRequestedVisibility(boolean visible,
+                    @NonNull ImeTracker.Token statsToken)
                     throws RemoteException {
                 mMainExecutor.execute(() -> {
-                    PerDisplay.this.setImeInputTargetRequestedVisibility(visible);
+                    PerDisplay.this.setImeInputTargetRequestedVisibility(visible, statsToken);
                 });
             }
         }
@@ -345,7 +348,10 @@
          * Called to set the requested visibility of the IME in DisplayImeController. Invoked by
          * {@link com.android.server.wm.DisplayContent.RemoteInsetsControlTarget}.
          * @param visible requested status of the IME
+         * @param statsToken the token tracking the current IME request
          */
-        default void setImeInputTargetRequestedVisibility(boolean visible) {}
+        default void setImeInputTargetRequestedVisibility(boolean visible,
+                @NonNull ImeTracker.Token statsToken) {
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
index 9abf0f6..de5c834 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
@@ -33,6 +33,9 @@
 
     default void onRecentTaskListFrozenChanged(boolean frozen) { }
 
+    /** A task is removed from recents as a result of another task being added to recent tasks. */
+    default void onRecentTaskRemovedForAddTask(int taskId) { }
+
     @BinderThread
     default void onTaskStackChangedBackground() { }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
index d8859ba..4e1dec6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
@@ -59,6 +59,7 @@
     private static final int ON_TASK_LIST_FROZEN_UNFROZEN = 18;
     private static final int ON_TASK_DESCRIPTION_CHANGED = 19;
     private static final int ON_ACTIVITY_ROTATION = 20;
+    private static final int ON_RECENT_TASK_REMOVED_FOR_ADD_TASK = 21;
 
     /**
      * List of {@link TaskStackListenerCallback} registered from {@link #addListener}.
@@ -132,6 +133,11 @@
     }
 
     @Override
+    public void onRecentTaskRemovedForAddTask(int taskId) {
+        mMainHandler.obtainMessage(ON_RECENT_TASK_REMOVED_FOR_ADD_TASK, taskId).sendToTarget();
+    }
+
+    @Override
     public void onTaskStackChanged() {
         // Call the task changed callback for the non-ui thread listeners first. Copy to a set
         // of temp listeners so that we don't lock on mTaskStackListeners while calling all the
@@ -408,6 +414,13 @@
                     }
                     break;
                 }
+                case ON_RECENT_TASK_REMOVED_FOR_ADD_TASK: {
+                    final int taskId = (int) msg.obj;
+                    for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                        mTaskStackListeners.get(i).onRecentTaskRemovedForAddTask(taskId);
+                    }
+                    break;
+                }
                 case ON_TASK_DESCRIPTION_CHANGED: {
                     final ActivityManager.RunningTaskInfo
                             info = (ActivityManager.RunningTaskInfo) msg.obj;
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 6beff19..1852cda 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
@@ -55,6 +55,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Handler;
+import android.util.Log;
 import android.view.Display;
 import android.view.InsetsController;
 import android.view.InsetsSource;
@@ -142,6 +143,9 @@
     @ShellMainThread
     private final Handler mHandler;
 
+    /** Singleton source of truth for the current state of split screen on this device. */
+    private final SplitState mSplitState;
+
     private int mDividerWindowWidth;
     private int mDividerInsets;
     private int mDividerSize;
@@ -204,7 +208,8 @@
             SplitLayoutHandler splitLayoutHandler,
             SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
             DisplayController displayController, DisplayImeController displayImeController,
-            ShellTaskOrganizer taskOrganizer, int parallaxType, @ShellMainThread Handler handler) {
+            ShellTaskOrganizer taskOrganizer, int parallaxType, SplitState splitState,
+            @ShellMainThread Handler handler) {
         mHandler = handler;
         mContext = context.createConfigurationContext(configuration);
         mOrientation = configuration.orientation;
@@ -220,6 +225,7 @@
         mTaskOrganizer = taskOrganizer;
         mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
         mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
+        mSplitState = splitState;
 
         final Resources res = mContext.getResources();
         mDimNonImeSide = res.getBoolean(R.bool.config_dimNonImeAttachedSide);
@@ -381,6 +387,11 @@
         return mDividerSnapAlgorithm.calculateNearestSnapPosition(mDividerPosition);
     }
 
+    /** Updates the {@link SplitState} using the current divider position. */
+    public void updateStateWithCurrentPosition() {
+        mSplitState.set(calculateCurrentSnapPosition());
+    }
+
     /**
      * Returns the divider position as a fraction from 0 to 1.
      */
@@ -413,7 +424,13 @@
             removeTouchZones();
         }
 
-        int currentPosition = calculateCurrentSnapPosition();
+        int currentPosition = mSplitState.get();
+        // TODO (b/349828130): Can delete this warning after brief soak time.
+        if (currentPosition != calculateCurrentSnapPosition()) {
+            Log.wtf(TAG, "SplitState is " + mSplitState.get()
+                    + ", expected " + calculateCurrentSnapPosition());
+        }
+
         switch (currentPosition) {
             case SNAP_TO_2_10_90:
             case SNAP_TO_3_10_45_45:
@@ -764,7 +781,10 @@
                 break;
             default:
                 flingDividerPosition(currentPosition, snapTarget.position, duration, interpolator,
-                        () -> setDividerPosition(snapTarget.position, true /* applyLayoutChange */));
+                        () -> {
+                            setDividerPosition(snapTarget.position, true /* applyLayoutChange */);
+                            mSplitState.set(snapTarget.snapPosition);
+                        });
                 break;
         }
     }
@@ -836,10 +856,12 @@
 
     /** Fling divider from current position to center position. */
     public void flingDividerToCenter(@Nullable Runnable finishCallback) {
-        final int pos = mDividerSnapAlgorithm.getMiddleTarget().position;
+        final SnapTarget target = mDividerSnapAlgorithm.getMiddleTarget();
+        final int pos = target.position;
         flingDividerPosition(getDividerPosition(), pos, FLING_ENTER_DURATION, FAST_OUT_SLOW_IN,
                 () -> {
                     setDividerPosition(pos, true /* applyLayoutChange */);
+                    mSplitState.set(target.snapPosition);
                     if (finishCallback != null) {
                         finishCallback.run();
                     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
new file mode 100644
index 0000000..71758e0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
@@ -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.wm.shell.common.split;
+
+import static com.android.wm.shell.shared.split.SplitScreenConstants.NOT_IN_SPLIT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState;
+
+/**
+ * A class that manages the "state" of split screen. See {@link SplitScreenState} for definitions.
+ */
+public class SplitState {
+    private @SplitScreenState int mState = NOT_IN_SPLIT;
+
+    /** Updates the current state of split screen on this device. */
+    public void set(@SplitScreenState int newState) {
+        mState = newState;
+    }
+
+    /** Reports the current state of split screen on this device. */
+    public @SplitScreenState int get() {
+        return mState;
+    }
+
+    /** Sets NOT_IN_SPLIT when user exits split. */
+    public void exit() {
+        set(NOT_IN_SPLIT);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/transition/TransitionStateHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/transition/TransitionStateHolder.kt
new file mode 100644
index 0000000..4dda2a8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/transition/TransitionStateHolder.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 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.common.transition
+
+import com.android.wm.shell.dagger.WMSingleton
+import com.android.wm.shell.recents.RecentsTransitionHandler
+import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransitionState
+import com.android.wm.shell.recents.RecentsTransitionStateListener.isRunning
+import com.android.wm.shell.sysui.ShellInit
+import javax.inject.Inject
+
+/**
+ * Holder for the state of the transitions.
+ */
+@WMSingleton
+class TransitionStateHolder @Inject constructor(
+    shellInit: ShellInit,
+    private val recentsTransitionHandler: RecentsTransitionHandler
+) {
+
+    @Volatile
+    @RecentsTransitionState
+    private var recentsTransitionState: Int =
+        RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING
+
+    init {
+        shellInit.addInitCallback({ onInit() }, this)
+    }
+
+    fun isRecentsTransitionRunning(): Boolean = isRunning(recentsTransitionState)
+
+    private fun onInit() {
+        recentsTransitionHandler.addTransitionStateListener(
+            object : RecentsTransitionStateListener {
+                override fun onTransitionStateChanged(@RecentsTransitionState state: Int) {
+                    recentsTransitionState = state
+                }
+            }
+        )
+    }
+}
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 c99d9ba8..9d4b4bb 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
@@ -54,6 +54,7 @@
 import com.android.wm.shell.compatui.api.CompatUIHandler;
 import com.android.wm.shell.compatui.api.CompatUIInfo;
 import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonClicked;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
@@ -65,6 +66,7 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -194,7 +196,7 @@
     private final CompatUIStatusManager mCompatUIStatusManager;
 
     @NonNull
-    private final IntPredicate mInDesktopModePredicate;
+    private final Optional<DesktopUserRepositories> mDesktopUserRepositories;
 
     public CompatUIController(@NonNull Context context,
             @NonNull ShellInit shellInit,
@@ -210,7 +212,7 @@
             @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler,
             @NonNull AccessibilityManager accessibilityManager,
             @NonNull CompatUIStatusManager compatUIStatusManager,
-            @NonNull IntPredicate isDesktopModeEnablePredicate) {
+            @NonNull Optional<DesktopUserRepositories> desktopUserRepositories) {
         mContext = context;
         mShellController = shellController;
         mDisplayController = displayController;
@@ -226,7 +228,7 @@
         mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis(
                 DISAPPEAR_DELAY_MS, flags);
         mCompatUIStatusManager = compatUIStatusManager;
-        mInDesktopModePredicate = isDesktopModeEnablePredicate;
+        mDesktopUserRepositories = desktopUserRepositories;
         shellInit.addInitCallback(this::onInit, this);
     }
 
@@ -267,7 +269,6 @@
             updateActiveTaskInfo(taskInfo);
         }
 
-
         // We're showing the first reachability education so we ignore incoming TaskInfo
         // until the education flow has completed or we double tap. The double-tap
         // basically cancel all the onboarding flow. We don't have to ignore events in case
@@ -865,7 +866,11 @@
     }
 
     private boolean isInDesktopMode(@Nullable TaskInfo taskInfo) {
-        return taskInfo != null && Flags.skipCompatUiEducationInDesktopMode()
-                && mInDesktopModePredicate.test(taskInfo.displayId);
+        if (mDesktopUserRepositories.isEmpty() || taskInfo == null) {
+            return false;
+        }
+        boolean isDesktopModeShowing = mDesktopUserRepositories.get().getCurrent()
+                .getVisibleTaskCount(taskInfo.displayId) > 0;
+        return Flags.skipCompatUiEducationInDesktopMode() && isDesktopModeShowing;
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
index 49c2785..688f8ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
@@ -68,7 +68,7 @@
 
     private void setViewVisibility(@IdRes int resId, boolean show) {
         final View view = findViewById(resId);
-        int visibility = show ? View.VISIBLE : View.INVISIBLE;
+        int visibility = show ? View.VISIBLE : View.GONE;
         if (view.getVisibility() == visibility) {
             return;
         }
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 2128cbc..0d16880 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
@@ -26,6 +26,7 @@
 import android.graphics.Rect;
 import android.util.Pair;
 import android.view.LayoutInflater;
+import android.view.SurfaceControl;
 import android.view.View;
 import android.window.DesktopModeFlags;
 
@@ -70,6 +71,9 @@
 
     private final float mHideScmTolerance;
 
+    @NonNull
+    private final Rect mLayoutBounds = new Rect();
+
     CompatUIWindowManager(@NonNull Context context, @NonNull TaskInfo taskInfo,
                           @NonNull SyncTransactionQueue syncQueue,
                           @NonNull Consumer<CompatUIEvent> callback,
@@ -105,6 +109,7 @@
 
     @Override
     protected void removeLayout() {
+        mLayoutBounds.setEmpty();
         mLayout = null;
     }
 
@@ -171,18 +176,21 @@
     @Override
     @VisibleForTesting
     public void updateSurfacePosition() {
-        if (mLayout == null) {
+        updateLayoutBounds();
+        if (mLayoutBounds.isEmpty()) {
             return;
         }
-        // Position of the button in the container coordinate.
-        final Rect taskBounds = getTaskBounds();
-        final Rect taskStableBounds = getTaskStableBounds();
-        final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
-                ? taskStableBounds.left - taskBounds.left
-                : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth();
-        final int positionY = taskStableBounds.bottom - taskBounds.top
-                - mLayout.getMeasuredHeight();
-        updateSurfacePosition(positionX, positionY);
+        updateSurfacePosition(mLayoutBounds.left, mLayoutBounds.top);
+    }
+
+    @Override
+    @VisibleForTesting
+    public void updateSurfacePosition(@NonNull SurfaceControl.Transaction tx) {
+        updateLayoutBounds();
+        if (mLayoutBounds.isEmpty()) {
+            return;
+        }
+        updateSurfaceBounds(tx, mLayoutBounds);
     }
 
     @VisibleForTesting
@@ -219,6 +227,23 @@
         return percentageAreaOfLetterboxInTask < mHideScmTolerance;
     }
 
+    private void updateLayoutBounds() {
+        if (mLayout == null) {
+            mLayoutBounds.setEmpty();
+            return;
+        }
+        // Position of the button in the container coordinate.
+        final Rect taskBounds = getTaskBounds();
+        final Rect taskStableBounds = getTaskStableBounds();
+        final int layoutWidth = mLayout.getMeasuredWidth();
+        final int layoutHeight = mLayout.getMeasuredHeight();
+        final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+                ? taskStableBounds.left - taskBounds.left
+                : taskStableBounds.right - taskBounds.left - layoutWidth;
+        final int positionY = taskStableBounds.bottom - taskBounds.top - layoutHeight;
+        mLayoutBounds.set(positionX, positionY, positionX + layoutWidth, positionY + layoutHeight);
+    }
+
     private void updateVisibilityOfViews() {
         if (mLayout == null) {
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index d2b4f1a..82acfe5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -43,6 +43,7 @@
 import android.view.WindowlessWindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -327,8 +328,15 @@
         if (mViewHost == null) {
             return;
         }
-        mViewHost.relayout(windowLayoutParams);
-        updateSurfacePosition();
+        if (Flags.appCompatAsyncRelayout()) {
+            mViewHost.relayout(windowLayoutParams, tx -> {
+                updateSurfacePosition(tx);
+                tx.apply();
+            });
+        } else {
+            mViewHost.relayout(windowLayoutParams);
+            updateSurfacePosition();
+        }
     }
 
     @NonNull
@@ -349,6 +357,10 @@
      */
     protected abstract void updateSurfacePosition();
 
+    protected void updateSurfacePosition(@NonNull SurfaceControl.Transaction tx) {
+
+    }
+
     /**
      * Updates the position of the surface with respect to the given {@code positionX} and {@code
      * positionY}.
@@ -366,6 +378,15 @@
         });
     }
 
+    protected void updateSurfaceBounds(@NonNull SurfaceControl.Transaction tx,
+            @NonNull Rect bounds) {
+        if (mLeash == null) {
+            return;
+        }
+        tx.setPosition(mLeash, bounds.left, bounds.top)
+                .setWindowCrop(mLeash, bounds.width(), bounds.height());
+    }
+
     protected int getLayoutDirection() {
         return mContext.getResources().getConfiguration().getLayoutDirection();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
index fd1bbc4..b141beb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
@@ -100,7 +100,7 @@
 
     private void setViewVisibility(@IdRes int resId, boolean show) {
         final View view = findViewById(resId);
-        int visibility = show ? View.VISIBLE : View.INVISIBLE;
+        int visibility = show ? View.VISIBLE : View.GONE;
         if (view.getVisibility() == visibility) {
             return;
         }
@@ -171,7 +171,7 @@
         fadeOut.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                view.setVisibility(View.INVISIBLE);
+                view.setVisibility(View.GONE);
             }
         });
         fadeOut.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index 3f67172..650d2170 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -27,6 +27,7 @@
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.view.LayoutInflater;
+import android.view.SurfaceControl;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
 
@@ -69,6 +70,9 @@
     @NonNull
     final CompatUIHintsState mCompatUIHintsState;
 
+    @NonNull
+    private final Rect mLayoutBounds = new Rect();
+
     @Nullable
     private UserAspectRatioSettingsLayout mLayout;
 
@@ -108,6 +112,7 @@
 
     @Override
     protected void removeLayout() {
+        mLayoutBounds.setEmpty();
         mLayout = null;
     }
 
@@ -168,18 +173,21 @@
     @Override
     @VisibleForTesting
     public void updateSurfacePosition() {
-        if (mLayout == null) {
+        updateLayoutBounds();
+        if (mLayoutBounds.isEmpty()) {
             return;
         }
-        // Position of the button in the container coordinate.
-        final Rect taskBounds = getTaskBounds();
-        final Rect taskStableBounds = getTaskStableBounds();
-        final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
-                ? taskStableBounds.left - taskBounds.left
-                : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth();
-        final int positionY = taskStableBounds.bottom - taskBounds.top
-                - mLayout.getMeasuredHeight();
-        updateSurfacePosition(positionX, positionY);
+        updateSurfacePosition(mLayoutBounds.left, mLayoutBounds.top);
+    }
+
+    @Override
+    @VisibleForTesting
+    public void updateSurfacePosition(@NonNull SurfaceControl.Transaction tx) {
+        updateLayoutBounds();
+        if (mLayoutBounds.isEmpty()) {
+            return;
+        }
+        updateSurfaceBounds(tx, mLayoutBounds);
     }
 
     @VisibleForTesting
@@ -202,6 +210,23 @@
                 && !isHideDelayReached(mNextButtonHideTimeMs));
     }
 
+    private void updateLayoutBounds() {
+        if (mLayout == null) {
+            mLayoutBounds.setEmpty();
+            return;
+        }
+        // Position of the button in the container coordinate.
+        final Rect taskBounds = getTaskBounds();
+        final Rect taskStableBounds = getTaskStableBounds();
+        final int layoutWidth = mLayout.getMeasuredWidth();
+        final int layoutHeight = mLayout.getMeasuredHeight();
+        final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+                ? taskStableBounds.left - taskBounds.left
+                : taskStableBounds.right - taskBounds.left - layoutWidth;
+        final int positionY = taskStableBounds.bottom - taskBounds.top - layoutHeight;
+        mLayoutBounds.set(positionX, positionY, positionX + layoutWidth, positionY + layoutHeight);
+    }
+
     private void showUserAspectRatioButton() {
         if (mLayout == null) {
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
index 0ac7aff..2c52e9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
@@ -18,133 +18,52 @@
 
 import android.graphics.Rect
 import android.view.SurfaceControl
-import com.android.internal.protolog.ProtoLog
-import com.android.wm.shell.dagger.WMSingleton
-import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
-import javax.inject.Inject
+import android.view.SurfaceControl.Transaction
 
 /**
- * Component responsible for handling the lifecycle of the letterbox surfaces.
+ * Abstracts the component responsible to handle a single or multiple letterbox surfaces for a
+ * specific [Change].
  */
-@WMSingleton
-class LetterboxController @Inject constructor(
-    private val letterboxConfiguration: LetterboxConfiguration
-) {
-
-    companion object {
-        /*
-         * Letterbox surfaces need to stay below the activity layer which is 0.
-         */
-        // TODO(b/378673153): Consider adding this to [TaskConstants].
-        @JvmStatic
-        private val TASK_CHILD_LAYER_LETTERBOX_BACKGROUND = -1000
-        @JvmStatic
-        private val TAG = "LetterboxController"
-    }
-
-    private val letterboxMap = mutableMapOf<LetterboxKey, LetterboxItem>()
+interface LetterboxController {
 
     /**
      * Creates a Letterbox Surface for a given displayId/taskId if it doesn't exist.
      */
     fun createLetterboxSurface(
         key: LetterboxKey,
-        startTransaction: SurfaceControl.Transaction,
+        transaction: Transaction,
         parentLeash: SurfaceControl
-    ) {
-        letterboxMap.runOnItem(key, onMissed = { k, m ->
-            m[k] = LetterboxItem(
-                SurfaceControl.Builder()
-                    .setName("ShellLetterboxSurface-$key")
-                    .setHidden(true)
-                    .setColorLayer()
-                    .setParent(parentLeash)
-                    .setCallsite("LetterboxController-createLetterboxSurface")
-                    .build().apply {
-                        startTransaction.setLayer(
-                            this,
-                            TASK_CHILD_LAYER_LETTERBOX_BACKGROUND
-                        ).setColorSpaceAgnostic(this, true)
-                            .setColor(this, letterboxConfiguration.getBackgroundColorRgbArray())
-                    }
-            )
-        })
-    }
+    )
 
     /**
      * Invoked to destroy the surfaces for a letterbox session for given displayId/taskId.
      */
     fun destroyLetterboxSurface(
         key: LetterboxKey,
-        startTransaction: SurfaceControl.Transaction
-    ) {
-        letterboxMap.runOnItem(key, onFound = { item ->
-            item.fullWindowSurface?.run {
-                startTransaction.remove(this)
-            }
-        })
-        letterboxMap.remove(key)
-    }
+        transaction: Transaction
+    )
 
     /**
      * Invoked to show/hide the letterbox surfaces for given displayId/taskId.
      */
     fun updateLetterboxSurfaceVisibility(
         key: LetterboxKey,
-        startTransaction: SurfaceControl.Transaction,
-        visible: Boolean = true
-    ) {
-        letterboxMap.runOnItem(key, onFound = { item ->
-            item.fullWindowSurface?.run {
-                startTransaction.setVisibility(this, visible)
-            }
-        })
-    }
+        transaction: Transaction,
+        visible: Boolean
+    )
 
     /**
      * Updates the bounds for the letterbox surfaces for given displayId/taskId.
      */
     fun updateLetterboxSurfaceBounds(
         key: LetterboxKey,
-        startTransaction: SurfaceControl.Transaction,
-        bounds: Rect
-    ) {
-        letterboxMap.runOnItem(key, onFound = { item ->
-            item.fullWindowSurface?.run {
-                startTransaction.moveAndCrop(this, bounds)
-            }
-        })
-    }
+        transaction: Transaction,
+        taskBounds: Rect,
+        activityBounds: Rect
+    )
 
-    /*
-     * Executes [onFound] on the [LetterboxItem] if present or [onMissed] if not present.
+    /**
+     * Utility method to dump the current state.
      */
-    private fun MutableMap<LetterboxKey, LetterboxItem>.runOnItem(
-        key: LetterboxKey,
-        onFound: (LetterboxItem) -> Unit = { _ -> },
-        onMissed: (
-            LetterboxKey,
-            MutableMap<LetterboxKey, LetterboxItem>
-        ) -> Unit = { _, _ -> }
-    ) {
-        this[key]?.let {
-            return onFound(it)
-        }
-        return onMissed(key, this)
-    }
-
-    fun dump() {
-        ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, "${letterboxMap.keys}")
-    }
-
-    private fun SurfaceControl.Transaction.moveAndCrop(
-        surface: SurfaceControl,
-        rect: Rect
-    ): SurfaceControl.Transaction =
-        setPosition(surface, rect.left.toFloat(), rect.top.toFloat())
-            .setWindowCrop(
-                surface,
-                rect.width(),
-                rect.height()
-            )
+    fun dump()
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
new file mode 100644
index 0000000..9e3edf6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import com.android.wm.shell.dagger.WMSingleton
+import javax.inject.Inject
+
+/**
+ * Encapsulate the logic related to the use of a single or multiple surfaces when
+ * implementing letterbox in shell.
+ */
+@WMSingleton
+class LetterboxControllerStrategy @Inject constructor() {
+
+    // Different letterbox implementation modes.
+    enum class LetterboxMode { SINGLE_SURFACE, MULTIPLE_SURFACES }
+
+    @Volatile
+    private var currentMode: LetterboxMode = LetterboxMode.SINGLE_SURFACE
+
+    fun configureLetterboxMode() {
+        // TODO(b/377875146): Define criteria for switching between [LetterboxMode]s.
+        currentMode = if (android.os.SystemProperties.getInt(
+                "multi_interface",
+                0
+            ) == 0
+        ) {
+            LetterboxMode.SINGLE_SURFACE
+        } else {
+            LetterboxMode.MULTIPLE_SURFACES
+        }
+    }
+
+    /**
+     * @return The specific mode to use for implementing letterboxing for the given [request].
+     */
+    fun getLetterboxImplementationMode(): LetterboxMode = currentMode
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
index 98fd247..771d618 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
@@ -21,5 +21,13 @@
 // The key to use for identify the letterbox sessions.
 data class LetterboxKey(val displayId: Int, val taskId: Int)
 
-// Encapsulate the objects for the specific letterbox session.
-data class LetterboxItem(val fullWindowSurface: SurfaceControl?)
\ No newline at end of file
+// Encapsulates the surfaces in the multiple surfaces scenario.
+data class LetterboxSurfaces(
+    var leftSurface: SurfaceControl? = null,
+    var topSurface: SurfaceControl? = null,
+    var rightSurface: SurfaceControl? = null,
+    var bottomSurface: SurfaceControl? = null
+) : Iterable<SurfaceControl?> {
+    override fun iterator() =
+        listOf(leftSurface, topSurface, rightSurface, bottomSurface).iterator()
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilder.kt
new file mode 100644
index 0000000..e88d91f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilder.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.view.SurfaceControl
+import com.android.wm.shell.dagger.WMSingleton
+import javax.inject.Inject
+
+/**
+ * Component responsible for the actual creation of the Letterbox surfaces.
+ */
+@WMSingleton
+class LetterboxSurfaceBuilder @Inject constructor(
+    private val letterboxConfiguration: LetterboxConfiguration
+) {
+
+    companion object {
+        /*
+         * Letterbox surfaces need to stay below the activity layer which is 0.
+         */
+        // TODO(b/378673153): Consider adding this to [TaskConstants].
+        @JvmStatic
+        private val TASK_CHILD_LAYER_LETTERBOX_BACKGROUND = -1000
+    }
+
+    fun createSurface(
+        tx: SurfaceControl.Transaction,
+        parentLeash: SurfaceControl,
+        surfaceName: String,
+        callSite: String,
+        surfaceBuilder: SurfaceControl.Builder = SurfaceControl.Builder()
+    ) = surfaceBuilder
+        .setName(surfaceName)
+        .setHidden(true)
+        .setColorLayer()
+        .setParent(parentLeash)
+        .setCallsite(callSite)
+        .build().apply {
+            tx.setLayer(
+                this,
+                TASK_CHILD_LAYER_LETTERBOX_BACKGROUND
+            ).setColorSpaceAgnostic(this, true)
+                .setColor(this, letterboxConfiguration.getBackgroundColorRgbArray())
+        }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
index 67429bd..4718071 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
@@ -22,6 +22,7 @@
 import android.window.TransitionInfo
 import com.android.internal.protolog.ProtoLog
 import com.android.window.flags.Flags.appCompatRefactoring
+import com.android.wm.shell.common.transition.TransitionStateHolder
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
 import com.android.wm.shell.shared.TransitionUtil.isClosingType
 import com.android.wm.shell.sysui.ShellInit
@@ -33,22 +34,21 @@
 class LetterboxTransitionObserver(
     shellInit: ShellInit,
     private val transitions: Transitions,
-    private val letterboxController: LetterboxController
+    private val letterboxController: LetterboxController,
+    private val transitionStateHolder: TransitionStateHolder,
+    private val letterboxModeStrategy: LetterboxControllerStrategy
 ) : Transitions.TransitionObserver {
 
     companion object {
         @JvmStatic
         private val TAG = "LetterboxTransitionObserver"
+        @JvmStatic
+        private val EMPTY_BOUNDS = Rect()
     }
 
     init {
         if (appCompatRefactoring()) {
-            ProtoLog.v(
-                WM_SHELL_APP_COMPAT,
-                "%s: %s",
-                TAG,
-                "Initializing LetterboxTransitionObserver"
-            )
+            logV("Initializing LetterboxTransitionObserver")
             shellInit.addInitCallback({
                 transitions.registerObserver(this)
             }, this)
@@ -64,43 +64,53 @@
         // We recognise the operation to execute and delegate to the LetterboxController
         // the related operation.
         // TODO(b/377875151): Identify Desktop Windowing Transactions.
-        // TODO(b/377857898): Handling multiple surfaces
         // TODO(b/371500295): Handle input events detection.
         for (change in info.changes) {
             change.taskInfo?.let { ti ->
                 val key = LetterboxKey(ti.displayId, ti.taskId)
-                if (isClosingType(change.mode)) {
-                    letterboxController.destroyLetterboxSurface(
-                        key,
-                        startTransaction
-                    )
-                } else {
-                    val isTopActivityLetterboxed = ti.appCompatTaskInfo.isTopActivityLetterboxed
-                    if (isTopActivityLetterboxed) {
-                        letterboxController.createLetterboxSurface(
-                            key,
-                            startTransaction,
-                            change.leash
-                        )
-                        letterboxController.updateLetterboxSurfaceBounds(
-                            key,
-                            startTransaction,
-                            Rect(
-                                change.endRelOffset.x,
-                                change.endRelOffset.y,
-                                change.endAbsBounds.width(),
-                                change.endAbsBounds.height()
+                val taskBounds = Rect(
+                    change.endRelOffset.x,
+                    change.endRelOffset.y,
+                    change.endAbsBounds.width(),
+                    change.endAbsBounds.height()
+                )
+                with(letterboxController) {
+                    // TODO(b/380274087) Handle return to home from a recents transition.
+                    if (isClosingType(change.mode) &&
+                        !transitionStateHolder.isRecentsTransitionRunning()) {
+                        // For the other types of close we need to check the recents.
+                        destroyLetterboxSurface(key, finishTransaction)
+                    } else {
+                        val isTopActivityLetterboxed = ti.appCompatTaskInfo.isTopActivityLetterboxed
+                        if (isTopActivityLetterboxed) {
+                            letterboxModeStrategy.configureLetterboxMode()
+                            createLetterboxSurface(
+                                key,
+                                startTransaction,
+                                change.leash
                             )
+                            val activityBounds =
+                                ti.appCompatTaskInfo.topActivityLetterboxBounds ?: EMPTY_BOUNDS
+                            updateLetterboxSurfaceBounds(
+                                key,
+                                startTransaction,
+                                taskBounds,
+                                activityBounds
+                            )
+                        }
+                        updateLetterboxSurfaceVisibility(
+                            key,
+                            startTransaction,
+                            isTopActivityLetterboxed
                         )
                     }
-                    letterboxController.updateLetterboxSurfaceVisibility(
-                        key,
-                        startTransaction,
-                        isTopActivityLetterboxed
-                    )
+                    dump()
                 }
-                letterboxController.dump()
             }
         }
     }
+
+    private fun logV(msg: String) {
+        ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, msg)
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt
new file mode 100644
index 0000000..ef964f4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.graphics.Rect
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+
+/**
+ * Creates a [LetterboxController] which is the composition of other two [LetterboxController].
+ * It basically invokes the method on both of them.
+ */
+infix fun LetterboxController.append(other: LetterboxController) = object : LetterboxController {
+    override fun createLetterboxSurface(
+        key: LetterboxKey,
+        transaction: Transaction,
+        parentLeash: SurfaceControl
+    ) {
+        this@append.createLetterboxSurface(key, transaction, parentLeash)
+        other.createLetterboxSurface(key, transaction, parentLeash)
+    }
+
+    override fun destroyLetterboxSurface(
+        key: LetterboxKey,
+        transaction: Transaction
+    ) {
+        this@append.destroyLetterboxSurface(key, transaction)
+        other.destroyLetterboxSurface(key, transaction)
+    }
+
+    override fun updateLetterboxSurfaceVisibility(
+        key: LetterboxKey,
+        transaction: Transaction,
+        visible: Boolean
+    ) {
+        this@append.updateLetterboxSurfaceVisibility(key, transaction, visible)
+        other.updateLetterboxSurfaceVisibility(key, transaction, visible)
+    }
+
+    override fun updateLetterboxSurfaceBounds(
+        key: LetterboxKey,
+        transaction: Transaction,
+        taskBounds: Rect,
+        activityBounds: Rect
+    ) {
+        this@append.updateLetterboxSurfaceBounds(key, transaction, taskBounds, activityBounds)
+        other.updateLetterboxSurfaceBounds(key, transaction, taskBounds, activityBounds)
+    }
+
+    override fun dump() {
+        this@append.dump()
+        other.dump()
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxController.kt
new file mode 100644
index 0000000..8d06570
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxController.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.MULTIPLE_SURFACES
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.SINGLE_SURFACE
+import com.android.wm.shell.dagger.WMSingleton
+import javax.inject.Inject
+
+/**
+ * [LetterboxController] implementation working as coordinator of other [LetterboxController]
+ * implementations.
+ */
+@WMSingleton
+class MixedLetterboxController @Inject constructor(
+    private val singleSurfaceController: SingleSurfaceLetterboxController,
+    private val multipleSurfaceController: MultiSurfaceLetterboxController,
+    private val controllerStrategy: LetterboxControllerStrategy
+) : LetterboxController by singleSurfaceController append multipleSurfaceController {
+
+    override fun createLetterboxSurface(
+        key: LetterboxKey,
+        transaction: Transaction,
+        parentLeash: SurfaceControl
+    ) {
+        when (controllerStrategy.getLetterboxImplementationMode()) {
+            SINGLE_SURFACE -> {
+                multipleSurfaceController.destroyLetterboxSurface(key, transaction)
+                singleSurfaceController.createLetterboxSurface(key, transaction, parentLeash)
+            }
+
+            MULTIPLE_SURFACES -> {
+                singleSurfaceController.destroyLetterboxSurface(key, transaction)
+                multipleSurfaceController.createLetterboxSurface(key, transaction, parentLeash)
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxController.kt
new file mode 100644
index 0000000..5129d03
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxController.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.graphics.Rect
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.dagger.WMSingleton
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
+import javax.inject.Inject
+
+/**
+ * Component responsible for handling the lifecycle of multiple letterbox surfaces when needed.
+ */
+@WMSingleton
+class MultiSurfaceLetterboxController @Inject constructor(
+    private val letterboxBuilder: LetterboxSurfaceBuilder
+) : LetterboxController {
+
+    companion object {
+        @JvmStatic
+        private val TAG = "MultiSurfaceLetterboxController"
+    }
+
+    private val letterboxMap = mutableMapOf<LetterboxKey, LetterboxSurfaces>()
+
+    override fun createLetterboxSurface(
+        key: LetterboxKey,
+        transaction: Transaction,
+        parentLeash: SurfaceControl
+    ) {
+        val surfaceBuilderFn = { position: String ->
+            letterboxBuilder.createSurface(
+                transaction,
+                parentLeash,
+                "ShellLetterboxSurface-$key-$position",
+                "MultiSurfaceLetterboxController#createLetterboxSurface"
+            )
+        }
+        letterboxMap.runOnItem(key, onMissed = { k, m ->
+            m[k] = LetterboxSurfaces(
+                leftSurface = surfaceBuilderFn("Left"),
+                topSurface = surfaceBuilderFn("Top"),
+                rightSurface = surfaceBuilderFn("Right"),
+                bottomSurface = surfaceBuilderFn("Bottom"),
+            )
+        })
+    }
+
+    override fun destroyLetterboxSurface(
+        key: LetterboxKey,
+        transaction: Transaction
+    ) {
+        letterboxMap.runOnItem(key, onFound = { item ->
+            item.forEach { s ->
+                s.remove(transaction)
+            }
+        })
+        letterboxMap.remove(key)
+    }
+
+    override fun updateLetterboxSurfaceVisibility(
+        key: LetterboxKey,
+        transaction: Transaction,
+        visible: Boolean
+    ) {
+        letterboxMap.runOnItem(key, onFound = { item ->
+            item.forEach { s ->
+                s.setVisibility(transaction, visible)
+            }
+        })
+    }
+
+    override fun updateLetterboxSurfaceBounds(
+        key: LetterboxKey,
+        transaction: Transaction,
+        taskBounds: Rect,
+        activityBounds: Rect
+    ) {
+        letterboxMap.runOnItem(key, onFound = { item ->
+            item.updateSurfacesBounds(transaction, taskBounds, activityBounds)
+        })
+    }
+
+    override fun dump() {
+        ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, "${letterboxMap.keys}")
+    }
+
+    /*
+     * Executes [onFound] on the [LetterboxItem] if present or [onMissed] if not present.
+     */
+    private fun MutableMap<LetterboxKey, LetterboxSurfaces>.runOnItem(
+        key: LetterboxKey,
+        onFound: (LetterboxSurfaces) -> Unit = { _ -> },
+        onMissed: (
+            LetterboxKey,
+            MutableMap<LetterboxKey, LetterboxSurfaces>
+        ) -> Unit = { _, _ -> }
+    ) {
+        this[key]?.let {
+            return onFound(it)
+        }
+        return onMissed(key, this)
+    }
+
+    private fun SurfaceControl?.remove(
+        tx: Transaction
+    ) = this?.let {
+        tx.remove(this)
+    }
+
+    private fun SurfaceControl?.setVisibility(
+        tx: Transaction,
+        visible: Boolean
+    ) = this?.let {
+        tx.setVisibility(this, visible)
+    }
+
+    private fun Transaction.moveAndCrop(
+        surface: SurfaceControl,
+        rect: Rect
+    ): Transaction =
+        setPosition(surface, rect.left.toFloat(), rect.top.toFloat())
+            .setWindowCrop(
+                surface,
+                rect.width(),
+                rect.height()
+            )
+
+    private fun LetterboxSurfaces.updateSurfacesBounds(
+        tx: Transaction,
+        taskBounds: Rect,
+        activityBounds: Rect
+    ) {
+        // Update the bounds depending on the activity position.
+        leftSurface?.let { s ->
+            tx.moveAndCrop(
+                s,
+                Rect(taskBounds.left, taskBounds.top, activityBounds.left, taskBounds.bottom)
+            )
+        }
+        rightSurface?.let { s ->
+            tx.moveAndCrop(
+                s,
+                Rect(activityBounds.right, taskBounds.top, taskBounds.right, taskBounds.bottom)
+            )
+        }
+        topSurface?.let { s ->
+            tx.moveAndCrop(
+                s,
+                Rect(taskBounds.left, taskBounds.top, taskBounds.right, activityBounds.top)
+            )
+        }
+        bottomSurface?.let { s ->
+            tx.moveAndCrop(
+                s,
+                Rect(taskBounds.left, activityBounds.bottom, taskBounds.right, taskBounds.bottom)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt
new file mode 100644
index 0000000..a67f608
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.graphics.Rect
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.dagger.WMSingleton
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
+import javax.inject.Inject
+
+/**
+ * Component responsible for handling the lifecycle of a single letterbox surface.
+ */
+@WMSingleton
+class SingleSurfaceLetterboxController @Inject constructor(
+    private val letterboxBuilder: LetterboxSurfaceBuilder
+) : LetterboxController {
+
+    companion object {
+        @JvmStatic
+        private val TAG = "SingleSurfaceLetterboxController"
+    }
+
+    private val letterboxMap = mutableMapOf<LetterboxKey, SurfaceControl>()
+
+    /**
+     * Creates a Letterbox Surface for a given displayId/taskId if it doesn't exist.
+     */
+    override fun createLetterboxSurface(
+        key: LetterboxKey,
+        transaction: Transaction,
+        parentLeash: SurfaceControl
+    ) {
+        letterboxMap.runOnItem(key, onMissed = { k, m ->
+            m[k] = letterboxBuilder.createSurface(
+                transaction,
+                parentLeash,
+                surfaceName = "ShellLetterboxSurface-$key",
+                callSite = "LetterboxController-createLetterboxSurface"
+            )
+        })
+    }
+
+    /**
+     * Invoked to destroy the surfaces for a letterbox session for given displayId/taskId.
+     */
+    override fun destroyLetterboxSurface(
+        key: LetterboxKey,
+        transaction: Transaction
+    ) {
+        letterboxMap.runOnItem(key, onFound = { item ->
+            item.run {
+                transaction.remove(this)
+            }
+        })
+        letterboxMap.remove(key)
+    }
+
+    /**
+     * Invoked to show/hide the letterbox surfaces for given displayId/taskId.
+     */
+    override fun updateLetterboxSurfaceVisibility(
+        key: LetterboxKey,
+        transaction: Transaction,
+        visible: Boolean
+    ) {
+        letterboxMap.runOnItem(key, onFound = { item ->
+            item.run {
+                transaction.setVisibility(this, visible)
+            }
+        })
+    }
+
+    /**
+     * Updates the bounds for the letterbox surfaces for given displayId/taskId.
+     */
+    override fun updateLetterboxSurfaceBounds(
+        key: LetterboxKey,
+        transaction: Transaction,
+        taskBounds: Rect,
+        activityBounds: Rect
+    ) {
+        letterboxMap.runOnItem(key, onFound = { item ->
+            item.run {
+                transaction.moveAndCrop(this, taskBounds)
+            }
+        })
+    }
+
+    override fun dump() {
+        ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, "${letterboxMap.keys}")
+    }
+
+    /*
+     * Executes [onFound] on the [SurfaceControl] if present or [onMissed] if not present.
+     */
+    private fun MutableMap<LetterboxKey, SurfaceControl>.runOnItem(
+        key: LetterboxKey,
+        onFound: (SurfaceControl) -> Unit = { _ -> },
+        onMissed: (
+            LetterboxKey,
+            MutableMap<LetterboxKey, SurfaceControl>
+        ) -> Unit = { _, _ -> }
+    ) {
+        this[key]?.let {
+            return onFound(it)
+        }
+        return onMissed(key, this)
+    }
+
+    private fun Transaction.moveAndCrop(
+        surface: SurfaceControl,
+        rect: Rect
+    ): Transaction =
+        setPosition(surface, rect.left.toFloat(), rect.top.toFloat())
+            .setWindowCrop(
+                surface,
+                rect.width(),
+                rect.height()
+            )
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 33e4fd8..aebd94f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -30,6 +30,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.split.SplitState;
 import com.android.wm.shell.dagger.pip.TvPipModule;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.shared.TransactionPool;
@@ -89,6 +90,7 @@
             Optional<RecentTasksController> recentTasks,
             LaunchAdjacentController launchAdjacentController,
             MultiInstanceHelper multiInstanceHelper,
+            SplitState splitState,
             @ShellMainThread ShellExecutor mainExecutor,
             Handler mainHandler,
             SystemWindows systemWindows) {
@@ -96,6 +98,6 @@
                 shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
                 displayImeController, displayInsetsController, transitions, transactionPool,
                 iconProvider, recentTasks, launchAdjacentController, multiInstanceHelper,
-                mainExecutor, mainHandler, systemWindows);
+                splitState, mainExecutor, mainHandler, systemWindows);
     }
 }
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 6023750..de86b22 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,6 +72,7 @@
 import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.common.split.SplitState;
 import com.android.wm.shell.compatui.CompatUIConfiguration;
 import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
@@ -87,8 +88,8 @@
 import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository;
 import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator;
 import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
 import com.android.wm.shell.freeform.FreeformComponents;
@@ -138,7 +139,6 @@
 import dagger.Provides;
 
 import java.util.Optional;
-import java.util.function.IntPredicate;
 
 /**
  * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
@@ -267,7 +267,7 @@
             Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
             Lazy<AccessibilityManager> accessibilityManager,
             CompatUIRepository compatUIRepository,
-            Optional<DesktopRepository> desktopRepository,
+            Optional<DesktopUserRepositories> desktopUserRepositories,
             @NonNull CompatUIState compatUIState,
             @NonNull CompatUIComponentIdGenerator componentIdGenerator,
             @NonNull CompatUIComponentFactory compatUIComponentFactory,
@@ -280,10 +280,6 @@
                     new DefaultCompatUIHandler(compatUIRepository, compatUIState,
                             componentIdGenerator, compatUIComponentFactory, mainExecutor));
         }
-        final IntPredicate inDesktopModePredicate =
-                desktopRepository.<IntPredicate>map(modeTaskRepository -> displayId ->
-                        modeTaskRepository.getVisibleTaskCount(displayId) > 0)
-                            .orElseGet(() -> displayId -> false);
         return Optional.of(
                 new CompatUIController(
                         context,
@@ -300,7 +296,7 @@
                         compatUIShellCommandHandler.get(),
                         accessibilityManager.get(),
                         compatUIStatusManager,
-                        inDesktopModePredicate));
+                        desktopUserRepositories));
     }
 
     @WMSingleton
@@ -704,14 +700,14 @@
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
             ActivityTaskManager activityTaskManager,
-            Optional<DesktopRepository> desktopRepository,
+            Optional<DesktopUserRepositories> desktopUserRepositories,
             TaskStackTransitionObserver taskStackTransitionObserver,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         return Optional.ofNullable(
                 RecentTasksController.create(context, shellInit, shellController,
                         shellCommandHandler, taskStackListener, activityTaskManager,
-                        desktopRepository, taskStackTransitionObserver, mainExecutor));
+                        desktopUserRepositories, taskStackTransitionObserver, mainExecutor));
     }
 
     @BindsOptionalOf
@@ -799,10 +795,11 @@
             Transitions transitions,
             TaskStackListenerImpl taskStackListener,
             @ShellMainThread Handler mainHandler,
-            @ShellMainThread ShellExecutor mainExecutor) {
+            @ShellMainThread ShellExecutor mainExecutor,
+            FocusTransitionObserver focusTransitionObserver) {
         return new KeyguardTransitionHandler(
                 shellInit, shellController, displayController, transitions, taskStackListener,
-                mainHandler, mainExecutor);
+                mainHandler, mainExecutor, focusTransitionObserver);
     }
 
     @WMSingleton
@@ -866,6 +863,12 @@
         return Optional.empty();
     }
 
+    @WMSingleton
+    @Provides
+    static SplitState provideSplitState() {
+        return new SplitState();
+    }
+
     //
     // Starting window
     //
@@ -1001,16 +1004,16 @@
 
     @BindsOptionalOf
     @DynamicOverride
-    abstract DesktopRepository optionalDesktopRepository();
+    abstract DesktopUserRepositories optionalDesktopUserRepositories();
 
     @WMSingleton
     @Provides
-    static Optional<DesktopRepository> provideDesktopRepository(Context context,
-            @DynamicOverride Optional<Lazy<DesktopRepository>> desktopRepository) {
+    static Optional<DesktopUserRepositories> provideDesktopUserRepositories(Context context,
+            @DynamicOverride Optional<Lazy<DesktopUserRepositories>> desktopUserRepositories) {
         // 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 desktopRepository.flatMap((lazy) -> {
+        return desktopUserRepositories.flatMap((lazy) -> {
             if (DesktopModeStatus.canEnterDesktopMode(context)) {
                 return Optional.of(lazy.get());
             }
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 635531e..806be8c 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
@@ -17,6 +17,7 @@
 package com.android.wm.shell.dagger;
 
 import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX;
 import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY;
 import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
 import static android.window.DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS;
@@ -50,6 +51,7 @@
 import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
 import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
 import com.android.wm.shell.apptoweb.AssistContentRequester;
+import com.android.wm.shell.back.BackAnimationController;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.BubbleData;
 import com.android.wm.shell.bubbles.BubbleDataRepository;
@@ -68,9 +70,13 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.split.SplitState;
+import com.android.wm.shell.common.transition.TransitionStateHolder;
 import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler;
 import com.android.wm.shell.compatui.letterbox.LetterboxController;
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy;
 import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver;
+import com.android.wm.shell.compatui.letterbox.MixedLetterboxController;
 import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
 import com.android.wm.shell.dagger.pip.PipModule;
 import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
@@ -85,11 +91,11 @@
 import com.android.wm.shell.desktopmode.DesktopModeKeyGestureHandler;
 import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
 import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
-import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.DesktopTaskChangeListener;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
 import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
 import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
 import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
@@ -149,6 +155,8 @@
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
+import com.android.wm.shell.windowdecor.common.viewhost.DefaultWindowDecorViewHostSupplier;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
 import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController;
 import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController;
 import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel;
@@ -336,6 +344,13 @@
         return new AdditionalSystemViewContainer.Factory();
     }
 
+    @WMSingleton
+    @Provides
+    static WindowDecorViewHostSupplier provideWindowDecorViewHostSupplier(
+            @ShellMainThread @NonNull CoroutineScope mainScope) {
+        return new DefaultWindowDecorViewHostSupplier(mainScope);
+    }
+
     //
     // Freeform
     //
@@ -361,7 +376,7 @@
             Context context,
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<DesktopRepository> desktopRepository,
+            Optional<DesktopUserRepositories> desktopUserRepositories,
             Optional<DesktopTasksController> desktopTasksController,
             LaunchAdjacentController launchAdjacentController,
             WindowDecorViewModel windowDecorViewModel,
@@ -373,7 +388,7 @@
                 context,
                 init,
                 shellTaskOrganizer,
-                desktopRepository,
+                desktopUserRepositories,
                 desktopTasksController,
                 launchAdjacentController,
                 windowDecorViewModel,
@@ -491,6 +506,7 @@
             Optional<WindowDecorViewModel> windowDecorViewModel,
             Optional<DesktopTasksController> desktopTasksController,
             MultiInstanceHelper multiInstanceHelper,
+            SplitState splitState,
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler) {
         return new SplitScreenController(
@@ -514,6 +530,7 @@
                 desktopTasksController,
                 null /* stageCoordinator */,
                 multiInstanceHelper,
+                splitState,
                 mainExecutor,
                 mainHandler);
     }
@@ -689,7 +706,7 @@
             DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler,
             ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
             DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
-            @DynamicOverride DesktopRepository desktopRepository,
+            @DynamicOverride DesktopUserRepositories desktopUserRepositories,
             Optional<DesktopImmersiveController> desktopImmersiveController,
             DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
             LaunchAdjacentController launchAdjacentController,
@@ -703,6 +720,7 @@
             InputManager inputManager,
             FocusTransitionObserver focusTransitionObserver,
             DesktopModeEventLogger desktopModeEventLogger,
+            DesktopModeUiEventLogger desktopModeUiEventLogger,
             DesktopTilingDecorViewModel desktopTilingDecorViewModel) {
         return new DesktopTasksController(
                 context,
@@ -724,7 +742,7 @@
                 toggleResizeDesktopTaskTransitionHandler,
                 dragToDesktopTransitionHandler,
                 desktopImmersiveController.get(),
-                desktopRepository,
+                desktopUserRepositories,
                 recentsTransitionHandler,
                 multiInstanceHelper,
                 mainExecutor,
@@ -733,6 +751,7 @@
                 interactionJankMonitor,
                 mainHandler,
                 desktopModeEventLogger,
+                desktopModeUiEventLogger,
                 desktopTilingDecorViewModel);
     }
 
@@ -746,7 +765,7 @@
             ShellTaskOrganizer shellTaskOrganizer,
             ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
             ReturnToDragStartAnimator returnToDragStartAnimator,
-            @DynamicOverride DesktopRepository desktopRepository,
+            @DynamicOverride DesktopUserRepositories desktopUserRepositories,
             DesktopModeEventLogger desktopModeEventLogger) {
         return new DesktopTilingDecorViewModel(
                 context,
@@ -757,7 +776,7 @@
                 shellTaskOrganizer,
                 toggleResizeDesktopTaskTransitionHandler,
                 returnToDragStartAnimator,
-                desktopRepository,
+                desktopUserRepositories,
                 desktopModeEventLogger
         );
     }
@@ -765,10 +784,10 @@
     @WMSingleton
     @Provides
     static Optional<TaskChangeListener> provideDesktopTaskChangeListener(
-            Context context, @DynamicOverride DesktopRepository desktopRepository) {
+            Context context, @DynamicOverride DesktopUserRepositories desktopUserRepositories) {
         if (ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue()
                 && DesktopModeStatus.canEnterDesktopMode(context)) {
-            return Optional.of(new DesktopTaskChangeListener(desktopRepository));
+            return Optional.of(new DesktopTaskChangeListener(desktopUserRepositories));
         }
         return Optional.empty();
     }
@@ -778,7 +797,7 @@
     static Optional<DesktopTasksLimiter> provideDesktopTasksLimiter(
             Context context,
             Transitions transitions,
-            @DynamicOverride DesktopRepository desktopRepository,
+            @DynamicOverride DesktopUserRepositories desktopUserRepositories,
             ShellTaskOrganizer shellTaskOrganizer,
             InteractionJankMonitor interactionJankMonitor,
             @ShellMainThread Handler handler) {
@@ -791,7 +810,7 @@
         return Optional.of(
                 new DesktopTasksLimiter(
                         transitions,
-                        desktopRepository,
+                        desktopUserRepositories,
                         shellTaskOrganizer,
                         maxTaskLimit,
                         interactionJankMonitor,
@@ -805,7 +824,7 @@
             Context context,
             ShellInit shellInit,
             Transitions transitions,
-            @DynamicOverride DesktopRepository desktopRepository,
+            @DynamicOverride DesktopUserRepositories desktopUserRepositories,
             DisplayController displayController,
             ShellTaskOrganizer shellTaskOrganizer,
             ShellCommandHandler shellCommandHandler) {
@@ -814,7 +833,7 @@
                     new DesktopImmersiveController(
                             shellInit,
                             transitions,
-                            desktopRepository,
+                            desktopUserRepositories,
                             displayController,
                             shellTaskOrganizer,
                             shellCommandHandler));
@@ -837,7 +856,8 @@
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             InteractionJankMonitor interactionJankMonitor) {
         return (Flags.enableDesktopWindowingTransitions()
-                        || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS.isTrue())
+                        || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS.isTrue()
+                        || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX.isTrue())
                 ? new SpringDragToDesktopTransitionHandler(
                         context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor)
                 : new DefaultDragToDesktopTransitionHandler(
@@ -852,14 +872,17 @@
             Optional<DesktopTasksController> desktopTasksController,
             InputManager inputManager,
             ShellTaskOrganizer shellTaskOrganizer,
-            FocusTransitionObserver focusTransitionObserver) {
+            FocusTransitionObserver focusTransitionObserver,
+            @ShellMainThread ShellExecutor mainExecutor,
+            DisplayController displayController) {
         if (DesktopModeStatus.canEnterDesktopMode(context) && useKeyGestureEventHandler()
                 && manageKeyGestures()
                 && (Flags.enableMoveToNextDisplayShortcut()
                 || Flags.enableTaskResizingKeyboardShortcuts())) {
             return Optional.of(new DesktopModeKeyGestureHandler(context,
                     desktopModeWindowDecorViewModel, desktopTasksController,
-                    inputManager, shellTaskOrganizer, focusTransitionObserver));
+                    inputManager, shellTaskOrganizer, focusTransitionObserver,
+                    mainExecutor, displayController));
         }
         return Optional.empty();
     }
@@ -876,13 +899,14 @@
             ShellCommandHandler shellCommandHandler,
             IWindowManager windowManager,
             ShellTaskOrganizer taskOrganizer,
-            @DynamicOverride DesktopRepository desktopRepository,
+            @DynamicOverride DesktopUserRepositories desktopUserRepositories,
             DisplayController displayController,
             ShellController shellController,
             DisplayInsetsController displayInsetsController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
+            Optional<DesktopImmersiveController> desktopImmersiveController,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             InteractionJankMonitor interactionJankMonitor,
             AppToWebGenericLinksParser genericLinksParser,
@@ -902,8 +926,9 @@
         }
         return Optional.of(new DesktopModeWindowDecorViewModel(context, shellExecutor, mainHandler,
                 mainChoreographer, bgExecutor, shellInit, shellCommandHandler, windowManager,
-                taskOrganizer, desktopRepository, displayController, shellController,
+                taskOrganizer, desktopUserRepositories, displayController, shellController,
                 displayInsetsController, syncQueue, transitions, desktopTasksController,
+                desktopImmersiveController.get(),
                 rootTaskDisplayAreaOrganizer, interactionJankMonitor, genericLinksParser,
                 assistContentRequester, multiInstanceHelper, desktopTasksLimiter,
                 appHandleEducationController, appToWebEducationController,
@@ -919,7 +944,7 @@
             @ShellAnimationThread ShellExecutor animExecutor,
             ShellInit shellInit,
             Transitions transitions,
-            @DynamicOverride DesktopRepository desktopRepository) {
+            @DynamicOverride DesktopUserRepositories desktopUserRepositories) {
         if (!DesktopModeStatus.canEnterDesktopMode(context)
                 || !ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
                 || !Flags.enableDesktopSystemDialogsTransitions()) {
@@ -928,7 +953,7 @@
         return Optional.of(
                 new SystemModalsTransitionHandler(
                         context, mainExecutor, animExecutor, shellInit, transitions,
-                        desktopRepository));
+                        desktopUserRepositories));
     }
 
     @WMSingleton
@@ -987,16 +1012,17 @@
     @WMSingleton
     @Provides
     @DynamicOverride
-    static DesktopRepository provideDesktopRepository(
+    static DesktopUserRepositories provideDesktopUserRepositories(
             Context context,
             ShellInit shellInit,
             DesktopPersistentRepository desktopPersistentRepository,
             DesktopRepositoryInitializer desktopRepositoryInitializer,
-            @ShellMainThread CoroutineScope mainScope
+            @ShellMainThread CoroutineScope mainScope,
+            UserManager userManager
     ) {
-        return new DesktopRepository(context, shellInit, desktopPersistentRepository,
+        return new DesktopUserRepositories(context, shellInit, desktopPersistentRepository,
                 desktopRepositoryInitializer,
-                mainScope);
+                mainScope, userManager);
     }
 
     @WMSingleton
@@ -1007,7 +1033,7 @@
             ShellTaskOrganizer shellTaskOrganizer,
             TaskStackListenerImpl taskStackListener,
             ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
-            @DynamicOverride DesktopRepository desktopRepository) {
+            @DynamicOverride DesktopUserRepositories desktopUserRepositories) {
         if (DesktopModeStatus.canEnterDesktopMode(context)) {
             return Optional.of(
                     new DesktopActivityOrientationChangeHandler(
@@ -1016,7 +1042,7 @@
                             shellTaskOrganizer,
                             taskStackListener,
                             toggleResizeDesktopTaskTransitionHandler,
-                            desktopRepository));
+                            desktopUserRepositories));
         }
         return Optional.empty();
     }
@@ -1025,12 +1051,13 @@
     @Provides
     static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver(
             Context context,
-            Optional<DesktopRepository> desktopRepository,
+            Optional<DesktopUserRepositories> desktopUserRepositories,
             Transitions transitions,
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
+            Optional<BackAnimationController> backAnimationController,
             ShellInit shellInit) {
-        return desktopRepository.flatMap(
+        return desktopUserRepositories.flatMap(
                 repository ->
                         Optional.of(
                                 new DesktopTasksTransitionObserver(
@@ -1039,6 +1066,7 @@
                                         transitions,
                                         shellTaskOrganizer,
                                         desktopMixedTransitionHandler.get(),
+                                        backAnimationController.get(),
                                         shellInit)));
     }
 
@@ -1047,7 +1075,7 @@
     static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
             Context context,
             Transitions transitions,
-            @DynamicOverride DesktopRepository desktopRepository,
+            @DynamicOverride DesktopUserRepositories desktopUserRepositories,
             FreeformTaskTransitionHandler freeformTaskTransitionHandler,
             CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
             Optional<DesktopImmersiveController> desktopImmersiveController,
@@ -1065,7 +1093,7 @@
                 new DesktopMixedTransitionHandler(
                         context,
                         transitions,
-                        desktopRepository,
+                        desktopUserRepositories,
                         freeformTaskTransitionHandler,
                         closeDesktopTaskTransitionHandler,
                         desktopImmersiveController.get(),
@@ -1310,8 +1338,16 @@
     static LetterboxTransitionObserver provideLetterboxTransitionObserver(
             @NonNull ShellInit shellInit,
             @NonNull Transitions transitions,
-            @NonNull LetterboxController letterboxController
+            @NonNull LetterboxController letterboxController,
+            @NonNull TransitionStateHolder transitionStateHolder,
+            @NonNull LetterboxControllerStrategy letterboxControllerStrategy
     ) {
-        return new LetterboxTransitionObserver(shellInit, transitions, letterboxController);
+        return new LetterboxTransitionObserver(shellInit, transitions, letterboxController,
+                transitionStateHolder, letterboxControllerStrategy);
     }
+
+    @WMSingleton
+    @Binds
+    abstract LetterboxController bindsLetterboxController(
+            MixedLetterboxController letterboxController);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 3cd5df3..cfdfe3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -42,7 +42,7 @@
 import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
-import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
@@ -171,7 +171,7 @@
             PipParamsChangedForwarder pipParamsChangedForwarder,
             Optional<SplitScreenController> splitScreenControllerOptional,
             Optional<PipPerfHintController> pipPerfHintControllerOptional,
-            Optional<DesktopRepository> desktopRepositoryOptional,
+            Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@@ -181,7 +181,7 @@
                 pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
                 pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
                 splitScreenControllerOptional, pipPerfHintControllerOptional,
-                desktopRepositoryOptional, rootTaskDisplayAreaOrganizer, displayController,
+                desktopUserRepositoriesOptional, rootTaskDisplayAreaOrganizer, displayController,
                 pipUiEventLogger, shellTaskOrganizer, mainExecutor);
     }
 
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 3508ece..3a99619 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
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.os.Handler;
 
+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.DisplayInsetsController;
@@ -38,6 +39,7 @@
 import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.pip2.phone.PhonePipMenuController;
 import com.android.wm.shell.pip2.phone.PipController;
 import com.android.wm.shell.pip2.phone.PipMotionHelper;
@@ -128,8 +130,11 @@
     static PipScheduler providePipScheduler(Context context,
             PipBoundsState pipBoundsState,
             @ShellMainThread ShellExecutor mainExecutor,
-            PipTransitionState pipTransitionState) {
-        return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState);
+            PipTransitionState pipTransitionState,
+            Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+        return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState,
+                desktopUserRepositoriesOptional, rootTaskDisplayAreaOrganizer);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
index a16c15df..9b5a289 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
@@ -106,13 +106,13 @@
                 // Scale the end bounds of the window down with an anchor in the center
                 inset(
                     (startBounds.width().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt(),
-                    (startBounds.height().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt()
+                    (startBounds.height().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt(),
                 )
                 val offsetY =
                     TypedValue.applyDimension(
                             TypedValue.COMPLEX_UNIT_DIP,
                             CLOSE_ANIM_OFFSET_Y,
-                            context.resources.displayMetrics
+                            context.resources.displayMetrics,
                         )
                         .toInt()
                 offset(/* dx= */ 0, offsetY)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
index 606aa6c..6104e79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
@@ -39,7 +39,7 @@
     private val shellTaskOrganizer: ShellTaskOrganizer,
     private val taskStackListener: TaskStackListenerImpl,
     private val resizeHandler: ToggleResizeDesktopTaskTransitionHandler,
-    private val taskRepository: DesktopRepository,
+    private val desktopUserRepositories: DesktopUserRepositories,
 ) {
 
     init {
@@ -49,15 +49,17 @@
     }
 
     private fun onInit() {
-        taskStackListener.addListener(object : TaskStackListenerCallback {
-            override fun onActivityRequestedOrientationChanged(
-                taskId: Int,
-                @ScreenOrientation requestedOrientation: Int
-            ) {
-                // Handle requested screen orientation changes at runtime.
-                handleActivityOrientationChange(taskId, requestedOrientation)
+        taskStackListener.addListener(
+            object : TaskStackListenerCallback {
+                override fun onActivityRequestedOrientationChanged(
+                    taskId: Int,
+                    @ScreenOrientation requestedOrientation: Int,
+                ) {
+                    // Handle requested screen orientation changes at runtime.
+                    handleActivityOrientationChange(taskId, requestedOrientation)
+                }
             }
-        })
+        )
     }
 
     /**
@@ -77,11 +79,13 @@
 
     private fun handleActivityOrientationChange(
         taskId: Int,
-        @ScreenOrientation requestedOrientation: Int
+        @ScreenOrientation requestedOrientation: Int,
     ) {
         if (!Flags.respectOrientationChangeForUnresizeable()) return
         val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return
-        if (!isDesktopModeShowing(task.displayId) || !task.isFreeform || task.isResizeable) return
+        val taskRepository = desktopUserRepositories.current
+        val isDesktopModeShowing = taskRepository.getVisibleTaskCount(task.displayId) > 0
+        if (!isDesktopModeShowing || !task.isFreeform || task.isResizeable) return
 
         val taskBounds = task.configuration.windowConfiguration.bounds
         val taskHeight = taskBounds.height()
@@ -91,10 +95,12 @@
             if (taskWidth > taskHeight) ORIENTATION_LANDSCAPE else ORIENTATION_PORTRAIT
 
         // Non-resizeable activity requested opposite orientation.
-        if (orientation == ORIENTATION_PORTRAIT
-                && ActivityInfo.isFixedOrientationLandscape(requestedOrientation)
-            || orientation == ORIENTATION_LANDSCAPE
-                && ActivityInfo.isFixedOrientationPortrait(requestedOrientation)) {
+        if (
+            orientation == ORIENTATION_PORTRAIT &&
+                ActivityInfo.isFixedOrientationLandscape(requestedOrientation) ||
+                orientation == ORIENTATION_LANDSCAPE &&
+                    ActivityInfo.isFixedOrientationPortrait(requestedOrientation)
+        ) {
 
             val finalSize = Size(taskHeight, taskWidth)
             // Use the center x as the resizing anchor point.
@@ -106,7 +112,4 @@
             resizeHandler.startTransition(wct)
         }
     }
-
-    private fun isDesktopModeShowing(displayId: Int): Boolean =
-        taskRepository.getVisibleTaskCount(displayId) > 0
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt
index 83b0f84..56c50ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt
@@ -71,8 +71,7 @@
         animations +=
             info.changes
                 .filter {
-                    it.mode == info.type &&
-                            it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
+                    it.mode == info.type && it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
                 }
                 .mapNotNull { createMinimizeAnimation(it, finishTransaction, onAnimFinish) }
         if (animations.isEmpty()) return false
@@ -83,7 +82,7 @@
     private fun createMinimizeAnimation(
         change: TransitionInfo.Change,
         finishTransaction: Transaction,
-        onAnimFinish: (Animator) -> Unit
+        onAnimFinish: (Animator) -> Unit,
     ): Animator? {
         val t = Transaction()
         val sc = change.leash
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
index ba383fa..43e8d2a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
@@ -12,7 +12,7 @@
  * WITHOUT 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
 
@@ -64,16 +64,22 @@
 
     private fun refreshDisplayWindowingMode() {
         // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
-        val isExtendedDisplayEnabled = 0 != Settings.Global.getInt(
-            context.contentResolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0
-        )
+        val isExtendedDisplayEnabled =
+            0 !=
+                Settings.Global.getInt(
+                    context.contentResolver,
+                    DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
+                    0,
+                )
         if (!isExtendedDisplayEnabled) {
             // No action needed in mirror or projected mode.
             return
         }
 
-        val hasNonDefaultDisplay = rootTaskDisplayAreaOrganizer.getDisplayIds()
-            .any { displayId -> displayId != DEFAULT_DISPLAY }
+        val hasNonDefaultDisplay =
+            rootTaskDisplayAreaOrganizer.getDisplayIds().any { displayId ->
+                displayId != DEFAULT_DISPLAY
+            }
         val targetDisplayWindowingMode =
             if (hasNonDefaultDisplay) {
                 WINDOWING_MODE_FREEFORM
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
index 4723eb2..8e2a412 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
@@ -43,14 +43,14 @@
 import java.io.PrintWriter
 
 /**
- * A controller to move tasks in/out of desktop's full immersive state where the task
- * remains freeform while being able to take fullscreen bounds and have its App Header visibility
- * be transient below the status bar like in fullscreen immersive mode.
+ * A controller to move tasks in/out of desktop's full immersive state where the task remains
+ * freeform while being able to take fullscreen bounds and have its App Header visibility be
+ * transient below the status bar like in fullscreen immersive mode.
  */
 class DesktopImmersiveController(
     shellInit: ShellInit,
     private val transitions: Transitions,
-    private val desktopRepository: DesktopRepository,
+    private val desktopUserRepositories: DesktopUserRepositories,
     private val displayController: DisplayController,
     private val shellTaskOrganizer: ShellTaskOrganizer,
     private val shellCommandHandler: ShellCommandHandler,
@@ -60,25 +60,23 @@
     constructor(
         shellInit: ShellInit,
         transitions: Transitions,
-        desktopRepository: DesktopRepository,
+        desktopUserRepositories: DesktopUserRepositories,
         displayController: DisplayController,
         shellTaskOrganizer: ShellTaskOrganizer,
         shellCommandHandler: ShellCommandHandler,
     ) : this(
         shellInit,
         transitions,
-        desktopRepository,
+        desktopUserRepositories,
         displayController,
         shellTaskOrganizer,
         shellCommandHandler,
-        { SurfaceControl.Transaction() }
+        { SurfaceControl.Transaction() },
     )
 
-    @VisibleForTesting
-    var state: TransitionState? = null
+    @VisibleForTesting var state: TransitionState? = null
 
-    @VisibleForTesting
-    val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>()
+    @VisibleForTesting val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>()
 
     /** Whether there is an immersive transition that hasn't completed yet. */
     private val inProgress: Boolean
@@ -99,47 +97,50 @@
 
     /** Starts a transition to enter full immersive state inside the desktop. */
     fun moveTaskToImmersive(taskInfo: RunningTaskInfo) {
+        check(taskInfo.isFreeform) { "Task must already be in freeform" }
         if (inProgress) {
             logV(
                 "Cannot start entry because transition(s) already in progress: %s",
-                getRunningTransitions()
+                getRunningTransitions(),
             )
             return
         }
-        val wct = WindowContainerTransaction().apply {
-            setBounds(taskInfo.token, Rect())
-        }
+        val wct = WindowContainerTransaction().apply { setBounds(taskInfo.token, Rect()) }
         logV("Moving task ${taskInfo.taskId} into immersive mode")
         val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this)
-        state = TransitionState(
-            transition = transition,
-            displayId = taskInfo.displayId,
-            taskId = taskInfo.taskId,
-            direction = Direction.ENTER
-        )
+        state =
+            TransitionState(
+                transition = transition,
+                displayId = taskInfo.displayId,
+                taskId = taskInfo.taskId,
+                direction = Direction.ENTER,
+            )
     }
 
     /** Starts a transition to move an immersive task out of immersive. */
     fun moveTaskToNonImmersive(taskInfo: RunningTaskInfo, reason: ExitReason) {
+        check(taskInfo.isFreeform) { "Task must already be in freeform" }
         if (inProgress) {
             logV(
                 "Cannot start exit because transition(s) already in progress: %s",
-                getRunningTransitions()
+                getRunningTransitions(),
             )
             return
         }
 
-        val wct = WindowContainerTransaction().apply {
-            setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
-        }
+        val wct =
+            WindowContainerTransaction().apply {
+                setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
+            }
         logV("Moving task %d out of immersive mode, reason: %s", taskInfo.taskId, reason)
         val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this)
-        state = TransitionState(
-            transition = transition,
-            displayId = taskInfo.displayId,
-            taskId = taskInfo.taskId,
-            direction = Direction.EXIT
-        )
+        state =
+            TransitionState(
+                transition = transition,
+                displayId = taskInfo.displayId,
+                taskId = taskInfo.taskId,
+                direction = Direction.EXIT,
+            )
     }
 
     /**
@@ -175,23 +176,26 @@
         reason: ExitReason,
     ): ExitResult {
         if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit
-        val immersiveTask = desktopRepository.getTaskInFullImmersiveState(displayId)
-            ?: return ExitResult.NoExit
+        val immersiveTask =
+            desktopUserRepositories.current.getTaskInFullImmersiveState(displayId)
+                ?: return ExitResult.NoExit
         if (immersiveTask == excludeTaskId) {
             return ExitResult.NoExit
         }
-        val taskInfo = shellTaskOrganizer.getRunningTaskInfo(immersiveTask)
-            ?: return ExitResult.NoExit
+        val taskInfo =
+            shellTaskOrganizer.getRunningTaskInfo(immersiveTask) ?: return ExitResult.NoExit
         logV(
             "Appending immersive exit for task: %d in display: %d for reason: %s",
-            immersiveTask, displayId, reason
+            immersiveTask,
+            displayId,
+            reason,
         )
         wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
         return ExitResult.Exit(
             exitingTask = immersiveTask,
             runOnTransitionStart = { transition ->
                 addPendingImmersiveExit(immersiveTask, displayId, transition)
-            }
+            },
         )
     }
 
@@ -208,7 +212,7 @@
         reason: ExitReason,
     ): ExitResult {
         if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit
-        if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) {
+        if (desktopUserRepositories.current.isTaskInFullImmersiveState(taskInfo.taskId)) {
             // A full immersive task is being minimized, make sure the immersive state is broken
             // (i.e. resize back to max bounds).
             wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
@@ -219,20 +223,16 @@
                     addPendingImmersiveExit(
                         taskId = taskInfo.taskId,
                         displayId = taskInfo.displayId,
-                        transition = transition
+                        transition = transition,
                     )
-                }
+                },
             )
         }
         return ExitResult.NoExit
     }
 
-
     /** Whether the [change] in the [transition] is a known immersive change. */
-    fun isImmersiveChange(
-        transition: IBinder,
-        change: TransitionInfo.Change,
-    ): Boolean {
+    fun isImmersiveChange(transition: IBinder, change: TransitionInfo.Change): Boolean {
         return pendingExternalExitTransitions.any {
             it.transition == transition && it.taskId == change.taskInfo?.taskId
         }
@@ -240,11 +240,7 @@
 
     private fun addPendingImmersiveExit(taskId: Int, displayId: Int, transition: IBinder) {
         pendingExternalExitTransitions.add(
-            ExternalPendingExit(
-                taskId = taskId,
-                displayId = displayId,
-                transition = transition
-            )
+            ExternalPendingExit(taskId = taskId, displayId = displayId, transition = transition)
         )
     }
 
@@ -253,7 +249,7 @@
         info: TransitionInfo,
         startTransaction: SurfaceControl.Transaction,
         finishTransaction: SurfaceControl.Transaction,
-        finishCallback: Transitions.TransitionFinishCallback
+        finishCallback: Transitions.TransitionFinishCallback,
     ): Boolean {
         val state = requireState()
         check(state.transition == transition) {
@@ -281,10 +277,11 @@
         finishCallback: Transitions.TransitionFinishCallback,
     ) {
         logD("animateResize for task#%d", targetTaskId)
-        val change = info.changes.firstOrNull { c ->
-            val taskInfo = c.taskInfo
-            return@firstOrNull taskInfo != null && taskInfo.taskId == targetTaskId
-        }
+        val change =
+            info.changes.firstOrNull { c ->
+                val taskInfo = c.taskInfo
+                return@firstOrNull taskInfo != null && taskInfo.taskId == targetTaskId
+            }
         if (change == null) {
             logD("Did not find change for task#%d to animate", targetTaskId)
             startTransaction.apply()
@@ -295,9 +292,9 @@
     }
 
     /**
-     *  Animate an immersive change.
+     * Animate an immersive change.
      *
-     *  As of now, both enter and exit transitions have the same animation, a veiled resize.
+     * As of now, both enter and exit transitions have the same animation, a veiled resize.
      */
     fun animateResizeChange(
         change: TransitionInfo.Change,
@@ -315,8 +312,7 @@
             .setPosition(leash, startBounds.left.toFloat(), startBounds.top.toFloat())
             .setWindowCrop(leash, startBounds.width(), startBounds.height())
             .show(leash)
-        onTaskResizeAnimationListener
-            ?.onAnimationStart(taskId, startTransaction, startBounds)
+        onTaskResizeAnimationListener?.onAnimationStart(taskId, startTransaction, startBounds)
             ?: startTransaction.apply()
         val updateTransaction = transactionSupplier()
         ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds).apply {
@@ -338,8 +334,7 @@
                     .setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
                     .setWindowCrop(leash, rect.width(), rect.height())
                     .apply()
-                onTaskResizeAnimationListener
-                    ?.onBoundsChange(taskId, updateTransaction, rect)
+                onTaskResizeAnimationListener?.onBoundsChange(taskId, updateTransaction, rect)
                     ?: updateTransaction.apply()
             }
             start()
@@ -348,13 +343,13 @@
 
     override fun handleRequest(
         transition: IBinder,
-        request: TransitionRequestInfo
+        request: TransitionRequestInfo,
     ): WindowContainerTransaction? = null
 
     override fun onTransitionConsumed(
         transition: IBinder,
         aborted: Boolean,
-        finishTransaction: SurfaceControl.Transaction?
+        finishTransaction: SurfaceControl.Transaction?,
     ) {
         val state = this.state ?: return
         if (transition == state.transition && aborted) {
@@ -375,9 +370,12 @@
         startTransaction: SurfaceControl.Transaction,
         finishTransaction: SurfaceControl.Transaction,
     ) {
+        val desktopRepository: DesktopRepository = desktopUserRepositories.current
         // Check if this is a pending external exit transition.
-        val pendingExit = pendingExternalExitTransitions
-            .firstOrNull { pendingExit -> pendingExit.transition == transition }
+        val pendingExit =
+            pendingExternalExitTransitions.firstOrNull { pendingExit ->
+                pendingExit.transition == transition
+            }
         if (pendingExit != null) {
             if (info.hasTaskChange(taskId = pendingExit.taskId)) {
                 if (desktopRepository.isTaskInFullImmersiveState(pendingExit.taskId)) {
@@ -385,7 +383,7 @@
                     desktopRepository.setTaskInFullImmersiveState(
                         displayId = pendingExit.displayId,
                         taskId = pendingExit.taskId,
-                        immersive = false
+                        immersive = false,
                     )
                     if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
                         desktopRepository.removeBoundsBeforeFullImmersive(pendingExit.taskId)
@@ -398,24 +396,25 @@
         // Check if this is a direct immersive enter/exit transition.
         if (transition == state?.transition) {
             val state = requireState()
-            val immersiveChange = info.changes.firstOrNull { c ->
-                c.taskInfo?.taskId == state.taskId
-            }
+            val immersiveChange =
+                info.changes.firstOrNull { c -> c.taskInfo?.taskId == state.taskId }
             if (immersiveChange == null) {
                 logV(
                     "Direct move for task#%d in %s direction missing immersive change.",
-                    state.taskId, state.direction
+                    state.taskId,
+                    state.direction,
                 )
                 return
             }
             val startBounds = immersiveChange.startAbsBounds
             logV("Direct move for task#%d in %s direction verified", state.taskId, state.direction)
+
             when (state.direction) {
                 Direction.ENTER -> {
                     desktopRepository.setTaskInFullImmersiveState(
                         displayId = state.displayId,
                         taskId = state.taskId,
-                        immersive = true
+                        immersive = true,
                     )
                     if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
                         desktopRepository.saveBoundsBeforeFullImmersive(state.taskId, startBounds)
@@ -425,7 +424,7 @@
                     desktopRepository.setTaskInFullImmersiveState(
                         displayId = state.displayId,
                         taskId = state.taskId,
-                        immersive = false
+                        immersive = false,
                     )
                     if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
                         desktopRepository.removeBoundsBeforeFullImmersive(state.taskId)
@@ -445,31 +444,34 @@
                 desktopRepository.setTaskInFullImmersiveState(
                     displayId = c.taskInfo!!.displayId,
                     taskId = c.taskInfo!!.taskId,
-                    immersive = false
+                    immersive = false,
                 )
             }
     }
 
     override fun onTransitionMerged(merged: IBinder, playing: IBinder) {
-        val pendingExit = pendingExternalExitTransitions
-            .firstOrNull { pendingExit -> pendingExit.transition == merged }
+        val pendingExit =
+            pendingExternalExitTransitions.firstOrNull { pendingExit ->
+                pendingExit.transition == merged
+            }
         if (pendingExit != null) {
             logV(
                 "Pending exit transition %s for task#%s merged into %s",
-                merged, pendingExit.taskId, playing
+                merged,
+                pendingExit.taskId,
+                playing,
             )
             pendingExit.transition = playing
         }
     }
 
     override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
-        val pendingExit = pendingExternalExitTransitions
-            .firstOrNull { pendingExit -> pendingExit.transition == transition }
+        val pendingExit =
+            pendingExternalExitTransitions.firstOrNull { pendingExit ->
+                pendingExit.transition == transition
+            }
         if (pendingExit != null) {
-            logV(
-                "Pending exit transition %s for task#%s finished",
-                transition, pendingExit
-            )
+            logV("Pending exit transition %s for task#%s finished", transition, pendingExit)
             pendingExternalExitTransitions.remove(pendingExit)
         }
     }
@@ -479,10 +481,11 @@
     }
 
     private fun getExitDestinationBounds(taskInfo: RunningTaskInfo): Rect {
-        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId)
-            ?: error("Expected non-null display layout for displayId: ${taskInfo.displayId}")
+        val displayLayout =
+            displayController.getDisplayLayout(taskInfo.displayId)
+                ?: error("Expected non-null display layout for displayId: ${taskInfo.displayId}")
         return if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
-            desktopRepository.removeBoundsBeforeFullImmersive(taskInfo.taskId)
+            desktopUserRepositories.current.removeBoundsBeforeFullImmersive(taskInfo.taskId)
                 ?: if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) {
                     calculateInitialBounds(displayLayout, taskInfo)
                 } else {
@@ -498,12 +501,8 @@
 
     private fun getRunningTransitions(): List<IBinder> {
         val running = mutableListOf<IBinder>()
-        state?.let {
-            running.add(it.transition)
-        }
-        pendingExternalExitTransitions.forEach {
-            running.add(it.transition)
-        }
+        state?.let { running.add(it.transition) }
+        pendingExternalExitTransitions.forEach { running.add(it.transition) }
         return running
     }
 
@@ -523,28 +522,23 @@
         val transition: IBinder,
         val displayId: Int,
         val taskId: Int,
-        val direction: Direction
+        val direction: Direction,
     )
 
     /**
      * Tracks state of a transition involving an immersive exit that is external to this class' own
-     * transitions. This usually means transitions that exit immersive mode as a side-effect and
-     * not the primary action (for example, minimizing the immersive task or launching a new task
-     * on top of the immersive task).
+     * transitions. This usually means transitions that exit immersive mode as a side-effect and not
+     * the primary action (for example, minimizing the immersive task or launching a new task on top
+     * of the immersive task).
      */
-    data class ExternalPendingExit(
-        val taskId: Int,
-        val displayId: Int,
-        var transition: IBinder,
-    )
+    data class ExternalPendingExit(val taskId: Int, val displayId: Int, var transition: IBinder)
 
     /** The result of an external exit request. */
     sealed class ExitResult {
         /** An immersive task exit (meaning, resize) was appended to the request. */
-        data class Exit(
-            val exitingTask: Int,
-            val runOnTransitionStart: ((IBinder) -> Unit)
-        ) : ExitResult()
+        data class Exit(val exitingTask: Int, val runOnTransitionStart: ((IBinder) -> Unit)) :
+            ExitResult()
+
         /** There was no exit appended to the request. */
         data object NoExit : ExitResult()
 
@@ -554,7 +548,8 @@
 
     @VisibleForTesting
     enum class Direction {
-        ENTER, EXIT
+        ENTER,
+        EXIT,
     }
 
     /** The reason for moving the task out of desktop immersive mode. */
@@ -577,7 +572,6 @@
     companion object {
         private const val TAG = "DesktopImmersive"
 
-        @VisibleForTesting
-        const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L
+        @VisibleForTesting const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index 82c2ebc..7764688 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -23,6 +23,7 @@
 import android.os.IBinder
 import android.view.SurfaceControl
 import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CLOSE
 import android.view.WindowManager.TRANSIT_OPEN
 import android.window.DesktopModeFlags
 import android.window.TransitionInfo
@@ -49,7 +50,7 @@
 class DesktopMixedTransitionHandler(
     private val context: Context,
     private val transitions: Transitions,
-    private val desktopRepository: DesktopRepository,
+    private val desktopUserRepositories: DesktopUserRepositories,
     private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler,
     private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler,
     private val desktopImmersiveController: DesktopImmersiveController,
@@ -61,11 +62,10 @@
 ) : MixedTransitionHandler, FreeformTaskTransitionStarter {
 
     init {
-        shellInit.addInitCallback ({ transitions.addHandler(this) }, this)
+        shellInit.addInitCallback({ transitions.addHandler(this) }, this)
     }
 
-    @VisibleForTesting
-    val pendingMixedTransitions = mutableListOf<PendingMixedTransition>()
+    @VisibleForTesting val pendingMixedTransitions = mutableListOf<PendingMixedTransition>()
 
     /** Delegates starting transition to [FreeformTaskTransitionHandler]. */
     override fun startWindowingModeTransition(
@@ -79,11 +79,15 @@
 
     /** Starts close transition and handles or delegates desktop task close animation. */
     override fun startRemoveTransition(wct: WindowContainerTransaction?): IBinder {
-        if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue) {
+        if (
+            !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue &&
+                !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue
+        ) {
             return freeformTaskTransitionHandler.startRemoveTransition(wct)
         }
         requireNotNull(wct)
-        return transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this)
+        return transitions
+            .startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this)
             .also { transition ->
                 pendingMixedTransitions.add(PendingMixedTransition.Close(transition))
             }
@@ -100,8 +104,11 @@
         minimizingTaskId: Int? = null,
         exitingImmersiveTask: Int? = null,
     ): IBinder {
-        if (!Flags.enableFullyImmersiveInDesktop() &&
-            !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+        if (
+            !Flags.enableFullyImmersiveInDesktop() &&
+                !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue &&
+                !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
+        ) {
             return transitions.startTransition(transitionType, wct, /* handler= */ null)
         }
         if (exitingImmersiveTask == null) {
@@ -109,18 +116,21 @@
         } else {
             logV(
                 "Starting mixed launch transition for task#%d with immersive exit of task#%d",
-                taskId, exitingImmersiveTask
+                taskId,
+                exitingImmersiveTask,
             )
         }
-        return transitions.startTransition(transitionType, wct, /* handler= */ this)
-            .also { transition ->
-                pendingMixedTransitions.add(PendingMixedTransition.Launch(
+        return transitions.startTransition(transitionType, wct, /* handler= */ this).also {
+            transition ->
+            pendingMixedTransitions.add(
+                PendingMixedTransition.Launch(
                     transition = transition,
                     launchingTask = taskId,
                     minimizingTask = minimizingTaskId,
                     exitingImmersiveTask = exitingImmersiveTask,
-                ))
-            }
+                )
+            )
+        }
     }
 
     /** Notifies this handler that there is a pending transition for it to handle. */
@@ -141,36 +151,38 @@
         finishTransaction: SurfaceControl.Transaction,
         finishCallback: TransitionFinishCallback,
     ): Boolean {
-        val pending = pendingMixedTransitions.find { pending -> pending.transition == transition }
-            ?: return false.also {
-                logV("No pending desktop transition")
-            }
+        val pending =
+            pendingMixedTransitions.find { pending -> pending.transition == transition }
+                ?: return false.also { logV("No pending desktop transition") }
         pendingMixedTransitions.remove(pending)
         logV("Animating pending mixed transition: %s", pending)
         return when (pending) {
-            is PendingMixedTransition.Close -> animateCloseTransition(
-                transition,
-                info,
-                startTransaction,
-                finishTransaction,
-                finishCallback
-            )
-            is PendingMixedTransition.Launch -> animateLaunchTransition(
-                pending,
-                transition,
-                info,
-                startTransaction,
-                finishTransaction,
-                finishCallback
-            )
-            is PendingMixedTransition.Minimize -> animateMinimizeTransition(
-                pending,
-                transition,
-                info,
-                startTransaction,
-                finishTransaction,
-                finishCallback
-            )
+            is PendingMixedTransition.Close ->
+                animateCloseTransition(
+                    transition,
+                    info,
+                    startTransaction,
+                    finishTransaction,
+                    finishCallback,
+                )
+            is PendingMixedTransition.Launch ->
+                animateLaunchTransition(
+                    pending,
+                    transition,
+                    info,
+                    startTransaction,
+                    finishTransaction,
+                    finishCallback,
+                )
+            is PendingMixedTransition.Minimize ->
+                animateMinimizeTransition(
+                    pending,
+                    transition,
+                    info,
+                    startTransaction,
+                    finishTransaction,
+                    finishCallback,
+                )
         }
     }
 
@@ -186,8 +198,9 @@
             logW("Should have closing desktop task")
             return false
         }
-        if (isLastDesktopTask(closeChange)) {
-            // Dispatch close desktop task animation to the default transition handlers.
+        if (isWallpaperActivityClosing(info)) {
+            // If the wallpaper activity is closing then the desktop is closing, animate the closing
+            // desktop by dispatching to other transition handlers.
             return dispatchCloseLastDesktopTaskAnimation(
                 transition,
                 info,
@@ -216,12 +229,10 @@
         finishCallback: TransitionFinishCallback,
     ): Boolean {
         // Check if there's also an immersive change during this launch.
-        val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask ->
-            findTaskChange(info, exitingTask)
-        }
-        val minimizeChange = pending.minimizingTask?.let { minimizingTask ->
-            findTaskChange(info, minimizingTask)
-        }
+        val immersiveExitChange =
+            pending.exitingImmersiveTask?.let { exitingTask -> findTaskChange(info, exitingTask) }
+        val minimizeChange =
+            pending.minimizingTask?.let { minimizingTask -> findTaskChange(info, minimizingTask) }
         val launchChange = findDesktopTaskLaunchChange(info, pending.launchingTask)
         if (launchChange == null) {
             check(minimizeChange == null)
@@ -241,10 +252,14 @@
 
         logV(
             "Animating mixed launch transition task#%d, minimizingTask#%s immersiveExitTask#%s",
-            launchChange.taskInfo!!.taskId, minimizeChange?.taskInfo?.taskId,
-            immersiveExitChange?.taskInfo?.taskId
+            launchChange.taskInfo!!.taskId,
+            minimizeChange?.taskInfo?.taskId,
+            immersiveExitChange?.taskInfo?.taskId,
         )
-        if (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+        if (
+            DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue ||
+                DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
+        ) {
             // Only apply minimize change reparenting here if we implement the new app launch
             // transitions, otherwise this reparenting is handled in the default handler.
             minimizeChange?.let {
@@ -259,7 +274,7 @@
                 immersiveExitChange,
                 startTransaction,
                 finishTransaction,
-                finishCb
+                finishCb,
             )
             // Let the leftover/default handler animate the remaining changes.
             return dispatchToLeftoverHandler(
@@ -267,7 +282,7 @@
                 info,
                 startTransaction,
                 finishTransaction,
-                finishCb
+                finishCb,
             )
         }
         // There's nothing to animate separately, so let the left over handler animate
@@ -278,7 +293,7 @@
             info,
             startTransaction,
             finishTransaction,
-            finishCb
+            finishCb,
         )
     }
 
@@ -304,7 +319,7 @@
                 info,
                 startTransaction,
                 finishTransaction,
-                finishCallback
+                finishCallback,
             )
         }
 
@@ -321,7 +336,7 @@
     override fun onTransitionConsumed(
         transition: IBinder,
         aborted: Boolean,
-        finishTransaction: SurfaceControl.Transaction?
+        finishTransaction: SurfaceControl.Transaction?,
     ) {
         pendingMixedTransitions.removeAll { pending -> pending.transition == transition }
         super.onTransitionConsumed(transition, aborted, finishTransaction)
@@ -356,7 +371,7 @@
             doOnFinishCallback = {
                 // Finish the jank trace when closing the last window in desktop mode.
                 interactionJankMonitor.end(CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE)
-            }
+            },
         )
     }
 
@@ -379,7 +394,10 @@
         require(taskInfo.isFreeform)
         logV("Reparenting minimizing task#%d", taskInfo.taskId)
         rootTaskDisplayAreaOrganizer.reparentToDisplayArea(
-            taskInfo.displayId, minimizeChange.leash, startTransaction)
+            taskInfo.displayId,
+            minimizeChange.leash,
+            startTransaction,
+        )
     }
 
     private fun dispatchToLeftoverHandler(
@@ -399,14 +417,16 @@
                 doOnFinishCallback?.invoke()
                 finishCallback.onTransitionFinished(wct)
             },
-            /* skip= */ this
+            /* skip= */ this,
         ) != null
     }
 
-    private fun isLastDesktopTask(change: TransitionInfo.Change): Boolean =
-        change.taskInfo?.let {
-            desktopRepository.getExpandedTaskCount(it.displayId) == 1
-        } ?: false
+    private fun isWallpaperActivityClosing(info: TransitionInfo) =
+        info.changes.any { change ->
+            change.mode == TRANSIT_CLOSE &&
+                change.taskInfo != null &&
+                DesktopWallpaperActivity.isWallpaperTask(change.taskInfo!!)
+        }
 
     private fun findCloseDesktopTaskChange(info: TransitionInfo): TransitionInfo.Change? {
         if (info.type != WindowManager.TRANSIT_CLOSE) return null
@@ -423,7 +443,7 @@
 
     private fun findDesktopTaskLaunchChange(
         info: TransitionInfo,
-        launchTaskId: Int?
+        launchTaskId: Int?,
     ): TransitionInfo.Change? {
         return if (launchTaskId != null) {
             // Launching a known task (probably from background or moving to front), so
@@ -432,8 +452,9 @@
         } else {
             // Launching a new task, so the first opening freeform task.
             info.changes.firstOrNull { change ->
-                change.mode == TRANSIT_OPEN
-                        && change.taskInfo != null && change.taskInfo!!.isFreeform
+                change.mode == TRANSIT_OPEN &&
+                    change.taskInfo != null &&
+                    change.taskInfo!!.isFreeform
             }
         }
     }
@@ -451,9 +472,7 @@
         abstract val transition: IBinder
 
         /** A task is closing. */
-        data class Close(
-            override val transition: IBinder,
-        ) : PendingMixedTransition()
+        data class Close(override val transition: IBinder) : PendingMixedTransition()
 
         /** A task is opening or moving to front. */
         data class Launch(
@@ -463,8 +482,10 @@
             val exitingImmersiveTask: Int?,
         ) : PendingMixedTransition()
 
-        /** A task is minimizing. This should be used for task going to back and some closing cases
-         * with back navigation. */
+        /**
+         * A task is minimizing. This should be used for task going to back and some closing cases
+         * with back navigation.
+         */
         data class Minimize(
             override val transition: IBinder,
             val minimizingTask: Int,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt
index a7a4a10..ca02c72 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt
@@ -27,16 +27,14 @@
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
 
-/**
- * Transition handler for drag-and-drop (i.e., tab tear) transitions that occur in desktop mode.
- */
+/** Transition handler for drag-and-drop (i.e., tab tear) transitions that occur in desktop mode. */
 class DesktopModeDragAndDropTransitionHandler(private val transitions: Transitions) :
     Transitions.TransitionHandler {
     private val pendingTransitionTokens: MutableList<IBinder> = mutableListOf()
 
     /**
-     * Begin a transition when a [android.app.PendingIntent] is dropped without a window to
-     * accept it.
+     * Begin a transition when a [android.app.PendingIntent] is dropped without a window to accept
+     * it.
      */
     fun handleDropEvent(wct: WindowContainerTransaction): IBinder {
         val token = transitions.startTransition(TRANSIT_OPEN, wct, this)
@@ -49,29 +47,32 @@
         info: TransitionInfo,
         startTransaction: SurfaceControl.Transaction,
         finishTransaction: SurfaceControl.Transaction,
-        finishCallback: TransitionFinishCallback
+        finishCallback: TransitionFinishCallback,
     ): Boolean {
         if (!pendingTransitionTokens.contains(transition)) return false
         val change = findRelevantChange(info)
         val leash = change.leash
         val endBounds = change.endAbsBounds
-        startTransaction.hide(leash)
+        startTransaction
+            .hide(leash)
             .setWindowCrop(leash, endBounds.width(), endBounds.height())
             .apply()
         val animator = ValueAnimator()
         animator.setFloatValues(0f, 1f)
         animator.setDuration(FADE_IN_ANIMATION_DURATION)
         val t = SurfaceControl.Transaction()
-        animator.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationStart(animation: Animator) {
-                t.show(leash)
-                t.apply()
-            }
+        animator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationStart(animation: Animator) {
+                    t.show(leash)
+                    t.apply()
+                }
 
-            override fun onAnimationEnd(animation: Animator) {
-                finishCallback.onTransitionFinished(null)
+                override fun onAnimationEnd(animation: Animator) {
+                    finishCallback.onTransitionFinished(null)
+                }
             }
-        })
+        )
         animator.addUpdateListener { animation: ValueAnimator ->
             t.setAlpha(leash, animation.animatedFraction)
             t.apply()
@@ -83,9 +84,7 @@
 
     private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change {
         val matchingChanges =
-            info.changes.filter { c ->
-                isValidTaskChange(c) && c.mode == TRANSIT_OPEN
-            }
+            info.changes.filter { c -> isValidTaskChange(c) && c.mode == TRANSIT_OPEN }
         if (matchingChanges.size != 1) {
             throw IllegalStateException(
                 "Expected 1 relevant change but found: ${matchingChanges.size}"
@@ -100,7 +99,7 @@
 
     override fun handleRequest(
         transition: IBinder,
-        request: TransitionRequestInfo
+        request: TransitionRequestInfo,
     ): WindowContainerTransaction? {
         return null
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index b4eac7a..ff6fb59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -35,29 +35,25 @@
 import java.util.Random
 import java.util.concurrent.atomic.AtomicInteger
 
-
 /** Event logger for logging desktop mode session events */
 class DesktopModeEventLogger {
     private val random: Random = SecureRandom()
 
     /** The session id for the current desktop mode session */
-    @VisibleForTesting
-    val currentSessionId: AtomicInteger = AtomicInteger(NO_SESSION_ID)
+    @VisibleForTesting val currentSessionId: AtomicInteger = AtomicInteger(NO_SESSION_ID)
 
     private fun generateSessionId() = 1 + random.nextInt(1 shl 20)
 
-    /**
-     * Logs enter into desktop mode with [enterReason]
-     */
+    /** Logs enter into desktop mode with [enterReason] */
     fun logSessionEnter(enterReason: EnterReason) {
         val sessionId = generateSessionId()
         val previousSessionId = currentSessionId.getAndSet(sessionId)
         if (previousSessionId != NO_SESSION_ID) {
             ProtoLog.w(
                 WM_SHELL_DESKTOP_MODE,
-                "DesktopModeLogger: Existing desktop mode session id: %s found on desktop "
-                    + "mode enter",
-                previousSessionId
+                "DesktopModeLogger: Existing desktop mode session id: %s found on desktop " +
+                    "mode enter",
+                previousSessionId,
             )
         }
 
@@ -65,27 +61,25 @@
             WM_SHELL_DESKTOP_MODE,
             "DesktopModeLogger: Logging session enter, session: %s reason: %s",
             sessionId,
-            enterReason.name
+            enterReason.name,
         )
         FrameworkStatsLog.write(
             DESKTOP_MODE_ATOM_ID,
             /* event */ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER,
             /* enterReason */ enterReason.reason,
             /* exitReason */ 0,
-            /* session_id */ sessionId
+            /* session_id */ sessionId,
         )
         EventLogTags.writeWmShellEnterDesktopMode(enterReason.reason, sessionId)
     }
 
-    /**
-     * Logs exit from desktop mode session with [exitReason]
-     */
+    /** Logs exit from desktop mode session with [exitReason] */
     fun logSessionExit(exitReason: ExitReason) {
         val sessionId = currentSessionId.getAndSet(NO_SESSION_ID)
         if (sessionId == NO_SESSION_ID) {
             ProtoLog.w(
                 WM_SHELL_DESKTOP_MODE,
-                "DesktopModeLogger: No session id found for logging exit from desktop mode"
+                "DesktopModeLogger: No session id found for logging exit from desktop mode",
             )
             return
         }
@@ -94,27 +88,25 @@
             WM_SHELL_DESKTOP_MODE,
             "DesktopModeLogger: Logging session exit, session: %s reason: %s",
             sessionId,
-            exitReason.name
+            exitReason.name,
         )
         FrameworkStatsLog.write(
             DESKTOP_MODE_ATOM_ID,
             /* event */ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT,
             /* enterReason */ 0,
             /* exitReason */ exitReason.reason,
-            /* session_id */ sessionId
+            /* session_id */ sessionId,
         )
         EventLogTags.writeWmShellExitDesktopMode(exitReason.reason, sessionId)
     }
 
-    /**
-     * Logs that a task with [taskUpdate] was added in a desktop mode session
-     */
+    /** Logs that a task with [taskUpdate] was added in a desktop mode session */
     fun logTaskAdded(taskUpdate: TaskUpdate) {
         val sessionId = currentSessionId.get()
         if (sessionId == NO_SESSION_ID) {
             ProtoLog.w(
                 WM_SHELL_DESKTOP_MODE,
-                "DesktopModeLogger: No session id found for logging task added"
+                "DesktopModeLogger: No session id found for logging task added",
             )
             return
         }
@@ -123,23 +115,22 @@
             WM_SHELL_DESKTOP_MODE,
             "DesktopModeLogger: Logging task added, session: %s taskId: %s",
             sessionId,
-            taskUpdate.instanceId
+            taskUpdate.instanceId,
         )
         logTaskUpdate(
             FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED,
-            sessionId, taskUpdate
+            sessionId,
+            taskUpdate,
         )
     }
 
-    /**
-     * Logs that a task with [taskUpdate] was removed from a desktop mode session
-     */
+    /** Logs that a task with [taskUpdate] was removed from a desktop mode session */
     fun logTaskRemoved(taskUpdate: TaskUpdate) {
         val sessionId = currentSessionId.get()
         if (sessionId == NO_SESSION_ID) {
             ProtoLog.w(
                 WM_SHELL_DESKTOP_MODE,
-                "DesktopModeLogger: No session id found for logging task removed"
+                "DesktopModeLogger: No session id found for logging task removed",
             )
             return
         }
@@ -148,23 +139,22 @@
             WM_SHELL_DESKTOP_MODE,
             "DesktopModeLogger: Logging task remove, session: %s taskId: %s",
             sessionId,
-            taskUpdate.instanceId
+            taskUpdate.instanceId,
         )
         logTaskUpdate(
             FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED,
-            sessionId, taskUpdate
+            sessionId,
+            taskUpdate,
         )
     }
 
-    /**
-     * Logs that a task with [taskUpdate] had it's info changed in a desktop mode session
-     */
+    /** Logs that a task with [taskUpdate] had it's info changed in a desktop mode session */
     fun logTaskInfoChanged(taskUpdate: TaskUpdate) {
         val sessionId = currentSessionId.get()
         if (sessionId == NO_SESSION_ID) {
             ProtoLog.w(
                 WM_SHELL_DESKTOP_MODE,
-                "DesktopModeLogger: No session id found for logging task info changed"
+                "DesktopModeLogger: No session id found for logging task info changed",
             )
             return
         }
@@ -173,11 +163,12 @@
             WM_SHELL_DESKTOP_MODE,
             "DesktopModeLogger: Logging task info changed, session: %s taskId: %s",
             sessionId,
-            taskUpdate.instanceId
+            taskUpdate.instanceId,
         )
         logTaskUpdate(
             FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
-            sessionId, taskUpdate
+            sessionId,
+            taskUpdate,
         )
     }
 
@@ -200,30 +191,32 @@
         if (sessionId == NO_SESSION_ID) {
             ProtoLog.w(
                 WM_SHELL_DESKTOP_MODE,
-                "DesktopModeLogger: No session id found for logging start of task resizing"
+                "DesktopModeLogger: No session id found for logging start of task resizing",
             )
             return
         }
 
-        val taskSizeUpdate = createTaskSizeUpdate(
-            resizeTrigger,
-            inputMethod,
-            taskInfo,
-            taskWidth,
-            taskHeight,
-            displayController = displayController,
-            displayLayoutSize = displayLayoutSize,
-        )
+        val taskSizeUpdate =
+            createTaskSizeUpdate(
+                resizeTrigger,
+                inputMethod,
+                taskInfo,
+                taskWidth,
+                taskHeight,
+                displayController = displayController,
+                displayLayoutSize = displayLayoutSize,
+            )
 
         ProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
             "DesktopModeLogger: Logging task resize is starting, session: %s, taskSizeUpdate: %s",
             sessionId,
-            taskSizeUpdate
+            taskSizeUpdate,
         )
         logTaskSizeUpdated(
             FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE,
-            sessionId, taskSizeUpdate
+            sessionId,
+            taskSizeUpdate,
         )
     }
 
@@ -245,31 +238,33 @@
         if (sessionId == NO_SESSION_ID) {
             ProtoLog.w(
                 WM_SHELL_DESKTOP_MODE,
-                "DesktopModeLogger: No session id found for logging end of task resizing"
+                "DesktopModeLogger: No session id found for logging end of task resizing",
             )
             return
         }
 
-        val taskSizeUpdate = createTaskSizeUpdate(
-            resizeTrigger,
-            inputMethod,
-            taskInfo,
-            taskWidth,
-            taskHeight,
-            displayController,
-            displayLayoutSize,
-        )
+        val taskSizeUpdate =
+            createTaskSizeUpdate(
+                resizeTrigger,
+                inputMethod,
+                taskInfo,
+                taskWidth,
+                taskHeight,
+                displayController,
+                displayLayoutSize,
+            )
 
         ProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
             "DesktopModeLogger: Logging task resize is ending, session: %s, taskSizeUpdate: %s",
             sessionId,
-            taskSizeUpdate
+            taskSizeUpdate,
         )
 
         logTaskSizeUpdated(
             FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE,
-            sessionId, taskSizeUpdate
+            sessionId,
+            taskSizeUpdate,
         )
     }
 
@@ -287,12 +282,15 @@
         val height = taskHeight ?: taskBounds.height()
         val width = taskWidth ?: taskBounds.width()
 
-        val displaySize = when {
-            displayLayoutSize != null -> displayLayoutSize.height * displayLayoutSize.width
-            displayController != null -> displayController.getDisplayLayout(taskInfo.displayId)
-                ?.let { it.height() * it.width() }
-            else -> null
-        }
+        val displaySize =
+            when {
+                displayLayoutSize != null -> displayLayoutSize.height * displayLayoutSize.width
+                displayController != null ->
+                    displayController.getDisplayLayout(taskInfo.displayId)?.let {
+                        it.height() * it.width()
+                    }
+                else -> null
+            }
 
         return TaskSizeUpdate(
             resizeTrigger,
@@ -316,8 +314,8 @@
                 taskHeight = 0,
                 taskWidth = 0,
                 taskX = 0,
-                taskY = 0
-            )
+                taskY = 0,
+            ),
         )
     }
 
@@ -343,7 +341,7 @@
             taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON,
             taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON,
             /* visible_task_count */
-            taskUpdate.visibleTaskCount
+            taskUpdate.visibleTaskCount,
         )
         EventLogTags.writeWmShellDesktopModeTaskUpdate(
             /* task_event */
@@ -365,14 +363,14 @@
             taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON,
             taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON,
             /* visible_task_count */
-            taskUpdate.visibleTaskCount
+            taskUpdate.visibleTaskCount,
         )
     }
 
     private fun logTaskSizeUpdated(
         resizingStage: Int,
         sessionId: Int,
-        taskSizeUpdate: TaskSizeUpdate
+        taskSizeUpdate: TaskSizeUpdate,
     ) {
         FrameworkStatsLog.write(
             DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID,
@@ -393,7 +391,7 @@
             /* task_width */
             taskSizeUpdate.taskWidth,
             /* display_area */
-            taskSizeUpdate.displayArea ?: -1
+            taskSizeUpdate.displayArea ?: -1,
         )
     }
 
@@ -410,7 +408,6 @@
          * @property taskY y-coordinate of the top-left corner
          * @property minimizeReason the reason the task was minimized
          * @property unminimizeEvent the reason the task was unminimized
-         *
          */
         data class TaskUpdate(
             val instanceId: Int,
@@ -425,8 +422,7 @@
         )
 
         /**
-         * Describes a task size update (resizing, snapping or maximizing to
-         * stable bounds).
+         * Describes a task size update (resizing, snapping or maximizing to stable bounds).
          *
          * @property resizeTrigger the trigger for task resize
          * @property inputMethod the input method for resizing this task
@@ -446,12 +442,11 @@
             val displayArea: Int?,
         )
 
+        @JvmStatic
         fun getInputMethodFromMotionEvent(e: MotionEvent?): InputMethod {
             if (e == null) return InputMethod.UNKNOWN_INPUT_METHOD
 
-            val toolType = e.getToolType(
-                e.findPointerIndex(e.getPointerId(0))
-            )
+            val toolType = e.getToolType(e.findPointerIndex(e.getPointerId(0)))
             return when {
                 toolType == TOOL_TYPE_STYLUS -> InputMethod.STYLUS
                 toolType == TOOL_TYPE_MOUSE -> InputMethod.MOUSE
@@ -473,8 +468,7 @@
                     .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT
             ),
             MINIMIZE_BUTTON( // TODO(b/356843241): use this enum value
-                FrameworkStatsLog
-                    .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON
+                FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON
             ),
         }
 
@@ -610,20 +604,16 @@
          */
         enum class InputMethod(val method: Int) {
             UNKNOWN_INPUT_METHOD(
-                FrameworkStatsLog
-                    .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD
+                FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD
             ),
             TOUCH(
-                FrameworkStatsLog
-                    .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCH_INPUT_METHOD
+                FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCH_INPUT_METHOD
             ),
             STYLUS(
-                FrameworkStatsLog
-                    .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__STYLUS_INPUT_METHOD
+                FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__STYLUS_INPUT_METHOD
             ),
             MOUSE(
-                FrameworkStatsLog
-                    .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__MOUSE_INPUT_METHOD
+                FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__MOUSE_INPUT_METHOD
             ),
             TOUCHPAD(
                 FrameworkStatsLog
@@ -642,4 +632,4 @@
             FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED
         @VisibleForTesting const val NO_SESSION_ID = 0
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
index 114563a..1ddb834 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
@@ -16,28 +16,28 @@
 
 package com.android.wm.shell.desktopmode
 
-import android.hardware.input.KeyGestureEvent
-
-import android.hardware.input.InputManager
-import android.hardware.input.InputManager.KeyGestureEventHandler
-import android.os.IBinder
-import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
-import com.android.wm.shell.ShellTaskOrganizer
 import android.app.ActivityManager.RunningTaskInfo
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel
-import com.android.internal.protolog.ProtoLog
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.content.Context
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.KeyGestureEventHandler
+import android.hardware.input.KeyGestureEvent
+import android.os.IBinder
 import com.android.hardware.input.Flags.manageKeyGestures
+import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
 import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts
-import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
-import com.android.wm.shell.transition.FocusTransitionObserver
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.transition.FocusTransitionObserver
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel
 import java.util.Optional
 
-/**
- * Handles key gesture events (keyboard shortcuts) in Desktop Mode.
- */
+/** Handles key gesture events (keyboard shortcuts) in Desktop Mode. */
 class DesktopModeKeyGestureHandler(
     private val context: Context,
     private val desktopModeWindowDecorViewModel: Optional<DesktopModeWindowDecorViewModel>,
@@ -45,66 +45,87 @@
     inputManager: InputManager,
     private val shellTaskOrganizer: ShellTaskOrganizer,
     private val focusTransitionObserver: FocusTransitionObserver,
-    ) : KeyGestureEventHandler {
+    @ShellMainThread private val mainExecutor: ShellExecutor,
+    private val displayController: DisplayController,
+) : KeyGestureEventHandler {
 
     init {
         inputManager.registerKeyGestureEventHandler(this)
     }
 
     override fun handleKeyGestureEvent(event: KeyGestureEvent, focusedToken: IBinder?): Boolean {
-        if (!isKeyGestureSupported(event.keyGestureType) || !desktopTasksController.isPresent
-            || !desktopModeWindowDecorViewModel.isPresent) {
+        if (
+            !isKeyGestureSupported(event.keyGestureType) ||
+                !desktopTasksController.isPresent ||
+                !desktopModeWindowDecorViewModel.isPresent
+        ) {
             return false
         }
         when (event.keyGestureType) {
             KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> {
                 logV("Key gesture MOVE_TO_NEXT_DISPLAY is handled")
                 getGloballyFocusedFreeformTask()?.let {
-                    desktopTasksController.get().moveToNextDisplay(
-                        it.taskId
-                    )
+                    mainExecutor.execute {
+                        desktopTasksController.get().moveToNextDisplay(it.taskId)
+                    }
                 }
                 return true
             }
             KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW -> {
                 logV("Key gesture SNAP_LEFT_FREEFORM_WINDOW is handled")
                 getGloballyFocusedFreeformTask()?.let {
-                    desktopModeWindowDecorViewModel.get().onSnapResize(
-                        it.taskId,
-                        true,
-                        DesktopModeEventLogger.Companion.InputMethod.KEYBOARD
-                    )
+                    mainExecutor.execute {
+                        desktopModeWindowDecorViewModel
+                            .get()
+                            .onSnapResize(
+                                it.taskId,
+                                true,
+                                DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+                                /* fromMenu= */ false,
+                            )
+                    }
                 }
                 return true
             }
             KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW -> {
                 logV("Key gesture SNAP_RIGHT_FREEFORM_WINDOW is handled")
                 getGloballyFocusedFreeformTask()?.let {
-                    desktopModeWindowDecorViewModel.get().onSnapResize(
-                        it.taskId,
-                        false,
-                        DesktopModeEventLogger.Companion.InputMethod.KEYBOARD
-                    )
+                    mainExecutor.execute {
+                        desktopModeWindowDecorViewModel
+                            .get()
+                            .onSnapResize(
+                                it.taskId,
+                                false,
+                                DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+                                /* fromMenu= */ false,
+                            )
+                    }
                 }
                 return true
             }
             KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW -> {
                 logV("Key gesture TOGGLE_MAXIMIZE_FREEFORM_WINDOW is handled")
-                getGloballyFocusedFreeformTask()?.let {
-                    desktopTasksController.get().toggleDesktopTaskSize(
-                        it,
-                        ResizeTrigger.MAXIMIZE_MENU,
-                        null,
-                    )
+                getGloballyFocusedFreeformTask()?.let { taskInfo ->
+                    mainExecutor.execute {
+                        desktopTasksController
+                            .get()
+                            .toggleDesktopTaskSize(
+                                taskInfo,
+                                ToggleTaskSizeInteraction(
+                                    isMaximized = isTaskMaximized(taskInfo, displayController),
+                                    source = ToggleTaskSizeInteraction.Source.KEYBOARD_SHORTCUT,
+                                    inputMethod =
+                                        DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+                                ),
+                            )
+                    }
                 }
                 return true
             }
             KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> {
                 logV("Key gesture MINIMIZE_FREEFORM_WINDOW is handled")
                 getGloballyFocusedFreeformTask()?.let {
-                    desktopTasksController.get().minimizeTask(
-                        it,
-                    )
+                    mainExecutor.execute { desktopTasksController.get().minimizeTask(it) }
                 }
                 return true
             }
@@ -112,16 +133,17 @@
         }
     }
 
-    override fun isKeyGestureSupported(gestureType: Int): Boolean = when (gestureType) {
-        KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
-            -> enableMoveToNextDisplayShortcut()
-        KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
-        KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
-        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW,
-        KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW
-            -> enableTaskResizingKeyboardShortcuts() && manageKeyGestures()
-        else -> false
-    }
+    override fun isKeyGestureSupported(gestureType: Int): Boolean =
+        when (gestureType) {
+            KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY ->
+                enableMoveToNextDisplayShortcut()
+            KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
+            KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
+            KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW,
+            KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW ->
+                enableTaskResizingKeyboardShortcuts() && manageKeyGestures()
+            else -> false
+        }
 
     //  TODO: b/364154795 - wait for the completion of moveToNextDisplay transition, otherwise it
     //  will pick a wrong task when a user quickly perform other actions with keyboard shortcuts
@@ -129,7 +151,7 @@
     private fun getGloballyFocusedFreeformTask(): RunningTaskInfo? =
         shellTaskOrganizer.getRunningTasks().find { taskInfo ->
             taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
-                    focusTransitionObserver.hasGlobalFocus(taskInfo)
+                focusTransitionObserver.hasGlobalFocus(taskInfo)
         }
 
     private fun logV(msg: String, vararg arguments: Any?) {
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 41febdf..dfa2d9b 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
@@ -60,7 +60,7 @@
     context: Context,
     shellInit: ShellInit,
     private val transitions: Transitions,
-    private val desktopModeEventLogger: DesktopModeEventLogger
+    private val desktopModeEventLogger: DesktopModeEventLogger,
 ) : Transitions.TransitionObserver {
 
     init {
@@ -89,7 +89,8 @@
         transitions.registerObserver(this)
         SystemProperties.set(
             VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
-            VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE)
+            VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE,
+        )
         desktopModeEventLogger.logTaskInfoStateInit()
     }
 
@@ -97,13 +98,13 @@
         transition: IBinder,
         info: TransitionInfo,
         startTransaction: SurfaceControl.Transaction,
-        finishTransaction: SurfaceControl.Transaction
+        finishTransaction: SurfaceControl.Transaction,
     ) {
         // this was a new recents animation
         if (info.isExitToRecentsTransition() && tasksSavedForRecents.isEmpty()) {
             ProtoLog.v(
                 WM_SHELL_DESKTOP_MODE,
-                "DesktopModeLogger: Recents animation running, saving tasks for later"
+                "DesktopModeLogger: Recents animation running, saving tasks for later",
             )
             // TODO (b/326391303) - avoid logging session exit if we can identify a cancelled
             // recents animation
@@ -129,7 +130,7 @@
         ) {
             ProtoLog.v(
                 WM_SHELL_DESKTOP_MODE,
-                "DesktopModeLogger: Canceled recents animation, restoring tasks"
+                "DesktopModeLogger: Canceled recents animation, restoring tasks",
             )
             // restore saved tasks in the updated set and clear for next use
             postTransitionVisibleFreeformTasks += tasksSavedForRecents
@@ -140,7 +141,7 @@
         identifyLogEventAndUpdateState(
             transitionInfo = info,
             preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos,
-            postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks
+            postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks,
         )
         wasPreviousTransitionExitToOverview = info.isExitToRecentsTransition()
     }
@@ -200,7 +201,7 @@
         ProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
             "DesktopModeLogger: taskInfo map after processing changes %s",
-            postTransitionFreeformTasks.size()
+            postTransitionFreeformTasks.size(),
         )
 
         return postTransitionFreeformTasks
@@ -226,7 +227,7 @@
     private fun identifyLogEventAndUpdateState(
         transitionInfo: TransitionInfo,
         preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
-        postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+        postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
     ) {
         if (
             postTransitionVisibleFreeformTasks.isEmpty() &&
@@ -236,12 +237,10 @@
             // Sessions is finishing, log task updates followed by an exit event
             identifyAndLogTaskUpdates(
                 preTransitionVisibleFreeformTasks,
-                postTransitionVisibleFreeformTasks
+                postTransitionVisibleFreeformTasks,
             )
 
-            desktopModeEventLogger.logSessionExit(
-                getExitReason(transitionInfo)
-            )
+            desktopModeEventLogger.logSessionExit(getExitReason(transitionInfo))
             isSessionActive = false
         } else if (
             postTransitionVisibleFreeformTasks.isNotEmpty() &&
@@ -250,19 +249,17 @@
         ) {
             // Session is starting, log enter event followed by task updates
             isSessionActive = true
-            desktopModeEventLogger.logSessionEnter(
-                getEnterReason(transitionInfo)
-            )
+            desktopModeEventLogger.logSessionEnter(getEnterReason(transitionInfo))
 
             identifyAndLogTaskUpdates(
                 preTransitionVisibleFreeformTasks,
-                postTransitionVisibleFreeformTasks
+                postTransitionVisibleFreeformTasks,
             )
         } else if (isSessionActive) {
             // Session is neither starting, nor finishing, log task updates if there are any
             identifyAndLogTaskUpdates(
                 preTransitionVisibleFreeformTasks,
-                postTransitionVisibleFreeformTasks
+                postTransitionVisibleFreeformTasks,
             )
         }
 
@@ -274,11 +271,11 @@
     /** Compare the old and new state of taskInfos and identify and log the changes */
     private fun identifyAndLogTaskUpdates(
         preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
-        postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+        postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
     ) {
         postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
-            val currentTaskUpdate = buildTaskUpdateForTask(taskInfo,
-                postTransitionVisibleFreeformTasks.size())
+            val currentTaskUpdate =
+                buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size())
             val previousTaskInfo = preTransitionVisibleFreeformTasks[taskId]
             when {
                 // new tasks added
@@ -287,16 +284,20 @@
                     Trace.setCounter(
                         Trace.TRACE_TAG_WINDOW_MANAGER,
                         VISIBLE_TASKS_COUNTER_NAME,
-                        postTransitionVisibleFreeformTasks.size().toLong()
+                        postTransitionVisibleFreeformTasks.size().toLong(),
                     )
-                    SystemProperties.set(VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
-                        postTransitionVisibleFreeformTasks.size().toString())
+                    SystemProperties.set(
+                        VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
+                        postTransitionVisibleFreeformTasks.size().toString(),
+                    )
                 }
                 // old tasks that were resized or repositioned
                 // TODO(b/347935387): Log changes only once they are stable.
-                buildTaskUpdateForTask(previousTaskInfo, postTransitionVisibleFreeformTasks.size())
-                        != currentTaskUpdate ->
-                            desktopModeEventLogger.logTaskInfoChanged(currentTaskUpdate)
+                buildTaskUpdateForTask(
+                    previousTaskInfo,
+                    postTransitionVisibleFreeformTasks.size(),
+                ) != currentTaskUpdate ->
+                    desktopModeEventLogger.logTaskInfoChanged(currentTaskUpdate)
             }
         }
 
@@ -304,14 +305,17 @@
         preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
             if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
                 desktopModeEventLogger.logTaskRemoved(
-                    buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size()))
+                    buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size())
+                )
                 Trace.setCounter(
                     Trace.TRACE_TAG_WINDOW_MANAGER,
                     VISIBLE_TASKS_COUNTER_NAME,
-                    postTransitionVisibleFreeformTasks.size().toLong()
+                    postTransitionVisibleFreeformTasks.size().toLong(),
                 )
-                SystemProperties.set(VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
-                    postTransitionVisibleFreeformTasks.size().toString())
+                SystemProperties.set(
+                    VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
+                    postTransitionVisibleFreeformTasks.size().toString(),
+                )
             }
         }
     }
@@ -332,45 +336,50 @@
 
     /** Get [EnterReason] for this session enter */
     private fun getEnterReason(transitionInfo: TransitionInfo): EnterReason {
-       val enterReason = when {
-            transitionInfo.type == WindowManager.TRANSIT_WAKE
-                   // If there is a screen lock, desktop window entry is after dismissing keyguard
-                   || (transitionInfo.type == WindowManager.TRANSIT_TO_BACK
-                   && wasPreviousTransitionExitByScreenOff) -> EnterReason.SCREEN_ON
-            transitionInfo.type == Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP ->
-                EnterReason.APP_HANDLE_DRAG
-            transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON ->
-                EnterReason.APP_HANDLE_MENU_BUTTON
-            transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW ->
-                EnterReason.APP_FROM_OVERVIEW
-            transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT ->
-                EnterReason.KEYBOARD_SHORTCUT_ENTER
-            // NOTE: the below condition also applies for EnterReason quickswitch
-            transitionInfo.type == WindowManager.TRANSIT_TO_FRONT -> EnterReason.OVERVIEW
-            // Enter desktop mode from cancelled recents has no transition. Enter is detected on the
-            // next transition involving freeform windows.
-            // TODO(b/346564416): Modify logging for cancelled recents once it transition is
-            //  changed. Also see how to account to time difference between actual enter time and
-            //  time of this log. Also account for the missed session when exit happens just after
-            //  a cancelled recents.
-            wasPreviousTransitionExitToOverview -> EnterReason.OVERVIEW
-            transitionInfo.type == WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT
-            else -> {
-                ProtoLog.w(
-                    WM_SHELL_DESKTOP_MODE,
-                    "Unknown enter reason for transition type: %s",
-                    transitionInfo.type
-                )
-                EnterReason.UNKNOWN_ENTER
+        val enterReason =
+            when {
+                transitionInfo.type == WindowManager.TRANSIT_WAKE
+                // If there is a screen lock, desktop window entry is after dismissing keyguard
+                ||
+                    (transitionInfo.type == WindowManager.TRANSIT_TO_BACK &&
+                        wasPreviousTransitionExitByScreenOff) -> EnterReason.SCREEN_ON
+                transitionInfo.type == Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP ->
+                    EnterReason.APP_HANDLE_DRAG
+                transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON ->
+                    EnterReason.APP_HANDLE_MENU_BUTTON
+                transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW ->
+                    EnterReason.APP_FROM_OVERVIEW
+                transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT ->
+                    EnterReason.KEYBOARD_SHORTCUT_ENTER
+                // NOTE: the below condition also applies for EnterReason quickswitch
+                transitionInfo.type == WindowManager.TRANSIT_TO_FRONT -> EnterReason.OVERVIEW
+                // Enter desktop mode from cancelled recents has no transition. Enter is detected on
+                // the
+                // next transition involving freeform windows.
+                // TODO(b/346564416): Modify logging for cancelled recents once it transition is
+                //  changed. Also see how to account to time difference between actual enter time
+                // and
+                //  time of this log. Also account for the missed session when exit happens just
+                // after
+                //  a cancelled recents.
+                wasPreviousTransitionExitToOverview -> EnterReason.OVERVIEW
+                transitionInfo.type == WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT
+                else -> {
+                    ProtoLog.w(
+                        WM_SHELL_DESKTOP_MODE,
+                        "Unknown enter reason for transition type: %s",
+                        transitionInfo.type,
+                    )
+                    EnterReason.UNKNOWN_ENTER
+                }
             }
-        }
         wasPreviousTransitionExitByScreenOff = false
         return enterReason
     }
 
     /** Get [ExitReason] for this session exit */
     private fun getExitReason(transitionInfo: TransitionInfo): ExitReason =
-         when {
+        when {
             transitionInfo.type == WindowManager.TRANSIT_SLEEP -> {
                 wasPreviousTransitionExitByScreenOff = true
                 ExitReason.SCREEN_OFF
@@ -387,7 +396,7 @@
                 ProtoLog.w(
                     WM_SHELL_DESKTOP_MODE,
                     "Unknown exit reason for transition type: %s",
-                    transitionInfo.type
+                    transitionInfo.type,
                 )
                 ExitReason.UNKNOWN_EXIT
             }
@@ -413,8 +422,7 @@
     }
 
     companion object {
-        @VisibleForTesting
-        const val VISIBLE_TASKS_COUNTER_NAME = "desktop_mode_visible_tasks"
+        @VisibleForTesting const val VISIBLE_TASKS_COUNTER_NAME = "desktop_mode_visible_tasks"
         @VisibleForTesting
         const val VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY =
             "debug.tracing." + VISIBLE_TASKS_COUNTER_NAME
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
index d6fccd1..9b3caca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
@@ -43,7 +43,7 @@
                 TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON,
                 TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW,
                 TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT,
-                TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN
+                TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN,
             )
     }
 
@@ -73,7 +73,7 @@
                 TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG,
                 TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON,
                 TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT,
-                TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN
+                TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN,
             )
     }
 
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
index 5801c6a..301ba9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
@@ -75,7 +75,7 @@
         instanceId: InstanceId,
         uid: Int,
         packageName: String,
-        event: DesktopUiEventEnum
+        event: DesktopUiEventEnum,
     ) {
         if (packageName.isEmpty() || uid < 0) {
             logD("Skip logging since package name is empty or bad uid")
@@ -84,11 +84,12 @@
         uiEventLogger.logWithInstanceId(event, uid, packageName, instanceId)
     }
 
-    private fun getUid(packageName: String, userId: Int): Int = try {
-        packageManager.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId).uid
-    } catch (e: PackageManager.NameNotFoundException) {
-        INVALID_PACKAGE_UID
-    }
+    private fun getUid(packageName: String, userId: Int): Int =
+        try {
+            packageManager.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId).uid
+        } catch (e: PackageManager.NameNotFoundException) {
+            INVALID_PACKAGE_UID
+        }
 
     private fun logD(msg: String, vararg arguments: Any?) {
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
@@ -103,10 +104,48 @@
         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 = "Tap on the window header restore button in desktop windowing mode")
+        DESKTOP_WINDOW_RESTORE_BUTTON_TAP(2017),
         @UiEvent(doc = "Double tap on window header to maximize it in desktop windowing mode")
         DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE(1724),
+        @UiEvent(doc = "Double tap on window header to restore from maximize in desktop windowing")
+        DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_RESTORE(2018),
         @UiEvent(doc = "Tap on the window Handle to open the Handle Menu")
-        DESKTOP_WINDOW_APP_HANDLE_TAP(1998);
+        DESKTOP_WINDOW_APP_HANDLE_TAP(1998),
+        @UiEvent(doc = "Tap on the desktop mode option under app handle menu")
+        DESKTOP_WINDOW_APP_HANDLE_MENU_TAP_TO_DESKTOP_MODE(1999),
+        @UiEvent(doc = "Tap on the split screen option under app handle menu")
+        DESKTOP_WINDOW_APP_HANDLE_MENU_TAP_TO_SPLIT_SCREEN(2000),
+        @UiEvent(doc = "Tap on the full screen option under app handle menu")
+        DESKTOP_WINDOW_APP_HANDLE_MENU_TAP_TO_FULL_SCREEN(2001),
+        @UiEvent(doc = "When user successfully drags the app handle to desktop mode")
+        DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_DESKTOP_MODE(2002),
+        @UiEvent(doc = "When user successfully drags the app handle to split screen")
+        DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_SPLIT_SCREEN(2003),
+        @UiEvent(doc = "When user successfully drags the app handle to full screen")
+        DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_FULL_SCREEN(2004),
+        @UiEvent(doc = "Drag the window header to the top to switch to full screen mode")
+        DESKTOP_WINDOW_APP_HEADER_DRAG_TO_FULL_SCREEN(2005),
+        @UiEvent(doc = "Drag the window header to an edge to tile it to the left side")
+        DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_LEFT(2006),
+        @UiEvent(doc = "Drag the window header to an edge to tile it to the right side")
+        DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_RIGHT(2007),
+        @UiEvent(doc = "Hover or long press the maximize button to reveal the menu")
+        DESKTOP_WINDOW_MAXIMIZE_BUTTON_REVEAL_MENU(2015),
+        @UiEvent(doc = "Tap on the maximize option in the maximize button menu")
+        DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_MAXIMIZE(2009),
+        @UiEvent(doc = "Tap on the immersive option in the maximize button menu")
+        DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_IMMERSIVE(2010),
+        @UiEvent(doc = "Tap on the restore option in the maximize button menu")
+        DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_RESTORE(2011),
+        @UiEvent(doc = "Tap on the tile to left option in the maximize button menu")
+        DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_LEFT(2012),
+        @UiEvent(doc = "Tap on the tile to right option in the maximize button menu")
+        DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_RIGHT(2013),
+        @UiEvent(doc = "Moving the desktop window by dragging the header")
+        DESKTOP_WINDOW_MOVE_BY_HEADER_DRAG(2021),
+        @UiEvent(doc = "Double tap on the window header to refocus a desktop window")
+        DESKTOP_WINDOW_HEADER_TAP_TO_REFOCUS(2022);
 
         override fun getId(): Int = mId
     }
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
index c7cf310..14623cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -28,6 +28,7 @@
 import android.graphics.Rect
 import android.os.SystemProperties
 import android.util.Size
+import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
 
 val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float =
@@ -36,21 +37,14 @@
 val DESKTOP_MODE_LANDSCAPE_APP_PADDING: Int =
     SystemProperties.getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25)
 
-/**
- * Calculates the initial bounds to enter desktop, centered on the display.
- */
+/** Calculates the initial bounds to enter desktop, centered on the display. */
 fun calculateDefaultDesktopTaskBounds(displayLayout: DisplayLayout): Rect {
     // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
     val desiredWidth = (displayLayout.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
     val desiredHeight = (displayLayout.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
     val heightOffset = (displayLayout.height() - desiredHeight) / 2
     val widthOffset = (displayLayout.width() - desiredWidth) / 2
-    return Rect(
-        widthOffset,
-        heightOffset,
-        desiredWidth + widthOffset,
-        desiredHeight + heightOffset
-    )
+    return Rect(widthOffset, heightOffset, desiredWidth + widthOffset, desiredHeight + heightOffset)
 }
 
 /**
@@ -62,7 +56,7 @@
 fun calculateInitialBounds(
     displayLayout: DisplayLayout,
     taskInfo: RunningTaskInfo,
-    scale: Float = DESKTOP_MODE_INITIAL_BOUNDS_SCALE
+    scale: Float = DESKTOP_MODE_INITIAL_BOUNDS_SCALE,
 ): Rect {
     val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height())
     val appAspectRatio = calculateAspectRatio(taskInfo)
@@ -87,8 +81,10 @@
                     if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
                         // For portrait resizeable activities, respect apps fullscreen width but
                         // apply ideal size height.
-                        Size(taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth,
-                            idealSize.height)
+                        Size(
+                            taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth,
+                            idealSize.height,
+                        )
                     } else {
                         // For landscape resizeable activities, simply apply ideal size.
                         idealSize
@@ -108,7 +104,7 @@
                         // apply custom app width.
                         Size(
                             customPortraitWidthForLandscapeApp,
-                            taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight
+                            taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight,
                         )
                     } else {
                         // For portrait resizeable activities, simply apply ideal size.
@@ -122,7 +118,7 @@
                         maximizeSizeGivenAspectRatio(
                             taskInfo,
                             Size(customPortraitWidthForLandscapeApp, idealSize.height),
-                            appAspectRatio
+                            appAspectRatio,
                         )
                     } else {
                         // For portrait unresizeable activities, calculate maximum size (within the
@@ -140,13 +136,10 @@
 }
 
 /**
- * Calculates the maximized bounds of a task given in the given [DisplayLayout], taking
- * resizability into consideration.
+ * Calculates the maximized bounds of a task given in the given [DisplayLayout], taking resizability
+ * into consideration.
  */
-fun calculateMaximizeBounds(
-    displayLayout: DisplayLayout,
-    taskInfo: RunningTaskInfo,
-): Rect {
+fun calculateMaximizeBounds(displayLayout: DisplayLayout, taskInfo: RunningTaskInfo): Rect {
     val stableBounds = Rect()
     displayLayout.getStableBounds(stableBounds)
     if (taskInfo.isResizeable) {
@@ -155,10 +148,13 @@
     } else {
         // if non-resizable then calculate max bounds according to aspect ratio
         val activityAspectRatio = calculateAspectRatio(taskInfo)
-        val newSize = maximizeSizeGivenAspectRatio(taskInfo,
-            Size(stableBounds.width(), stableBounds.height()), activityAspectRatio)
-        return centerInArea(
-            newSize, stableBounds, stableBounds.left, stableBounds.top)
+        val newSize =
+            maximizeSizeGivenAspectRatio(
+                taskInfo,
+                Size(stableBounds.width(), stableBounds.height()),
+                activityAspectRatio,
+            )
+        return centerInArea(newSize, stableBounds, stableBounds.left, stableBounds.top)
     }
 }
 
@@ -169,7 +165,7 @@
 fun maximizeSizeGivenAspectRatio(
     taskInfo: RunningTaskInfo,
     targetArea: Size,
-    aspectRatio: Float
+    aspectRatio: Float,
 ): Size {
     val targetHeight = targetArea.height
     val targetWidth = targetArea.width
@@ -211,16 +207,36 @@
         minOf(appBounds.height(), appBounds.width()).toFloat()
 }
 
+/** Returns whether the task is maximized. */
+fun isTaskMaximized(taskInfo: RunningTaskInfo, displayController: DisplayController): Boolean {
+    val displayLayout =
+        displayController.getDisplayLayout(taskInfo.displayId)
+            ?: error("Could not get display layout for display=${taskInfo.displayId}")
+    val stableBounds = Rect()
+    displayLayout.getStableBounds(stableBounds)
+    return isTaskMaximized(taskInfo, stableBounds)
+}
+
+/** Returns whether the task is maximized. */
+fun isTaskMaximized(taskInfo: RunningTaskInfo, stableBounds: Rect): Boolean {
+    val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
+    return if (taskInfo.isResizeable) {
+        isTaskBoundsEqual(currentTaskBounds, stableBounds)
+    } else {
+        isTaskWidthOrHeightEqual(currentTaskBounds, stableBounds)
+    }
+}
+
 /** Returns true if task's width or height is maximized else returns false. */
 fun isTaskWidthOrHeightEqual(taskBounds: Rect, stableBounds: Rect): Boolean {
     return taskBounds.width() == stableBounds.width() ||
-            taskBounds.height() == stableBounds.height()
+        taskBounds.height() == stableBounds.height()
 }
 
 /** Returns true if task bound is equal to stable bounds else returns false. */
 fun isTaskBoundsEqual(taskBounds: Rect, stableBounds: Rect): Boolean {
     return taskBounds.width() == stableBounds.width() &&
-            taskBounds.height() == stableBounds.height()
+        taskBounds.height() == stableBounds.height()
 }
 
 /**
@@ -248,8 +264,8 @@
     get() = isResizeable && !appCompatTaskInfo.hasMinAspectRatioOverride()
 
 /**
- * Adjusts bounds to be positioned in the middle of the area provided, not necessarily the
- * entire screen, as area can be offset by left and top start.
+ * Adjusts bounds to be positioned in the middle of the area provided, not necessarily the entire
+ * screen, as area can be offset by left and top start.
  */
 fun centerInArea(desiredSize: Size, areaBounds: Rect, leftStart: Int, topStart: Int): Rect {
     val heightOffset = (areaBounds.height() - desiredSize.height) / 2
@@ -286,6 +302,6 @@
 }
 
 private fun hasFullscreenOverride(taskInfo: RunningTaskInfo): Boolean {
-    return taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled
-            || taskInfo.appCompatTaskInfo.isSystemFullscreenOverrideEnabled
+    return taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled ||
+        taskInfo.appCompatTaskInfo.isSystemFullscreenOverrideEnabled
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 7fcb767..bccb609 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.desktopmode
 
-import android.content.Context
 import android.graphics.Rect
 import android.graphics.Region
 import android.util.ArrayMap
@@ -30,11 +29,8 @@
 import androidx.core.util.valueIterator
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
-import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.shared.annotations.ShellMainThread
-import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
-import com.android.wm.shell.sysui.ShellInit
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import java.util.function.Consumer
@@ -42,26 +38,23 @@
 import kotlinx.coroutines.launch
 
 /** Tracks desktop data for Android Desktop Windowing. */
-class DesktopRepository (
-    private val context: Context,
-    shellInit: ShellInit,
+class DesktopRepository(
     private val persistentRepository: DesktopPersistentRepository,
-    private val repositoryInitializer: DesktopRepositoryInitializer,
     @ShellMainThread private val mainCoroutineScope: CoroutineScope,
-){
-
+    val userId: Int,
+) {
     /**
      * Task data tracked per desktop.
      *
      * @property activeTasks task ids of active tasks currently or previously visible in Desktop
-     * mode session. Tasks become inactive when task closes or when desktop mode session ends.
+     *   mode session. Tasks become inactive when task closes or when desktop mode session ends.
      * @property visibleTasks task ids for active freeform tasks that are currently visible. There
-     * might be other active tasks in desktop mode that are not visible.
+     *   might be other active tasks in desktop mode that are not visible.
      * @property minimizedTasks task ids for active freeform tasks that are currently minimized.
      * @property closingTasks task ids for tasks that are going to close, but are currently visible.
      * @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom
      * @property fullImmersiveTaskId the task id of the desktop task that is in full-immersive mode.
-     * (top is at index 0).
+     *   (top is at index 0).
      */
     private data class DesktopTaskData(
         val activeTasks: ArraySet<Int> = ArraySet(),
@@ -72,14 +65,16 @@
         val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
         var fullImmersiveTaskId: Int? = null,
     ) {
-        fun deepCopy(): DesktopTaskData = DesktopTaskData(
-            activeTasks = ArraySet(activeTasks),
-            visibleTasks = ArraySet(visibleTasks),
-            minimizedTasks = ArraySet(minimizedTasks),
-            closingTasks = ArraySet(closingTasks),
-            freeformTasksInZOrder = ArrayList(freeformTasksInZOrder),
-            fullImmersiveTaskId = fullImmersiveTaskId
-        )
+        fun deepCopy(): DesktopTaskData =
+            DesktopTaskData(
+                activeTasks = ArraySet(activeTasks),
+                visibleTasks = ArraySet(visibleTasks),
+                minimizedTasks = ArraySet(minimizedTasks),
+                closingTasks = ArraySet(closingTasks),
+                freeformTasksInZOrder = ArrayList(freeformTasksInZOrder),
+                fullImmersiveTaskId = fullImmersiveTaskId,
+            )
+
         fun clear() {
             activeTasks.clear()
             visibleTasks.clear()
@@ -111,21 +106,12 @@
     private var desktopGestureExclusionListener: Consumer<Region>? = null
     private var desktopGestureExclusionExecutor: Executor? = null
 
-    private val desktopTaskDataByDisplayId = object : SparseArray<DesktopTaskData>() {
-        /** Gets [DesktopTaskData] for existing [displayId] or creates a new one. */
-        fun getOrCreate(displayId: Int): DesktopTaskData =
-            this[displayId] ?: DesktopTaskData().also { this[displayId] = it }
-    }
-
-    init {
-        if (DesktopModeStatus.canEnterDesktopMode(context)) {
-            shellInit.addInitCallback(::initRepoFromPersistentStorage, this)
+    private val desktopTaskDataByDisplayId =
+        object : SparseArray<DesktopTaskData>() {
+            /** Gets [DesktopTaskData] for existing [displayId] or creates a new one. */
+            fun getOrCreate(displayId: Int): DesktopTaskData =
+                this[displayId] ?: DesktopTaskData().also { this[displayId] = it }
         }
-    }
-
-    private fun initRepoFromPersistentStorage() {
-        repositoryInitializer.initialize(this)
-    }
 
     /** Adds [activeTasksListener] to be notified of updates to active tasks. */
     fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) {
@@ -137,9 +123,7 @@
         visibleTasksListeners[visibleTasksListener] = executor
         desktopTaskDataByDisplayId.keyIterator().forEach {
             val visibleTaskCount = getVisibleTaskCount(it)
-            executor.execute {
-                visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount)
-            }
+            executor.execute { visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount) }
         }
     }
 
@@ -201,8 +185,7 @@
     /** Removes task from active task list of displays excluding the [excludedDisplayId]. */
     fun removeActiveTask(taskId: Int, excludedDisplayId: Int? = null) {
         desktopTaskDataByDisplayId.forEach { displayId, desktopTaskData ->
-            if ((displayId != excludedDisplayId)
-                && desktopTaskData.activeTasks.remove(taskId)) {
+            if ((displayId != excludedDisplayId) && desktopTaskData.activeTasks.remove(taskId)) {
                 logD("Removed active task=%d displayId=%d", taskId, displayId)
                 updateActiveTasksListeners(displayId)
             }
@@ -229,16 +212,18 @@
     }
 
     fun isActiveTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.activeTasks }
+
     fun isClosingTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.closingTasks }
+
     fun isVisibleTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.visibleTasks }
+
     fun isMinimizedTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.minimizedTasks }
 
     /** Checks if a task is the only visible, non-closing, non-minimized task on its display. */
     fun isOnlyVisibleNonClosingTask(taskId: Int): Boolean =
-        desktopTaskDataSequence().any { it.visibleTasks
-            .subtract(it.closingTasks)
-            .subtract(it.minimizedTasks)
-            .singleOrNull() == taskId
+        desktopTaskDataSequence().any {
+            it.visibleTasks.subtract(it.closingTasks).subtract(it.minimizedTasks).singleOrNull() ==
+                taskId
         }
 
     fun getActiveTasks(displayId: Int): ArraySet<Int> =
@@ -272,10 +257,12 @@
     /**
      * Updates visibility of a freeform task with [taskId] on [displayId] and notifies listeners.
      *
-     * If task was visible on a different display with a different [displayId], removes from
-     * the set of visible tasks on that display and notifies listeners.
+     * If task was visible on a different display with a different [displayId], removes from the set
+     * of visible tasks on that display and notifies listeners.
      */
     fun updateTask(displayId: Int, taskId: Int, isVisible: Boolean) {
+        logD("updateTask taskId=%d, displayId=%d, isVisible=%b", taskId, displayId, isVisible)
+
         if (isVisible) {
             // If task is visible, remove it from any other display besides [displayId].
             removeVisibleTask(taskId, excludedDisplayId = displayId)
@@ -293,8 +280,12 @@
         }
         val newCount = getVisibleTaskCount(displayId)
         if (prevCount != newCount) {
-            logD("Update task visibility taskId=%d visible=%b displayId=%d",
-                taskId, isVisible, displayId)
+            logD(
+                "Update task visibility taskId=%d visible=%b displayId=%d",
+                taskId,
+                isVisible,
+                displayId,
+            )
             logD("VisibleTaskCount has changed from %d to %d", prevCount, newCount)
             notifyVisibleTaskListeners(displayId, newCount)
             if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
@@ -332,9 +323,8 @@
 
     /** Gets number of visible tasks on given [displayId] */
     fun getVisibleTaskCount(displayId: Int): Int =
-        desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size ?: 0.also {
-            logD("getVisibleTaskCount=$it")
-        }
+        desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size
+            ?: 0.also { logD("getVisibleTaskCount=$it") }
 
     /**
      * Adds task (or moves if it already exists) to the top of the ordered list.
@@ -343,7 +333,9 @@
      */
     private fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
         logD("Add or move task to top: display=%d taskId=%d", taskId, displayId)
-        desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
+        desktopTaskDataByDisplayId.forEach { _, value ->
+            value.freeformTasksInZOrder.remove(taskId)
+        }
         desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
         // Unminimize the task if it is minimized.
         unminimizeTask(displayId, taskId)
@@ -357,9 +349,8 @@
         if (displayId == INVALID_DISPLAY) {
             // When a task vanishes it doesn't have a displayId. Find the display of the task and
             // mark it as minimized.
-            getDisplayIdForTask(taskId)?.let {
-                minimizeTask(it, taskId)
-            } ?: logW("Minimize task: No display id found for task: taskId=%d", taskId)
+            getDisplayIdForTask(taskId)?.let { minimizeTask(it, taskId) }
+                ?: logW("Minimize task: No display id found for task: taskId=%d", taskId)
         } else {
             logD("Minimize Task: display=%d, task=%d", displayId, taskId)
             desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
@@ -373,8 +364,8 @@
     /** Unminimizes the task for [taskId] and [displayId] */
     fun unminimizeTask(displayId: Int, taskId: Int) {
         logD("Unminimize Task: display=%d, task=%d", displayId, taskId)
-        desktopTaskDataByDisplayId[displayId]?.minimizedTasks?.remove(taskId) ?:
-            logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)
+        desktopTaskDataByDisplayId[displayId]?.minimizedTasks?.remove(taskId)
+            ?: logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)
     }
 
     private fun getDisplayIdForTask(taskId: Int): Int? {
@@ -407,16 +398,14 @@
         desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
         boundsBeforeMaximizeByTaskId.remove(taskId)
         boundsBeforeFullImmersiveByTaskId.remove(taskId)
-        logD("Remaining freeform tasks: %s",
-            desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString())
+        logD(
+            "Remaining freeform tasks: %s",
+            desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString(),
+        )
         // Remove task from unminimized task if it is minimized.
         unminimizeTask(displayId, taskId)
         // Mark task as not in immersive if it was immersive.
-        setTaskInFullImmersiveState(
-            displayId = displayId,
-            taskId = taskId,
-            immersive = false
-        )
+        setTaskInFullImmersiveState(displayId = displayId, taskId = taskId, immersive = false)
         removeActiveTask(taskId)
         removeVisibleTask(taskId)
         if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
@@ -495,15 +484,16 @@
                     persistentRepository.addOrUpdateDesktop(
                         // Use display id as desktop id for now since only once desktop per display
                         // is supported.
+                        userId = userId,
                         desktopId = displayId,
                         visibleTasks = desktopTaskDataByDisplayIdCopy.visibleTasks,
                         minimizedTasks = desktopTaskDataByDisplayIdCopy.minimizedTasks,
-                        freeformTasksInZOrder = desktopTaskDataByDisplayIdCopy.freeformTasksInZOrder
+                        freeformTasksInZOrder = desktopTaskDataByDisplayIdCopy.freeformTasksInZOrder,
                     )
                 } catch (exception: Exception) {
                     logE(
                         "An exception occurred while updating the persistent repository \n%s",
-                        exception.stackTrace
+                        exception.stackTrace,
                     )
                 }
             }
@@ -529,6 +519,7 @@
             )
             pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}")
             pw.println("${innerPrefix}fullImmersiveTaskId=${data.fullImmersiveTaskId}")
+            pw.println("${innerPrefix}wallpaperActivityToken=$wallpaperActivityToken")
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
index 94ac2e6..947a8dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt
@@ -22,72 +22,81 @@
 import com.android.wm.shell.freeform.TaskChangeListener
 
 /** Manages tasks handling specific to Android Desktop Mode. */
-class DesktopTaskChangeListener(
-    private val desktopRepository: DesktopRepository,
-) : TaskChangeListener {
+class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUserRepositories) :
+    TaskChangeListener {
 
-  override fun onTaskOpening(taskInfo: RunningTaskInfo) {
-    if (!isFreeformTask(taskInfo) && desktopRepository.isActiveTask(taskInfo.taskId)) {
-      desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
-      return
+    override fun onTaskOpening(taskInfo: RunningTaskInfo) {
+        val desktopRepository: DesktopRepository =
+            desktopUserRepositories.getProfile(taskInfo.userId)
+        if (!isFreeformTask(taskInfo) && desktopRepository.isActiveTask(taskInfo.taskId)) {
+            desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+            return
+        }
+        if (isFreeformTask(taskInfo)) {
+            desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
+        }
     }
-    if (isFreeformTask(taskInfo)) {
-      desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
+
+    override fun onTaskChanging(taskInfo: RunningTaskInfo) {
+        val desktopRepository: DesktopRepository =
+            desktopUserRepositories.getProfile(taskInfo.userId)
+        if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
+
+        // Case 1: Freeform task is changed in Desktop Mode.
+        if (isFreeformTask(taskInfo)) {
+            if (taskInfo.isVisible) {
+                desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
+            }
+            desktopRepository.updateTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
+        } else {
+            // Case 2: Freeform task is changed outside Desktop Mode.
+            desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+        }
     }
-  }
 
-  override fun onTaskChanging(taskInfo: RunningTaskInfo) {
-    if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
-
-    // Case 1: Freeform task is changed in Desktop Mode.
-    if (isFreeformTask(taskInfo)) {
-      if (taskInfo.isVisible) {
-        desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
-      }
-      desktopRepository.updateTask(
-          taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
-    } else {
-      // Case 2: Freeform task is changed outside Desktop Mode.
-      desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+    // This method should only be used for scenarios where the task info changes are not propagated
+    // to
+    // [DesktopTaskChangeListener#onTaskChanging] via [TransitionsObserver].
+    // Any changes to [DesktopRepository] from this method should be made carefully to minimize risk
+    // of race conditions and possible duplications with [onTaskChanging].
+    override fun onNonTransitionTaskChanging(taskInfo: RunningTaskInfo) {
+        // TODO: b/367268953 - Propapagate usages from FreeformTaskListener to this method.
     }
-  }
 
-  // This method should only be used for scenarios where the task info changes are not propagated to
-  // [DesktopTaskChangeListener#onTaskChanging] via [TransitionsObserver].
-  // Any changes to [DesktopRepository] from this method should be made carefully to minimize risk
-  // of race conditions and possible duplications with [onTaskChanging].
-  override fun onNonTransitionTaskChanging(taskInfo: RunningTaskInfo) {
-    // TODO: b/367268953 - Propapagate usages from FreeformTaskListener to this method.
-  }
-
-  override fun onTaskMovingToFront(taskInfo: RunningTaskInfo) {
-    if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
-    if (!isFreeformTask(taskInfo)) {
-      desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+    override fun onTaskMovingToFront(taskInfo: RunningTaskInfo) {
+        val desktopRepository: DesktopRepository =
+            desktopUserRepositories.getProfile(taskInfo.userId)
+        if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
+        if (!isFreeformTask(taskInfo)) {
+            desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+        }
+        // TODO: b/367268953 - Connect this with DesktopRepository for handling
+        // task moving to front for tasks in windowing mode.
     }
-    // TODO: b/367268953 - Connect this with DesktopRepository for handling
-    // task moving to front for tasks in windowing mode.
-  }
 
-  override fun onTaskMovingToBack(taskInfo: RunningTaskInfo) {
-    // TODO: b/367268953 - Connect this with DesktopRepository.
-  }
-
-  override fun onTaskClosing(taskInfo: RunningTaskInfo) {
-    if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
-    // TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
-    if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() ||
-        desktopRepository.isClosingTask(taskInfo.taskId)) {
-      // A task that's vanishing should be removed:
-      // - If it's closed by the X button which means it's marked as a closing task.
-      desktopRepository.removeClosingTask(taskInfo.taskId)
-      desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
-    } else {
-      desktopRepository.updateTask(taskInfo.displayId, taskInfo.taskId, isVisible = false)
-      desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+    override fun onTaskMovingToBack(taskInfo: RunningTaskInfo) {
+        // TODO: b/367268953 - Connect this with DesktopRepository.
     }
-  }
 
-  private fun isFreeformTask(taskInfo: RunningTaskInfo): Boolean =
-      taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
+    override fun onTaskClosing(taskInfo: RunningTaskInfo) {
+        val desktopRepository: DesktopRepository =
+            desktopUserRepositories.getProfile(taskInfo.userId)
+        if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
+        // TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
+        if (
+            !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() ||
+                desktopRepository.isClosingTask(taskInfo.taskId)
+        ) {
+            // A task that's vanishing should be removed:
+            // - If it's closed by the X button which means it's marked as a closing task.
+            desktopRepository.removeClosingTask(taskInfo.taskId)
+            desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
+        } else {
+            desktopRepository.updateTask(taskInfo.displayId, taskInfo.taskId, isVisible = false)
+            desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+        }
+    }
+
+    private fun isFreeformTask(taskInfo: RunningTaskInfo): Boolean =
+        taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt
index 65f12cf..848d80f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt
@@ -22,16 +22,14 @@
 import android.graphics.Rect
 import android.view.Gravity
 import com.android.internal.annotations.VisibleForTesting
+import com.android.wm.shell.R
 import com.android.wm.shell.desktopmode.DesktopTaskPosition.BottomLeft
 import com.android.wm.shell.desktopmode.DesktopTaskPosition.BottomRight
 import com.android.wm.shell.desktopmode.DesktopTaskPosition.Center
 import com.android.wm.shell.desktopmode.DesktopTaskPosition.TopLeft
 import com.android.wm.shell.desktopmode.DesktopTaskPosition.TopRight
-import com.android.wm.shell.R
 
-/**
- * The position of a task window in desktop mode.
- */
+/** The position of a task window in desktop mode. */
 sealed class DesktopTaskPosition {
     data object Center : DesktopTaskPosition() {
         private const val WINDOW_HEIGHT_PROPORTION = 0.375
@@ -89,8 +87,8 @@
     }
 
     /**
-     * Returns the top left coordinates for the window to be placed in the given
-     * DesktopTaskPosition in the frame.
+     * Returns the top left coordinates for the window to be placed in the given DesktopTaskPosition
+     * in the frame.
      */
     abstract fun getTopLeftCoordinates(frame: Rect, window: Rect): Point
 
@@ -98,8 +96,8 @@
 }
 
 /**
- * If the app has specified horizontal or vertical gravity layout, don't change the
- * task position for cascading effect.
+ * If the app has specified horizontal or vertical gravity layout, don't change the task position
+ * for cascading effect.
  */
 fun canChangeTaskPosition(taskInfo: TaskInfo): Boolean {
     taskInfo.topActivityInfo?.windowLayout?.let {
@@ -110,9 +108,7 @@
     return true
 }
 
-/**
- * Returns the current DesktopTaskPosition for a given window in the frame.
- */
+/** Returns the current DesktopTaskPosition for a given window in the frame. */
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 fun Rect.getDesktopTaskPosition(bounds: Rect): DesktopTaskPosition {
     return when {
@@ -140,8 +136,8 @@
 
 internal fun prevBoundsMovedAboveThreshold(res: Resources, prev: Rect, newBounds: Rect): Boolean {
     // This is the required minimum dp for a task to be touchable.
-    val moveThresholdPx = res.getDimensionPixelSize(
-        R.dimen.freeform_required_visible_empty_space_in_header)
+    val moveThresholdPx =
+        res.getDimensionPixelSize(R.dimen.freeform_required_visible_empty_space_in_header)
     val leftFar = newBounds.left - prev.left > moveThresholdPx
     val topFar = newBounds.top - prev.top > moveThresholdPx
     val rightFar = prev.right - newBounds.right > moveThresholdPx
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 ccbc920..7bb7242 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
@@ -84,10 +84,17 @@
 import com.android.wm.shell.common.SingleInstanceRemoteListener
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
 import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS
 import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
+import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION
+import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
 import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
 import com.android.wm.shell.draganddrop.DragAndDropController
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
@@ -95,7 +102,8 @@
 import com.android.wm.shell.recents.RecentTasksController
 import com.android.wm.shell.recents.RecentsTransitionHandler
 import com.android.wm.shell.recents.RecentsTransitionStateListener
-import com.android.wm.shell.shared.ShellSharedConstants
+import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransitionState
+import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING
 import com.android.wm.shell.shared.TransitionUtil
 import com.android.wm.shell.shared.annotations.ExternalThread
 import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -127,14 +135,8 @@
 import java.io.PrintWriter
 import java.util.Optional
 import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
 import java.util.function.Consumer
-import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
-import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
-import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS
-import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION
-import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION
-import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransitionState
-import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING
 
 /** Handles moving tasks in and out of desktop */
 class DesktopTasksController(
@@ -157,7 +159,7 @@
     private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
     private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
     private val desktopImmersiveController: DesktopImmersiveController,
-    private val taskRepository: DesktopRepository,
+    private val userRepositories: DesktopUserRepositories,
     private val recentsTransitionHandler: RecentsTransitionHandler,
     private val multiInstanceHelper: MultiInstanceHelper,
     @ShellMainThread private val mainExecutor: ShellExecutor,
@@ -166,6 +168,7 @@
     private val interactionJankMonitor: InteractionJankMonitor,
     @ShellMainThread private val handler: Handler,
     private val desktopModeEventLogger: DesktopModeEventLogger,
+    private val desktopModeUiEventLogger: DesktopModeUiEventLogger,
     private val desktopTilingDecorViewModel: DesktopTilingDecorViewModel,
 ) :
     RemoteCallable<DesktopTasksController>,
@@ -174,6 +177,7 @@
     UserChangeListener {
 
     private val desktopMode: DesktopModeImpl
+    private var taskRepository: DesktopRepository
     private var visualIndicator: DesktopModeVisualIndicator? = null
     private var userId: Int
     private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
@@ -201,8 +205,7 @@
             }
         }
 
-    @VisibleForTesting
-    var taskbarDesktopTaskListener: TaskbarDesktopTaskListener? = null
+    @VisibleForTesting var taskbarDesktopTaskListener: TaskbarDesktopTaskListener? = null
 
     @VisibleForTesting
     var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener? = null
@@ -211,8 +214,7 @@
     val draggingTaskId
         get() = dragToDesktopTransitionHandler.draggingTaskId
 
-    @RecentsTransitionState
-    private var recentsTransitionState = TRANSITION_STATE_NOT_RUNNING
+    @RecentsTransitionState private var recentsTransitionState = TRANSITION_STATE_NOT_RUNNING
 
     private lateinit var splitScreenController: SplitScreenController
     lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter
@@ -226,6 +228,7 @@
             shellInit.addInitCallback({ onInit() }, this)
         }
         userId = ActivityManager.getCurrentUser()
+        taskRepository = userRepositories.getProfile(userId)
     }
 
     private fun onInit() {
@@ -233,9 +236,9 @@
         shellCommandHandler.addDumpCallback(this::dump, this)
         shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler, this)
         shellController.addExternalInterface(
-            ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
+            IDesktopMode.DESCRIPTOR,
             { createExternalInterface() },
-            this
+            this,
         )
         shellController.addUserChangeListener(this)
         transitions.addHandler(this)
@@ -245,7 +248,7 @@
                 override fun onTransitionStateChanged(@RecentsTransitionState state: Int) {
                     logV(
                         "Recents transition state changed: %s",
-                        RecentsTransitionStateListener.stateToString(state)
+                        RecentsTransitionStateListener.stateToString(state),
                     )
                     recentsTransitionState = state
                     desktopTilingDecorViewModel.onOverviewAnimationStateChange(
@@ -295,17 +298,17 @@
         bringDesktopAppsToFront(displayId, wct)
 
         val transitionType = transitionType(remoteTransition)
-        val handler = remoteTransition?.let {
-            OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
-        }
+        val handler =
+            remoteTransition?.let {
+                OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
+            }
         transitions.startTransition(transitionType, wct, handler).also { t ->
             handler?.setTransition(t)
         }
     }
 
     /** Gets number of visible tasks in [displayId]. */
-    fun visibleTaskCount(displayId: Int): Int =
-        taskRepository.getVisibleTaskCount(displayId)
+    fun visibleTaskCount(displayId: Int): Int = taskRepository.getVisibleTaskCount(displayId)
 
     /** Returns true if any tasks are visible in Desktop Mode. */
     fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0
@@ -316,29 +319,37 @@
         when (allFocusedTasks.size) {
             0 -> return
             // Full screen case
-            1 -> moveRunningTaskToDesktop(
-                allFocusedTasks.single(), transitionSource = transitionSource)
+            1 ->
+                moveRunningTaskToDesktop(
+                    allFocusedTasks.single(),
+                    transitionSource = transitionSource,
+                )
             // Split-screen case where there are two focused tasks, then we find the child
             // task to move to desktop.
-            2 -> moveRunningTaskToDesktop(
-                getSplitFocusedTask(allFocusedTasks[0], allFocusedTasks[1]),
-                    transitionSource = transitionSource)
-            else -> logW(
-                "DesktopTasksController: Cannot enter desktop, expected less " +
-                "than 3 focused tasks but found %d", allFocusedTasks.size)
+            2 ->
+                moveRunningTaskToDesktop(
+                    getSplitFocusedTask(allFocusedTasks[0], allFocusedTasks[1]),
+                    transitionSource = transitionSource,
+                )
+            else ->
+                logW(
+                    "DesktopTasksController: Cannot enter desktop, expected less " +
+                        "than 3 focused tasks but found %d",
+                    allFocusedTasks.size,
+                )
         }
     }
 
     /**
-     * Returns all focused tasks in full screen or split screen mode in [displayId] when
-     * it is not the home activity.
+     * Returns all focused tasks in full screen or split screen mode in [displayId] when it is not
+     * the home activity.
      */
     private fun getAllFocusedTasks(displayId: Int): List<RunningTaskInfo> =
         shellTaskOrganizer.getRunningTasks(displayId).filter {
             it.isFocused &&
-            (it.windowingMode == WINDOWING_MODE_FULLSCREEN ||
-                it.windowingMode == WINDOWING_MODE_MULTI_WINDOW) &&
-            it.activityType != ACTIVITY_TYPE_HOME
+                (it.windowingMode == WINDOWING_MODE_FULLSCREEN ||
+                    it.windowingMode == WINDOWING_MODE_MULTI_WINDOW) &&
+                it.activityType != ACTIVITY_TYPE_HOME
         }
 
     /** Returns child task from two focused tasks in split screen mode. */
@@ -374,9 +385,9 @@
     }
 
     private fun moveBackgroundTaskToDesktop(
-            taskId: Int,
-            wct: WindowContainerTransaction,
-            transitionSource: DesktopModeTransitionSource,
+        taskId: Int,
+        wct: WindowContainerTransaction,
+        transitionSource: DesktopModeTransitionSource,
     ): Boolean {
         if (recentTasksController?.findTaskInBackground(taskId) == null) {
             logW("moveBackgroundTaskToDesktop taskId=%d not found", taskId)
@@ -384,54 +395,62 @@
         }
         logV("moveBackgroundTaskToDesktop with taskId=%d", taskId)
         // TODO(342378842): Instead of using default display, support multiple displays
-        val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
-            DEFAULT_DISPLAY, wct, taskId)
-        val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
-            wct = wct,
-            displayId = DEFAULT_DISPLAY,
-            excludeTaskId = taskId,
-            reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
-        )
+        val taskIdToMinimize =
+            bringDesktopAppsToFrontBeforeShowingNewTask(DEFAULT_DISPLAY, wct, taskId)
+        val exitResult =
+            desktopImmersiveController.exitImmersiveIfApplicable(
+                wct = wct,
+                displayId = DEFAULT_DISPLAY,
+                excludeTaskId = taskId,
+                reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
+            )
         wct.startTask(
             taskId,
-            ActivityOptions.makeBasic().apply {
-                launchWindowingMode = WINDOWING_MODE_FREEFORM
-            }.toBundle(),
+            ActivityOptions.makeBasic()
+                .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM }
+                .toBundle(),
         )
         // TODO(343149901): Add DPI changes for task launch
         val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
-        desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+        desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
+            FREEFORM_ANIMATION_DURATION
+        )
         taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
         exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
         return true
     }
 
-   /** Moves a running task to desktop. */
+    /** Moves a running task to desktop. */
     fun moveRunningTaskToDesktop(
         task: RunningTaskInfo,
         wct: WindowContainerTransaction = WindowContainerTransaction(),
         transitionSource: DesktopModeTransitionSource,
     ) {
-        if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
-            && isTopActivityExemptFromDesktopWindowing(context, task)) {
+        if (
+            DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() &&
+                isTopActivityExemptFromDesktopWindowing(context, task)
+        ) {
             logW("Cannot enter desktop for taskId %d, ineligible top activity found", task.taskId)
             return
         }
         logV("moveRunningTaskToDesktop taskId=%d", task.taskId)
         exitSplitIfApplicable(wct, task)
-        val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
-            wct = wct,
-            displayId = task.displayId,
-            excludeTaskId = task.taskId,
-            reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
-        )
+        val exitResult =
+            desktopImmersiveController.exitImmersiveIfApplicable(
+                wct = wct,
+                displayId = task.displayId,
+                excludeTaskId = task.taskId,
+                reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
+            )
         // Bring other apps to front first
         val taskIdToMinimize =
             bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
         addMoveToDesktopChanges(wct, task)
 
         val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
-        desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+        desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
+            FREEFORM_ANIMATION_DURATION
+        )
         taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
         exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
     }
@@ -446,11 +465,18 @@
         taskSurface: SurfaceControl,
     ) {
         logV("startDragToDesktop taskId=%d", taskInfo.taskId)
-        interactionJankMonitor.begin(taskSurface, context, handler,
-            CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
+        val jankConfigBuilder =
+            InteractionJankMonitor.Configuration.Builder.withSurface(
+                    CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD,
+                    context,
+                    taskSurface,
+                    handler,
+                )
+                .setTimeout(APP_HANDLE_DRAG_HOLD_CUJ_TIMEOUT_MS)
+        interactionJankMonitor.begin(jankConfigBuilder)
         dragToDesktopTransitionHandler.startDragToDesktopTransition(
-            taskInfo.taskId,
-            dragToDesktopValueAnimator
+            taskInfo,
+            dragToDesktopValueAnimator,
         )
     }
 
@@ -462,7 +488,7 @@
         ProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
             "DesktopTasksController: finalizeDragToDesktop taskId=%d",
-            taskInfo.taskId
+            taskInfo.taskId,
         )
         val wct = WindowContainerTransaction()
         exitSplitIfApplicable(wct, taskInfo)
@@ -470,12 +496,13 @@
         val taskIdToMinimize =
             bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
         addMoveToDesktopChanges(wct, taskInfo)
-        val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
-            wct = wct,
-            displayId = taskInfo.displayId,
-            excludeTaskId = null,
-            reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH
-        )
+        val exitResult =
+            desktopImmersiveController.exitImmersiveIfApplicable(
+                wct = wct,
+                displayId = taskInfo.displayId,
+                excludeTaskId = null,
+                reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
+            )
         val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct)
         desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
             DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS.toInt()
@@ -518,16 +545,16 @@
         performDesktopExitCleanupIfNeeded(taskId, wct)
         taskRepository.addClosingTask(displayId, taskId)
         taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
-            doesAnyTaskRequireTaskbarRounding(
-                displayId,
-                taskId
-            )
+            doesAnyTaskRequireTaskbarRounding(displayId, taskId)
         )
-        return desktopImmersiveController.exitImmersiveIfApplicable(
-            wct = wct,
-            taskInfo = taskInfo,
-            reason = DesktopImmersiveController.ExitReason.CLOSED
-        ).asExit()?.runOnTransitionStart
+        return desktopImmersiveController
+            .exitImmersiveIfApplicable(
+                wct = wct,
+                taskInfo = taskInfo,
+                reason = DesktopImmersiveController.ExitReason.CLOSED,
+            )
+            .asExit()
+            ?.runOnTransitionStart
     }
 
     fun minimizeTask(taskInfo: RunningTaskInfo) {
@@ -536,11 +563,12 @@
         val wct = WindowContainerTransaction()
         performDesktopExitCleanupIfNeeded(taskId, wct)
         // Notify immersive handler as it might need to exit immersive state.
-        val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
-            wct = wct,
-            taskInfo = taskInfo,
-            reason = DesktopImmersiveController.ExitReason.MINIMIZED
-        )
+        val exitResult =
+            desktopImmersiveController.exitImmersiveIfApplicable(
+                wct = wct,
+                taskInfo = taskInfo,
+                reason = DesktopImmersiveController.ExitReason.MINIMIZED,
+            )
 
         wct.reorder(taskInfo.token, false)
         val transition = freeformTaskTransitionStarter.startMinimizedModeTransition(wct)
@@ -548,7 +576,7 @@
             it.addPendingMinimizeChange(
                 transition = transition,
                 displayId = displayId,
-                taskId = taskId
+                taskId = taskId,
             )
         }
         exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
@@ -575,7 +603,7 @@
             splitScreenController.prepareExitSplitScreen(
                 wct,
                 splitScreenController.getStageOfTask(taskInfo.taskId),
-                EXIT_REASON_DESKTOP_MODE
+                EXIT_REASON_DESKTOP_MODE,
             )
             splitScreenController.transitionHandler?.onSplitToDesktop()
         }
@@ -595,22 +623,24 @@
     private fun moveToFullscreenWithAnimation(
         task: RunningTaskInfo,
         position: Point,
-        transitionSource: DesktopModeTransitionSource
+        transitionSource: DesktopModeTransitionSource,
     ) {
         logV("moveToFullscreenWithAnimation taskId=%d", task.taskId)
         val wct = WindowContainerTransaction()
         addMoveToFullscreenChanges(wct, task)
 
         exitDesktopTaskTransitionHandler.startTransition(
-                transitionSource,
-                wct,
-                position,
-                mOnAnimationFinishedCallback
-            )
+            transitionSource,
+            wct,
+            position,
+            mOnAnimationFinishedCallback,
+        )
 
         // handles case where we are moving to full screen without closing all DW tasks.
         if (!taskRepository.isOnlyVisibleNonClosingTask(task.taskId)) {
-            desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+            desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(
+                FULLSCREEN_ANIMATION_DURATION
+            )
         }
     }
 
@@ -640,16 +670,11 @@
         val wct = WindowContainerTransaction()
         wct.startTask(
             taskId,
-            ActivityOptions.makeBasic().apply {
-                launchWindowingMode = WINDOWING_MODE_FREEFORM
-            }.toBundle(),
+            ActivityOptions.makeBasic()
+                .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM }
+                .toBundle(),
         )
-        startLaunchTransition(
-            TRANSIT_OPEN,
-            wct,
-            taskId,
-            remoteTransition = remoteTransition
-        )
+        startLaunchTransition(TRANSIT_OPEN, wct, taskId, remoteTransition = remoteTransition)
     }
 
     /**
@@ -684,29 +709,32 @@
         remoteTransition: RemoteTransition? = null,
         displayId: Int = DEFAULT_DISPLAY,
     ): IBinder {
-        val taskIdToMinimize = if (launchingTaskId != null) {
-            addAndGetMinimizeChanges(displayId, wct, newTaskId = launchingTaskId)
-        } else {
-            logW("Starting desktop task launch without checking the task-limit")
-            // TODO(b/378920066): This currently does not respect the desktop window limit.
-            //  It's possible that |launchingTaskId| is null when launching using an intent, and
-            //  the task-limit should be respected then too.
-            null
-        }
-        val exitImmersiveResult = desktopImmersiveController.exitImmersiveIfApplicable(
-            wct = wct,
-            displayId = displayId,
-            excludeTaskId = launchingTaskId,
-            reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
-        )
-        if (remoteTransition == null) {
-            val t = desktopMixedTransitionHandler.startLaunchTransition(
-                transitionType = transitionType,
+        val taskIdToMinimize =
+            if (launchingTaskId != null) {
+                addAndGetMinimizeChanges(displayId, wct, newTaskId = launchingTaskId)
+            } else {
+                logW("Starting desktop task launch without checking the task-limit")
+                // TODO(b/378920066): This currently does not respect the desktop window limit.
+                //  It's possible that |launchingTaskId| is null when launching using an intent, and
+                //  the task-limit should be respected then too.
+                null
+            }
+        val exitImmersiveResult =
+            desktopImmersiveController.exitImmersiveIfApplicable(
                 wct = wct,
-                taskId = launchingTaskId,
-                minimizingTaskId = taskIdToMinimize,
-                exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask,
+                displayId = displayId,
+                excludeTaskId = launchingTaskId,
+                reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
             )
+        if (remoteTransition == null) {
+            val t =
+                desktopMixedTransitionHandler.startLaunchTransition(
+                    transitionType = transitionType,
+                    wct = wct,
+                    taskId = launchingTaskId,
+                    minimizingTaskId = taskIdToMinimize,
+                    exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask,
+                )
             taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
             exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
             return t
@@ -720,7 +748,11 @@
         }
         val remoteTransitionHandler =
             DesktopWindowLimitRemoteHandler(
-                mainExecutor, rootTaskDisplayAreaOrganizer, remoteTransition, taskIdToMinimize)
+                mainExecutor,
+                rootTaskDisplayAreaOrganizer,
+                remoteTransition,
+                taskIdToMinimize,
+            )
         val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
         remoteTransitionHandler.setTransition(t)
         taskIdToMinimize.let { addPendingMinimizeTransition(t, it) }
@@ -786,60 +818,27 @@
         transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
     }
 
-    /** Moves a task in/out of full immersive state within the desktop. */
-    fun toggleDesktopTaskFullImmersiveState(taskInfo: RunningTaskInfo) {
-        if (taskRepository.isTaskInFullImmersiveState(taskInfo.taskId)) {
-            exitDesktopTaskFromFullImmersive(
-                taskInfo,
-                DesktopImmersiveController.ExitReason.USER_INTERACTION,
-            )
-        } else {
-            moveDesktopTaskToFullImmersive(taskInfo)
-        }
-    }
-
-    private fun moveDesktopTaskToFullImmersive(taskInfo: RunningTaskInfo) {
-        check(taskInfo.isFreeform) { "Task must already be in freeform" }
-        desktopImmersiveController.moveTaskToImmersive(taskInfo)
-    }
-
-    private fun exitDesktopTaskFromFullImmersive(
-        taskInfo: RunningTaskInfo,
-        reason: DesktopImmersiveController.ExitReason,
-    ) {
-        check(taskInfo.isFreeform) { "Task must already be in freeform" }
-        desktopImmersiveController.moveTaskToNonImmersive(taskInfo, reason)
-    }
-
     /**
      * 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,
-        resizeTrigger: ResizeTrigger,
-        motionEvent: MotionEvent?,
-    ) {
+    fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, interaction: ToggleTaskSizeInteraction) {
         val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
         desktopModeEventLogger.logTaskResizingStarted(
-            resizeTrigger,
-            DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent),
+            interaction.resizeTrigger,
+            interaction.inputMethod,
             taskInfo,
             currentTaskBounds.width(),
             currentTaskBounds.height(),
-            displayController
+            displayController,
         )
-
         val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
-
-        val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
         val destinationBounds = Rect()
-
-        val isMaximized = isTaskMaximized(taskInfo, stableBounds)
+        val isMaximized = interaction.direction == ToggleTaskSizeInteraction.Direction.RESTORE
         // If the task is currently maximized, we will toggle it not to be and vice versa. This is
         // helpful to eliminate the current task from logic to calculate taskbar corner rounding.
-        val willMaximize = !isMaximized
+        val willMaximize = interaction.direction == ToggleTaskSizeInteraction.Direction.MAXIMIZE
         if (isMaximized) {
             // The desktop task is at the maximized width and/or height of the stable bounds.
             // If the task's pre-maximize stable bounds were saved, toggle the task to those bounds.
@@ -864,22 +863,27 @@
             destinationBounds.set(calculateMaximizeBounds(displayLayout, taskInfo))
         }
 
-
         val shouldRestoreToSnap =
             isMaximized && isTaskSnappedToHalfScreen(taskInfo, destinationBounds)
 
         logD("willMaximize = %s", willMaximize)
         logD("shouldRestoreToSnap = %s", shouldRestoreToSnap)
 
-        val doesAnyTaskRequireTaskbarRounding = willMaximize || shouldRestoreToSnap ||
+        val doesAnyTaskRequireTaskbarRounding =
+            willMaximize ||
+                shouldRestoreToSnap ||
                 doesAnyTaskRequireTaskbarRounding(taskInfo.displayId, taskInfo.taskId)
 
         taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding)
         val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
+        interaction.uiEvent?.let { uiEvent -> desktopModeUiEventLogger.log(taskInfo, uiEvent) }
         desktopModeEventLogger.logTaskResizingEnded(
-            resizeTrigger, DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent),
-            taskInfo, destinationBounds.width(),
-            destinationBounds.height(), displayController
+            interaction.resizeTrigger,
+            interaction.inputMethod,
+            taskInfo,
+            destinationBounds.width(),
+            destinationBounds.height(),
+            displayController,
         )
         toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
     }
@@ -888,12 +892,9 @@
         taskInfo: RunningTaskInfo,
         taskSurface: SurfaceControl,
         currentDragBounds: Rect,
-        motionEvent: MotionEvent
+        motionEvent: MotionEvent,
     ) {
-        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
-        val stableBounds = Rect()
-        displayLayout.getStableBounds(stableBounds)
-        if (isTaskMaximized(taskInfo, stableBounds)) {
+        if (isTaskMaximized(taskInfo, displayController)) {
             // Handle the case where we attempt to drag-to-maximize when already maximized: the task
             // position won't need to change but we want to animate the surface going back to the
             // maximized position.
@@ -909,7 +910,14 @@
             return
         }
 
-        toggleDesktopTaskSize(taskInfo, ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, motionEvent)
+        toggleDesktopTaskSize(
+            taskInfo,
+            ToggleTaskSizeInteraction(
+                direction = ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+                source = ToggleTaskSizeInteraction.Source.HEADER_DRAG_TO_TOP,
+                inputMethod = DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent),
+            ),
+        )
     }
 
     private fun getMaximizeBounds(taskInfo: RunningTaskInfo, stableBounds: Rect): Rect {
@@ -919,29 +927,19 @@
         } else {
             // if non-resizable then calculate max bounds according to aspect ratio
             val activityAspectRatio = calculateAspectRatio(taskInfo)
-            val newSize = maximizeSizeGivenAspectRatio(taskInfo,
-                Size(stableBounds.width(), stableBounds.height()), activityAspectRatio)
-            return centerInArea(
-                newSize, stableBounds, stableBounds.left, stableBounds.top)
-        }
-    }
-
-    private fun isTaskMaximized(
-        taskInfo: RunningTaskInfo,
-        stableBounds: Rect
-    ): Boolean {
-        val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
-
-        return if (taskInfo.isResizeable) {
-            isTaskBoundsEqual(currentTaskBounds, stableBounds)
-        } else {
-            isTaskWidthOrHeightEqual(currentTaskBounds, stableBounds)
+            val newSize =
+                maximizeSizeGivenAspectRatio(
+                    taskInfo,
+                    Size(stableBounds.width(), stableBounds.height()),
+                    activityAspectRatio,
+                )
+            return centerInArea(newSize, stableBounds, stableBounds.left, stableBounds.top)
         }
     }
 
     private fun isMaximizedToStableBoundsEdges(
         taskInfo: RunningTaskInfo,
-        stableBounds: Rect
+        stableBounds: Rect,
     ): Boolean {
         val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
         return isTaskBoundsEqual(currentTaskBounds, stableBounds)
@@ -950,18 +948,16 @@
     /** Returns if current task bound is snapped to half screen */
     private fun isTaskSnappedToHalfScreen(
         taskInfo: RunningTaskInfo,
-        taskBounds: Rect = taskInfo.configuration.windowConfiguration.bounds
+        taskBounds: Rect = taskInfo.configuration.windowConfiguration.bounds,
     ): Boolean =
         getSnapBounds(taskInfo, SnapPosition.LEFT) == taskBounds ||
-                getSnapBounds(taskInfo, SnapPosition.RIGHT) == taskBounds
+            getSnapBounds(taskInfo, SnapPosition.RIGHT) == taskBounds
 
     @VisibleForTesting
-    fun doesAnyTaskRequireTaskbarRounding(
-        displayId: Int,
-        excludeTaskId: Int? = null,
-    ): Boolean {
+    fun doesAnyTaskRequireTaskbarRounding(displayId: Int, excludeTaskId: Int? = null): Boolean {
         val doesAnyTaskRequireTaskbarRounding =
-            taskRepository.getExpandedTasksOrdered(displayId)
+            taskRepository
+                .getExpandedTasksOrdered(displayId)
                 // exclude current task since maximize/restore transition has not taken place yet.
                 .filterNot { taskId -> taskId == excludeTaskId }
                 .any { taskId ->
@@ -971,14 +967,14 @@
                     logD("taskInfo = %s", taskInfo)
                     logD(
                         "isTaskSnappedToHalfScreen(taskInfo) = %s",
-                        isTaskSnappedToHalfScreen(taskInfo)
+                        isTaskSnappedToHalfScreen(taskInfo),
                     )
                     logD(
                         "isMaximizedToStableBoundsEdges(taskInfo, stableBounds) = %s",
-                        isMaximizedToStableBoundsEdges(taskInfo, stableBounds)
+                        isMaximizedToStableBoundsEdges(taskInfo, stableBounds),
                     )
-                    isTaskSnappedToHalfScreen(taskInfo)
-                            || isMaximizedToStableBoundsEdges(taskInfo, stableBounds)
+                    isTaskSnappedToHalfScreen(taskInfo) ||
+                        isMaximizedToStableBoundsEdges(taskInfo, stableBounds)
                 }
 
         logD("doesAnyTaskRequireTaskbarRounding = %s", doesAnyTaskRequireTaskbarRounding)
@@ -991,7 +987,7 @@
      * @param taskInfo current task that is being snap-resized via dragging or maximize menu button
      * @param taskSurface the leash of the task being dragged
      * @param currentDragBounds current position of the task leash being dragged (or current task
-     *                          bounds if being snapped resize via maximize menu button)
+     *   bounds if being snapped resize via maximize menu button)
      * @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
      */
     fun snapToHalfScreen(
@@ -1009,7 +1005,7 @@
             taskInfo,
             currentDragBounds.width(),
             currentDragBounds.height(),
-            displayController
+            displayController,
         )
 
         val destinationBounds = getSnapBounds(taskInfo, position)
@@ -1023,12 +1019,13 @@
         )
 
         if (DesktopModeFlags.ENABLE_TILE_RESIZING.isTrue()) {
-            val isTiled = desktopTilingDecorViewModel.snapToHalfScreen(
-                taskInfo,
-                desktopWindowDecoration,
-                position,
-                currentDragBounds,
-            )
+            val isTiled =
+                desktopTilingDecorViewModel.snapToHalfScreen(
+                    taskInfo,
+                    desktopWindowDecoration,
+                    position,
+                    currentDragBounds,
+                )
             if (isTiled) {
                 taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(true)
             }
@@ -1069,10 +1066,11 @@
     ) {
         if (!isSnapResizingAllowed(taskInfo)) {
             Toast.makeText(
-                getContext(),
-                R.string.desktop_mode_non_resizable_snap_text,
-                Toast.LENGTH_SHORT
-            ).show()
+                    getContext(),
+                    R.string.desktop_mode_non_resizable_snap_text,
+                    Toast.LENGTH_SHORT,
+                )
+                .show()
             return
         }
 
@@ -1083,11 +1081,10 @@
             position,
             resizeTrigger,
             inputMethod,
-            desktopModeWindowDecoration
+            desktopModeWindowDecoration,
         )
     }
 
-
     @VisibleForTesting
     fun handleSnapResizingTaskOnDrag(
         taskInfo: RunningTaskInfo,
@@ -1101,7 +1098,11 @@
         releaseVisualIndicator()
         if (!isSnapResizingAllowed(taskInfo)) {
             interactionJankMonitor.begin(
-                taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable"
+                taskSurface,
+                context,
+                handler,
+                CUJ_DESKTOP_MODE_SNAP_RESIZE,
+                "drag_non_resizable",
             )
 
             // reposition non-resizable app back to its original position before being dragged
@@ -1112,20 +1113,26 @@
                 endBounds = dragStartBounds,
                 doOnEnd = {
                     Toast.makeText(
-                        context,
-                        com.android.wm.shell.R.string.desktop_mode_non_resizable_snap_text,
-                        Toast.LENGTH_SHORT
-                    ).show()
+                            context,
+                            com.android.wm.shell.R.string.desktop_mode_non_resizable_snap_text,
+                            Toast.LENGTH_SHORT,
+                        )
+                        .show()
                 },
             )
         } else {
-            val resizeTrigger = if (position == SnapPosition.LEFT) {
-                ResizeTrigger.DRAG_LEFT
-            } else {
-                ResizeTrigger.DRAG_RIGHT
-            }
+            val resizeTrigger =
+                if (position == SnapPosition.LEFT) {
+                    ResizeTrigger.DRAG_LEFT
+                } else {
+                    ResizeTrigger.DRAG_RIGHT
+                }
             interactionJankMonitor.begin(
-                taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
+                taskSurface,
+                context,
+                handler,
+                CUJ_DESKTOP_MODE_SNAP_RESIZE,
+                "drag_resizable",
             )
             snapToHalfScreen(
                 taskInfo,
@@ -1155,7 +1162,7 @@
                     stableBounds.left,
                     stableBounds.top,
                     stableBounds.left + destinationWidth,
-                    stableBounds.bottom
+                    stableBounds.bottom,
                 )
             }
             SnapPosition.RIGHT -> {
@@ -1163,7 +1170,7 @@
                     stableBounds.right - destinationWidth,
                     stableBounds.top,
                     stableBounds.right,
-                    stableBounds.bottom
+                    stableBounds.bottom,
                 )
             }
         }
@@ -1183,36 +1190,32 @@
     private fun bringDesktopAppsToFrontBeforeShowingNewTask(
         displayId: Int,
         wct: WindowContainerTransaction,
-        newTaskIdInFront: Int
+        newTaskIdInFront: Int,
     ): Int? = bringDesktopAppsToFront(displayId, wct, newTaskIdInFront)
 
     private fun bringDesktopAppsToFront(
         displayId: Int,
         wct: WindowContainerTransaction,
-        newTaskIdInFront: Int? = null
+        newTaskIdInFront: Int? = null,
     ): Int? {
         logV("bringDesktopAppsToFront, newTaskId=%d", newTaskIdInFront)
         // Move home to front, ensures that we go back home when all desktop windows are closed
         moveHomeTask(wct, toTop = true)
 
         // Currently, we only handle the desktop on the default display really.
-        if (displayId == DEFAULT_DISPLAY
-            && ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
+        if (displayId == DEFAULT_DISPLAY && ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
             // Add translucent wallpaper activity to show the wallpaper underneath
             addWallpaperActivity(wct)
         }
 
-        val expandedTasksOrderedFrontToBack =
-            taskRepository.getExpandedTasksOrdered(displayId)
+        val expandedTasksOrderedFrontToBack = taskRepository.getExpandedTasksOrdered(displayId)
         // If we're adding a new Task we might need to minimize an old one
         // TODO(b/365725441): Handle non running task minimization
         val taskIdToMinimize: Int? =
             if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) {
-                desktopTasksLimiter.get()
-                    .getTaskIdToMinimize(
-                        expandedTasksOrderedFrontToBack,
-                        newTaskIdInFront
-                    )
+                desktopTasksLimiter
+                    .get()
+                    .getTaskIdToMinimize(expandedTasksOrderedFrontToBack, newTaskIdInFront)
             } else {
                 null
             }
@@ -1230,15 +1233,16 @@
                     // Task is not running, start it
                     wct.startTask(
                         taskId,
-                        ActivityOptions.makeBasic().apply {
-                            launchWindowingMode = WINDOWING_MODE_FREEFORM
-                        }.toBundle(),
+                        ActivityOptions.makeBasic()
+                            .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM }
+                            .toBundle(),
                     )
                 }
             }
 
-        taskbarDesktopTaskListener?.
-            onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(displayId))
+        taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
+            doesAnyTaskRequireTaskbarRounding(displayId)
+        )
 
         return taskIdToMinimize
     }
@@ -1253,8 +1257,7 @@
     private fun addWallpaperActivity(wct: WindowContainerTransaction) {
         logV("addWallpaperActivity")
         val userHandle = UserHandle.of(userId)
-        val userContext =
-            context.createContextAsUser(userHandle, /* flags= */ 0)
+        val userContext = context.createContextAsUser(userHandle, /* flags= */ 0)
         val intent = Intent(userContext, DesktopWallpaperActivity::class.java)
         intent.putExtra(Intent.EXTRA_USER_HANDLE, userId)
         val options =
@@ -1269,8 +1272,8 @@
                 /* requestCode= */ 0,
                 intent,
                 PendingIntent.FLAG_IMMUTABLE,
-                /* bundle= */ null,
-                userHandle
+                /* options= */ null,
+                userHandle,
             )
         wct.sendPendingIntent(pendingIntent, intent, options.toBundle())
     }
@@ -1290,13 +1293,14 @@
         if (!taskRepository.isOnlyVisibleNonClosingTask(taskId)) {
             return
         }
-        desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+        desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(
+            FULLSCREEN_ANIMATION_DURATION
+        )
         if (taskRepository.wallpaperActivityToken != null) {
             removeWallpaperActivity(wct)
         }
     }
 
-
     fun releaseVisualIndicator() {
         val t = SurfaceControl.Transaction()
         visualIndicator?.releaseVisualIndicator(t)
@@ -1320,7 +1324,7 @@
         info: TransitionInfo,
         startTransaction: SurfaceControl.Transaction,
         finishTransaction: SurfaceControl.Transaction,
-        finishCallback: Transitions.TransitionFinishCallback
+        finishCallback: Transitions.TransitionFinishCallback,
     ): Boolean {
         // This handler should never be the sole handler, so should not animate anything.
         return false
@@ -1328,7 +1332,7 @@
 
     override fun handleRequest(
         transition: IBinder,
-        request: TransitionRequestInfo
+        request: TransitionRequestInfo,
     ): WindowContainerTransaction? {
         logV("handleRequest request=%s", request)
         // Check if we should skip handling this transition
@@ -1391,11 +1395,8 @@
                     // Check if freeform task launch during recents should be handled
                     shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task)
                     // Check if the closing task needs to be handled
-                    TransitionUtil.isClosingType(request.type) -> handleTaskClosing(
-                        task,
-                        transition,
-                        request.type
-                    )
+                    TransitionUtil.isClosingType(request.type) ->
+                        handleTaskClosing(task, transition, request.type)
                     // Check if the top task shouldn't be allowed to enter desktop mode
                     isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task)
                     // Check if fullscreen task should be updated
@@ -1412,19 +1413,16 @@
     }
 
     /** Whether the given [change] in the [transition] is a known desktop change. */
-    fun isDesktopChange(
-        transition: IBinder,
-        change: TransitionInfo.Change,
-    ): Boolean {
+    fun isDesktopChange(transition: IBinder, change: TransitionInfo.Change): Boolean {
         // Only the immersive controller is currently involved in mixed transitions.
-        return Flags.enableFullyImmersiveInDesktop()
-                && desktopImmersiveController.isImmersiveChange(transition, change)
+        return Flags.enableFullyImmersiveInDesktop() &&
+            desktopImmersiveController.isImmersiveChange(transition, change)
     }
 
     /**
-     * Whether the given transition [info] will potentially include a desktop change, in which
-     * case the transition should be treated as mixed so that the change is in part animated by
-     * one of the desktop transition handlers.
+     * Whether the given transition [info] will potentially include a desktop change, in which case
+     * the transition should be treated as mixed so that the change is in part animated by one of
+     * the desktop transition handlers.
      */
     fun shouldPlayDesktopAnimation(info: TransitionRequestInfo): Boolean {
         // Only immersive mixed transition are currently supported.
@@ -1468,7 +1466,7 @@
             change,
             startTransaction,
             finishTransaction,
-            finishCallback
+            finishCallback,
         )
     }
 
@@ -1493,13 +1491,14 @@
 
     /** Returns whether an existing desktop task is being relaunched in freeform or not. */
     private fun isFreeformRelaunch(triggerTask: RunningTaskInfo?, request: TransitionRequestInfo) =
-        (triggerTask != null && triggerTask.windowingMode == WINDOWING_MODE_FREEFORM
-                && TransitionUtil.isOpeningType(request.type)
-                && taskRepository.isActiveTask(triggerTask.taskId))
+        (triggerTask != null &&
+            triggerTask.windowingMode == WINDOWING_MODE_FREEFORM &&
+            TransitionUtil.isOpeningType(request.type) &&
+            taskRepository.isActiveTask(triggerTask.taskId))
 
     private fun isIncompatibleTask(task: TaskInfo) =
-        DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
-                && isTopActivityExemptFromDesktopWindowing(context, task)
+        DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() &&
+            isTopActivityExemptFromDesktopWindowing(context, task)
 
     private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean {
         return ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue() &&
@@ -1508,18 +1507,19 @@
     }
 
     /** Open an existing instance of an app. */
-    fun openInstance(
-        callingTask: RunningTaskInfo,
-        requestedTaskId: Int
-    ) {
+    fun openInstance(callingTask: RunningTaskInfo, requestedTaskId: Int) {
         val wct = WindowContainerTransaction()
         val options = createNewWindowOptions(callingTask)
         if (options.launchWindowingMode == WINDOWING_MODE_FREEFORM) {
             wct.startTask(requestedTaskId, options.toBundle())
-            val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
-                callingTask.displayId, wct, requestedTaskId)
-            val exitResult = desktopImmersiveController
-                .exitImmersiveIfApplicable(
+            val taskIdToMinimize =
+                bringDesktopAppsToFrontBeforeShowingNewTask(
+                    callingTask.displayId,
+                    wct,
+                    requestedTaskId,
+                )
+            val exitResult =
+                desktopImmersiveController.exitImmersiveIfApplicable(
                     wct = wct,
                     displayId = callingTask.displayId,
                     excludeTaskId = requestedTaskId,
@@ -1531,44 +1531,51 @@
             exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
         } else {
             val splitPosition = splitScreenController.determineNewInstancePosition(callingTask)
-            splitScreenController.startTask(requestedTaskId, splitPosition,
-                options.toBundle(), null /* hideTaskToken */)
+            splitScreenController.startTask(
+                requestedTaskId,
+                splitPosition,
+                options.toBundle(),
+                null, /* hideTaskToken */
+            )
         }
     }
 
     /** Create an Intent to open a new window of a task. */
-    fun openNewWindow(
-        callingTaskInfo: RunningTaskInfo
-    ) {
+    fun openNewWindow(callingTaskInfo: RunningTaskInfo) {
         // TODO(b/337915660): Add a transition handler for these; animations
         //  need updates in some cases.
         val baseActivity = callingTaskInfo.baseActivity ?: return
-        val fillIn: Intent = context.packageManager
-            .getLaunchIntentForPackage(
-                baseActivity.packageName
-            ) ?: return
-        fillIn
-            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
-        val launchIntent = PendingIntent.getActivity(
-            context,
-            /* requestCode= */ 0,
-            fillIn,
-            PendingIntent.FLAG_IMMUTABLE
-        )
+        val fillIn: Intent =
+            context.packageManager.getLaunchIntentForPackage(baseActivity.packageName) ?: return
+        fillIn.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+        val launchIntent =
+            PendingIntent.getActivity(
+                context,
+                /* requestCode= */ 0,
+                fillIn,
+                PendingIntent.FLAG_IMMUTABLE,
+            )
         val options = createNewWindowOptions(callingTaskInfo)
         when (options.launchWindowingMode) {
             WINDOWING_MODE_MULTI_WINDOW -> {
-                val splitPosition = splitScreenController
-                    .determineNewInstancePosition(callingTaskInfo)
+                val splitPosition =
+                    splitScreenController.determineNewInstancePosition(callingTaskInfo)
                 // TODO(b/349828130) currently pass in index_undefined until we can revisit these
                 //  specific cases in the future.
-                val splitIndex = if (enableFlexibleSplit())
-                    splitScreenController.determineNewInstanceIndex(callingTaskInfo) else
-                    SPLIT_INDEX_UNDEFINED
+                val splitIndex =
+                    if (enableFlexibleSplit())
+                        splitScreenController.determineNewInstanceIndex(callingTaskInfo)
+                    else SPLIT_INDEX_UNDEFINED
                 splitScreenController.startIntent(
-                    launchIntent, context.userId, fillIn, splitPosition,
-                    options.toBundle(), null /* hideTaskToken */,
-                    true /* forceLaunchNewTask */, splitIndex)
+                    launchIntent,
+                    context.userId,
+                    fillIn,
+                    splitPosition,
+                    options.toBundle(),
+                    null /* hideTaskToken */,
+                    true /* forceLaunchNewTask */,
+                    splitIndex,
+                )
             }
             WINDOWING_MODE_FREEFORM -> {
                 val wct = WindowContainerTransaction()
@@ -1577,36 +1584,39 @@
                     transitionType = TRANSIT_OPEN,
                     wct = wct,
                     launchingTaskId = null,
-                    displayId = callingTaskInfo.displayId
+                    displayId = callingTaskInfo.displayId,
                 )
             }
         }
     }
 
     private fun createNewWindowOptions(callingTask: RunningTaskInfo): ActivityOptions {
-        val newTaskWindowingMode = when {
-            callingTask.isFreeform -> {
-                WINDOWING_MODE_FREEFORM
+        val newTaskWindowingMode =
+            when {
+                callingTask.isFreeform -> {
+                    WINDOWING_MODE_FREEFORM
+                }
+                callingTask.isFullscreen || callingTask.isMultiWindow -> {
+                    WINDOWING_MODE_MULTI_WINDOW
+                }
+                else -> {
+                    error("Invalid windowing mode: ${callingTask.windowingMode}")
+                }
             }
-            callingTask.isFullscreen || callingTask.isMultiWindow -> {
-                WINDOWING_MODE_MULTI_WINDOW
+        val bounds =
+            when (newTaskWindowingMode) {
+                WINDOWING_MODE_FREEFORM -> {
+                    displayController.getDisplayLayout(callingTask.displayId)?.let {
+                        getInitialBounds(it, callingTask, callingTask.displayId)
+                    }
+                }
+                WINDOWING_MODE_MULTI_WINDOW -> {
+                    Rect()
+                }
+                else -> {
+                    error("Invalid windowing mode: $newTaskWindowingMode")
+                }
             }
-            else -> {
-                error("Invalid windowing mode: ${callingTask.windowingMode}")
-            }
-        }
-        val bounds = when (newTaskWindowingMode) {
-            WINDOWING_MODE_FREEFORM -> {
-                displayController.getDisplayLayout(callingTask.displayId)
-                    ?.let { getInitialBounds(it, callingTask, callingTask.displayId) }
-            }
-            WINDOWING_MODE_MULTI_WINDOW -> {
-                Rect()
-            }
-            else -> {
-                error("Invalid windowing mode: $newTaskWindowingMode")
-            }
-        }
         return ActivityOptions.makeBasic().apply {
             launchWindowingMode = newTaskWindowingMode
             pendingIntentBackgroundActivityStartMode =
@@ -1632,7 +1642,7 @@
 
     private fun handleFreeformTaskLaunch(
         task: RunningTaskInfo,
-        transition: IBinder
+        transition: IBinder,
     ): WindowContainerTransaction? {
         logV("handleFreeformTaskLaunch")
         if (keyguardManager.isKeyguardLocked) {
@@ -1659,8 +1669,10 @@
         // TODO(b/365723620): Handle non running tasks that were launched after reboot.
         // If task is already visible, it must have been handled already and added to desktop mode.
         // Cascade task only if it's not visible yet.
-        if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue()
-                && !taskRepository.isVisibleTask(task.taskId)) {
+        if (
+            DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue() &&
+                !taskRepository.isVisibleTask(task.taskId)
+        ) {
             val displayLayout = displayController.getDisplayLayout(task.displayId)
             if (displayLayout != null) {
                 val initialBounds = Rect(task.configuration.windowConfiguration.bounds)
@@ -1695,7 +1707,7 @@
 
     private fun handleFullscreenTaskLaunch(
         task: RunningTaskInfo,
-        transition: IBinder
+        transition: IBinder,
     ): WindowContainerTransaction? {
         logV("handleFullscreenTaskLaunch")
         if (shouldFullscreenTaskLaunchSwitchToDesktop(task)) {
@@ -1706,8 +1718,10 @@
                 // that's not the case for launches in desktop. Also, if this launch is the first
                 // one to trigger the desktop mode (e.g., when [forceEnterDesktop()]), activate the
                 // desktop mode here.
-                if (task.baseIntent.flags.and(Intent.FLAG_ACTIVITY_TASK_ON_HOME) != 0
-                    || !isDesktopModeShowing(task.displayId)) {
+                if (
+                    task.baseIntent.flags.and(Intent.FLAG_ACTIVITY_TASK_ON_HOME) != 0 ||
+                        !isDesktopModeShowing(task.displayId)
+                ) {
                     bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
                     wct.reorder(task.token, true)
                 }
@@ -1721,7 +1735,7 @@
                     transition,
                     wct,
                     task.displayId,
-                    reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH
+                    reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
                 )
             }
         } else if (taskRepository.isActiveTask(task.taskId)) {
@@ -1752,10 +1766,13 @@
     }
 
     /** Handle task closing by removing wallpaper activity if it's the last active task */
-    private fun handleTaskClosing(task: RunningTaskInfo, transition: IBinder, requestType: Int): WindowContainerTransaction? {
+    private fun handleTaskClosing(
+        task: RunningTaskInfo,
+        transition: IBinder,
+        requestType: Int,
+    ): WindowContainerTransaction? {
         logV("handleTaskClosing")
-        if (!isDesktopModeShowing(task.displayId))
-            return null
+        if (!isDesktopModeShowing(task.displayId)) return null
 
         val wct = WindowContainerTransaction()
         performDesktopExitCleanupIfNeeded(task.taskId, wct)
@@ -1763,28 +1780,18 @@
         if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
             taskRepository.addClosingTask(task.displayId, task.taskId)
             desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId)
-        } else if (requestType == TRANSIT_CLOSE) {
-            // Handle closing tasks, tasks that are going to back are handled in
-            // [DesktopTasksTransitionObserver].
-            desktopMixedTransitionHandler.addPendingMixedTransition(
-                DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
-                    transition, task.taskId, taskRepository.getVisibleTaskCount(task.displayId) == 1
-                )
-            )
         }
+
         taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
-            doesAnyTaskRequireTaskbarRounding(
-                task.displayId,
-                task.taskId
-            )
+            doesAnyTaskRequireTaskbarRounding(task.displayId, task.taskId)
         )
         return if (wct.isEmpty) null else wct
     }
 
     /**
      * Apply all changes required when task is first added to desktop. Uses the task's current
-     * display by default to apply initial bounds and placement relative to the display.
-     * Use a different [displayId] if the task should be moved to a different display.
+     * display by default to apply initial bounds and placement relative to the display. Use a
+     * different [displayId] if the task should be moved to a different display.
      */
     @VisibleForTesting
     fun addMoveToDesktopChanges(
@@ -1819,11 +1826,12 @@
         taskInfo: RunningTaskInfo,
         displayId: Int,
     ): Rect {
-        val bounds = if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue) {
-            calculateInitialBounds(displayLayout, taskInfo)
-        } else {
-            calculateDefaultDesktopTaskBounds(displayLayout)
-        }
+        val bounds =
+            if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue) {
+                calculateInitialBounds(displayLayout, taskInfo)
+            } else {
+                calculateDefaultDesktopTaskBounds(displayLayout)
+            }
 
         if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue) {
             cascadeWindow(bounds, displayLayout, displayId)
@@ -1833,7 +1841,7 @@
 
     private fun addMoveToFullscreenChanges(
         wct: WindowContainerTransaction,
-        taskInfo: RunningTaskInfo
+        taskInfo: RunningTaskInfo,
     ) {
         val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
         val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
@@ -1860,8 +1868,12 @@
         val activeTasks = taskRepository.getExpandedTasksOrdered(displayId)
         activeTasks.firstOrNull()?.let { activeTask ->
             shellTaskOrganizer.getRunningTaskInfo(activeTask)?.let {
-                cascadeWindow(context.resources, stableBounds,
-                    it.configuration.windowConfiguration.bounds, bounds)
+                cascadeWindow(
+                    context.resources,
+                    stableBounds,
+                    it.configuration.windowConfiguration.bounds,
+                    bounds,
+                )
             }
         }
     }
@@ -1887,24 +1899,19 @@
     private fun addAndGetMinimizeChanges(
         displayId: Int,
         wct: WindowContainerTransaction,
-        newTaskId: Int
+        newTaskId: Int,
     ): Int? {
         if (!desktopTasksLimiter.isPresent) return null
-        return desktopTasksLimiter
-            .get()
-            .addAndGetMinimizeTaskChanges(displayId, wct, newTaskId)
+        return desktopTasksLimiter.get().addAndGetMinimizeTaskChanges(displayId, wct, newTaskId)
     }
 
-    private fun addPendingMinimizeTransition(
-        transition: IBinder,
-        taskIdToMinimize: Int,
-    ) {
+    private fun addPendingMinimizeTransition(transition: IBinder, taskIdToMinimize: Int) {
         val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize)
         desktopTasksLimiter.ifPresent {
             it.addPendingMinimizeChange(
                 transition = transition,
                 displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY,
-                taskId = taskIdToMinimize
+                taskId = taskIdToMinimize,
             )
         }
     }
@@ -1914,13 +1921,21 @@
         launchTaskId: Int,
         minimizeTaskId: Int?,
     ) {
-        if (!DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+        if (
+            !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue &&
+                !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
+        ) {
             return
         }
         // TODO b/359523924: pass immersive task here?
         desktopMixedTransitionHandler.addPendingMixedTransition(
             DesktopMixedTransitionHandler.PendingMixedTransition.Launch(
-                transition, launchTaskId, minimizeTaskId, /* exitingImmersiveTask= */ null))
+                transition,
+                launchTaskId,
+                minimizeTaskId,
+                /* exitingImmersiveTask= */ null,
+            )
+        )
     }
 
     fun removeDesktop(displayId: Int) {
@@ -1955,10 +1970,7 @@
      * changes if this transition is enabled.
      */
     @JvmOverloads
-    fun requestSplit(
-        taskInfo: RunningTaskInfo,
-        leftOrTop: Boolean = false
-    ) {
+    fun requestSplit(taskInfo: RunningTaskInfo, leftOrTop: Boolean = false) {
         // If a drag to desktop is in progress, we want to enter split select
         // even if the requesting task is already in split.
         val isDragging = dragToDesktopTransitionHandler.inProgress
@@ -1966,11 +1978,12 @@
         if (shouldRequestSplit) {
             if (isDragging) {
                 releaseVisualIndicator()
-                val cancelState = if (leftOrTop) {
-                    DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT
-                } else {
-                    DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT
-                }
+                val cancelState =
+                    if (leftOrTop) {
+                        DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT
+                    } else {
+                        DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT
+                    }
                 dragToDesktopTransitionHandler.cancelDragToDesktopTransition(cancelState)
             } else {
                 val wct = WindowContainerTransaction()
@@ -1979,7 +1992,7 @@
                     taskInfo,
                     wct,
                     if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT,
-                    taskInfo.configuration.windowConfiguration.bounds
+                    taskInfo.configuration.windowConfiguration.bounds,
                 )
             }
         }
@@ -2014,12 +2027,17 @@
         taskInfo: RunningTaskInfo,
         taskSurface: SurfaceControl,
         inputX: Float,
-        taskBounds: Rect
+        taskBounds: Rect,
     ) {
         if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return
         desktopTilingDecorViewModel.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId)
-        updateVisualIndicator(taskInfo, taskSurface, inputX, taskBounds.top.toFloat(),
-            DragStartState.FROM_FREEFORM)
+        updateVisualIndicator(
+            taskInfo,
+            taskSurface,
+            inputX,
+            taskBounds.top.toFloat(),
+            DragStartState.FROM_FREEFORM,
+        )
     }
 
     fun updateVisualIndicator(
@@ -2027,7 +2045,7 @@
         taskSurface: SurfaceControl?,
         inputX: Float,
         taskTop: Float,
-        dragStartState: DragStartState
+        dragStartState: DragStartState,
     ): DesktopModeVisualIndicator.IndicatorType {
         // If the visual indicator does not exist, create it.
         val indicator =
@@ -2039,7 +2057,7 @@
                     context,
                     taskSurface,
                     rootTaskDisplayAreaOrganizer,
-                    dragStartState
+                    dragStartState,
                 )
         if (visualIndicator == null) visualIndicator = indicator
         return indicator.updateIndicatorType(PointF(inputX, taskTop))
@@ -2054,7 +2072,7 @@
      * @param position position of surface when drag ends.
      * @param inputCoordinate the coordinates of the motion event
      * @param currentDragBounds the current bounds of where the visible task is (might be actual
-     *                          task bounds or just task leash)
+     *   task bounds or just task leash)
      * @param validDragArea the bounds of where the task can be dragged within the display.
      * @param dragStartBounds the bounds of the task before starting dragging.
      */
@@ -2076,21 +2094,29 @@
         val indicator = getVisualIndicator() ?: return
         val indicatorType =
             indicator.updateIndicatorType(
-                PointF(inputCoordinate.x, currentDragBounds.top.toFloat()),
+                PointF(inputCoordinate.x, currentDragBounds.top.toFloat())
             )
         when (indicatorType) {
             IndicatorType.TO_FULLSCREEN_INDICATOR -> {
                 if (DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)) {
                     dragToMaximizeDesktopTask(taskInfo, taskSurface, currentDragBounds, motionEvent)
                 } else {
+                    desktopModeUiEventLogger.log(
+                        taskInfo,
+                        DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_FULL_SCREEN,
+                    )
                     moveToFullscreenWithAnimation(
                         taskInfo,
                         position,
-                        DesktopModeTransitionSource.TASK_DRAG
+                        DesktopModeTransitionSource.TASK_DRAG,
                     )
                 }
             }
             IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+                desktopModeUiEventLogger.log(
+                    taskInfo,
+                    DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_LEFT,
+                )
                 handleSnapResizingTaskOnDrag(
                     taskInfo,
                     SnapPosition.LEFT,
@@ -2102,6 +2128,10 @@
                 )
             }
             IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+                desktopModeUiEventLogger.log(
+                    taskInfo,
+                    DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_RIGHT,
+                )
                 handleSnapResizingTaskOnDrag(
                     taskInfo,
                     SnapPosition.RIGHT,
@@ -2119,7 +2149,7 @@
                 // If task bounds are outside valid drag area, snap them inward
                 DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
                     destinationBounds,
-                    validDragArea
+                    validDragArea,
                 )
 
                 if (destinationBounds == dragStartBounds) {
@@ -2151,8 +2181,9 @@
         }
         // A freeform drag-move ended, remove the indicator immediately.
         releaseVisualIndicator()
-        taskbarDesktopTaskListener
-            ?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(taskInfo.displayId))
+        taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
+            doesAnyTaskRequireTaskbarRounding(taskInfo.displayId)
+        )
     }
 
     /**
@@ -2160,9 +2191,7 @@
      *
      * @param taskInfo the task being dragged.
      */
-    fun onDragPositioningCancelThroughStatusBar(
-        taskInfo: RunningTaskInfo,
-    ) {
+    fun onDragPositioningCancelThroughStatusBar(taskInfo: RunningTaskInfo) {
         interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
         cancelDragToDesktop(taskInfo)
     }
@@ -2186,18 +2215,39 @@
         when (indicatorType) {
             IndicatorType.TO_DESKTOP_INDICATOR -> {
                 // Start a new jank interaction for the drag release to desktop window animation.
-                interactionJankMonitor.begin(taskSurface, context, handler,
-                    CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop")
+                interactionJankMonitor.begin(
+                    taskSurface,
+                    context,
+                    handler,
+                    CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE,
+                    "to_desktop",
+                )
+                desktopModeUiEventLogger.log(
+                    taskInfo,
+                    DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_DESKTOP_MODE,
+                )
                 finalizeDragToDesktop(taskInfo)
             }
             IndicatorType.NO_INDICATOR,
             IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+                desktopModeUiEventLogger.log(
+                    taskInfo,
+                    DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_FULL_SCREEN,
+                )
                 cancelDragToDesktop(taskInfo)
             }
             IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+                desktopModeUiEventLogger.log(
+                    taskInfo,
+                    DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_SPLIT_SCREEN,
+                )
                 requestSplit(taskInfo, leftOrTop = true)
             }
             IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+                desktopModeUiEventLogger.log(
+                    taskInfo,
+                    DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_SPLIT_SCREEN,
+                )
                 requestSplit(taskInfo, leftOrTop = false)
             }
         }
@@ -2238,7 +2288,7 @@
     override fun onUnhandledDrag(
         launchIntent: PendingIntent,
         dragEvent: DragEvent,
-        onFinishCallback: Consumer<Boolean>
+        onFinishCallback: Consumer<Boolean>,
     ): Boolean {
         // TODO(b/320797628): Pass through which display we are dropping onto
         if (!isDesktopModeShowing(DEFAULT_DISPLAY)) {
@@ -2257,22 +2307,27 @@
         //  window will accept a drag event. This way, we can hide the indicator when we won't
         //  be handling the transition here, allowing us to display the indicator accurately.
         //  For now, we create the indicator only on drag end and immediately dispose it.
-        val indicatorType = updateVisualIndicator(taskInfo, dragEvent.dragSurface,
-            dragEvent.x, dragEvent.y,
-            DragStartState.DRAGGED_INTENT)
+        val indicatorType =
+            updateVisualIndicator(
+                taskInfo,
+                dragEvent.dragSurface,
+                dragEvent.x,
+                dragEvent.y,
+                DragStartState.DRAGGED_INTENT,
+            )
         releaseVisualIndicator()
-        val windowingMode = when (indicatorType) {
-            IndicatorType.TO_FULLSCREEN_INDICATOR -> {
-                WINDOWING_MODE_FULLSCREEN
+        val windowingMode =
+            when (indicatorType) {
+                IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+                    WINDOWING_MODE_FULLSCREEN
+                }
+                IndicatorType.TO_SPLIT_LEFT_INDICATOR,
+                IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
+                IndicatorType.TO_DESKTOP_INDICATOR -> {
+                    WINDOWING_MODE_FREEFORM
+                }
+                else -> error("Invalid indicator type: $indicatorType")
             }
-            IndicatorType.TO_SPLIT_LEFT_INDICATOR,
-            IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
-            IndicatorType.TO_DESKTOP_INDICATOR
-            -> {
-                WINDOWING_MODE_FREEFORM
-            }
-            else -> error("Invalid indicator type: $indicatorType")
-        }
         val displayLayout = displayController.getDisplayLayout(DEFAULT_DISPLAY) ?: return false
         val newWindowBounds = Rect()
         when (indicatorType) {
@@ -2281,7 +2336,7 @@
                 newWindowBounds.set(calculateDefaultDesktopTaskBounds(displayLayout))
                 newWindowBounds.offsetTo(
                     dragEvent.x.toInt() - (newWindowBounds.width() / 2),
-                    dragEvent.y.toInt()
+                    dragEvent.y.toInt(),
                 )
             }
             IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
@@ -2329,7 +2384,9 @@
 
     // TODO(b/366397912): Support full multi-user mode in Windowing.
     override fun onUserChanged(newUserId: Int, userContext: Context) {
+        logV("onUserChanged previousUserId=%d, newUserId=%d", userId, newUserId)
         userId = newUserId
+        taskRepository = userRepositories.getProfile(userId)
         desktopTilingDecorViewModel.onUserChange()
     }
 
@@ -2338,12 +2395,15 @@
         if (!Flags.enableFullyImmersiveInDesktop()) return
         val inImmersive = taskRepository.isTaskInFullImmersiveState(taskInfo.taskId)
         val requestingImmersive = taskInfo.requestingImmersive
-        if (inImmersive && !requestingImmersive
-            && !RecentsTransitionStateListener.isRunning(recentsTransitionState)) {
+        if (
+            inImmersive &&
+                !requestingImmersive &&
+                !RecentsTransitionStateListener.isRunning(recentsTransitionState)
+        ) {
             // Exit immersive if the app is no longer requesting it.
-            exitDesktopTaskFromFullImmersive(
+            desktopImmersiveController.moveTaskToNonImmersive(
                 taskInfo,
-                DesktopImmersiveController.ExitReason.APP_NOT_IMMERSIVE
+                DesktopImmersiveController.ExitReason.APP_NOT_IMMERSIVE,
             )
         }
     }
@@ -2352,6 +2412,7 @@
         val innerPrefix = "$prefix  "
         pw.println("${prefix}DesktopTasksController")
         DesktopModeStatus.dump(pw, innerPrefix, context)
+        pw.println("${prefix}userId=$userId")
         taskRepository.dump(pw, innerPrefix)
     }
 
@@ -2360,7 +2421,7 @@
     private inner class DesktopModeImpl : DesktopMode {
         override fun addVisibleTasksListener(
             listener: VisibleTasksListener,
-            callbackExecutor: Executor
+            callbackExecutor: Executor,
         ) {
             mainExecutor.execute {
                 this@DesktopTasksController.addVisibleTasksListener(listener, callbackExecutor)
@@ -2369,7 +2430,7 @@
 
         override fun addDesktopGestureExclusionRegionListener(
             listener: Consumer<Region>,
-            callbackExecutor: Executor
+            callbackExecutor: Executor,
         ) {
             mainExecutor.execute {
                 this@DesktopTasksController.setTaskRegionListener(listener, callbackExecutor)
@@ -2378,8 +2439,9 @@
 
         override fun moveFocusedTaskToDesktop(
             displayId: Int,
-            transitionSource: DesktopModeTransitionSource
+            transitionSource: DesktopModeTransitionSource,
         ) {
+            logV("moveFocusedTaskToDesktop")
             mainExecutor.execute {
                 this@DesktopTasksController.moveFocusedTaskToDesktop(displayId, transitionSource)
             }
@@ -2387,14 +2449,16 @@
 
         override fun moveFocusedTaskToFullscreen(
             displayId: Int,
-            transitionSource: DesktopModeTransitionSource
+            transitionSource: DesktopModeTransitionSource,
         ) {
+            logV("moveFocusedTaskToFullscreen")
             mainExecutor.execute {
                 this@DesktopTasksController.enterFullscreen(displayId, transitionSource)
             }
         }
 
         override fun moveFocusedTaskToStageSplit(displayId: Int, leftOrTop: Boolean) {
+            logV("moveFocusedTaskToStageSplit")
             mainExecutor.execute { this@DesktopTasksController.enterSplit(displayId, leftOrTop) }
         }
     }
@@ -2414,7 +2478,7 @@
                         WM_SHELL_DESKTOP_MODE,
                         "IDesktopModeImpl: onVisibilityChanged display=%d visible=%d",
                         displayId,
-                        visibleTasksCount
+                        visibleTasksCount,
                     )
                     remoteListener.call { l ->
                         l.onTasksVisibilityChanged(displayId, visibleTasksCount)
@@ -2423,21 +2487,22 @@
             }
 
         private val taskbarDesktopTaskListener: TaskbarDesktopTaskListener =
-                object : TaskbarDesktopTaskListener {
-                    override fun onTaskbarCornerRoundingUpdate(
-                        hasTasksRequiringTaskbarRounding: Boolean) {
-                        ProtoLog.v(
-                                WM_SHELL_DESKTOP_MODE,
-                                "IDesktopModeImpl: onTaskbarCornerRoundingUpdate " +
-                                        "doesAnyTaskRequireTaskbarRounding=%s",
-                                hasTasksRequiringTaskbarRounding
-                        )
+            object : TaskbarDesktopTaskListener {
+                override fun onTaskbarCornerRoundingUpdate(
+                    hasTasksRequiringTaskbarRounding: Boolean
+                ) {
+                    ProtoLog.v(
+                        WM_SHELL_DESKTOP_MODE,
+                        "IDesktopModeImpl: onTaskbarCornerRoundingUpdate " +
+                            "doesAnyTaskRequireTaskbarRounding=%s",
+                        hasTasksRequiringTaskbarRounding,
+                    )
 
-                        remoteListener.call { l ->
-                            l.onTaskbarCornerRoundingUpdate(hasTasksRequiringTaskbarRounding)
-                        }
+                    remoteListener.call { l ->
+                        l.onTaskbarCornerRoundingUpdate(hasTasksRequiringTaskbarRounding)
                     }
                 }
+            }
 
         private val desktopModeEntryExitTransitionListener: DesktopModeEntryExitTransitionListener =
             object : DesktopModeEntryExitTransitionListener {
@@ -2445,18 +2510,22 @@
                     ProtoLog.v(
                         WM_SHELL_DESKTOP_MODE,
                         "IDesktopModeImpl: onEnterDesktopModeTransitionStarted transitionTime=%s",
-                        transitionDuration
+                        transitionDuration,
                     )
-                    remoteListener.call { l -> l.onEnterDesktopModeTransitionStarted(transitionDuration) }
+                    remoteListener.call { l ->
+                        l.onEnterDesktopModeTransitionStarted(transitionDuration)
+                    }
                 }
 
                 override fun onExitDesktopModeTransitionStarted(transitionDuration: Int) {
                     ProtoLog.v(
                         WM_SHELL_DESKTOP_MODE,
                         "IDesktopModeImpl: onExitDesktopModeTransitionStarted transitionTime=%s",
-                        transitionDuration
+                        transitionDuration,
                     )
-                    remoteListener.call { l -> l.onExitDesktopModeTransitionStarted(transitionDuration) }
+                    remoteListener.call { l ->
+                        l.onExitDesktopModeTransitionStarted(transitionDuration)
+                    }
                 }
             }
 
@@ -2478,7 +2547,7 @@
                             c.taskbarDesktopTaskListener = null
                             c.desktopModeEnterExitTransitionListener = null
                         }
-                    }
+                    },
                 )
         }
 
@@ -2505,8 +2574,10 @@
         }
 
         override fun hideStashedDesktopApps(displayId: Int) {
-            ProtoLog.w(WM_SHELL_DESKTOP_MODE,
-                "IDesktopModeImpl: hideStashedDesktopApps is deprecated")
+            ProtoLog.w(
+                WM_SHELL_DESKTOP_MODE,
+                "IDesktopModeImpl: hideStashedDesktopApps is deprecated",
+            )
         }
 
         override fun getVisibleTaskCount(displayId: Int): Int {
@@ -2515,16 +2586,14 @@
                 controller,
                 "visibleTaskCount",
                 { controller -> result[0] = controller.visibleTaskCount(displayId) },
-                true /* blocking */
+                true, /* blocking */
             )
             return result[0]
         }
 
         override fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
-            executeRemoteCallWithTaskPermission(
-                controller,
-                "onDesktopSplitSelectAnimComplete"
-            ) { c ->
+            executeRemoteCallWithTaskPermission(controller, "onDesktopSplitSelectAnimComplete") { c
+                ->
                 c.onDesktopSplitSelectAnimComplete(taskInfo)
             }
         }
@@ -2558,9 +2627,11 @@
     private fun logV(msg: String, vararg arguments: Any?) {
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
     }
+
     private fun logD(msg: String, vararg arguments: Any?) {
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
     }
+
     private fun logW(msg: String, vararg arguments: Any?) {
         ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
     }
@@ -2570,6 +2641,10 @@
         val DESKTOP_MODE_INITIAL_BOUNDS_SCALE =
             SystemProperties.getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
 
+        // Timeout used for CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD, this is longer than the
+        // default timeout to avoid timing out in the middle of a drag action.
+        private val APP_HANDLE_DRAG_HOLD_CUJ_TIMEOUT_MS: Long = TimeUnit.SECONDS.toMillis(10L)
+
         private const val TAG = "DesktopTasksController"
     }
 
@@ -2595,6 +2670,6 @@
     /** The positions on a screen that a task can snap to. */
     enum class SnapPosition {
         RIGHT,
-        LEFT
+        LEFT,
     }
 }
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 77af627..635078e 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
@@ -16,14 +16,15 @@
 
 package com.android.wm.shell.desktopmode
 
+import android.app.ActivityManager
 import android.content.Context
 import android.os.Handler
 import android.os.IBinder
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_TO_BACK
+import android.window.DesktopModeFlags
 import android.window.TransitionInfo
 import android.window.WindowContainerTransaction
-import android.window.DesktopModeFlags
 import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
 import com.android.internal.jank.InteractionJankMonitor
@@ -31,6 +32,7 @@
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.sysui.UserChangeListener
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TransitionObserver
 
@@ -38,35 +40,37 @@
  * Limits the number of tasks shown in Desktop Mode.
  *
  * This class should only be used if
- * [android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT]
- * is enabled and [maxTasksLimit] is strictly greater than 0.
+ * [android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT] is enabled and
+ * [maxTasksLimit] is strictly greater than 0.
  */
-class DesktopTasksLimiter (
-        transitions: Transitions,
-        private val taskRepository: DesktopRepository,
-        private val shellTaskOrganizer: ShellTaskOrganizer,
-        private val maxTasksLimit: Int,
-        private val interactionJankMonitor: InteractionJankMonitor,
-        private val context: Context,
-        @ShellMainThread private val handler: Handler,
+class DesktopTasksLimiter(
+    transitions: Transitions,
+    private val desktopUserRepositories: DesktopUserRepositories,
+    private val shellTaskOrganizer: ShellTaskOrganizer,
+    private val maxTasksLimit: Int,
+    private val interactionJankMonitor: InteractionJankMonitor,
+    private val context: Context,
+    @ShellMainThread private val handler: Handler,
 ) {
     private val minimizeTransitionObserver = MinimizeTransitionObserver()
-    @VisibleForTesting
-    val leftoverMinimizedTasksRemover = LeftoverMinimizedTasksRemover()
+    @VisibleForTesting val leftoverMinimizedTasksRemover = LeftoverMinimizedTasksRemover()
+
+    private var userId: Int
 
     init {
         require(maxTasksLimit > 0) {
             "DesktopTasksLimiter: maxTasksLimit should be greater than 0. Current value: $maxTasksLimit."
         }
         transitions.registerObserver(minimizeTransitionObserver)
-        taskRepository.addActiveTaskListener(leftoverMinimizedTasksRemover)
+        userId = ActivityManager.getCurrentUser()
+        desktopUserRepositories.current.addActiveTaskListener(leftoverMinimizedTasksRemover)
         logV("Starting limiter with a maximum of %d tasks", maxTasksLimit)
     }
 
     private data class TaskDetails(
         val displayId: Int,
         val taskId: Int,
-        var transitionInfo: TransitionInfo?
+        var transitionInfo: TransitionInfo?,
     )
 
     // TODO(b/333018485): replace this observer when implementing the minimize-animation
@@ -82,8 +86,9 @@
             transition: IBinder,
             info: TransitionInfo,
             startTransaction: SurfaceControl.Transaction,
-            finishTransaction: SurfaceControl.Transaction
+            finishTransaction: SurfaceControl.Transaction,
         ) {
+            val taskRepository = desktopUserRepositories.current
             val taskToMinimize = pendingTransitionTokensAndTasks.remove(transition) ?: return
             if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return
             if (!isTaskReadyForMinimize(info, taskToMinimize)) {
@@ -94,12 +99,13 @@
             activeTransitionTokensAndTasks[transition] = taskToMinimize
 
             // Save current bounds before minimizing in case we need to restore to it later.
-            val boundsBeforeMinimize = info.changes.find { change ->
-                change.taskInfo?.taskId == taskToMinimize.taskId }?.startAbsBounds
+            val boundsBeforeMinimize =
+                info.changes
+                    .find { change -> change.taskInfo?.taskId == taskToMinimize.taskId }
+                    ?.startAbsBounds
             taskRepository.saveBoundsBeforeMinimize(taskToMinimize.taskId, boundsBeforeMinimize)
 
-            this@DesktopTasksLimiter.minimizeTask(
-                    taskToMinimize.displayId, taskToMinimize.taskId)
+            this@DesktopTasksLimiter.minimizeTask(taskToMinimize.displayId, taskToMinimize.taskId)
         }
 
         /**
@@ -110,10 +116,11 @@
          */
         private fun isTaskReadyForMinimize(
             info: TransitionInfo,
-            taskDetails: TaskDetails
+            taskDetails: TaskDetails,
         ): Boolean {
-            val taskChange = info.changes.find { change ->
-                change.taskInfo?.taskId == taskDetails.taskId }
+            val taskChange =
+                info.changes.find { change -> change.taskInfo?.taskId == taskDetails.taskId }
+            val taskRepository = desktopUserRepositories.current
             if (taskChange == null) return !taskRepository.isVisibleTask(taskDetails.taskId)
             return taskChange.mode == TRANSIT_TO_BACK
         }
@@ -123,8 +130,10 @@
             if (mActiveTaskDetails != null && mActiveTaskDetails.transitionInfo != null) {
                 // Begin minimize window CUJ instrumentation.
                 interactionJankMonitor.begin(
-                    mActiveTaskDetails.transitionInfo?.rootLeash, context, handler,
-                    CUJ_DESKTOP_MODE_MINIMIZE_WINDOW
+                    mActiveTaskDetails.transitionInfo?.rootLeash,
+                    context,
+                    handler,
+                    CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
                 )
             }
         }
@@ -151,7 +160,8 @@
     }
 
     @VisibleForTesting
-    inner class LeftoverMinimizedTasksRemover : DesktopRepository.ActiveTasksListener {
+    inner class LeftoverMinimizedTasksRemover :
+        DesktopRepository.ActiveTasksListener, UserChangeListener {
         override fun onActiveTasksChanged(displayId: Int) {
             // If back navigation is enabled, we shouldn't remove the leftover tasks
             if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
@@ -161,6 +171,7 @@
         }
 
         fun removeLeftoverMinimizedTasks(displayId: Int, wct: WindowContainerTransaction) {
+            val taskRepository = desktopUserRepositories.current
             if (taskRepository.getExpandedTasksOrdered(displayId).isNotEmpty()) return
             val remainingMinimizedTasks = taskRepository.getMinimizedTasks(displayId)
             if (remainingMinimizedTasks.isEmpty()) return
@@ -173,39 +184,46 @@
                 }
             }
         }
+
+        override fun onUserChanged(newUserId: Int, userContext: Context) {
+            // Removes active task listener for the previous repository
+            desktopUserRepositories.getProfile(userId).removeActiveTasksListener(this)
+
+            // Sets active listener for the current repository.
+            userId = newUserId
+            desktopUserRepositories.getProfile(newUserId).addActiveTaskListener(this)
+        }
     }
 
     /**
      * Mark task with [taskId] on [displayId] as minimized.
      *
-     * This should be after the corresponding transition has finished so we don't
-     * minimize the task if the transition fails.
+     * This should be after the corresponding transition has finished so we don't minimize the task
+     * if the transition fails.
      */
     private fun minimizeTask(displayId: Int, taskId: Int) {
         logV("Minimize taskId=%d, displayId=%d", taskId, displayId)
+        val taskRepository = desktopUserRepositories.current
         taskRepository.minimizeTask(displayId, taskId)
     }
 
     /**
-     * Adds a minimize-transition to [wct] if adding [newFrontTaskInfo] crosses task
-     * limit, returning the task to minimize.
+     * Adds a minimize-transition to [wct] if adding [newFrontTaskInfo] crosses task limit,
+     * returning the task to minimize.
      */
     fun addAndGetMinimizeTaskChanges(
-            displayId: Int,
-            wct: WindowContainerTransaction,
-            newFrontTaskId: Int,
+        displayId: Int,
+        wct: WindowContainerTransaction,
+        newFrontTaskId: Int,
     ): Int? {
         logV("addAndGetMinimizeTaskChanges, newFrontTask=%d", newFrontTaskId)
-
+        val taskRepository = desktopUserRepositories.current
         val taskIdToMinimize =
-            getTaskIdToMinimize(
-                taskRepository.getExpandedTasksOrdered(displayId),
-                newFrontTaskId
-            )
+            getTaskIdToMinimize(taskRepository.getExpandedTasksOrdered(displayId), newFrontTaskId)
         // If it's a running task, reorder it to back.
-        taskIdToMinimize?.let { shellTaskOrganizer.getRunningTaskInfo(it) }?.let {
-            wct.reorder(it.token, false /* onTop */)
-        }
+        taskIdToMinimize
+            ?.let { shellTaskOrganizer.getRunningTaskInfo(it) }
+            ?.let { wct.reorder(it.token, false /* onTop */) }
         return taskIdToMinimize
     }
 
@@ -215,20 +233,19 @@
      */
     fun addPendingMinimizeChange(transition: IBinder, displayId: Int, taskId: Int) {
         minimizeTransitionObserver.addPendingTransitionToken(
-                transition, TaskDetails(displayId, taskId, transitionInfo = null))
+            transition,
+            TaskDetails(displayId, taskId, transitionInfo = null),
+        )
     }
 
     /**
-     * Returns the minimized task from the list of visible tasks ordered from front to back with
-     * the new task placed in front of other tasks.
+     * Returns the minimized task from the list of visible tasks ordered from front to back with the
+     * new task placed in front of other tasks.
      */
-    fun getTaskIdToMinimize(
-            visibleOrderedTasks: List<Int>,
-            newTaskIdInFront: Int? = null
-    ): Int? {
+    fun getTaskIdToMinimize(visibleOrderedTasks: List<Int>, newTaskIdInFront: Int? = null): Int? {
         return getTaskIdToMinimize(
-            createOrderedTaskListWithGivenTaskInFront(
-                visibleOrderedTasks, newTaskIdInFront))
+            createOrderedTaskListWithGivenTaskInFront(visibleOrderedTasks, newTaskIdInFront)
+        )
     }
 
     /** Returns the Task to minimize given a list of visible tasks ordered from front to back. */
@@ -242,16 +259,16 @@
     }
 
     private fun createOrderedTaskListWithGivenTaskInFront(
-            existingTaskIdsOrderedFrontToBack: List<Int>,
-            newTaskId: Int?
+        existingTaskIdsOrderedFrontToBack: List<Int>,
+        newTaskId: Int?,
     ): List<Int> {
         return if (newTaskId == null) existingTaskIdsOrderedFrontToBack
-        else listOf(newTaskId) +
+        else
+            listOf(newTaskId) +
                 existingTaskIdsOrderedFrontToBack.filter { taskId -> taskId != newTaskId }
     }
 
-    @VisibleForTesting
-    fun getTransitionObserver(): TransitionObserver = minimizeTransitionObserver
+    @VisibleForTesting fun getTransitionObserver(): TransitionObserver = minimizeTransitionObserver
 
     private fun logV(msg: String, vararg arguments: Any?) {
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
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 c39c715..9625b71 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,6 +16,7 @@
 
 package com.android.wm.shell.desktopmode
 
+import android.app.ActivityManager
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.content.Context
 import android.os.IBinder
@@ -23,12 +24,13 @@
 import android.view.WindowManager
 import android.view.WindowManager.TRANSIT_CLOSE
 import android.view.WindowManager.TRANSIT_TO_BACK
-import android.window.TransitionInfo
-import android.window.WindowContainerTransaction
 import android.window.DesktopModeFlags
 import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+import android.window.TransitionInfo
+import android.window.WindowContainerTransaction
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.back.BackAnimationController
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktopModeTransition
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.shared.TransitionUtil
@@ -43,19 +45,22 @@
  */
 class DesktopTasksTransitionObserver(
     private val context: Context,
-    private val desktopRepository: DesktopRepository,
+    private val desktopUserRepositories: DesktopUserRepositories,
     private val transitions: Transitions,
     private val shellTaskOrganizer: ShellTaskOrganizer,
     private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
-    shellInit: ShellInit
+    private val backAnimationController: BackAnimationController,
+    shellInit: ShellInit,
 ) : Transitions.TransitionObserver {
 
     private var transitionToCloseWallpaper: IBinder? = null
+    private var currentProfileId: Int
 
     init {
         if (DesktopModeStatus.canEnterDesktopMode(context)) {
             shellInit.addInitCallback(::onInit, this)
         }
+        currentProfileId = ActivityManager.getCurrentUser()
     }
 
     fun onInit() {
@@ -67,7 +72,7 @@
         transition: IBinder,
         info: TransitionInfo,
         startTransaction: SurfaceControl.Transaction,
-        finishTransaction: SurfaceControl.Transaction
+        finishTransaction: SurfaceControl.Transaction,
     ) {
         // TODO: b/332682201 Update repository state
         updateWallpaperToken(info)
@@ -89,8 +94,11 @@
             val taskInfo = change.taskInfo
             if (taskInfo == null || taskInfo.taskId == -1) continue
 
-            if (desktopRepository.isActiveTask(taskInfo.taskId) &&
-                taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) {
+            val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
+            if (
+                desktopRepository.isActiveTask(taskInfo.taskId) &&
+                    taskInfo.windowingMode != WINDOWING_MODE_FREEFORM
+            ) {
                 desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
             }
         }
@@ -105,35 +113,103 @@
                 if (taskInfo == null || taskInfo.taskId == -1) {
                     continue
                 }
-
+                val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
                 val visibleTaskCount = desktopRepository.getVisibleTaskCount(taskInfo.displayId)
-                if (visibleTaskCount > 0 &&
-                    change.mode == TRANSIT_TO_BACK &&
-                    taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+                if (
+                    visibleTaskCount > 0 &&
+                        change.mode == TRANSIT_TO_BACK &&
+                        taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
+                ) {
                     desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
                     desktopMixedTransitionHandler.addPendingMixedTransition(
                         DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
-                            transition, taskInfo.taskId, visibleTaskCount == 1))
+                            transition,
+                            taskInfo.taskId,
+                            visibleTaskCount == 1,
+                        )
+                    )
                 }
             }
+        } else if (info.type == TRANSIT_CLOSE) {
+            // In some cases app will be closing as a result of back navigation but we would like
+            // to minimize. Mark the task closing as minimized.
+            var hasWallpaperClosing = false
+            var minimizingTask: Int? = null
+            for (change in info.changes) {
+                val taskInfo = change.taskInfo
+                if (taskInfo == null || taskInfo.taskId == -1) continue
+                if (change.mode != TRANSIT_CLOSE) continue
+
+                if (minimizingTask == null) {
+                    minimizingTask = getMinimizingTaskForClosingTransition(taskInfo)
+                }
+
+                if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) {
+                    hasWallpaperClosing = true
+                }
+            }
+
+            if (minimizingTask == null) return
+            // If the transition has wallpaper closing, it means we are moving out of desktop.
+            desktopMixedTransitionHandler.addPendingMixedTransition(
+                DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
+                    transition,
+                    minimizingTask,
+                    isLastTask = hasWallpaperClosing,
+                )
+            )
         }
     }
 
+    /**
+     * Given this a closing task in a closing transition, a task is assumed to be closed by back
+     * navigation if:
+     * 1) Desktop mode is visible.
+     * 2) Task is in freeform.
+     * 3) Task is the latest task that the back gesture is triggered on.
+     * 4) It's not marked as a closing task as a result of closing it by the app header.
+     *
+     * This doesn't necessarily mean all the cases are because of back navigation but those cases
+     * will be rare. E.g. triggering back navigation on an app that pops up a close dialog, and
+     * closing it will minimize it here.
+     */
+    private fun getMinimizingTaskForClosingTransition(
+        taskInfo: ActivityManager.RunningTaskInfo
+    ): Int? {
+        val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
+        val visibleTaskCount = desktopRepository.getVisibleTaskCount(taskInfo.displayId)
+        if (
+            visibleTaskCount > 0 &&
+                taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
+                backAnimationController.latestTriggerBackTask == taskInfo.taskId &&
+                !desktopRepository.isClosingTask(taskInfo.taskId)
+        ) {
+            desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+            return taskInfo.taskId
+        }
+        return null
+    }
+
     private fun removeWallpaperOnLastTaskClosingIfNeeded(
         transition: IBinder,
-        info: TransitionInfo
+        info: TransitionInfo,
     ) {
+        // TODO: 380868195 - Smooth animation for wallpaper activity closing just by itself
         for (change in info.changes) {
             val taskInfo = change.taskInfo
             if (taskInfo == null || taskInfo.taskId == -1) {
                 continue
             }
 
-            if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 1 &&
-                change.mode == TRANSIT_CLOSE &&
-                taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
-                desktopRepository.wallpaperActivityToken != null) {
+            val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
+            if (
+                desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 0 &&
+                    change.mode == TRANSIT_CLOSE &&
+                    taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
+                    desktopRepository.wallpaperActivityToken != null
+            ) {
                 transitionToCloseWallpaper = transition
+                currentProfileId = taskInfo.userId
             }
         }
     }
@@ -150,11 +226,13 @@
         // TODO: b/332682201 Update repository state
         if (transitionToCloseWallpaper == transition) {
             // TODO: b/362469671 - Handle merging the animation when desktop is also closing.
+            val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
             desktopRepository.wallpaperActivityToken?.let { wallpaperActivityToken ->
                 transitions.startTransition(
                     TRANSIT_CLOSE,
                     WindowContainerTransaction().removeTask(wallpaperActivityToken),
-                    null)
+                    null,
+                )
             }
             transitionToCloseWallpaper = null
         }
@@ -167,6 +245,7 @@
         info.changes.forEach { change ->
             change.taskInfo?.let { taskInfo ->
                 if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) {
+                    val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
                     when (change.mode) {
                         WindowManager.TRANSIT_OPEN -> {
                             desktopRepository.wallpaperActivityToken = taskInfo.token
@@ -175,10 +254,10 @@
                             // task.
                             shellTaskOrganizer.applyTransaction(
                                 WindowContainerTransaction()
-                                    .setTaskTrimmableFromRecents(taskInfo.token, false))
+                                    .setTaskTrimmableFromRecents(taskInfo.token, false)
+                            )
                         }
-                        TRANSIT_CLOSE ->
-                            desktopRepository.wallpaperActivityToken = null
+                        TRANSIT_CLOSE -> desktopRepository.wallpaperActivityToken = null
                         else -> {}
                     }
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
new file mode 100644
index 0000000..e5f5283
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.app.ActivityManager
+import android.content.Context
+import android.content.pm.UserInfo
+import android.os.UserManager
+import android.util.SparseArray
+import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.sysui.UserChangeListener
+import kotlinx.coroutines.CoroutineScope
+
+/** Manages per-user DesktopRepository instances. */
+class DesktopUserRepositories(
+    context: Context,
+    shellInit: ShellInit,
+    private val persistentRepository: DesktopPersistentRepository,
+    private val repositoryInitializer: DesktopRepositoryInitializer,
+    @ShellMainThread private val mainCoroutineScope: CoroutineScope,
+    userManager: UserManager,
+) : UserChangeListener {
+    private var userId: Int
+    private var userIdToProfileIdsMap: MutableMap<Int, List<Int>> = mutableMapOf()
+
+    // TODO(b/357060209): Add caching for this logic to improve efficiency.
+    val current: DesktopRepository
+        get() = desktopRepoByUserId.getOrCreate(userId)
+
+    private val desktopRepoByUserId =
+        object : SparseArray<DesktopRepository>() {
+            /** Gets [DesktopRepository] for existing [userId] or creates a new one. */
+            fun getOrCreate(userId: Int): DesktopRepository =
+                this[userId]
+                    ?: DesktopRepository(persistentRepository, mainCoroutineScope, userId).also {
+                        this[userId] = it
+                    }
+        }
+
+    init {
+        userId = ActivityManager.getCurrentUser()
+        if (DesktopModeStatus.canEnterDesktopMode(context)) {
+            shellInit.addInitCallback(::initRepoFromPersistentStorage, this)
+        }
+        if (Flags.enableDesktopWindowingHsum()) {
+            userIdToProfileIdsMap[userId] = userManager.getProfiles(userId).map { it.id }
+        }
+    }
+
+    private fun initRepoFromPersistentStorage() {
+        repositoryInitializer.initialize(this)
+    }
+
+    /** Returns [DesktopRepository] for the parent user id. */
+    fun getProfile(profileId: Int): DesktopRepository {
+        if (Flags.enableDesktopWindowingHsum()) {
+            for ((uid, profileIds) in userIdToProfileIdsMap) {
+                if (profileId in profileIds) {
+                    return desktopRepoByUserId.getOrCreate(uid)
+                }
+            }
+        }
+        return desktopRepoByUserId.getOrCreate(profileId)
+    }
+
+    override fun onUserChanged(newUserId: Int, userContext: Context) {
+        logD("onUserChanged previousUserId=%d, newUserId=%d", userId, newUserId)
+        userId = newUserId
+    }
+
+    override fun onUserProfilesChanged(profiles: MutableList<UserInfo>) {
+        logD("onUserProfilesChanged profiles=%s", profiles.toString())
+        if (Flags.enableDesktopWindowingHsum()) {
+            // TODO(b/366397912): Remove all persisted profile data when the profile changes.
+            userIdToProfileIdsMap[userId] = profiles.map { it.id }
+        }
+    }
+
+    private fun logD(msg: String, vararg arguments: Any?) {
+        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+    }
+
+    companion object {
+        private const val TAG = "DesktopUserRepositories"
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
index e835b2f..fd39becc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt
@@ -17,12 +17,10 @@
 package com.android.wm.shell.desktopmode
 
 import android.app.Activity
-import android.app.ActivityManager
+import android.app.TaskInfo
 import android.content.ComponentName
 import android.os.Bundle
 import android.view.WindowManager
-import com.android.internal.protolog.ProtoLog
-import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 
 /**
  * A transparent activity used in the desktop mode to show the wallpaper under the freeform windows.
@@ -42,15 +40,15 @@
 
     companion object {
         private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
-        private val wallpaperActivityComponent =
+        @JvmStatic
+        val wallpaperActivityComponent =
             ComponentName(SYSTEM_UI_PACKAGE_NAME, DesktopWallpaperActivity::class.java.name)
 
         @JvmStatic
-        fun isWallpaperTask(taskInfo: ActivityManager.RunningTaskInfo) =
+        fun isWallpaperTask(taskInfo: TaskInfo) =
             taskInfo.baseIntent.component?.let(::isWallpaperComponent) ?: false
 
         @JvmStatic
-        fun isWallpaperComponent(component: ComponentName) =
-            component == wallpaperActivityComponent
+        fun isWallpaperComponent(component: ComponentName) = component == wallpaperActivityComponent
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index d7d5519..11110f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -23,6 +23,7 @@
 import android.os.IBinder
 import android.os.SystemClock
 import android.os.SystemProperties
+import android.os.UserHandle
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CLOSE
 import android.window.TransitionInfo
@@ -109,13 +110,13 @@
      * after one of the "end" or "cancel" transitions is merged into this transition.
      */
     fun startDragToDesktopTransition(
-        taskId: Int,
+        taskInfo: RunningTaskInfo,
         dragToDesktopAnimator: MoveToDesktopAnimator,
     ) {
         if (inProgress) {
             ProtoLog.v(
                 ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
-                "DragToDesktop: Drag to desktop transition already in progress."
+                "DragToDesktop: Drag to desktop transition already in progress.",
             )
             return
         }
@@ -127,13 +128,15 @@
                 pendingIntentCreatorBackgroundActivityStartMode =
                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
             }
+        val taskUser = UserHandle.of(taskInfo.userId)
         val pendingIntent =
-            PendingIntent.getActivity(
-                context,
+            PendingIntent.getActivityAsUser(
+                context.createContextAsUser(taskUser, /* flags= */ 0),
                 0 /* requestCode */,
                 launchHomeIntent,
                 FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT,
-                options.toBundle()
+                options.toBundle(),
+                taskUser,
             )
         val wct = WindowContainerTransaction()
         wct.sendPendingIntent(pendingIntent, launchHomeIntent, Bundle())
@@ -141,21 +144,21 @@
             transitions.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
 
         transitionState =
-            if (isSplitTask(taskId)) {
+            if (isSplitTask(taskInfo.taskId)) {
                 val otherTask =
-                    getOtherSplitTask(taskId)
+                    getOtherSplitTask(taskInfo.taskId)
                         ?: throw IllegalStateException("Expected split task to have a counterpart.")
                 TransitionState.FromSplit(
-                    draggedTaskId = taskId,
+                    draggedTaskId = taskInfo.taskId,
                     dragAnimator = dragToDesktopAnimator,
                     startTransitionToken = startTransitionToken,
-                    otherSplitTask = otherTask
+                    otherSplitTask = otherTask,
                 )
             } else {
                 TransitionState.FromFullscreen(
-                    draggedTaskId = taskId,
+                    draggedTaskId = taskInfo.taskId,
                     dragAnimator = dragToDesktopAnimator,
-                    startTransitionToken = startTransitionToken
+                    startTransitionToken = startTransitionToken,
                 )
             }
     }
@@ -244,7 +247,7 @@
     /** Calculate the bounds of a scaled task, then use those bounds to request split select. */
     private fun requestSplitFromScaledTask(
         @SplitPosition splitPosition: Int,
-        wct: WindowContainerTransaction
+        wct: WindowContainerTransaction,
     ) {
         val state = requireTransitionState()
         val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo")
@@ -259,7 +262,7 @@
                 dragPosition.x.toInt(),
                 dragPosition.y.toInt(),
                 (dragPosition.x + scaledWidth).toInt(),
-                (dragPosition.y + scaledHeight).toInt()
+                (dragPosition.y + scaledHeight).toInt(),
             )
         requestSplitSelect(wct, taskInfo, splitPosition, animatedTaskBounds)
     }
@@ -268,14 +271,14 @@
         wct: WindowContainerTransaction,
         taskInfo: RunningTaskInfo,
         @SplitPosition splitPosition: Int,
-        taskBounds: Rect = Rect(taskInfo.configuration.windowConfiguration.bounds)
+        taskBounds: Rect = Rect(taskInfo.configuration.windowConfiguration.bounds),
     ) {
         // Prepare to exit split in order to enter split select.
         if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
             splitScreenController.prepareExitSplitScreen(
                 wct,
                 splitScreenController.getStageOfTask(taskInfo.taskId),
-                SplitScreenController.EXIT_REASON_DESKTOP_MODE
+                SplitScreenController.EXIT_REASON_DESKTOP_MODE,
             )
             splitScreenController.transitionHandler.onSplitToDesktop()
         }
@@ -289,7 +292,7 @@
         info: TransitionInfo,
         startTransaction: SurfaceControl.Transaction,
         finishTransaction: SurfaceControl.Transaction,
-        finishCallback: Transitions.TransitionFinishCallback
+        finishCallback: Transitions.TransitionFinishCallback,
     ): Boolean {
         val state = requireTransitionState()
 
@@ -387,7 +390,7 @@
                     taskDisplayAreaOrganizer.reparentToDisplayArea(
                         change.endDisplayId,
                         change.leash,
-                        startTransaction
+                        startTransaction,
                     )
                     val bounds = change.endAbsBounds
                     startTransaction.apply {
@@ -454,7 +457,7 @@
         info: TransitionInfo,
         t: SurfaceControl.Transaction,
         mergeTarget: IBinder,
-        finishCallback: Transitions.TransitionFinishCallback
+        finishCallback: Transitions.TransitionFinishCallback,
     ) {
         val state = requireTransitionState()
         // We don't want to merge the split select animation if that's what we requested.
@@ -483,7 +486,7 @@
             setupEndDragToDesktop(
                 info,
                 startTransaction = t,
-                finishTransaction = startTransactionFinishT
+                finishTransaction = startTransactionFinishT,
             )
             // Call finishCallback to merge animation before startTransitionFinishCb is called
             finishCallback.onTransitionFinished(null /* wct */)
@@ -503,7 +506,7 @@
     protected open fun setupEndDragToDesktop(
         info: TransitionInfo,
         startTransaction: SurfaceControl.Transaction,
-        finishTransaction: SurfaceControl.Transaction
+        finishTransaction: SurfaceControl.Transaction,
     ) {
         val state = requireTransitionState()
         val freeformTaskChanges = mutableListOf<Change>()
@@ -545,7 +548,7 @@
 
     protected open fun animateEndDragToDesktop(
         startTransaction: SurfaceControl.Transaction,
-        startTransitionFinishCb: Transitions.TransitionFinishCallback
+        startTransitionFinishCb: Transitions.TransitionFinishCallback,
     ) {
         val state = requireTransitionState()
         val draggedTaskChange =
@@ -568,7 +571,7 @@
                 startPosition.x.toInt(),
                 startPosition.y.toInt(),
                 startPosition.x.toInt() + unscaledStartWidth,
-                startPosition.y.toInt() + unscaledStartHeight
+                startPosition.y.toInt() + unscaledStartHeight,
             )
 
         dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction)
@@ -578,7 +581,7 @@
         onTaskResizeAnimationListener.onAnimationStart(
             state.draggedTaskId,
             startTransaction,
-            unscaledStartBounds
+            unscaledStartBounds,
         )
         val tx: SurfaceControl.Transaction = transactionSupplier.get()
         ValueAnimator.ofObject(rectEvaluator, unscaledStartBounds, endBounds)
@@ -594,21 +597,21 @@
                         setPosition(
                             draggedTaskLeash,
                             animBounds.left.toFloat(),
-                            animBounds.top.toFloat()
+                            animBounds.top.toFloat(),
                         )
                         setWindowCrop(draggedTaskLeash, animBounds.width(), animBounds.height())
                     }
                     onTaskResizeAnimationListener.onBoundsChange(
                         state.draggedTaskId,
                         tx,
-                        animBounds
+                        animBounds,
                     )
                 }
                 addListener(
                     object : AnimatorListenerAdapter() {
                         override fun onAnimationEnd(animation: Animator) {
                             onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
-                            startTransitionFinishCb.onTransitionFinished(/* wct = */ null)
+                            startTransitionFinishCb.onTransitionFinished(/* wct= */ null)
                             clearState()
                             interactionJankMonitor.end(
                                 CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
@@ -622,7 +625,7 @@
 
     override fun handleRequest(
         transition: IBinder,
-        request: TransitionRequestInfo
+        request: TransitionRequestInfo,
     ): WindowContainerTransaction? {
         // Only handle transitions started from shell.
         return null
@@ -631,7 +634,7 @@
     override fun onTransitionConsumed(
         transition: IBinder,
         aborted: Boolean,
-        finishTransaction: SurfaceControl.Transaction?
+        finishTransaction: SurfaceControl.Transaction?,
     ) {
         val state = transitionState ?: return
         if (!aborted) {
@@ -640,15 +643,13 @@
         if (state.startTransitionToken == transition) {
             ProtoLog.v(
                 ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
-                "DragToDesktop: onTransitionConsumed() start transition aborted"
+                "DragToDesktop: onTransitionConsumed() start transition aborted",
             )
             state.startAborted = true
             // The start-transition (DRAG_HOLD) is aborted, cancel its jank interaction.
             interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
         } else if (state.cancelTransitionToken == transition) {
-            state.draggedTaskChange?.leash?.let {
-                state.startTransitionFinishTransaction?.show(it)
-            }
+            state.draggedTaskChange?.leash?.let { state.startTransitionFinishTransaction?.show(it) }
             state.startTransitionFinishCb?.onTransitionFinished(null /* wct */)
             clearState()
         } else {
@@ -724,7 +725,7 @@
 
     private fun restoreWindowOrder(
         wct: WindowContainerTransaction,
-        state: TransitionState = requireTransitionState()
+        state: TransitionState = requireTransitionState(),
     ) {
         when (state) {
             is TransitionState.FromFullscreen -> {
@@ -831,7 +832,7 @@
             override var surfaceLayers: DragToDesktopLayers? = null,
             override var cancelState: CancelState = CancelState.NO_CANCEL,
             override var startAborted: Boolean = false,
-            var otherRootChanges: MutableList<Change> = mutableListOf()
+            var otherRootChanges: MutableList<Change> = mutableListOf(),
         ) : TransitionState()
 
         data class FromSplit(
@@ -848,7 +849,7 @@
             override var cancelState: CancelState = CancelState.NO_CANCEL,
             override var startAborted: Boolean = false,
             var splitRootChange: Change? = null,
-            var otherSplitTask: Int
+            var otherSplitTask: Int,
         ) : TransitionState()
     }
 
@@ -861,7 +862,7 @@
         /** A cancel event where the task will request to enter split on the left side. */
         CANCEL_SPLIT_LEFT,
         /** A cancel event where the task will request to enter split on the right side. */
-        CANCEL_SPLIT_RIGHT
+        CANCEL_SPLIT_RIGHT,
     }
 
     companion object {
@@ -888,7 +889,7 @@
         transitions,
         taskDisplayAreaOrganizer,
         interactionJankMonitor,
-        transactionSupplier
+        transactionSupplier,
     ) {
 
     /**
@@ -903,7 +904,7 @@
             topAppLayer = info.changes.size,
             topHomeLayer = info.changes.size * 2,
             topWallpaperLayer = info.changes.size * 3,
-            dragLayer = info.changes.size * 3
+            dragLayer = info.changes.size * 3,
         )
 }
 
@@ -924,7 +925,7 @@
         transitions,
         taskDisplayAreaOrganizer,
         interactionJankMonitor,
-        transactionSupplier
+        transactionSupplier,
     ) {
 
     private val positionSpringConfig =
@@ -945,13 +946,13 @@
             topAppLayer = -1,
             topHomeLayer = info.changes.size - 1,
             topWallpaperLayer = info.changes.size * 2 - 1,
-            dragLayer = info.changes.size * 2
+            dragLayer = info.changes.size * 2,
         )
 
     override fun setupEndDragToDesktop(
         info: TransitionInfo,
         startTransaction: SurfaceControl.Transaction,
-        finishTransaction: SurfaceControl.Transaction
+        finishTransaction: SurfaceControl.Transaction,
     ) {
         super.setupEndDragToDesktop(info, startTransaction, finishTransaction)
 
@@ -974,7 +975,7 @@
 
     override fun animateEndDragToDesktop(
         startTransaction: SurfaceControl.Transaction,
-        startTransitionFinishCb: Transitions.TransitionFinishCallback
+        startTransitionFinishCb: Transitions.TransitionFinishCallback,
     ) {
         val state = requireTransitionState()
         val draggedTaskChange =
@@ -1002,7 +1003,7 @@
         onTaskResizeAnimationListener.onAnimationStart(
             state.draggedTaskId,
             startTransaction,
-            startBoundsWithOffset
+            startBoundsWithOffset,
         )
 
         val tx: SurfaceControl.Transaction = transactionSupplier.get()
@@ -1011,13 +1012,13 @@
                 FloatProperties.RECT_X,
                 endBounds.left.toFloat(),
                 currentVelocity.x,
-                positionSpringConfig
+                positionSpringConfig,
             )
             .spring(
                 FloatProperties.RECT_Y,
                 endBounds.top.toFloat(),
                 currentVelocity.y,
-                positionSpringConfig
+                positionSpringConfig,
             )
             .spring(FloatProperties.RECT_WIDTH, endBounds.width().toFloat(), sizeSpringConfig)
             .spring(FloatProperties.RECT_HEIGHT, endBounds.height().toFloat(), sizeSpringConfig)
@@ -1050,7 +1051,7 @@
                     setPosition(
                         draggedTaskLeash,
                         animBounds.left.toFloat(),
-                        animBounds.top.toFloat()
+                        animBounds.top.toFloat(),
                     )
                     // Update freeform tasks
                     freeformTaskChanges.forEach {
@@ -1069,7 +1070,7 @@
             }
             .withEndActions({
                 onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
-                startTransitionFinishCb.onTransitionFinished(/* wct = */ null)
+                startTransitionFinishCb.onTransitionFinished(/* wct= */ null)
                 clearState()
                 interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
             })
@@ -1094,7 +1095,7 @@
             propertyValue(
                 "position_damping_ratio",
                 scale = 100f,
-                default = SpringForce.DAMPING_RATIO_LOW_BOUNCY
+                default = SpringForce.DAMPING_RATIO_LOW_BOUNCY,
             )
 
         /** The spring force stiffness used to resize the window into the final bounds. */
@@ -1106,7 +1107,7 @@
             propertyValue(
                 "size_damping_ratio",
                 scale = 100f,
-                default = SpringForce.DAMPING_RATIO_NO_BOUNCY
+                default = SpringForce.DAMPING_RATIO_NO_BOUNCY,
             )
 
         /** Drag to desktop transition system properties group. */
@@ -1123,7 +1124,7 @@
         fun propertyValue(name: String, scale: Float = 1f, default: Float = 0f): Float =
             SystemProperties.getInt(
                 /* key= */ "$SYSTEM_PROPERTIES_GROUP.$name",
-                /* def= */ (default * scale).toInt()
+                /* def= */ (default * scale).toInt(),
             ) / scale
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
index 4e08d10..3edeecb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
@@ -30,13 +30,14 @@
 /** Animates the task surface moving from its current drag position to its pre-drag position. */
 class ReturnToDragStartAnimator(
     private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
-    private val interactionJankMonitor: InteractionJankMonitor
+    private val interactionJankMonitor: InteractionJankMonitor,
 ) {
     private var boundsAnimator: Animator? = null
     private lateinit var taskRepositionAnimationListener: OnTaskRepositionAnimationListener
 
-    constructor(interactionJankMonitor: InteractionJankMonitor) :
-            this(Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
+    constructor(
+        interactionJankMonitor: InteractionJankMonitor
+    ) : this(Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
 
     /** Sets a listener for the start and end of the reposition animation. */
     fun setTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) {
@@ -65,7 +66,7 @@
                                 .setPosition(
                                     taskSurface,
                                     startBounds.left.toFloat(),
-                                    startBounds.top.toFloat()
+                                    startBounds.top.toFloat(),
                                 )
                                 .show(taskSurface)
                                 .apply()
@@ -77,7 +78,7 @@
                                 .setPosition(
                                     taskSurface,
                                     endBounds.left.toFloat(),
-                                    endBounds.top.toFloat()
+                                    endBounds.top.toFloat(),
                                 )
                                 .show(taskSurface)
                                 .apply()
@@ -85,7 +86,7 @@
                             boundsAnimator = null
                             doOnEnd?.invoke()
                             interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
-                        }
+                        },
                     )
                     addUpdateListener { anim ->
                         val rect = anim.animatedValue as Rect
@@ -100,4 +101,4 @@
     companion object {
         const val RETURN_TO_DRAG_START_ANIMATION_MS = 300L
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 9411150..e683f62 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -39,7 +39,7 @@
 class ToggleResizeDesktopTaskTransitionHandler(
     private val transitions: Transitions,
     private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
-    private val interactionJankMonitor: InteractionJankMonitor
+    private val interactionJankMonitor: InteractionJankMonitor,
 ) : Transitions.TransitionHandler {
 
     private val rectEvaluator = RectEvaluator(Rect())
@@ -50,16 +50,16 @@
 
     constructor(
         transitions: Transitions,
-        interactionJankMonitor: InteractionJankMonitor
+        interactionJankMonitor: InteractionJankMonitor,
     ) : this(transitions, Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
 
     /**
      * Starts a quick resize transition.
      *
-     *  @param wct WindowContainerTransaction that will update core about the task changes applied
-     *  @param taskLeashBounds current bounds of the task leash (Note: not guaranteed to be the
-     *                         bounds of the actual task). This is provided so that the animation
-     *                         resizing can begin where the task leash currently is for smoother UX.
+     * @param wct WindowContainerTransaction that will update core about the task changes applied
+     * @param taskLeashBounds current bounds of the task leash (Note: not guaranteed to be the
+     *   bounds of the actual task). This is provided so that the animation resizing can begin where
+     *   the task leash currently is for smoother UX.
      */
     fun startTransition(wct: WindowContainerTransaction, taskLeashBounds: Rect? = null) {
         transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
@@ -75,7 +75,7 @@
         info: TransitionInfo,
         startTransaction: SurfaceControl.Transaction,
         finishTransaction: SurfaceControl.Transaction,
-        finishCallback: Transitions.TransitionFinishCallback
+        finishCallback: Transitions.TransitionFinishCallback,
     ): Boolean {
         val change = findRelevantChange(info)
         val leash = change.leash
@@ -95,7 +95,7 @@
                                 .setPosition(
                                     leash,
                                     startBounds.left.toFloat(),
-                                    startBounds.top.toFloat()
+                                    startBounds.top.toFloat(),
                                 )
                                 .setWindowCrop(leash, startBounds.width(), startBounds.height())
                                 .show(leash)
@@ -110,7 +110,7 @@
                                 .setPosition(
                                     leash,
                                     endBounds.left.toFloat(),
-                                    endBounds.top.toFloat()
+                                    endBounds.top.toFloat(),
                                 )
                                 .setWindowCrop(leash, endBounds.width(), endBounds.height())
                                 .show(leash)
@@ -119,8 +119,9 @@
                             initialBounds = null
                             boundsAnimator = null
                             interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
+                            interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW)
                             interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
-                        }
+                        },
                     )
                     addUpdateListener { anim ->
                         val rect = anim.animatedValue as Rect
@@ -137,7 +138,7 @@
 
     override fun handleRequest(
         transition: IBinder,
-        request: TransitionRequestInfo
+        request: TransitionRequestInfo,
     ): WindowContainerTransaction? {
         return null
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt
index 8bfcca0..131b748 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt
@@ -27,23 +27,22 @@
 
 /** Repository to observe caption state. */
 class WindowDecorCaptionHandleRepository {
-  private val _captionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption)
-  /** Observer for app handle state changes. */
-  val captionStateFlow: StateFlow<CaptionState> = _captionStateFlow
-  private val _appToWebUsageFlow = MutableSharedFlow<Unit>()
-  /** Observer for App-to-Web usage. */
-  val appToWebUsageFlow = _appToWebUsageFlow
+    private val _captionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption)
+    /** Observer for app handle state changes. */
+    val captionStateFlow: StateFlow<CaptionState> = _captionStateFlow
+    private val _appToWebUsageFlow = MutableSharedFlow<Unit>()
+    /** Observer for App-to-Web usage. */
+    val appToWebUsageFlow = _appToWebUsageFlow
 
+    /** Notifies [captionStateFlow] if there is a change to caption state. */
+    fun notifyCaptionChanged(captionState: CaptionState) {
+        _captionStateFlow.value = captionState
+    }
 
-  /** Notifies [captionStateFlow] if there is a change to caption state. */
-  fun notifyCaptionChanged(captionState: CaptionState) {
-    _captionStateFlow.value = captionState
-  }
-
-  /** Notifies [appToWebUsageFlow] if App-to-Web feature is used. */
-  fun onAppToWebUsage() {
-    _appToWebUsageFlow.tryEmit(Unit)
-  }
+    /** Notifies [appToWebUsageFlow] if App-to-Web feature is used. */
+    fun onAppToWebUsage() {
+        _appToWebUsageFlow.tryEmit(Unit)
+    }
 }
 
 /**
@@ -54,20 +53,20 @@
  * * [AppHeader]: Indicating that there is at least one visible app chip on the screen.
  * * [NoCaption]: Signifying that no caption handle is currently visible on the device.
  */
-sealed class CaptionState{
-  data class AppHandle(
-    val runningTaskInfo: RunningTaskInfo,
-    val isHandleMenuExpanded: Boolean,
-    val globalAppHandleBounds: Rect,
-    val isCapturedLinkAvailable: Boolean
-  ) : CaptionState()
+sealed class CaptionState {
+    data class AppHandle(
+        val runningTaskInfo: RunningTaskInfo,
+        val isHandleMenuExpanded: Boolean,
+        val globalAppHandleBounds: Rect,
+        val isCapturedLinkAvailable: Boolean,
+    ) : CaptionState()
 
-  data class AppHeader(
-    val runningTaskInfo: RunningTaskInfo,
-    val isHeaderMenuExpanded: Boolean,
-    val globalAppChipBounds: Rect,
-    val isCapturedLinkAvailable: Boolean
-  ) : CaptionState()
+    data class AppHeader(
+        val runningTaskInfo: RunningTaskInfo,
+        val isHeaderMenuExpanded: Boolean,
+        val globalAppChipBounds: Rect,
+        val isCapturedLinkAvailable: Boolean,
+    ) : CaptionState()
 
-  data object NoCaption : CaptionState()
+    data object NoCaption : CaptionState()
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt
new file mode 100644
index 0000000..7afd8d7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt
@@ -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.wm.shell.desktopmode.common
+
+import com.android.internal.jank.Cuj
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction.AmbiguousSource
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction.Source
+
+/** Represents a user interaction to toggle a desktop task's size from to maximize or vice versa. */
+data class ToggleTaskSizeInteraction(
+    val direction: Direction,
+    val source: Source,
+    val inputMethod: InputMethod,
+) {
+    constructor(
+        isMaximized: Boolean,
+        source: Source,
+        inputMethod: InputMethod,
+    ) : this(
+        direction = if (isMaximized) Direction.RESTORE else Direction.MAXIMIZE,
+        source = source,
+        inputMethod = inputMethod,
+    )
+
+    val jankTag: String? =
+        when (source) {
+            Source.HEADER_BUTTON_TO_MAXIMIZE -> "caption_bar_button"
+            Source.HEADER_BUTTON_TO_RESTORE -> "caption_bar_button"
+            Source.KEYBOARD_SHORTCUT -> null
+            Source.HEADER_DRAG_TO_TOP -> null
+            Source.MAXIMIZE_MENU_TO_MAXIMIZE -> "maximize_menu"
+            Source.MAXIMIZE_MENU_TO_RESTORE -> "maximize_menu"
+            Source.DOUBLE_TAP_TO_MAXIMIZE -> "double_tap"
+            Source.DOUBLE_TAP_TO_RESTORE -> "double_tap"
+        }
+    val uiEvent: DesktopUiEventEnum? =
+        when (source) {
+            Source.HEADER_BUTTON_TO_MAXIMIZE ->
+                DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP
+            Source.HEADER_BUTTON_TO_RESTORE -> DesktopUiEventEnum.DESKTOP_WINDOW_RESTORE_BUTTON_TAP
+            Source.KEYBOARD_SHORTCUT -> null
+            Source.HEADER_DRAG_TO_TOP -> null
+            Source.MAXIMIZE_MENU_TO_MAXIMIZE -> {
+                DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_MAXIMIZE
+            }
+            Source.MAXIMIZE_MENU_TO_RESTORE -> {
+                DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_RESTORE
+            }
+            Source.DOUBLE_TAP_TO_MAXIMIZE -> {
+                DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE
+            }
+            Source.DOUBLE_TAP_TO_RESTORE -> {
+                DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_RESTORE
+            }
+        }
+    val resizeTrigger =
+        when (source) {
+            Source.HEADER_BUTTON_TO_MAXIMIZE -> ResizeTrigger.MAXIMIZE_BUTTON
+            Source.HEADER_BUTTON_TO_RESTORE -> ResizeTrigger.MAXIMIZE_BUTTON
+            Source.KEYBOARD_SHORTCUT -> ResizeTrigger.UNKNOWN_RESIZE_TRIGGER
+            Source.HEADER_DRAG_TO_TOP -> ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER
+            Source.MAXIMIZE_MENU_TO_MAXIMIZE -> ResizeTrigger.MAXIMIZE_MENU
+            Source.MAXIMIZE_MENU_TO_RESTORE -> ResizeTrigger.MAXIMIZE_MENU
+            Source.DOUBLE_TAP_TO_MAXIMIZE -> ResizeTrigger.DOUBLE_TAP_APP_HEADER
+            Source.DOUBLE_TAP_TO_RESTORE -> ResizeTrigger.DOUBLE_TAP_APP_HEADER
+        }
+    val cujTracing: Int? =
+        when (source) {
+            Source.HEADER_BUTTON_TO_MAXIMIZE -> Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW
+            Source.HEADER_BUTTON_TO_RESTORE -> Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW
+            Source.KEYBOARD_SHORTCUT -> null
+            Source.HEADER_DRAG_TO_TOP -> null
+            Source.MAXIMIZE_MENU_TO_MAXIMIZE -> null
+            Source.MAXIMIZE_MENU_TO_RESTORE -> null
+            Source.DOUBLE_TAP_TO_MAXIMIZE -> null
+            Source.DOUBLE_TAP_TO_RESTORE -> null
+        }
+
+    /** The direction to which the task is being resized. */
+    enum class Direction {
+        MAXIMIZE,
+        RESTORE,
+    }
+
+    /** The user interaction source. */
+    enum class Source {
+        HEADER_BUTTON_TO_MAXIMIZE,
+        HEADER_BUTTON_TO_RESTORE,
+        KEYBOARD_SHORTCUT,
+        HEADER_DRAG_TO_TOP,
+        MAXIMIZE_MENU_TO_MAXIMIZE,
+        MAXIMIZE_MENU_TO_RESTORE,
+        DOUBLE_TAP_TO_MAXIMIZE,
+        DOUBLE_TAP_TO_RESTORE,
+    }
+
+    /**
+     * Temporary sources for interactions that should be broken into more specific sources, for
+     * example, the header button click should use [Source.HEADER_BUTTON_TO_MAXIMIZE] and
+     * [Source.HEADER_BUTTON_TO_RESTORE].
+     *
+     * TODO: b/341320112 - break these out into different [Source]s.
+     */
+    enum class AmbiguousSource {
+        HEADER_BUTTON,
+        MAXIMIZE_MENU,
+        DOUBLE_TAP,
+    }
+}
+
+/** Returns the non-ambiguous [Source] based on the maximized state of the task. */
+fun AmbiguousSource.toSource(isMaximized: Boolean): Source {
+    return when (this) {
+        AmbiguousSource.HEADER_BUTTON ->
+            if (isMaximized) {
+                Source.HEADER_BUTTON_TO_RESTORE
+            } else {
+                Source.HEADER_BUTTON_TO_MAXIMIZE
+            }
+        AmbiguousSource.MAXIMIZE_MENU ->
+            if (isMaximized) {
+                Source.MAXIMIZE_MENU_TO_RESTORE
+            } else {
+                Source.MAXIMIZE_MENU_TO_MAXIMIZE
+            }
+        AmbiguousSource.DOUBLE_TAP ->
+            if (isMaximized) {
+                Source.DOUBLE_TAP_TO_RESTORE
+            } else {
+                Source.DOUBLE_TAP_TO_MAXIMIZE
+            }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt
index 826de08..a428ce1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt
@@ -29,7 +29,7 @@
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
-import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.shared.TransitionUtil.isClosingMode
 import com.android.wm.shell.shared.TransitionUtil.isClosingType
@@ -46,7 +46,7 @@
     private val animExecutor: ShellExecutor,
     private val shellInit: ShellInit,
     private val transitions: Transitions,
-    private val desktopRepository: DesktopRepository,
+    private val desktopUserRepositories: DesktopUserRepositories,
 ) : TransitionHandler {
 
     private val showingSystemModalsIds = mutableSetOf<Int>()
@@ -156,7 +156,7 @@
         }
 
     private fun isDesktopModeShowing(displayId: Int): Boolean =
-        desktopRepository.getVisibleTaskCount(displayId) > 0
+        desktopUserRepositories.current.getVisibleTaskCount(displayId) > 0
 
     override fun handleRequest(
         transition: IBinder,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
index e01c448..c5fca02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
@@ -36,8 +36,8 @@
 import com.android.wm.shell.windowdecor.common.DecorThemeUtil
 import com.android.wm.shell.windowdecor.common.Theme
 import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController
-import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipEducationViewConfig
 import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipColorScheme
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipEducationViewConfig
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.MainCoroutineDispatcher
@@ -74,293 +74,330 @@
     @ShellMainThread private val applicationCoroutineScope: CoroutineScope,
     @ShellBackgroundThread private val backgroundDispatcher: MainCoroutineDispatcher,
 ) {
-  private val decorThemeUtil = DecorThemeUtil(context)
-  private lateinit var openHandleMenuCallback: (Int) -> Unit
-  private lateinit var toDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit
+    private val decorThemeUtil = DecorThemeUtil(context)
+    private lateinit var openHandleMenuCallback: (Int) -> Unit
+    private lateinit var toDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit
 
-  init {
-    runIfEducationFeatureEnabled {
-      applicationCoroutineScope.launch {
-        // Central block handling the app handle's educational flow end-to-end.
-        isEducationViewedFlow()
-            .flatMapLatest { isEducationViewed ->
-              if (isEducationViewed) {
-                // If the education is viewed then return emptyFlow() that completes immediately.
-                // This will help us to not listen to [captionHandleStateFlow] after the education
-                // has been viewed already.
-                emptyFlow()
-              } else {
-                // Listen for changes to window decor's caption handle.
-                windowDecorCaptionHandleRepository.captionStateFlow
-                    // Wait for few seconds before emitting the latest state.
-                    .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
-                    .filter { captionState ->
-                      captionState is CaptionState.AppHandle &&
-                          appHandleEducationFilter.shouldShowAppHandleEducation(captionState)
+    init {
+        runIfEducationFeatureEnabled {
+            applicationCoroutineScope.launch {
+                // Central block handling the app handle's educational flow end-to-end.
+                isAppHandleHintViewedFlow()
+                    .flatMapLatest { isAppHandleHintViewed ->
+                        if (isAppHandleHintViewed) {
+                            // If the education is viewed then return emptyFlow() that completes
+                            // immediately.
+                            // This will help us to not listen to [captionHandleStateFlow] after the
+                            // education
+                            // has been viewed already.
+                            emptyFlow()
+                        } else {
+                            // Listen for changes to window decor's caption handle.
+                            windowDecorCaptionHandleRepository.captionStateFlow
+                                // Wait for few seconds before emitting the latest state.
+                                .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
+                                .filter { captionState ->
+                                    captionState is CaptionState.AppHandle &&
+                                        appHandleEducationFilter.shouldShowAppHandleEducation(
+                                            captionState
+                                        )
+                                }
+                        }
                     }
-              }
-            }
-            .flowOn(backgroundDispatcher)
-            .collectLatest { captionState ->
-              val tooltipColorScheme = tooltipColorScheme(captionState)
+                    .flowOn(backgroundDispatcher)
+                    .collectLatest { captionState ->
+                        val tooltipColorScheme = tooltipColorScheme(captionState)
 
-              showEducation(captionState, tooltipColorScheme)
-              // After showing first tooltip, mark education as viewed
-              appHandleEducationDatastoreRepository.updateEducationViewedTimestampMillis(true)
+                        showEducation(captionState, tooltipColorScheme)
+                        // After showing first tooltip, mark education as viewed
+                        appHandleEducationDatastoreRepository
+                            .updateAppHandleHintViewedTimestampMillis(true)
+                    }
             }
-      }
 
-      applicationCoroutineScope.launch {
-        if (isFeatureUsed()) return@launch
+            applicationCoroutineScope.launch {
+                if (isAppHandleHintUsed()) return@launch
+                windowDecorCaptionHandleRepository.captionStateFlow
+                    .filter { captionState ->
+                        captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
+                    }
+                    .take(1)
+                    .flowOn(backgroundDispatcher)
+                    .collect {
+                        // If user expands app handle, mark user has used the app handle hint
+                        appHandleEducationDatastoreRepository
+                            .updateAppHandleHintUsedTimestampMillis(true)
+                    }
+            }
+        }
+    }
+
+    private inline fun runIfEducationFeatureEnabled(block: () -> Unit) {
+        if (canEnterDesktopMode(context) && Flags.enableDesktopWindowingAppHandleEducation())
+            block()
+    }
+
+    private fun showEducation(captionState: CaptionState, tooltipColorScheme: TooltipColorScheme) {
+        val appHandleBounds = (captionState as CaptionState.AppHandle).globalAppHandleBounds
+        val tooltipGlobalCoordinates =
+            Point(appHandleBounds.left + appHandleBounds.width() / 2, appHandleBounds.bottom)
+        // TODO: b/370546801 - Differentiate between user dismissing the tooltip vs following the
+        // cue.
+        // Populate information important to inflate app handle education tooltip.
+        val appHandleTooltipConfig =
+            TooltipEducationViewConfig(
+                tooltipViewLayout = R.layout.desktop_windowing_education_top_arrow_tooltip,
+                tooltipColorScheme = tooltipColorScheme,
+                tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
+                tooltipText = getString(R.string.windowing_app_handle_education_tooltip),
+                arrowDirection =
+                    DesktopWindowingEducationTooltipController.TooltipArrowDirection.UP,
+                onEducationClickAction = {
+                    launchWithExceptionHandling {
+                        showWindowingImageButtonTooltip(tooltipColorScheme)
+                    }
+                    openHandleMenuCallback(captionState.runningTaskInfo.taskId)
+                },
+                onDismissAction = {
+                    launchWithExceptionHandling {
+                        showWindowingImageButtonTooltip(tooltipColorScheme)
+                    }
+                },
+            )
+
+        windowingEducationViewController.showEducationTooltip(
+            tooltipViewConfig = appHandleTooltipConfig,
+            taskId = captionState.runningTaskInfo.taskId,
+        )
+    }
+
+    /** Show tooltip that points to windowing image button in app handle menu */
+    private suspend fun showWindowingImageButtonTooltip(tooltipColorScheme: TooltipColorScheme) {
+        val appInfoPillHeight = getSize(R.dimen.desktop_mode_handle_menu_app_info_pill_height)
+        val windowingOptionPillHeight =
+            getSize(R.dimen.desktop_mode_handle_menu_windowing_pill_height)
+        val appHandleMenuWidth =
+            getSize(R.dimen.desktop_mode_handle_menu_width) +
+                getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
+        val appHandleMenuMargins =
+            getSize(R.dimen.desktop_mode_handle_menu_margin_top) +
+                getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
+
         windowDecorCaptionHandleRepository.captionStateFlow
-            .filter { captionState ->
-              captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
+            // After the first tooltip was dismissed, wait for 400 ms and see if the app handle menu
+            // has been expanded.
+            .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds)
+            .catchTimeoutAndLog {
+                // TODO: b/341320146 - Log previous tooltip was dismissed
             }
+            // Wait for few milliseconds before emitting the latest state.
+            .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
+            .filter { captionState ->
+                // Filter out states when app handle is not visible or not expanded.
+                captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
+            }
+            // Before showing this tooltip, stop listening to further emissions to avoid
+            // accidentally
+            // showing the same tooltip on future emissions.
             .take(1)
             .flowOn(backgroundDispatcher)
-            .collect {
-              // If user expands app handle, mark user has used the feature
-              appHandleEducationDatastoreRepository.updateFeatureUsedTimestampMillis(true)
+            .collectLatest { captionState ->
+                captionState as CaptionState.AppHandle
+                val appHandleBounds = captionState.globalAppHandleBounds
+                val tooltipGlobalCoordinates =
+                    Point(
+                        appHandleBounds.left + appHandleBounds.width() / 2 + appHandleMenuWidth / 2,
+                        appHandleBounds.top +
+                            appHandleMenuMargins +
+                            appInfoPillHeight +
+                            windowingOptionPillHeight / 2,
+                    )
+                // Populate information important to inflate windowing image button education
+                // tooltip.
+                val windowingImageButtonTooltipConfig =
+                    TooltipEducationViewConfig(
+                        tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
+                        tooltipColorScheme = tooltipColorScheme,
+                        tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
+                        tooltipText =
+                            getString(
+                                R.string.windowing_desktop_mode_image_button_education_tooltip
+                            ),
+                        arrowDirection =
+                            DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
+                        onEducationClickAction = {
+                            launchWithExceptionHandling {
+                                showExitWindowingTooltip(tooltipColorScheme)
+                            }
+                            toDesktopModeCallback(
+                                captionState.runningTaskInfo.taskId,
+                                DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON,
+                            )
+                        },
+                        onDismissAction = {
+                            launchWithExceptionHandling {
+                                showExitWindowingTooltip(tooltipColorScheme)
+                            }
+                        },
+                    )
+
+                windowingEducationViewController.showEducationTooltip(
+                    taskId = captionState.runningTaskInfo.taskId,
+                    tooltipViewConfig = windowingImageButtonTooltipConfig,
+                )
             }
-      }
     }
-  }
 
-  private inline fun runIfEducationFeatureEnabled(block: () -> Unit) {
-    if (canEnterDesktopMode(context) && Flags.enableDesktopWindowingAppHandleEducation()) block()
-  }
+    /** Show tooltip that points to app chip button and educates user on how to exit desktop mode */
+    private suspend fun showExitWindowingTooltip(tooltipColorScheme: TooltipColorScheme) {
+        windowDecorCaptionHandleRepository.captionStateFlow
+            // After the previous tooltip was dismissed, wait for 400 ms and see if the user entered
+            // desktop mode.
+            .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds)
+            .catchTimeoutAndLog {
+                // TODO: b/341320146 - Log previous tooltip was dismissed
+            }
+            // Wait for few milliseconds before emitting the latest state.
+            .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
+            .filter { captionState ->
+                // Filter out states when app header is not visible or expanded.
+                captionState is CaptionState.AppHeader && !captionState.isHeaderMenuExpanded
+            }
+            // Before showing this tooltip, stop listening to further emissions to avoid
+            // accidentally
+            // showing the same tooltip on future emissions.
+            .take(1)
+            .flowOn(backgroundDispatcher)
+            .collectLatest { captionState ->
+                captionState as CaptionState.AppHeader
+                val globalAppChipBounds = captionState.globalAppChipBounds
+                val tooltipGlobalCoordinates =
+                    Point(
+                        globalAppChipBounds.right,
+                        globalAppChipBounds.top + globalAppChipBounds.height() / 2,
+                    )
+                // Populate information important to inflate exit desktop mode education tooltip.
+                val exitWindowingTooltipConfig =
+                    TooltipEducationViewConfig(
+                        tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
+                        tooltipColorScheme = tooltipColorScheme,
+                        tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
+                        tooltipText =
+                            getString(R.string.windowing_desktop_mode_exit_education_tooltip),
+                        arrowDirection =
+                            DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
+                        onDismissAction = {},
+                        onEducationClickAction = {
+                            openHandleMenuCallback(captionState.runningTaskInfo.taskId)
+                        },
+                    )
+                windowingEducationViewController.showEducationTooltip(
+                    taskId = captionState.runningTaskInfo.taskId,
+                    tooltipViewConfig = exitWindowingTooltipConfig,
+                )
+            }
+    }
 
-  private fun showEducation(captionState: CaptionState, tooltipColorScheme: TooltipColorScheme) {
-    val appHandleBounds = (captionState as CaptionState.AppHandle).globalAppHandleBounds
-    val tooltipGlobalCoordinates =
-        Point(appHandleBounds.left + appHandleBounds.width() / 2, appHandleBounds.bottom)
-    // TODO: b/370546801 - Differentiate between user dismissing the tooltip vs following the cue.
-    // Populate information important to inflate app handle education tooltip.
-    val appHandleTooltipConfig =
-        TooltipEducationViewConfig(
-            tooltipViewLayout = R.layout.desktop_windowing_education_top_arrow_tooltip,
-            tooltipColorScheme = tooltipColorScheme,
-            tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
-            tooltipText = getString(R.string.windowing_app_handle_education_tooltip),
-            arrowDirection = DesktopWindowingEducationTooltipController.TooltipArrowDirection.UP,
-            onEducationClickAction = {
-              launchWithExceptionHandling { showWindowingImageButtonTooltip(tooltipColorScheme) }
-              openHandleMenuCallback(captionState.runningTaskInfo.taskId)
-            },
-            onDismissAction = {
-              launchWithExceptionHandling { showWindowingImageButtonTooltip(tooltipColorScheme) }
-            },
-        )
+    private fun tooltipColorScheme(captionState: CaptionState): TooltipColorScheme {
+        context.withStyledAttributes(
+            set = null,
+            attrs =
+                intArrayOf(
+                    com.android.internal.R.attr.materialColorOnTertiaryFixed,
+                    com.android.internal.R.attr.materialColorTertiaryFixed,
+                    com.android.internal.R.attr.materialColorTertiaryFixedDim,
+                ),
+            defStyleAttr = 0,
+            defStyleRes = 0,
+        ) {
+            val onTertiaryFixed = getColor(/* index= */ 0, /* defValue= */ 0)
+            val tertiaryFixed = getColor(/* index= */ 1, /* defValue= */ 0)
+            val tertiaryFixedDim = getColor(/* index= */ 2, /* defValue= */ 0)
+            val taskInfo = (captionState as CaptionState.AppHandle).runningTaskInfo
 
-    windowingEducationViewController.showEducationTooltip(
-        tooltipViewConfig = appHandleTooltipConfig, taskId = captionState.runningTaskInfo.taskId)
-  }
-
-  /** Show tooltip that points to windowing image button in app handle menu */
-  private suspend fun showWindowingImageButtonTooltip(tooltipColorScheme: TooltipColorScheme) {
-    val appInfoPillHeight = getSize(R.dimen.desktop_mode_handle_menu_app_info_pill_height)
-    val windowingOptionPillHeight = getSize(R.dimen.desktop_mode_handle_menu_windowing_pill_height)
-    val appHandleMenuWidth =
-        getSize(R.dimen.desktop_mode_handle_menu_width) +
-            getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
-    val appHandleMenuMargins =
-        getSize(R.dimen.desktop_mode_handle_menu_margin_top) +
-            getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
-
-    windowDecorCaptionHandleRepository.captionStateFlow
-        // After the first tooltip was dismissed, wait for 400 ms and see if the app handle menu
-        // has been expanded.
-        .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds)
-        .catchTimeoutAndLog {
-          // TODO: b/341320146 - Log previous tooltip was dismissed
+            val tooltipContainerColor =
+                if (decorThemeUtil.getAppTheme(taskInfo) == Theme.LIGHT) {
+                    tertiaryFixed
+                } else {
+                    tertiaryFixedDim
+                }
+            return TooltipColorScheme(tooltipContainerColor, onTertiaryFixed, onTertiaryFixed)
         }
-        // Wait for few milliseconds before emitting the latest state.
-        .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
-        .filter { captionState ->
-          // Filter out states when app handle is not visible or not expanded.
-          captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
+        return TooltipColorScheme(0, 0, 0)
+    }
+
+    /**
+     * Setup callbacks for app handle education tooltips.
+     *
+     * @param openHandleMenuCallback callback invoked to open app handle menu or app chip menu.
+     * @param toDesktopModeCallback callback invoked to move task into desktop mode.
+     */
+    fun setAppHandleEducationTooltipCallbacks(
+        openHandleMenuCallback: (taskId: Int) -> Unit,
+        toDesktopModeCallback: (taskId: Int, DesktopModeTransitionSource) -> Unit,
+    ) {
+        this.openHandleMenuCallback = openHandleMenuCallback
+        this.toDesktopModeCallback = toDesktopModeCallback
+    }
+
+    private inline fun <T> Flow<T>.catchTimeoutAndLog(crossinline block: () -> Unit) =
+        catch { exception ->
+            if (exception is TimeoutCancellationException) block() else throw exception
         }
-        // Before showing this tooltip, stop listening to further emissions to avoid accidentally
-        // showing the same tooltip on future emissions.
-        .take(1)
-        .flowOn(backgroundDispatcher)
-        .collectLatest { captionState ->
-          captionState as CaptionState.AppHandle
-          val appHandleBounds = captionState.globalAppHandleBounds
-          val tooltipGlobalCoordinates =
-              Point(
-                  appHandleBounds.left + appHandleBounds.width() / 2 + appHandleMenuWidth / 2,
-                  appHandleBounds.top +
-                      appHandleMenuMargins +
-                      appInfoPillHeight +
-                      windowingOptionPillHeight / 2)
-          // Populate information important to inflate windowing image button education tooltip.
-          val windowingImageButtonTooltipConfig =
-              TooltipEducationViewConfig(
-                  tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
-                  tooltipColorScheme = tooltipColorScheme,
-                  tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
-                  tooltipText =
-                      getString(R.string.windowing_desktop_mode_image_button_education_tooltip),
-                  arrowDirection =
-                      DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
-                  onEducationClickAction = {
-                    launchWithExceptionHandling { showExitWindowingTooltip(tooltipColorScheme) }
-                    toDesktopModeCallback(
-                        captionState.runningTaskInfo.taskId,
-                        DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
-                  },
-                  onDismissAction = {
-                    launchWithExceptionHandling { showExitWindowingTooltip(tooltipColorScheme) }
-                  },
-              )
 
-          windowingEducationViewController.showEducationTooltip(
-              taskId = captionState.runningTaskInfo.taskId,
-              tooltipViewConfig = windowingImageButtonTooltipConfig)
+    private fun launchWithExceptionHandling(block: suspend () -> Unit) =
+        applicationCoroutineScope.launch {
+            try {
+                block()
+            } catch (e: Throwable) {
+                Slog.e(TAG, "Error: ", e)
+            }
         }
-  }
 
-  /** Show tooltip that points to app chip button and educates user on how to exit desktop mode */
-  private suspend fun showExitWindowingTooltip(tooltipColorScheme: TooltipColorScheme) {
-    windowDecorCaptionHandleRepository.captionStateFlow
-        // After the previous tooltip was dismissed, wait for 400 ms and see if the user entered
-        // desktop mode.
-        .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds)
-        .catchTimeoutAndLog {
-          // TODO: b/341320146 - Log previous tooltip was dismissed
-        }
-        // Wait for few milliseconds before emitting the latest state.
-        .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
-        .filter { captionState ->
-          // Filter out states when app header is not visible or expanded.
-          captionState is CaptionState.AppHeader && !captionState.isHeaderMenuExpanded
-        }
-        // Before showing this tooltip, stop listening to further emissions to avoid accidentally
-        // showing the same tooltip on future emissions.
-        .take(1)
-        .flowOn(backgroundDispatcher)
-        .collectLatest { captionState ->
-          captionState as CaptionState.AppHeader
-          val globalAppChipBounds = captionState.globalAppChipBounds
-          val tooltipGlobalCoordinates =
-              Point(
-                  globalAppChipBounds.right,
-                  globalAppChipBounds.top + globalAppChipBounds.height() / 2)
-          // Populate information important to inflate exit desktop mode education tooltip.
-          val exitWindowingTooltipConfig =
-              TooltipEducationViewConfig(
-                  tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
-                  tooltipColorScheme = tooltipColorScheme,
-                  tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
-                  tooltipText = getString(R.string.windowing_desktop_mode_exit_education_tooltip),
-                  arrowDirection =
-                      DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
-                  onDismissAction = {},
-                  onEducationClickAction = {
-                    openHandleMenuCallback(captionState.runningTaskInfo.taskId)
-                  },
-              )
-          windowingEducationViewController.showEducationTooltip(
-              taskId = captionState.runningTaskInfo.taskId,
-              tooltipViewConfig = exitWindowingTooltipConfig,
-          )
-        }
-  }
+    /**
+     * Listens to the changes to [WindowingEducationProto#hasAppHandleHintViewedTimestampMillis()]
+     * in datastore proto object.
+     *
+     * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this flow will always emit false. That
+     * means it will always emit app handle hint has not been viewed yet.
+     */
+    private fun isAppHandleHintViewedFlow(): Flow<Boolean> =
+        appHandleEducationDatastoreRepository.dataStoreFlow
+            .map { preferences ->
+                preferences.hasAppHandleHintViewedTimestampMillis() &&
+                    !SHOULD_OVERRIDE_EDUCATION_CONDITIONS
+            }
+            .distinctUntilChanged()
 
-  private fun tooltipColorScheme(captionState: CaptionState): TooltipColorScheme {
-    context.withStyledAttributes(
-        set = null,
-        attrs =
-            intArrayOf(
-                com.android.internal.R.attr.materialColorOnTertiaryFixed,
-                com.android.internal.R.attr.materialColorTertiaryFixed,
-                com.android.internal.R.attr.materialColorTertiaryFixedDim),
-        defStyleAttr = 0,
-        defStyleRes = 0) {
-          val onTertiaryFixed = getColor(/* index= */ 0, /* defValue= */ 0)
-          val tertiaryFixed = getColor(/* index= */ 1, /* defValue= */ 0)
-          val tertiaryFixedDim = getColor(/* index= */ 2, /* defValue= */ 0)
-          val taskInfo = (captionState as CaptionState.AppHandle).runningTaskInfo
+    /**
+     * Listens to the changes to [WindowingEducationProto#hasAppHandleHintUsedTimestampMillis()] in
+     * datastore proto object.
+     */
+    private suspend fun isAppHandleHintUsed(): Boolean =
+        appHandleEducationDatastoreRepository.dataStoreFlow
+            .first()
+            .hasAppHandleHintUsedTimestampMillis()
 
-          val tooltipContainerColor =
-              if (decorThemeUtil.getAppTheme(taskInfo) == Theme.LIGHT) {
-                tertiaryFixed
-              } else {
-                tertiaryFixedDim
-              }
-          return TooltipColorScheme(tooltipContainerColor, onTertiaryFixed, onTertiaryFixed)
-        }
-    return TooltipColorScheme(0, 0, 0)
-  }
+    private fun getSize(@DimenRes resourceId: Int): Int {
+        if (resourceId == Resources.ID_NULL) return 0
+        return context.resources.getDimensionPixelSize(resourceId)
+    }
 
-  /**
-   * Setup callbacks for app handle education tooltips.
-   *
-   * @param openHandleMenuCallback callback invoked to open app handle menu or app chip menu.
-   * @param toDesktopModeCallback callback invoked to move task into desktop mode.
-   */
-  fun setAppHandleEducationTooltipCallbacks(
-      openHandleMenuCallback: (taskId: Int) -> Unit,
-      toDesktopModeCallback: (taskId: Int, DesktopModeTransitionSource) -> Unit
-  ) {
-    this.openHandleMenuCallback = openHandleMenuCallback
-    this.toDesktopModeCallback = toDesktopModeCallback
-  }
+    private fun getString(@StringRes resId: Int): String = context.resources.getString(resId)
 
-  private inline fun <T> Flow<T>.catchTimeoutAndLog(crossinline block: () -> Unit) =
-      catch { exception ->
-        if (exception is TimeoutCancellationException) block() else throw exception
-      }
+    companion object {
+        const val TAG = "AppHandleEducationController"
+        val APP_HANDLE_EDUCATION_DELAY_MILLIS: Long
+            get() = SystemProperties.getLong("persist.windowing_app_handle_education_delay", 3000L)
 
-  private fun launchWithExceptionHandling(block: suspend () -> Unit) =
-      applicationCoroutineScope.launch {
-        try {
-          block()
-        } catch (e: Throwable) {
-          Slog.e(TAG, "Error: ", e)
-        }
-      }
+        val APP_HANDLE_EDUCATION_TIMEOUT_MILLIS: Long
+            get() = SystemProperties.getLong("persist.windowing_app_handle_education_timeout", 400L)
 
-  /**
-   * Listens to the changes to [WindowingEducationProto#hasEducationViewedTimestampMillis()] in
-   * datastore proto object.
-   *
-   * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this flow will always emit false. That means
-   * it will emit education has not been viewed yet always.
-   */
-  private fun isEducationViewedFlow(): Flow<Boolean> =
-      appHandleEducationDatastoreRepository.dataStoreFlow
-          .map { preferences ->
-            preferences.hasEducationViewedTimestampMillis() && !SHOULD_OVERRIDE_EDUCATION_CONDITIONS
-          }
-          .distinctUntilChanged()
-
-  /**
-   * Listens to the changes to [WindowingEducationProto#hasFeatureUsedTimestampMillis()] in
-   * datastore proto object.
-   */
-  private suspend fun isFeatureUsed(): Boolean =
-      appHandleEducationDatastoreRepository.dataStoreFlow.first().hasFeatureUsedTimestampMillis()
-
-  private fun getSize(@DimenRes resourceId: Int): Int {
-    if (resourceId == Resources.ID_NULL) return 0
-    return context.resources.getDimensionPixelSize(resourceId)
-  }
-
-  private fun getString(@StringRes resId: Int): String = context.resources.getString(resId)
-
-  companion object {
-    const val TAG = "AppHandleEducationController"
-    val APP_HANDLE_EDUCATION_DELAY_MILLIS: Long
-      get() = SystemProperties.getLong("persist.windowing_app_handle_education_delay", 3000L)
-
-    val APP_HANDLE_EDUCATION_TIMEOUT_MILLIS: Long
-      get() = SystemProperties.getLong("persist.windowing_app_handle_education_timeout", 400L)
-
-    val SHOULD_OVERRIDE_EDUCATION_CONDITIONS: Boolean
-      get() =
-          SystemProperties.getBoolean(
-              "persist.desktop_windowing_app_handle_education_override_conditions", false)
-  }
+        val SHOULD_OVERRIDE_EDUCATION_CONDITIONS: Boolean
+            get() =
+                SystemProperties.getBoolean(
+                    "persist.desktop_windowing_app_handle_education_override_conditions",
+                    false,
+                )
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt
index 144370d..9990846 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt
@@ -32,106 +32,114 @@
 /** Filters incoming app handle education triggers based on set conditions. */
 class AppHandleEducationFilter(
     private val context: Context,
-    private val appHandleEducationDatastoreRepository: AppHandleEducationDatastoreRepository
+    private val appHandleEducationDatastoreRepository: AppHandleEducationDatastoreRepository,
 ) {
-  private val usageStatsManager =
-      context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
+    private val usageStatsManager =
+        context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
 
-  /**
-   * Returns true if conditions to show app handle education are met, returns false otherwise.
-   *
-   * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this method will always return
-   * ![captionState.isHandleMenuExpanded].
-   */
-  suspend fun shouldShowAppHandleEducation(captionState: CaptionState): Boolean {
-    if ((captionState as CaptionState.AppHandle).isHandleMenuExpanded) return false
-    if (SHOULD_OVERRIDE_EDUCATION_CONDITIONS) return true
+    /**
+     * Returns true if conditions to show app handle education are met, returns false otherwise.
+     *
+     * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this method will always return
+     * ![captionState.isHandleMenuExpanded].
+     */
+    suspend fun shouldShowAppHandleEducation(captionState: CaptionState): Boolean {
+        if ((captionState as CaptionState.AppHandle).isHandleMenuExpanded) return false
+        if (SHOULD_OVERRIDE_EDUCATION_CONDITIONS) return true
 
-    val focusAppPackageName =
-        captionState.runningTaskInfo.topActivityInfo?.packageName ?: return false
-    val windowingEducationProto = appHandleEducationDatastoreRepository.windowingEducationProto()
+        val focusAppPackageName =
+            captionState.runningTaskInfo.topActivityInfo?.packageName ?: return false
+        val windowingEducationProto =
+            appHandleEducationDatastoreRepository.windowingEducationProto()
 
-    return isFocusAppInAllowlist(focusAppPackageName) &&
-        !isOtherEducationShowing() &&
-        hasSufficientTimeSinceSetup() &&
-        !isEducationViewedBefore(windowingEducationProto) &&
-        !isFeatureUsedBefore(windowingEducationProto) &&
-        hasMinAppUsage(windowingEducationProto, focusAppPackageName)
-  }
+        return isFocusAppInAllowlist(focusAppPackageName) &&
+            !isOtherEducationShowing() &&
+            hasSufficientTimeSinceSetup() &&
+            !isAppHandleHintViewedBefore(windowingEducationProto) &&
+            !isAppHandleHintUsedBefore(windowingEducationProto) &&
+            hasMinAppUsage(windowingEducationProto, focusAppPackageName)
+    }
 
-  private fun isFocusAppInAllowlist(focusAppPackageName: String): Boolean =
-      focusAppPackageName in
-          context.resources.getStringArray(
-              R.array.desktop_windowing_app_handle_education_allowlist_apps)
+    private fun isFocusAppInAllowlist(focusAppPackageName: String): Boolean =
+        focusAppPackageName in
+            context.resources.getStringArray(
+                R.array.desktop_windowing_app_handle_education_allowlist_apps
+            )
 
-  // TODO: b/350953004 - Add checks based on App compat
-  // TODO: b/350951797 - Add checks based on PKT tips education
-  private fun isOtherEducationShowing(): Boolean = isTaskbarEducationShowing()
+    // TODO: b/350953004 - Add checks based on App compat
+    // TODO: b/350951797 - Add checks based on PKT tips education
+    private fun isOtherEducationShowing(): Boolean = isTaskbarEducationShowing()
 
-  private fun isTaskbarEducationShowing(): Boolean =
-      Secure.getInt(context.contentResolver, Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0) == 1
+    private fun isTaskbarEducationShowing(): Boolean =
+        Secure.getInt(context.contentResolver, Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0) == 1
 
-  private fun hasSufficientTimeSinceSetup(): Boolean =
-      Duration.ofMillis(SystemClock.elapsedRealtime()) >
-          convertIntegerResourceToDuration(
-              R.integer.desktop_windowing_education_required_time_since_setup_seconds)
+    private fun hasSufficientTimeSinceSetup(): Boolean =
+        Duration.ofMillis(SystemClock.elapsedRealtime()) >
+            convertIntegerResourceToDuration(
+                R.integer.desktop_windowing_education_required_time_since_setup_seconds
+            )
 
-  private fun isEducationViewedBefore(windowingEducationProto: WindowingEducationProto): Boolean =
-      windowingEducationProto.hasEducationViewedTimestampMillis()
+    private fun isAppHandleHintViewedBefore(
+        windowingEducationProto: WindowingEducationProto
+    ): Boolean = windowingEducationProto.hasAppHandleHintViewedTimestampMillis()
 
-  private fun isFeatureUsedBefore(windowingEducationProto: WindowingEducationProto): Boolean =
-      windowingEducationProto.hasFeatureUsedTimestampMillis()
+    private fun isAppHandleHintUsedBefore(
+        windowingEducationProto: WindowingEducationProto
+    ): Boolean = windowingEducationProto.hasAppHandleHintUsedTimestampMillis()
 
-  private suspend fun hasMinAppUsage(
-      windowingEducationProto: WindowingEducationProto,
-      focusAppPackageName: String
-  ): Boolean =
-      (launchCountByPackageName(windowingEducationProto)[focusAppPackageName] ?: 0) >=
-          context.resources.getInteger(R.integer.desktop_windowing_education_min_app_launch_count)
+    private suspend fun hasMinAppUsage(
+        windowingEducationProto: WindowingEducationProto,
+        focusAppPackageName: String,
+    ): Boolean =
+        (launchCountByPackageName(windowingEducationProto)[focusAppPackageName] ?: 0) >=
+            context.resources.getInteger(R.integer.desktop_windowing_education_min_app_launch_count)
 
-  private suspend fun launchCountByPackageName(
-      windowingEducationProto: WindowingEducationProto
-  ): Map<String, Int> =
-      if (isAppUsageCacheStale(windowingEducationProto)) {
-        // Query and return user stats, update cache in datastore
-        getAndCacheAppUsageStats()
-      } else {
-        // Return cached usage stats
-        windowingEducationProto.appHandleEducation.appUsageStatsMap
-      }
+    private suspend fun launchCountByPackageName(
+        windowingEducationProto: WindowingEducationProto
+    ): Map<String, Int> =
+        if (isAppUsageCacheStale(windowingEducationProto)) {
+            // Query and return user stats, update cache in datastore
+            getAndCacheAppUsageStats()
+        } else {
+            // Return cached usage stats
+            windowingEducationProto.appHandleEducation.appUsageStatsMap
+        }
 
-  private fun isAppUsageCacheStale(windowingEducationProto: WindowingEducationProto): Boolean {
-    val currentTime = currentTimeInDuration()
-    val lastUpdateTime =
-        Duration.ofMillis(
-            windowingEducationProto.appHandleEducation.appUsageStatsLastUpdateTimestampMillis)
-    val appUsageStatsCachingInterval =
-        convertIntegerResourceToDuration(
-            R.integer.desktop_windowing_education_app_usage_cache_interval_seconds)
-    return (currentTime - lastUpdateTime) > appUsageStatsCachingInterval
-  }
+    private fun isAppUsageCacheStale(windowingEducationProto: WindowingEducationProto): Boolean {
+        val currentTime = currentTimeInDuration()
+        val lastUpdateTime =
+            Duration.ofMillis(
+                windowingEducationProto.appHandleEducation.appUsageStatsLastUpdateTimestampMillis
+            )
+        val appUsageStatsCachingInterval =
+            convertIntegerResourceToDuration(
+                R.integer.desktop_windowing_education_app_usage_cache_interval_seconds
+            )
+        return (currentTime - lastUpdateTime) > appUsageStatsCachingInterval
+    }
 
-  private suspend fun getAndCacheAppUsageStats(): Map<String, Int> {
-    val currentTime = currentTimeInDuration()
-    val appUsageStats = queryAppUsageStats()
-    appHandleEducationDatastoreRepository.updateAppUsageStats(appUsageStats, currentTime)
-    return appUsageStats
-  }
+    private suspend fun getAndCacheAppUsageStats(): Map<String, Int> {
+        val currentTime = currentTimeInDuration()
+        val appUsageStats = queryAppUsageStats()
+        appHandleEducationDatastoreRepository.updateAppUsageStats(appUsageStats, currentTime)
+        return appUsageStats
+    }
 
-  private fun queryAppUsageStats(): Map<String, Int> {
-    val endTime = currentTimeInDuration()
-    val appLaunchInterval =
-        convertIntegerResourceToDuration(
-            R.integer.desktop_windowing_education_app_launch_interval_seconds)
-    val startTime = endTime - appLaunchInterval
+    private fun queryAppUsageStats(): Map<String, Int> {
+        val endTime = currentTimeInDuration()
+        val appLaunchInterval =
+            convertIntegerResourceToDuration(
+                R.integer.desktop_windowing_education_app_launch_interval_seconds
+            )
+        val startTime = endTime - appLaunchInterval
 
-    return usageStatsManager
-        .queryAndAggregateUsageStats(startTime.toMillis(), endTime.toMillis())
-        .mapValues { it.value.appLaunchCount }
-  }
+        return usageStatsManager
+            .queryAndAggregateUsageStats(startTime.toMillis(), endTime.toMillis())
+            .mapValues { it.value.appLaunchCount }
+    }
 
-  private fun convertIntegerResourceToDuration(@IntegerRes resourceId: Int): Duration =
-      Duration.ofSeconds(context.resources.getInteger(resourceId).toLong())
+    private fun convertIntegerResourceToDuration(@IntegerRes resourceId: Int): Duration =
+        Duration.ofSeconds(context.resources.getInteger(resourceId).toLong())
 
-  private fun currentTimeInDuration(): Duration = Duration.ofMillis(System.currentTimeMillis())
+    private fun currentTimeInDuration(): Duration = Duration.ofMillis(System.currentTimeMillis())
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
index 693da81..ac0a627 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
@@ -53,8 +53,8 @@
 /**
  * Controls App-to-Web education end to end.
  *
- * Listen to usages of App-to-Web, calls an api to check if the education
- * should be shown and controls education UI.
+ * Listen to usages of App-to-Web, calls an api to check if the education should be shown and
+ * controls education UI.
  */
 @OptIn(kotlinx.coroutines.FlowPreview::class)
 @kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -88,8 +88,9 @@
                                 .debounce(APP_TO_WEB_EDUCATION_DELAY_MILLIS)
                                 .filter { captionState ->
                                     captionState !is CaptionState.NoCaption &&
-                                            appToWebEducationFilter
-                                                .shouldShowAppToWebEducation(captionState)
+                                        appToWebEducationFilter.shouldShowAppToWebEducation(
+                                            captionState
+                                        )
                                 }
                         }
                     }
@@ -104,18 +105,23 @@
 
             applicationCoroutineScope.launch {
                 if (isFeatureUsed()) return@launch
-                windowDecorCaptionHandleRepository.appToWebUsageFlow
-                    .collect {
-                        // If user utilizes App-to-Web, mark user has used the feature
-                        appToWebEducationDatastoreRepository
-                            .updateFeatureUsedTimestampMillis(isViewed = true)
-                    }
+                windowDecorCaptionHandleRepository.appToWebUsageFlow.collect {
+                    // If user utilizes App-to-Web, mark user has used the feature
+                    appToWebEducationDatastoreRepository.updateFeatureUsedTimestampMillis(
+                        isViewed = true
+                    )
+                }
             }
         }
     }
 
     private inline fun runIfEducationFeatureEnabled(block: () -> Unit) {
-        if (canEnterDesktopMode(context) && Flags.enableDesktopWindowingAppToWebEducation()) block()
+        if (
+            canEnterDesktopMode(context) &&
+                Flags.enableDesktopWindowingAppToWebEducationIntegration()
+        ) {
+            block()
+        }
     }
 
     private fun showEducation(captionState: CaptionState, colorScheme: EducationColorScheme) {
@@ -126,10 +132,8 @@
                 val appHandleBounds = captionState.globalAppHandleBounds
                 val educationWidth =
                     loadDimensionPixelSize(R.dimen.desktop_windowing_education_promo_width)
-                educationGlobalCoordinates = Point(
-                    appHandleBounds.centerX() - educationWidth / 2,
-                    appHandleBounds.bottom
-                )
+                educationGlobalCoordinates =
+                    Point(appHandleBounds.centerX() - educationWidth / 2, appHandleBounds.bottom)
                 taskId = captionState.runningTaskInfo.taskId
             }
 
@@ -152,19 +156,22 @@
                 viewGlobalCoordinates = educationGlobalCoordinates,
                 educationText = getString(R.string.desktop_windowing_app_to_web_education_text),
                 widthId = R.dimen.desktop_windowing_education_promo_width,
-                heightId = R.dimen.desktop_windowing_education_promo_height
+                heightId = R.dimen.desktop_windowing_education_promo_height,
             )
 
         windowingEducationViewController.showEducation(
-            viewConfig = educationConfig, taskId = taskId)
+            viewConfig = educationConfig,
+            taskId = taskId,
+        )
     }
 
     private fun educationColorScheme(captionState: CaptionState): EducationColorScheme? {
-        val taskInfo: RunningTaskInfo = when (captionState) {
-            is CaptionState.AppHandle -> captionState.runningTaskInfo
-            is CaptionState.AppHeader -> captionState.runningTaskInfo
-            else -> return null
-        }
+        val taskInfo: RunningTaskInfo =
+            when (captionState) {
+                is CaptionState.AppHandle -> captionState.runningTaskInfo
+                is CaptionState.AppHeader -> captionState.runningTaskInfo
+                else -> return null
+            }
 
         val colorScheme = decorThemeUtil.getColorScheme(taskInfo)
         val tooltipContainerColor = colorScheme.surfaceBright.toArgb()
@@ -178,8 +185,7 @@
      */
     private fun isEducationViewLimitReachedFlow(): Flow<Boolean> =
         appToWebEducationDatastoreRepository.dataStoreFlow
-            .map { preferences ->
-                appToWebEducationFilter.isEducationViewLimitReached(preferences)}
+            .map { preferences -> appToWebEducationFilter.isEducationViewLimitReached(preferences) }
             .distinctUntilChanged()
 
     /**
@@ -199,9 +205,6 @@
     companion object {
         const val TAG = "AppToWebEducationController"
         val APP_TO_WEB_EDUCATION_DELAY_MILLIS: Long
-            get() = SystemProperties.getLong(
-                "persist.windowing_app_handle_education_delay",
-                3000L
-            )
+            get() = SystemProperties.getLong("persist.windowing_app_handle_education_delay", 3000L)
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt
index feee6ed..e272b54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt
@@ -30,39 +30,41 @@
 /** Filters incoming App-to-Web education triggers based on set conditions. */
 class AppToWebEducationFilter(
     private val context: Context,
-    private val appToWebEducationDatastoreRepository: AppToWebEducationDatastoreRepository
+    private val appToWebEducationDatastoreRepository: AppToWebEducationDatastoreRepository,
 ) {
 
     /** Returns true if conditions to show App-to-web education are met, returns false otherwise. */
     suspend fun shouldShowAppToWebEducation(captionState: CaptionState): Boolean {
-        val (taskInfo: RunningTaskInfo, isCapturedLinkAvailable: Boolean) = when (captionState) {
-            is CaptionState.AppHandle ->
-                Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
-            is CaptionState.AppHeader ->
-                Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
-            else -> return false
-        }
+        val (taskInfo: RunningTaskInfo, isCapturedLinkAvailable: Boolean) =
+            when (captionState) {
+                is CaptionState.AppHandle ->
+                    Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
+                is CaptionState.AppHeader ->
+                    Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
+                else -> return false
+            }
 
         val focusAppPackageName = taskInfo.topActivityInfo?.packageName ?: return false
         val windowingEducationProto = appToWebEducationDatastoreRepository.windowingEducationProto()
 
         return !isOtherEducationShowing() &&
-                !isEducationViewLimitReached(windowingEducationProto) &&
-                hasSufficientTimeSinceSetup() &&
-                !isFeatureUsedBefore(windowingEducationProto) &&
-                isCapturedLinkAvailable &&
-                isFocusAppInAllowlist(focusAppPackageName)
+            !isEducationViewLimitReached(windowingEducationProto) &&
+            hasSufficientTimeSinceSetup() &&
+            !isFeatureUsedBefore(windowingEducationProto) &&
+            isCapturedLinkAvailable &&
+            isFocusAppInAllowlist(focusAppPackageName)
     }
 
     private fun isFocusAppInAllowlist(focusAppPackageName: String): Boolean =
         focusAppPackageName in
-                context.resources.getStringArray(
-                    R.array.desktop_windowing_app_to_web_education_allowlist_apps)
+            context.resources.getStringArray(
+                R.array.desktop_windowing_app_to_web_education_allowlist_apps
+            )
 
     // TODO: b/350953004 - Add checks based on App compat
     // TODO: b/350951797 - Add checks based on PKT tips education
-    private fun isOtherEducationShowing(): Boolean = isTaskbarEducationShowing() ||
-            isCompatUiEducationShowing()
+    private fun isOtherEducationShowing(): Boolean =
+        isTaskbarEducationShowing() || isCompatUiEducationShowing()
 
     private fun isTaskbarEducationShowing(): Boolean =
         Secure.getInt(context.contentResolver, Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0) == 1
@@ -72,13 +74,14 @@
 
     private fun hasSufficientTimeSinceSetup(): Boolean =
         Duration.ofMillis(SystemClock.elapsedRealtime()) >
-                convertIntegerResourceToDuration(
-                    R.integer.desktop_windowing_education_required_time_since_setup_seconds)
+            convertIntegerResourceToDuration(
+                R.integer.desktop_windowing_education_required_time_since_setup_seconds
+            )
 
     /** Returns true if education is viewed maximum amount of times it should be shown. */
     fun isEducationViewLimitReached(windowingEducationProto: WindowingEducationProto): Boolean =
         windowingEducationProto.getAppToWebEducation().getEducationShownCount() >=
-                MAXIMUM_TIMES_EDUCATION_SHOWN
+            MAXIMUM_TIMES_EDUCATION_SHOWN
 
     private fun isFeatureUsedBefore(windowingEducationProto: WindowingEducationProto): Boolean =
         windowingEducationProto.hasFeatureUsedTimestampMillis()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
index d21b208..3e120b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
@@ -42,101 +42,109 @@
 class AppHandleEducationDatastoreRepository
 @VisibleForTesting
 constructor(private val dataStore: DataStore<WindowingEducationProto>) {
-  constructor(
-      context: Context
-  ) : this(
-      DataStoreFactory.create(
-          serializer = WindowingEducationProtoSerializer,
-          produceFile = { context.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_FILEPATH) }))
+    constructor(
+        context: Context
+    ) : this(
+        DataStoreFactory.create(
+            serializer = WindowingEducationProtoSerializer,
+            produceFile = { context.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_FILEPATH) },
+        )
+    )
 
-  /** Provides dataStore.data flow and handles exceptions thrown during collection */
-  val dataStoreFlow: Flow<WindowingEducationProto> =
-      dataStore.data.catch { exception ->
-        // dataStore.data throws an IOException when an error is encountered when reading data
-        if (exception is IOException) {
-          Log.e(
-              TAG,
-              "Error in reading app handle education related data from datastore, data is " +
-                  "stored in a file named $APP_HANDLE_EDUCATION_DATASTORE_FILEPATH",
-              exception)
-        } else {
-          throw exception
+    /** Provides dataStore.data flow and handles exceptions thrown during collection */
+    val dataStoreFlow: Flow<WindowingEducationProto> =
+        dataStore.data.catch { exception ->
+            // dataStore.data throws an IOException when an error is encountered when reading data
+            if (exception is IOException) {
+                Log.e(
+                    TAG,
+                    "Error in reading app handle education related data from datastore, data is " +
+                        "stored in a file named $APP_HANDLE_EDUCATION_DATASTORE_FILEPATH",
+                    exception,
+                )
+            } else {
+                throw exception
+            }
         }
-      }
 
-  /**
-   * Reads and returns the [WindowingEducationProto] Proto object from the DataStore. If the
-   * DataStore is empty or there's an error reading, it returns the default value of Proto.
-   */
-  suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first()
+    /**
+     * Reads and returns the [WindowingEducationProto] Proto object from the DataStore. If the
+     * DataStore is empty or there's an error reading, it returns the default value of Proto.
+     */
+    suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first()
 
-  /**
-   * Updates [WindowingEducationProto.educationViewedTimestampMillis_] field in datastore with
-   * current timestamp if [isViewed] is true, if not then clears the field.
-   */
-  suspend fun updateEducationViewedTimestampMillis(isViewed: Boolean) {
-    dataStore.updateData { preferences ->
-      if (isViewed) {
-        preferences
-            .toBuilder()
-            .setEducationViewedTimestampMillis(System.currentTimeMillis())
-            .build()
-      } else {
-        preferences.toBuilder().clearEducationViewedTimestampMillis().build()
-      }
+    /**
+     * Updates [WindowingEducationProto.appHandleHintViewedTimestampMillis_] field in datastore with
+     * current timestamp if [isViewed] is true, if not then clears the field.
+     */
+    suspend fun updateAppHandleHintViewedTimestampMillis(isViewed: Boolean) {
+        dataStore.updateData { preferences ->
+            if (isViewed) {
+                preferences
+                    .toBuilder()
+                    .setAppHandleHintViewedTimestampMillis(System.currentTimeMillis())
+                    .build()
+            } else {
+                preferences.toBuilder().clearAppHandleHintViewedTimestampMillis().build()
+            }
+        }
     }
-  }
 
-  /**
-   * Updates [WindowingEducationProto.featureUsedTimestampMillis_] field in datastore with current
-   * timestamp if [isViewed] is true, if not then clears the field.
-   */
-  suspend fun updateFeatureUsedTimestampMillis(isViewed: Boolean) {
-    dataStore.updateData { preferences ->
-      if (isViewed) {
-        preferences.toBuilder().setFeatureUsedTimestampMillis(System.currentTimeMillis()).build()
-      } else {
-        preferences.toBuilder().clearFeatureUsedTimestampMillis().build()
-      }
+    /**
+     * Updates [WindowingEducationProto.appHandleHintUsedTimestampMillis_] field in datastore with
+     * current timestamp if [isViewed] is true, if not then clears the field.
+     */
+    suspend fun updateAppHandleHintUsedTimestampMillis(isViewed: Boolean) {
+        dataStore.updateData { preferences ->
+            if (isViewed) {
+                preferences
+                    .toBuilder()
+                    .setAppHandleHintUsedTimestampMillis(System.currentTimeMillis())
+                    .build()
+            } else {
+                preferences.toBuilder().clearAppHandleHintUsedTimestampMillis().build()
+            }
+        }
     }
-  }
 
-  /**
-   * Updates [AppHandleEducation.appUsageStats] and
-   * [AppHandleEducation.appUsageStatsLastUpdateTimestampMillis] fields in datastore with
-   * [appUsageStats] and [appUsageStatsLastUpdateTimestamp].
-   */
-  suspend fun updateAppUsageStats(
-      appUsageStats: Map<String, Int>,
-      appUsageStatsLastUpdateTimestamp: Duration
-  ) {
-    val currentAppHandleProto = windowingEducationProto().appHandleEducation.toBuilder()
-    currentAppHandleProto
-        .putAllAppUsageStats(appUsageStats)
-        .setAppUsageStatsLastUpdateTimestampMillis(appUsageStatsLastUpdateTimestamp.toMillis())
-    dataStore.updateData { preferences: WindowingEducationProto ->
-      preferences.toBuilder().setAppHandleEducation(currentAppHandleProto).build()
+    /**
+     * Updates [AppHandleEducation.appUsageStats] and
+     * [AppHandleEducation.appUsageStatsLastUpdateTimestampMillis] fields in datastore with
+     * [appUsageStats] and [appUsageStatsLastUpdateTimestamp].
+     */
+    suspend fun updateAppUsageStats(
+        appUsageStats: Map<String, Int>,
+        appUsageStatsLastUpdateTimestamp: Duration,
+    ) {
+        val currentAppHandleProto = windowingEducationProto().appHandleEducation.toBuilder()
+        currentAppHandleProto
+            .putAllAppUsageStats(appUsageStats)
+            .setAppUsageStatsLastUpdateTimestampMillis(appUsageStatsLastUpdateTimestamp.toMillis())
+        dataStore.updateData { preferences: WindowingEducationProto ->
+            preferences.toBuilder().setAppHandleEducation(currentAppHandleProto).build()
+        }
     }
-  }
 
-  companion object {
-    private const val TAG = "AppHandleEducationDatastoreRepository"
-    private const val APP_HANDLE_EDUCATION_DATASTORE_FILEPATH = "app_handle_education.pb"
+    companion object {
+        private const val TAG = "AppHandleEducationDatastoreRepository"
+        private const val APP_HANDLE_EDUCATION_DATASTORE_FILEPATH = "app_handle_education.pb"
 
-    object WindowingEducationProtoSerializer : Serializer<WindowingEducationProto> {
+        object WindowingEducationProtoSerializer : Serializer<WindowingEducationProto> {
 
-      override val defaultValue: WindowingEducationProto =
-          WindowingEducationProto.getDefaultInstance()
+            override val defaultValue: WindowingEducationProto =
+                WindowingEducationProto.getDefaultInstance()
 
-      override suspend fun readFrom(input: InputStream): WindowingEducationProto =
-          try {
-            WindowingEducationProto.parseFrom(input)
-          } catch (exception: InvalidProtocolBufferException) {
-            throw CorruptionException("Cannot read proto.", exception)
-          }
+            override suspend fun readFrom(input: InputStream): WindowingEducationProto =
+                try {
+                    WindowingEducationProto.parseFrom(input)
+                } catch (exception: InvalidProtocolBufferException) {
+                    throw CorruptionException("Cannot read proto.", exception)
+                }
 
-      override suspend fun writeTo(windowingProto: WindowingEducationProto, output: OutputStream) =
-          windowingProto.writeTo(output)
+            override suspend fun writeTo(
+                windowingProto: WindowingEducationProto,
+                output: OutputStream,
+            ) = windowingProto.writeTo(output)
+        }
     }
-  }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt
index 8be6e6d..e5ad901 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt
@@ -25,12 +25,12 @@
 import androidx.datastore.dataStoreFile
 import com.android.framework.protobuf.InvalidProtocolBufferException
 import com.android.internal.annotations.VisibleForTesting
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.first
 import java.io.IOException
 import java.io.InputStream
 import java.io.OutputStream
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.first
 
 /** Updates data in App-to-Web's education datastore. */
 class AppToWebEducationDatastoreRepository
@@ -41,7 +41,9 @@
     ) : this(
         DataStoreFactory.create(
             serializer = WindowingEducationProtoSerializer,
-            produceFile = { context.dataStoreFile(APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH) }))
+            produceFile = { context.dataStoreFile(APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH) },
+        )
+    )
 
     /** Provides dataStore.data flow and handles exceptions thrown during collection */
     val dataStoreFlow: Flow<WindowingEducationProto> =
@@ -51,8 +53,10 @@
                 Slog.e(
                     TAG,
                     "Error in reading App-to-Web education related data from datastore," +
-                            "data is stored in a file named" +
-                            "$APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH", exception)
+                        "data is stored in a file named" +
+                        "$APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH",
+                    exception,
+                )
             } else {
                 throw exception
             }
@@ -72,26 +76,26 @@
         dataStore.updateData { preferences ->
             if (isViewed) {
                 preferences
-                    .toBuilder().setFeatureUsedTimestampMillis(System.currentTimeMillis()).build()
+                    .toBuilder()
+                    .setFeatureUsedTimestampMillis(System.currentTimeMillis())
+                    .build()
             } else {
                 preferences.toBuilder().clearFeatureUsedTimestampMillis().build()
             }
         }
     }
 
-    /**
-     * Increases [AppToWebEducation.educationShownCount] field by one.
-     */
+    /** Increases [AppToWebEducation.educationShownCount] field by one. */
     suspend fun updateEducationShownCount() {
         val currentAppHandleProto = windowingEducationProto().appToWebEducation.toBuilder()
-        currentAppHandleProto
-            .setEducationShownCount(currentAppHandleProto.getEducationShownCount() + 1)
+        currentAppHandleProto.setEducationShownCount(
+            currentAppHandleProto.getEducationShownCount() + 1
+        )
         dataStore.updateData { preferences ->
             preferences.toBuilder().setAppToWebEducation(currentAppHandleProto).build()
         }
     }
 
-
     companion object {
         private const val TAG = "AppToWebEducationDatastoreRepository"
         private const val APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH = "app_to_web_education.pb"
@@ -110,7 +114,7 @@
 
             override suspend fun writeTo(
                 windowingProto: WindowingEducationProto,
-                output: OutputStream
+                output: OutputStream,
             ) = windowingProto.writeTo(output)
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto
index 4cddd01..0c4d562 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto
@@ -22,9 +22,19 @@
 // Desktop Windowing education data
 message WindowingEducationProto {
   // Timestamp in milliseconds of when the education was last viewed.
-  optional int64 education_viewed_timestamp_millis = 1;
+  optional int64 education_viewed_timestamp_millis = 1 [deprecated=true];
   // Timestamp in milliseconds of when the feature was last used.
-  optional int64 feature_used_timestamp_millis = 2;
+  optional int64 feature_used_timestamp_millis = 2 [deprecated=true];
+
+  // Timestamp in milliseconds of when the app handle hint was last viewed.
+  optional int64 app_handle_hint_viewed_timestamp_millis = 5;
+  // Timestamp in milliseconds of when the app handle hint was last used.
+  optional int64 app_handle_hint_used_timestamp_millis = 6;
+  // Timestamp in milliseconds of when the enter desktop mode hint was last viewed.
+  optional int64 enter_desktop_mode_hint_viewed_timestamp_millis = 7;
+  // Timestamp in milliseconds of when the exit desktop mode hint was last viewed.
+  optional int64 exit_desktop_mode_hint_viewed_timestamp_millis = 8;
+
   oneof education_data {
     // Fields specific to app handle education
     AppHandleEducation app_handle_education = 3;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandler.kt
index 7554cbb..4298bd2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandler.kt
@@ -43,7 +43,7 @@
     private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
     remoteTransition: RemoteTransition,
     private val taskIdToMinimize: Int,
-    ) : TransitionHandler {
+) : TransitionHandler {
 
     private val oneShotRemoteHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
     private var transition: IBinder? = null
@@ -56,7 +56,7 @@
 
     override fun handleRequest(
         transition: IBinder,
-        request: TransitionRequestInfo
+        request: TransitionRequestInfo,
     ): WindowContainerTransaction? {
         this.transition = transition
         return oneShotRemoteHandler.handleRequest(transition, request)
@@ -67,7 +67,7 @@
         info: TransitionInfo,
         startTransaction: SurfaceControl.Transaction,
         finishTransaction: SurfaceControl.Transaction,
-        finishCallback: Transitions.TransitionFinishCallback
+        finishCallback: Transitions.TransitionFinishCallback,
     ): Boolean {
         if (transition != this.transition) return false
         val minimizeChange = findMinimizeChange(info, taskIdToMinimize) ?: return false
@@ -76,7 +76,12 @@
         // have access to RootTaskDisplayAreaOrganizer.
         applyMinimizeChangeReparenting(info, minimizeChange, startTransaction)
         return oneShotRemoteHandler.startAnimation(
-            transition, info, startTransaction, finishTransaction, finishCallback)
+            transition,
+            info,
+            startTransaction,
+            finishTransaction,
+            finishCallback,
+        )
     }
 
     private fun applyMinimizeChangeReparenting(
@@ -87,14 +92,15 @@
         val taskInfo = minimizeChange.taskInfo ?: return
         if (taskInfo.isFreeform && TransitionUtil.isOpeningMode(info.type)) {
             rootTaskDisplayAreaOrganizer.reparentToDisplayArea(
-                taskInfo.displayId, minimizeChange.leash, startTransaction)
+                taskInfo.displayId,
+                minimizeChange.leash,
+                startTransaction,
+            )
         }
     }
 
-    private fun findMinimizeChange(
-        info: TransitionInfo,
-        taskIdToMinimize: Int,
-    ): Change? =
+    private fun findMinimizeChange(info: TransitionInfo, taskIdToMinimize: Int): Change? =
         info.changes.find { change ->
-            change.taskInfo?.taskId == taskIdToMinimize && change.mode == TRANSIT_TO_BACK }
+            change.taskInfo?.taskId == taskIdToMinimize && change.mode == TRANSIT_TO_BACK
+        }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
index 9e646f4..a6998e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
@@ -40,9 +40,7 @@
  *
  * The main constructor is public only for testing purposes.
  */
-class DesktopPersistentRepository(
-    private val dataStore: DataStore<DesktopPersistentRepositories>,
-) {
+class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersistentRepositories>) {
     constructor(
         context: Context,
         @ShellBackgroundThread bgCoroutineScope: CoroutineScope,
@@ -51,7 +49,7 @@
             serializer = DesktopPersistentRepositoriesSerializer,
             produceFile = { context.dataStoreFile(DESKTOP_REPOSITORIES_DATASTORE_FILE) },
             scope = bgCoroutineScope,
-        ),
+        )
     )
 
     /** Provides `dataStore.data` flow and handles exceptions thrown during collection */
@@ -63,7 +61,8 @@
                     TAG,
                     "Error in reading desktop mode related data from datastore, data is " +
                         "stored in a file named $DESKTOP_REPOSITORIES_DATASTORE_FILE",
-                    exception)
+                    exception,
+                )
             } else {
                 throw exception
             }
@@ -73,13 +72,17 @@
      * Reads and returns the [DesktopRepositoryState] proto object from the DataStore for a user. If
      * the DataStore is empty or there's an error reading, it returns the default value of Proto.
      */
-    private suspend fun getDesktopRepositoryState(
-        userId: Int = DEFAULT_USER_ID
-    ): DesktopRepositoryState? =
+    suspend fun getDesktopRepositoryState(userId: Int): DesktopRepositoryState? =
         try {
-            dataStoreFlow
-                .first()
-                .desktopRepoByUserMap[userId]
+            dataStoreFlow.first().desktopRepoByUserMap[userId]
+        } catch (e: Exception) {
+            Log.e(TAG, "Unable to read from datastore", e)
+            null
+        }
+
+    suspend fun getUserDesktopRepositoryMap(): Map<Int, DesktopRepositoryState>? =
+        try {
+            dataStoreFlow.first().desktopRepoByUserMap
         } catch (e: Exception) {
             Log.e(TAG, "Unable to read from datastore", e)
             null
@@ -89,10 +92,7 @@
      * Reads the [Desktop] of a desktop filtering by the [userId] and [desktopId]. Executes the
      * [callback] using the [mainCoroutineScope].
      */
-    suspend fun readDesktop(
-        userId: Int = DEFAULT_USER_ID,
-        desktopId: Int = DEFAULT_DESKTOP_ID,
-    ): Desktop? =
+    suspend fun readDesktop(userId: Int, desktopId: Int = DEFAULT_DESKTOP_ID): Desktop? =
         try {
             val repository = getDesktopRepositoryState(userId)
             repository?.getDesktopOrThrow(desktopId)
@@ -103,7 +103,7 @@
 
     /** Adds or updates a desktop stored in the datastore */
     suspend fun addOrUpdateDesktop(
-        userId: Int = DEFAULT_USER_ID,
+        userId: Int,
         desktopId: Int = 0,
         visibleTasks: ArraySet<Int> = ArraySet(),
         minimizedTasks: ArraySet<Int> = ArraySet(),
@@ -111,36 +111,33 @@
     ) {
         // TODO: b/367609270 - Improve the API to support multi-user
         try {
-            dataStore.updateData { desktopPersistentRepositories: DesktopPersistentRepositories ->
+            dataStore.updateData { persistentRepositories: DesktopPersistentRepositories ->
                 val currentRepository =
-                    desktopPersistentRepositories.getDesktopRepoByUserOrDefault(
-                        userId, DesktopRepositoryState.getDefaultInstance())
+                    persistentRepositories.getDesktopRepoByUserOrDefault(
+                        userId,
+                        DesktopRepositoryState.getDefaultInstance(),
+                    )
                 val desktop =
                     getDesktop(currentRepository, desktopId)
                         .toBuilder()
-                        .updateTaskStates(
-                            visibleTasks,
-                            minimizedTasks,
-                            freeformTasksInZOrder,
-                        )
+                        .updateTaskStates(visibleTasks, minimizedTasks, freeformTasksInZOrder)
                         .updateZOrder(freeformTasksInZOrder)
 
-                desktopPersistentRepositories
+                persistentRepositories
                     .toBuilder()
                     .putDesktopRepoByUser(
                         userId,
-                        currentRepository
-                            .toBuilder()
-                            .putDesktop(desktopId, desktop.build())
-                            .build())
+                        currentRepository.toBuilder().putDesktop(desktopId, desktop.build()).build(),
+                    )
                     .build()
             }
-        } catch (exception: IOException) {
+        } catch (exception: Exception) {
             Log.e(
                 TAG,
                 "Error in updating desktop mode related data, data is " +
                     "stored in a file named $DESKTOP_REPOSITORIES_DATASTORE_FILE",
-                exception)
+                exception,
+            )
         }
     }
 
@@ -148,13 +145,13 @@
         // If there are no desktops set up, create one on the default display
         currentRepository.getDesktopOrDefault(
             desktopId,
-            Desktop.newBuilder().setDesktopId(desktopId).setDisplayId(DEFAULT_DISPLAY).build())
+            Desktop.newBuilder().setDesktopId(desktopId).setDisplayId(DEFAULT_DISPLAY).build(),
+        )
 
     companion object {
         private const val TAG = "DesktopPersistenceRepo"
         private const val DESKTOP_REPOSITORIES_DATASTORE_FILE = "desktop_persistent_repositories.pb"
 
-        private const val DEFAULT_USER_ID = 1000
         private const val DEFAULT_DESKTOP_ID = 0
 
         object DesktopPersistentRepositoriesSerializer : Serializer<DesktopPersistentRepositories> {
@@ -185,19 +182,22 @@
             // visible, they will be marked as not visible afterwards. This ensures that they are
             // still persisted as visible.
             // TODO - b/350476823: Remove this logic once repository holds expanded tasks
-            if (freeformTasksInZOrder.size > visibleTasks.size + minimizedTasks.size &&
-                visibleTasks.isEmpty()
+            if (
+                freeformTasksInZOrder.size > visibleTasks.size + minimizedTasks.size &&
+                    visibleTasks.isEmpty()
             ) {
                 visibleTasks.addAll(freeformTasksInZOrder.filterNot { it in minimizedTasks })
             }
             putAllTasksByTaskId(
                 visibleTasks.associateWith {
                     createDesktopTask(it, state = DesktopTaskState.VISIBLE)
-                })
+                }
+            )
             putAllTasksByTaskId(
                 minimizedTasks.associateWith {
                     createDesktopTask(it, state = DesktopTaskState.MINIMIZED)
-                })
+                }
+            )
             return this
         }
 
@@ -211,7 +211,7 @@
 
         private fun createDesktopTask(
             taskId: Int,
-            state: DesktopTaskState = DesktopTaskState.VISIBLE
+            state: DesktopTaskState = DesktopTaskState.VISIBLE,
         ): DesktopTask =
             DesktopTask.newBuilder().setTaskId(taskId).setDesktopTaskState(state).build()
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt
index 771c3d1..a26ebbf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt
@@ -16,9 +16,9 @@
 
 package com.android.wm.shell.desktopmode.persistence
 
-import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
 
-/** Interface for initializing the [DesktopRepository]. */
+/** Interface for initializing the [DesktopUserRepositories]. */
 fun interface DesktopRepositoryInitializer {
-    fun initialize(repository: DesktopRepository)
+    fun initialize(userRepositories: DesktopUserRepositories)
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
index d815656..58a49a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.window.DesktopModeFlags
 import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
 import com.android.wm.shell.shared.annotations.ShellMainThread
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import kotlinx.coroutines.CoroutineScope
@@ -35,32 +36,50 @@
     private val persistentRepository: DesktopPersistentRepository,
     @ShellMainThread private val mainCoroutineScope: CoroutineScope,
 ) : DesktopRepositoryInitializer {
-    override fun initialize(repository: DesktopRepository) {
+    override fun initialize(userRepositories: DesktopUserRepositories) {
         if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) return
         //  TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized
         mainCoroutineScope.launch {
-            val desktop = persistentRepository.readDesktop() ?: return@launch
-
-            val maxTasks =
-                DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
-                    ?: desktop.zOrderedTasksCount
-
-            var visibleTasksCount = 0
-            desktop.zOrderedTasksList
-                // Reverse it so we initialize the repo from bottom to top.
-                .reversed()
-                .mapNotNull { taskId -> desktop.tasksByTaskIdMap[taskId] }
-                .forEach { task ->
-                    if (task.desktopTaskState == DesktopTaskState.VISIBLE
-                        && visibleTasksCount < maxTasks
-                    ) {
-                        visibleTasksCount++
-                        repository.addTask(desktop.displayId, task.taskId, isVisible = false)
-                    } else {
-                        repository.addTask(desktop.displayId, task.taskId, isVisible = false)
-                        repository.minimizeTask(desktop.displayId, task.taskId)
-                    }
+            val desktopUserPersistentRepositoryMap =
+                persistentRepository.getUserDesktopRepositoryMap() ?: return@launch
+            for (userId in desktopUserPersistentRepositoryMap.keys) {
+                val repository = userRepositories.getProfile(userId)
+                val desktopRepositoryState =
+                    persistentRepository.getDesktopRepositoryState(userId) ?: continue
+                val desktopByDesktopIdMap = desktopRepositoryState.desktopMap
+                for (desktopId in desktopByDesktopIdMap.keys) {
+                    val persistentDesktop =
+                        persistentRepository.readDesktop(userId, desktopId) ?: continue
+                    val maxTasks =
+                        DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
+                            ?: persistentDesktop.zOrderedTasksCount
+                    var visibleTasksCount = 0
+                    persistentDesktop.zOrderedTasksList
+                        // Reverse it so we initialize the repo from bottom to top.
+                        .reversed()
+                        .mapNotNull { taskId -> persistentDesktop.tasksByTaskIdMap[taskId] }
+                        .forEach { task ->
+                            if (
+                                task.desktopTaskState == DesktopTaskState.VISIBLE &&
+                                    visibleTasksCount < maxTasks
+                            ) {
+                                visibleTasksCount++
+                                repository.addTask(
+                                    persistentDesktop.displayId,
+                                    task.taskId,
+                                    isVisible = false,
+                                )
+                            } else {
+                                repository.addTask(
+                                    persistentDesktop.displayId,
+                                    task.taskId,
+                                    isVisible = false,
+                                )
+                                repository.minimizeTask(persistentDesktop.displayId, task.taskId)
+                            }
+                        }
                 }
+            }
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 22e8dc1..491b577 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -31,8 +31,6 @@
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
-
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.PendingIntent;
@@ -167,7 +165,7 @@
         mMainExecutor.executeDelayed(() -> {
             mDisplayController.addDisplayWindowListener(this);
         }, 0);
-        mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP,
+        mShellController.addExternalInterface(IDragAndDrop.DESCRIPTOR,
                 this::createExternalInterface, this);
         mShellTaskOrganizer.addTaskVanishedListener(this);
         mShellCommandHandler.addDumpCallback(this::dump, this);
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 cd20d97..4b59efb 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
@@ -31,6 +31,7 @@
 import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInit;
@@ -49,7 +50,7 @@
 
     private final Context mContext;
     private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final Optional<DesktopRepository> mDesktopRepository;
+    private final Optional<DesktopUserRepositories> mDesktopUserRepositories;
     private final Optional<DesktopTasksController> mDesktopTasksController;
     private final WindowDecorViewModel mWindowDecorationViewModel;
     private final LaunchAdjacentController mLaunchAdjacentController;
@@ -61,7 +62,7 @@
             Context context,
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<DesktopRepository> desktopRepository,
+            Optional<DesktopUserRepositories> desktopUserRepositories,
             Optional<DesktopTasksController> desktopTasksController,
             LaunchAdjacentController launchAdjacentController,
             WindowDecorViewModel windowDecorationViewModel,
@@ -69,7 +70,7 @@
         mContext = context;
         mShellTaskOrganizer = shellTaskOrganizer;
         mWindowDecorationViewModel = windowDecorationViewModel;
-        mDesktopRepository = desktopRepository;
+        mDesktopUserRepositories = desktopUserRepositories;
         mDesktopTasksController = desktopTasksController;
         mLaunchAdjacentController = launchAdjacentController;
         mTaskChangeListener = taskChangeListener;
@@ -99,8 +100,9 @@
 
         if (!DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue() &&
                 DesktopModeStatus.canEnterDesktopMode(mContext)) {
-            mDesktopRepository.ifPresent(repository -> {
-                repository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible);
+            mDesktopUserRepositories.ifPresent(userRepositories -> {
+                DesktopRepository currentRepo = userRepositories.getProfile(taskInfo.userId);
+                currentRepo.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible);
             });
         }
         updateLaunchAdjacentController();
@@ -113,21 +115,20 @@
         mTasks.remove(taskInfo.taskId);
 
         if (!DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue() &&
-                DesktopModeStatus.canEnterDesktopMode(mContext)) {
-            mDesktopRepository.ifPresent(repository -> {
-                // TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
-                if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()
-                        || repository.isClosingTask(taskInfo.taskId)) {
-                    // A task that's vanishing should be removed:
-                    // - If it's closed by the X button which means it's marked as a closing task.
-                    repository.removeClosingTask(taskInfo.taskId);
-                    repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId);
-                } else {
-                    repository.updateTask(taskInfo.displayId, taskInfo.taskId, /* isVisible= */
-                            false);
-                    repository.minimizeTask(taskInfo.displayId, taskInfo.taskId);
-                }
-            });
+                DesktopModeStatus.canEnterDesktopMode(mContext)
+                && mDesktopUserRepositories.isPresent()) {
+            DesktopRepository repository =
+                    mDesktopUserRepositories.get().getProfile(taskInfo.userId);
+            // TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
+            if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()
+                    || !repository.isMinimizedTask(taskInfo.taskId)) {
+                // A task that's vanishing should be removed:
+                // - If it's not yet minimized. It can be minimized when a back navigation is
+                // triggered on a task and the task is closing. It will be marked as minimized in
+                // [DesktopTasksTransitionObserver] before it gets here.
+                repository.removeClosingTask(taskInfo.taskId);
+                repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId);
+            }
         }
         mWindowDecorationViewModel.onTaskVanished(taskInfo);
         updateLaunchAdjacentController();
@@ -148,11 +149,11 @@
                 // does not propagate all task info changes.
                 mTaskChangeListener.ifPresent(listener ->
                         listener.onNonTransitionTaskChanging(taskInfo));
-            } else {
-                mDesktopRepository.ifPresent(repository -> {
-                    repository.updateTask(taskInfo.displayId, taskInfo.taskId,
-                            taskInfo.isVisible);
-                });
+            } else if (mDesktopUserRepositories.isPresent()) {
+                DesktopRepository currentRepo =
+                        mDesktopUserRepositories.get().getProfile(taskInfo.userId);
+                currentRepo.updateTask(taskInfo.displayId, taskInfo.taskId,
+                        taskInfo.isVisible);
             }
         }
         updateLaunchAdjacentController();
@@ -176,10 +177,11 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
                 "Freeform Task Focus Changed: #%d focused=%b",
                 taskInfo.taskId, taskInfo.isFocused);
-        if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused) {
-            mDesktopRepository.ifPresent(repository -> {
-                repository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible);
-            });
+        if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused
+                && mDesktopUserRepositories.isPresent()) {
+            DesktopRepository repository =
+                mDesktopUserRepositories.get().getProfile(taskInfo.userId);
+            repository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible);
         }
     }
 
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 e4f8333..f8e6285 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
@@ -42,7 +42,6 @@
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
-import android.view.Display;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.IRemoteTransition;
@@ -64,6 +63,7 @@
 import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.FocusTransitionObserver;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
 
@@ -89,6 +89,7 @@
 
     private final ArrayMap<IBinder, StartedTransition> mStartedTransitions = new ArrayMap<>();
     private final TaskStackListenerImpl mTaskStackListener;
+    private final FocusTransitionObserver mFocusTransitionObserver;
 
     /**
      * Local IRemoteTransition implementations registered by the keyguard service.
@@ -129,7 +130,8 @@
             @NonNull Transitions transitions,
             @NonNull TaskStackListenerImpl taskStackListener,
             @NonNull Handler mainHandler,
-            @NonNull ShellExecutor mainExecutor) {
+            @NonNull ShellExecutor mainExecutor,
+            @NonNull FocusTransitionObserver focusTransitionObserver) {
         mTransitions = transitions;
         mShellController = shellController;
         mDisplayController = displayController;
@@ -137,6 +139,7 @@
         mMainExecutor = mainExecutor;
         mTaskStackListener = taskStackListener;
         shellInit.addInitCallback(this::onInit, this);
+        mFocusTransitionObserver = focusTransitionObserver;
     }
 
     private void onInit() {
@@ -396,7 +399,8 @@
             final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
             if (taskInfo != null && taskInfo.taskId != INVALID_TASK_ID
                     && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
-                    && taskInfo.isFocused && change.getContainer() != null) {
+                    && mFocusTransitionObserver.hasGlobalFocus(taskInfo)
+                    && change.getContainer() != null) {
                 wct.setWindowingMode(change.getContainer(), WINDOWING_MODE_FULLSCREEN);
                 wct.setBounds(change.getContainer(), null);
                 return;
@@ -439,10 +443,8 @@
         @Override
         public void startKeyguardTransition(boolean keyguardShowing, boolean aodShowing) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
-            for (Display display : mDisplayController.getDisplays()) {
-                wct.addKeyguardState(new KeyguardState.Builder(display.getDisplayId())
-                        .setKeyguardShowing(keyguardShowing).setAodShowing(aodShowing).build());
-            }
+            wct.addKeyguardState(new KeyguardState.Builder().setKeyguardShowing(keyguardShowing)
+                    .setAodShowing(aodShowing).build());
             mMainExecutor.execute(() -> {
                 mTransitions.startTransition(keyguardShowing ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK,
                         wct, KeyguardTransitionHandler.this);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 15472eb..8625202 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -23,7 +23,6 @@
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING;
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
 
 import android.annotation.BinderThread;
 import android.content.ComponentName;
@@ -298,7 +297,7 @@
         mShellController.addConfigurationChangeListener(this);
         mShellController.addKeyguardChangeListener(this);
         mShellController.addUserChangeListener(this);
-        mShellController.addExternalInterface(KEY_EXTRA_SHELL_ONE_HANDED,
+        mShellController.addExternalInterface(IOneHanded.DESCRIPTOR,
                 this::createExternalInterface, this);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt
index f7977f8..c655d86 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt
@@ -45,9 +45,15 @@
     private fun onInit() {
         mShellCommandHandler.addDumpCallback(this::dump, this)
         val perfHintMgr = mContext.getSystemService(PerformanceHintManager::class.java)
-        val adpfSession = perfHintMgr!!.createHintSession(intArrayOf(Process.myTid()),
-                TimeUnit.SECONDS.toNanos(1))
-        hinter.setAdpfSession(adpfSession)
+        if (perfHintMgr != null) {
+            val adpfSession = perfHintMgr.createHintSession(
+                intArrayOf(Process.myTid()),
+                TimeUnit.SECONDS.toNanos(1)
+            )
+            if (adpfSession != null) {
+                hinter.setAdpfSession(adpfSession)
+            }
+        }
     }
 
     fun dump(pw: PrintWriter, prefix: String?) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 5276d9d..55e90e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -324,6 +324,8 @@
         private final @AnimationType int mAnimationType;
         private final Rect mDestinationBounds = new Rect();
 
+        private final Point mLeashOffset = new Point();
+
         private T mBaseValue;
         protected T mCurrentValue;
         protected T mStartValue;
@@ -338,13 +340,22 @@
         // Flag to avoid double-end
         private boolean mHasRequestedEnd;
 
-        private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
-                @AnimationType int animationType,
-                Rect destinationBounds, T baseValue, T startValue, T endValue) {
+        private PipTransitionAnimator(@NonNull TaskInfo taskInfo, @NonNull SurfaceControl leash,
+                @AnimationType int animationType, @NonNull Rect destinationBounds,
+                @NonNull T baseValue, @NonNull T startValue, @NonNull T endValue) {
+            this(taskInfo, leash, animationType, destinationBounds, new Point(), baseValue,
+                    startValue, endValue);
+        }
+
+        private PipTransitionAnimator(@NonNull TaskInfo taskInfo, @NonNull SurfaceControl leash,
+                @AnimationType int animationType, @NonNull Rect destinationBounds,
+                @NonNull Point leashOffset, @NonNull T baseValue, @NonNull T startValue,
+                @NonNull T endValue) {
             mTaskInfo = taskInfo;
             mLeash = leash;
             mAnimationType = animationType;
             mDestinationBounds.set(destinationBounds);
+            mLeashOffset.set(leashOffset);
             mBaseValue = baseValue;
             mStartValue = startValue;
             mEndValue = endValue;
@@ -496,6 +507,15 @@
             }
         }
 
+        /**
+         * Returns the offset of the {@link #mLeash}.
+         */
+        @NonNull
+        Point getLeashOffset() {
+            // Use copy to prevent the leash to be modified unexpectedly.
+            return new Point(mLeashOffset);
+        }
+
         void setCurrentValue(T value) {
             mCurrentValue = value;
         }
@@ -692,8 +712,8 @@
             final Rect zeroInsets = new Rect(0, 0, 0, 0);
 
             // construct new Rect instances in case they are recycled
-            return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
-                    endBounds, new Rect(baseBounds), new Rect(startBounds), new Rect(endBounds)) {
+            return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS, endBounds,
+                    leashOffset, new Rect(baseBounds), new Rect(startBounds), new Rect(endBounds)) {
                 private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
                 private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
 
@@ -720,6 +740,7 @@
                             // Use the bounds relative to the task leash in case the leash does not
                             // start from (0, 0).
                             final Rect relativeEndBounds = new Rect(end);
+                            final Point leashOffset = getLeashOffset();
                             relativeEndBounds.offset(-leashOffset.x, -leashOffset.y);
                             getSurfaceTransactionHelper()
                                     .crop(tx, leash, relativeEndBounds)
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 30f1948..af18768 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
@@ -26,6 +26,8 @@
 
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
 import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
+import static com.android.wm.shell.desktopmode.DesktopModeUtils.calculateInitialBounds;
+import static com.android.wm.shell.desktopmode.DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
 import static com.android.wm.shell.pip.PipAnimationController.FRACTION_START;
@@ -92,6 +94,7 @@
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.animation.Interpolators;
@@ -150,8 +153,9 @@
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
     private final Optional<SplitScreenController> mSplitScreenOptional;
     @Nullable private final PipPerfHintController mPipPerfHintController;
-    private final Optional<DesktopRepository> mDesktopRepositoryOptional;
+    private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+    private final DisplayController mDisplayController;
     protected final ShellTaskOrganizer mTaskOrganizer;
     protected final ShellExecutor mMainExecutor;
 
@@ -395,7 +399,7 @@
             @NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<PipPerfHintController> pipPerfHintControllerOptional,
-            Optional<DesktopRepository> desktopRepositoryOptional,
+            Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             @NonNull DisplayController displayController,
             @NonNull PipUiEventLogger pipUiEventLogger,
@@ -423,8 +427,9 @@
                 new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
         mSplitScreenOptional = splitScreenOptional;
         mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
-        mDesktopRepositoryOptional = desktopRepositoryOptional;
+        mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+        mDisplayController = displayController;
         mTaskOrganizer = shellTaskOrganizer;
         mMainExecutor = mainExecutor;
 
@@ -754,19 +759,36 @@
     /** Returns the bounds to restore to when exiting PIP mode. */
     // TODO(b/377581840): Instead of manually tracking bounds, use bounds from Core.
     public Rect getExitDestinationBounds() {
-        if (isPipLaunchedInDesktopMode()) {
-            final Rect freeformBounds = mDesktopRepositoryOptional.get().removeBoundsBeforeMinimize(
+        if (isPipExitingToDesktopMode()) {
+            // If we are exiting PiP while device is in Desktop mode:
+            // 1) If PiP was entered via Desktop minimize (e.g. via minimize button), restore to the
+            //    previous freeform bounds that is saved in DesktopRepository.
+            // 2) If PiP was entered through other means (e.g. user swipe up), exit to initial
+            //    freeform bounds. Note that this case has a flicker at the moment (b/379984108).
+            Rect freeformBounds = getCurrentRepo().removeBoundsBeforeMinimize(
                     mTaskInfo.taskId);
-            return Objects.requireNonNullElseGet(freeformBounds, mPipBoundsState::getDisplayBounds);
+            return freeformBounds != null
+                    ? freeformBounds
+                    : calculateInitialBounds(
+                            mDisplayController.getDisplayLayout(mTaskInfo.displayId),
+                            mTaskInfo,
+                            DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         }
         return mPipBoundsState.getDisplayBounds();
     }
 
-    /** Returns whether PiP was launched while in desktop mode. */
-    // TODO(377581840): Update this check to include non-minimized cases, e.g. split to PiP etc.
-    private boolean isPipLaunchedInDesktopMode() {
-        return Flags.enableDesktopWindowingPip() && mDesktopRepositoryOptional.isPresent()
-                && mDesktopRepositoryOptional.get().isMinimizedTask(mTaskInfo.taskId);
+    /** Returns whether PiP is exiting while we're in desktop mode. */
+    // TODO(b/377581840): Update this check to include non-minimized cases, e.g. split to PiP etc.
+    private boolean isPipExitingToDesktopMode() {
+        DesktopRepository currentRepo = getCurrentRepo();
+        return Flags.enableDesktopWindowingPip() && currentRepo != null
+                && (currentRepo.getVisibleTaskCount(mTaskInfo.displayId) > 0
+                    || isDisplayInFreeform());
+    }
+
+    private DesktopRepository getCurrentRepo() {
+        return mDesktopUserRepositoriesOptional.map(DesktopUserRepositories::getCurrent).orElse(
+                null);
     }
 
     private void exitLaunchIntoPipTask(WindowContainerTransaction wct) {
@@ -1827,23 +1849,28 @@
                 == SPLIT_POSITION_TOP_OR_LEFT;
     }
 
+    private boolean isDisplayInFreeform() {
+        final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+                mTaskInfo.displayId);
+        if (tdaInfo != null) {
+            return tdaInfo.configuration.windowConfiguration.getWindowingMode()
+                    == WINDOWING_MODE_FREEFORM;
+        }
+        return false;
+    }
+
     /**
      * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
      * and can be overridden to restore to an alternate windowing mode.
      */
     public int getOutPipWindowingMode() {
-        final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
-                mTaskInfo.displayId);
-
-        // If PiP was launched while in desktop mode (we should return the task to freeform
-        // windowing mode):
+        // If we are exiting PiP while the device is in Desktop mode (the task should expand to
+        // freeform windowing mode):
         // 1) If the display windowing mode is freeform, set windowing mode to undefined so it will
         //    resolve the windowing mode to the display's windowing mode.
         // 2) If the display windowing mode is not freeform, set windowing mode to freeform.
-        if (tdaInfo != null && isPipLaunchedInDesktopMode()) {
-            final int displayWindowingMode =
-                    tdaInfo.configuration.windowConfiguration.getWindowingMode();
-            if (displayWindowingMode == WINDOWING_MODE_FREEFORM) {
+        if (isPipExitingToDesktopMode()) {
+            if (isDisplayInFreeform()) {
                 return WINDOWING_MODE_UNDEFINED;
             } else {
                 return WINDOWING_MODE_FREEFORM;
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 f7aed44..0042ec9 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
@@ -73,6 +73,7 @@
 import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipMenuController;
 import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.TransitionUtil;
 import com.android.wm.shell.shared.pip.PipContentOverlay;
@@ -506,8 +507,8 @@
     }
 
     @Override
-    public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
-            @PipAnimationController.TransitionDirection int direction,
+    public void onFinishResize(@NonNull TaskInfo taskInfo, @NonNull Rect destinationBounds,
+            @NonNull Point leashOffset, @PipAnimationController.TransitionDirection int direction,
             @NonNull SurfaceControl.Transaction tx) {
         final boolean enteringPip = isInPipDirection(direction);
         if (enteringPip) {
@@ -530,12 +531,16 @@
                 if (mFixedRotationState != FIXED_ROTATION_TRANSITION
                         && mFinishTransaction != null) {
                     mFinishTransaction.merge(tx);
-                    // Set window crop and position to destination bounds to avoid flickering.
+                    // Set crop and position to destination bounds to avoid flickering.
                     if (hasValidLeash) {
-                        mFinishTransaction.setWindowCrop(leash, destinationBounds.width(),
-                                destinationBounds.height());
-                        mFinishTransaction.setPosition(leash, destinationBounds.left,
-                                destinationBounds.top);
+                        final Rect relativeDestinationBounds = new Rect(destinationBounds);
+                        relativeDestinationBounds.offset(-leashOffset.x, -leashOffset.y);
+                        mFinishTransaction
+                                .setCrop(leash, relativeDestinationBounds)
+                                // Note that we should set the position to the start position of
+                                // leash then the visible region will be at the same place even if
+                                // the crop region doesn't start at (0, 0).
+                                .setPosition(leash, leashOffset.x, leashOffset.y);
                     }
                 }
             } else {
@@ -1266,7 +1271,8 @@
 
         mPipBoundsState.setBounds(destinationBounds);
         final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
-        onFinishResize(pipTaskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, tx);
+        onFinishResize(pipTaskInfo, destinationBounds, animator.getLeashOffset(),
+                TRANSITION_DIRECTION_TO_PIP, tx);
         sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
         if (swipePipToHomeOverlay != null) {
             mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
@@ -1346,6 +1352,13 @@
         return true;
     }
 
+    @Override
+    public boolean isPackageActiveInPip(@Nullable String packageName) {
+        final TaskInfo inPipTask = mPipOrganizer.getTaskInfo();
+        return packageName != null && inPipTask != null && mPipOrganizer.isInPip()
+                && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
+    }
+
     private void updatePipForUnhandledTransition(@NonNull TransitionInfo.Change pipChange,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 79a9ce5..6129651 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -31,6 +31,7 @@
 import android.app.TaskInfo;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -92,7 +93,8 @@
                         mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay,
                                 null /* callback */, true /* withStartDelay*/);
                     }
-                    onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx);
+                    onFinishResize(taskInfo, animator.getDestinationBounds(),
+                            animator.getLeashOffset(), direction, tx);
                     sendOnPipTransitionFinished(direction);
                 }
 
@@ -112,9 +114,9 @@
      * Called when transition is about to finish. This is usually for performing tasks such as
      * applying WindowContainerTransaction to finalize the PiP bounds and send to the framework.
      */
-    public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
-            @PipAnimationController.TransitionDirection int direction,
-            SurfaceControl.Transaction tx) {
+    public void onFinishResize(@NonNull TaskInfo taskInfo, @NonNull Rect destinationBounds,
+            @NonNull Point leashOffset, @PipAnimationController.TransitionDirection int direction,
+            @NonNull SurfaceControl.Transaction tx) {
     }
 
     /**
@@ -312,9 +314,8 @@
 
     /** Whether a particular package is same as current pip package. */
     public boolean isPackageActiveInPip(@Nullable String packageName) {
-        return packageName != null
-                && mPipBoundsState.getLastPipComponentName() != null
-                && packageName.equals(mPipBoundsState.getLastPipComponentName().getPackageName());
+        // No-op, to be handled differently in PIP1 and PIP2
+        return false;
     }
 
     /** Add PiP-related changes to `outWCT` for the given request. */
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 588b887..582df48 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
@@ -32,7 +32,6 @@
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE;
 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -727,7 +726,7 @@
         mShellController.addConfigurationChangeListener(this);
         mShellController.addKeyguardChangeListener(this);
         mShellController.addUserChangeListener(this);
-        mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+        mShellController.addExternalInterface(IPip.DESCRIPTOR,
                 this::createExternalInterface, this);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 6d2df95..19428ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -20,8 +20,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
 
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
-
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.PictureInPictureParams;
@@ -213,7 +211,7 @@
                 });
 
         // Allow other outside processes to bind to PiP controller using the key below.
-        mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+        mShellController.addExternalInterface(IPip.DESCRIPTOR,
                 this::createExternalInterface, this);
         mShellController.addConfigurationChangeListener(this);
 
@@ -254,6 +252,7 @@
     @Override
     public void onConfigurationChanged(Configuration newConfiguration) {
         mPipDisplayLayoutState.onConfigurationChanged();
+        mPipTouchHandler.onConfigurationChanged();
     }
 
     @Override
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 5438a01..4461a5c 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
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.pip2.phone;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
@@ -25,6 +26,7 @@
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
+import android.window.DisplayAreaInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -33,13 +35,19 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
+import java.util.Objects;
+import java.util.Optional;
+
 /**
  * Scheduler for Shell initiated PiP transitions and animations.
  */
@@ -50,6 +58,8 @@
     private final PipBoundsState mPipBoundsState;
     private final ShellExecutor mMainExecutor;
     private final PipTransitionState mPipTransitionState;
+    private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
+    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
     private PipTransitionController mPipTransitionController;
     private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
@@ -61,11 +71,15 @@
     public PipScheduler(Context context,
             PipBoundsState pipBoundsState,
             ShellExecutor mainExecutor,
-            PipTransitionState pipTransitionState) {
+            PipTransitionState pipTransitionState,
+            Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
         mContext = context;
         mPipBoundsState = pipBoundsState;
         mMainExecutor = mainExecutor;
         mPipTransitionState = pipTransitionState;
+        mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
+        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
 
         mSurfaceControlTransactionFactory =
                 new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
@@ -87,7 +101,7 @@
         wct.setBounds(pipTaskToken, null);
         // if we are hitting a multi-activity case
         // windowing mode change will reparent to original host task
-        wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
+        wct.setWindowingMode(pipTaskToken, getOutPipWindowingMode());
         return wct;
     }
 
@@ -241,6 +255,47 @@
         maybeUpdateMovementBounds();
     }
 
+    /** Returns whether the display is in freeform windowing mode. */
+    private boolean isDisplayInFreeform() {
+        final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+                Objects.requireNonNull(mPipTransitionState.getPipTaskInfo()).displayId);
+        if (tdaInfo != null) {
+            return tdaInfo.configuration.windowConfiguration.getWindowingMode()
+                    == WINDOWING_MODE_FREEFORM;
+        }
+        return false;
+    }
+
+    /** Returns whether PiP is exiting while we're in desktop mode. */
+    private boolean isPipExitingToDesktopMode() {
+        return Flags.enableDesktopWindowingPip() && mDesktopUserRepositoriesOptional.isPresent()
+                && (mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount(
+                Objects.requireNonNull(mPipTransitionState.getPipTaskInfo()).displayId) > 0
+                || isDisplayInFreeform());
+    }
+
+    /**
+     * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
+     * and can be overridden to restore to an alternate windowing mode.
+     */
+    private int getOutPipWindowingMode() {
+        // If we are exiting PiP while the device is in Desktop mode (the task should expand to
+        // freeform windowing mode):
+        // 1) If the display windowing mode is freeform, set windowing mode to undefined so it will
+        //    resolve the windowing mode to the display's windowing mode.
+        // 2) If the display windowing mode is not freeform, set windowing mode to freeform.
+        if (isPipExitingToDesktopMode()) {
+            if (isDisplayInFreeform()) {
+                return WINDOWING_MODE_UNDEFINED;
+            } else {
+                return WINDOWING_MODE_FREEFORM;
+            }
+        }
+
+        // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
+        return WINDOWING_MODE_UNDEFINED;
+    }
+
     @VisibleForTesting
     void setSurfaceControlTransactionFactory(
             @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
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 65972fb..44cc563 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
@@ -363,12 +363,10 @@
         mMotionHelper.synchronizePinnedStackBounds();
         reloadResources();
 
-        /*
-        if (mPipTaskOrganizer.isInPip()) {
+        if (mPipTransitionState.isInPip()) {
             // Recreate the dismiss target for the new orientation.
             mPipDismissTargetHandler.createOrUpdateDismissTarget();
         }
-         */
     }
 
     void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
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 5e6b51e7..b171db2 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
@@ -35,6 +35,7 @@
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.PictureInPictureParams;
+import android.app.TaskInfo;
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -57,6 +58,7 @@
 import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipMenuController;
 import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
 import com.android.wm.shell.pip2.animation.PipEnterAnimator;
@@ -74,8 +76,8 @@
     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";
+    private static final String PIP_TASK_INFO = "pip_task_info";
 
     // Used for PiP CHANGING_BOUNDS state update.
     static final String PIP_START_TX = "pip_start_tx";
@@ -245,8 +247,8 @@
 
             // Update the PipTransitionState while supplying the PiP leash and token to be cached.
             Bundle extra = new Bundle();
-            extra.putParcelable(PIP_TASK_TOKEN, pipChange.getContainer());
             extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
+            extra.putParcelable(PIP_TASK_INFO, pipChange.getTaskInfo());
             mPipTransitionState.setState(PipTransitionState.ENTERING_PIP, extra);
 
             if (isInSwipePipToHomeTransition()) {
@@ -625,6 +627,12 @@
             finishTransition();
         });
         cacheAndStartTransitionAnimator(animator);
+
+        // Save the PiP bounds in case, we re-enter the PiP with the same component.
+        float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
+                mPipBoundsState.getBounds());
+        mPipBoundsState.saveReentryState(snapFraction);
+
         return true;
     }
 
@@ -899,10 +907,10 @@
                 Preconditions.checkState(extra != null,
                         "No extra bundle for " + mPipTransitionState);
 
-                mPipTransitionState.setPipTaskToken(extra.getParcelable(
-                        PIP_TASK_TOKEN, WindowContainerToken.class));
                 mPipTransitionState.setPinnedTaskLeash(extra.getParcelable(
                         PIP_TASK_LEASH, SurfaceControl.class));
+                mPipTransitionState.setPipTaskInfo(extra.getParcelable(
+                        PIP_TASK_INFO, TaskInfo.class));
                 boolean hasValidTokenAndLeash = mPipTransitionState.getPipTaskToken() != null
                         && mPipTransitionState.getPinnedTaskLeash() != null;
 
@@ -910,9 +918,16 @@
                         "Unexpected bundle for " + mPipTransitionState);
                 break;
             case PipTransitionState.EXITED_PIP:
-                mPipTransitionState.setPipTaskToken(null);
                 mPipTransitionState.setPinnedTaskLeash(null);
+                mPipTransitionState.setPipTaskInfo(null);
                 break;
         }
     }
+
+    @Override
+    public boolean isPackageActiveInPip(@Nullable String packageName) {
+        final TaskInfo inPipTask = mPipTransitionState.getPipTaskInfo();
+        return packageName != null && inPipTask != null && mPipTransitionState.isInPip()
+                && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
+    }
 }
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 8e90bfe..6f9f40a 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
@@ -17,6 +17,7 @@
 package com.android.wm.shell.pip2.phone;
 
 import android.annotation.IntDef;
+import android.app.TaskInfo;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
@@ -133,17 +134,17 @@
     private final Rect mSwipePipToHomeAppBounds = new Rect();
 
     //
-    // Tokens and leashes
+    // Task related caches
     //
 
-    // pinned PiP task's WC token
-    @Nullable
-    private WindowContainerToken mPipTaskToken;
-
     // pinned PiP task's leash
     @Nullable
     private SurfaceControl mPinnedTaskLeash;
 
+    // pinned PiP task info
+    @Nullable
+    private TaskInfo mPipTaskInfo;
+
     // Overlay leash potentially used during swipe PiP to home transition;
     // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
     @Nullable
@@ -305,11 +306,7 @@
     }
 
     @Nullable WindowContainerToken getPipTaskToken() {
-        return mPipTaskToken;
-    }
-
-    public void setPipTaskToken(@Nullable WindowContainerToken token) {
-        mPipTaskToken = token;
+        return mPipTaskInfo != null ? mPipTaskInfo.getToken() : null;
     }
 
     @Nullable SurfaceControl getPinnedTaskLeash() {
@@ -320,6 +317,14 @@
         mPinnedTaskLeash = leash;
     }
 
+    @Nullable TaskInfo getPipTaskInfo() {
+        return mPipTaskInfo;
+    }
+
+    void setPipTaskInfo(@Nullable TaskInfo pipTaskInfo) {
+        mPipTaskInfo = pipTaskInfo;
+    }
+
     /**
      * @return true if either in swipe or button-nav fixed rotation.
      */
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 d917f93..b922cd0 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
@@ -19,10 +19,11 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.PackageManager.FEATURE_PC;
+import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.wm.shell.Flags.enableShellTopTaskTracking;
+import static com.android.wm.shell.desktopmode.DesktopWallpaperActivity.isWallpaperTask;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_OBSERVER;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
 
 import android.Manifest;
 import android.annotation.RequiresPermission;
@@ -53,6 +54,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.protolog.ProtoLog;
+import com.android.launcher3.Flags;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
@@ -60,6 +62,7 @@
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.GroupedTaskInfo;
 import com.android.wm.shell.shared.annotations.ExternalThread;
@@ -69,6 +72,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.UserChangeListener;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -86,13 +90,14 @@
  */
 public class RecentTasksController implements TaskStackListenerCallback,
         RemoteCallable<RecentTasksController>, DesktopRepository.ActiveTasksListener,
-        TaskStackTransitionObserver.TaskStackTransitionObserverListener {
+        TaskStackTransitionObserver.TaskStackTransitionObserverListener, UserChangeListener {
     private static final String TAG = RecentTasksController.class.getSimpleName();
 
     private final Context mContext;
     private final ShellController mShellController;
     private final ShellCommandHandler mShellCommandHandler;
-    private final Optional<DesktopRepository> mDesktopRepository;
+    private final Optional<DesktopUserRepositories> mDesktopUserRepositories;
+
     private final ShellExecutor mMainExecutor;
     private final TaskStackListenerImpl mTaskStackListener;
     private final RecentTasksImpl mImpl = new RecentTasksImpl();
@@ -105,6 +110,8 @@
     // Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a
     // pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1)
     private final SparseIntArray mSplitTasks = new SparseIntArray();
+
+    private int mUserId;
     /**
      * Maps taskId to {@link SplitBounds} for both taskIDs.
      * Meaning there will be two taskId integers mapping to the same object.
@@ -130,7 +137,7 @@
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
             ActivityTaskManager activityTaskManager,
-            Optional<DesktopRepository> desktopRepository,
+            Optional<DesktopUserRepositories> desktopUserRepositories,
             TaskStackTransitionObserver taskStackTransitionObserver,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
@@ -138,7 +145,7 @@
             return null;
         }
         return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
-                taskStackListener, activityTaskManager, desktopRepository,
+                taskStackListener, activityTaskManager, desktopUserRepositories,
                 taskStackTransitionObserver, mainExecutor);
     }
 
@@ -148,7 +155,7 @@
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
             ActivityTaskManager activityTaskManager,
-            Optional<DesktopRepository> desktopRepository,
+            Optional<DesktopUserRepositories> desktopUserRepositories,
             TaskStackTransitionObserver taskStackTransitionObserver,
             ShellExecutor mainExecutor) {
         mContext = context;
@@ -157,7 +164,7 @@
         mActivityTaskManager = activityTaskManager;
         mPcFeatureEnabled = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
         mTaskStackListener = taskStackListener;
-        mDesktopRepository = desktopRepository;
+        mDesktopUserRepositories = desktopUserRepositories;
         mTaskStackTransitionObserver = taskStackTransitionObserver;
         mMainExecutor = mainExecutor;
         shellInit.addInitCallback(this::onInit, this);
@@ -172,12 +179,15 @@
     }
 
     @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
-    private void onInit() {
-        mShellController.addExternalInterface(KEY_EXTRA_SHELL_RECENT_TASKS,
+    void onInit() {
+        mShellController.addExternalInterface(IRecentTasks.DESCRIPTOR,
                 this::createExternalInterface, this);
         mShellCommandHandler.addDumpCallback(this::dump, this);
+        mUserId = ActivityManager.getCurrentUser();
+        mDesktopUserRepositories.ifPresent(
+                desktopUserRepositories ->
+                        desktopUserRepositories.getCurrent().addActiveTaskListener(this));
         mTaskStackListener.addListener(this);
-        mDesktopRepository.ifPresent(it -> it.addActiveTaskListener(this));
         mTaskStackTransitionObserver.addTaskStackTransitionObserverListener(this,
                 mMainExecutor);
         mContext.getSystemService(KeyguardManager.class).addKeyguardLockedStateListener(
@@ -282,6 +292,17 @@
         notifyRecentTasksChanged();
     }
 
+    /**
+     * This method only gets notified when a task is removed from recents as a result of another
+     * task being added to recent tasks.
+     */
+    @Override
+    public void onRecentTaskRemovedForAddTask(int taskId) {
+        mDesktopUserRepositories.ifPresent(
+                desktopUserRepositories -> desktopUserRepositories.getCurrent().removeFreeformTask(
+                        INVALID_DISPLAY, taskId));
+    }
+
     public void onTaskAdded(RunningTaskInfo taskInfo) {
         notifyRunningTaskAppeared(taskInfo);
     }
@@ -498,10 +519,9 @@
                 // If it's not in the mapping, then it was already paired with another task
                 continue;
             }
-
-            if (DesktopModeStatus.canEnterDesktopMode(mContext)
-                    && mDesktopRepository.isPresent()
-                    && mDesktopRepository.get().isActiveTask(taskInfo.taskId)) {
+            if (DesktopModeStatus.canEnterDesktopMode(mContext) &&
+                mDesktopUserRepositories.isPresent()
+                    && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskInfo.taskId)) {
                 // Freeform tasks will be added as a separate entry
                 if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
                     mostRecentFreeformTaskIndex = groupedTasks.size();
@@ -517,7 +537,7 @@
                             taskInfo.lastNonFullscreenBounds.top);
                 }
                 freeformTasks.add(taskInfo);
-                if (mDesktopRepository.get().isMinimizedTask(taskInfo.taskId)) {
+                if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskInfo.taskId)) {
                     minimizedFreeformTasks.add(taskInfo.taskId);
                 }
                 continue;
@@ -530,6 +550,10 @@
                 groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
                         mTaskSplitBoundsMap.get(pairedTaskId)));
             } else {
+                if (Flags.enableRefactorTaskThumbnail() && isWallpaperTask(taskInfo)) {
+                    // Don't add the wallpaper task as an entry in grouped tasks
+                    continue;
+                }
                 // TODO(346588978): Consolidate multiple visible fullscreen tasks into the same
                 //  grouped task
                 groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
@@ -685,6 +709,21 @@
         }
     }
 
+    @Override
+    public void onUserChanged(int newUserId, @NonNull Context userContext) {
+        if (mDesktopUserRepositories.isEmpty()) return;
+
+        DesktopRepository previousUserRepository =
+                mDesktopUserRepositories.get().getProfile(mUserId);
+        mUserId = newUserId;
+        DesktopRepository currentUserRepository =
+                mDesktopUserRepositories.get().getProfile(newUserId);
+
+        // No-op if both profile ids map to the same user.
+        if (previousUserRepository.getUserId() == currentUserRepository.getUserId()) return;
+        previousUserRepository.removeActiveTasksListener(this);
+        currentUserRepository.addActiveTaskListener(this);
+    }
 
     /**
      * The interface for calls from outside the host process.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 032dac9..37d5878 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -1317,9 +1317,6 @@
                 // otherwise a new transition will notify the relevant observers
                 if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
                     mHomeTransitionObserver.notifyHomeVisibilityChanged(true);
-                } else if (!toHome && mState == STATE_NEW_TASK
-                        && allAppsAreTranslucent(mOpeningTasks)) {
-                    // We are opening a translucent app. Launcher is still visible so we do nothing.
                 } else if (!toHome) {
                     // For some transitions, we may have notified home activity that it became
                     // visible. We need to notify the observer that we are no longer going home.
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 4f0f676..a368245 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
@@ -32,7 +32,6 @@
 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.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1;
@@ -94,6 +93,7 @@
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.common.split.SplitState;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.draganddrop.SplitDragPolicy;
@@ -199,6 +199,7 @@
     private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
     private final Optional<DesktopTasksController> mDesktopTasksController;
     private final MultiInstanceHelper mMultiInstanceHelpher;
+    private final SplitState mSplitState;
     private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
 
     @VisibleForTesting
@@ -228,6 +229,7 @@
             Optional<DesktopTasksController> desktopTasksController,
             @Nullable StageCoordinator stageCoordinator,
             MultiInstanceHelper multiInstanceHelper,
+            SplitState splitState,
             ShellExecutor mainExecutor,
             Handler mainHandler) {
         mShellCommandHandler = shellCommandHandler;
@@ -252,6 +254,7 @@
         mDesktopTasksController = desktopTasksController;
         mStageCoordinator = stageCoordinator;
         mMultiInstanceHelpher = multiInstanceHelper;
+        mSplitState = splitState;
         mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
         // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
         //                    override for this controller from the base module
@@ -278,7 +281,7 @@
         mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler,
                 this);
         mShellController.addKeyguardChangeListener(this);
-        mShellController.addExternalInterface(KEY_EXTRA_SHELL_SPLIT_SCREEN,
+        mShellController.addExternalInterface(ISplitScreen.DESCRIPTOR,
                 this::createExternalInterface, this);
         if (mStageCoordinator == null) {
             // TODO: Multi-display
@@ -296,7 +299,7 @@
                 mTaskOrganizer, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider,
                 mMainExecutor, mMainHandler, mRecentTasksOptional, mLaunchAdjacentController,
-                mWindowDecorViewModel);
+                mWindowDecorViewModel, mSplitState);
     }
 
     @Override
@@ -582,6 +585,7 @@
             @Nullable WindowContainerToken hideTaskToken) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
                 "Legacy startTask does not support hide task token");
+        if (isTaskInSplitScreenForeground(taskId)) return;
         final int[] result = new int[1];
         IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
             @Override
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 88a9566..b40996f 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
@@ -139,6 +139,7 @@
 import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.common.split.SplitState;
 import com.android.wm.shell.common.split.SplitWindowManager;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentTasksController;
@@ -218,6 +219,8 @@
     private final Optional<RecentTasksController> mRecentTasks;
     private final LaunchAdjacentController mLaunchAdjacentController;
     private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
+    /** Singleton source of truth for the current state of split screen on this device. */
+    private final SplitState mSplitState;
 
     private final Rect mTempRect1 = new Rect();
     private final Rect mTempRect2 = new Rect();
@@ -344,7 +347,7 @@
             TransactionPool transactionPool, IconProvider iconProvider, ShellExecutor mainExecutor,
             Handler mainHandler, Optional<RecentTasksController> recentTasks,
             LaunchAdjacentController launchAdjacentController,
-            Optional<WindowDecorViewModel> windowDecorViewModel) {
+            Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) {
         mContext = context;
         mDisplayId = displayId;
         mSyncQueue = syncQueue;
@@ -355,6 +358,7 @@
         mRecentTasks = recentTasks;
         mLaunchAdjacentController = launchAdjacentController;
         mWindowDecorViewModel = windowDecorViewModel;
+        mSplitState = splitState;
 
         taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
 
@@ -412,7 +416,7 @@
             Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor,
             Handler mainHandler, Optional<RecentTasksController> recentTasks,
             LaunchAdjacentController launchAdjacentController,
-            Optional<WindowDecorViewModel> windowDecorViewModel) {
+            Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) {
         mContext = context;
         mDisplayId = displayId;
         mSyncQueue = syncQueue;
@@ -432,6 +436,8 @@
         mRecentTasks = recentTasks;
         mLaunchAdjacentController = launchAdjacentController;
         mWindowDecorViewModel = windowDecorViewModel;
+        mSplitState = splitState;
+
         mDisplayController.addDisplayWindowListener(this);
         transitions.addHandler(this);
         mSplitUnsupportedToast = Toast.makeText(mContext,
@@ -501,6 +507,7 @@
     /**
      * Deactivates main stage by removing the stage from the top level split root (usually when a
      * task underneath gets removed from the stage root).
+     * This function should always be called as part of exiting split screen.
      * @param stageToTop stage which we want to put on top
      */
     private void deactivateSplit(WindowContainerTransaction wct, @StageType int stageToTop) {
@@ -833,7 +840,14 @@
 
         setSideStagePosition(splitPosition, wct);
         options1 = options1 != null ? options1 : new Bundle();
-        addActivityOptions(options1, mSideStage);
+        StageTaskListener stageForTask1;
+        if (enableFlexibleSplit()) {
+            stageForTask1 = mStageOrderOperator.getStageForLegacyPosition(splitPosition,
+                    true /*checkAllStagesIfNotActive*/);
+        } else {
+            stageForTask1 = mSideStage;
+        }
+        addActivityOptions(options1, stageForTask1);
         wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
         prepareTasksForSplitScreen(new int[] {taskId}, wct);
 
@@ -878,7 +892,14 @@
 
         setSideStagePosition(splitPosition, wct);
         options1 = options1 != null ? options1 : new Bundle();
-        addActivityOptions(options1, mSideStage);
+        StageTaskListener stageForTask1;
+        if (enableFlexibleSplit()) {
+            stageForTask1 = mStageOrderOperator.getStageForLegacyPosition(splitPosition,
+                    true /*checkAllStagesIfNotActive*/);
+        } else {
+            stageForTask1 = mSideStage;
+        }
+        addActivityOptions(options1, stageForTask1);
         wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
         prepareTasksForSplitScreen(new int[] {taskId}, wct);
 
@@ -1010,14 +1031,29 @@
         setRootForceTranslucent(false, wct);
 
         options1 = options1 != null ? options1 : new Bundle();
-        addActivityOptions(options1, mSideStage);
+        StageTaskListener stageForTask1;
+        if (enableFlexibleSplit()) {
+            stageForTask1 = mStageOrderOperator.getStageForLegacyPosition(splitPosition,
+                    true /*checkAllStagesIfNotActive*/);
+        } else {
+            stageForTask1 = mSideStage;
+        }
+        addActivityOptions(options1, stageForTask1);
         if (shortcutInfo1 != null) {
             wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
         } else {
             wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
         }
+
+        StageTaskListener stageForTask2;
+        if (enableFlexibleSplit()) {
+            stageForTask2 = mStageOrderOperator.getStageForLegacyPosition(
+                    reverseSplitPosition(splitPosition), true /*checkAllStagesIfNotActive*/);
+        } else {
+            stageForTask2 = mMainStage;
+        }
         options2 = options2 != null ? options2 : new Bundle();
-        addActivityOptions(options2, mMainStage);
+        addActivityOptions(options2, stageForTask2);
         if (shortcutInfo2 != null) {
             wct.startShortcut(mContext.getPackageName(), shortcutInfo2, options2);
         } else {
@@ -1246,9 +1282,13 @@
                     // Restore focus-ability to the windows and divider
                     wct.setFocusable(mRootTaskInfo.token, true);
 
+                    if (enableFlexibleSplit()) {
+                        mStageOrderOperator.onDoubleTappedDivider();
+                    }
                     setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct);
                     mSyncQueue.queue(wct);
                     mSyncQueue.runInSync(st -> {
+                        mSplitLayout.updateStateWithCurrentPosition();
                         updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */);
 
                         // updateSurfaceBounds(), above, officially puts the two apps in their new
@@ -1404,6 +1444,7 @@
         if (!isSplitActive() || mIsExiting) return;
 
         onSplitScreenExit();
+        mSplitState.exit();
         clearSplitPairedInRecents(exitReason);
 
         mShouldUpdateRecents = false;
@@ -1599,6 +1640,7 @@
             mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
         }
         deactivateSplit(wct, stageToTop);
+        mSplitState.exit();
     }
 
     private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
@@ -1697,6 +1739,7 @@
 
     void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen");
+        mSplitLayout.updateStateWithCurrentPosition();
         mSplitLayout.update(null, true /* resetImePosition */);
         if (enableFlexibleSplit()) {
             runForActiveStages((stage) ->
@@ -1921,6 +1964,8 @@
                 }
             }
 
+            int currentSnapPosition = mSplitLayout.calculateCurrentSnapPosition();
+
             if (Flags.enableFlexibleTwoAppSplit()) {
                 // Split screen can be laid out in such a way that some of the apps are offscreen.
                 // For the purposes of passing SplitBounds up to launcher (for use in thumbnails
@@ -1933,10 +1978,17 @@
                         Math.min(bottomRightBounds.right, mSplitLayout.getDisplayWidth());
                 bottomRightBounds.top =
                         Math.min(bottomRightBounds.top, mSplitLayout.getDisplayHeight());
+
+                // TODO (b/349828130): Can change to getState() fully after brief soak time.
+                if (mSplitState.get() != currentSnapPosition) {
+                    Log.wtf(TAG, "SplitState is " + mSplitState.get()
+                            + ", expected " + currentSnapPosition);
+                    currentSnapPosition = mSplitState.get();
+                }
             }
 
             SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds,
-                    leftTopTaskId, rightBottomTaskId, mSplitLayout.calculateCurrentSnapPosition());
+                    leftTopTaskId, rightBottomTaskId, currentSnapPosition);
             if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
                 // Update the pair for the top tasks
                 boolean added = recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId,
@@ -1975,7 +2027,7 @@
             mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
                     mRootTaskInfo.configuration, this, mParentContainerCallbacks,
                     mDisplayController, mDisplayImeController, mTaskOrganizer,
-                    PARALLAX_ALIGN_CENTER /* parallaxType */, mMainHandler);
+                    PARALLAX_ALIGN_CENTER /* parallaxType */, mSplitState, mMainHandler);
             mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt
index b7b3c9b..3fa8df4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt
@@ -38,6 +38,7 @@
 import com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_C
 import com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString
 import com.android.wm.shell.windowdecor.WindowDecorViewModel
+import java.util.Collections
 import java.util.Optional
 
 /**
@@ -148,6 +149,24 @@
     }
 
     /**
+     * This will swap the stages for the two stages on either side of the given divider.
+     * Note: This will keep [activeStages] and [allStages] in sync by swapping both of them
+     * If there are no [activeStages] then this will be a no-op.
+     *
+     * TODO(b/379984874): Take in a divider identifier to determine which array indices to swap
+     */
+    fun onDoubleTappedDivider() {
+        if (activeStages.isEmpty()) {
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                "Stages not active, ignoring swap request")
+            return
+        }
+
+        Collections.swap(activeStages, 0, 1)
+        Collections.swap(allStages, 0, 1)
+    }
+
+    /**
      * Returns a legacy split position for the given stage. If no stages are active then this will
      * return [SPLIT_POSITION_UNDEFINED]
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index 3468156..c5e158c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -32,6 +32,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.split.SplitState;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -60,6 +61,7 @@
     private final IconProvider mIconProvider;
     private final Optional<RecentTasksController> mRecentTasksOptional;
     private final LaunchAdjacentController mLaunchAdjacentController;
+    private final SplitState mSplitState;
 
     private final Handler mMainHandler;
     private final SystemWindows mSystemWindows;
@@ -80,6 +82,7 @@
             Optional<RecentTasksController> recentTasks,
             LaunchAdjacentController launchAdjacentController,
             MultiInstanceHelper multiInstanceHelper,
+            SplitState splitState,
             ShellExecutor mainExecutor,
             Handler mainHandler,
             SystemWindows systemWindows) {
@@ -87,8 +90,8 @@
                 syncQueue, rootTDAOrganizer, displayController, displayImeController,
                 displayInsetsController, null, transitions, transactionPool,
                 iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
-                Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, mainExecutor,
-                mainHandler);
+                Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, splitState,
+                mainExecutor, mainHandler);
 
         mTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
@@ -102,6 +105,7 @@
         mIconProvider = iconProvider;
         mRecentTasksOptional = recentTasks;
         mLaunchAdjacentController = launchAdjacentController;
+        mSplitState = splitState;
 
         mMainHandler = mainHandler;
         mSystemWindows = systemWindows;
@@ -117,7 +121,7 @@
                 mTaskOrganizer, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mTransitions, mTransactionPool,
                 mIconProvider, mMainExecutor, mMainHandler,
-                mRecentTasksOptional, mLaunchAdjacentController, mSystemWindows);
+                mRecentTasksOptional, mLaunchAdjacentController, mSplitState, mSystemWindows);
     }
 
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
index 4451ee8..ef1f88e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
@@ -28,6 +28,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.split.SplitState;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.shared.split.SplitScreenConstants;
@@ -53,10 +54,12 @@
             Handler mainHandler,
             Optional<RecentTasksController> recentTasks,
             LaunchAdjacentController launchAdjacentController,
+            SplitState splitState,
             SystemWindows systemWindows) {
         super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController,
                 displayInsetsController, transitions, transactionPool, iconProvider,
-                mainExecutor, mainHandler, recentTasks, launchAdjacentController, Optional.empty());
+                mainExecutor, mainHandler, recentTasks, launchAdjacentController, Optional.empty(),
+                splitState);
 
         mTvSplitMenuController = new TvSplitMenuController(context, this,
                 systemWindows, mainHandler);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index 7cb8e8a..72bad41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -23,8 +23,6 @@
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS;
 
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
-
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
 import android.content.Context;
@@ -119,7 +117,7 @@
 
     private void onInit() {
         mShellTaskOrganizer.initStartingWindow(this);
-        mShellController.addExternalInterface(KEY_EXTRA_SHELL_STARTING_WINDOW,
+        mShellController.addExternalInterface(IStartingWindow.DESCRIPTOR,
                 this::createExternalInterface, this);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
index 4ea4613..d8884f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
@@ -41,9 +41,9 @@
             @NonNull Animation anim, @NonNull SurfaceControl leash,
             @NonNull Runnable finishCallback, @NonNull TransactionPool pool,
             @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius,
-            @Nullable Rect clipRect, boolean isActivity) {
+            @Nullable Rect clipRect) {
         final DefaultAnimationAdapter adapter = new DefaultAnimationAdapter(anim, leash,
-                position, clipRect, cornerRadius, isActivity);
+                position, clipRect, cornerRadius);
         buildSurfaceAnimation(animations, anim, finishCallback, pool, mainExecutor, adapter);
     }
 
@@ -138,11 +138,9 @@
         @Nullable final Rect mClipRect;
         @Nullable private final Rect mAnimClipRect;
         final float mCornerRadius;
-        final boolean mIsActivity;
 
         DefaultAnimationAdapter(@NonNull Animation anim, @NonNull SurfaceControl leash,
-                @Nullable Point position, @Nullable Rect clipRect, float cornerRadius,
-                boolean isActivity) {
+                @Nullable Point position, @Nullable Rect clipRect, float cornerRadius) {
             super(leash);
             mAnim = anim;
             mPosition = (position != null && (position.x != 0 || position.y != 0))
@@ -150,7 +148,6 @@
             mClipRect = (clipRect != null && !clipRect.isEmpty()) ? clipRect : null;
             mAnimClipRect = mClipRect != null ? new Rect() : null;
             mCornerRadius = cornerRadius;
-            mIsActivity = isActivity;
         }
 
         @Override
@@ -160,10 +157,6 @@
             final SurfaceControl leash = mLeash;
             transformation.clear();
             mAnim.getTransformation(currentPlayTime, transformation);
-            if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
-                    && mIsActivity && mAnim.getExtensionEdges() != 0) {
-                t.setEdgeExtensionEffect(leash, mAnim.getExtensionEdges());
-            }
             if (mPosition != null) {
                 transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 9fcf98b..e80016d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -506,6 +506,8 @@
 
                 if (!isTask && a.getExtensionEdges() != 0x0) {
                     if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
+                        startTransaction.setEdgeExtensionEffect(
+                                change.getLeash(), a.getExtensionEdges());
                         finishTransaction.setEdgeExtensionEffect(change.getLeash(), /* edge */ 0);
                     } else {
                         if (!TransitionUtil.isOpeningType(mode)) {
@@ -564,7 +566,7 @@
 
                 buildSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
                         mTransactionPool, mMainExecutor, animRelOffset, cornerRadius,
-                        clipRect, change.getActivityComponent() != null);
+                        clipRect);
 
                 final TransitionInfo.AnimationOptions options;
                 if (Flags.moveAnimationOptionsToChange()) {
@@ -876,8 +878,7 @@
         a.restrictDuration(MAX_ANIMATION_DURATION);
         a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
-                mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds(),
-                change.getActivityComponent() != null);
+                mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds());
     }
 
     private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations,
@@ -901,8 +902,7 @@
         a.restrictDuration(MAX_ANIMATION_DURATION);
         a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
-                mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds(),
-                change.getActivityComponent() != null);
+                mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds());
     }
 
     private static int getWallpaperTransitType(TransitionInfo info) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 6f3aa11..aa42b7f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -347,21 +347,21 @@
             @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
         buildSurfaceAnimation(animations, mRotateEnterAnimation, getEnterSurface(), finishCallback,
                 mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
-                null /* clipRect */, false /* isActivity */);
+                null /* clipRect */);
     }
 
     private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations,
             @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
         buildSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback,
                 mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
-                null /* clipRect */, false /* isActivity */);
+                null /* clipRect */);
     }
 
     private void buildScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations,
             @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
         buildSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback,
                 mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
-                null /* clipRect */, false /* isActivity */);
+                null /* clipRect */);
     }
 
     private void buildLumaAnimation(@NonNull ArrayList<Animator> animations,
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 3f19149..611f3e0 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
@@ -41,7 +41,6 @@
 import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived;
 import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
 import static com.android.window.flags.Flags.migratePredictiveBackTransition;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
 import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
 import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
 
@@ -373,7 +372,7 @@
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             mOrganizer.shareTransactionQueue();
         }
-        mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
+        mShellController.addExternalInterface(IShellTransitions.DESCRIPTOR,
                 this::createExternalInterface, this);
 
         ContentResolver resolver = mContext.getContentResolver();
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 c9f2d2e..885f3db 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 @@
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 import com.android.wm.shell.shared.FocusTransitionListener;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.FocusTransitionObserver;
@@ -119,8 +120,8 @@
     public CaptionWindowDecorViewModel(
             Context context,
             Handler mainHandler,
+            @ShellMainThread ShellExecutor shellExecutor,
             @ShellBackgroundThread ShellExecutor bgExecutor,
-            ShellExecutor shellExecutor,
             Choreographer mainChoreographer,
             IWindowManager windowManager,
             ShellInit shellInit,
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 982fda0..aa954fb 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
@@ -194,6 +194,7 @@
     @VisibleForTesting
     static void updateRelayoutParams(
             RelayoutParams relayoutParams,
+            @NonNull Context context,
             ActivityManager.RunningTaskInfo taskInfo,
             boolean applyStartTransactionOnDraw,
             boolean shouldSetTaskVisibilityPositionAndCrop,
@@ -206,9 +207,11 @@
         relayoutParams.mRunningTaskInfo = taskInfo;
         relayoutParams.mLayoutResId = R.layout.caption_window_decor;
         relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
-        relayoutParams.mShadowRadiusId = hasGlobalFocus
-                ? R.dimen.freeform_decor_shadow_focused_thickness
-                : R.dimen.freeform_decor_shadow_unfocused_thickness;
+        relayoutParams.mShadowRadius = hasGlobalFocus
+                ? context.getResources().getDimensionPixelSize(
+                        R.dimen.freeform_decor_shadow_focused_thickness)
+                : context.getResources().getDimensionPixelSize(
+                        R.dimen.freeform_decor_shadow_unfocused_thickness);
         relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
         relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
         relayoutParams.mIsCaptionVisible = taskInfo.isFreeform()
@@ -251,7 +254,7 @@
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
-        updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw,
+        updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
                 shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
                 mIsKeyguardVisibleAndOccluded,
                 mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt
index b8c9151..4d95cde 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt
@@ -24,7 +24,7 @@
 import android.window.TaskSnapshot
 import androidx.compose.ui.graphics.toArgb
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
-import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer
+import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer
 import com.android.wm.shell.shared.split.SplitScreenConstants
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
index dd68105..ff52a45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
@@ -32,8 +32,8 @@
 import com.android.window.flags.Flags
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.common.DisplayController
-import com.android.wm.shell.desktopmode.DesktopRepository
-import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
+import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
@@ -51,7 +51,7 @@
     private val displayController: DisplayController,
     private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer,
     context: Context,
-    private val desktopRepository: DesktopRepository,
+    private val desktopUserRepositories: DesktopUserRepositories,
     private val surfaceControlBuilderSupplier: Supplier<SurfaceControl.Builder>,
     private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>,
     snapshotList: List<Pair<Int, TaskSnapshot>>,
@@ -76,6 +76,7 @@
         val flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
                 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
                 WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+        val desktopRepository = desktopUserRepositories.getProfile(callerTaskInfo.userId)
         menuViewContainer = if (Flags.enableFullyImmersiveInDesktop()
             && desktopRepository.isTaskInFullImmersiveState(callerTaskInfo.taskId)) {
             // Use system view container so that forcibly shown system bars take effect in
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 6caef36..e8b02dc 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
@@ -39,7 +39,7 @@
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
-import static com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer.MANAGE_WINDOWS_MINIMUM_INSTANCES;
+import static com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer.MANAGE_WINDOWS_MINIMUM_INSTANCES;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -107,16 +107,21 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
+import com.android.wm.shell.desktopmode.DesktopImmersiveController;
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
 import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum;
+import com.android.wm.shell.desktopmode.DesktopModeUtils;
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
 import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
 import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction;
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeUtilsKt;
 import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
 import com.android.wm.shell.desktopmode.education.AppToWebEducationController;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
@@ -165,7 +170,7 @@
     private final ActivityTaskManager mActivityTaskManager;
     private final ShellCommandHandler mShellCommandHandler;
     private final ShellTaskOrganizer mTaskOrganizer;
-    private final DesktopRepository mDesktopRepository;
+    private final DesktopUserRepositories mDesktopUserRepositories;
     private final ShellController mShellController;
     private final Context mContext;
     private final @ShellMainThread Handler mMainHandler;
@@ -174,6 +179,7 @@
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
     private final DesktopTasksController mDesktopTasksController;
+    private final DesktopImmersiveController mDesktopImmersiveController;
     private final InputManager mInputManager;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final MultiInstanceHelper mMultiInstanceHelper;
@@ -242,13 +248,14 @@
             ShellCommandHandler shellCommandHandler,
             IWindowManager windowManager,
             ShellTaskOrganizer taskOrganizer,
-            DesktopRepository desktopRepository,
+            DesktopUserRepositories desktopUserRepositories,
             DisplayController displayController,
             ShellController shellController,
             DisplayInsetsController displayInsetsController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
+            DesktopImmersiveController desktopImmersiveController,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             InteractionJankMonitor interactionJankMonitor,
             AppToWebGenericLinksParser genericLinksParser,
@@ -272,13 +279,14 @@
                 shellCommandHandler,
                 windowManager,
                 taskOrganizer,
-                desktopRepository,
+                desktopUserRepositories,
                 displayController,
                 shellController,
                 displayInsetsController,
                 syncQueue,
                 transitions,
                 desktopTasksController,
+                desktopImmersiveController,
                 genericLinksParser,
                 assistContentRequester,
                 multiInstanceHelper,
@@ -311,13 +319,14 @@
             ShellCommandHandler shellCommandHandler,
             IWindowManager windowManager,
             ShellTaskOrganizer taskOrganizer,
-            DesktopRepository desktopRepository,
+            DesktopUserRepositories desktopUserRepositories,
             DisplayController displayController,
             ShellController shellController,
             DisplayInsetsController displayInsetsController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
+            DesktopImmersiveController desktopImmersiveController,
             AppToWebGenericLinksParser genericLinksParser,
             AssistContentRequester assistContentRequester,
             MultiInstanceHelper multiInstanceHelper,
@@ -344,13 +353,14 @@
         mBgExecutor = bgExecutor;
         mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
         mTaskOrganizer = taskOrganizer;
-        mDesktopRepository = desktopRepository;
+        mDesktopUserRepositories = desktopUserRepositories;
         mShellController = shellController;
         mDisplayController = displayController;
         mDisplayInsetsController = displayInsetsController;
         mSyncQueue = syncQueue;
         mTransitions = transitions;
         mDesktopTasksController = desktopTasksController.get();
+        mDesktopImmersiveController = desktopImmersiveController;
         mMultiInstanceHelper = multiInstanceHelper;
         mShellCommandHandler = shellCommandHandler;
         mWindowManager = windowManager;
@@ -574,34 +584,83 @@
                 >= MANAGE_WINDOWS_MINIMUM_INSTANCES);
     }
 
-    private void onMaximizeOrRestore(int taskId, String source, ResizeTrigger resizeTrigger,
-            MotionEvent motionEvent) {
+    private void onToggleSizeInteraction(
+            int taskId, @NonNull ToggleTaskSizeInteraction.AmbiguousSource source,
+            @Nullable MotionEvent motionEvent) {
         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
         if (decoration == null) {
             return;
         }
-        mInteractionJankMonitor.begin(
-                decoration.mTaskSurface, mContext, mMainHandler,
-                Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
-        mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo, resizeTrigger,
-                motionEvent);
+        final ToggleTaskSizeInteraction interaction =
+                createToggleSizeInteraction(decoration, source, motionEvent);
+        if (interaction == null) {
+            return;
+        }
+        if (interaction.getCujTracing() != null) {
+            mInteractionJankMonitor.begin(
+                    decoration.mTaskSurface, mContext, mMainHandler,
+                    interaction.getCujTracing(), interaction.getJankTag());
+        }
+        mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo, interaction);
         decoration.closeHandleMenu();
         decoration.closeMaximizeMenu();
     }
 
-    private void onEnterOrExitImmersive(int taskId) {
+    private ToggleTaskSizeInteraction createToggleSizeInteraction(
+            @NonNull DesktopModeWindowDecoration decoration,
+            @NonNull ToggleTaskSizeInteraction.AmbiguousSource source,
+            @Nullable MotionEvent motionEvent) {
+        final RunningTaskInfo taskInfo = decoration.mTaskInfo;
+
+        final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(taskInfo.displayId);
+        if (displayLayout == null) {
+            return null;
+        }
+        final Rect stableBounds = new Rect();
+        displayLayout.getStableBounds(stableBounds);
+        boolean isMaximized = DesktopModeUtils.isTaskMaximized(taskInfo, stableBounds);
+
+        return new ToggleTaskSizeInteraction(
+                isMaximized
+                        ? ToggleTaskSizeInteraction.Direction.RESTORE
+                        : ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+                ToggleTaskSizeUtilsKt.toSource(source, isMaximized),
+                DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent)
+        );
+    }
+
+    private void onEnterOrExitImmersive(RunningTaskInfo taskInfo) {
+        final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+        if (decoration == null) {
+            return;
+        }
+        final DesktopRepository desktopRepository = mDesktopUserRepositories.getProfile(
+                taskInfo.userId);
+        if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) {
+            mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
+                    DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_RESTORE);
+            mDesktopImmersiveController.moveTaskToNonImmersive(
+                    decoration.mTaskInfo, DesktopImmersiveController.ExitReason.USER_INTERACTION);
+        } else {
+            mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
+                    DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_IMMERSIVE);
+            mDesktopImmersiveController.moveTaskToImmersive(decoration.mTaskInfo);
+        }
+        decoration.closeMaximizeMenu();
+    }
+
+    /** Snap-resize a task to the left or right side of the desktop. */
+    public void onSnapResize(int taskId, boolean left, InputMethod inputMethod, boolean fromMenu) {
         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
         if (decoration == null) {
             return;
         }
-        mDesktopTasksController.toggleDesktopTaskFullImmersiveState(decoration.mTaskInfo);
-        decoration.closeMaximizeMenu();
-    }
 
-    public void onSnapResize(int taskId, boolean left, InputMethod inputMethod) {
-        final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
-        if (decoration == null) {
-            return;
+        if (fromMenu) {
+            final DesktopModeUiEventLogger.DesktopUiEventEnum event = left
+                    ? DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_LEFT
+                    : DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_RIGHT;
+            mDesktopModeUiEventLogger.log(decoration.mTaskInfo, event);
         }
 
         mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler,
@@ -644,6 +703,11 @@
         decoration.addCaptionInset(wct);
         mDesktopTasksController.moveTaskToDesktop(taskId, wct, source);
         decoration.closeHandleMenu();
+
+        if (source == DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON) {
+            mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
+                    DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_MENU_TAP_TO_DESKTOP_MODE);
+        }
     }
 
     private void onToFullscreen(int taskId) {
@@ -659,6 +723,8 @@
             mDesktopTasksController.moveToFullscreen(taskId,
                     DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON);
         }
+        mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
+                DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_MENU_TAP_TO_FULL_SCREEN);
     }
 
     private void onToSplitScreen(int taskId) {
@@ -671,6 +737,8 @@
         // we shouldn't receive input for it any longer.
         decoration.disposeStatusBarInputLayer();
         mDesktopTasksController.requestSplit(decoration.mTaskInfo, false /* leftOrTop */);
+        mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
+                DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_MENU_TAP_TO_SPLIT_SCREEN);
     }
 
     private void onNewWindow(int taskId) {
@@ -763,7 +831,6 @@
         private boolean mIsResizeGesture;
         private boolean mIsDragging;
         private boolean mTouchscreenInUse;
-        private boolean mHasLongClicked;
         private int mDragPointerId = -1;
         private MotionEvent mMotionEvent;
 
@@ -829,12 +896,12 @@
                         && TaskInfoKt.getRequestingImmersive(decoration.mTaskInfo)) {
                     // Task is requesting immersive, so it should either enter or exit immersive,
                     // depending on immersive state.
-                    onEnterOrExitImmersive(decoration.mTaskInfo.taskId);
+                    onEnterOrExitImmersive(decoration.mTaskInfo);
                 } else {
                     // Full immersive is disabled or task doesn't request/support it, so just
                     // toggle between maximize/restore states.
-                    onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button",
-                            ResizeTrigger.MAXIMIZE_BUTTON, mMotionEvent);
+                    onToggleSizeInteraction(decoration.mTaskInfo.taskId,
+                            ToggleTaskSizeInteraction.AmbiguousSource.HEADER_BUTTON, mMotionEvent);
                 }
             } else if (id == R.id.minimize_window) {
                 mDesktopTasksController.minimizeTask(decoration.mTaskInfo);
@@ -922,7 +989,8 @@
                 if (decoration.isMaximizeMenuActive()) {
                     decoration.closeMaximizeMenu();
                 } else {
-                    mHasLongClicked = true;
+                    mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
+                            DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_REVEAL_MENU);
                     decoration.createMaximizeMenu();
                 }
                 return true;
@@ -959,6 +1027,8 @@
 
         private void moveTaskToFront(RunningTaskInfo taskInfo) {
             if (!mFocusTransitionObserver.hasGlobalFocus(taskInfo)) {
+                mDesktopModeUiEventLogger.log(taskInfo,
+                        DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_TAP_TO_REFOCUS);
                 mDesktopTasksController.moveTaskToFront(taskInfo);
             }
         }
@@ -1015,8 +1085,10 @@
             }
             final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window
                     || id == R.id.open_menu_button || id == R.id.minimize_window);
+            final DesktopRepository desktopRepository = mDesktopUserRepositories.getProfile(
+                    taskInfo.userId);
             final boolean dragAllowed =
-                    !mDesktopRepository.isTaskInFullImmersiveState(taskInfo.taskId);
+                    !desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId);
             switch (e.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN: {
                     if (dragAllowed) {
@@ -1027,7 +1099,6 @@
                         updateDragStatus(e.getActionMasked());
                         mOnDragStartInitialBounds.set(initialBounds);
                     }
-                    mHasLongClicked = false;
                     // Do not consume input event if a button is touched, otherwise it would
                     // prevent the button's ripple effect from showing.
                     return !touchingButton;
@@ -1061,6 +1132,8 @@
                     if (!wasDragging) {
                         return false;
                     }
+                    mDesktopModeUiEventLogger.log(taskInfo,
+                            DesktopUiEventEnum.DESKTOP_WINDOW_MOVE_BY_HEADER_DRAG);
                     if (e.findPointerIndex(mDragPointerId) == -1) {
                         mDragPointerId = e.getPointerId(0);
                     }
@@ -1082,7 +1155,7 @@
                             newTaskBounds, decoration.calculateValidDragArea(),
                             new Rect(mOnDragStartInitialBounds), e,
                             mWindowDecorByTaskId.get(taskInfo.taskId));
-                    if (touchingButton && !mHasLongClicked) {
+                    if (touchingButton) {
                         // We need the input event to not be consumed here to end the ripple
                         // effect on the touched button. We will reset drag state in the ensuing
                         // onClick call that results.
@@ -1124,11 +1197,13 @@
                     && action != MotionEvent.ACTION_CANCEL)) {
                 return false;
             }
-            if (mDesktopRepository.isTaskInFullImmersiveState(mTaskId)) {
+            final DesktopRepository desktopRepository = mDesktopUserRepositories.getCurrent();
+            if (desktopRepository.isTaskInFullImmersiveState(mTaskId)) {
                 // Disallow double-tap to resize when in full immersive.
                 return false;
             }
-            onMaximizeOrRestore(mTaskId, "double_tap", ResizeTrigger.DOUBLE_TAP_APP_HEADER, e);
+            onToggleSizeInteraction(mTaskId,
+                    ToggleTaskSizeInteraction.AmbiguousSource.DOUBLE_TAP, e);
             return true;
         }
     }
@@ -1536,7 +1611,7 @@
                         mContext.createContextAsUser(UserHandle.of(taskInfo.userId), 0 /* flags */),
                         mDisplayController,
                         mSplitScreenController,
-                        mDesktopRepository,
+                        mDesktopUserRepositories,
                         mTaskOrganizer,
                         taskInfo,
                         taskSurface,
@@ -1566,23 +1641,26 @@
 
         final DesktopModeTouchEventListener touchEventListener =
                 new DesktopModeTouchEventListener(taskInfo, taskPositioner);
-        InputMethod inputMethod = DesktopModeEventLogger.Companion.getInputMethodFromMotionEvent(
-                touchEventListener.mMotionEvent);
         windowDecoration.setOnMaximizeOrRestoreClickListener(() -> {
-            onMaximizeOrRestore(taskInfo.taskId, "maximize_menu", ResizeTrigger.MAXIMIZE_MENU,
+            onToggleSizeInteraction(taskInfo.taskId,
+                    ToggleTaskSizeInteraction.AmbiguousSource.MAXIMIZE_MENU,
                     touchEventListener.mMotionEvent);
             return Unit.INSTANCE;
         });
         windowDecoration.setOnImmersiveOrRestoreClickListener(() -> {
-            onEnterOrExitImmersive(taskInfo.taskId);
+            onEnterOrExitImmersive(taskInfo);
             return Unit.INSTANCE;
         });
         windowDecoration.setOnLeftSnapClickListener(() -> {
-            onSnapResize(taskInfo.taskId, /* isLeft= */ true, inputMethod);
+            onSnapResize(taskInfo.taskId, /* isLeft= */ true,
+                    DesktopModeEventLogger.getInputMethodFromMotionEvent(
+                            touchEventListener.mMotionEvent), /* fromMenu= */ true);
             return Unit.INSTANCE;
         });
         windowDecoration.setOnRightSnapClickListener(() -> {
-            onSnapResize(taskInfo.taskId, /* isLeft= */ false, inputMethod);
+            onSnapResize(taskInfo.taskId, /* isLeft= */ false,
+                    DesktopModeEventLogger.getInputMethodFromMotionEvent(
+                            touchEventListener.mMotionEvent), /* fromMenu= */ true);
             return Unit.INSTANCE;
         });
         windowDecoration.setOnToDesktopClickListener(desktopModeTransitionSource -> {
@@ -1611,6 +1689,14 @@
             CompatUIController.launchUserAspectRatioSettings(mContext, taskInfo);
             return Unit.INSTANCE;
         });
+        windowDecoration.setOnMaximizeHoverListener(() -> {
+            if (!windowDecoration.isMaximizeMenuActive()) {
+                mDesktopModeUiEventLogger.log(taskInfo,
+                        DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_REVEAL_MENU);
+                windowDecoration.createMaximizeMenu();
+            }
+            return Unit.INSTANCE;
+        });
         windowDecoration.setCaptionListeners(
                 touchEventListener, touchEventListener, touchEventListener, touchEventListener);
         windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
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 b6fa059..e7985de 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
@@ -99,12 +99,12 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.CaptionState;
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
-import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
-import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer;
+import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
@@ -155,6 +155,7 @@
     private Function0<Unit> mOnNewWindowClickListener;
     private Function0<Unit> mOnManageWindowsClickListener;
     private Function0<Unit> mOnChangeAspectRatioClickListener;
+    private Function0<Unit> mOnMaximizeHoverListener;
     private DragPositioningCallback mDragPositioningCallback;
     private DragResizeInputListener mDragResizeListener;
     private Runnable mCurrentViewHostRunnable = null;
@@ -205,14 +206,14 @@
     private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired;
     private final MultiInstanceHelper mMultiInstanceHelper;
     private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
-    private final DesktopRepository mDesktopRepository;
+    private final DesktopUserRepositories mDesktopUserRepositories;
 
     public DesktopModeWindowDecoration(
             Context context,
             @NonNull Context userContext,
             DisplayController displayController,
             SplitScreenController splitScreenController,
-            DesktopRepository desktopRepository,
+            DesktopUserRepositories desktopUserRepositories,
             ShellTaskOrganizer taskOrganizer,
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
@@ -227,10 +228,10 @@
             MultiInstanceHelper multiInstanceHelper,
             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
             DesktopModeEventLogger desktopModeEventLogger) {
-        this (context, userContext, displayController, splitScreenController, desktopRepository,
-                taskOrganizer, taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue,
-                appHeaderViewHolderFactory, rootTaskDisplayAreaOrganizer, genericLinksParser,
-                assistContentRequester,
+        this (context, userContext, displayController, splitScreenController,
+                desktopUserRepositories, taskOrganizer, taskInfo, taskSurface, handler,
+                bgExecutor, choreographer, syncQueue, appHeaderViewHolderFactory,
+                rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester,
                 SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
                 WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(
                         context.getSystemService(WindowManager.class)),
@@ -245,7 +246,7 @@
             @NonNull Context userContext,
             DisplayController displayController,
             SplitScreenController splitScreenController,
-            DesktopRepository desktopRepository,
+            DesktopUserRepositories desktopUserRepositories,
             ShellTaskOrganizer taskOrganizer,
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
@@ -286,7 +287,7 @@
         mMultiInstanceHelper = multiInstanceHelper;
         mWindowManagerWrapper = windowManagerWrapper;
         mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
-        mDesktopRepository = desktopRepository;
+        mDesktopUserRepositories = desktopUserRepositories;
     }
 
     /**
@@ -370,6 +371,11 @@
         mOnChangeAspectRatioClickListener = listener;
     }
 
+    /** Registers a listener to be called when the maximize header button is hovered. */
+    void setOnMaximizeHoverListener(Function0<Unit> listener) {
+        mOnMaximizeHoverListener = listener;
+    }
+
     void setCaptionListeners(
             View.OnClickListener onCaptionButtonClickListener,
             View.OnTouchListener onCaptionTouchListener,
@@ -431,7 +437,7 @@
     public void updateDisabledResizingEdge(
             DragResizeWindowGeometry.DisabledEdge disabledResizingEdge, boolean shouldDelayUpdate) {
         mDisabledResizingEdge = disabledResizingEdge;
-        final boolean inFullImmersive = mDesktopRepository
+        final boolean inFullImmersive = mDesktopUserRepositories.getCurrent()
                 .isTaskInFullImmersiveState(mTaskInfo.taskId);
         if (shouldDelayUpdate) {
             return;
@@ -535,11 +541,11 @@
             mOpenByDefaultDialog.relayout(taskInfo);
         }
 
-        final boolean inFullImmersive = mDesktopRepository
+        final boolean inFullImmersive = mDesktopUserRepositories.getProfile(taskInfo.userId)
                 .isTaskInFullImmersiveState(taskInfo.taskId);
-        updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
-                shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
-                mIsKeyguardVisibleAndOccluded, inFullImmersive,
+        updateRelayoutParams(mRelayoutParams, mContext, taskInfo, mSplitScreenController,
+                applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
+                mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive,
                 mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
                 displayExclusionRegion);
 
@@ -841,12 +847,7 @@
                     mOnCaptionGenericMotionListener,
                     mAppName,
                     mAppIconBitmap,
-                    () -> {
-                        if (!isMaximizeMenuActive()) {
-                            createMaximizeMenu();
-                        }
-                        return Unit.INSTANCE;
-                    });
+                    mOnMaximizeHoverListener);
         }
         throw new IllegalArgumentException("Unexpected layout resource id");
     }
@@ -876,6 +877,7 @@
             RelayoutParams relayoutParams,
             Context context,
             ActivityManager.RunningTaskInfo taskInfo,
+            SplitScreenController splitScreenController,
             boolean applyStartTransactionOnDraw,
             boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean isStatusBarVisible,
@@ -917,7 +919,10 @@
                     || (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
         }
         relayoutParams.mIsCaptionVisible = showCaption;
-        relayoutParams.mIsInsetSource = isAppHeader && !inFullImmersiveMode;
+        final boolean isBottomSplit = !splitScreenController.isLeftRightSplit()
+                && splitScreenController.getSplitPosition(taskInfo.taskId)
+                == SPLIT_POSITION_BOTTOM_OR_RIGHT;
+        relayoutParams.mIsInsetSource = (isAppHeader && !inFullImmersiveMode) || isBottomSplit;
         if (isAppHeader) {
             if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
                 // The app is requesting to customize the caption bar, which means input on
@@ -979,10 +984,15 @@
             relayoutParams.mInputFeatures
                     |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
         }
-        if (DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ hasGlobalFocus)) {
-            relayoutParams.mShadowRadiusId = hasGlobalFocus
-                    ? R.dimen.freeform_decor_shadow_focused_thickness
-                    : R.dimen.freeform_decor_shadow_unfocused_thickness;
+        if (isAppHeader
+                && DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ hasGlobalFocus)) {
+            relayoutParams.mShadowRadius = hasGlobalFocus
+                    ? context.getResources().getDimensionPixelSize(
+                            R.dimen.freeform_decor_shadow_focused_thickness)
+                    : context.getResources().getDimensionPixelSize(
+                            R.dimen.freeform_decor_shadow_unfocused_thickness);
+        } else {
+            relayoutParams.mShadowRadius = INVALID_SHADOW_RADIUS;
         }
         relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
         relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
@@ -1296,9 +1306,11 @@
         mMaximizeMenu = mMaximizeMenuFactory.create(mSyncQueue, mRootTaskDisplayAreaOrganizer,
                 mDisplayController, mTaskInfo, mContext,
                 calculateMaximizeMenuPosition(menuWidth), mSurfaceControlTransactionSupplier);
+
         mMaximizeMenu.show(
                 /* isTaskInImmersiveMode= */ Flags.enableFullyImmersiveInDesktop()
-                        && mDesktopRepository.isTaskInFullImmersiveState(mTaskInfo.taskId),
+                        && mDesktopUserRepositories.getProfile(mTaskInfo.userId)
+                            .isTaskInFullImmersiveState(mTaskInfo.taskId),
                 /* menuWidth= */ menuWidth,
                 /* showImmersiveOption= */ Flags.enableFullyImmersiveInDesktop()
                         && TaskInfoKt.getRequestingImmersive(mTaskInfo),
@@ -1388,7 +1400,7 @@
                 && mMinimumInstancesFound;
         final boolean shouldShowChangeAspectRatioButton = HandleMenu.Companion
                 .shouldShowChangeAspectRatioButton(mTaskInfo);
-        final boolean inDesktopImmersive = mDesktopRepository
+        final boolean inDesktopImmersive = mDesktopUserRepositories.getProfile(mTaskInfo.userId)
                 .isTaskInFullImmersiveState(mTaskInfo.taskId);
         final boolean isBrowserApp = isBrowserApp();
         mHandleMenu = mHandleMenuFactory.create(
@@ -1417,7 +1429,6 @@
         mHandleMenu.show(
                 /* onToDesktopClickListener= */ () -> {
                     mOnToDesktopClickListener.accept(APP_HANDLE_MENU_BUTTON);
-                    mOnToDesktopClickListener.accept(APP_HANDLE_MENU_BUTTON);
                     return Unit.INSTANCE;
                 },
                 /* onToFullscreenClickListener= */ mOnToFullscreenClickListener,
@@ -1428,7 +1439,7 @@
                 /* openInBrowserClickListener= */ (intent) -> {
                     mOpenInBrowserClickListener.accept(intent);
                     onCapturedLinkExpired();
-                    if (Flags.enableDesktopWindowingAppToWebEducation()) {
+                    if (Flags.enableDesktopWindowingAppToWebEducationIntegration()) {
                         mWindowDecorCaptionHandleRepository.onAppToWebUsage();
                     }
                     return Unit.INSTANCE;
@@ -1459,14 +1470,17 @@
             @NonNull Function1<Integer, Unit> onIconClickListener
     ) {
         if (mTaskInfo.isFreeform()) {
+            // The menu uses display-wide coordinates for positioning, so make position the sum
+            // of task position and caption position.
+            final Rect taskBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
             mManageWindowsMenu = new DesktopHeaderManageWindowsMenu(
                     mTaskInfo,
-                    /* x= */ mResult.mCaptionX,
-                    /* y= */ mResult.mCaptionY + mResult.mCaptionTopPadding,
+                    /* x= */ taskBounds.left + mResult.mCaptionX,
+                    /* y= */ taskBounds.top + mResult.mCaptionY + mResult.mCaptionTopPadding,
                     mDisplayController,
                     mRootTaskDisplayAreaOrganizer,
                     mContext,
-                    mDesktopRepository,
+                    mDesktopUserRepositories,
                     mSurfaceControlBuilderSupplier,
                     mSurfaceControlTransactionSupplier,
                     snapshotList,
@@ -1678,7 +1692,7 @@
     /** Returns true if at least one education flag is enabled. */
     private boolean isEducationEnabled() {
         return Flags.enableDesktopWindowingAppHandleEducation()
-                || Flags.enableDesktopWindowingAppToWebEducation();
+                || Flags.enableDesktopWindowingAppToWebEducationIntegration();
     }
 
     @Override
@@ -1762,7 +1776,8 @@
     void setAnimatingTaskResizeOrReposition(boolean animatingTaskResizeOrReposition) {
         if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) return;
         final boolean inFullImmersive =
-                mDesktopRepository.isTaskInFullImmersiveState(mTaskInfo.taskId);
+                mDesktopUserRepositories.getProfile(mTaskInfo.userId)
+                        .isTaskInFullImmersiveState(mTaskInfo.taskId);
         asAppHeader(mWindowDecorViewHolder).bindData(new AppHeaderViewHolder.HeaderData(
                 mTaskInfo,
                 TaskInfoKt.getRequestingImmersive(mTaskInfo),
@@ -1790,8 +1805,9 @@
             return !animatingTaskResizeOrReposition;
         }
         final boolean inImmersiveAndRequesting =
-                mDesktopRepository.isTaskInFullImmersiveState(mTaskInfo.taskId)
-                        && TaskInfoKt.getRequestingImmersive(mTaskInfo);
+                mDesktopUserRepositories.getProfile(mTaskInfo.userId)
+                        .isTaskInFullImmersiveState(mTaskInfo.taskId)
+                    && TaskInfoKt.getRequestingImmersive(mTaskInfo);
         return !animatingTaskResizeOrReposition && !inImmersiveAndRequesting;
     }
 
@@ -1812,7 +1828,7 @@
                 @NonNull Context userContext,
                 DisplayController displayController,
                 SplitScreenController splitScreenController,
-                DesktopRepository desktopRepository,
+                DesktopUserRepositories desktopUserRepositories,
                 ShellTaskOrganizer taskOrganizer,
                 ActivityManager.RunningTaskInfo taskInfo,
                 SurfaceControl taskSurface,
@@ -1832,7 +1848,7 @@
                     userContext,
                     displayController,
                     splitScreenController,
-                    desktopRepository,
+                    desktopUserRepositories,
                     taskOrganizer,
                     taskInfo,
                     taskSurface,
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 b321399..a6d503d 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
@@ -460,7 +460,7 @@
                                 || ctrlType == CTRL_TYPE_RIGHT || ctrlType == CTRL_TYPE_LEFT)
                                 ? ResizeTrigger.EDGE : ResizeTrigger.CORNER;
                         mDesktopModeEventLogger.logTaskResizingStarted(mResizeTrigger,
-                                DesktopModeEventLogger.Companion.getInputMethodFromMotionEvent(e),
+                                DesktopModeEventLogger.getInputMethodFromMotionEvent(e),
                                 mTaskInfo, mDragStartTaskBounds.width(),
                                 mDragStartTaskBounds.height(), /* displayController= */ null,
                                 /* displayLayoutSize= */ mDisplayLayoutSizeSupplier.get());
@@ -514,7 +514,7 @@
                         }
 
                         mDesktopModeEventLogger.logTaskResizingEnded(mResizeTrigger,
-                                DesktopModeEventLogger.Companion.getInputMethodFromMotionEvent(
+                                DesktopModeEventLogger.getInputMethodFromMotionEvent(
                                         mLastMotionEventOnDown), mTaskInfo, taskBounds.width(),
                                 taskBounds.height(),
                                 /* displayController= */ null,
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 6f72d34..c8aff78 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
@@ -74,8 +74,7 @@
         mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize, disabledEdge);
 
         // Save touch areas for each edge.
-        mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleEdgeOutset, mResizeHandleEdgeInset,
-                mDisabledEdge);
+        mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleEdgeOutset, mDisabledEdge);
     }
 
     /**
@@ -459,7 +458,7 @@
         private final @NonNull DisabledEdge mDisabledEdge;
 
         private TaskEdges(@NonNull Size taskSize, int resizeHandleThickness,
-                int resizeHandleEdgeInset, DisabledEdge disabledEdge) {
+                DisabledEdge disabledEdge) {
             // Save touch areas for each edge.
             mDisabledEdge = disabledEdge;
             // Save touch areas for each edge.
@@ -471,16 +470,16 @@
             mLeftEdgeBounds = new Rect(
                     -resizeHandleThickness,
                     0,
-                    resizeHandleEdgeInset,
+                    resizeHandleThickness,
                     taskSize.getHeight());
             mRightEdgeBounds = new Rect(
-                    taskSize.getWidth() - resizeHandleEdgeInset,
+                    taskSize.getWidth() - resizeHandleThickness,
                     0,
                     taskSize.getWidth() + resizeHandleThickness,
                     taskSize.getHeight());
             mBottomEdgeBounds = new Rect(
                     -resizeHandleThickness,
-                    taskSize.getHeight() - resizeHandleEdgeInset,
+                    taskSize.getHeight() - resizeHandleThickness,
                     taskSize.getWidth() + resizeHandleThickness,
                     taskSize.getHeight() + resizeHandleThickness);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index a1e329a..1f03d75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -37,6 +37,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.jank.Cuj;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -44,6 +45,7 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Supplier;
 
 /**
@@ -53,6 +55,9 @@
  * If the drag is repositioning, we update in the typical manner.
  */
 public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.TransitionHandler {
+    // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
+    // timing out in the middle of a resize or drag action.
+    private static final long LONG_CUJ_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10L);
 
     private DesktopModeWindowDecoration mDesktopWindowDecoration;
     private ShellTaskOrganizer mTaskOrganizer;
@@ -106,8 +111,8 @@
         mRepositionStartPoint.set(x, y);
         if (isResizing()) {
             // Capture CUJ for re-sizing window in DW mode.
-            mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
-                    mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_RESIZE_WINDOW);
+            mInteractionJankMonitor.begin(
+                    createLongTimeoutJankConfigBuilder(CUJ_DESKTOP_MODE_RESIZE_WINDOW));
             if (!mDesktopWindowDecoration.mHasGlobalFocus) {
                 WindowContainerTransaction wct = new WindowContainerTransaction();
                 wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true /* onTop */,
@@ -153,8 +158,8 @@
             }
         } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
             // Begin window drag CUJ instrumentation only when drag position moves.
-            mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
-                    mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_DRAG_WINDOW);
+            mInteractionJankMonitor.begin(
+                    createLongTimeoutJankConfigBuilder(CUJ_DESKTOP_MODE_DRAG_WINDOW));
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
                     mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
@@ -207,6 +212,14 @@
         }
     }
 
+    private InteractionJankMonitor.Configuration.Builder createLongTimeoutJankConfigBuilder(
+            @Cuj.CujType int cujType) {
+        return InteractionJankMonitor.Configuration.Builder
+                .withSurface(cujType, mDesktopWindowDecoration.mContext,
+                        mDesktopWindowDecoration.mTaskSurface, mHandler)
+                .setTimeout(LONG_CUJ_TIMEOUT_MS);
+    }
+
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
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 852eee5f..584ee39 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
@@ -17,7 +17,6 @@
 package com.android.wm.shell.windowdecor;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
 import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.mandatorySystemGestures;
@@ -110,6 +109,10 @@
      * Invalid corner radius that signifies that corner radius should not be set.
      */
     static final int INVALID_CORNER_RADIUS = -1;
+    /**
+     * Invalid corner radius that signifies that shadow radius should not be set.
+     */
+    static final int INVALID_SHADOW_RADIUS = -1;
 
     /**
      * System-wide context. Only used to create context with overridden configurations.
@@ -439,16 +442,10 @@
                     .setPosition(mTaskSurface, taskPosition.x, taskPosition.y);
         }
 
-        float shadowRadius;
-        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
-            // Shadow is not needed for fullscreen tasks
-            shadowRadius = 0;
-        } else {
-            shadowRadius =
-                    loadDimension(mDecorWindowContext.getResources(), params.mShadowRadiusId);
+        if (params.mShadowRadius != INVALID_SHADOW_RADIUS) {
+            startT.setShadowRadius(mTaskSurface, params.mShadowRadius);
+            finishT.setShadowRadius(mTaskSurface, params.mShadowRadius);
         }
-        startT.setShadowRadius(mTaskSurface, shadowRadius);
-        finishT.setShadowRadius(mTaskSurface, shadowRadius);
 
         if (params.mSetTaskVisibilityPositionAndCrop) {
             startT.show(mTaskSurface);
@@ -851,8 +848,8 @@
         @InsetsSource.Flags int mInsetSourceFlags;
         final Region mDisplayExclusionRegion = Region.obtain();
 
-        int mShadowRadiusId;
-        int mCornerRadius;
+        int mShadowRadius = INVALID_SHADOW_RADIUS;
+        int mCornerRadius = INVALID_CORNER_RADIUS;
 
         int mCaptionTopPadding;
         boolean mIsCaptionVisible;
@@ -874,8 +871,8 @@
             mInsetSourceFlags = 0;
             mDisplayExclusionRegion.setEmpty();
 
-            mShadowRadiusId = Resources.ID_NULL;
-            mCornerRadius = 0;
+            mShadowRadius = INVALID_SHADOW_RADIUS;
+            mCornerRadius = INVALID_SHADOW_RADIUS;
 
             mCaptionTopPadding = 0;
             mIsCaptionVisible = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
new file mode 100644
index 0000000..c470eef
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.common.viewhost
+
+import android.content.Context
+import android.content.res.Configuration
+import android.view.Display
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.View
+import android.view.WindowManager
+import android.view.WindowlessWindowManager
+import androidx.tracing.Trace
+import com.android.internal.annotations.VisibleForTesting
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+typealias SurfaceControlViewHostFactory =
+    (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost
+
+/**
+ * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHost].
+ *
+ * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and
+ * any attempts to do will throw, which means that once a [View] is added using [updateView] or
+ * [updateViewAsync], only its properties and binding may be changed, its children views may be
+ * added, removed or changed and its [WindowManager.LayoutParams] may be changed. It also supports
+ * asynchronously updating the view hierarchy using [updateViewAsync], in which case the update work
+ * will be posted on the [ShellMainThread] with no delay.
+ */
+class DefaultWindowDecorViewHost(
+    private val context: Context,
+    @ShellMainThread private val mainScope: CoroutineScope,
+    private val display: Display,
+    private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s ->
+        SurfaceControlViewHost(c, d, wwm, s)
+    },
+) : WindowDecorViewHost {
+
+    private val rootSurface: SurfaceControl =
+        SurfaceControl.Builder()
+            .setName("DefaultWindowDecorViewHost surface")
+            .setContainerLayer()
+            .setCallsite("DefaultWindowDecorViewHost#init")
+            .build()
+
+    private var wwm: WindowlessWindowManager? = null
+    @VisibleForTesting var viewHost: SurfaceControlViewHost? = null
+    private var currentUpdateJob: Job? = null
+
+    override val surfaceControl: SurfaceControl
+        get() = rootSurface
+
+    override fun updateView(
+        view: View,
+        attrs: WindowManager.LayoutParams,
+        configuration: Configuration,
+        onDrawTransaction: SurfaceControl.Transaction?,
+    ) {
+        Trace.beginSection("DefaultWindowDecorViewHost#updateView")
+        clearCurrentUpdateJob()
+        updateViewHost(view, attrs, configuration, onDrawTransaction)
+        Trace.endSection()
+    }
+
+    override fun updateViewAsync(
+        view: View,
+        attrs: WindowManager.LayoutParams,
+        configuration: Configuration,
+    ) {
+        Trace.beginSection("DefaultWindowDecorViewHost#updateViewAsync")
+        clearCurrentUpdateJob()
+        currentUpdateJob =
+            mainScope.launch {
+                updateViewHost(view, attrs, configuration, onDrawTransaction = null)
+            }
+        Trace.endSection()
+    }
+
+    override fun release(t: SurfaceControl.Transaction) {
+        clearCurrentUpdateJob()
+        viewHost?.release()
+        t.remove(rootSurface)
+    }
+
+    private fun updateViewHost(
+        view: View,
+        attrs: WindowManager.LayoutParams,
+        configuration: Configuration,
+        onDrawTransaction: SurfaceControl.Transaction?,
+    ) {
+        Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost")
+        if (wwm == null) {
+            wwm = WindowlessWindowManager(configuration, rootSurface, null)
+        }
+        requireWindowlessWindowManager().setConfiguration(configuration)
+        if (viewHost == null) {
+            viewHost =
+                surfaceControlViewHostFactory.invoke(
+                    context,
+                    display,
+                    requireWindowlessWindowManager(),
+                    "DefaultWindowDecorViewHost#updateViewHost",
+                )
+        }
+        onDrawTransaction?.let { requireViewHost().rootSurfaceControl.applyTransactionOnDraw(it) }
+        if (requireViewHost().view == null) {
+            Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-setView")
+            requireViewHost().setView(view, attrs)
+            Trace.endSection()
+        } else {
+            check(requireViewHost().view == view) { "Changing view is not allowed" }
+            Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-relayout")
+            requireViewHost().relayout(attrs)
+            Trace.endSection()
+        }
+        Trace.endSection()
+    }
+
+    private fun clearCurrentUpdateJob() {
+        currentUpdateJob?.cancel()
+        currentUpdateJob = null
+    }
+
+    private fun requireWindowlessWindowManager(): WindowlessWindowManager {
+        return wwm ?: error("Expected non-null windowless window manager")
+    }
+
+    private fun requireViewHost(): SurfaceControlViewHost {
+        return viewHost ?: error("Expected non-null view host")
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt
new file mode 100644
index 0000000..27ffd6c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.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.wm.shell.windowdecor.common.viewhost
+
+import android.content.Context
+import android.view.Display
+import android.view.SurfaceControl
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * A supplier of [DefaultWindowDecorViewHost]s. It creates a new one every time one is requested.
+ */
+class DefaultWindowDecorViewHostSupplier(@ShellMainThread private val mainScope: CoroutineScope) :
+    WindowDecorViewHostSupplier<DefaultWindowDecorViewHost> {
+
+    override fun acquire(context: Context, display: Display): DefaultWindowDecorViewHost {
+        return DefaultWindowDecorViewHost(context, mainScope, display)
+    }
+
+    override fun release(viewHost: DefaultWindowDecorViewHost, t: SurfaceControl.Transaction) {
+        viewHost.release(t)
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt
new file mode 100644
index 0000000..7c1479e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.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.wm.shell.windowdecor.common.viewhost
+
+import android.content.res.Configuration
+import android.view.SurfaceControl
+import android.view.View
+import android.view.WindowManager
+import com.android.wm.shell.windowdecor.WindowDecoration
+
+/**
+ * An interface for a utility that hosts a [WindowDecoration]'s [View] hierarchy under a
+ * [SurfaceControl].
+ */
+interface WindowDecorViewHost {
+    /** The surface where the underlying [View] hierarchy is being rendered. */
+    val surfaceControl: SurfaceControl
+
+    /** Synchronously update the view hierarchy of this view host. */
+    fun updateView(
+        view: View,
+        attrs: WindowManager.LayoutParams,
+        configuration: Configuration,
+        onDrawTransaction: SurfaceControl.Transaction?,
+    )
+
+    /** Asynchronously update the view hierarchy of this view host. */
+    fun updateViewAsync(view: View, attrs: WindowManager.LayoutParams, configuration: Configuration)
+
+    /** Releases the underlying [View] hierarchy and removes the backing [SurfaceControl]. */
+    fun release(t: SurfaceControl.Transaction)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHostSupplier.kt
new file mode 100644
index 0000000..00e29ec
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHostSupplier.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.common.viewhost
+
+import android.content.Context
+import android.view.Display
+import android.view.SurfaceControl
+
+/** An interface for a supplier of [WindowDecorViewHost]s. */
+interface WindowDecorViewHostSupplier<T : WindowDecorViewHost> {
+    /** Acquire a [WindowDecorViewHost]. */
+    fun acquire(context: Context, display: Display): T
+
+    /**
+     * Release a [WindowDecorViewHost] when it is no longer used.
+     *
+     * @param viewHost the [WindowDecorViewHost] to release
+     * @param t a transaction that may be used to remove any underlying backing [SurfaceControl]
+     *   that are hosting this [WindowDecorViewHost]. The supplier is not expected to apply the
+     *   transaction. It should be applied by the owner of this supplier.
+     */
+    fun release(viewHost: T, t: SurfaceControl.Transaction)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
index 0e40a53..9db69d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
@@ -33,6 +33,7 @@
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger
 import com.android.wm.shell.desktopmode.DesktopRepository
 import com.android.wm.shell.desktopmode.DesktopTasksController
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
 import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
 import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler
 import com.android.wm.shell.transition.Transitions
@@ -48,7 +49,7 @@
     private val shellTaskOrganizer: ShellTaskOrganizer,
     private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
     private val returnToDragStartAnimator: ReturnToDragStartAnimator,
-    private val taskRepository: DesktopRepository,
+    private val desktopUserRepositories: DesktopUserRepositories,
     private val desktopModeEventLogger: DesktopModeEventLogger,
 ) : DisplayChangeController.OnDisplayChangingListener {
     @VisibleForTesting
@@ -81,7 +82,7 @@
                             shellTaskOrganizer,
                             toggleResizeDesktopTaskTransitionHandler,
                             returnToDragStartAnimator,
-                            taskRepository,
+                            desktopUserRepositories,
                             desktopModeEventLogger,
                         )
                     tilingTransitionHandlerByDisplayId.put(displayId, newHandler)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index 6cdc517c..5832822 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -18,10 +18,12 @@
 
 import android.content.Context
 import android.content.res.Configuration
+import android.graphics.Path
 import android.graphics.PixelFormat
 import android.graphics.Rect
 import android.graphics.Region
 import android.os.Binder
+import android.util.Size
 import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.RoundedCorner
@@ -40,7 +42,6 @@
 import android.view.WindowlessWindowManager
 import com.android.wm.shell.R
 import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.desktopmode.DesktopModeEventLogger
 import java.util.function.Supplier
 
 /**
@@ -48,7 +49,7 @@
  * when two tasks are tiled on left and right to resize them simultaneously.
  */
 class DesktopTilingDividerWindowManager(
-    private val config: Configuration,
+    config: Configuration,
     private val windowName: String,
     private val context: Context,
     private val leash: SurfaceControl,
@@ -61,7 +62,11 @@
     private lateinit var viewHost: SurfaceControlViewHost
     private var tilingDividerView: TilingDividerView? = null
     private var dividerShown = false
-    private var handleRegionWidth: Int = -1
+    private var handleRegionSize: Size =
+        Size(
+            context.resources.getDimensionPixelSize(R.dimen.split_divider_handle_region_width),
+            context.resources.getDimensionPixelSize(R.dimen.split_divider_handle_region_height),
+        )
     private var setTouchRegion = true
     private val maxRoundedCornerRadius = getMaxRoundedCornerRadius()
 
@@ -74,9 +79,62 @@
         rect.set(dividerBounds)
     }
 
-    /** Sets the touch region for the SurfaceControlViewHost. */
-    fun setTouchRegion(region: Rect) {
-        setTouchRegion(viewHost.windowToken.asBinder(), Region(region))
+    /**
+     * Sets the touch region for the SurfaceControlViewHost.
+     *
+     * The region includes the area around the handle (for accessibility), the divider itself and
+     * the rounded corners (to prevent click reaching windows behind).
+     */
+    fun setTouchRegion(handle: Rect, divider: Rect, cornerRadius: Float) {
+        val path = Path()
+        path.fillType = Path.FillType.WINDING
+        // The UI starts on the top-left corner, the region will be:
+        //
+        //      cornerLeft     cornerRight
+        // c1Top        +--------+
+        //              |corners |
+        // c1Bottom     +--+  +--+
+        //                 |  |
+        //       handleLeft|  |  handleRight
+        // handleTop  +----+  +----+
+        //            |  handle    |
+        // handleBot  +----+  +----+
+        //                 |  |
+        //                 |  |
+        // c2Top        +--+  +--+
+        //              |corners |
+        // c2Bottom     +--------+
+        val cornerLeft = 0f
+        val centerX = cornerRadius + divider.width() / 2f
+        val centerY = divider.height()
+        val cornerRight = divider.width() + 2 * cornerRadius
+        val handleLeft = centerX - handle.width() / 2f
+        val handleRight = handleLeft + handle.width()
+        val dividerLeft = centerY - divider.width() / 2f
+        val dividerRight = dividerLeft + divider.width()
+
+        val c1Top = 0f
+        val c1Bottom = cornerRadius
+        val handleTop = centerY - handle.height() / 2f
+        val handleBottom = handleTop + handle.height()
+        val c2Top = divider.height() - cornerRadius
+        val c2Bottom = divider.height().toFloat()
+
+        // Top corners
+        path.addRect(cornerLeft, c1Top, cornerRight, c1Bottom, Path.Direction.CCW)
+        // Bottom corners
+        path.addRect(cornerLeft, c1Top, cornerRight, c2Bottom, Path.Direction.CCW)
+        // Handle
+        path.addRect(handleLeft, handleTop, handleRight, handleBottom, Path.Direction.CCW)
+        // Divider
+        path.addRect(dividerLeft, c2Top, dividerRight, c2Bottom, Path.Direction.CCW)
+
+        val clip = Rect(handleLeft.toInt(), c1Top.toInt(), handleRight.toInt(), c2Bottom.toInt())
+
+        val region = Region()
+        region.setPath(path, Region(clip))
+
+        setTouchRegion(viewHost.windowToken.asBinder(), region)
     }
 
     /**
@@ -96,7 +154,7 @@
         surfaceControlViewHost.setView(dividerView, lp)
         val tmpDividerBounds = Rect()
         getDividerBounds(tmpDividerBounds)
-        dividerView.setup(this, tmpDividerBounds)
+        dividerView.setup(this, tmpDividerBounds, handleRegionSize)
         t.setRelativeLayer(leash, relativeLeash, 1)
             .setPosition(
                 leash,
@@ -112,7 +170,7 @@
         viewHost = surfaceControlViewHost
         dividerView.addOnLayoutChangeListener(this)
         tilingDividerView = dividerView
-        handleRegionWidth = dividerView.handleRegionWidth
+        updateTouchRegion()
     }
 
     /** Hides the divider bar. */
@@ -176,8 +234,8 @@
     private fun getWindowManagerParams(): WindowManager.LayoutParams {
         val lp =
             WindowManager.LayoutParams(
-                dividerBounds.width() + 2 * maxRoundedCornerRadius,
-                dividerBounds.height(),
+                /* w= */ dividerBounds.width() + 2 * maxRoundedCornerRadius,
+                /* h= */ dividerBounds.height(),
                 TYPE_DOCK_DIVIDER,
                 FLAG_NOT_FOCUSABLE or
                     FLAG_NOT_TOUCH_MODAL or
@@ -216,13 +274,16 @@
     ) {
         if (!setTouchRegion) return
 
-        val startX = (dividerBounds.width() - handleRegionWidth) / 2
-        val startY = 0
-        val tempRect = Rect(startX, startY, startX + handleRegionWidth, dividerBounds.height())
-        setTouchRegion(tempRect)
+        updateTouchRegion()
         setTouchRegion = false
     }
 
+    private fun updateTouchRegion() {
+        val startX = -handleRegionSize.width / 2
+        val handle = Rect(startX, 0, startX + handleRegionSize.width, dividerBounds.height())
+        setTouchRegion(handle, dividerBounds, maxRoundedCornerRadius.toFloat())
+    }
+
     private fun setSlippery(slippery: Boolean) {
         val lp = tilingDividerView?.layoutParams as WindowManager.LayoutParams
         val isSlippery = (lp.flags and FLAG_SLIPPERY) != 0
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index bc7576c..7ceac52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -49,6 +49,7 @@
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 import com.android.wm.shell.desktopmode.DesktopRepository
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
 import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
 import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler
 import com.android.wm.shell.transition.Transitions
@@ -72,7 +73,7 @@
     private val shellTaskOrganizer: ShellTaskOrganizer,
     private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
     private val returnToDragStartAnimator: ReturnToDragStartAnimator,
-    private val taskRepository: DesktopRepository,
+    private val desktopUserRepositories: DesktopUserRepositories,
     private val desktopModeEventLogger: DesktopModeEventLogger,
     private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
 ) :
@@ -492,9 +493,8 @@
 
     // Only called if [taskInfo] relates to a focused task
     private fun isTilingRefocused(taskInfo: RunningTaskInfo): Boolean {
-        return !isTilingFocused &&
-            (taskInfo.taskId == leftTaskResizingHelper?.taskInfo?.taskId ||
-                taskInfo.taskId == rightTaskResizingHelper?.taskInfo?.taskId)
+        return taskInfo.taskId == leftTaskResizingHelper?.taskInfo?.taskId ||
+                taskInfo.taskId == rightTaskResizingHelper?.taskInfo?.taskId
     }
 
     private fun buildTiledTasksMoveToFront(leftOnTop: Boolean): WindowContainerTransaction {
@@ -631,6 +631,7 @@
     private fun allTiledTasksVisible(): Boolean {
         val leftTiledTask = leftTaskResizingHelper ?: return false
         val rightTiledTask = rightTaskResizingHelper ?: return false
+        val taskRepository = desktopUserRepositories.current
         return taskRepository.isVisibleTask(leftTiledTask.taskInfo.taskId) &&
             taskRepository.isVisibleTask(rightTiledTask.taskInfo.taskId)
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
index 111e28e..b8e3b0f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
@@ -21,8 +21,10 @@
 import android.graphics.Rect
 import android.provider.DeviceConfig
 import android.util.AttributeSet
+import android.util.Size
 import android.view.MotionEvent
 import android.view.PointerIcon
+import android.view.RoundedCorner
 import android.view.View
 import android.view.ViewConfiguration
 import android.widget.FrameLayout
@@ -42,6 +44,7 @@
     private lateinit var callback: DividerMoveCallback
     private lateinit var handle: DividerHandleView
     private lateinit var corners: DividerRoundedCorner
+    private var cornersRadius: Int = 0
     private var touchElevation = 0
 
     private var moving = false
@@ -49,8 +52,7 @@
     var handleRegionWidth: Int = 0
     private var handleRegionHeight = 0
     private var lastAcceptedPos = 0
-    @VisibleForTesting var handleStartY = 0
-    @VisibleForTesting var handleEndY = 0
+    @VisibleForTesting var handleY: IntRange = 0..0
     private var canResize = false
     private var resized = false
     /**
@@ -79,16 +81,19 @@
     ) : super(context, attrs, defStyleAttr, defStyleRes)
 
     /** Sets up essential dependencies of the divider bar. */
-    fun setup(dividerMoveCallback: DividerMoveCallback, dividerBounds: Rect) {
+    fun setup(
+        dividerMoveCallback: DividerMoveCallback,
+        dividerBounds: Rect,
+        handleRegionSize: Size,
+    ) {
         callback = dividerMoveCallback
         this.dividerBounds.set(dividerBounds)
         handle.setIsLeftRightSplit(true)
         corners.setIsLeftRightSplit(true)
-        handleRegionHeight =
-            resources.getDimensionPixelSize(R.dimen.split_divider_handle_region_width)
-
-        handleRegionWidth =
-            resources.getDimensionPixelSize(R.dimen.split_divider_handle_region_height)
+        handleRegionHeight = handleRegionSize.height
+        handleRegionWidth = handleRegionSize.width
+        cornersRadius =
+            context.display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT)?.radius ?: 0
         initHandleYCoordinates()
         dragDetector =
             DragDetector(
@@ -241,17 +246,17 @@
         return true
     }
 
-    private fun isWithinHandleRegion(touchYPos: Int): Boolean {
-        return touchYPos in handleStartY..handleEndY
-    }
+    private fun isWithinHandleRegion(touchYPos: Int): Boolean = touchYPos in handleY
 
     private fun initHandleYCoordinates() {
-        handleStartY = (dividerBounds.height() - handleRegionHeight) / 2
-        handleEndY = handleStartY + handleRegionHeight
+        val handleStartY = (dividerBounds.height() - handleRegionHeight) / 2
+        val handleEndY = handleStartY + handleRegionHeight
+        handleY = handleStartY..handleEndY
     }
 
     companion object {
         const val TOUCH_ANIMATION_DURATION: Long = 150
         const val TOUCH_RELEASE_ANIMATION_DURATION: Long = 200
+        private val TAG = TilingDividerView::class.java.simpleName
     }
 }
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index 0cc8b0c..f4f60d7 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -25,6 +25,7 @@
 import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
 import android.tools.flicker.assertors.assertions.AppWindowAlignsWithOnlyOneDisplayCornerAtEnd
 import android.tools.flicker.assertors.assertions.AppWindowBecomesInvisible
+import android.tools.flicker.assertors.assertions.AppWindowBecomesPinned
 import android.tools.flicker.assertors.assertions.AppWindowBecomesTopWindow
 import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
 import android.tools.flicker.assertors.assertions.AppWindowCoversLeftHalfScreenAtEnd
@@ -61,6 +62,14 @@
 
 class DesktopModeFlickerScenarios {
     companion object {
+        // In DesktopMode, window snap can be done with just a single window. In this case, the
+        // divider tiling between left and right window won't be shown, and hence its states are not
+        // obtainable in test.
+        // As the test should just focus on ensuring window goes to one side of the screen, an
+        // acceptable approach is to ensure snapped window still fills > 95% of either side of the
+        // screen.
+        private const val SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO = 0.05
+
         val END_DRAG_TO_DESKTOP =
             FlickerConfigEntry(
                 scenarioId = ScenarioId("END_DRAG_TO_DESKTOP"),
@@ -229,9 +238,11 @@
                         TaggedCujTransitionMatcher(associatedTransitionRequired = false)
                     )
                     .build(),
-                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
-                        listOf(AppWindowCoversLeftHalfScreenAtEnd(DESKTOP_MODE_APP))
-                            .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf(
+                    AppWindowCoversLeftHalfScreenAtEnd(
+                        DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO
+                    )
+                ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
             )
 
         val SNAP_RESIZE_RIGHT_WITH_BUTTON =
@@ -244,9 +255,11 @@
                         TaggedCujTransitionMatcher(associatedTransitionRequired = false)
                     )
                     .build(),
-                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
-                        listOf(AppWindowCoversRightHalfScreenAtEnd(DESKTOP_MODE_APP))
-                            .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf(
+                    AppWindowCoversRightHalfScreenAtEnd(
+                        DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO
+                    )
+                ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
             )
 
         val SNAP_RESIZE_LEFT_WITH_DRAG =
@@ -259,9 +272,11 @@
                         TaggedCujTransitionMatcher(associatedTransitionRequired = false)
                     )
                     .build(),
-                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
-                        listOf(AppWindowCoversLeftHalfScreenAtEnd(DESKTOP_MODE_APP))
-                            .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf(
+                    AppWindowCoversLeftHalfScreenAtEnd(
+                        DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO
+                    )
+                ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
             )
 
         val SNAP_RESIZE_RIGHT_WITH_DRAG =
@@ -274,9 +289,11 @@
                         TaggedCujTransitionMatcher(associatedTransitionRequired = false)
                     )
                     .build(),
-                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
-                        listOf(AppWindowCoversRightHalfScreenAtEnd(DESKTOP_MODE_APP))
-                            .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf(
+                    AppWindowCoversRightHalfScreenAtEnd(
+                        DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO
+                    )
+                ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
             )
 
         val SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE =
@@ -465,5 +482,25 @@
                             AppWindowIsVisibleAlways(SIMPLE_APP)
                         ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
             )
+
+        val MINIMIZE_AUTO_PIP_APP =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("MINIMIZE_AUTO_PIP_APP"),
+                extractor =
+                ShellTransitionScenarioExtractor(
+                    transitionMatcher =
+                    object : ITransitionMatcher {
+                        override fun findAll(
+                            transitions: Collection<Transition>
+                        ): Collection<Transition> =
+                            transitions.filter { it.type == TransitionType.PIP }
+                    }
+                ),
+                assertions =
+                AssertionTemplates.COMMON_ASSERTIONS +
+                    listOf(
+                        AppWindowBecomesPinned(DESKTOP_MODE_APP),
+                    ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+            )
     }
 }
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAutoPipAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAutoPipAppWindow.kt
new file mode 100644
index 0000000..b10db68
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAutoPipAppWindow.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.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_AUTO_PIP_APP
+import com.android.wm.shell.scenarios.MinimizeAutoPipAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MinimizeAutoPipAppWindow : MinimizeAutoPipAppWindow() {
+    @ExpectedScenarios(["MINIMIZE_AUTO_PIP_APP"])
+    @Test
+    override fun minimizePipAppWindow() = super.minimizePipAppWindow()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MINIMIZE_AUTO_PIP_APP)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/UnmaximizeAppWindowTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/UnmaximizeAppWindowTest.kt
new file mode 100644
index 0000000..7a71d4b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/UnmaximizeAppWindowTest.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.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.UnmaximizeAppWindow
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [UnmaximizeAppWindow]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class UnmaximizeAppWindowTest : UnmaximizeAppWindow()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
index c43a575..bb812ad 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
@@ -45,9 +45,9 @@
     @Before
     fun setup() {
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        testApp.enterDesktopMode(wmHelper, device)
         // Set string extra to ensure the app is on PiP mode at launch
         pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = mapOf("enter_pip" to "true"))
-        testApp.enterDesktopMode(wmHelper, device)
         mailApp.launchViaIntent(wmHelper)
         newTasksApp.launchViaIntent(wmHelper)
         imeApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
index 0226eb3..41452c3 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
@@ -63,9 +63,9 @@
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
+        testApp.enterDesktopMode(wmHelper, device)
         // Set string extra to ensure the app is on PiP mode at launch
         pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = mapOf("enter_pip" to "true"))
-        testApp.enterDesktopMode(wmHelper, device)
         mailApp.launchViaIntent(wmHelper)
         newTasksApp.launchViaIntent(wmHelper)
         imeApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnmaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnmaximizeAppWindow.kt
new file mode 100644
index 0000000..7411250
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnmaximizeAppWindow.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Testing "unmaximizing" a window e.g. making it get out of/exit a window that was already
+ * maximized.
+ */
+@Ignore("Test Base Class")
+abstract class UnmaximizeAppWindow
+constructor(private val rotation: Rotation = Rotation.ROTATION_0, isResizable: Boolean = true) {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp = if (isResizable) {
+        DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    } else {
+        DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+    }
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        ChangeDisplayOrientationRule.setRotation(rotation)
+        testApp.enterDesktopMode(wmHelper, device)
+        // Press the buttonn once to setup app window to be maximized already
+        testApp.maximiseDesktopApp(wmHelper, device)
+    }
+
+    @Test
+    open fun unmaximizeAppWindow() {
+        // Re-press button to exit maximized state
+        testApp.maximiseDesktopApp(wmHelper, device)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
index 706c632..1de47df 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
@@ -48,6 +48,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
index 7df1675..34d001c 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
@@ -48,6 +48,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
index 7df1675..34d001c 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
@@ -48,6 +48,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
index d87c179..9c1a8f1 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
@@ -48,6 +48,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index 99969e7..02b2cec 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -48,6 +48,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
index ddbc681..f40edae 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -266,5 +266,26 @@
     test_suites: ["device-tests"],
 }
 
+test_module_config {
+    name: "WMShellFlickerTestsPip-nonMatchParent",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.*"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-BottomHalfExitPipToAppViaExpandButtonTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaExpandButtonTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-BottomHalfExitPipToAppViaIntentTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaIntentTest"],
+    test_suites: ["device-tests"],
+}
+
 // End breakdowns for WMShellFlickerTestsPip module
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
index 19c3e40..a136936 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -25,7 +25,7 @@
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
         <!-- Turns off Wi-fi -->
-        <option name="wifi" value="off"/>
+        <option name="wifi" value="on"/>
         <!-- Turns off Bluetooth -->
         <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
@@ -48,6 +48,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
@@ -107,4 +109,11 @@
         <option name="collect-on-run-ended-only" value="true"/>
         <option name="clean-up" value="true"/>
     </metrics_collector>
+    <!-- Enable mocking GPS location by the test app -->
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command"
+            value="appops set com.android.shell android:mock_location allow"/>
+        <option name="teardown-command"
+            value="appops set com.android.shell android:mock_location deny"/>
+    </target_preparer>
 </configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
index 7505860..34e4e74 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
@@ -48,6 +48,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index fd4328d..609a284 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -27,6 +27,7 @@
 import android.tools.flicker.subject.exceptions.ExceptionMessageBuilder
 import android.tools.flicker.subject.exceptions.IncorrectRegionException
 import android.tools.flicker.subject.layers.LayerSubject
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.EnterPipTransition
 import org.junit.Assume
@@ -65,6 +66,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
index d4ad4ef8..5698023 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
@@ -22,6 +22,7 @@
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
 import android.tools.traces.component.ComponentNameMatcher
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -57,6 +58,8 @@
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class AutoEnterPipWithSourceRectHintTest(flicker: LegacyFlickerTest) :
     AutoEnterPipOnGoToHomeTest(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
             pipApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index 53725fa..880e4cd 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -21,6 +21,7 @@
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.ClosePipTransition
 import org.junit.FixMethodOrder
@@ -56,6 +57,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.closePipWindow(wmHelper) }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index a1551b7..4399a23 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -21,6 +21,7 @@
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.EnterPipTransition
 import org.junit.Assume
@@ -47,6 +48,7 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
     override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index ea5b3e5..49efd1d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -31,6 +31,7 @@
 import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
 import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
 import com.android.wm.shell.Flags
@@ -72,6 +73,7 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
     private val testApp = FixedOrientationAppHelper(instrumentation)
     private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
     private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index a109c4b..97cc9d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -20,6 +20,7 @@
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.EnterPipTransition
 import org.junit.FixMethodOrder
@@ -53,6 +54,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 open class EnterPipViaAppUiButtonTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.clickEnterPipButton(wmHelper) }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 14ec303..b5b7847 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -20,6 +20,7 @@
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
 import org.junit.FixMethodOrder
@@ -56,6 +57,8 @@
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
     ExitPipToAppTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val thisTransition: FlickerBuilder.() -> Unit = {
         setup {
             // launch an app behind the pip one
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index 8a34b5e..f9a9df4 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -20,6 +20,7 @@
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
 import org.junit.FixMethodOrder
@@ -54,6 +55,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val thisTransition: FlickerBuilder.() -> Unit = {
         setup {
             // launch an app behind the pip one
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
index 12e2328..79e2e4e 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -27,6 +27,7 @@
 import android.tools.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.helpers.WindowUtils
 import android.tools.traces.parsers.toFlickerComponent
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.wm.shell.Flags
@@ -68,6 +69,7 @@
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) :
     EnterPipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
     private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
     /** Second app used to enter split screen mode */
     private val secondAppForSplitScreen =
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
index 04016a9..14ae93a 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
@@ -23,6 +23,7 @@
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
 import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.PipTransition
 import org.junit.FixMethodOrder
@@ -37,6 +38,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class PipAspectRatioChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.changeAspectRatio(wmHelper) }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index 6bcaabc..81162c6 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -49,7 +49,8 @@
         val stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
         setup {
             tapl.setEnableRotation(true)
-            pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+            pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
+            pipApp.waitForPip(wmHelper)
 
             // determine the direction of dragging to test for
             isDraggedLeft = pipApp.isCloserToRightEdge(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index d82bfdd..6118d73 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -59,7 +59,8 @@
                 // Launch the PIP activity and wait for it to enter PiP mode
                 setRotation(Rotation.ROTATION_0)
                 RemoveAllTasksButHomeRule.removeAllTasksButHome()
-                pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+                pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
+                pipApp.waitForPip(wmHelper)
 
                 // get the initial region bounds and cache them
                 val initRegion = pipApp.getWindowRect(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index dbc97d0..61c59cc 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -25,6 +25,7 @@
 import android.tools.flicker.legacy.LegacyFlickerTest
 import android.tools.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.flicker.subject.exceptions.IncorrectRegionException
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.wm.shell.Flags
 import com.android.wm.shell.flicker.pip.common.PipTransition
 import org.junit.FixMethodOrder
@@ -40,6 +41,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
 class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+    override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
index 65b60ce..0867f65 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
@@ -18,7 +18,6 @@
 
 import android.platform.test.annotations.Postsubmit
 import android.tools.Rotation
-import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.flicker.junit.FlickerBuilderProvider
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
@@ -29,8 +28,6 @@
 import org.junit.runners.Parameterized
 
 abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
-    protected abstract val standardAppHelper: StandardAppHelper
-
     protected abstract val permissions: Array<String>
 
     @FlickerBuilderProvider
@@ -39,7 +36,7 @@
             instrumentation.uiAutomation.adoptShellPermissionIdentity()
             for (permission in permissions) {
                 instrumentation.uiAutomation.grantRuntimePermission(
-                    standardAppHelper.packageName,
+                    pipApp.packageName,
                     permission
                 )
             }
@@ -48,18 +45,18 @@
         }
     }
 
-    /** Checks [standardAppHelper] window remains visible throughout the animation */
+    /** Checks [pipApp] window remains visible throughout the animation */
     @Postsubmit
     @Test
     override fun pipAppWindowAlwaysVisible() {
-        flicker.assertWm { this.isAppWindowVisible(standardAppHelper.packageNameMatcher) }
+        flicker.assertWm { this.isAppWindowVisible(pipApp.packageNameMatcher) }
     }
 
-    /** Checks [standardAppHelper] layer remains visible throughout the animation */
+    /** Checks [pipApp] layer remains visible throughout the animation */
     @Postsubmit
     @Test
     override fun pipAppLayerAlwaysVisible() {
-        flicker.assertLayers { this.isVisible(standardAppHelper.packageNameMatcher) }
+        flicker.assertLayers { this.isVisible(pipApp.packageNameMatcher) }
     }
 
     /** Checks the content overlay appears then disappears during the animation */
@@ -70,39 +67,39 @@
     }
 
     /**
-     * Checks that [standardAppHelper] window remains inside the display bounds throughout the whole
+     * Checks that [pipApp] window remains inside the display bounds throughout the whole
      * animation
      */
     @Postsubmit
     @Test
     override fun pipWindowRemainInsideVisibleBounds() {
-        flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+        flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) {
             coversAtMost(displayBounds)
         }
     }
 
     /**
-     * Checks that the [standardAppHelper] layer remains inside the display bounds throughout the
+     * Checks that the [pipApp] layer remains inside the display bounds throughout the
      * whole animation
      */
     @Postsubmit
     @Test
     override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
         flicker.assertLayersVisibleRegion(
-            standardAppHelper.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+            pipApp.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
         ) {
             coversAtMost(displayBounds)
         }
     }
 
-    /** Checks that the visible region of [standardAppHelper] always reduces during the animation */
+    /** Checks that the visible region of [pipApp] always reduces during the animation */
     @Postsubmit
     @Test
     override fun pipLayerReduces() {
         flicker.assertLayers {
             val pipLayerList =
                 this.layers {
-                    standardAppHelper.packageNameMatcher.layerMatchesAnyOf(it) && it.isVisible
+                    pipApp.packageNameMatcher.layerMatchesAnyOf(it) && it.isVisible
                 }
             pipLayerList.zipWithNext { previous, current ->
                 current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
@@ -110,14 +107,14 @@
         }
     }
 
-    /** Checks that [standardAppHelper] window becomes pinned */
+    /** Checks that [pipApp] window becomes pinned */
     @Postsubmit
     @Test
     override fun pipWindowBecomesPinned() {
         flicker.assertWm {
-            invoke("pipWindowIsNotPinned") { it.isNotPinned(standardAppHelper.packageNameMatcher) }
+            invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp.packageNameMatcher) }
                 .then()
-                .invoke("pipWindowIsPinned") { it.isPinned(standardAppHelper.packageNameMatcher) }
+                .invoke("pipWindowIsPinned") { it.isPinned(pipApp.packageNameMatcher) }
         }
     }
 
@@ -129,14 +126,14 @@
     }
 
     /**
-     * Checks that the focus changes between the [standardAppHelper] window and the launcher when
+     * Checks that the focus changes between the [pipApp] window and the launcher when
      * closing the pip window
      */
     @Postsubmit
     @Test
     override fun focusChanges() {
         flicker.assertEventLog {
-            this.focusChanges(standardAppHelper.packageName, "NexusLauncherActivity")
+            this.focusChanges(pipApp.packageName, "NexusLauncherActivity")
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
index 7b04b76..651c923 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -29,6 +29,8 @@
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
+import android.tools.traces.component.ComponentRegexMatcher
 import androidx.test.filters.RequiresDevice
 import org.junit.Assume
 import org.junit.FixMethodOrder
@@ -63,7 +65,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
-    override val standardAppHelper: MapsAppHelper = MapsAppHelper(instrumentation)
+    override val pipApp: MapsAppHelper = MapsAppHelper(instrumentation)
 
     override val permissions: Array<String> =
         arrayOf(Manifest.permission.POST_NOTIFICATIONS, Manifest.permission.ACCESS_FINE_LOCATION)
@@ -110,23 +112,23 @@
 
             // normal app open through the Launcher All Apps
             // var mapsAddressOption = "Golden Gate Bridge"
-            // standardAppHelper.open()
-            // standardAppHelper.doSearch(mapsAddressOption)
-            // standardAppHelper.getDirections()
-            // standardAppHelper.startNavigation();
+            // pipApp.open()
+            // pipApp.doSearch(mapsAddressOption)
+            // pipApp.getDirections()
+            // pipApp.startNavigation();
 
-            standardAppHelper.launchViaIntent(
+            pipApp.launchViaIntent(
                 wmHelper,
                 MapsAppHelper.getMapIntent(MapsAppHelper.INTENT_NAVIGATION)
             )
 
-            standardAppHelper.waitForNavigationToStart()
+            pipApp.waitForNavigationToStart()
         }
     }
 
     override val defaultTeardown: FlickerBuilder.() -> Unit = {
         teardown {
-            standardAppHelper.exit(wmHelper)
+            pipApp.exit(wmHelper)
             mainHandler.removeCallbacks(updateLocation)
             // the main looper callback might have tried to provide a new location after the
             // provider is no longer in test mode, causing a crash, this prevents it from happening
@@ -137,14 +139,14 @@
 
     override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
 
-    /** Checks [standardAppHelper] layer remains visible throughout the animation */
+    /** Checks [pipApp] layer remains visible throughout the animation */
     @Postsubmit
     @Test
     override fun pipAppLayerAlwaysVisible() {
         // For Maps the transition goes through the UI mode change that adds a snapshot overlay so
         // we assert only start/end layers matching the app instead.
-        flicker.assertLayersStart { this.isVisible(standardAppHelper.packageNameMatcher) }
-        flicker.assertLayersEnd { this.isVisible(standardAppHelper.packageNameMatcher) }
+        flicker.assertLayersStart { this.isVisible(pipApp.packageNameMatcher) }
+        flicker.assertLayersEnd { this.isVisible(pipApp.packageNameMatcher) }
     }
 
     @Postsubmit
@@ -154,4 +156,15 @@
         Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
         super.focusChanges()
     }
+
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        flicker.assertLayers {
+            this.visibleLayersShownMoreThanOneConsecutiveEntry(
+                ignoreLayers = VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
+                    + ComponentRegexMatcher(Regex("Background for .* SurfaceView\\[com\\.google\\.android\\.apps\\.maps/com\\.google\\.android\\.maps\\.MapsActivity\\]\\#\\d+"))
+            )
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
index 6911946..be4cd78 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -61,7 +61,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
-    override val standardAppHelper: NetflixAppHelper = NetflixAppHelper(instrumentation)
+    override val pipApp: NetflixAppHelper = NetflixAppHelper(instrumentation)
     private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
     private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
 
@@ -69,17 +69,17 @@
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
-            standardAppHelper.launchViaIntent(
+            pipApp.launchViaIntent(
                 wmHelper,
                 NetflixAppHelper.getNetflixWatchVideoIntent("81605060"),
                 ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME, NetflixAppHelper.WATCH_ACTIVITY)
             )
-            standardAppHelper.waitForVideoPlaying()
+            pipApp.waitForVideoPlaying()
         }
     }
 
     override val defaultTeardown: FlickerBuilder.() -> Unit = {
-        teardown { standardAppHelper.exit(wmHelper) }
+        teardown { pipApp.exit(wmHelper) }
     }
 
     override val thisTransition: FlickerBuilder.() -> Unit = {
@@ -143,7 +143,7 @@
         // might go outside of bounds as we resize from landscape fullscreen to destination bounds,
         // and once the animation is over we assert that it's fully within the display bounds, at
         // which point the device also performs orientation change from landscape to portrait
-        flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+        flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) {
             regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
         }
     }
@@ -156,7 +156,7 @@
         // and once the animation is over we assert that it's fully within the display bounds, at
         // which point the device also performs orientation change from landscape to portrait
         // since Netflix uses source rect hint, there is no PiP overlay present
-        flicker.assertLayersVisibleRegion(standardAppHelper.packageNameMatcher) {
+        flicker.assertLayersVisibleRegion(pipApp.packageNameMatcher) {
             regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
index 5e54f30..3e4ff30 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
@@ -57,23 +57,23 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class YouTubeEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
-    override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
+    override val pipApp: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
 
     override val permissions: Array<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS)
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
-            standardAppHelper.launchViaIntent(
+            pipApp.launchViaIntent(
                 wmHelper,
                 YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
                 ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
             )
-            standardAppHelper.waitForVideoPlaying()
+            pipApp.waitForVideoPlaying()
         }
     }
 
     override val defaultTeardown: FlickerBuilder.() -> Unit = {
-        teardown { standardAppHelper.exit(wmHelper) }
+        teardown { pipApp.exit(wmHelper) }
     }
 
     override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
index 159cba4..2c6cb503 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
@@ -63,7 +63,7 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) :
     YouTubeEnterPipTest(flicker) {
-    override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
+    override val pipApp: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
     private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
     private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
 
@@ -71,13 +71,13 @@
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
-            standardAppHelper.launchViaIntent(
+            pipApp.launchViaIntent(
                 wmHelper,
                 YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
                 ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
             )
-            standardAppHelper.enterFullscreen()
-            standardAppHelper.waitForVideoPlaying()
+            pipApp.enterFullscreen()
+            pipApp.waitForVideoPlaying()
         }
     }
 
@@ -101,7 +101,7 @@
         // might go outside of bounds as we resize from landscape fullscreen to destination bounds,
         // and once the animation is over we assert that it's fully within the display bounds, at
         // which point the device also performs orientation change from landscape to portrait
-        flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+        flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) {
             regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
         }
     }
@@ -114,7 +114,7 @@
         // and once the animation is over we assert that it's fully within the display bounds, at
         // which point the device also performs orientation change from landscape to portrait
         // since YouTube uses source rect hint, there is no PiP overlay present
-        flicker.assertLayersVisibleRegion(standardAppHelper.packageNameMatcher) {
+        flicker.assertLayersVisibleRegion(pipApp.packageNameMatcher) {
             regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
index 6dd3a17..a72de0f 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
@@ -71,7 +71,9 @@
     @Presubmit
     @Test
     open fun pipLayerOrOverlayRemainInsideVisibleBounds() {
-        flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)) {
+        flicker.assertLayersVisibleRegion(
+            pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+        ) {
             coversAtMost(displayBounds)
         }
     }
@@ -117,7 +119,9 @@
     @Presubmit
     @Test
     open fun focusChanges() {
-        flicker.assertEventLog { this.focusChanges(pipApp.packageName, "NexusLauncherActivity") }
+        flicker.assertEventLog {
+            this.focusChanges(pipApp.packageName, "NexusLauncherActivity")
+        }
     }
 
     companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
index c37bf35..7b6625d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
@@ -27,6 +27,7 @@
 import android.tools.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
 import android.tools.helpers.WindowUtils
 import android.tools.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.PipApp
 import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -40,7 +41,6 @@
     @Rule
     val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
 
-    protected val pipApp = PipAppHelper(instrumentation)
     protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
     protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
 
@@ -63,6 +63,11 @@
         }
     }
 
+    /**
+     * Defines the test app to run PIP flicker test.
+     */
+    protected open val pipApp: PipApp = PipAppHelper(instrumentation)
+
     /** Defines the transition used to run the test */
     protected open val thisTransition: FlickerBuilder.() -> Unit = {}
 
@@ -85,10 +90,11 @@
     /** Defines the default method of entering PiP */
     protected open val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
-            pipApp.launchViaIntentAndWaitForPip(
+            pipApp.launchViaIntent(
                 wmHelper,
                 stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
             )
+            pipApp.waitForPip(wmHelper)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt
new file mode 100644
index 0000000..c405b66
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.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.wm.shell.flicker.pip.nonmatchparent
+
+import android.platform.test.annotations.Presubmit
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import com.android.server.wm.flicker.helpers.BottomHalfPipAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
+import org.junit.Test
+
+/**
+ * Base test class to verify PIP exit animation with an activity layout to the bottom half of
+ * the container.
+ */
+abstract class BottomHalfExitPipToAppTransition(flicker: LegacyFlickerTest) :
+    ExitPipToAppTransition(flicker) {
+
+    override val pipApp: PipAppHelper = BottomHalfPipAppHelper(instrumentation)
+
+    @Presubmit
+    @Test
+    override fun showBothAppLayersThenHidePip() {
+        // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
+    }
+
+    @Presubmit
+    @Test
+    override fun showBothAppWindowsThenHidePip() {
+        // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
+    }
+
+    @Presubmit
+    @Test
+    override fun pipAppCoversFullScreenAtEnd() {
+        // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
+    }
+
+    /**
+     * Checks that the [testApp] and [pipApp] are always visible since the [pipApp] only covers
+     * half of screen.
+     */
+    @Presubmit
+    @Test
+    fun showBothAppLayersDuringPipTransition() {
+        flicker.assertLayers {
+            isVisible(testApp)
+                .isVisible(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT))
+        }
+    }
+
+    /**
+     * Checks that the [testApp] and [pipApp] are always visible since the [pipApp] only covers
+     * half of screen.
+     */
+    @Presubmit
+    @Test
+    fun showBothAppWindowsDuringPipTransition() {
+        flicker.assertWm {
+            isAppWindowVisible(testApp)
+                .isAppWindowOnTop(pipApp)
+                .isAppWindowVisible(pipApp)
+        }
+    }
+
+    /**
+     * Verify that the [testApp] and [pipApp] covers the entire screen at the end of PIP exit
+     * animation since the [pipApp] will use a bottom half layout.
+     */
+    @Presubmit
+    @Test
+    fun testPlusPipAppCoversWindowFrameAtEnd() {
+        flicker.assertLayersEnd {
+            val pipRegion = visibleRegion(pipApp).region
+            visibleRegion(testApp).plus(pipRegion).coversExactly(displayBounds)
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt
new file mode 100644
index 0000000..2a3dc07
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.flicker.pip.nonmatchparent
+
+import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.window.flags.Flags
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test expanding a pip window back to bottom half layout via the expand button
+ *
+ * To run this test: `atest WMShellFlickerTestsPip:BottomHalfExitPipToAppViaExpandButtonTest`
+ *
+ * Actions:
+ * ```
+ *     Launch an app in pip mode [bottomHalfPipApp],
+ *     Launch another full screen mode [testApp]
+ *     Expand [bottomHalfPipApp] app to bottom half layout by clicking on the pip window and
+ *     then on the expand button
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [PipTransition]
+ *     2. Part of the test setup occurs automatically via
+ *        [android.tools.flicker.legacy.runner.TransitionRunner],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+// TODO(b/380796448): re-enable tests after the support of non-match parent PIP animation for PIP2.
+@RequiresFlagsDisabled(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
+@RequiresFlagsEnabled(Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY)
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class BottomHalfExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
+    BottomHalfExitPipToAppTransition(flicker)
+{
+    override val thisTransition: FlickerBuilder.() -> Unit = {
+        setup {
+            // launch an app behind the pip one
+            testApp.launchViaIntent(wmHelper)
+        }
+        transitions {
+            // This will bring PipApp to fullscreen
+            pipApp.expandPipWindowToApp(wmHelper)
+            // Wait until the transition idle and test and pip app still shows.
+            wmHelper.StateSyncBuilder().withLayerVisible(testApp).withLayerVisible(pipApp)
+                .withAppTransitionIdle().waitForAndVerify()
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt
new file mode 100644
index 0000000..8ed9cd2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.flicker.pip.nonmatchparent
+
+import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.window.flags.Flags
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test expanding a pip window back to bottom half layout via an intent
+ *
+ * To run this test: `atest WMShellFlickerTestsPip:BottomHalfExitPipToAppViaIntentTest`
+ *
+ * Actions:
+ * ```
+ *     Launch an app in pip mode [bottomHalfPipApp],
+ *     Launch another full screen mode [testApp]
+ *     Expand [bottomHalfPipApp] app to bottom half layout via an intent
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited from [PipTransition]
+ *     2. Part of the test setup occurs automatically via
+ *        [android.tools.flicker.legacy.runner.TransitionRunner],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+// TODO(b/380796448): re-enable tests after the support of non-match parent PIP animation for PIP2.
+@RequiresFlagsDisabled(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
+@RequiresFlagsEnabled(Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY)
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class BottomHalfExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) :
+    BottomHalfExitPipToAppTransition(flicker)
+{
+    override val thisTransition: FlickerBuilder.() -> Unit = {
+        setup {
+            // launch an app behind the pip one
+            testApp.launchViaIntent(wmHelper)
+        }
+        transitions {
+            // This will bring PipApp to fullscreen
+            pipApp.exitPipToFullScreenViaIntent(wmHelper)
+            // Wait until the transition idle and test and pip app still shows.
+            wmHelper.StateSyncBuilder().withLayerVisible(testApp).withLayerVisible(pipApp)
+                .withAppTransitionIdle().waitForAndVerify()
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index c4954f9..feb3edc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -20,6 +20,7 @@
 import android.graphics.Point
 import android.os.SystemClock
 import android.tools.Rotation
+import android.tools.device.apphelpers.IStandardAppHelper
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.flicker.rules.ChangeDisplayOrientationRule
 import android.tools.traces.component.ComponentNameMatcher
@@ -102,8 +103,8 @@
         wmHelper: WindowManagerStateHelper,
         tapl: LauncherInstrumentation,
         device: UiDevice,
-        primaryApp: StandardAppHelper,
-        secondaryApp: StandardAppHelper,
+        primaryApp: IStandardAppHelper,
+        secondaryApp: IStandardAppHelper,
         rotation: Rotation
     ) {
         primaryApp.launchViaIntent(wmHelper)
@@ -117,8 +118,8 @@
 
     fun enterSplitViaIntent(
         wmHelper: WindowManagerStateHelper,
-        primaryApp: StandardAppHelper,
-        secondaryApp: StandardAppHelper
+        primaryApp: IStandardAppHelper,
+        secondaryApp: IStandardAppHelper
     ) {
         val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true")
         primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
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 c3e3965..f22e2a5 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
@@ -86,7 +86,6 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.shared.ShellSharedConstants;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
@@ -254,7 +253,7 @@
     @Test
     public void instantiateController_addExternalInterface() {
         verify(mShellController, times(1)).addExternalInterface(
-                eq(ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION), any(), any());
+                eq(IBackAnimation.DESCRIPTOR), any(), any());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 0373bbd..6f3a3ec 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -172,10 +172,10 @@
         var mockPp = mock(DisplayImeController.ImePositionProcessor.class);
         mDisplayImeController.addPositionProcessor(mockPp);
 
-        mPerDisplay.setImeInputTargetRequestedVisibility(true);
+        mPerDisplay.setImeInputTargetRequestedVisibility(true, null /* statsToken */);
         verify(mockPp).onImeRequested(anyInt(), eq(true));
 
-        mPerDisplay.setImeInputTargetRequestedVisibility(false);
+        mPerDisplay.setImeInputTargetRequestedVisibility(false, null /* statsToken */);
         verify(mockPp).onImeRequested(anyInt(), eq(false));
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index cf69704..fd3d3b5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -56,6 +56,7 @@
     private @Mock DisplayController mDisplayController;
     private @Mock DisplayImeController mDisplayImeController;
     private @Mock ShellTaskOrganizer mTaskOrganizer;
+    private @Mock SplitState mSplitState;
     private @Mock Handler mHandler;
     private SplitLayout mSplitLayout;
     private DividerView mDividerView;
@@ -67,7 +68,7 @@
         Configuration configuration = getConfiguration();
         mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration,
                 mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController,
-                mTaskOrganizer, SplitLayout.PARALLAX_NONE, mHandler);
+                mTaskOrganizer, SplitLayout.PARALLAX_NONE, mSplitState, mHandler);
         SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
                 mContext,
                 configuration, mCallbacks);
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 dc0f213..1904c43 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
@@ -66,6 +66,7 @@
     @Mock DisplayImeController mDisplayImeController;
     @Mock ShellTaskOrganizer mTaskOrganizer;
     @Mock WindowContainerTransaction mWct;
+    @Mock SplitState mSplitState;
     @Mock Handler mHandler;
     @Captor ArgumentCaptor<Runnable> mRunnableCaptor;
     private SplitLayout mSplitLayout;
@@ -83,6 +84,7 @@
                 mDisplayImeController,
                 mTaskOrganizer,
                 SplitLayout.PARALLAX_NONE,
+                mSplitState,
                 mHandler));
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt
new file mode 100644
index 0000000..64772d0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 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.common.transition
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.recents.RecentsTransitionHandler
+import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING
+import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING
+import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED
+import com.android.wm.shell.sysui.ShellInit
+import kotlin.test.assertNotNull
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+
+/**
+ * Test class for {@link TransitionStateHolder}
+ *
+ * Usage: atest WMShellUnitTests:TransitionStateHolderTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TransitionStateHolderTest {
+
+    lateinit var recentTransitionHandler: RecentsTransitionHandler
+    lateinit var shellInit: ShellInit
+
+    @Before
+    fun before() {
+        recentTransitionHandler = mock(RecentsTransitionHandler::class.java)
+        shellInit = ShellInit(TestShellExecutor())
+    }
+
+    @Test
+    fun `No TransitionStateHolder listeners before initialization`() {
+        TransitionStateHolder(shellInit, recentTransitionHandler)
+        verify(recentTransitionHandler, never()).addTransitionStateListener(any())
+    }
+
+    @Test
+    fun `When TransitionStateHolder initialized a listener has been registered `() {
+        TransitionStateHolder(shellInit, recentTransitionHandler)
+        shellInit.init()
+        assertNotNull(recentsTransitionStateListener)
+    }
+
+    @Test
+    fun `When TransitionStateHolder is created  no recent animation running`() {
+        val holder = TransitionStateHolder(shellInit, recentTransitionHandler)
+        shellInit.init()
+        assertFalse(holder.isRecentsTransitionRunning())
+    }
+
+    @Test
+    fun `Recent animation running updates after callback value`() {
+        val holder = TransitionStateHolder(shellInit, recentTransitionHandler)
+        shellInit.init()
+
+        assertFalse(holder.isRecentsTransitionRunning())
+
+        recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_NOT_RUNNING)
+        assertFalse(holder.isRecentsTransitionRunning())
+
+        recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_REQUESTED)
+        assertTrue(holder.isRecentsTransitionRunning())
+
+        recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING)
+        assertTrue(holder.isRecentsTransitionRunning())
+    }
+
+    private val recentsTransitionStateListener: RecentsTransitionStateListener
+        get() = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java).run {
+            verify(recentTransitionHandler).addTransitionStateListener(capture())
+            value
+        }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index 803e5d4..1d39000 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -21,7 +21,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.R
 import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Test
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 d5287e7..67573da 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
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
@@ -61,12 +62,16 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
 import dagger.Lazy;
 
+import java.util.Optional;
+
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -130,6 +135,10 @@
     private CompatUIShellCommandHandler mCompatUIShellCommandHandler;
     @Mock
     private AccessibilityManager mAccessibilityManager;
+    @Mock
+    private DesktopUserRepositories mDesktopUserRepositories;
+    @Mock
+    private DesktopRepository mDesktopRepository;
 
     @Captor
     ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -137,7 +146,6 @@
     @NonNull
     private CompatUIStatusManager mCompatUIStatusManager;
 
-    private boolean mInDesktopModePredicateResult;
 
     @Before
     public void setUp() {
@@ -152,6 +160,8 @@
         doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
         doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
         doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
+        doReturn(mDesktopRepository).when(mDesktopUserRepositories).getCurrent();
+        doReturn(mDesktopRepository).when(mDesktopUserRepositories).getProfile(anyInt());
 
         doReturn(DISPLAY_ID).when(mMockRestartDialogLayout).getDisplayId();
         doReturn(TASK_ID).when(mMockRestartDialogLayout).getTaskId();
@@ -164,7 +174,7 @@
                 mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
                 mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
                 mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager,
-                mCompatUIStatusManager, i -> mInDesktopModePredicateResult) {
+                mCompatUIStatusManager, Optional.of(mDesktopUserRepositories)) {
             @Override
             CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
                     ShellTaskOrganizer.TaskListener taskListener) {
@@ -707,13 +717,17 @@
     @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
     @EnableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE)
     public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagEnabled() {
-        mInDesktopModePredicateResult = false;
         TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+        when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
+
         mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+
         verify(mController, never()).removeLayouts(taskInfo.taskId);
 
-        mInDesktopModePredicateResult = true;
+        when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(2);
+
         mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+
         verify(mController).removeLayouts(taskInfo.taskId);
     }
 
@@ -721,13 +735,17 @@
     @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
     @DisableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE)
     public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagDisabled() {
-        mInDesktopModePredicateResult = false;
+        when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
         TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+
         mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+
         verify(mController, never()).removeLayouts(taskInfo.taskId);
 
-        mInDesktopModePredicateResult = true;
+        when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(2);
+
         mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+
         verify(mController, never()).removeLayouts(taskInfo.taskId);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt
new file mode 100644
index 0000000..95a0c82
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.SurfaceControl
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
+import com.android.wm.shell.compatui.letterbox.LetterboxMatchers.asAnyMode
+import org.mockito.kotlin.any
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+/**
+ * Robot to test [LetterboxController] implementations.
+ */
+open class LetterboxControllerRobotTest(
+    ctx: Context,
+    controllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController
+) {
+
+    companion object {
+        @JvmStatic
+        private val DISPLAY_ID = 1
+
+        @JvmStatic
+        private val TASK_ID = 20
+    }
+
+    private val letterboxConfiguration: LetterboxConfiguration
+    private val surfaceBuilder: LetterboxSurfaceBuilder
+    private val letterboxController: LetterboxController
+    private val transaction: SurfaceControl.Transaction
+    private val parentLeash: SurfaceControl
+
+    init {
+        letterboxConfiguration = LetterboxConfiguration(ctx)
+        surfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration)
+        letterboxController = controllerBuilder(surfaceBuilder)
+        transaction = getTransactionMock()
+        parentLeash = mock<SurfaceControl>()
+        spyOn(surfaceBuilder)
+    }
+
+    fun sendCreateSurfaceRequest(
+        displayId: Int = DISPLAY_ID,
+        taskId: Int = TASK_ID
+    ) = letterboxController.createLetterboxSurface(
+        key = LetterboxKey(displayId, taskId),
+        transaction = transaction,
+        parentLeash = parentLeash
+    )
+
+    fun sendDestroySurfaceRequest(
+        displayId: Int = DISPLAY_ID,
+        taskId: Int = TASK_ID
+    ) = letterboxController.destroyLetterboxSurface(
+        key = LetterboxKey(displayId, taskId),
+        transaction = transaction
+    )
+
+    fun sendUpdateSurfaceVisibilityRequest(
+        displayId: Int = DISPLAY_ID,
+        taskId: Int = TASK_ID,
+        visible: Boolean
+    ) = letterboxController.updateLetterboxSurfaceVisibility(
+        key = LetterboxKey(displayId, taskId),
+        transaction = transaction,
+        visible = visible
+    )
+
+    fun sendUpdateSurfaceBoundsRequest(
+        displayId: Int = DISPLAY_ID,
+        taskId: Int = TASK_ID,
+        taskBounds: Rect,
+        activityBounds: Rect
+    ) = letterboxController.updateLetterboxSurfaceBounds(
+        key = LetterboxKey(displayId, taskId),
+        transaction = transaction,
+        taskBounds = taskBounds,
+        activityBounds = activityBounds
+    )
+
+    fun invokeDump() {
+        letterboxController.dump()
+    }
+
+    fun checkSurfaceBuilderInvoked(times: Int = 1, name: String = "", callSite: String = "") {
+        verify(surfaceBuilder, times(times)).createSurface(
+            eq(transaction),
+            eq(parentLeash),
+            name.asAnyMode(),
+            callSite.asAnyMode(),
+            any()
+        )
+    }
+
+    fun checkTransactionRemovedInvoked(times: Int = 1) {
+        verify(transaction, times(times)).remove(any())
+    }
+
+    fun checkVisibilityUpdated(times: Int = 1, expectedVisibility: Boolean) {
+        verify(transaction, times(times)).setVisibility(any(), eq(expectedVisibility))
+    }
+
+    fun checkSurfacePositionUpdated(
+        times: Int = 1,
+        expectedX: Float = -1f,
+        expectedY: Float = -1f
+    ) {
+        verify(transaction, times(times)).setPosition(
+            any(),
+            expectedX.asAnyMode(),
+            expectedY.asAnyMode()
+        )
+    }
+
+    fun checkSurfaceSizeUpdated(times: Int = 1, expectedWidth: Int = -1, expectedHeight: Int = -1) {
+        verify(transaction, times(times)).setWindowCrop(
+            any(),
+            expectedWidth.asAnyMode(),
+            expectedHeight.asAnyMode()
+        )
+    }
+
+    fun resetTransitionTest() {
+        clearInvocations(transaction)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt
new file mode 100644
index 0000000..c37913e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
+import com.android.wm.shell.ShellTestCase
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.times
+
+/**
+ * Tests for [LetterboxSurfaceBuilder].
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:LetterboxSurfaceBuilderTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxSurfaceBuilderTest : ShellTestCase() {
+
+    @Test
+    fun `When surface is created mandatory methods are invoked`() {
+        runTestScenario { r ->
+            r.invokeBuilder()
+
+            r.checkNameIsSet(expected = true)
+            r.checkCallSiteIsSet(expected = true)
+            r.checkSurfaceIsHidden(invoked = true, isHidden = true)
+            r.checkColorLayerIsSet(expected = true)
+            r.checkParentLeashIsSet(expected = true)
+            r.checkSetLayerIsInvoked(expected = true)
+            r.checkColorSpaceAgnosticIsSet(expected = true, value = true)
+            r.checkColorIsSetFromLetterboxConfiguration(expected = true)
+        }
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    fun runTestScenario(consumer: Consumer<LetterboxSurfaceBuilderRobotTest>) {
+        val robot = LetterboxSurfaceBuilderRobotTest(mContext)
+        consumer.accept(robot)
+    }
+
+    class LetterboxSurfaceBuilderRobotTest(val ctx: Context) {
+
+        private val letterboxConfiguration: LetterboxConfiguration
+        private val letterboxSurfaceBuilder: LetterboxSurfaceBuilder
+        private val tx: SurfaceControl.Transaction
+        private val parentLeash: SurfaceControl
+        private val surfaceBuilder: SurfaceControl.Builder
+
+        companion object {
+            @JvmStatic
+            val TEST_SURFACE_NAME = "SurfaceForTest"
+
+            @JvmStatic
+            val TEST_SURFACE_CALL_SITE = "CallSiteForTest"
+        }
+
+        init {
+            letterboxConfiguration = LetterboxConfiguration(ctx)
+            letterboxSurfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration)
+            tx = getTransactionMock()
+            parentLeash = org.mockito.kotlin.mock<SurfaceControl>()
+            surfaceBuilder = SurfaceControl.Builder()
+            spyOn(surfaceBuilder)
+        }
+
+        fun invokeBuilder() {
+            letterboxSurfaceBuilder.createSurface(
+                tx,
+                parentLeash,
+                TEST_SURFACE_NAME,
+                TEST_SURFACE_CALL_SITE,
+                surfaceBuilder
+            )
+        }
+
+        fun checkNameIsSet(expected: Boolean) {
+            verify(surfaceBuilder, expected.asMode())
+                .setName(TEST_SURFACE_NAME)
+        }
+
+        fun checkCallSiteIsSet(expected: Boolean) {
+            verify(surfaceBuilder, expected.asMode())
+                .setCallsite(TEST_SURFACE_CALL_SITE)
+        }
+
+        fun checkSurfaceIsHidden(invoked: Boolean, isHidden: Boolean) {
+            verify(surfaceBuilder, invoked.asMode())
+                .setHidden(isHidden)
+        }
+
+        fun checkColorLayerIsSet(expected: Boolean) {
+            verify(surfaceBuilder, expected.asMode()).setColorLayer()
+        }
+
+        fun checkParentLeashIsSet(expected: Boolean) {
+            verify(surfaceBuilder, expected.asMode()).setParent(parentLeash)
+        }
+
+        fun checkSetLayerIsInvoked(expected: Boolean) {
+            verify(tx, expected.asMode()).setLayer(anyOrNull(), ArgumentMatchers.anyInt())
+        }
+
+        fun checkColorSpaceAgnosticIsSet(expected: Boolean, value: Boolean) {
+            verify(tx, expected.asMode()).setColorSpaceAgnostic(anyOrNull(), eq(value))
+        }
+
+        fun checkColorIsSetFromLetterboxConfiguration(expected: Boolean) {
+            val components = letterboxConfiguration.getBackgroundColorRgbArray()
+            verify(tx, expected.asMode()).setColor(anyOrNull(), eq(components))
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt
new file mode 100644
index 0000000..2c06dfd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.view.SurfaceControl
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.verification.VerificationMode
+
+/**
+ * @return A [SurfaceControl.Transaction] mock supporting chaining for some operations. Please
+ *         add other operations if needed.
+ */
+fun getTransactionMock(): SurfaceControl.Transaction = mock<SurfaceControl.Transaction>().apply {
+    doReturn(this).`when`(this).setLayer(anyOrNull(), anyOrNull())
+    doReturn(this).`when`(this).setColorSpaceAgnostic(anyOrNull(), anyOrNull())
+    doReturn(this).`when`(this).setPosition(anyOrNull(), any(), any())
+    doReturn(this).`when`(this).setWindowCrop(anyOrNull(), any(), any())
+}
+
+// Utility to make verification mode depending on a [Boolean].
+fun Boolean.asMode(): VerificationMode = if (this) times(1) else never()
+
+// Utility matchers to use for the main types as Mockito [VerificationMode].
+object LetterboxMatchers {
+    fun Int.asAnyMode() = asAnyMode { this < 0 }
+    fun Float.asAnyMode() = asAnyMode { this < 0f }
+    fun String.asAnyMode() = asAnyMode { this.isEmpty() }
+}
+
+private inline fun <reified T : Any> T.asAnyMode(condition: () -> Boolean) =
+    (if (condition()) any() else eq(this))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
index 1ae1c3f..78bb721 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
@@ -16,15 +16,21 @@
 
 package com.android.wm.shell.compatui.letterbox
 
+import android.graphics.Point
+import android.graphics.Rect
 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.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CLOSE
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
 import com.android.window.flags.Flags
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.transition.TransitionStateHolder
+import com.android.wm.shell.recents.RecentsTransitionHandler
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.util.TransitionObserverInputBuilder
@@ -33,13 +39,12 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito
 import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.eq
-import org.mockito.kotlin.never
+import org.mockito.kotlin.mock
 import org.mockito.kotlin.times
-import org.mockito.kotlin.anyOrNull
-import org.mockito.verification.VerificationMode
+import org.mockito.kotlin.verify
 
 /**
  * Tests for [LetterboxTransitionObserver].
@@ -91,9 +96,10 @@
 
                 validateOutput {
                     r.creationEventDetected(expected = false)
+                    r.configureStrategyInvoked(expected = false)
                     r.visibilityEventDetected(expected = false)
                     r.destroyEventDetected(expected = false)
-                    r.boundsEventDetected(expected = false)
+                    r.updateSurfaceBoundsEventDetected(expected = false)
                 }
             }
         }
@@ -107,14 +113,24 @@
 
                 inputBuilder {
                     buildTransitionInfo()
-                    r.createTopActivityChange(inputBuilder = this, isLetterboxed = true)
+                    r.createTopActivityChange(
+                        inputBuilder = this,
+                        isLetterboxed = true,
+                        taskPosition = Point(20, 30),
+                        taskWidth = 200,
+                        taskHeight = 300
+                    )
                 }
 
                 validateOutput {
                     r.creationEventDetected(expected = true)
+                    r.configureStrategyInvoked(expected = true)
                     r.visibilityEventDetected(expected = true, visible = true)
                     r.destroyEventDetected(expected = false)
-                    r.boundsEventDetected(expected = true)
+                    r.updateSurfaceBoundsEventDetected(
+                        expected = true,
+                        taskBounds = Rect(20, 30, 200, 300)
+                    )
                 }
             }
         }
@@ -135,14 +151,33 @@
                     r.creationEventDetected(expected = false)
                     r.visibilityEventDetected(expected = true, visible = false)
                     r.destroyEventDetected(expected = false)
-                    r.boundsEventDetected(expected = false)
+                    r.updateSurfaceBoundsEventDetected(expected = false)
                 }
             }
         }
     }
 
     @Test
-    fun `When closing change letterbox surface destroy is triggered`() {
+    fun `When closing change with no recents running letterbox surfaces are destroyed`() {
+        runTestScenario { r ->
+            executeTransitionObserverTest(observerFactory = r.observerFactory) {
+                r.invokeShellInit()
+
+                inputBuilder {
+                    buildTransitionInfo()
+                    r.configureRecentsState(running = false)
+                    r.createClosingChange(inputBuilder = this)
+                }
+
+                validateOutput {
+                    r.destroyEventDetected(expected = true)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `When closing change and recents are running letterbox surfaces are not destroyed`() {
         runTestScenario { r ->
             executeTransitionObserverTest(observerFactory = r.observerFactory) {
                 r.invokeShellInit()
@@ -150,13 +185,11 @@
                 inputBuilder {
                     buildTransitionInfo()
                     r.createClosingChange(inputBuilder = this)
+                    r.configureRecentsState(running = true)
                 }
 
                 validateOutput {
-                    r.destroyEventDetected(expected = true)
-                    r.creationEventDetected(expected = false)
-                    r.visibilityEventDetected(expected = false, visible = false)
-                    r.boundsEventDetected(expected = false)
+                    r.destroyEventDetected(expected = false)
                 }
             }
         }
@@ -185,16 +218,28 @@
         private val transitions: Transitions
         private val letterboxController: LetterboxController
         private val letterboxObserver: LetterboxTransitionObserver
+        private val transitionStateHolder: TransitionStateHolder
+        private val letterboxStrategy: LetterboxControllerStrategy
 
         val observerFactory: () -> LetterboxTransitionObserver
 
         init {
-            executor = Mockito.mock(ShellExecutor::class.java)
+            executor = mock<ShellExecutor>()
             shellInit = ShellInit(executor)
-            transitions = Mockito.mock(Transitions::class.java)
-            letterboxController = Mockito.mock(LetterboxController::class.java)
+            transitions = mock<Transitions>()
+            letterboxController = mock<LetterboxController>()
+            letterboxStrategy = mock<LetterboxControllerStrategy>()
+            transitionStateHolder =
+                TransitionStateHolder(shellInit, mock<RecentsTransitionHandler>())
+            spyOn(transitionStateHolder)
             letterboxObserver =
-                LetterboxTransitionObserver(shellInit, transitions, letterboxController)
+                LetterboxTransitionObserver(
+                    shellInit,
+                    transitions,
+                    letterboxController,
+                    transitionStateHolder,
+                    letterboxStrategy
+                )
             observerFactory = { letterboxObserver }
         }
 
@@ -203,67 +248,87 @@
         fun observer() = letterboxObserver
 
         fun checkObservableIsRegistered(expected: Boolean) {
-            Mockito.verify(transitions, expected.asMode()).registerObserver(observer())
+            verify(transitions, expected.asMode()).registerObserver(observer())
+        }
+
+        fun configureRecentsState(running: Boolean) {
+            doReturn(running).`when`(transitionStateHolder).isRecentsTransitionRunning()
         }
 
         fun creationEventDetected(
             expected: Boolean,
             displayId: Int = DISPLAY_ID,
             taskId: Int = TASK_ID
-        ) {
-            Mockito.verify(letterboxController, expected.asMode()).createLetterboxSurface(
-                toLetterboxKeyMatcher(displayId, taskId),
-                anyOrNull(),
-                anyOrNull()
-            )
-        }
+        ) = verify(
+            letterboxController,
+            expected.asMode()
+        ).createLetterboxSurface(
+            eq(LetterboxKey(displayId, taskId)),
+            any<SurfaceControl.Transaction>(),
+            any<SurfaceControl>()
+        )
 
         fun visibilityEventDetected(
             expected: Boolean,
+            visible: Boolean = true,
             displayId: Int = DISPLAY_ID,
-            taskId: Int = TASK_ID,
-            visible: Boolean? = null
-        ) {
-            Mockito.verify(letterboxController, expected.asMode()).updateLetterboxSurfaceVisibility(
-                toLetterboxKeyMatcher(displayId, taskId),
-                anyOrNull(),
-                visible.asMatcher()
-            )
-        }
+            taskId: Int = TASK_ID
+        ) = verify(letterboxController, expected.asMode()).updateLetterboxSurfaceVisibility(
+            eq(LetterboxKey(displayId, taskId)),
+            any<SurfaceControl.Transaction>(),
+            eq(visible)
+        )
 
         fun destroyEventDetected(
             expected: Boolean,
             displayId: Int = DISPLAY_ID,
             taskId: Int = TASK_ID
-        ) {
-            Mockito.verify(letterboxController, expected.asMode()).destroyLetterboxSurface(
-                toLetterboxKeyMatcher(displayId, taskId),
-                anyOrNull()
-            )
-        }
+        ) = verify(
+            letterboxController,
+            expected.asMode()
+        ).destroyLetterboxSurface(
+            eq(LetterboxKey(displayId, taskId)),
+            any<SurfaceControl.Transaction>()
+        )
 
-        fun boundsEventDetected(
+        fun updateSurfaceBoundsEventDetected(
             expected: Boolean,
             displayId: Int = DISPLAY_ID,
-            taskId: Int = TASK_ID
-        ) {
-            Mockito.verify(letterboxController, expected.asMode()).updateLetterboxSurfaceBounds(
-                toLetterboxKeyMatcher(displayId, taskId),
-                anyOrNull(),
-                anyOrNull()
-            )
-        }
+            taskId: Int = TASK_ID,
+            taskBounds: Rect = Rect(),
+            activityBounds: Rect = Rect()
+        ) = verify(
+            letterboxController,
+            expected.asMode()
+        ).updateLetterboxSurfaceBounds(
+            eq(LetterboxKey(displayId, taskId)),
+            any<SurfaceControl.Transaction>(),
+            eq(taskBounds),
+            eq(activityBounds)
+        )
+
+        fun configureStrategyInvoked(expected: Boolean) =
+            verify(letterboxStrategy, expected.asMode()).configureLetterboxMode()
 
         fun createTopActivityChange(
             inputBuilder: TransitionObserverInputBuilder,
             isLetterboxed: Boolean = true,
             displayId: Int = DISPLAY_ID,
-            taskId: Int = TASK_ID
+            taskId: Int = TASK_ID,
+            taskPosition: Point = Point(),
+            taskWidth: Int = 0,
+            taskHeight: Int = 0
         ) {
-            inputBuilder.addChange(changeTaskInfo = inputBuilder.createTaskInfo().apply {
-                appCompatTaskInfo.isTopActivityLetterboxed = isLetterboxed
-                this.taskId = taskId
-                this.displayId = displayId
+            inputBuilder.addChange(inputBuilder.createChange(
+                changeTaskInfo = inputBuilder.createTaskInfo().apply {
+                    appCompatTaskInfo.isTopActivityLetterboxed = isLetterboxed
+                    this.taskId = taskId
+                    this.displayId = displayId
+                }
+            ).apply {
+                endRelOffset.x = taskPosition.x
+                endRelOffset.y = taskPosition.y
+                endAbsBounds.set(Rect(0, 0, taskWidth, taskHeight))
             })
         }
 
@@ -277,18 +342,5 @@
                 this.displayId = displayId
             }, changeMode = TRANSIT_CLOSE)
         }
-
-        private fun Boolean.asMode(): VerificationMode = if (this) times(1) else never()
-
-        private fun Boolean?.asMatcher(): Boolean =
-            if (this != null) eq(this) else any()
-
-        private fun toLetterboxKeyMatcher(displayId: Int, taskId: Int): LetterboxKey {
-            if (displayId < 0 || taskId < 0) {
-                return any()
-            } else {
-                return eq(LetterboxKey(displayId, taskId))
-            }
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt
new file mode 100644
index 0000000..06b8052
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.content.Context
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [LetterboxUtils].
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:LetterboxUtilsTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxUtilsTest : ShellTestCase() {
+
+    val firstLetterboxController = mock<LetterboxController>()
+    val secondLetterboxController = mock<LetterboxController>()
+    val thirdLetterboxController = mock<LetterboxController>()
+
+    private val letterboxControllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController =
+        { _ ->
+            firstLetterboxController.append(secondLetterboxController)
+                .append(thirdLetterboxController)
+        }
+
+    @Test
+    fun `Appended LetterboxController invoked creation on all the controllers`() {
+        runTestScenario { r ->
+            r.sendCreateSurfaceRequest()
+
+            r.verifyCreateSurfaceInvokedWithRequest(target = firstLetterboxController)
+            r.verifyCreateSurfaceInvokedWithRequest(target = secondLetterboxController)
+            r.verifyCreateSurfaceInvokedWithRequest(target = thirdLetterboxController)
+        }
+    }
+
+    @Test
+    fun `Appended LetterboxController invoked destroy on all the controllers`() {
+        runTestScenario { r ->
+            r.sendDestroySurfaceRequest()
+            r.verifyDestroySurfaceInvokedWithRequest(target = firstLetterboxController)
+            r.verifyDestroySurfaceInvokedWithRequest(target = secondLetterboxController)
+            r.verifyDestroySurfaceInvokedWithRequest(target = thirdLetterboxController)
+        }
+    }
+
+    @Test
+    fun `Appended LetterboxController invoked update visibility on all the controllers`() {
+        runTestScenario { r ->
+            r.sendUpdateSurfaceVisibilityRequest(visible = true)
+            r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = firstLetterboxController)
+            r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = secondLetterboxController)
+            r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = thirdLetterboxController)
+        }
+    }
+
+    @Test
+    fun `Appended LetterboxController invoked update bounds on all the controllers`() {
+        runTestScenario { r ->
+            r.sendUpdateSurfaceBoundsRequest(taskBounds = Rect(), activityBounds = Rect())
+            r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = firstLetterboxController)
+            r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = secondLetterboxController)
+            r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = thirdLetterboxController)
+        }
+    }
+
+    @Test
+    fun `Appended LetterboxController invoked update dump on all the controllers`() {
+        runTestScenario { r ->
+            r.invokeDump()
+            r.verifyDumpInvoked(target = firstLetterboxController)
+            r.verifyDumpInvoked(target = secondLetterboxController)
+            r.verifyDumpInvoked(target = thirdLetterboxController)
+        }
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    fun runTestScenario(consumer: Consumer<AppendLetterboxControllerRobotTest>) {
+        val robot = AppendLetterboxControllerRobotTest(mContext, letterboxControllerBuilder)
+        consumer.accept(robot)
+    }
+
+    class AppendLetterboxControllerRobotTest(
+        ctx: Context,
+        builder: (LetterboxSurfaceBuilder) -> LetterboxController
+    ) : LetterboxControllerRobotTest(ctx, builder) {
+
+        fun verifyCreateSurfaceInvokedWithRequest(
+            target: LetterboxController,
+            times: Int = 1
+        ) {
+            verify(target, times(times)).createLetterboxSurface(any(), any(), any())
+        }
+
+        fun verifyDestroySurfaceInvokedWithRequest(
+            target: LetterboxController,
+            times: Int = 1
+        ) {
+            verify(target, times(times)).destroyLetterboxSurface(any(), any())
+        }
+
+        fun verifyUpdateVisibilitySurfaceInvokedWithRequest(
+            target: LetterboxController,
+            times: Int = 1
+        ) {
+            verify(target, times(times)).updateLetterboxSurfaceVisibility(any(), any(), any())
+        }
+
+        fun verifyUpdateSurfaceBoundsInvokedWithRequest(
+            target: LetterboxController,
+            times: Int = 1
+        ) {
+            verify(target, times(times)).updateLetterboxSurfaceBounds(any(), any(), any(), any())
+        }
+
+        fun verifyDumpInvoked(
+            target: LetterboxController,
+            times: Int = 1
+        ) {
+            verify(target, times(times)).dump()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt
new file mode 100644
index 0000000..e6bff4c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [MixedLetterboxController].
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:MixedLetterboxControllerTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MixedLetterboxControllerTest : ShellTestCase() {
+
+    @Test
+    fun `When strategy is SINGLE_SURFACE and a create request is sent multi are destroyed`() {
+        runTestScenario { r ->
+            r.configureStrategyFor(LetterboxMode.SINGLE_SURFACE)
+            r.sendCreateSurfaceRequest()
+            r.checkCreateInvokedOnSingleController()
+            r.checkDestroyInvokedOnMultiController()
+        }
+    }
+
+    @Test
+    fun `When strategy is MULTIPLE_SURFACES and a create request is sent single is destroyed`() {
+        runTestScenario { r ->
+            r.configureStrategyFor(LetterboxMode.MULTIPLE_SURFACES)
+            r.sendCreateSurfaceRequest()
+            r.checkDestroyInvokedOnSingleController()
+            r.checkCreateInvokedOnMultiController()
+        }
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    fun runTestScenario(consumer: Consumer<MixedLetterboxControllerRobotTest>) {
+        val robot = MixedLetterboxControllerRobotTest(mContext, ObjectToTestHolder())
+        consumer.accept(robot)
+    }
+
+    class MixedLetterboxControllerRobotTest(
+        ctx: Context,
+        private val objectToTestHolder: ObjectToTestHolder
+    ) : LetterboxControllerRobotTest(ctx, objectToTestHolder.controllerBuilder) {
+
+        fun configureStrategyFor(letterboxMode: LetterboxMode) {
+            doReturn(letterboxMode).`when`(objectToTestHolder.controllerStrategy)
+                .getLetterboxImplementationMode()
+        }
+
+        fun checkCreateInvokedOnSingleController(times: Int = 1) {
+            verify(
+                objectToTestHolder.singleLetterboxController,
+                times(times)
+            ).createLetterboxSurface(any(), any(), any())
+        }
+
+        fun checkCreateInvokedOnMultiController(times: Int = 1) {
+            verify(
+                objectToTestHolder.multipleLetterboxController,
+                times(times)
+            ).createLetterboxSurface(any(), any(), any())
+        }
+
+        fun checkDestroyInvokedOnSingleController(times: Int = 1) {
+            verify(
+                objectToTestHolder.singleLetterboxController,
+                times(times)
+            ).destroyLetterboxSurface(any(), any())
+        }
+
+        fun checkDestroyInvokedOnMultiController(times: Int = 1) {
+            verify(
+                objectToTestHolder.multipleLetterboxController,
+                times(times)
+            ).destroyLetterboxSurface(any(), any())
+        }
+    }
+
+    data class ObjectToTestHolder(
+        val singleLetterboxController: SingleSurfaceLetterboxController =
+        mock<SingleSurfaceLetterboxController>(),
+        val multipleLetterboxController: MultiSurfaceLetterboxController =
+        mock<MultiSurfaceLetterboxController>(),
+        val controllerStrategy: LetterboxControllerStrategy = mock<LetterboxControllerStrategy>()
+    ) {
+
+        private val mixedController =
+            MixedLetterboxController(
+                singleLetterboxController,
+                multipleLetterboxController,
+                controllerStrategy
+            )
+
+        val controllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController =
+            { _ -> mixedController }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt
new file mode 100644
index 0000000..295d4ed
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for [MultiSurfaceLetterboxController].
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:MultiSurfaceLetterboxControllerTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MultiSurfaceLetterboxControllerTest : ShellTestCase() {
+
+    @Test
+    fun `When creation is requested the surfaces are created if not present`() {
+        runTestScenario { r ->
+            r.sendCreateSurfaceRequest()
+
+            r.checkSurfaceBuilderInvoked(times = 4)
+        }
+    }
+
+    @Test
+    fun `When creation is requested multiple times the surfaces are created once`() {
+        runTestScenario { r ->
+            r.sendCreateSurfaceRequest()
+            r.sendCreateSurfaceRequest()
+            r.sendCreateSurfaceRequest()
+            r.sendCreateSurfaceRequest()
+
+            r.checkSurfaceBuilderInvoked(times = 4)
+        }
+    }
+
+    @Test
+    fun `Different surfaces are created for every key`() {
+        runTestScenario { r ->
+            r.sendCreateSurfaceRequest()
+            r.sendCreateSurfaceRequest()
+            r.sendCreateSurfaceRequest(displayId = 2)
+            r.sendCreateSurfaceRequest(displayId = 2, taskId = 2)
+            r.sendCreateSurfaceRequest(displayId = 2)
+            r.sendCreateSurfaceRequest(displayId = 2, taskId = 2)
+
+            r.checkSurfaceBuilderInvoked(times = 12)
+        }
+    }
+
+    @Test
+    fun `Created surface are removed once`() {
+        runTestScenario { r ->
+            r.sendCreateSurfaceRequest()
+            r.checkSurfaceBuilderInvoked(times = 4)
+
+            r.sendDestroySurfaceRequest()
+            r.sendDestroySurfaceRequest()
+            r.sendDestroySurfaceRequest()
+
+            r.checkTransactionRemovedInvoked(times = 4)
+        }
+    }
+
+    @Test
+    fun `Only existing surfaces receive visibility update`() {
+        runTestScenario { r ->
+            r.sendCreateSurfaceRequest()
+            r.sendUpdateSurfaceVisibilityRequest(visible = true)
+            r.sendUpdateSurfaceVisibilityRequest(visible = true, displayId = 20)
+
+            r.checkVisibilityUpdated(times = 4, expectedVisibility = true)
+        }
+    }
+
+    @Test
+    fun `Only existing surfaces receive taskBounds update`() {
+        runTestScenario { r ->
+            r.sendUpdateSurfaceBoundsRequest(
+                taskBounds = Rect(0, 0, 2000, 1000),
+                activityBounds = Rect(500, 0, 1500, 1000)
+            )
+
+            r.checkSurfacePositionUpdated(times = 0)
+            r.checkSurfaceSizeUpdated(times = 0)
+
+            r.sendCreateSurfaceRequest()
+
+            // Pillarbox.
+            r.sendUpdateSurfaceBoundsRequest(
+                taskBounds = Rect(0, 0, 2000, 1000),
+                activityBounds = Rect(500, 0, 1500, 1000)
+            )
+            // The Left and Top surfaces.
+            r.checkSurfacePositionUpdated(times = 2, expectedX = 0f, expectedY = 0f)
+            // The Right surface.
+            r.checkSurfacePositionUpdated(times = 1, expectedX = 1500f, expectedY = 0f)
+            // The Bottom surface.
+            r.checkSurfacePositionUpdated(times = 1, expectedX = 0f, expectedY = 1000f)
+            // Left and Right surface.
+            r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 500, expectedHeight = 1000)
+            // Top and Button.
+            r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 2000, expectedHeight = 0)
+
+            r.resetTransitionTest()
+
+            // Letterbox.
+            r.sendUpdateSurfaceBoundsRequest(
+                taskBounds = Rect(0, 0, 1000, 2000),
+                activityBounds = Rect(0, 500, 1000, 1500)
+            )
+            // Top and Left surfaces.
+            r.checkSurfacePositionUpdated(times = 2, expectedX = 0f, expectedY = 0f)
+            // Bottom surface.
+            r.checkSurfacePositionUpdated(times = 1, expectedX = 0f, expectedY = 1500f)
+            // Right surface.
+            r.checkSurfacePositionUpdated(times = 1, expectedX = 1000f, expectedY = 0f)
+
+            // Left and Right surfaces.
+            r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 0, expectedHeight = 2000)
+            // Top and Button surfaces,
+            r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 1000, expectedHeight = 500)
+        }
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    fun runTestScenario(consumer: Consumer<LetterboxControllerRobotTest>) {
+        val robot =
+            LetterboxControllerRobotTest(mContext, { sb -> MultiSurfaceLetterboxController(sb) })
+        consumer.accept(robot)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt
new file mode 100644
index 0000000..125e700
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for [SingleSurfaceLetterboxController].
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:SingleSurfaceLetterboxControllerTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class SingleSurfaceLetterboxControllerTest : ShellTestCase() {
+
+    @Test
+    fun `When creation is requested the surface is created if not present`() {
+        runTestScenario { r ->
+            r.sendCreateSurfaceRequest()
+
+            r.checkSurfaceBuilderInvoked()
+        }
+    }
+
+    @Test
+    fun `When creation is requested multiple times the surface is created once`() {
+        runTestScenario { r ->
+            r.sendCreateSurfaceRequest()
+            r.sendCreateSurfaceRequest()
+            r.sendCreateSurfaceRequest()
+            r.sendCreateSurfaceRequest()
+
+            r.checkSurfaceBuilderInvoked(times = 1)
+        }
+    }
+
+    @Test
+    fun `A different surface is created for every key`() {
+        runTestScenario { r ->
+            r.sendCreateSurfaceRequest()
+            r.sendCreateSurfaceRequest()
+            r.sendCreateSurfaceRequest(displayId = 2)
+            r.sendCreateSurfaceRequest(displayId = 2, taskId = 2)
+            r.sendCreateSurfaceRequest(displayId = 2)
+            r.sendCreateSurfaceRequest(displayId = 2, taskId = 2)
+
+            r.checkSurfaceBuilderInvoked(times = 3)
+        }
+    }
+
+    @Test
+    fun `Created surface is removed once`() {
+        runTestScenario { r ->
+            r.sendCreateSurfaceRequest()
+            r.checkSurfaceBuilderInvoked()
+
+            r.sendDestroySurfaceRequest()
+            r.sendDestroySurfaceRequest()
+            r.sendDestroySurfaceRequest()
+
+            r.checkTransactionRemovedInvoked()
+        }
+    }
+
+    @Test
+    fun `Only existing surfaces receive visibility update`() {
+        runTestScenario { r ->
+            r.sendCreateSurfaceRequest()
+            r.sendUpdateSurfaceVisibilityRequest(visible = true)
+            r.sendUpdateSurfaceVisibilityRequest(visible = true, displayId = 20)
+
+            r.checkVisibilityUpdated(expectedVisibility = true)
+        }
+    }
+
+    @Test
+    fun `Only existing surfaces receive taskBounds update`() {
+        runTestScenario { r ->
+            r.sendUpdateSurfaceBoundsRequest(
+                taskBounds = Rect(0, 0, 2000, 1000),
+                activityBounds = Rect(500, 0, 1500, 1000)
+            )
+
+            r.checkSurfacePositionUpdated(times = 0)
+            r.checkSurfaceSizeUpdated(times = 0)
+
+            r.resetTransitionTest()
+
+            r.sendCreateSurfaceRequest()
+            r.sendUpdateSurfaceBoundsRequest(
+                taskBounds = Rect(0, 0, 2000, 1000),
+                activityBounds = Rect(500, 0, 1500, 1000)
+            )
+            r.checkSurfacePositionUpdated(times = 1, expectedX = 0f, expectedY = 0f)
+            r.checkSurfaceSizeUpdated(times = 1, expectedWidth = 2000, expectedHeight = 1000)
+        }
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    fun runTestScenario(consumer: Consumer<LetterboxControllerRobotTest>) {
+        val robot =
+            LetterboxControllerRobotTest(mContext, { sb -> SingleSurfaceLetterboxController(sb) })
+        consumer.accept(robot)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index aabd973..41a594a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -23,6 +23,7 @@
 import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
 import android.graphics.Rect
 import android.os.Binder
+import android.os.UserManager
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
@@ -39,8 +40,8 @@
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.TaskStackListenerImpl
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask
 import com.android.wm.shell.desktopmode.persistence.Desktop
 import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
 import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
@@ -96,11 +97,12 @@
     @Mock lateinit var taskStackListener: TaskStackListenerImpl
     @Mock lateinit var persistentRepository: DesktopPersistentRepository
     @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
+    @Mock lateinit var userManager: UserManager
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var handler: DesktopActivityOrientationChangeHandler
     private lateinit var shellInit: ShellInit
-    private lateinit var taskRepository: DesktopRepository
+    private lateinit var userRepositories: DesktopUserRepositories
     private lateinit var testScope: CoroutineScope
     // Mock running tasks are registered here so we can get the list from mock shell task organizer.
     private val runningTasks = mutableListOf<RunningTaskInfo>()
@@ -117,13 +119,14 @@
 
         testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
         shellInit = spy(ShellInit(testExecutor))
-        taskRepository =
-            DesktopRepository(
+        userRepositories =
+            DesktopUserRepositories(
                 context,
                 shellInit,
                 persistentRepository,
                 repositoryInitializer,
-                testScope
+                testScope,
+                userManager
             )
         whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
         whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
@@ -132,7 +135,7 @@
         )
 
         handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer,
-            taskStackListener, resizeTransitionHandler, taskRepository)
+            taskStackListener, resizeTransitionHandler, userRepositories)
 
         shellInit.init()
     }
@@ -156,7 +159,7 @@
         clearInvocations(shellInit)
 
         handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer,
-            taskStackListener, resizeTransitionHandler, taskRepository)
+            taskStackListener, resizeTransitionHandler, userRepositories)
 
         verify(shellInit, never()).addInitCallback(any(),
             any<DesktopActivityOrientationChangeHandler>())
@@ -180,7 +183,7 @@
         activityInfo.screenOrientation = SCREEN_ORIENTATION_PORTRAIT
         task.topActivityInfo = activityInfo
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
-        taskRepository.addTask(DEFAULT_DISPLAY, task.taskId, isVisible = true)
+        userRepositories.current.addTask(DEFAULT_DISPLAY, task.taskId, isVisible = true)
         runningTasks.add(task)
 
         taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
@@ -203,7 +206,7 @@
     @Test
     fun handleActivityOrientationChange_notInDesktopMode_doNothing() {
         val task = setUpFreeformTask(isResizeable = false)
-        taskRepository.updateTask(task.displayId, task.taskId, isVisible = false)
+        userRepositories.current.updateTask(task.displayId, task.taskId, isVisible = false)
 
         taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
             SCREEN_ORIENTATION_LANDSCAPE)
@@ -268,7 +271,7 @@
         task.topActivityInfo = activityInfo
         task.isResizeable = isResizeable
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
-        taskRepository.addTask(displayId, task.taskId, isVisible = true)
+        userRepositories.current.addTask(displayId, task.taskId, isVisible = true)
         runningTasks.add(task)
         return task
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
index 4666276..db4c746 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
@@ -43,7 +43,7 @@
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitReason.USER_INTERACTION
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.util.StubTransaction
@@ -76,18 +76,19 @@
     @JvmField @Rule val animatorTestRule = AnimatorTestRule(this)
 
     @Mock private lateinit var mockTransitions: Transitions
-    private lateinit var desktopRepository: DesktopRepository
+    private lateinit var userRepositories: DesktopUserRepositories
     @Mock private lateinit var mockDisplayController: DisplayController
     @Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
     @Mock private lateinit var mockDisplayLayout: DisplayLayout
     private val transactionSupplier = { StubTransaction() }
 
     private lateinit var controller: DesktopImmersiveController
+    private lateinit var desktopRepository: DesktopRepository
 
     @Before
     fun setUp() {
-        desktopRepository = DesktopRepository(
-            context, ShellInit(TestShellExecutor()), mock(), mock(), mock()
+        userRepositories = DesktopUserRepositories(
+            context, ShellInit(TestShellExecutor()), mock(), mock(), mock(), mock()
         )
         whenever(mockDisplayController.getDisplayLayout(DEFAULT_DISPLAY))
             .thenReturn(mockDisplayLayout)
@@ -97,12 +98,13 @@
         controller = DesktopImmersiveController(
             shellInit = mock(),
             transitions = mockTransitions,
-            desktopRepository = desktopRepository,
+            desktopUserRepositories = userRepositories,
             displayController = mockDisplayController,
             shellTaskOrganizer = mockShellTaskOrganizer,
             shellCommandHandler = mock(),
             transactionSupplier = transactionSupplier,
         )
+        desktopRepository = userRepositories.current
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index 62717a3..49a7e29 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -21,6 +21,7 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.app.WindowConfiguration.WindowingMode
+import android.content.Intent
 import android.os.Binder
 import android.os.Handler
 import android.os.IBinder
@@ -36,7 +37,9 @@
 import android.view.WindowManager.TRANSIT_OPEN
 import android.view.WindowManager.TRANSIT_TO_BACK
 import android.view.WindowManager.TransitionType
+import android.window.IWindowContainerToken
 import android.window.TransitionInfo
+import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
@@ -60,6 +63,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.kotlin.any
@@ -84,7 +88,7 @@
     @Mock
     lateinit var transitions: Transitions
     @Mock
-    lateinit var desktopRepository: DesktopRepository
+    lateinit var userRepositories: DesktopUserRepositories
     @Mock
     lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
     @Mock
@@ -103,16 +107,21 @@
     lateinit var shellInit: ShellInit
     @Mock
     lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+    @Mock
+    private lateinit var desktopRepository: DesktopRepository
 
     private lateinit var mixedHandler: DesktopMixedTransitionHandler
 
+
     @Before
     fun setUp() {
+        whenever(userRepositories.current).thenReturn(desktopRepository)
+        whenever(userRepositories.getProfile(Mockito.anyInt())).thenReturn(desktopRepository)
         mixedHandler =
             DesktopMixedTransitionHandler(
                 context,
                 transitions,
-                desktopRepository,
+                userRepositories,
                 freeformTaskTransitionHandler,
                 closeDesktopTaskTransitionHandler,
                 desktopImmersiveController,
@@ -146,7 +155,9 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+    @DisableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
     fun startRemoveTransition_callsFreeformTaskTransitionHandler() {
         val wct = WindowContainerTransaction()
         whenever(freeformTaskTransitionHandler.startRemoveTransition(wct))
@@ -158,7 +169,9 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
     fun startRemoveTransition_startsCloseTransition() {
         val wct = WindowContainerTransaction()
         whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler))
@@ -178,7 +191,7 @@
     fun startAnimation_withoutClosingDesktopTask_returnsFalse() {
         val transition = mock<IBinder>()
         val transitionInfo =
-            createTransitionInfo(
+            createCloseTransitionInfo(
                 changeMode = TRANSIT_OPEN,
                 task = createTask(WINDOWING_MODE_FREEFORM)
             )
@@ -197,12 +210,13 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
     fun startAnimation_withClosingDesktopTask_callsCloseTaskHandler() {
         val wct = WindowContainerTransaction()
         val transition = mock<IBinder>()
-        val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
-        whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2)
+        val transitionInfo = createCloseTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
         whenever(
                 closeDesktopTaskTransitionHandler.startAnimation(any(), any(), any(), any(), any())
             )
@@ -225,12 +239,14 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
     fun startAnimation_withClosingLastDesktopTask_dispatchesTransition() {
         val wct = WindowContainerTransaction()
         val transition = mock<IBinder>()
-        val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM))
-        whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(1)
+        val transitionInfo = createCloseTransitionInfo(
+            task = createTask(WINDOWING_MODE_FREEFORM), withWallpaper = true)
         whenever(transitions.dispatchTransition(any(), any(), any(), any(), any(), any()))
             .thenReturn(mock())
         whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler))
@@ -266,7 +282,8 @@
     @Test
     @DisableFlags(
         Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
-        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
     fun startLaunchTransition_immersiveAndAppLaunchFlagsDisabled_doesNotUseMixedHandler() {
         val wct = WindowContainerTransaction()
         val task = createTask(WINDOWING_MODE_FREEFORM)
@@ -302,7 +319,9 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
     fun startLaunchTransition_desktopAppLaunchEnabled_usesMixedHandler() {
         val wct = WindowContainerTransaction()
         val task = createTask(WINDOWING_MODE_FREEFORM)
@@ -338,7 +357,7 @@
         val otherChange = createChange(createTask(WINDOWING_MODE_FREEFORM))
         mixedHandler.startAnimation(
             transition,
-            createTransitionInfo(
+            createCloseTransitionInfo(
                 TRANSIT_OPEN,
                 listOf(launchTaskChange, otherChange)
             ),
@@ -378,7 +397,7 @@
         val immersiveChange = createChange(immersiveTask)
         mixedHandler.startAnimation(
             transition,
-            createTransitionInfo(
+            createCloseTransitionInfo(
                 TRANSIT_OPEN,
                 listOf(launchTaskChange, immersiveChange)
             ),
@@ -401,7 +420,9 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
     fun startAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
         val wct = WindowContainerTransaction()
         val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -418,7 +439,7 @@
         )
         mixedHandler.startAnimation(
             transition,
-            createTransitionInfo(
+            createCloseTransitionInfo(
                 TRANSIT_OPEN,
                 listOf(launchTaskChange)
             ),
@@ -431,7 +452,9 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
     fun startAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
         val wct = WindowContainerTransaction()
         val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -450,7 +473,7 @@
         )
         mixedHandler.startAnimation(
             transition,
-            createTransitionInfo(
+            createCloseTransitionInfo(
                 TRANSIT_OPEN,
                 listOf(launchTaskChange, minimizeChange)
             ),
@@ -463,7 +486,9 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
     fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() {
         val wct = WindowContainerTransaction()
         val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -482,7 +507,7 @@
 
         val started = mixedHandler.startAnimation(
             transition,
-            createTransitionInfo(
+            createCloseTransitionInfo(
                 TRANSIT_OPEN,
                 listOf(nonLaunchTaskChange)
             ),
@@ -512,7 +537,7 @@
 
         val started = mixedHandler.startAnimation(
             transition,
-            createTransitionInfo(
+            createCloseTransitionInfo(
                 TRANSIT_OPEN,
                 listOf(createChange(task, mode = TRANSIT_OPEN))
             ),
@@ -546,7 +571,7 @@
         val openingChange = createChange(openingTask, mode = TRANSIT_OPEN)
         val started = mixedHandler.startAnimation(
             transition,
-            createTransitionInfo(
+            createCloseTransitionInfo(
                 TRANSIT_OPEN,
                 listOf(immersiveChange, openingChange)
             ),
@@ -560,7 +585,9 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
     fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
         val wct = WindowContainerTransaction()
         val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -579,7 +606,7 @@
         )
         mixedHandler.startAnimation(
             transition,
-            createTransitionInfo(
+            createCloseTransitionInfo(
                 TRANSIT_OPEN,
                 listOf(launchTaskChange)
             ),
@@ -592,7 +619,9 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
     fun addPendingAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
         val wct = WindowContainerTransaction()
         val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -613,7 +642,7 @@
         )
         mixedHandler.startAnimation(
             transition,
-            createTransitionInfo(
+            createCloseTransitionInfo(
                 TRANSIT_OPEN,
                 listOf(launchTaskChange, minimizeChange)
             ),
@@ -643,7 +672,7 @@
         val launchTaskChange = createChange(launchingTask)
         mixedHandler.startAnimation(
             transition,
-            createTransitionInfo(
+            createCloseTransitionInfo(
                 TRANSIT_OPEN,
                 listOf(launchTaskChange)
             ),
@@ -700,7 +729,7 @@
         val started = mixedHandler.startAnimation(
             transition = transition,
             info =
-                createTransitionInfo(
+                createCloseTransitionInfo(
                 TRANSIT_TO_BACK,
                 listOf(minimizingTaskChange)
             ),
@@ -739,7 +768,7 @@
         mixedHandler.startAnimation(
             transition = transition,
             info =
-            createTransitionInfo(
+            createCloseTransitionInfo(
                 TRANSIT_TO_BACK,
                 listOf(minimizingTaskChange)
             ),
@@ -759,12 +788,12 @@
             )
     }
 
-    private fun createTransitionInfo(
-        type: Int = WindowManager.TRANSIT_CLOSE,
+    private fun createCloseTransitionInfo(
         changeMode: Int = WindowManager.TRANSIT_CLOSE,
-        task: RunningTaskInfo
+        task: RunningTaskInfo,
+        withWallpaper: Boolean = false,
     ): TransitionInfo =
-        TransitionInfo(type, 0 /* flags */).apply {
+        TransitionInfo(WindowManager.TRANSIT_CLOSE, 0 /* flags */).apply {
             addChange(
                 TransitionInfo.Change(mock(), closingTaskLeash).apply {
                     mode = changeMode
@@ -772,9 +801,18 @@
                     taskInfo = task
                 }
             )
+            if (withWallpaper) {
+                addChange(
+                    TransitionInfo.Change(/* container= */ mock(), /* leash= */ mock()).apply {
+                        mode = WindowManager.TRANSIT_CLOSE
+                        parent = null
+                        taskInfo = createWallpaperTask()
+                    }
+                )
+            }
         }
 
-    private fun createTransitionInfo(
+    private fun createCloseTransitionInfo(
         @TransitionType type: Int,
         changes: List<TransitionInfo.Change> = emptyList()
     ): TransitionInfo = TransitionInfo(type, /* flags= */ 0).apply {
@@ -795,4 +833,13 @@
             .setActivityType(ACTIVITY_TYPE_STANDARD)
             .setWindowingMode(windowingMode)
             .build()
+
+    private fun createWallpaperTask() =
+        RunningTaskInfo().apply {
+            token = WindowContainerToken(mock<IWindowContainerToken>())
+            baseIntent =
+                Intent().apply {
+                    component = DesktopWallpaperActivity.wallpaperActivityComponent
+                }
+        }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
index 7adc339..e57ae2a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
@@ -38,7 +38,7 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 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.desktopmode.DesktopTestHelpers.createFreeformTask
 import com.android.wm.shell.transition.FocusTransitionObserver
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -53,10 +53,10 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
+import com.android.wm.shell.TestShellExecutor
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
-import com.android.wm.shell.common.ShellExecutor
-import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel
@@ -91,7 +91,7 @@
     private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
     private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
     private val focusTransitionObserver = mock<FocusTransitionObserver>()
-    private val testExecutor = mock<ShellExecutor>()
+    private val testExecutor = TestShellExecutor()
     private val inputManager = mock<InputManager>()
     private val displayController = mock<DisplayController>()
     private val displayLayout = mock<DisplayLayout>()
@@ -134,6 +134,17 @@
             null
         }.whenever(inputManager).registerKeyGestureEventHandler(any())
         shellInit.init()
+
+        desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
+            context,
+            Optional.of(desktopModeWindowDecorViewModel),
+            Optional.of(desktopTasksController),
+            inputManager,
+            shellTaskOrganizer,
+            focusTransitionObserver,
+            testExecutor,
+            displayController
+        )
     }
 
     @After
@@ -142,6 +153,7 @@
 
         runningTasks.clear()
         testScope.cancel()
+        testExecutor.flushAll()
     }
 
     @Test
@@ -151,11 +163,6 @@
         FLAG_USE_KEY_GESTURE_EVENT_HANDLER
     )
     fun keyGestureMoveToNextDisplay_shouldMoveToNextDisplay() {
-        desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
-            context,
-            Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController),
-            inputManager, shellTaskOrganizer, focusTransitionObserver
-        )
         // Set up two display ids
         whenever(rootTaskDisplayAreaOrganizer.displayIds)
             .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
@@ -176,6 +183,7 @@
             .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
             .build()
         val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+        testExecutor.flushAll()
 
         assertThat(result).isTrue()
         verify(desktopTasksController).moveToNextDisplay(task.taskId)
@@ -187,11 +195,6 @@
         FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
     )
     fun keyGestureSnapLeft_shouldSnapResizeTaskToLeft() {
-        desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
-            context,
-            Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController),
-            inputManager, shellTaskOrganizer, focusTransitionObserver
-        )
         val task = setUpFreeformTask()
         task.isFocused = true
         whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
@@ -203,10 +206,14 @@
             .setModifierState(KeyEvent.META_META_ON)
             .build()
         val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+        testExecutor.flushAll()
 
         assertThat(result).isTrue()
         verify(desktopModeWindowDecorViewModel).onSnapResize(
-            task.taskId, true, DesktopModeEventLogger.Companion.InputMethod.KEYBOARD
+            task.taskId,
+            true,
+            DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+            /* fromMenu= */ false
         )
     }
 
@@ -216,11 +223,6 @@
         FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
     )
     fun keyGestureSnapRight_shouldSnapResizeTaskToRight() {
-        desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
-            context,
-            Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController),
-            inputManager, shellTaskOrganizer, focusTransitionObserver
-        )
         val task = setUpFreeformTask()
         task.isFocused = true
         whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
@@ -232,10 +234,14 @@
             .setModifierState(KeyEvent.META_META_ON)
             .build()
         val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+        testExecutor.flushAll()
 
         assertThat(result).isTrue()
         verify(desktopModeWindowDecorViewModel).onSnapResize(
-            task.taskId, false, DesktopModeEventLogger.Companion.InputMethod.KEYBOARD
+            task.taskId,
+            false,
+            DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+            /* fromMenu= */ false
         )
     }
 
@@ -245,11 +251,6 @@
         FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
     )
     fun keyGestureToggleFreeformWindowSize_shouldToggleTaskSize() {
-        desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
-            context,
-            Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController),
-            inputManager, shellTaskOrganizer, focusTransitionObserver
-        )
         val task = setUpFreeformTask()
         task.isFocused = true
         whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
@@ -261,12 +262,17 @@
             .setModifierState(KeyEvent.META_META_ON)
             .build()
         val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+        testExecutor.flushAll()
 
         assertThat(result).isTrue()
         verify(desktopTasksController).toggleDesktopTaskSize(
             task,
-            ResizeTrigger.MAXIMIZE_MENU,
-            null
+            ToggleTaskSizeInteraction(
+                isMaximized = isTaskMaximized(task, displayController),
+                source = ToggleTaskSizeInteraction.Source.KEYBOARD_SHORTCUT,
+                inputMethod =
+                    DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+            ),
         )
     }
 
@@ -276,11 +282,6 @@
         FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
     )
     fun keyGestureMinimizeFreeformWindow_shouldMinimizeTask() {
-        desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
-            context,
-            Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController),
-            inputManager, shellTaskOrganizer, focusTransitionObserver
-        )
         val task = setUpFreeformTask()
         task.isFocused = true
         whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
@@ -292,6 +293,7 @@
             .setModifierState(KeyEvent.META_META_ON)
             .build()
         val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+        testExecutor.flushAll()
 
         assertThat(result).isTrue()
         verify(desktopTasksController).minimizeTask(task)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 7f790d5..344140d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -30,7 +30,6 @@
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.desktopmode.persistence.Desktop
 import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
-import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
 import com.android.wm.shell.sysui.ShellInit
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.fail
@@ -70,7 +69,6 @@
 
     @Mock private lateinit var testExecutor: ShellExecutor
     @Mock private lateinit var persistentRepository: DesktopPersistentRepository
-    @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
 
     @Before
     fun setUp() {
@@ -80,11 +78,9 @@
 
         repo =
             DesktopRepository(
-                context,
-                shellInit,
                 persistentRepository,
-                repositoryInitializer,
-                datastoreScope
+                datastoreScope,
+                DEFAULT_USER_ID
             )
         whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
             Desktop.getDefaultInstance()
@@ -153,6 +149,14 @@
     }
 
     @Test
+    fun addTask_multipleDisplays_moveToAnotherDisplay() {
+        repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
+        repo.addTask(SECOND_DISPLAY, taskId = 1, isVisible = true)
+        assertThat(repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)).isEmpty()
+        assertThat(repo.getFreeformTasksInZOrder(SECOND_DISPLAY)).containsExactly(1)
+    }
+
+    @Test
     fun removeActiveTask_notifiesActiveTaskListener() {
         val listener = TestListener()
         repo.addActiveTaskListener(listener)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
index e977966..b4daa66 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
@@ -22,13 +22,15 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
 import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
 import org.mockito.kotlin.verify
@@ -47,131 +49,163 @@
 
   private lateinit var desktopTaskChangeListener: DesktopTaskChangeListener
 
+  private val desktopUserRepositories = mock<DesktopUserRepositories>()
   private val desktopRepository = mock<DesktopRepository>()
 
   @Before
   fun setUp() {
-    desktopTaskChangeListener = DesktopTaskChangeListener(desktopRepository)
+    desktopTaskChangeListener = DesktopTaskChangeListener(desktopUserRepositories)
+
+    whenever(desktopUserRepositories.current).thenReturn(desktopRepository)
+    whenever(desktopUserRepositories.getProfile(anyInt())).thenReturn(desktopRepository)
   }
 
   @Test
   fun onTaskOpening_fullscreenTask_notActiveDesktopTask_noop() {
     val task = createFullscreenTask().apply { isVisible = true }
-    whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(false)
+    whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+        .thenReturn(false)
 
     desktopTaskChangeListener.onTaskOpening(task)
 
-    verify(desktopRepository, never()).addTask(task.displayId, task.taskId, task.isVisible)
-    verify(desktopRepository, never()).removeFreeformTask(task.displayId, task.taskId)
+    verify(desktopUserRepositories.current, never())
+        .addTask(task.displayId, task.taskId, task.isVisible)
+    verify(desktopUserRepositories.current, never())
+        .removeFreeformTask(task.displayId, task.taskId)
   }
 
   @Test
   fun onTaskOpening_freeformTask_activeDesktopTask_removesTaskFromRepo() {
     val task = createFullscreenTask().apply { isVisible = true }
-    whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+    whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+        .thenReturn(true)
 
     desktopTaskChangeListener.onTaskOpening(task)
 
-    verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+    verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId)
   }
 
   @Test
   fun onTaskOpening_freeformTask_visibleDesktopTask_addsTaskToRepository() {
     val task = createFreeformTask().apply { isVisible = true }
-    whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(false)
+    whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+        .thenReturn(false)
 
     desktopTaskChangeListener.onTaskOpening(task)
 
-    verify(desktopRepository).addTask(task.displayId, task.taskId, task.isVisible)
+    verify(desktopUserRepositories.current)
+        .addTask(task.displayId, task.taskId, task.isVisible)
   }
 
   @Test
   fun onTaskOpening_freeformTask_nonVisibleDesktopTask_addsTaskToRepository() {
     val task = createFreeformTask().apply { isVisible = false }
-    whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+    whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+        .thenReturn(true)
 
     desktopTaskChangeListener.onTaskOpening(task)
 
-    verify(desktopRepository).addTask(task.displayId, task.taskId, task.isVisible)
+    verify(desktopUserRepositories.current)
+        .addTask(task.displayId, task.taskId, task.isVisible)
   }
 
   @Test
   fun onTaskChanging_freeformTaskOutsideDesktop_removesTaskFromRepo() {
     val task = createFullscreenTask().apply { isVisible = true }
-    whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+    whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+        .thenReturn(true)
 
     desktopTaskChangeListener.onTaskChanging(task)
 
-    verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+    verify(desktopUserRepositories.current)
+        .removeFreeformTask(task.displayId, task.taskId)
   }
 
   @Test
   fun onTaskChanging_visibleTaskInDesktop_updatesTaskVisibility() {
     val task = createFreeformTask().apply { isVisible = true }
-    whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+    whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+        .thenReturn(true)
 
     desktopTaskChangeListener.onTaskChanging(task)
 
-    verify(desktopRepository).updateTask(task.displayId, task.taskId, task.isVisible)
+    verify(desktopUserRepositories.current)
+        .updateTask(task.displayId, task.taskId, task.isVisible)
   }
 
   @Test
   fun onTaskChanging_nonVisibleTask_updatesTaskVisibility() {
     val task = createFreeformTask().apply { isVisible = false }
-    whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+    whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+        .thenReturn(true)
 
     desktopTaskChangeListener.onTaskChanging(task)
 
-    verify(desktopRepository).updateTask(task.displayId, task.taskId, task.isVisible)
+    verify(desktopUserRepositories.current)
+        .updateTask(task.displayId, task.taskId, task.isVisible)
   }
 
   @Test
   fun onTaskMovingToFront_freeformTaskOutsideDesktop_removesTaskFromRepo() {
     val task = createFullscreenTask().apply { isVisible = true }
-    whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
+    whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+        .thenReturn(true)
 
     desktopTaskChangeListener.onTaskMovingToFront(task)
 
-    verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+    verify(desktopUserRepositories.current)
+        .removeFreeformTask(task.displayId, task.taskId)
   }
 
   @Test
   @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
   fun onTaskClosing_backNavEnabled_nonClosingTask_minimizesTaskInRepo() {
     val task = createFreeformTask().apply { isVisible = true }
-    whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
-    whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(false)
+    whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+        .thenReturn(true)
+    whenever(desktopUserRepositories.current.isClosingTask(task.taskId))
+        .thenReturn(false)
 
     desktopTaskChangeListener.onTaskClosing(task)
 
-    verify(desktopRepository).updateTask(task.displayId, task.taskId, isVisible = false)
-    verify(desktopRepository).minimizeTask(task.displayId, task.taskId)
+    verify(desktopUserRepositories.current)
+        .updateTask(task.displayId, task.taskId, isVisible = false)
+    verify(desktopUserRepositories.current)
+        .minimizeTask(task.displayId, task.taskId)
   }
 
   @Test
   @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
   fun onTaskClosing_backNavDisabled_closingTask_removesTaskInRepo() {
     val task = createFreeformTask().apply { isVisible = true }
-    whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
-    whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(true)
+    whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+        .thenReturn(true)
+    whenever(desktopUserRepositories.current.isClosingTask(task.taskId))
+        .thenReturn(true)
 
     desktopTaskChangeListener.onTaskClosing(task)
 
-    verify(desktopRepository, never()).minimizeTask(task.displayId, task.taskId)
-    verify(desktopRepository).removeClosingTask(task.taskId)
-    verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+    verify(desktopUserRepositories.current, never())
+        .minimizeTask(task.displayId, task.taskId)
+    verify(desktopUserRepositories.current)
+        .removeClosingTask(task.taskId)
+    verify(desktopUserRepositories.current)
+        .removeFreeformTask(task.displayId, task.taskId)
   }
 
   @Test
   @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
   fun onTaskClosing_backNavEnabled_closingTask_removesTaskFromRepo() {
     val task = createFreeformTask().apply { isVisible = true }
-    whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true)
-    whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(true)
+    whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
+        .thenReturn(true)
+    whenever(desktopUserRepositories.current.isClosingTask(task.taskId))
+        .thenReturn(true)
 
     desktopTaskChangeListener.onTaskClosing(task)
 
-    verify(desktopRepository).removeClosingTask(task.taskId)
-    verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId)
+    verify(desktopUserRepositories.current).removeClosingTask(task.taskId)
+    verify(desktopUserRepositories.current)
+        .removeFreeformTask(task.displayId, task.taskId)
   }
 }
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 a8cfef7..c10434a 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
@@ -44,6 +44,7 @@
 import android.os.Bundle
 import android.os.Handler
 import android.os.IBinder
+import android.os.UserManager
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
@@ -93,16 +94,17 @@
 import com.android.wm.shell.common.MultiInstanceHelper
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
 import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 import com.android.wm.shell.desktopmode.DesktopTasksController.DesktopModeEntryExitTransitionListener
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
 import com.android.wm.shell.desktopmode.DesktopTasksController.TaskbarDesktopTaskListener
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createHomeTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSplitScreenTask
 import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION
 import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION
 import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
@@ -220,6 +222,7 @@
   @Mock private lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter
   @Mock private lateinit var mockHandler: Handler
   @Mock private lateinit var desktopModeEventLogger: DesktopModeEventLogger
+  @Mock private lateinit var desktopModeUiEventLogger: DesktopModeUiEventLogger
   @Mock lateinit var persistentRepository: DesktopPersistentRepository
   @Mock lateinit var motionEvent: MotionEvent
   @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
@@ -232,9 +235,11 @@
   @Mock private lateinit var resources: Resources
   @Mock
   lateinit var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener
+  @Mock private lateinit var userManager: UserManager
   private lateinit var controller: DesktopTasksController
   private lateinit var shellInit: ShellInit
   private lateinit var taskRepository: DesktopRepository
+  private lateinit var userRepositories: DesktopUserRepositories
   private lateinit var desktopTasksLimiter: DesktopTasksLimiter
   private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
   private lateinit var testScope: CoroutineScope
@@ -266,12 +271,18 @@
 
     testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
     shellInit = spy(ShellInit(testExecutor))
-    taskRepository =
-      DesktopRepository(context, shellInit, persistentRepository, repositoryInitializer, testScope)
+    userRepositories =
+      DesktopUserRepositories(
+        context,
+        shellInit,
+        persistentRepository,
+        repositoryInitializer,
+        testScope,
+        userManager)
     desktopTasksLimiter =
         DesktopTasksLimiter(
             transitions,
-            taskRepository,
+            userRepositories,
             shellTaskOrganizer,
             MAX_TASK_LIMIT,
             mockInteractionJankMonitor,
@@ -314,6 +325,8 @@
     controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
 
     assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+    taskRepository = userRepositories.current
   }
 
   private fun createController(): DesktopTasksController {
@@ -337,7 +350,7 @@
         toggleResizeDesktopTaskTransitionHandler,
         dragToDesktopTransitionHandler,
         mMockDesktopImmersiveController,
-        taskRepository,
+        userRepositories,
         recentsTransitionHandler,
         multiInstanceHelper,
         shellExecutor,
@@ -346,6 +359,7 @@
         mockInteractionJankMonitor,
         mockHandler,
         desktopModeEventLogger,
+        desktopModeUiEventLogger,
         desktopTilingDecorViewModel,
       )
   }
@@ -375,12 +389,19 @@
     val task1 = setUpFreeformTask()
 
     val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
-    controller.toggleDesktopTaskSize(task1, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+    controller.toggleDesktopTaskSize(
+      task1,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
 
     verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
     verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
       ResizeTrigger.MAXIMIZE_BUTTON,
-      InputMethod.UNKNOWN_INPUT_METHOD,
+      InputMethod.TOUCH,
       task1,
       STABLE_BOUNDS.width(),
       STABLE_BOUNDS.height(),
@@ -397,21 +418,29 @@
   }
 
   @Test
-  fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFullScreenTask_returnFalse() {
+  fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfMaximizedTask_returnFalse() {
     val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
     val task1 = setUpFreeformTask(bounds = stableBounds, active = true)
 
     val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
-    controller.toggleDesktopTaskSize(task1, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+    controller.toggleDesktopTaskSize(
+      task1,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.RESTORE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+        InputMethod.TOUCH
+      )
+    )
 
     verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
     verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
-      ResizeTrigger.MAXIMIZE_BUTTON,
-      InputMethod.UNKNOWN_INPUT_METHOD,
-      task1,
-      0,
-      0,
-      displayController
+      eq(ResizeTrigger.MAXIMIZE_BUTTON),
+      eq(InputMethod.TOUCH),
+      eq(task1),
+      anyOrNull(),
+      anyOrNull(),
+      eq(displayController),
+      anyOrNull()
     )
     assertThat(argumentCaptor.value).isFalse()
   }
@@ -3357,14 +3386,21 @@
     val bounds = Rect(0, 0, 100, 100)
     val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
 
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
 
     // Assert bounds set to stable bounds
     val wct = getLatestToggleResizeDesktopTaskWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
     verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
       ResizeTrigger.MAXIMIZE_BUTTON,
-      InputMethod.UNKNOWN_INPUT_METHOD,
+      InputMethod.TOUCH,
       task,
       STABLE_BOUNDS.width(),
       STABLE_BOUNDS.height(),
@@ -3582,14 +3618,21 @@
     // Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds
     val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750)
 
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
 
     // Assert bounds set to stable bounds
     val wct = getLatestToggleResizeDesktopTaskWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
     verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
       ResizeTrigger.MAXIMIZE_BUTTON,
-      InputMethod.UNKNOWN_INPUT_METHOD,
+      InputMethod.TOUCH,
       task,
       expectedBounds.width(),
       expectedBounds.height(),
@@ -3602,7 +3645,15 @@
     val bounds = Rect(0, 0, 100, 100)
     val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
 
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
+
     assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isEqualTo(bounds)
     verify(desktopModeEventLogger, never()).logTaskResizingEnded(
       any(), any(), any(), any(),
@@ -3616,18 +3667,32 @@
     val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
 
     // Maximize
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
     task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
 
     // Restore
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.RESTORE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+        InputMethod.TOUCH
+      )
+    )
 
     // Assert bounds set to last bounds before maximize
     val wct = getLatestToggleResizeDesktopTaskWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
     verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
       ResizeTrigger.MAXIMIZE_BUTTON,
-      InputMethod.UNKNOWN_INPUT_METHOD,
+      InputMethod.TOUCH,
       task,
       boundsBeforeMaximize.width(),
       boundsBeforeMaximize.height(),
@@ -3643,19 +3708,33 @@
     }
 
     // Maximize
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
     task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS.left,
       boundsBeforeMaximize.top, STABLE_BOUNDS.right, boundsBeforeMaximize.bottom)
 
     // Restore
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.RESTORE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+        InputMethod.TOUCH
+      )
+    )
 
     // Assert bounds set to last bounds before maximize
     val wct = getLatestToggleResizeDesktopTaskWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
     verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
       ResizeTrigger.MAXIMIZE_BUTTON,
-      InputMethod.UNKNOWN_INPUT_METHOD,
+      InputMethod.TOUCH,
       task,
       boundsBeforeMaximize.width(),
       boundsBeforeMaximize.height(),
@@ -3671,19 +3750,33 @@
     }
 
     // Maximize
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
     task.configuration.windowConfiguration.bounds.set(boundsBeforeMaximize.left,
       STABLE_BOUNDS.top, boundsBeforeMaximize.right, STABLE_BOUNDS.bottom)
 
     // Restore
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.RESTORE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+        InputMethod.TOUCH
+      )
+    )
 
     // Assert bounds set to last bounds before maximize
     val wct = getLatestToggleResizeDesktopTaskWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
     verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
       ResizeTrigger.MAXIMIZE_BUTTON,
-      InputMethod.UNKNOWN_INPUT_METHOD,
+      InputMethod.TOUCH,
       task,
       boundsBeforeMaximize.width(),
       boundsBeforeMaximize.height(),
@@ -3697,17 +3790,31 @@
     val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
 
     // Maximize
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+        InputMethod.TOUCH
+      )
+    )
     task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
 
     // Restore
-    controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, motionEvent)
+    controller.toggleDesktopTaskSize(
+      task,
+      ToggleTaskSizeInteraction(
+        ToggleTaskSizeInteraction.Direction.RESTORE,
+        ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+        InputMethod.TOUCH
+      )
+    )
 
     // Assert last bounds before maximize removed after use
     assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
     verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
       ResizeTrigger.MAXIMIZE_BUTTON,
-      InputMethod.UNKNOWN_INPUT_METHOD,
+      InputMethod.TOUCH,
       task,
       boundsBeforeMaximize.width(),
       boundsBeforeMaximize.height(),
@@ -3749,26 +3856,6 @@
   }
 
   @Test
-  fun toggleImmersive_enter_movesToImmersive() {
-    val task = setUpFreeformTask(DEFAULT_DISPLAY)
-    taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, false /* immersive */)
-
-    controller.toggleDesktopTaskFullImmersiveState(task)
-
-    verify(mMockDesktopImmersiveController).moveTaskToImmersive(task)
-  }
-
-  @Test
-  fun toggleImmersive_exit_movesToNonImmersive() {
-    val task = setUpFreeformTask(DEFAULT_DISPLAY)
-    taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, true /* immersive */)
-
-    controller.toggleDesktopTaskFullImmersiveState(task)
-
-    verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(eq(task), any())
-  }
-
-  @Test
   @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
   fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_exits() {
     val task = setUpFreeformTask(DEFAULT_DISPLAY)
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 456b50d..0712d58 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -20,6 +20,7 @@
 import android.graphics.Rect
 import android.os.Binder
 import android.os.Handler
+import android.os.UserManager
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
@@ -42,7 +43,7 @@
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.common.ShellExecutor
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
 import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
 import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -73,7 +74,6 @@
 import org.mockito.kotlin.verify
 import org.mockito.quality.Strictness
 
-
 /**
  * Test class for {@link DesktopTasksLimiter}
  *
@@ -95,9 +95,11 @@
     @Mock lateinit var testExecutor: ShellExecutor
     @Mock lateinit var persistentRepository: DesktopPersistentRepository
     @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
+    @Mock lateinit var userManager: UserManager
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var desktopTasksLimiter: DesktopTasksLimiter
+    private lateinit var userRepositories: DesktopUserRepositories
     private lateinit var desktopTaskRepo: DesktopRepository
     private lateinit var shellInit: ShellInit
     private lateinit var testScope: CoroutineScope
@@ -111,16 +113,18 @@
         Dispatchers.setMain(StandardTestDispatcher())
         testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
 
-        desktopTaskRepo =
-            DesktopRepository(
+        userRepositories =
+            DesktopUserRepositories(
                 context,
                 shellInit,
                 persistentRepository,
                 repositoryInitializer,
-                testScope
+                testScope,
+                userManager
             )
+        desktopTaskRepo = userRepositories.current
         desktopTasksLimiter =
-            DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT,
+            DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, MAX_TASK_LIMIT,
                 interactionJankMonitor, mContext, handler)
     }
 
@@ -133,7 +137,7 @@
     @Test
     fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() {
         assertFailsWith<IllegalArgumentException> {
-            DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, 0,
+            DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, 0,
                 interactionJankMonitor, mContext, handler)
         }
     }
@@ -141,7 +145,7 @@
     @Test
     fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() {
         assertFailsWith<IllegalArgumentException> {
-            DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, -5,
+            DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, -5,
                 interactionJankMonitor, mContext, handler)
         }
     }
@@ -411,7 +415,7 @@
     @Test
     fun getTaskToMinimize_tasksAboveLimit_otherLimit_returnsBackTask() {
         desktopTasksLimiter =
-            DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT2,
+            DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, MAX_TASK_LIMIT2,
                 interactionJankMonitor, mContext, handler)
         val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 7f1c1db..b31a3f5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -39,6 +39,7 @@
 import com.android.window.flags.Flags
 import com.android.wm.shell.MockToken
 import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.back.BackAnimationController
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -50,6 +51,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.ArgumentMatchers.isA
 import org.mockito.Mockito
@@ -66,17 +68,17 @@
     @JvmField
     @Rule
     val extendedMockitoRule =
-        ExtendedMockitoRule.Builder(this)
-            .mockStatic(DesktopModeStatus::class.java)
-            .build()!!
+        ExtendedMockitoRule.Builder(this).mockStatic(DesktopModeStatus::class.java).build()!!
 
     private val testExecutor = mock<ShellExecutor>()
     private val mockShellInit = mock<ShellInit>()
     private val transitions = mock<Transitions>()
     private val context = mock<Context>()
     private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
+    private val userRepositories = mock<DesktopUserRepositories>()
     private val taskRepository = mock<DesktopRepository>()
     private val mixedHandler = mock<DesktopMixedTransitionHandler>()
+    private val backAnimationController = mock<BackAnimationController>()
 
     private lateinit var transitionObserver: DesktopTasksTransitionObserver
     private lateinit var shellInit: ShellInit
@@ -86,9 +88,18 @@
         whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
         shellInit = spy(ShellInit(testExecutor))
 
+        whenever(userRepositories.current).thenReturn(taskRepository)
+        whenever(userRepositories.getProfile(anyInt())).thenReturn(taskRepository)
+
         transitionObserver =
             DesktopTasksTransitionObserver(
-                context, taskRepository, transitions, shellTaskOrganizer, mixedHandler, shellInit
+                context,
+                userRepositories,
+                transitions,
+                shellTaskOrganizer,
+                mixedHandler,
+                backAnimationController,
+                shellInit
             )
     }
 
@@ -100,8 +111,7 @@
 
         transitionObserver.onTransitionReady(
             transition = mock(),
-            info =
-            createBackNavigationTransition(task),
+            info = createBackNavigationTransition(task),
             startTransaction = mock(),
             finishTransaction = mock(),
         )
@@ -112,14 +122,59 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+    fun backNavigation_withCloseTransitionNotLastTask_taskMinimized() {
+        val task = createTaskInfo(1)
+        val transition = mock<IBinder>()
+        whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(2)
+        whenever(taskRepository.isClosingTask(task.taskId)).thenReturn(false)
+        whenever(backAnimationController.latestTriggerBackTask).thenReturn(task.taskId)
+
+        transitionObserver.onTransitionReady(
+            transition = transition,
+            info = createBackNavigationTransition(task, TRANSIT_CLOSE),
+            startTransaction = mock(),
+            finishTransaction = mock(),
+        )
+
+        verify(taskRepository).minimizeTask(task.displayId, task.taskId)
+        val pendingTransition =
+            DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
+                transition, task.taskId, isLastTask = false)
+        verify(mixedHandler).addPendingMixedTransition(pendingTransition)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+    fun backNavigation_withCloseTransitionLastTask_taskMinimized() {
+        val task = createTaskInfo(1)
+        val transition = mock<IBinder>()
+        whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1)
+        whenever(taskRepository.isClosingTask(task.taskId)).thenReturn(false)
+        whenever(backAnimationController.latestTriggerBackTask).thenReturn(task.taskId)
+
+        transitionObserver.onTransitionReady(
+            transition = transition,
+            info = createBackNavigationTransition(task, TRANSIT_CLOSE, true),
+            startTransaction = mock(),
+            finishTransaction = mock(),
+        )
+
+        verify(taskRepository).minimizeTask(task.displayId, task.taskId)
+        val pendingTransition =
+            DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
+                transition, task.taskId, isLastTask = true)
+        verify(mixedHandler).addPendingMixedTransition(pendingTransition)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
     fun backNavigation_nullTaskInfo_taskNotMinimized() {
         val task = createTaskInfo(1)
         whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1)
 
         transitionObserver.onTransitionReady(
             transition = mock(),
-            info =
-            createBackNavigationTransition(null),
+            info = createBackNavigationTransition(null),
             startTransaction = mock(),
             finishTransaction = mock(),
         )
@@ -168,7 +223,7 @@
         val mockTransition = Mockito.mock(IBinder::class.java)
         val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
         val wallpaperToken = MockToken().token()
-        whenever(taskRepository.getVisibleTaskCount(task.displayId)).thenReturn(1)
+        whenever(taskRepository.getVisibleTaskCount(task.displayId)).thenReturn(0)
         whenever(taskRepository.wallpaperActivityToken).thenReturn(wallpaperToken)
 
         transitionObserver.onTransitionReady(
@@ -185,17 +240,27 @@
     }
 
     private fun createBackNavigationTransition(
-        task: RunningTaskInfo?
+        task: RunningTaskInfo?,
+        type: Int = TRANSIT_TO_BACK,
+        withWallpaper: Boolean = false,
     ): TransitionInfo {
-        return TransitionInfo(TRANSIT_TO_BACK, 0 /* flags */).apply {
+        return TransitionInfo(type, 0 /* flags */).apply {
             addChange(
                 Change(mock(), mock()).apply {
-                    mode = TRANSIT_TO_BACK
+                    mode = type
                     parent = null
                     taskInfo = task
                     flags = flags
-                }
-            )
+                })
+            if (withWallpaper) {
+                addChange(
+                    Change(mock(), mock()).apply {
+                        mode = TRANSIT_CLOSE
+                        parent = null
+                        taskInfo = createWallpaperTaskInfo()
+                        flags = flags
+                    })
+            }
         }
     }
 
@@ -210,14 +275,11 @@
                     parent = null
                     taskInfo = task
                     flags = flags
-                }
-            )
+                })
         }
     }
 
-    private fun createCloseTransition(
-        task: RunningTaskInfo?
-    ): TransitionInfo {
+    private fun createCloseTransition(task: RunningTaskInfo?): TransitionInfo {
         return TransitionInfo(TRANSIT_CLOSE, 0 /* flags */).apply {
             addChange(
                 Change(mock(), mock()).apply {
@@ -225,8 +287,7 @@
                     parent = null
                     taskInfo = task
                     flags = flags
-                }
-            )
+                })
         }
     }
 
@@ -238,8 +299,7 @@
         if (handlerClass == null) {
             Mockito.verify(transitions).startTransition(eq(type), arg.capture(), isNull())
         } else {
-            Mockito.verify(transitions)
-                .startTransition(eq(type), arg.capture(), isA(handlerClass))
+            Mockito.verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
         }
         return arg.value
     }
@@ -263,8 +323,15 @@
             displayId = DEFAULT_DISPLAY
             configuration.windowConfiguration.windowingMode = windowingMode
             token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java))
-            baseIntent = Intent().apply {
-                component = ComponentName("package", "component.name")
-            }
+            baseIntent = Intent().apply { component = ComponentName("package", "component.name") }
+        }
+
+    private fun createWallpaperTaskInfo() =
+        RunningTaskInfo().apply {
+            token = mock<WindowContainerToken>()
+            baseIntent =
+                Intent().apply {
+                    component = DesktopWallpaperActivity.wallpaperActivityComponent
+                }
         }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index 2134f3b..866d1b3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -27,78 +27,56 @@
 import com.android.wm.shell.MockToken
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 
-class DesktopTestHelpers {
-    companion object {
-        /** Create a task that has windowing mode set to [WINDOWING_MODE_FREEFORM] */
-        @JvmStatic
-        @JvmOverloads
-        fun createFreeformTask(
-                displayId: Int = DEFAULT_DISPLAY,
-                bounds: Rect? = null
-        ): RunningTaskInfo {
-            return TestRunningTaskInfoBuilder()
-                    .setDisplayId(displayId)
-                    .setToken(MockToken().token())
-                    .setActivityType(ACTIVITY_TYPE_STANDARD)
-                    .setWindowingMode(WINDOWING_MODE_FREEFORM)
-                    .setLastActiveTime(100)
-                    .apply { bounds?.let { setBounds(it) }}
-                    .build()
-        }
+object DesktopTestHelpers {
+    /** Create a task that has windowing mode set to [WINDOWING_MODE_FREEFORM] */
+    fun createFreeformTask(
+        displayId: Int = DEFAULT_DISPLAY,
+        bounds: Rect? = null,
+    ): RunningTaskInfo =
+        TestRunningTaskInfoBuilder()
+            .setDisplayId(displayId)
+            .setToken(MockToken().token())
+            .setActivityType(ACTIVITY_TYPE_STANDARD)
+            .setWindowingMode(WINDOWING_MODE_FREEFORM)
+            .setLastActiveTime(100)
+            .apply { bounds?.let { setBounds(it) } }
+            .build()
 
-        /** Create a task builder that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */
-        @JvmStatic
-        @JvmOverloads
-        fun createFullscreenTaskBuilder(displayId: Int = DEFAULT_DISPLAY): TestRunningTaskInfoBuilder {
-            return TestRunningTaskInfoBuilder()
-                .setDisplayId(displayId)
-                .setToken(MockToken().token())
-                .setActivityType(ACTIVITY_TYPE_STANDARD)
-                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
-                .setLastActiveTime(100)
-        }
+    fun createFullscreenTaskBuilder(displayId: Int = DEFAULT_DISPLAY): TestRunningTaskInfoBuilder =
+        TestRunningTaskInfoBuilder()
+            .setDisplayId(displayId)
+            .setToken(MockToken().token())
+            .setActivityType(ACTIVITY_TYPE_STANDARD)
+            .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+            .setLastActiveTime(100)
 
-        /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */
-        @JvmStatic
-        @JvmOverloads
-        fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
-            return createFullscreenTaskBuilder(displayId).build()
-        }
+    /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */
+    fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo =
+        createFullscreenTaskBuilder(displayId).build()
 
-        /** Create a task that has windowing mode set to [WINDOWING_MODE_MULTI_WINDOW] */
-        @JvmStatic
-        @JvmOverloads
-        fun createSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
-            return TestRunningTaskInfoBuilder()
-                .setDisplayId(displayId)
-                .setToken(MockToken().token())
-                .setActivityType(ACTIVITY_TYPE_STANDARD)
-                .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
-                .setLastActiveTime(100)
-                .build()
-        }
+    /** Create a task that has windowing mode set to [WINDOWING_MODE_MULTI_WINDOW] */
+    fun createSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo =
+        TestRunningTaskInfoBuilder()
+            .setDisplayId(displayId)
+            .setToken(MockToken().token())
+            .setActivityType(ACTIVITY_TYPE_STANDARD)
+            .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+            .setLastActiveTime(100)
+            .build()
 
-        /** Create a new home task */
-        @JvmStatic
-        @JvmOverloads
-        fun createHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
-            return TestRunningTaskInfoBuilder()
-                    .setDisplayId(displayId)
-                    .setToken(MockToken().token())
-                    .setActivityType(ACTIVITY_TYPE_HOME)
-                    .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
-                    .setLastActiveTime(100)
-                    .build()
-        }
+    fun createHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo =
+        TestRunningTaskInfoBuilder()
+            .setDisplayId(displayId)
+            .setToken(MockToken().token())
+            .setActivityType(ACTIVITY_TYPE_HOME)
+            .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+            .setLastActiveTime(100)
+            .build()
 
-        /** Create a new System Modal task, i.e. a task with a single transparent activity. */
-        @JvmStatic
-        @JvmOverloads
-        fun createSystemModalTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
-            return createFullscreenTaskBuilder(displayId)
-                .setTopActivityTransparent(true)
-                .setNumActivities(1)
-                .build()
-        }
-    }
-}
\ No newline at end of file
+    /** Create a new System Modal task, i.e. a task with a single transparent activity. */
+    fun createSystemModalTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo =
+        createFullscreenTaskBuilder(displayId)
+            .setTopActivityTransparent(true)
+            .setNumActivities(1)
+            .build()
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
new file mode 100644
index 0000000..5767df4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.app.ActivityManager
+import android.content.pm.UserInfo
+import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_HSUM
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
+import com.android.wm.shell.sysui.ShellInit
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.spy
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
+class DesktopUserRepositoriesTest : ShellTestCase() {
+    @get:Rule val setFlagsRule = SetFlagsRule()
+
+    private lateinit var userRepositories: DesktopUserRepositories
+    private lateinit var shellInit: ShellInit
+    private lateinit var datastoreScope: CoroutineScope
+    private lateinit var mockitoSession: StaticMockitoSession
+
+    private val testExecutor = mock<ShellExecutor>()
+    private val persistentRepository = mock<DesktopPersistentRepository>()
+    private val repositoryInitializer = mock<DesktopRepositoryInitializer>()
+    private val userManager = mock<UserManager>()
+
+    @Before
+    fun setUp() {
+        Dispatchers.setMain(StandardTestDispatcher())
+        mockitoSession =
+            mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .spyStatic(ActivityManager::class.java)
+                .startMocking()
+        doReturn(USER_ID_1).`when` { ActivityManager.getCurrentUser() }
+
+        datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
+        shellInit = spy(ShellInit(testExecutor))
+
+        val profiles: MutableList<UserInfo> = mutableListOf(
+            UserInfo(USER_ID_1, "User 1", 0),
+            UserInfo(PROFILE_ID_2, "Profile 2", 0))
+        whenever(userManager.getProfiles(USER_ID_1)).thenReturn(profiles)
+
+        userRepositories = DesktopUserRepositories(
+            context, shellInit, persistentRepository, repositoryInitializer, datastoreScope,
+                userManager)
+    }
+
+    @After
+    fun tearDown() {
+        mockitoSession.finishMocking()
+        datastoreScope.cancel()
+    }
+
+    @Test
+    fun getCurrent_returnsUserId() {
+        val desktopRepository: DesktopRepository = userRepositories.current
+
+        assertThat(desktopRepository.userId).isEqualTo(USER_ID_1)
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
+    fun getProfile_flagEnabled_returnsProfileGroupId() {
+        val desktopRepository: DesktopRepository = userRepositories.getProfile(PROFILE_ID_2)
+
+        assertThat(desktopRepository.userId).isEqualTo(USER_ID_1)
+    }
+
+    @Test
+    @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
+    fun getProfile_flagDisabled_returnsProfileId() {
+        val desktopRepository: DesktopRepository = userRepositories.getProfile(PROFILE_ID_2)
+
+        assertThat(desktopRepository.userId).isEqualTo(PROFILE_ID_2)
+    }
+
+    private companion object {
+        const val USER_ID_1 = 7
+        const val PROFILE_ID_2 = 5
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 79e16fe..13528b9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -607,7 +607,7 @@
                 )
             )
             .thenReturn(token)
-        handler.startDragToDesktopTransition(task.taskId, dragAnimator)
+        handler.startDragToDesktopTransition(task, dragAnimator)
         return token
     }
 
@@ -661,6 +661,7 @@
         return TestRunningTaskInfoBuilder()
             .setActivityType(if (isHome) ACTIVITY_TYPE_HOME else ACTIVITY_TYPE_STANDARD)
             .setWindowingMode(windowingMode)
+            .setUserId(mContext.userId)
             .build()
             .also {
                 whenever(splitScreenController.isTaskInSplitScreen(it.taskId))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
index 79d9083..b9d7bbf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
@@ -25,10 +25,11 @@
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTaskBuilder
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSystemModalTask
 import com.android.wm.shell.desktopmode.DesktopRepository
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTaskBuilder
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSystemModalTask
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.TransitionInfoBuilder
 import com.android.wm.shell.transition.Transitions
@@ -49,6 +50,7 @@
     private val animExecutor = mock<ShellExecutor>()
     private val shellInit = mock<ShellInit>()
     private val transitions = mock<Transitions>()
+    private val desktopUserRepositories = mock<DesktopUserRepositories>()
     private val desktopRepository = mock<DesktopRepository>()
     private val startT = mock<SurfaceControl.Transaction>()
     private val finishT = mock<SurfaceControl.Transaction>()
@@ -58,6 +60,7 @@
     @Before
     fun setUp() {
         // Simulate having one Desktop task so that we see Desktop Mode as active
+        whenever(desktopUserRepositories.current).thenReturn(desktopRepository)
         whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1)
         transitionHandler = createTransitionHandler()
     }
@@ -69,7 +72,7 @@
             animExecutor,
             shellInit,
             transitions,
-            desktopRepository,
+            desktopUserRepositories,
         )
 
     @Test
@@ -79,7 +82,7 @@
 
     @Test
     fun startAnimation_desktopNotActive_doesNotAnimate() {
-        whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1)
+        whenever(desktopUserRepositories.current.getVisibleTaskCount(anyInt())).thenReturn(1)
         val info =
             TransitionInfoBuilder(TRANSIT_OPEN)
                 .addChange(TRANSIT_OPEN, createSystemModalTask())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
index d94186c..9c00c0c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
@@ -175,13 +175,13 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
-  fun init_educationViewedAlready_shouldNotCallShowEducationTooltip() =
+  fun init_appHandleHintViewedAlready_shouldNotCallShowEducationTooltip() =
       testScope.runTest {
-        // App handle is visible but education has been viewed before. Should not show education
-        // tooltip.
-        // Mark education viewed.
+        // App handle is visible but app handle hint has been viewed before,
+        // should not show education tooltip.
+        // Mark app handle hint viewed.
         testDataStoreFlow.value =
-            createWindowingEducationProto(educationViewedTimestampMillis = 123L)
+            createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L)
         setShouldShowAppHandleEducation(true)
 
         // Simulate app handle visible.
@@ -194,13 +194,14 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
-  fun overridePrerequisite_educationViewedAlready_shouldCallShowEducationTooltip() =
+  fun overridePrerequisite_appHandleHintViewedAlready_shouldCallShowEducationTooltip() =
       testScope.runTest {
-        // App handle is visible but education has been viewed before. But as we are overriding
-        // prerequisite conditions, we should show education tooltip.
-        // Mark education viewed.
+        // App handle is visible but app handle hint has been viewed before.
+        // But as we are overriding prerequisite conditions, we should show app
+        // handle tooltip.
+        // Mark app handle hint viewed.
         testDataStoreFlow.value =
-            createWindowingEducationProto(educationViewedTimestampMillis = 123L)
+            createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L)
         val systemPropertiesKey =
             "persist.desktop_windowing_app_handle_education_override_conditions"
         whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean()))
@@ -217,7 +218,7 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
-  fun init_appHandleExpanded_shouldMarkFeatureViewed() =
+  fun init_appHandleExpanded_shouldMarkAppHandleHintUsed() =
       testScope.runTest {
         setShouldShowAppHandleEducation(false)
 
@@ -226,12 +227,12 @@
         // Wait for some time before verifying
         waitForBufferDelay()
 
-        verify(mockDataStoreRepository, times(1)).updateFeatureUsedTimestampMillis(eq(true))
+        verify(mockDataStoreRepository, times(1)).updateAppHandleHintUsedTimestampMillis(eq(true))
       }
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
-  fun init_showFirstTooltip_shouldMarkEducationViewed() =
+  fun init_showFirstTooltip_shouldMarkAppHandleHintViewed() =
       testScope.runTest {
         // App handle is visible. Should show education tooltip.
         setShouldShowAppHandleEducation(true)
@@ -241,7 +242,7 @@
         // Wait for first tooltip to showup.
         waitForBufferDelay()
 
-        verify(mockDataStoreRepository, times(1)).updateEducationViewedTimestampMillis(eq(true))
+        verify(mockDataStoreRepository, times(1)).updateAppHandleHintViewedTimestampMillis(eq(true))
       }
 
   @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt
index c286544..963890d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt
@@ -81,8 +81,8 @@
       runTest(StandardTestDispatcher()) {
         val windowingEducationProto =
             createWindowingEducationProto(
-                educationViewedTimestampMillis = 123L,
-                featureUsedTimestampMillis = 124L,
+                appHandleHintViewedTimestampMillis = 123L,
+                appHandleHintUsedTimestampMillis = 124L,
                 appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2),
                 appUsageStatsLastUpdateTimestampMillis = 125L)
         testDatastore.updateData { windowingEducationProto }
@@ -110,20 +110,20 @@
       }
 
   @Test
-  fun updateEducationViewedTimestampMillis_updatesDatastoreProto() =
+  fun updateAppHandleHintViewedTimestampMillis_updatesDatastoreProto() =
       runTest(StandardTestDispatcher()) {
-        datastoreRepository.updateEducationViewedTimestampMillis(true)
+        datastoreRepository.updateAppHandleHintViewedTimestampMillis(true)
 
-        val result = testDatastore.data.first().hasEducationViewedTimestampMillis()
+        val result = testDatastore.data.first().hasAppHandleHintViewedTimestampMillis()
         assertThat(result).isEqualTo(true)
       }
 
   @Test
-  fun updateFeatureUsedTimestampMillis_updatesDatastoreProto() =
+  fun updateAppHandleHintUsedTimestampMillis_updatesDatastoreProto() =
       runTest(StandardTestDispatcher()) {
-        datastoreRepository.updateFeatureUsedTimestampMillis(true)
+        datastoreRepository.updateAppHandleHintUsedTimestampMillis(true)
 
-        val result = testDatastore.data.first().hasFeatureUsedTimestampMillis()
+        val result = testDatastore.data.first().hasAppHandleHintUsedTimestampMillis()
         assertThat(result).isEqualTo(true)
       }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt
index a3e74e8..e5edd69 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt
@@ -134,12 +134,12 @@
   }
 
   @Test
-  fun shouldShowAppHandleEducation_educationViewedBefore_returnsFalse() = runTest {
-    // Education has been viewed before, hence #shouldShowAppHandleEducation should return false
+  fun shouldShowAppHandleEducation_appHandleHintViewedBefore_returnsFalse() = runTest {
+    // App handle hint has been viewed before, hence #shouldShowAppHandleEducation should return false
     val windowingEducationProto =
         createWindowingEducationProto(
             appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
-            educationViewedTimestampMillis = 123L,
+            appHandleHintViewedTimestampMillis = 123L,
             appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE)
     `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
 
@@ -149,12 +149,12 @@
   }
 
   @Test
-  fun shouldShowAppHandleEducation_featureUsedBefore_returnsFalse() = runTest {
-    // Feature has been used before, hence #shouldShowAppHandleEducation should return false
+  fun shouldShowAppHandleEducation_appHandleHintUsedBefore_returnsFalse() = runTest {
+    // App handle hint has been used before, hence #shouldShowAppHandleEducation should return false
     val windowingEducationProto =
         createWindowingEducationProto(
             appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
-            featureUsedTimestampMillis = 123L,
+            appHandleHintUsedTimestampMillis = 123L,
             appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE)
     `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
index 8495580..4f7e80c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
@@ -112,7 +112,8 @@
             datastoreRepository.addOrUpdateDesktop(
                 visibleTasks = visibleTasks,
                 minimizedTasks = minimizedTasks,
-                freeformTasksInZOrder = freeformTasksInZOrder)
+                freeformTasksInZOrder = freeformTasksInZOrder,
+                userId = DEFAULT_USER_ID)
 
             val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
             assertThat(actualDesktop?.tasksByTaskIdMap).hasSize(2)
@@ -135,7 +136,8 @@
             datastoreRepository.addOrUpdateDesktop(
                 visibleTasks = visibleTasks,
                 minimizedTasks = minimizedTasks,
-                freeformTasksInZOrder = freeformTasksInZOrder)
+                freeformTasksInZOrder = freeformTasksInZOrder,
+                userId = DEFAULT_USER_ID)
 
             val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
             assertThat(actualDesktop?.tasksByTaskIdMap?.get(task.taskId)?.desktopTaskState)
@@ -158,7 +160,8 @@
             datastoreRepository.addOrUpdateDesktop(
                 visibleTasks = visibleTasks,
                 minimizedTasks = minimizedTasks,
-                freeformTasksInZOrder = freeformTasksInZOrder)
+                freeformTasksInZOrder = freeformTasksInZOrder,
+                userId = DEFAULT_USER_ID)
 
             val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
             assertThat(actualDesktop?.tasksByTaskIdMap).isEmpty()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
index 9753429..1c88a29 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
@@ -16,14 +16,17 @@
 
 package com.android.wm.shell.desktopmode.persistence
 
+import android.os.UserManager
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.view.Display.DEFAULT_DISPLAY
 import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_HSUM
 import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.common.ShellExecutor
-import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
 import com.android.wm.shell.sysui.ShellInit
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
@@ -36,26 +39,30 @@
 import kotlinx.coroutines.test.setMain
 import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.spy
-import org.mockito.kotlin.any
 import org.mockito.kotlin.mock
-import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
+
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @ExperimentalCoroutinesApi
 class DesktopRepositoryInitializerTest : ShellTestCase() {
 
+    @JvmField
+    @Rule
+    val setFlagsRule = SetFlagsRule()
+
     private lateinit var repositoryInitializer: DesktopRepositoryInitializer
     private lateinit var shellInit: ShellInit
     private lateinit var datastoreScope: CoroutineScope
 
-    private lateinit var desktopRepository: DesktopRepository
+    private lateinit var desktopUserRepositories: DesktopUserRepositories
     private val persistentRepository = mock<DesktopPersistentRepository>()
+    private val userManager = mock<UserManager>()
     private val testExecutor = mock<ShellExecutor>()
 
     @Before
@@ -65,55 +72,193 @@
         datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
         repositoryInitializer =
             DesktopRepositoryInitializerImpl(context, persistentRepository, datastoreScope)
-        desktopRepository =
-            DesktopRepository(
-                context, shellInit, persistentRepository, repositoryInitializer, datastoreScope)
+        desktopUserRepositories =
+            DesktopUserRepositories(
+                context, shellInit, persistentRepository, repositoryInitializer, datastoreScope,
+                userManager
+            )
     }
 
     @Test
-    @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
-    fun initWithPersistence_multipleTasks_addedCorrectly() =
+    @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
+    fun initWithPersistence_multipleUsers_addedCorrectly() =
         runTest(StandardTestDispatcher()) {
-            val freeformTasksInZOrder = listOf(1, 2, 3)
-            whenever(persistentRepository.readDesktop(any(), any()))
-                .thenReturn(
-                    Desktop.newBuilder()
-                        .setDesktopId(1)
-                        .addAllZOrderedTasks(freeformTasksInZOrder)
-                        .putTasksByTaskId(
-                            1,
-                            DesktopTask.newBuilder()
-                                .setTaskId(1)
-                                .setDesktopTaskState(DesktopTaskState.VISIBLE)
-                                .build())
-                        .putTasksByTaskId(
-                            2,
-                            DesktopTask.newBuilder()
-                                .setTaskId(2)
-                                .setDesktopTaskState(DesktopTaskState.VISIBLE)
-                                .build())
-                        .putTasksByTaskId(
-                            3,
-                            DesktopTask.newBuilder()
-                                .setTaskId(3)
-                                .setDesktopTaskState(DesktopTaskState.MINIMIZED)
-                                .build())
-                        .build())
+            whenever(persistentRepository.getUserDesktopRepositoryMap()).thenReturn(
+                mapOf(
+                    USER_ID_1 to desktopRepositoryState1,
+                    USER_ID_2 to desktopRepositoryState2
+                )
+            )
+            whenever(persistentRepository.getDesktopRepositoryState(USER_ID_1))
+                .thenReturn(desktopRepositoryState1)
+            whenever(persistentRepository.getDesktopRepositoryState(USER_ID_2))
+                .thenReturn(desktopRepositoryState2)
+            whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1))
+                .thenReturn(desktop1)
+            whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2))
+                .thenReturn(desktop2)
+            whenever(persistentRepository.readDesktop(USER_ID_2, DESKTOP_ID_3))
+                .thenReturn(desktop3)
 
-            repositoryInitializer.initialize(desktopRepository)
+            repositoryInitializer.initialize(desktopUserRepositories)
 
-            verify(persistentRepository).readDesktop(any(), any())
-            assertThat(desktopRepository.getActiveTasks(DEFAULT_DISPLAY))
-                .containsExactly(1, 2, 3)
+            // Desktop Repository currently returns all tasks across desktops for a specific user
+            // since the repository currently doesn't handle desktops. This test logic should be updated
+            // once the repository handles multiple desktops.
+            assertThat(
+                desktopUserRepositories.getProfile(USER_ID_1)
+                    .getActiveTasks(DEFAULT_DISPLAY)
+            )
+                .containsExactly(1, 3, 4, 5)
                 .inOrder()
-            assertThat(desktopRepository.getExpandedTasksOrdered(DEFAULT_DISPLAY))
-                .containsExactly(1, 2)
+            assertThat(
+                desktopUserRepositories.getProfile(USER_ID_1)
+                    .getExpandedTasksOrdered(DEFAULT_DISPLAY)
+            )
+                .containsExactly(5, 1)
                 .inOrder()
-            assertThat(desktopRepository.getMinimizedTasks(DEFAULT_DISPLAY)).containsExactly(3)
+            assertThat(
+                desktopUserRepositories.getProfile(USER_ID_1)
+                    .getMinimizedTasks(DEFAULT_DISPLAY)
+            )
+                .containsExactly(3, 4)
+                .inOrder()
+
+            assertThat(
+                desktopUserRepositories.getProfile(USER_ID_2)
+                    .getActiveTasks(DEFAULT_DISPLAY)
+            )
+                .containsExactly(7, 8)
+                .inOrder()
+            assertThat(
+                desktopUserRepositories.getProfile(USER_ID_2)
+                    .getExpandedTasksOrdered(DEFAULT_DISPLAY)
+            )
+                .contains(7)
+            assertThat(
+                desktopUserRepositories.getProfile(USER_ID_2)
+                    .getMinimizedTasks(DEFAULT_DISPLAY)
+            ).containsExactly(8)
+        }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+    fun initWithPersistence_singleUser_addedCorrectly() =
+        runTest(StandardTestDispatcher()) {
+            whenever(persistentRepository.getUserDesktopRepositoryMap()).thenReturn(
+                mapOf(
+                    USER_ID_1 to desktopRepositoryState1,
+                )
+            )
+            whenever(persistentRepository.getDesktopRepositoryState(USER_ID_1))
+                .thenReturn(desktopRepositoryState1)
+            whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1))
+                .thenReturn(desktop1)
+            whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2))
+                .thenReturn(desktop2)
+
+            repositoryInitializer.initialize(desktopUserRepositories)
+
+            // Desktop Repository currently returns all tasks across desktops for a specific user
+            // since the repository currently doesn't handle desktops. This test logic should be updated
+            // once the repository handles multiple desktops.
+            assertThat(
+                desktopUserRepositories.getProfile(USER_ID_1)
+                    .getActiveTasks(DEFAULT_DISPLAY)
+            )
+                .containsExactly(1, 3, 4, 5)
+                .inOrder()
+            assertThat(
+                desktopUserRepositories.getProfile(USER_ID_1)
+                    .getExpandedTasksOrdered(DEFAULT_DISPLAY)
+            )
+                .containsExactly(5, 1)
+                .inOrder()
+            assertThat(
+                desktopUserRepositories.getProfile(USER_ID_1)
+                    .getMinimizedTasks(DEFAULT_DISPLAY)
+            )
+                .containsExactly(3, 4)
+                .inOrder()
         }
 
     @After
     fun tearDown() {
         datastoreScope.cancel()
     }
+
+    private companion object {
+        const val USER_ID_1 = 5
+        const val USER_ID_2 = 6
+        const val DESKTOP_ID_1 = 2
+        const val DESKTOP_ID_2 = 3
+        const val DESKTOP_ID_3 = 4
+
+        val freeformTasksInZOrder1 = listOf(1, 3)
+        val desktop1: Desktop = Desktop.newBuilder()
+            .setDesktopId(DESKTOP_ID_1)
+            .addAllZOrderedTasks(freeformTasksInZOrder1)
+            .putTasksByTaskId(
+                1,
+                DesktopTask.newBuilder()
+                    .setTaskId(1)
+                    .setDesktopTaskState(DesktopTaskState.VISIBLE)
+                    .build()
+            )
+            .putTasksByTaskId(
+                3,
+                DesktopTask.newBuilder()
+                    .setTaskId(3)
+                    .setDesktopTaskState(DesktopTaskState.MINIMIZED)
+                    .build()
+            )
+            .build()
+
+        val freeformTasksInZOrder2 = listOf(4, 5)
+        val desktop2: Desktop = Desktop.newBuilder()
+            .setDesktopId(DESKTOP_ID_2)
+            .addAllZOrderedTasks(freeformTasksInZOrder2)
+            .putTasksByTaskId(
+                4,
+                DesktopTask.newBuilder()
+                    .setTaskId(4)
+                    .setDesktopTaskState(DesktopTaskState.MINIMIZED)
+                    .build()
+            )
+            .putTasksByTaskId(
+                5,
+                DesktopTask.newBuilder()
+                    .setTaskId(5)
+                    .setDesktopTaskState(DesktopTaskState.VISIBLE)
+                    .build()
+            )
+            .build()
+
+        val freeformTasksInZOrder3 = listOf(7, 8)
+        val desktop3: Desktop = Desktop.newBuilder()
+            .setDesktopId(DESKTOP_ID_3)
+            .addAllZOrderedTasks(freeformTasksInZOrder3)
+            .putTasksByTaskId(
+                7,
+                DesktopTask.newBuilder()
+                    .setTaskId(7)
+                    .setDesktopTaskState(DesktopTaskState.VISIBLE)
+                    .build()
+            )
+            .putTasksByTaskId(
+                8,
+                DesktopTask.newBuilder()
+                    .setTaskId(8)
+                    .setDesktopTaskState(DesktopTaskState.MINIMIZED)
+                    .build()
+            )
+            .build()
+        val desktopRepositoryState1: DesktopRepositoryState = DesktopRepositoryState.newBuilder()
+            .putDesktop(DESKTOP_ID_1, desktop1)
+            .putDesktop(DESKTOP_ID_2, desktop2)
+            .build()
+        val desktopRepositoryState2: DesktopRepositoryState = DesktopRepositoryState.newBuilder()
+            .putDesktop(DESKTOP_ID_3, desktop3)
+            .build()
+    }
 }
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 b504a88..b8629b2 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
@@ -27,6 +27,7 @@
 import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -47,6 +48,7 @@
 import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -81,6 +83,8 @@
     @Mock
     private SurfaceControl mMockSurfaceControl;
     @Mock
+    private DesktopUserRepositories mDesktopUserRepositories;
+    @Mock
     private DesktopRepository mDesktopRepository;
     @Mock
     private DesktopTasksController mDesktopTasksController;
@@ -101,13 +105,14 @@
                         .mockStatic(DesktopModeStatus.class)
                         .startMocking();
         doReturn(true).when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
-
+        when(mDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
+        when(mDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository);
         mFreeformTaskListener =
                 new FreeformTaskListener(
                         mContext,
                         mShellInit,
                         mTaskOrganizer,
-                        Optional.of(mDesktopRepository),
+                        Optional.of(mDesktopUserRepositories),
                         Optional.of(mDesktopTasksController),
                         mLaunchAdjacentController,
                         mWindowDecorViewModel,
@@ -123,7 +128,8 @@
 
         mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
 
-        verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible = true);
+        verify(mDesktopUserRepositories.getCurrent())
+                .addTask(task.displayId, task.taskId, task.isVisible = true);
     }
 
     @Test
@@ -135,7 +141,8 @@
 
         mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
 
-        verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible);
+        verify(mDesktopUserRepositories.getCurrent())
+                .addTask(task.displayId, task.taskId, task.isVisible);
     }
 
     @Test
@@ -147,7 +154,8 @@
 
         mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
 
-        verify(mDesktopRepository, never()).addTask(task.displayId, task.taskId, task.isVisible);
+        verify(mDesktopUserRepositories.getCurrent(), never())
+                .addTask(task.displayId, task.taskId, task.isVisible);
     }
 
     @Test
@@ -158,7 +166,8 @@
 
         mFreeformTaskListener.onFocusTaskChanged(task);
 
-        verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible);
+        verify(mDesktopUserRepositories.getCurrent())
+                .addTask(task.displayId, task.taskId, task.isVisible);
     }
 
     @Test
@@ -171,7 +180,7 @@
 
         mFreeformTaskListener.onFocusTaskChanged(fullscreenTask);
 
-        verify(mDesktopRepository, never())
+        verify(mDesktopUserRepositories.getCurrent(), never())
                 .addTask(fullscreenTask.displayId, fullscreenTask.taskId, fullscreenTask.isVisible);
     }
 
@@ -203,10 +212,11 @@
     @Test
     @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
     @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
-    public void onTaskVanished_nonClosingTask_noTransitionObservers_isMinimized() {
+    public void onTaskVanished_minimizedTask_noTransitionObservers_isNotRemoved() {
         ActivityManager.RunningTaskInfo task =
                 new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         task.isVisible = true;
+        when(mDesktopRepository.isMinimizedTask(task.taskId)).thenReturn(true);
 
         mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
 
@@ -214,7 +224,8 @@
         task.displayId = INVALID_DISPLAY;
         mFreeformTaskListener.onTaskVanished(task);
 
-        verify(mDesktopRepository).minimizeTask(task.displayId, task.taskId);
+        verify(mDesktopUserRepositories.getCurrent(), never()).removeFreeformTask(task.displayId,
+                task.taskId);
     }
 
     @Test
@@ -227,14 +238,17 @@
 
         mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
 
-        when(mDesktopRepository.isClosingTask(task.taskId)).thenReturn(true);
+        when(mDesktopUserRepositories.getCurrent()
+                .isClosingTask(task.taskId)).thenReturn(true);
         task.isVisible = false;
         task.displayId = INVALID_DISPLAY;
         mFreeformTaskListener.onTaskVanished(task);
 
-        verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId);
-        verify(mDesktopRepository).removeClosingTask(task.taskId);
-        verify(mDesktopRepository).removeFreeformTask(task.displayId, task.taskId);
+        verify(mDesktopUserRepositories.getCurrent(), never())
+                .minimizeTask(task.displayId, task.taskId);
+        verify(mDesktopUserRepositories.getCurrent()).removeClosingTask(task.taskId);
+        verify(mDesktopUserRepositories.getCurrent())
+                .removeFreeformTask(task.displayId, task.taskId);
     }
 
     @Test
@@ -246,9 +260,12 @@
 
         mFreeformTaskListener.onTaskVanished(task);
 
-        verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId);
-        verify(mDesktopRepository, never()).removeClosingTask(task.taskId);
-        verify(mDesktopRepository, never()).removeFreeformTask(task.displayId, task.taskId);
+        verify(mDesktopUserRepositories.getCurrent(), never())
+                .minimizeTask(task.displayId, task.taskId);
+        verify(mDesktopUserRepositories.getCurrent(), never())
+                .removeClosingTask(task.taskId);
+        verify(mDesktopUserRepositories.getCurrent(), never())
+                .removeFreeformTask(task.displayId, task.taskId);
     }
 
     @Test
@@ -274,7 +291,8 @@
         mFreeformTaskListener.onTaskInfoChanged(task);
 
         verify(mTaskChangeListener, never()).onTaskChanging(any());
-        verify(mDesktopRepository).updateTask(task.displayId, task.taskId, task.isVisible);
+        verify(mDesktopUserRepositories.getCurrent())
+                .updateTask(task.displayId, task.taskId, task.isVisible);
     }
 
     @Test
@@ -289,7 +307,7 @@
         mFreeformTaskListener.onTaskInfoChanged(task);
 
         verify(mTaskChangeListener).onNonTransitionTaskChanging(any());
-        verify(mDesktopRepository, never())
+        verify(mDesktopUserRepositories.getCurrent(), never())
                 .updateTask(task.displayId, task.taskId, task.isVisible);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 7d063a0..256ed41 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -48,7 +48,6 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.shared.ShellSharedConstants;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
@@ -179,7 +178,7 @@
     @Test
     public void testControllerRegisteresExternalInterface() {
         verify(mMockShellController, times(1)).addExternalInterface(
-                eq(ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED), any(), any());
+                eq(IOneHanded.DESCRIPTOR), any(), any());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 289fd2d..2eb2c3b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -65,7 +65,7 @@
 import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
@@ -103,7 +103,7 @@
     @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
     @Mock private PipUiEventLogger mMockPipUiEventLogger;
     @Mock private Optional<SplitScreenController> mMockOptionalSplitScreen;
-    @Mock private Optional<DesktopRepository> mMockOptionalDesktopRepository;
+    @Mock private Optional<DesktopUserRepositories> mMockOptionalDesktopUserRepositories;
     @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
     @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
     @Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
@@ -136,7 +136,7 @@
                 mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
                 mMockPipParamsChangedForwarder, mMockOptionalSplitScreen,
                 Optional.empty() /* pipPerfHintControllerOptional */,
-                mMockOptionalDesktopRepository, mRootTaskDisplayAreaOrganizer,
+                mMockOptionalDesktopUserRepositories, mRootTaskDisplayAreaOrganizer,
                 mMockDisplayController, mMockPipUiEventLogger, mMockShellTaskOrganizer,
                 mMainExecutor);
         mMainExecutor.flushAll();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index b123f4d..5ef934c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -58,6 +58,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.IPip;
 import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
 import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -71,7 +72,6 @@
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.shared.ShellSharedConstants;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
@@ -178,7 +178,7 @@
     @Test
     public void instantiatePipController_registerExternalInterface() {
         verify(mShellController, times(1)).addExternalInterface(
-                eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), eq(mPipController));
+                eq(IPip.DESCRIPTOR), any(), eq(mPipController));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
index cab6252..3fe8c10 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -42,8 +42,10 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
@@ -56,6 +58,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Optional;
+
 /**
  * Unit test against {@link PipScheduler}
  */
@@ -79,6 +83,8 @@
     @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
     @Mock private SurfaceControl.Transaction mMockTransaction;
     @Mock private PipAlphaAnimator mMockAlphaAnimator;
+    @Mock private Optional<DesktopUserRepositories> mMockOptionalDesktopUserRepositories;
+    @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
 
     @Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
     @Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor;
@@ -96,7 +102,8 @@
                 .thenReturn(mMockTransaction);
 
         mPipScheduler = new PipScheduler(mMockContext, mMockPipBoundsState, mMockMainExecutor,
-                mMockPipTransitionState);
+                mMockPipTransitionState, mMockOptionalDesktopUserRepositories,
+                mRootTaskDisplayAreaOrganizer);
         mPipScheduler.setPipTransitionController(mMockPipTransitionController);
         mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory);
         mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, tx, direction) ->
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 12c3978..c6835b7 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
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.launcher3.Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL;
 import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
 
@@ -51,6 +52,7 @@
 import android.app.KeyguardManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -72,8 +74,9 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
 import com.android.wm.shell.shared.GroupedTaskInfo;
-import com.android.wm.shell.shared.ShellSharedConstants;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.shared.split.SplitBounds;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -110,8 +113,6 @@
     @Mock
     private ShellCommandHandler mShellCommandHandler;
     @Mock
-    private DesktopRepository mDesktopRepository;
-    @Mock
     private ActivityTaskManager mActivityTaskManager;
     @Mock
     private DisplayInsetsController mDisplayInsetsController;
@@ -119,6 +120,10 @@
     private IRecentTasksListener mRecentTasksListener;
     @Mock
     private TaskStackTransitionObserver mTaskStackTransitionObserver;
+    @Mock
+    private DesktopUserRepositories mDesktopUserRepositories;
+    @Mock
+    private DesktopRepository mDesktopRepository;
 
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -139,6 +144,8 @@
                 .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
 
         mMainExecutor = new TestShellExecutor();
+        when(mDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
+        when(mDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository);
         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
         when(mContext.getSystemService(KeyguardManager.class))
                 .thenReturn(mock(KeyguardManager.class));
@@ -147,7 +154,7 @@
                 mDisplayInsetsController, mMainExecutor));
         mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
                 mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
-                Optional.of(mDesktopRepository), mTaskStackTransitionObserver,
+                Optional.of(mDesktopUserRepositories), mTaskStackTransitionObserver,
                 mMainExecutor);
         mRecentTasksController = spy(mRecentTasksControllerReal);
         mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
@@ -175,7 +182,13 @@
     @Test
     public void instantiateController_addExternalInterface() {
         verify(mShellController, times(1)).addExternalInterface(
-                eq(ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS), any(), any());
+                eq(IRecentTasks.DESCRIPTOR), any(), any());
+    }
+
+    @Test
+    public void instantiateController_initializesRepository() {
+        verify(mDesktopUserRepositories, times(1)).getCurrent();
+        verify(mDesktopRepository, times(1)).addActiveTaskListener(any());
     }
 
     @Test
@@ -237,6 +250,19 @@
                 t3.taskId, -1);
     }
 
+    @EnableFlags(FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
+    @Test
+    public void testGetRecentTasks_removesDesktopWallpaperActivity() {
+        RecentTaskInfo t1 = makeTaskInfo(1);
+        RecentTaskInfo desktopWallpaperTaskInfo = makeDesktopWallpaperTaskInfo(2);
+        RecentTaskInfo t3 = makeTaskInfo(3);
+        setRawList(t1, desktopWallpaperTaskInfo, t3);
+
+        ArrayList<GroupedTaskInfo> recentTasks =
+                mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+        assertGroupedTasksListEquals(recentTasks, t1.taskId, -1, t3.taskId, -1);
+    }
+
     @Test
     public void testGetRecentTasks_withPairs() {
         RecentTaskInfo t1 = makeTaskInfo(1);
@@ -307,8 +333,8 @@
         RecentTaskInfo t4 = makeTaskInfo(4);
         setRawList(t1, t2, t3, t4);
 
-        when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
-        when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+        when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true);
+        when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true);
 
         ArrayList<GroupedTaskInfo> recentTasks =
                 mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -346,8 +372,8 @@
                 new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_2_50_50);
         mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, pair1Bounds);
 
-        when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
-        when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
+        when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true);
+        when(mDesktopUserRepositories.getCurrent().isActiveTask(5)).thenReturn(true);
 
         ArrayList<GroupedTaskInfo> recentTasks =
                 mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -386,8 +412,8 @@
         RecentTaskInfo t4 = makeTaskInfo(4);
         setRawList(t1, t2, t3, t4);
 
-        when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
-        when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+        when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true);
+        when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true);
 
         ArrayList<GroupedTaskInfo> recentTasks =
                 mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -415,7 +441,9 @@
         setRawList(t1, t2, t3, t4, t5);
 
         when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+        when(mDesktopRepository.isActiveTask(2)).thenReturn(false);
         when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+        when(mDesktopRepository.isActiveTask(4)).thenReturn(false);
         when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
         when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true);
 
@@ -454,8 +482,8 @@
         t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450);
         setRawList(t1, t2);
 
-        when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
-        when(mDesktopRepository.isActiveTask(2)).thenReturn(true);
+        when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true);
+        when(mDesktopUserRepositories.getCurrent().isActiveTask(2)).thenReturn(true);
 
         ArrayList<GroupedTaskInfo> recentTasks =
                 mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
@@ -691,11 +719,25 @@
     private RecentTaskInfo makeTaskInfo(int taskId) {
         RecentTaskInfo info = new RecentTaskInfo();
         info.taskId = taskId;
+
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName("com." + taskId, "Activity" + taskId));
+        info.baseIntent = intent;
+
         info.lastNonFullscreenBounds = new Rect();
         return info;
     }
 
     /**
+     * Helper to create a desktop wallpaper activity with a given task id.
+     */
+    private RecentTaskInfo makeDesktopWallpaperTaskInfo(int taskId) {
+        RecentTaskInfo info = makeTaskInfo(taskId);
+        info.baseIntent.setComponent(DesktopWallpaperActivity.getWallpaperActivityComponent());
+        return info;
+    }
+
+    /**
      * Helper to create a running task with a given task id.
      */
     private ActivityManager.RunningTaskInfo makeRunningTaskInfo(int taskId) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index f0f5fe1..894d238 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -65,6 +65,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
@@ -100,7 +101,7 @@
     @Mock
     private ShellCommandHandler mShellCommandHandler;
     @Mock
-    private DesktopRepository mDesktopRepository;
+    private DesktopUserRepositories mDesktopUserRepositories;
     @Mock
     private ActivityTaskManager mActivityTaskManager;
     @Mock
@@ -112,6 +113,8 @@
     @Mock
     private Transitions mTransitions;
 
+    @Mock private DesktopRepository mDesktopRepository;
+
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
@@ -131,6 +134,7 @@
         ExtendedMockito.doReturn(true)
                 .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
 
+        when(mDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
         mMainExecutor = new TestShellExecutor();
         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
         when(mContext.getSystemService(KeyguardManager.class))
@@ -140,7 +144,7 @@
                 mDisplayInsetsController, mMainExecutor));
         mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
                 mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
-                Optional.of(mDesktopRepository), mTaskStackTransitionObserver,
+                Optional.of(mDesktopUserRepositories), mTaskStackTransitionObserver,
                 mMainExecutor);
         mRecentTasksController = spy(mRecentTasksControllerReal);
         mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 72a7a3f..bb9703f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -71,10 +71,10 @@
 import com.android.wm.shell.common.MultiInstanceHelper;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.split.SplitState;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.shared.ShellSharedConstants;
 import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
@@ -119,6 +119,7 @@
     @Mock WindowDecorViewModel mWindowDecorViewModel;
     @Mock DesktopTasksController mDesktopTasksController;
     @Mock MultiInstanceHelper mMultiInstanceHelper;
+    @Mock SplitState mSplitState;
     @Captor ArgumentCaptor<Intent> mIntentCaptor;
 
     private ShellController mShellController;
@@ -136,7 +137,7 @@
                 mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
                 mIconProvider, Optional.of(mRecentTasks), mLaunchAdjacentController,
                 Optional.of(mWindowDecorViewModel), Optional.of(mDesktopTasksController),
-                mStageCoordinator, mMultiInstanceHelper, mMainExecutor, mMainHandler));
+                mStageCoordinator, mMultiInstanceHelper, mSplitState, mMainExecutor, mMainHandler));
     }
 
     @Test
@@ -178,7 +179,7 @@
         when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
         mSplitScreenController.onInit();
         verify(mShellController, times(1)).addExternalInterface(
-                eq(ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN), any(), any());
+                eq(ISplitScreen.DESCRIPTOR), any(), any());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index d13a888..1a2d60d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -35,6 +35,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitState;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.transition.Transitions;
@@ -80,11 +81,11 @@
                 ShellExecutor mainExecutor, Handler mainHandler,
                 Optional<RecentTasksController> recentTasks,
                 LaunchAdjacentController launchAdjacentController,
-                Optional<WindowDecorViewModel> windowDecorViewModel) {
+                Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) {
             super(context, displayId, syncQueue, taskOrganizer, mainStage,
                     sideStage, displayController, imeController, insetsController, splitLayout,
                     transitions, transactionPool, mainExecutor, mainHandler, recentTasks,
-                    launchAdjacentController, windowDecorViewModel);
+                    launchAdjacentController, windowDecorViewModel, splitState);
 
             // Prepare root task for testing.
             mRootTask = new TestRunningTaskInfoBuilder().build();
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 e32cf38..de77837 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
@@ -78,6 +78,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitState;
 import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.transition.DefaultMixedHandler;
 import com.android.wm.shell.transition.TestRemoteTransition;
@@ -108,6 +109,7 @@
     @Mock private Transitions mTransitions;
     @Mock private IconProvider mIconProvider;
     @Mock private WindowDecorViewModel mWindowDecorViewModel;
+    @Mock private SplitState mSplitState;
     @Mock private ShellExecutor mMainExecutor;
     @Mock private Handler mMainHandler;
     @Mock private LaunchAdjacentController mLaunchAdjacentController;
@@ -144,7 +146,7 @@
                 mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
                 mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
                 mTransactionPool, mMainExecutor, mMainHandler, Optional.empty(),
-                mLaunchAdjacentController, Optional.empty());
+                mLaunchAdjacentController, Optional.empty(), mSplitState);
         mStageCoordinator.setMixedHandler(mMixedHandler);
         mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
         doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 1e739cd..7afcce1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -71,6 +71,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitState;
 import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
 import com.android.wm.shell.sysui.ShellController;
@@ -116,6 +117,8 @@
     private LaunchAdjacentController mLaunchAdjacentController;
     @Mock
     private DefaultMixedHandler mDefaultMixedHandler;
+    @Mock
+    private SplitState mSplitState;
 
     private final Rect mBounds1 = new Rect(10, 20, 30, 40);
     private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -139,7 +142,7 @@
                 mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
                 mMainExecutor, mMainHandler, Optional.empty(), mLaunchAdjacentController,
-                Optional.empty()));
+                Optional.empty(), mSplitState));
         mDividerLeash = new SurfaceControl.Builder().setName("fakeDivider").build();
 
         when(mSplitLayout.getTopLeftBounds()).thenReturn(mBounds1);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 7fd1c11..17a5f5c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -42,7 +42,6 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.shared.ShellSharedConstants;
 import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
@@ -99,7 +98,7 @@
     @Test
     public void instantiateController_addExternalInterface() {
         verify(mShellController, times(1)).addExternalInterface(
-                eq(ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW), any(), any());
+                eq(IStartingWindow.DESCRIPTOR), any(), any());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 2442a55..dd645fd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -110,7 +110,7 @@
 import com.android.wm.shell.recents.IRecentsAnimationRunner;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.shared.ShellSharedConstants;
+import com.android.wm.shell.shared.IShellTransitions;
 import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
@@ -176,7 +176,7 @@
                 mock(FocusTransitionObserver.class));
         shellInit.init();
         verify(shellController, times(1)).addExternalInterface(
-                eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any());
+                eq(IShellTransitions.DESCRIPTOR), any(), any());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt
index 3e26ee0..a328b5b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt
@@ -36,7 +36,6 @@
 import android.window.WindowContainerToken
 import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
 import com.android.wm.shell.transition.Transitions.TransitionObserver
-import org.mockito.Mockito
 import org.mockito.kotlin.mock
 
 @DslMarker
@@ -78,6 +77,30 @@
         validateObj.validate()
     }
 
+    fun validateOnMerged(
+        validate:
+        TransitionObserverOnTransitionMergedValidation.() -> Unit
+    ) {
+        val validateObj = TransitionObserverOnTransitionMergedValidation()
+        transitionObserver.onTransitionMerged(
+            validateObj.playing,
+            validateObj.merged
+        )
+        validateObj.validate()
+    }
+
+    fun validateOnFinished(
+        validate:
+        TransitionObserverOnTransitionFinishedValidation.() -> Unit
+    ) {
+        val validateObj = TransitionObserverOnTransitionFinishedValidation()
+        transitionObserver.onTransitionFinished(
+            transitionReadyInput.transition,
+            validateObj.aborted
+        )
+        validateObj.validate()
+    }
+
     fun invokeObservable() {
         transitionObserver.onTransitionReady(
             transitionReadyInput.transition,
@@ -93,10 +116,10 @@
  */
 class TransitionObserverInputBuilder : TransitionObserverTestStep {
 
-    private val transition = Mockito.mock(IBinder::class.java)
+    private val transition = mock<IBinder>()
     private var transitionInfo: TransitionInfo? = null
-    private val startTransaction = Mockito.mock(Transaction::class.java)
-    private val finishTransaction = Mockito.mock(Transaction::class.java)
+    private val startTransaction = mock<Transaction>()
+    private val finishTransaction = mock<Transaction>()
 
     fun buildTransitionInfo(
         @TransitionType type: Int = TRANSIT_NONE,
@@ -143,7 +166,7 @@
             taskId = id
             displayId = DEFAULT_DISPLAY
             configuration.windowConfiguration.windowingMode = windowingMode
-            token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java))
+            token = WindowContainerToken(mock<IWindowContainerToken>())
             baseIntent = Intent().apply {
                 component = ComponentName("package", "component.name")
             }
@@ -163,6 +186,28 @@
 class TransitionObserverResultValidation : TransitionObserverTestStep
 
 /**
+ * Phase responsible for the execution of validation methods after the
+ * [TransitionObservable#onTransitionMerged] has been executed.
+ */
+class TransitionObserverOnTransitionMergedValidation : TransitionObserverTestStep {
+    val merged = mock<IBinder>()
+    val playing = mock<IBinder>()
+
+    init {
+        spyOn(merged)
+        spyOn(playing)
+    }
+}
+
+/**
+ * Phase responsible for the execution of validation methods after the
+ * [TransitionObservable#onTransitionFinished] has been executed.
+ */
+class TransitionObserverOnTransitionFinishedValidation : TransitionObserverTestStep {
+    var aborted: Boolean = false
+}
+
+/**
  * Allows to run a test about a specific [TransitionObserver] passing the specific
  * implementation and input value as parameters for the [TransitionObserver#onTransitionReady]
  * method.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt
index 99e8295..b9d91e7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt
@@ -78,18 +78,18 @@
  * Any fields without corresponding parameters will retain their default values.
  */
 fun createWindowingEducationProto(
-    educationViewedTimestampMillis: Long? = null,
-    featureUsedTimestampMillis: Long? = null,
+    appHandleHintViewedTimestampMillis: Long? = null,
+    appHandleHintUsedTimestampMillis: Long? = null,
     appUsageStats: Map<String, Int>? = null,
     appUsageStatsLastUpdateTimestampMillis: Long? = null
 ): WindowingEducationProto =
     WindowingEducationProto.newBuilder()
         .apply {
-          if (educationViewedTimestampMillis != null) {
-            setEducationViewedTimestampMillis(educationViewedTimestampMillis)
+          if (appHandleHintViewedTimestampMillis != null) {
+            setAppHandleHintViewedTimestampMillis(appHandleHintViewedTimestampMillis)
           }
-          if (featureUsedTimestampMillis != null) {
-            setFeatureUsedTimestampMillis(featureUsedTimestampMillis)
+          if (appHandleHintUsedTimestampMillis != null) {
+            setAppHandleHintUsedTimestampMillis(appHandleHintUsedTimestampMillis)
           }
           setAppHandleEducation(
               createAppHandleEducationProto(appUsageStats, appUsageStatsLastUpdateTimestampMillis))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
index 59141ca..b856a28 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
@@ -48,6 +48,7 @@
 
         CaptionWindowDecoration.updateRelayoutParams(
             relayoutParams,
+            mContext,
             taskInfo,
             true,
             false,
@@ -71,6 +72,7 @@
 
         CaptionWindowDecoration.updateRelayoutParams(
             relayoutParams,
+            mContext,
             taskInfo,
             true,
             false,
@@ -90,6 +92,7 @@
         val relayoutParams = WindowDecoration.RelayoutParams()
         CaptionWindowDecoration.updateRelayoutParams(
             relayoutParams,
+            mContext,
             taskInfo,
             true,
             false,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
index 1215c52..e871711 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
@@ -29,7 +29,7 @@
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.TestShellExecutor
-import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
 import com.google.common.truth.Truth.assertThat
@@ -55,17 +55,18 @@
     @Rule
     val setFlagsRule: SetFlagsRule = SetFlagsRule()
 
-    private lateinit var desktopRepository: DesktopRepository
+    private lateinit var userRepositories: DesktopUserRepositories
     private lateinit var menu: DesktopHeaderManageWindowsMenu
 
     @Before
     fun setUp() {
-        desktopRepository = DesktopRepository(
+        userRepositories = DesktopUserRepositories(
             context = context,
             shellInit = ShellInit(TestShellExecutor()),
             persistentRepository = mock(),
             repositoryInitializer = mock(),
-            mainCoroutineScope = mock()
+            mainCoroutineScope = mock(),
+            userManager = mock(),
         )
     }
 
@@ -78,12 +79,11 @@
     @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
     fun testShow_forImmersiveTask_usesSystemViewContainer() {
         val task = createFreeformTask()
-        desktopRepository.setTaskInFullImmersiveState(
+        userRepositories.getProfile(DEFAULT_USER_ID).setTaskInFullImmersiveState(
             displayId = task.displayId,
             taskId = task.taskId,
             immersive = true
         )
-
         menu = createMenu(task)
 
         assertThat(menu.menuViewContainer).isInstanceOf(AdditionalSystemViewContainer::class.java)
@@ -96,7 +96,7 @@
         displayController = mock(),
         rootTdaOrganizer = mock(),
         context = context,
-        desktopRepository = desktopRepository,
+        desktopUserRepositories = userRepositories,
         surfaceControlBuilderSupplier = { SurfaceControl.Builder() },
         surfaceControlTransactionSupplier = { SurfaceControl.Transaction() },
         snapshotList = emptyList(),
@@ -108,5 +108,10 @@
         .setToken(MockToken().token())
         .setActivityType(ACTIVITY_TYPE_STANDARD)
         .setWindowingMode(WINDOWING_MODE_FREEFORM)
+        .setUserId(DEFAULT_USER_ID)
         .build()
+
+    private companion object {
+        const val DEFAULT_USER_ID = 10
+    }
 }
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 7bb8e891..a4e3af4 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
@@ -59,6 +59,8 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.window.flags.Flags
 import com.android.wm.shell.R
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
+import com.android.wm.shell.desktopmode.DesktopImmersiveController
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
@@ -398,8 +400,11 @@
 
         verify(mockDesktopTasksController).toggleDesktopTaskSize(
             decor.mTaskInfo,
-            ResizeTrigger.MAXIMIZE_MENU,
-            null
+            ToggleTaskSizeInteraction(
+                ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+                ToggleTaskSizeInteraction.Source.MAXIMIZE_MENU_TO_MAXIMIZE,
+                InputMethod.UNKNOWN_INPUT_METHOD
+            )
         )
     }
 
@@ -1000,7 +1005,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
-    fun testMaximizeButtonClick_requestingImmersive_togglesDesktopImmersiveState() {
+    fun testImmersiveButtonClick_entersImmersiveMode() {
         val onClickListenerCaptor = forClass(View.OnClickListener::class.java)
                 as ArgumentCaptor<View.OnClickListener>
         val decor = createOpenTaskDecoration(
@@ -1010,11 +1015,35 @@
         )
         val view = mock(View::class.java)
         whenever(view.id).thenReturn(R.id.maximize_window)
+        whenever(mockDesktopRepository.isTaskInFullImmersiveState(decor.mTaskInfo.taskId))
+            .thenReturn(false)
 
         onClickListenerCaptor.value.onClick(view)
 
-        verify(mockDesktopTasksController)
-            .toggleDesktopTaskFullImmersiveState(decor.mTaskInfo)
+        verify(mockDesktopImmersiveController).moveTaskToImmersive(decor.mTaskInfo)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+    fun testImmersiveRestoreButtonClick_exitsImmersiveMode() {
+        val onClickListenerCaptor = forClass(View.OnClickListener::class.java)
+                as ArgumentCaptor<View.OnClickListener>
+        val decor = createOpenTaskDecoration(
+            windowingMode = WINDOWING_MODE_FREEFORM,
+            onCaptionButtonClickListener = onClickListenerCaptor,
+            requestingImmersive = true,
+        )
+        val view = mock(View::class.java)
+        whenever(view.id).thenReturn(R.id.maximize_window)
+        whenever(mockDesktopRepository.isTaskInFullImmersiveState(decor.mTaskInfo.taskId))
+            .thenReturn(true)
+
+        onClickListenerCaptor.value.onClick(view)
+
+        verify(mockDesktopImmersiveController).moveTaskToNonImmersive(
+            decor.mTaskInfo,
+            DesktopImmersiveController.ExitReason.USER_INTERACTION
+        )
     }
 
     @Test
@@ -1033,23 +1062,31 @@
         onClickListenerCaptor.value.onClick(view)
 
         verify(mockDesktopTasksController)
-            .toggleDesktopTaskSize(decor.mTaskInfo, ResizeTrigger.MAXIMIZE_BUTTON, null)
+            .toggleDesktopTaskSize(
+                decor.mTaskInfo,
+                ToggleTaskSizeInteraction(
+                    ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+                    ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+                    InputMethod.UNKNOWN_INPUT_METHOD
+                )
+            )
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
-    fun testImmersiveClick_togglesImmersiveState() {
+    fun testImmersiveMenuOptionClick_entersImmersiveMode() {
         val onImmersiveClickCaptor = argumentCaptor<() -> Unit>()
         val decor = createOpenTaskDecoration(
             windowingMode = WINDOWING_MODE_FREEFORM,
             onImmersiveOrRestoreListenerCaptor = onImmersiveClickCaptor,
             requestingImmersive = true,
         )
+        whenever(mockDesktopRepository.isTaskInFullImmersiveState(decor.mTaskInfo.taskId))
+            .thenReturn(false)
 
         onImmersiveClickCaptor.firstValue()
 
-        verify(mockDesktopTasksController)
-            .toggleDesktopTaskFullImmersiveState(decor.mTaskInfo)
+        verify(mockDesktopImmersiveController).moveTaskToImmersive(decor.mTaskInfo)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 1670f2a..afd4607 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -54,11 +54,13 @@
 import com.android.wm.shell.common.MultiInstanceHelper
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
+import com.android.wm.shell.desktopmode.DesktopImmersiveController
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger
 import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger
 import com.android.wm.shell.desktopmode.DesktopRepository
 import com.android.wm.shell.desktopmode.DesktopTasksController
 import com.android.wm.shell.desktopmode.DesktopTasksLimiter
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository
 import com.android.wm.shell.desktopmode.education.AppHandleEducationController
 import com.android.wm.shell.desktopmode.education.AppToWebEducationController
@@ -107,11 +109,12 @@
     protected val mockTaskOrganizer = mock<ShellTaskOrganizer>()
     protected val mockDisplayController = mock<DisplayController>()
     protected val mockSplitScreenController = mock<SplitScreenController>()
-    protected val mockDesktopRepository = mock<DesktopRepository>()
+    protected val mockDesktopUserRepositories = mock<DesktopUserRepositories>()
     protected val mockDisplayLayout = mock<DisplayLayout>()
     protected val displayInsetsController = mock<DisplayInsetsController>()
     protected val mockSyncQueue = mock<SyncTransactionQueue>()
     protected val mockDesktopTasksController = mock<DesktopTasksController>()
+    protected val mockDesktopImmersiveController = mock<DesktopImmersiveController>()
     protected val mockInputMonitor = mock<InputMonitor>()
     protected val mockTransitions = mock<Transitions>()
     internal val mockInputMonitorFactory =
@@ -140,6 +143,7 @@
     protected val mockAppToWebEducationController = mock<AppToWebEducationController>()
     protected val mockFocusTransitionObserver = mock<FocusTransitionObserver>()
     protected val mockCaptionHandleRepository = mock<WindowDecorCaptionHandleRepository>()
+    protected val mockDesktopRepository: DesktopRepository = mock<DesktopRepository>()
     protected val motionEvent = mock<MotionEvent>()
     val displayController = mock<DisplayController>()
     val displayLayout = mock<DisplayLayout>()
@@ -166,6 +170,9 @@
         windowDecorByTaskIdSpy.clear()
         spyContext.addMockSystemService(InputManager::class.java, mockInputManager)
         desktopModeEventLogger = mock<DesktopModeEventLogger>()
+        whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
+        whenever(mockDesktopUserRepositories.getProfile(anyInt()))
+            .thenReturn(mockDesktopRepository)
         desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
             spyContext,
             testShellExecutor,
@@ -176,13 +183,14 @@
             mockShellCommandHandler,
             mockWindowManager,
             mockTaskOrganizer,
-            mockDesktopRepository,
+            mockDesktopUserRepositories,
             mockDisplayController,
             mockShellController,
             displayInsetsController,
             mockSyncQueue,
             mockTransitions,
             Optional.of(mockDesktopTasksController),
+            mockDesktopImmersiveController,
             mockGenericLinksParser,
             mockAssistContentRequester,
             mockMultiInstanceHelper,
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 e390fbb..0bef4191 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
@@ -29,6 +29,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.CLOSE_MAXIMIZE_MENU_DELAY_MS;
 import static com.android.wm.shell.windowdecor.WindowDecoration.INVALID_CORNER_RADIUS;
 
@@ -61,7 +62,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -110,6 +110,7 @@
 import com.android.wm.shell.desktopmode.CaptionState;
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -166,7 +167,7 @@
     @Mock
     private ShellTaskOrganizer mMockShellTaskOrganizer;
     @Mock
-    private DesktopRepository mMockDesktopRepository;
+    private DesktopUserRepositories mMockDesktopUserRepositories;
     @Mock
     private Choreographer mMockChoreographer;
     @Mock
@@ -215,6 +216,8 @@
     private WindowDecorCaptionHandleRepository mMockCaptionHandleRepository;
     @Mock
     private DesktopModeEventLogger mDesktopModeEventLogger;
+    @Mock
+    private DesktopRepository mDesktopRepository;
     @Captor
     private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
     @Captor
@@ -271,6 +274,8 @@
         when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false);
         when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any(), any(),
                 any())).thenReturn(mMockAppHeaderViewHolder);
+        when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
+        when(mMockDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository);
     }
 
     @After
@@ -295,12 +300,14 @@
     }
 
     @Test
-    public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreEnabled() {
+    public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreSetForFreeform() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
         RelayoutParams relayoutParams = new RelayoutParams();
 
         DesktopModeWindowDecoration.updateRelayoutParams(
-                relayoutParams, mContext, taskInfo, /* applyStartTransactionOnDraw= */ true,
+                relayoutParams, mContext, taskInfo, mMockSplitScreenController,
+                /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
                 /* isKeyguardVisibleAndOccluded */ false,
@@ -309,7 +316,48 @@
                 /* hasGlobalFocus= */ true,
                 mExclusionRegion);
 
-        assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL);
+        assertThat(relayoutParams.mShadowRadius)
+                .isNotEqualTo(WindowDecoration.INVALID_SHADOW_RADIUS);
+    }
+
+    @Test
+    public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForFullscreen() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams, mContext, taskInfo, mMockSplitScreenController,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false,
+                /* isStatusBarVisible */ true,
+                /* isKeyguardVisibleAndOccluded */ false,
+                /* inFullImmersiveMode */ false,
+                new InsetsState(),
+                /* hasGlobalFocus= */ true,
+                mExclusionRegion);
+
+        assertThat(relayoutParams.mShadowRadius).isEqualTo(WindowDecoration.INVALID_SHADOW_RADIUS);
+    }
+
+    @Test
+    public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForSplit() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams, mContext, taskInfo, mMockSplitScreenController,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false,
+                /* isStatusBarVisible */ true,
+                /* isKeyguardVisibleAndOccluded */ false,
+                /* inFullImmersiveMode */ false,
+                new InsetsState(),
+                /* hasGlobalFocus= */ true,
+                mExclusionRegion);
+
+        assertThat(relayoutParams.mShadowRadius).isEqualTo(WindowDecoration.INVALID_SHADOW_RADIUS);
     }
 
     @Test
@@ -323,6 +371,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -346,6 +395,31 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false,
+                /* isStatusBarVisible */ true,
+                /* isKeyguardVisibleAndOccluded */ false,
+                /* inFullImmersiveMode */ false,
+                new InsetsState(),
+                /* hasGlobalFocus= */ true,
+                mExclusionRegion);
+
+        assertThat(relayoutParams.mCornerRadius).isEqualTo(INVALID_CORNER_RADIUS);
+    }
+
+    @Test
+    public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForSplit() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        fillRoundedCornersResources(/* fillValue= */ 30);
+        RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -373,6 +447,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -401,6 +476,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -426,6 +502,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -451,6 +528,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -475,6 +553,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -499,6 +578,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -522,6 +602,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -545,6 +626,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -567,6 +649,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -590,6 +673,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -613,6 +697,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -637,6 +722,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -662,6 +748,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -685,6 +772,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -710,6 +798,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -725,6 +814,31 @@
     }
 
     @Test
+    public void updateRelayoutParams_handle_bottomSplitIsInsetSource() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        final RelayoutParams relayoutParams = new RelayoutParams();
+        when(mMockSplitScreenController.isLeftRightSplit()).thenReturn(false);
+        when(mMockSplitScreenController.getSplitPosition(taskInfo.taskId))
+                .thenReturn(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                mMockSplitScreenController,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false,
+                /* isStatusBarVisible */ true,
+                /* isKeyguardVisibleAndOccluded */ false,
+                /* inFullImmersiveMode */ true,
+                new InsetsState(),
+                /* hasGlobalFocus= */ true,
+                mExclusionRegion);
+
+        assertThat(relayoutParams.mIsInsetSource).isTrue();
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
     public void updateRelayoutParams_header_addsPaddingInFullImmersive() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
@@ -741,6 +855,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -765,6 +880,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -788,6 +904,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ false,
@@ -811,6 +928,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -833,6 +951,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ false,
@@ -855,6 +974,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -878,6 +998,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -893,6 +1014,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ false,
@@ -916,6 +1038,7 @@
                 relayoutParams,
                 mTestableContext,
                 taskInfo,
+                mMockSplitScreenController,
                 /* applyStartTransactionOnDraw= */ true,
                 /* shouldSetTaskPositionAndCrop */ false,
                 /* isStatusBarVisible */ true,
@@ -1407,8 +1530,8 @@
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
                 true /* relayout */);
-        when(mMockDesktopRepository.isTaskInFullImmersiveState(taskInfo.taskId))
-                .thenReturn(true);
+        when(mMockDesktopUserRepositories.getCurrent()
+                .isTaskInFullImmersiveState(taskInfo.taskId)).thenReturn(true);
 
         createHandleMenu(decoration);
 
@@ -1429,7 +1552,7 @@
 
     @Test
     @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION,
-            Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION})
+            Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION_INTEGRATION})
     public void notifyCaptionStateChanged_flagDisabled_doNoNotify() {
         when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
@@ -1643,8 +1766,8 @@
             boolean relayout) {
         final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
                 mContext, mMockDisplayController, mMockSplitScreenController,
-                mMockDesktopRepository, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl,
-                mMockHandler, mBgExecutor, mMockChoreographer, mMockSyncQueue,
+                mMockDesktopUserRepositories, mMockShellTaskOrganizer, taskInfo,
+                mMockSurfaceControl, mMockHandler, mBgExecutor, mMockChoreographer, mMockSyncQueue,
                 mMockAppHeaderViewHolderFactory, mMockRootTaskDisplayAreaOrganizer,
                 mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new,
                 mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
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 e7d328e..479f156 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
@@ -63,6 +63,7 @@
     private static final int EDGE_RESIZE_HANDLE_INSET = 4;
     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 int SMALL_OFFSET = 10;
     private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
             TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS, EDGE_RESIZE_HANDLE_INSET,
             FINE_CORNER_SIZE, LARGE_CORNER_SIZE, DragResizeWindowGeometry.DisabledEdge.NONE);
@@ -147,15 +148,19 @@
         assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue();
         assertThat(region.contains(point.x - EDGE_RESIZE_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 + 10)).isFalse();
+        assertThat(
+                region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS - SMALL_OFFSET)).isFalse();
+        assertThat(
+                region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS + SMALL_OFFSET)).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_THICKNESS + SMALL_OFFSET, point.y)).isFalse();
+        assertThat(
+                region.contains(point.x - EDGE_RESIZE_THICKNESS - SMALL_OFFSET, 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();
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 534803d..04b2be0 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
@@ -114,6 +114,7 @@
     private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
     private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
     private static final int CORNER_RADIUS = 20;
+    private static final int SHADOW_RADIUS = 10;
     private static final int STATUS_BAR_INSET_SOURCE_ID = 0;
 
     @Rule
@@ -162,7 +163,7 @@
         mRelayoutParams.mLayoutResId = 0;
         mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
         mCaptionMenuWidthId = R.dimen.test_freeform_decor_caption_menu_width;
-        mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
+        mRelayoutParams.mShadowRadius = SHADOW_RADIUS;
         mRelayoutParams.mCornerRadius = CORNER_RADIUS;
 
         when(mMockDisplayController.getDisplay(Display.DEFAULT_DISPLAY))
@@ -280,7 +281,7 @@
 
         verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
         verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
-        verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, 10);
+        verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, SHADOW_RADIUS);
 
         assertEquals(300, mRelayoutResult.mWidth);
         assertEquals(100, mRelayoutResult.mHeight);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt
new file mode 100644
index 0000000..2f223de
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor.common.viewhost
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.View
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+
+/**
+ * Tests for [DefaultWindowDecorViewHost].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DefaultWindowDecorViewHostTest
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class DefaultWindowDecorViewHostTest : ShellTestCase() {
+
+    @Test
+    fun updateView_layoutInViewHost() = runTest {
+        val windowDecorViewHost = createDefaultViewHost()
+        val view = View(context)
+
+        windowDecorViewHost.updateView(
+            view = view,
+            attrs = WindowManager.LayoutParams(100, 100),
+            configuration = context.resources.configuration,
+            onDrawTransaction = null,
+        )
+
+        assertThat(windowDecorViewHost.viewHost).isNotNull()
+        assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view)
+    }
+
+    @Test
+    fun updateView_alreadyLaidOut_relayouts() = runTest {
+        val windowDecorViewHost = createDefaultViewHost()
+        val view = View(context)
+        windowDecorViewHost.updateView(
+            view = view,
+            attrs = WindowManager.LayoutParams(100, 100),
+            configuration = context.resources.configuration,
+            onDrawTransaction = null,
+        )
+
+        val otherParams = WindowManager.LayoutParams(200, 200)
+        windowDecorViewHost.updateView(
+            view = view,
+            attrs = otherParams,
+            configuration = context.resources.configuration,
+            onDrawTransaction = null,
+        )
+
+        assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view)
+        assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width)
+            .isEqualTo(otherParams.width)
+    }
+
+    @Test
+    fun updateView_replacingView_throws() = runTest {
+        val windowDecorViewHost = createDefaultViewHost()
+        val view = View(context)
+        windowDecorViewHost.updateView(
+            view = view,
+            attrs = WindowManager.LayoutParams(100, 100),
+            configuration = context.resources.configuration,
+            onDrawTransaction = null,
+        )
+
+        val otherView = View(context)
+        assertThrows(Exception::class.java) {
+            windowDecorViewHost.updateView(
+                view = otherView,
+                attrs = WindowManager.LayoutParams(100, 100),
+                configuration = context.resources.configuration,
+                onDrawTransaction = null,
+            )
+        }
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun updateView_clearsPendingAsyncJob() = runTest {
+        val windowDecorViewHost = createDefaultViewHost()
+        val asyncView = View(context)
+        val syncView = View(context)
+        val asyncAttrs = WindowManager.LayoutParams(100, 100)
+        val syncAttrs = WindowManager.LayoutParams(200, 200)
+
+        windowDecorViewHost.updateViewAsync(
+            view = asyncView,
+            attrs = asyncAttrs,
+            configuration = context.resources.configuration,
+        )
+
+        // No view host yet, since the coroutine hasn't run.
+        assertThat(windowDecorViewHost.viewHost).isNull()
+
+        windowDecorViewHost.updateView(
+            view = syncView,
+            attrs = syncAttrs,
+            configuration = context.resources.configuration,
+            onDrawTransaction = null,
+        )
+
+        // Would run coroutine if it hadn't been cancelled.
+        advanceUntilIdle()
+
+        assertThat(windowDecorViewHost.viewHost).isNotNull()
+        assertThat(windowDecorViewHost.viewHost!!.view).isNotNull()
+        // View host view/attrs should match the ones from the sync call, plus, since the
+        // sync/async were made with different views, if the job hadn't been cancelled there
+        // would've been an exception thrown as replacing views isn't allowed.
+        assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(syncView)
+        assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width)
+            .isEqualTo(syncAttrs.width)
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun updateViewAsync() = runTest {
+        val windowDecorViewHost = createDefaultViewHost()
+        val view = View(context)
+        val attrs = WindowManager.LayoutParams(100, 100)
+
+        windowDecorViewHost.updateViewAsync(
+            view = view,
+            attrs = attrs,
+            configuration = context.resources.configuration,
+        )
+
+        assertThat(windowDecorViewHost.viewHost).isNull()
+
+        advanceUntilIdle()
+
+        assertThat(windowDecorViewHost.viewHost).isNotNull()
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun updateViewAsync_clearsPendingAsyncJob() = runTest {
+        val windowDecorViewHost = createDefaultViewHost()
+
+        val view = View(context)
+        windowDecorViewHost.updateViewAsync(
+            view = view,
+            attrs = WindowManager.LayoutParams(100, 100),
+            configuration = context.resources.configuration,
+        )
+        val otherView = View(context)
+        windowDecorViewHost.updateViewAsync(
+            view = otherView,
+            attrs = WindowManager.LayoutParams(100, 100),
+            configuration = context.resources.configuration,
+        )
+
+        advanceUntilIdle()
+
+        assertThat(windowDecorViewHost.viewHost).isNotNull()
+        assertThat(windowDecorViewHost.viewHost!!.view).isNotNull()
+        assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(otherView)
+    }
+
+    @Test
+    fun release() = runTest {
+        val windowDecorViewHost = createDefaultViewHost()
+
+        val view = View(context)
+        windowDecorViewHost.updateView(
+            view = view,
+            attrs = WindowManager.LayoutParams(100, 100),
+            configuration = context.resources.configuration,
+            onDrawTransaction = null,
+        )
+
+        val t = mock(SurfaceControl.Transaction::class.java)
+        windowDecorViewHost.release(t)
+
+        verify(windowDecorViewHost.viewHost!!).release()
+        verify(t).remove(windowDecorViewHost.surfaceControl)
+    }
+
+    private fun CoroutineScope.createDefaultViewHost() =
+        DefaultWindowDecorViewHost(
+            context = context,
+            mainScope = this,
+            display = context.display,
+            surfaceControlViewHostFactory = { c, d, wwm, s ->
+                spy(SurfaceControlViewHost(c, d, wwm, s))
+            },
+        )
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
index d8c1a11..193c2c2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
@@ -25,9 +25,9 @@
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger
-import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
 import com.android.wm.shell.desktopmode.DesktopTasksController
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
 import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
 import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler
 import com.android.wm.shell.transition.Transitions
@@ -52,7 +52,7 @@
     private val syncQueueMock: SyncTransactionQueue = mock()
     private val transitionsMock: Transitions = mock()
     private val shellTaskOrganizerMock: ShellTaskOrganizer = mock()
-    private val desktopRepository: DesktopRepository = mock()
+    private val userRepositories: DesktopUserRepositories = mock()
     private val desktopModeEventLogger: DesktopModeEventLogger = mock()
     private val toggleResizeDesktopTaskTransitionHandlerMock:
         ToggleResizeDesktopTaskTransitionHandler =
@@ -75,7 +75,7 @@
                 shellTaskOrganizerMock,
                 toggleResizeDesktopTaskTransitionHandlerMock,
                 returnToDragStartAnimatorMock,
-                desktopRepository,
+                userRepositories,
                 desktopModeEventLogger,
             )
         whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
index 3143946..121e0e9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
@@ -122,6 +122,6 @@
 
     companion object {
         private val BOUNDS = Rect(1, 2, 3, 4)
-        private val CORNER_RADIUS = 28
+        private const val CORNER_RADIUS = 28
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
index ad6fdf4..95e2151 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
@@ -38,7 +38,8 @@
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 import com.android.wm.shell.desktopmode.DesktopRepository
 import com.android.wm.shell.desktopmode.DesktopTasksController
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
 import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
 import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler
 import com.android.wm.shell.transition.Transitions
@@ -93,10 +94,11 @@
     private val transition: IBinder = mock()
     private val info: TransitionInfo = mock()
     private val finishCallback: Transitions.TransitionFinishCallback = mock()
-    private val desktopRepository: DesktopRepository = mock()
+    private val userRepositories: DesktopUserRepositories = mock()
     private val desktopModeEventLogger: DesktopModeEventLogger = mock()
     private val desktopTilingDividerWindowManager: DesktopTilingDividerWindowManager = mock()
     private val motionEvent: MotionEvent = mock()
+    private val desktopRepository: DesktopRepository = mock()
     private lateinit var tilingDecoration: DesktopTilingWindowDecoration
 
     private val split_divider_width = 10
@@ -116,15 +118,16 @@
                 shellTaskOrganizer,
                 toggleResizeDesktopTaskTransitionHandler,
                 returnToDragStartAnimator,
-                desktopRepository,
+                userRepositories,
                 desktopModeEventLogger,
             )
         whenever(context.createContextAsUser(any(), any())).thenReturn(context)
+        whenever(userRepositories.current).thenReturn(desktopRepository)
     }
 
     @Test
     fun taskTiled_toCorrectBounds_leftTile() {
-        val task1 = createFreeformTask()
+        val task1 = createVisibleTask()
         val stableBounds = STABLE_BOUNDS_MOCK
         whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
@@ -151,7 +154,7 @@
     @Test
     fun taskTiled_toCorrectBounds_rightTile() {
         // Setup
-        val task1 = createFreeformTask()
+        val task1 = createVisibleTask()
         val stableBounds = STABLE_BOUNDS_MOCK
         whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
@@ -177,7 +180,7 @@
 
     @Test
     fun taskTiled_notAnimated_whenTilingPositionNotChange() {
-        val task1 = createFreeformTask()
+        val task1 = createVisibleTask()
         val stableBounds = STABLE_BOUNDS_MOCK
         whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
@@ -213,8 +216,8 @@
 
     @Test
     fun taskNotTiled_notBroughtToFront_tilingNotInitialised() {
-        val task1 = createFreeformTask()
-        val task2 = createFreeformTask()
+        val task1 = createVisibleTask()
+        val task2 = createVisibleTask()
         val stableBounds = STABLE_BOUNDS_MOCK
         whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
@@ -236,9 +239,9 @@
 
     @Test
     fun taskNotTiled_notBroughtToFront_taskNotTiled() {
-        val task1 = createFreeformTask()
-        val task2 = createFreeformTask()
-        val task3 = createFreeformTask()
+        val task1 = createVisibleTask()
+        val task2 = createVisibleTask()
+        val task3 = createVisibleTask()
         val stableBounds = STABLE_BOUNDS_MOCK
         whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
@@ -265,9 +268,9 @@
     }
 
     @Test
-    fun taskTiled_broughtToFront_alreadyInFrontNoAction() {
-        val task1 = createFreeformTask()
-        val task2 = createFreeformTask()
+    fun taskTiled_broughtToFront_alreadyInFrontStillReorder() {
+        val task1 = createVisibleTask()
+        val task2 = createVisibleTask()
         val stableBounds = STABLE_BOUNDS_MOCK
         whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
@@ -275,6 +278,8 @@
         }
         whenever(context.resources).thenReturn(resources)
         whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width)
+        whenever(userRepositories.current.isVisibleTask(eq(task1.taskId))).thenReturn(true)
+        whenever(userRepositories.current.isVisibleTask(eq(task2.taskId))).thenReturn(true)
 
         tilingDecoration.onAppTiled(
             task1,
@@ -290,15 +295,15 @@
         )
         task1.isFocused = true
 
-        assertThat(tilingDecoration.moveTiledPairToFront(task1)).isFalse()
-        verify(transitions, never()).startTransition(any(), any(), any())
+        assertThat(tilingDecoration.moveTiledPairToFront(task1, isTaskFocused = true)).isTrue()
+        verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null))
     }
 
     @Test
     fun taskTiled_broughtToFront_bringToFront() {
-        val task1 = createFreeformTask()
-        val task2 = createFreeformTask()
-        val task3 = createFreeformTask()
+        val task1 = createVisibleTask()
+        val task2 = createVisibleTask()
+        val task3 = createVisibleTask()
         val stableBounds = STABLE_BOUNDS_MOCK
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
             (i.arguments.first() as Rect).set(stableBounds)
@@ -306,7 +311,7 @@
         whenever(context.resources).thenReturn(resources)
         whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width)
         whenever(desktopWindowDecoration.getLeash()).thenReturn(surfaceControlMock)
-        whenever(desktopRepository.isVisibleTask(any())).thenReturn(true)
+        whenever(userRepositories.current.isVisibleTask(any())).thenReturn(true)
         tilingDecoration.onAppTiled(
             task1,
             desktopWindowDecoration,
@@ -329,9 +334,9 @@
 
     @Test
     fun taskTiled_broughtToFront_taskInfoNotUpdated_bringToFront() {
-        val task1 = createFreeformTask()
-        val task2 = createFreeformTask()
-        val task3 = createFreeformTask()
+        val task1 = createVisibleTask()
+        val task2 = createVisibleTask()
+        val task3 = createVisibleTask()
         val stableBounds = STABLE_BOUNDS_MOCK
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
             (i.arguments.first() as Rect).set(stableBounds)
@@ -339,7 +344,7 @@
         whenever(context.resources).thenReturn(resources)
         whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width)
         whenever(desktopWindowDecoration.getLeash()).thenReturn(surfaceControlMock)
-        whenever(desktopRepository.isVisibleTask(any())).thenReturn(true)
+        whenever(userRepositories.current.isVisibleTask(any())).thenReturn(true)
         tilingDecoration.onAppTiled(
             task1,
             desktopWindowDecoration,
@@ -361,8 +366,8 @@
     @Test
     fun taskTiledTasks_NotResized_BeforeTouchEndArrival() {
         // Setup
-        val task1 = createFreeformTask()
-        val task2 = createFreeformTask()
+        val task1 = createVisibleTask()
+        val task2 = createVisibleTask()
         val stableBounds = STABLE_BOUNDS_MOCK
         whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
@@ -428,8 +433,8 @@
     @Test
     fun tiledTasksResizedUsingDividerHandle_shouldLogResizingEvents() {
         // Setup
-        val task1 = createFreeformTask()
-        val task2 = createFreeformTask()
+        val task1 = createVisibleTask()
+        val task2 = createVisibleTask()
         val stableBounds = STABLE_BOUNDS_MOCK
         whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
@@ -487,7 +492,7 @@
 
     @Test
     fun taskTiled_shouldBeRemoved_whenTileBroken() {
-        val task1 = createFreeformTask()
+        val task1 = createVisibleTask()
         val stableBounds = STABLE_BOUNDS_MOCK
         whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
@@ -516,8 +521,8 @@
 
     @Test
     fun taskNotTiled_shouldNotBeRemoved_whenNotTiled() {
-        val task1 = createFreeformTask()
-        val task2 = createFreeformTask()
+        val task1 = createVisibleTask()
+        val task2 = createVisibleTask()
         val stableBounds = STABLE_BOUNDS_MOCK
         whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
@@ -545,8 +550,8 @@
 
     @Test
     fun tasksTiled_shouldBeRemoved_whenSessionDestroyed() {
-        val task1 = createFreeformTask()
-        val task2 = createFreeformTask()
+        val task1 = createVisibleTask()
+        val task2 = createVisibleTask()
         val stableBounds = STABLE_BOUNDS_MOCK
         whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
@@ -610,6 +615,11 @@
         return Rect(stableBounds.left, stableBounds.top, rightBound, stableBounds.bottom)
     }
 
+    private fun createVisibleTask() =
+        createFreeformTask().also {
+            whenever(userRepositories.current.isVisibleTask(eq(it.taskId))).thenReturn(true)
+        }
+
     companion object {
         private val NON_STABLE_BOUNDS_MOCK = Rect(50, 55, 100, 100)
         private val STABLE_BOUNDS_MOCK = Rect(0, 0, 100, 100)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
index 734815c..9a9d05a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
@@ -19,9 +19,12 @@
 import android.graphics.Rect
 import android.os.SystemClock
 import android.testing.AndroidTestingRunner
+import android.util.Size
+import android.view.Display
 import android.view.InputDevice
 import android.view.LayoutInflater
 import android.view.MotionEvent
+import android.view.RoundedCorner
 import android.view.View
 import androidx.test.annotation.UiThreadTest
 import androidx.test.filters.SmallTest
@@ -46,16 +49,19 @@
     private val dividerMoveCallbackMock = mock<DividerMoveCallback>()
 
     private val viewMock = mock<View>()
+    private val display = mock<Display>()
+    private val roundedCorner = mock<RoundedCorner>()
 
     @Before
     @UiThreadTest
     fun setUp() {
+        whenever(display.getRoundedCorner(any())).thenReturn(roundedCorner)
+        whenever(roundedCorner.radius).thenReturn(CORNER_RADIUS)
         tilingDividerView =
             LayoutInflater.from(mContext).inflate(R.layout.tiling_split_divider, /* root= */ null)
                 as TilingDividerView
-        tilingDividerView.setup(dividerMoveCallbackMock, BOUNDS)
-        tilingDividerView.handleStartY = 0
-        tilingDividerView.handleEndY = 1500
+        tilingDividerView.setup(dividerMoveCallbackMock, DIVIDER_BOUNDS, HANDLE_SIZE)
+        tilingDividerView.handleY = 0..1500
     }
 
     @Test
@@ -130,6 +136,8 @@
     }
 
     companion object {
-        private val BOUNDS = Rect(0, 0, 1500, 1500)
+        private val DIVIDER_BOUNDS = Rect(15, 0, 35, 1500)
+        private val HANDLE_SIZE = Size(800, 300)
+        private const val CORNER_RADIUS = 15
     }
 }
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 49254d1..dbb8914 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -40,20 +40,21 @@
 }
 
 ApkAssetsPtr ApkAssets::Load(const std::string& path, package_property_t flags) {
-  return Load(ZipAssetsProvider::Create(path, flags), flags);
+  return LoadImpl(ZipAssetsProvider::Create(path, flags), flags);
 }
 
 ApkAssetsPtr ApkAssets::LoadFromFd(base::unique_fd fd, const std::string& debug_name,
                                    package_property_t flags, off64_t offset, off64_t len) {
-  return Load(ZipAssetsProvider::Create(std::move(fd), debug_name, offset, len), flags);
+  return LoadImpl(ZipAssetsProvider::Create(std::move(fd), debug_name, offset, len), flags);
 }
 
-ApkAssetsPtr ApkAssets::Load(std::unique_ptr<AssetsProvider> assets, package_property_t flags) {
+ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider>&& assets,
+                                 package_property_t flags) {
   return LoadImpl(std::move(assets), flags, nullptr /* idmap_asset */, nullptr /* loaded_idmap */);
 }
 
-ApkAssetsPtr ApkAssets::LoadTable(std::unique_ptr<Asset> resources_asset,
-                                  std::unique_ptr<AssetsProvider> assets,
+ApkAssetsPtr ApkAssets::LoadTable(std::unique_ptr<Asset>&& resources_asset,
+                                  std::unique_ptr<AssetsProvider>&& assets,
                                   package_property_t flags) {
   if (resources_asset == nullptr) {
     return {};
@@ -97,10 +98,10 @@
                   std::move(loaded_idmap));
 }
 
-ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider> assets,
+ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider>&& assets,
                                  package_property_t property_flags,
-                                 std::unique_ptr<Asset> idmap_asset,
-                                 std::unique_ptr<LoadedIdmap> loaded_idmap) {
+                                 std::unique_ptr<Asset>&& idmap_asset,
+                                 std::unique_ptr<LoadedIdmap>&& loaded_idmap) {
   if (assets == nullptr) {
     return {};
   }
@@ -119,11 +120,11 @@
                   std::move(idmap_asset), std::move(loaded_idmap));
 }
 
-ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<Asset> resources_asset,
-                                 std::unique_ptr<AssetsProvider> assets,
+ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<Asset>&& resources_asset,
+                                 std::unique_ptr<AssetsProvider>&& assets,
                                  package_property_t property_flags,
-                                 std::unique_ptr<Asset> idmap_asset,
-                                 std::unique_ptr<LoadedIdmap> loaded_idmap) {
+                                 std::unique_ptr<Asset>&& idmap_asset,
+                                 std::unique_ptr<LoadedIdmap>&& loaded_idmap) {
   if (assets == nullptr ) {
     return {};
   }
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 1fa6752..231808b 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -47,13 +47,37 @@
                                  package_property_t flags = 0U, off64_t offset = 0,
                                  off64_t len = AssetsProvider::kUnknownLength);
 
+  //
   // Creates an ApkAssets from an AssetProvider.
-  // The ApkAssets will take care of destroying the AssetsProvider when it is destroyed.
-  static ApkAssetsPtr Load(std::unique_ptr<AssetsProvider> assets, package_property_t flags = 0U);
+  // The ApkAssets will take care of destroying the AssetsProvider when it is destroyed;
+  // the original argument is not moved from if loading fails.
+  //
+  // Note: this function takes care of the case when you pass a move(unique_ptr<Derived>)
+  //    that would create a temporary unique_ptr<AssetsProvider> by moving your pointer into
+  //    it before the function call, making it impossible to not move from the parameter
+  //    on loading failure. The two overloads take care of moving the pointer back if needed.
+  //
+
+  template <class T>
+  static ApkAssetsPtr Load(std::unique_ptr<T>&& assets, package_property_t flags = 0U)
+      requires(std::is_same_v<T, AssetsProvider>) {
+    return LoadImpl(std::move(assets), flags);
+  }
+
+  template <class T>
+  static ApkAssetsPtr Load(std::unique_ptr<T>&& assets, package_property_t flags = 0U)
+      requires(!std::is_same_v<T, AssetsProvider> && std::is_base_of_v<AssetsProvider, T>) {
+    std::unique_ptr<AssetsProvider> base_assets(std::move(assets));
+    auto res = LoadImpl(std::move(base_assets), flags);
+    if (!res) {
+      assets.reset(static_cast<T*>(base_assets.release()));
+    }
+    return res;
+  }
 
   // Creates an ApkAssets from the given asset file representing a resources.arsc.
-  static ApkAssetsPtr LoadTable(std::unique_ptr<Asset> resources_asset,
-                                std::unique_ptr<AssetsProvider> assets,
+  static ApkAssetsPtr LoadTable(std::unique_ptr<Asset>&& resources_asset,
+                                std::unique_ptr<AssetsProvider>&& assets,
                                 package_property_t flags = 0U);
 
   // Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay
@@ -94,17 +118,29 @@
 
   bool IsUpToDate() const;
 
- private:
-  static ApkAssetsPtr LoadImpl(std::unique_ptr<AssetsProvider> assets,
-                               package_property_t property_flags,
-                               std::unique_ptr<Asset> idmap_asset,
-                               std::unique_ptr<LoadedIdmap> loaded_idmap);
+  // DANGER!
+  // This is a destructive method that rips the assets provider out of ApkAssets object.
+  // It is only useful when one knows this assets object can't be used anymore, and they
+  // need the underlying assets provider back (e.g. when initialization fails for some
+  // reason).
+  std::unique_ptr<AssetsProvider> TakeAssetsProvider() && {
+    return std::move(assets_provider_);
+  }
 
-  static ApkAssetsPtr LoadImpl(std::unique_ptr<Asset> resources_asset,
-                               std::unique_ptr<AssetsProvider> assets,
+ private:
+  static ApkAssetsPtr LoadImpl(std::unique_ptr<AssetsProvider>&& assets,
                                package_property_t property_flags,
-                               std::unique_ptr<Asset> idmap_asset,
-                               std::unique_ptr<LoadedIdmap> loaded_idmap);
+                               std::unique_ptr<Asset>&& idmap_asset,
+                               std::unique_ptr<LoadedIdmap>&& loaded_idmap);
+
+  static ApkAssetsPtr LoadImpl(std::unique_ptr<Asset>&& resources_asset,
+                               std::unique_ptr<AssetsProvider>&& assets,
+                               package_property_t property_flags,
+                               std::unique_ptr<Asset>&& idmap_asset,
+                               std::unique_ptr<LoadedIdmap>&& loaded_idmap);
+
+  static ApkAssetsPtr LoadImpl(std::unique_ptr<AssetsProvider>&& assets,
+                               package_property_t flags = 0U);
 
   // Allows us to make it possible to call make_shared from inside the class but still keeps the
   // ctor 'private' for all means and purposes.
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index 70326b7..c36d990 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -28,6 +28,7 @@
 using ::com::android::basic::R;
 using ::testing::Eq;
 using ::testing::Ge;
+using ::testing::IsNull;
 using ::testing::NotNull;
 using ::testing::SizeIs;
 using ::testing::StrEq;
@@ -108,4 +109,26 @@
   EXPECT_THAT(buffer, StrEq("This should be uncompressed.\n\n"));
 }
 
+TEST(ApkAssetsTest, TakeAssetsProviderNotCrashing) {
+  // Make sure the apk assets object can survive taking its assets provider and doesn't crash
+  // the process.
+  {
+    auto loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+    ASSERT_THAT(loaded_apk, NotNull());
+
+    auto provider = std::move(*loaded_apk).TakeAssetsProvider();
+    ASSERT_THAT(provider, NotNull());
+  }
+  // If this test doesn't crash by this point we're all good.
+}
+
+TEST(ApkAssetsTest, AssetsProviderNotMovedOnError) {
+  auto assets_provider
+      = ZipAssetsProvider::Create(GetTestDataPath() + "/bad/bad.apk", 0);
+  ASSERT_THAT(assets_provider, NotNull());
+  auto loaded_apk = ApkAssets::Load(std::move(assets_provider));
+  ASSERT_THAT(loaded_apk, IsNull());
+  ASSERT_THAT(assets_provider, NotNull());
+}
+
 }  // namespace android
diff --git a/libs/androidfw/tests/data/bad/bad.apk b/libs/androidfw/tests/data/bad/bad.apk
new file mode 100644
index 0000000..3226bcd5
--- /dev/null
+++ b/libs/androidfw/tests/data/bad/bad.apk
Binary files differ
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index fa27af6..e497ea1 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -6,6 +6,7 @@
   namespace: "core_graphics"
   description: "API for AGSL authored runtime color filters and blenders"
   bug: "358126864"
+  is_exported: true
 }
 
 flag {
@@ -44,6 +45,7 @@
   namespace: "accessibility"
   description: "Draw a solid rectangle background behind text instead of a stroke outline"
   bug: "186567103"
+  is_exported: true
 }
 
 flag {
@@ -96,6 +98,7 @@
   namespace: "core_graphics"
   description: "Add canvas#drawRegion API"
   bug: "318612129"
+  is_exported: true
 }
 
 flag {
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index e5fb755..7b45070 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -48,7 +48,14 @@
     minikinPaint.localeListId = paint->getMinikinLocaleListId();
     minikinPaint.fontStyle = resolvedFace->fStyle;
     minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
-    minikinPaint.fontVariationSettings = paint->getFontVariationOverride();
+    if (!resolvedFace->fIsVariationInstance) {
+        // This is an optimization for direct private API use typically done by System UI.
+        // In the public API surface, if Typeface is already configured for variation instance
+        // (Target SDK <= 35) the font variation settings of Paint is not set.
+        // On the other hand, if Typeface is not configured so (Target SDK >= 36), the font
+        // variation settings are configured dynamically.
+        minikinPaint.fontVariationSettings = paint->getFontVariationOverride();
+    }
     minikinPaint.verticalText = paint->isVerticalText();
 
     const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant();
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 2d812d6..4dfe053 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -76,6 +76,7 @@
         result->fBaseWeight = resolvedFace->fBaseWeight;
         result->fAPIStyle = style;
         result->fStyle = computeRelativeStyle(result->fBaseWeight, style);
+        result->fIsVariationInstance = resolvedFace->fIsVariationInstance;
     }
     return result;
 }
@@ -88,6 +89,7 @@
         result->fBaseWeight = resolvedFace->fBaseWeight;
         result->fAPIStyle = computeAPIStyle(weight, italic);
         result->fStyle = computeMinikinStyle(weight, italic);
+        result->fIsVariationInstance = resolvedFace->fIsVariationInstance;
     }
     return result;
 }
@@ -109,6 +111,7 @@
         result->fBaseWeight = resolvedFace->fBaseWeight;
         result->fAPIStyle = resolvedFace->fAPIStyle;
         result->fStyle = resolvedFace->fStyle;
+        result->fIsVariationInstance = true;
     }
     return result;
 }
@@ -121,6 +124,7 @@
         result->fBaseWeight = weight;
         result->fAPIStyle = resolvedFace->fAPIStyle;
         result->fStyle = computeRelativeStyle(weight, result->fAPIStyle);
+        result->fIsVariationInstance = resolvedFace->fIsVariationInstance;
     }
     return result;
 }
@@ -170,6 +174,7 @@
     result->fBaseWeight = weight;
     result->fAPIStyle = computeAPIStyle(weight, italic);
     result->fStyle = computeMinikinStyle(weight, italic);
+    result->fIsVariationInstance = false;
     return result;
 }
 
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index 2c96c1a..97d1bf4 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -44,6 +44,9 @@
     // base weight in CSS-style units, 1..1000
     int fBaseWeight;
 
+    // True if the Typeface is already created for variation settings.
+    bool fIsVariationInstance;
+
     static const Typeface* resolveDefault(const Typeface* src);
 
     // The following three functions create new Typeface from an existing Typeface with a different
diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp
index 20301d2..1c6d886 100644
--- a/libs/hwui/jni/ColorFilter.cpp
+++ b/libs/hwui/jni/ColorFilter.cpp
@@ -163,6 +163,20 @@
             filter->updateChild(env, name.c_str(), child);
         }
     }
+
+    static void RuntimeColorFilter_updateInputColorFilter(JNIEnv* env, jobject,
+                                                          jlong colorFilterPtr, jstring childName,
+                                                          jlong childFilterPtr) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, childName);
+        auto* child = reinterpret_cast<ColorFilter*>(childFilterPtr);
+        if (filter && child) {
+            auto childInput = child->getInstance();
+            if (childInput) {
+                filter->updateChild(env, name.c_str(), childInput.release());
+            }
+        }
+    }
 };
 
 static const JNINativeMethod colorfilter_methods[] = {
@@ -193,7 +207,9 @@
         {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
          (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsInts},
         {"nativeUpdateChild", "(JLjava/lang/String;J)V",
-         (void*)ColorFilterGlue::RuntimeColorFilter_updateChild}};
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateChild},
+        {"nativeUpdateInputColorFilter", "(JLjava/lang/String;J)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateInputColorFilter}};
 
 int register_android_graphics_ColorFilter(JNIEnv* env) {
     android::RegisterMethodsOrDie(env, "android/graphics/ColorFilter", colorfilter_methods,
diff --git a/libs/hwui/jni/RuntimeXfermode.cpp b/libs/hwui/jni/RuntimeXfermode.cpp
index c1c8964..17bee8f 100644
--- a/libs/hwui/jni/RuntimeXfermode.cpp
+++ b/libs/hwui/jni/RuntimeXfermode.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "ColorFilter.h"
 #include "GraphicsJNI.h"
 #include "RuntimeEffectUtils.h"
 #include "SkBlender.h"
@@ -93,6 +94,19 @@
     }
 }
 
+static void RuntimeXfermode_updateColorFilter(JNIEnv* env, jobject, jlong builderPtr,
+                                              jstring childName, jlong colorFilterPtr) {
+    auto* builder = reinterpret_cast<SkRuntimeEffectBuilder*>(builderPtr);
+    ScopedUtfChars name(env, childName);
+    auto* child = reinterpret_cast<ColorFilter*>(colorFilterPtr);
+    if (child) {
+        auto childInput = child->getInstance();
+        if (childInput) {
+            UpdateChild(env, builder, name.c_str(), childInput.release());
+        }
+    }
+}
+
 static const JNINativeMethod gRuntimeXfermodeMethods[] = {
         {"nativeGetFinalizer", "()J", (void*)RuntimeXfermode_getNativeFinalizer},
         {"nativeCreateBlenderBuilder", "(Ljava/lang/String;)J",
@@ -107,6 +121,8 @@
         {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
          (void*)RuntimeXfermode_updateIntUniforms},
         {"nativeUpdateChild", "(JLjava/lang/String;J)V", (void*)RuntimeXfermode_updateChild},
+        {"nativeUpdateColorFilter", "(JLjava/lang/String;J)V",
+         (void*)RuntimeXfermode_updateColorFilter},
 };
 
 int register_android_graphics_RuntimeXfermode(JNIEnv* env) {
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 018c2b13..eadb9de 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,5 +1,6 @@
 #include <vector>
 
+#include "ColorFilter.h"
 #include "Gainmap.h"
 #include "GraphicsJNI.h"
 #include "RuntimeEffectUtils.h"
@@ -331,6 +332,15 @@
     builder->child(name.c_str()) = sk_ref_sp(shader);
 }
 
+static void RuntimeShader_updateColorFilter(JNIEnv* env, jobject, jlong shaderBuilder,
+                                            jstring jUniformName, jlong colorFilterHandle) {
+    SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
+    ScopedUtfChars name(env, jUniformName);
+    auto* childEffect = reinterpret_cast<ColorFilter*>(colorFilterHandle);
+
+    UpdateChild(env, builder, name.c_str(), childEffect->getInstance().release());
+}
+
 static void RuntimeShader_updateChild(JNIEnv* env, jobject, jlong shaderBuilder,
                                       jstring jUniformName, jlong childHandle) {
     SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
@@ -380,6 +390,8 @@
         {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
          (void*)RuntimeShader_updateIntUniforms},
         {"nativeUpdateShader", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateShader},
+        {"nativeUpdateColorFilter", "(JLjava/lang/String;J)V",
+         (void*)RuntimeShader_updateColorFilter},
         {"nativeUpdateChild", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateChild},
 };
 
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index d9dc8eb..a0291a9 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -594,14 +594,12 @@
             Matrix4 transform;
             SkIRect clipBounds;
             uirenderer::Rect initialClipBounds;
-            const auto clipFlags = props.getClippingFlags();
             if (enableClip) {
-                if (clipFlags) {
-                    props.getClippingRectForFlags(clipFlags, &initialClipBounds);
-                } else {
-                    // Works for RenderNode::damageSelf()
-                    initialClipBounds.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
-                }
+                // SurfaceView never draws beyond its bounds regardless of if it can or not,
+                // so if clip-to-bounds is disabled just use the bounds as the starting point
+                // regardless
+                const auto clipFlags = props.getClippingFlags();
+                props.getClippingRectForFlags(clipFlags | CLIP_TO_BOUNDS, &initialClipBounds);
                 clipBounds =
                         info.damageAccumulator
                                 ->computeClipAndTransform(initialClipBounds.toSkRect(), &transform)
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 5f69346..c735989 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -68,7 +68,7 @@
 static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int count,
     int contextStart, int contextCount, minikin::Bidi bidiFlags,
     const Paint& paint, const Typeface* typeface) {
-
+    const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
     minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(&paint, typeface);
 
     minikin::Layout layout = MinikinUtils::doLayout(&paint, bidiFlags, typeface,
@@ -103,8 +103,16 @@
                 fontId = it->second;  // We've seen it.
             } else {
                 fontId = fonts.size();  // This is new to us. Create new one.
-                std::shared_ptr<minikin::Font> font = std::make_shared<minikin::Font>(
-                        fakedFont.font, fakedFont.fakery.variationSettings());
+                std::shared_ptr<minikin::Font> font;
+                if (resolvedFace->fIsVariationInstance) {
+                    // The optimization for target SDK 35 or before because the variation instance
+                    // is already created and no runtime variation resolution happens on such
+                    // environment.
+                    font = fakedFont.font;
+                } else {
+                    font = std::make_shared<minikin::Font>(fakedFont.font,
+                                                           fakedFont.fakery.variationSettings());
+                }
                 fonts.push_back(reinterpret_cast<jlong>(new FontWrapper(std::move(font))));
                 fakedToFontIds.insert(std::make_pair(fakedFont, fontId));
             }
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index d993b87..28d96e3 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -28,12 +28,14 @@
 #define INDENT "  "
 #define INDENT2 "    "
 
+namespace android {
+
 namespace {
+
 // Time to spend fading out the pointer completely.
 const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
-} // namespace
 
-namespace android {
+} // namespace
 
 // --- MouseCursorController ---
 
@@ -64,17 +66,23 @@
     mLocked.pointerSprite.clear();
 }
 
-void MouseCursorController::move(float deltaX, float deltaY) {
+FloatPoint MouseCursorController::move(float deltaX, float deltaY) {
 #if DEBUG_MOUSE_CURSOR_UPDATES
     ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
 #endif
     if (deltaX == 0.0f && deltaY == 0.0f) {
-        return;
+        return {0, 0};
     }
 
+    // When transition occurs, the MouseCursorController object may or may not be deleted, depending
+    // if there's another display on the other side of the transition. At this point we still need
+    // to move the cursor to the boundary.
     std::scoped_lock lock(mLock);
-
-    setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
+    const FloatPoint position{mLocked.pointerX + deltaX, mLocked.pointerY + deltaY};
+    setPositionLocked(position.x, position.y);
+    // The amount of the delta that was not consumed as a result of the cursor
+    // hitting the edge of the display.
+    return {position.x - mLocked.pointerX, position.y - mLocked.pointerY};
 }
 
 void MouseCursorController::setPosition(float x, float y) {
@@ -85,19 +93,23 @@
     setPositionLocked(x, y);
 }
 
-void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
-    const auto& v = mLocked.viewport;
-    if (!v.isValid()) return;
-
+FloatRect MouseCursorController::getBoundsLocked() REQUIRES(mLock) {
     // The valid bounds for a mouse cursor. Since the right and bottom edges are considered outside
     // the display, clip the bounds by one pixel instead of letting the cursor get arbitrarily
     // close to the outside edge.
-    const FloatRect bounds{
+    return FloatRect{
             static_cast<float>(mLocked.viewport.logicalLeft),
             static_cast<float>(mLocked.viewport.logicalTop),
             static_cast<float>(mLocked.viewport.logicalRight - 1),
             static_cast<float>(mLocked.viewport.logicalBottom - 1),
     };
+}
+
+void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
+    const auto& v = mLocked.viewport;
+    if (!v.isValid()) return;
+
+    const FloatRect bounds = getBoundsLocked();
     mLocked.pointerX = std::max(bounds.left, std::min(bounds.right, x));
     mLocked.pointerY = std::max(bounds.top, std::min(bounds.bottom, y));
 
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 12b31a8..e14a55d 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -20,9 +20,6 @@
 #include <gui/DisplayEventReceiver.h>
 #include <input/DisplayViewport.h>
 #include <input/Input.h>
-#include <utils/BitSet.h>
-#include <utils/Looper.h>
-#include <utils/RefBase.h>
 
 #include <functional>
 #include <map>
@@ -43,7 +40,8 @@
     MouseCursorController(PointerControllerContext& context);
     ~MouseCursorController();
 
-    void move(float deltaX, float deltaY);
+    // Move the pointer and return unconsumed delta
+    FloatPoint move(float deltaX, float deltaY);
     void setPosition(float x, float y);
     FloatPoint getPosition() const;
     ui::LogicalDisplayId getDisplayId() const;
@@ -113,6 +111,7 @@
     bool doFadingAnimationLocked(nsecs_t timestamp);
 
     void startAnimationLocked();
+    FloatRect getBoundsLocked();
 };
 
 } // namespace android
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 78d7d3a..a713f1d 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -138,15 +138,19 @@
     return mDisplayInfoListener->mLock;
 }
 
-void PointerController::move(float deltaX, float deltaY) {
+FloatPoint PointerController::move(float deltaX, float deltaY) {
     const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
-    vec2 transformed;
+    ui::Transform transform;
     {
         std::scoped_lock lock(getLock());
-        const auto& transform = getTransformForDisplayLocked(displayId);
-        transformed = transformWithoutTranslation(transform, {deltaX, deltaY});
+        transform = getTransformForDisplayLocked(displayId);
     }
-    mCursorController.move(transformed.x, transformed.y);
+
+    const vec2 transformed = transformWithoutTranslation(transform, {deltaX, deltaY});
+
+    const FloatPoint unconsumedDelta = mCursorController.move(transformed.x, transformed.y);
+    return FloatPoint(transformWithoutTranslation(transform.inverse(),
+                                                  {unconsumedDelta.x, unconsumedDelta.y}));
 }
 
 void PointerController::setPosition(float x, float y) {
@@ -295,6 +299,11 @@
     mCursorController.setSkipScreenshot(false);
 }
 
+ui::Transform PointerController::getDisplayTransform() const {
+    std::scoped_lock lock(getLock());
+    return getTransformForDisplayLocked(mLocked.pointerDisplayId);
+}
+
 void PointerController::doInactivityTimeout() {
     fade(Transition::GRADUAL);
 }
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index ee8d121..8b33190 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -51,7 +51,7 @@
 
     ~PointerController() override;
 
-    void move(float deltaX, float deltaY) override;
+    FloatPoint move(float deltaX, float deltaY) override;
     void setPosition(float x, float y) override;
     FloatPoint getPosition() const override;
     ui::LogicalDisplayId getDisplayId() const override;
@@ -67,6 +67,7 @@
     void setCustomPointerIcon(const SpriteIcon& icon) override;
     void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) override;
     void clearSkipScreenshotFlags() override;
+    ui::Transform getDisplayTransform() const override;
 
     virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
     void doInactivityTimeout();
@@ -165,7 +166,7 @@
 
     ~TouchPointerController() override;
 
-    void move(float, float) override {
+    FloatPoint move(float, float) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
     void setPosition(float, float) override {
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 5b00fca..80c934a 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -40,6 +40,8 @@
     CURSOR_TYPE_CUSTOM = -1,
 };
 
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+
 using ::testing::AllOf;
 using ::testing::Field;
 using ::testing::NiceMock;
@@ -399,6 +401,135 @@
                          testing::Values(PointerControllerInterface::ControllerType::MOUSE,
                                          PointerControllerInterface::ControllerType::STYLUS));
 
+class MousePointerControllerTest : public PointerControllerTest {
+protected:
+    MousePointerControllerTest() {
+        sp<MockSprite> testPointerSprite(new NiceMock<MockSprite>);
+        EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testPointerSprite));
+
+        // create a mouse pointer controller
+        mPointerController =
+                PointerController::create(mPolicy, mLooper, *mSpriteController,
+                                          PointerControllerInterface::ControllerType::MOUSE);
+
+        // set display viewport
+        DisplayViewport viewport;
+        viewport.displayId = ui::LogicalDisplayId::DEFAULT;
+        viewport.logicalRight = 5;
+        viewport.logicalBottom = 5;
+        viewport.physicalRight = 5;
+        viewport.physicalBottom = 5;
+        viewport.deviceWidth = 5;
+        viewport.deviceHeight = 5;
+        mPointerController->setDisplayViewport(viewport);
+    }
+};
+
+struct MousePointerControllerTestParam {
+    vec2 startPosition;
+    vec2 delta;
+    vec2 endPosition;
+    vec2 unconsumedDelta;
+};
+
+class PointerControllerViewportTransitionTest
+      : public MousePointerControllerTest,
+        public testing::WithParamInterface<MousePointerControllerTestParam> {};
+
+TEST_P(PointerControllerViewportTransitionTest, testPointerViewportTransition) {
+    const auto& params = GetParam();
+
+    mPointerController->setPosition(params.startPosition.x, params.startPosition.y);
+    auto unconsumedDelta = mPointerController->move(params.delta.x, params.delta.y);
+
+    auto position = mPointerController->getPosition();
+    EXPECT_NEAR(position.x, params.endPosition.x, EPSILON);
+    EXPECT_NEAR(position.y, params.endPosition.y, EPSILON);
+    EXPECT_NEAR(unconsumedDelta.x, params.unconsumedDelta.x, EPSILON);
+    EXPECT_NEAR(unconsumedDelta.y, params.unconsumedDelta.y, EPSILON);
+}
+
+INSTANTIATE_TEST_SUITE_P(PointerControllerViewportTransitionTest,
+                         PointerControllerViewportTransitionTest,
+                         testing::Values(
+                                 // no transition
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {2.0f, 2.0f},
+                                                                 {4.0f, 4.0f},
+                                                                 {0.0f, 0.0f}},
+                                 // right boundary
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {3.0f, 0.0f},
+                                                                 {4.0f, 2.0f},
+                                                                 {1.0f, 0.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {3.0f, -1.0f},
+                                                                 {4.0f, 1.0f},
+                                                                 {1.0f, 0.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {3.0f, 1.0f},
+                                                                 {4.0f, 3.0f},
+                                                                 {1.0f, 0.0f}},
+                                 // left boundary
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {-3.0f, 0.0f},
+                                                                 {0.0f, 2.0f},
+                                                                 {-1.0f, 0.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {-3.0f, -1.0f},
+                                                                 {0.0f, 1.0f},
+                                                                 {-1.0f, 0.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {-3.0f, 1.0f},
+                                                                 {0.0f, 3.0f},
+                                                                 {-1.0f, 0.0f}},
+                                 // bottom boundary
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {0.0f, 3.0f},
+                                                                 {2.0f, 4.0f},
+                                                                 {0.0f, 1.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {-1.0f, 3.0f},
+                                                                 {1.0f, 4.0f},
+                                                                 {0.0f, 1.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {1.0f, 3.0f},
+                                                                 {3.0f, 4.0f},
+                                                                 {0.0f, 1.0f}},
+                                 // top boundary
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {0.0f, -3.0f},
+                                                                 {2.0f, 0.0f},
+                                                                 {0.0f, -1.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {-1.0f, -3.0f},
+                                                                 {1.0f, 0.0f},
+                                                                 {0.0f, -1.0f}},
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {1.0f, -3.0f},
+                                                                 {3.0f, 0.0f},
+                                                                 {0.0f, -1.0f}},
+                                 // top-left corner
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {-3.0f, -3.0f},
+                                                                 {0.0f, 0.0f},
+                                                                 {-1.0f, -1.0f}},
+                                 // top-right corner
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {3.0f, -3.0f},
+                                                                 {4.0f, 0.0f},
+                                                                 {1.0f, -1.0f}},
+                                 // bottom-right corner
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {3.0f, 3.0f},
+                                                                 {4.0f, 4.0f},
+                                                                 {1.0f, 1.0f}},
+                                 // bottom-left corner
+                                 MousePointerControllerTestParam{{2.0f, 2.0f},
+                                                                 {-3.0f, 3.0f},
+                                                                 {0.0f, 4.0f},
+                                                                 {-1.0f, 1.0f}}));
+
 class PointerControllerWindowInfoListenerTest : public Test {};
 
 TEST_F(PointerControllerWindowInfoListenerTest,
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
index cf3f740..9478e35 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -642,6 +642,14 @@
     method public void onFlushComplete();
   }
 
+  @FlaggedApi("android.location.flags.population_density_provider") public abstract class PopulationDensityProviderBase {
+    ctor public PopulationDensityProviderBase(@NonNull android.content.Context, @NonNull String);
+    method @Nullable public final android.os.IBinder getBinder();
+    method public abstract void onGetCoarsenedS2Cells(double, double, @IntRange(from=0) int, @NonNull android.os.OutcomeReceiver<long[],java.lang.Throwable>);
+    method public abstract void onGetDefaultCoarseningLevel(@NonNull android.os.OutcomeReceiver<java.lang.Integer,java.lang.Throwable>);
+    field public static final String ACTION_POPULATION_DENSITY_PROVIDER = "com.android.location.service.PopulationDensityProvider";
+  }
+
   public final class ProviderRequest implements android.os.Parcelable {
     method public int describeContents();
     method @IntRange(from=0) public long getIntervalMillis();
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
index b9fe804..3a4e70d 100644
--- a/location/java/android/location/altitude/AltitudeConverter.java
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -26,8 +26,8 @@
 import android.location.flags.Flags;
 
 import com.android.internal.location.altitude.GeoidMap;
-import com.android.internal.location.altitude.S2CellIdUtils;
 import com.android.internal.location.altitude.nano.MapParamsProto;
+import com.android.internal.location.geometry.S2CellIdUtils;
 import com.android.internal.util.Preconditions;
 
 import java.io.IOException;
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 24e1d32..5395206 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -6,6 +6,7 @@
     namespace: "location"
     description: "Deprecates LocationManager ProviderChanged APIs"
     bug: "361811782"
+    is_exported: true
 }
 
 flag {
@@ -27,6 +28,7 @@
     namespace: "location"
     description: "Flag for new Geocoder APIs"
     bug: "229872126"
+    is_exported: true
 }
 
 flag {
@@ -56,6 +58,7 @@
     namespace: "location"
     description: "Flag for making geoid heights available via the Altitude HAL"
     bug: "304375846"
+    is_exported: true
 }
 
 flag {
@@ -63,6 +66,7 @@
     namespace: "location"
     description: "Flag for GNSS API for NavIC L1"
     bug: "302199306"
+    is_exported: true
 }
 
 flag {
@@ -70,6 +74,7 @@
     namespace: "location"
     description: "Flag for GnssMeasurementRequest WorkSource API"
     bug: "295235160"
+    is_exported: true
 }
 
 flag {
@@ -129,6 +134,7 @@
     metadata {
         purpose: PURPOSE_BUGFIX
     }
+    is_exported: true
 }
 
 flag {
diff --git a/location/java/android/location/provider/IPopulationDensityProvider.aidl b/location/java/android/location/provider/IPopulationDensityProvider.aidl
new file mode 100644
index 0000000..41fe500
--- /dev/null
+++ b/location/java/android/location/provider/IPopulationDensityProvider.aidl
@@ -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 android.location.provider;
+
+import android.os.Bundle;
+
+import android.location.Location;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+
+/**
+ * Binder interface for services that implement a population density provider. Do not implement this
+ * directly, extend {@link PopulationDensityProviderBase} instead.
+ * @hide
+ */
+oneway interface IPopulationDensityProvider {
+    /**
+     * Gets the default S2 level to be used to coarsen any location, in case a more precise answer
+     * from the method below can't be obtained.
+     */
+    void getDefaultCoarseningLevel(in IS2LevelCallback callback);
+
+    /**
+     * Requests a list of IDs of the S2 cells to be used to coarsen a location. The answer should
+     * contain at least one S2 cell, which should contain the requested location. Its level
+     * represents the population density. Optionally, if numAdditionalCells is greater than 0,
+     * additional nearby cells can be also returned, to assist in coarsening nearby locations.
+     */
+    void getCoarsenedS2Cells(double latitudeDegrees, double longitudeDegrees,
+        int numAdditionalCells, in IS2CellIdsCallback callback);
+}
diff --git a/location/java/android/location/provider/IS2CellIdsCallback.aidl b/location/java/android/location/provider/IS2CellIdsCallback.aidl
new file mode 100644
index 0000000..f583045
--- /dev/null
+++ b/location/java/android/location/provider/IS2CellIdsCallback.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.provider;
+
+import android.location.Location;
+
+/**
+ * Binder interface for S2 cell IDs callbacks.
+ * @hide
+ */
+oneway interface IS2CellIdsCallback {
+
+    /**
+     * Called with the resulting list of S2 cell IDs. The first cell is expected to contain
+     * the requested latitude/longitude. Its level represent the population density. Optionally,
+     * the list can also contain additional nearby cells.
+     */
+    void onResult(in long[] s2CellIds);
+
+    /** Called if any error occurs while processing the query. */
+    void onError();
+}
diff --git a/location/java/android/location/provider/IS2LevelCallback.aidl b/location/java/android/location/provider/IS2LevelCallback.aidl
new file mode 100644
index 0000000..49f96ef
--- /dev/null
+++ b/location/java/android/location/provider/IS2LevelCallback.aidl
@@ -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 android.location.provider;
+
+import android.location.Location;
+
+/**
+ * Binder interface for S2 level callback.
+ * @hide
+ */
+oneway interface IS2LevelCallback {
+    /**
+     * Called with the resulting default S2 level for coarsening a location, in case a better
+     * answer cannot be obtained for a latitude/longitude.
+     */
+    void onResult(int s2Level);
+
+    /** Called if any error occurs while processing the query. */
+    void onError();
+}
diff --git a/location/java/android/location/provider/PopulationDensityProviderBase.java b/location/java/android/location/provider/PopulationDensityProviderBase.java
new file mode 100644
index 0000000..0177cf8
--- /dev/null
+++ b/location/java/android/location/provider/PopulationDensityProviderBase.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.provider;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.location.flags.Flags;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A provider for population density.
+ * The population density is defined as the S2 level at which the S2 cell around the latitude /
+ * longitude contains at least a thousand people.
+ * It exposes two methods: one about providing population density around a latitude / longitude,
+ * and one about providing a "default" population density to fall back to in case the first API
+ * can't be used or returns an error.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_POPULATION_DENSITY_PROVIDER)
+public abstract class PopulationDensityProviderBase {
+
+    final String mTag;
+    final @Nullable String mAttributionTag;
+    final IBinder mBinder;
+
+    /**
+     * The action the wrapping service should have in its intent filter to implement the
+     * PopulationDensity provider.
+     */
+    @SuppressLint("ActionValue")
+    public static final String ACTION_POPULATION_DENSITY_PROVIDER =
+            "com.android.location.service.PopulationDensityProvider";
+
+    public PopulationDensityProviderBase(@NonNull Context context, @NonNull String tag) {
+        mTag = tag;
+        mAttributionTag = context.getAttributionTag();
+        mBinder = new Service();
+    }
+
+    /**
+     * Returns the IBinder instance that should be returned from the
+     * {@link android.app.Service#onBind(Intent)} method of the wrapping service.
+     */
+    public final @Nullable IBinder getBinder() {
+        return mBinder;
+    }
+
+    /**
+     * Called upon receiving a new request for the default coarsening level.
+     * The callback {@link OutcomeReceiver#onResult} should be called with the result; or, in case
+     * an error occurs, {@link OutcomeReceiver#onError} should be called.
+     * The callback is single-use, calling more than any one of these two methods throws an
+     * AssertionException.
+     *
+     * @param callback A single-use callback that either returns the coarsening level, or an error.
+     */
+    public abstract void onGetDefaultCoarseningLevel(@NonNull OutcomeReceiver<Integer, Throwable>
+            callback);
+
+    /**
+     * Called upon receiving a new request for population density at a specific latitude/longitude,
+     * expressed in degrees.
+     * The answer is at least one S2CellId corresponding to the coarsening level at the specified
+     * location. This must be the first element of the result array. Optionally, if
+     * numAdditionalCells is greater than zero, additional nearby S2CellIds can be returned. One use
+     * for the optional nearby cells is when the client has a local cache that needs to be filled
+     * with the local area around a certain latitude/longitude. The callback
+     * {@link OutcomeReceiver#onResult} should be called with the result; or, in case an error
+     * occurs, {@link OutcomeReceiver#onError} should be called. The callback is single-use, calling
+     * more than any one of these two methods throws an AssertionException.
+     *
+     * @param callback A single-use callback that either returns S2CellIds, or an error.
+     */
+    public abstract void onGetCoarsenedS2Cells(double latitudeDegrees, double longitudeDegrees,
+            @IntRange(from = 0) int numAdditionalCells,
+            @NonNull OutcomeReceiver<long[], Throwable> callback);
+
+    private final class Service extends IPopulationDensityProvider.Stub {
+        @Override
+        public void getDefaultCoarseningLevel(@NonNull IS2LevelCallback callback) {
+            try {
+                onGetDefaultCoarseningLevel(new SingleUseS2LevelCallback(callback));
+            } catch (RuntimeException e) {
+                // exceptions on one-way binder threads are dropped - move to a different thread
+                Log.w(mTag, e);
+                new Handler(Looper.getMainLooper())
+                        .post(
+                                () -> {
+                                    throw new AssertionError(e);
+                                });
+            }
+        }
+
+        @Override
+        public void getCoarsenedS2Cells(double latitudeDegrees, double longitudeDegrees,
+                int numAdditionalCells, @NonNull IS2CellIdsCallback callback) {
+            try {
+                onGetCoarsenedS2Cells(latitudeDegrees, longitudeDegrees, numAdditionalCells,
+                        new SingleUseS2CellIdsCallback(callback));
+            } catch (RuntimeException e) {
+                // exceptions on one-way binder threads are dropped - move to a different thread
+                Log.w(mTag, e);
+                new Handler(Looper.getMainLooper())
+                        .post(
+                                () -> {
+                                    throw new AssertionError(e);
+                                });
+            }
+        }
+    }
+
+    private static class SingleUseS2LevelCallback implements OutcomeReceiver<Integer, Throwable> {
+
+        private final AtomicReference<IS2LevelCallback> mCallback;
+
+        SingleUseS2LevelCallback(IS2LevelCallback callback) {
+            mCallback = new AtomicReference<>(callback);
+        }
+
+        @Override
+        public void onResult(Integer level) {
+            try {
+                Objects.requireNonNull(mCallback.getAndSet(null)).onResult(level);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        @Override
+        public void onError(Throwable e) {
+            try {
+                Objects.requireNonNull(mCallback.getAndSet(null)).onError();
+            } catch (RemoteException r) {
+                throw r.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    private static class SingleUseS2CellIdsCallback implements OutcomeReceiver<long[], Throwable> {
+
+        private final AtomicReference<IS2CellIdsCallback> mCallback;
+
+        SingleUseS2CellIdsCallback(IS2CellIdsCallback callback) {
+            mCallback = new AtomicReference<>(callback);
+        }
+
+        @Override
+        public void onResult(long[] s2CellIds) {
+            try {
+                Objects.requireNonNull(mCallback.getAndSet(null)).onResult(s2CellIds);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        @Override
+        public void onError(Throwable e) {
+            try {
+                Objects.requireNonNull(mCallback.getAndSet(null)).onError();
+            } catch (RemoteException r) {
+                throw r.rethrowFromSystemServer();
+            }
+        }
+    }
+}
diff --git a/location/java/com/android/internal/location/altitude/GeoidMap.java b/location/java/com/android/internal/location/altitude/GeoidMap.java
index df9ca97..d77fb9e 100644
--- a/location/java/com/android/internal/location/altitude/GeoidMap.java
+++ b/location/java/com/android/internal/location/altitude/GeoidMap.java
@@ -26,6 +26,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.location.altitude.nano.MapParamsProto;
 import com.android.internal.location.altitude.nano.S2TileProto;
+import com.android.internal.location.geometry.S2CellIdUtils;
 import com.android.internal.util.Preconditions;
 
 import java.io.IOException;
diff --git a/location/java/com/android/internal/location/altitude/S2CellIdUtils.java b/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
deleted file mode 100644
index 08bcda4..0000000
--- a/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
+++ /dev/null
@@ -1,657 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.location.altitude;
-
-import android.annotation.NonNull;
-
-import java.util.Arrays;
-import java.util.Locale;
-
-/**
- * Provides lightweight S2 cell ID utilities without traditional geometry dependencies.
- *
- * <p>See <a href="https://s2geometry.io/">the S2 Geometry Library website</a> for more details.
- */
-public final class S2CellIdUtils {
-
-    /** The level of all leaf S2 cells. */
-    public static final int MAX_LEVEL = 30;
-
-    private static final int MAX_SIZE = 1 << MAX_LEVEL;
-    private static final double ONE_OVER_MAX_SIZE = 1.0 / MAX_SIZE;
-    private static final int NUM_FACES = 6;
-    private static final int POS_BITS = 2 * MAX_LEVEL + 1;
-    private static final int SWAP_MASK = 0x1;
-    private static final int LOOKUP_BITS = 4;
-    private static final int LOOKUP_MASK = (1 << LOOKUP_BITS) - 1;
-    private static final int INVERT_MASK = 0x2;
-    private static final int LEAF_MASK = 0x1;
-    private static final int[] LOOKUP_POS = new int[1 << (2 * LOOKUP_BITS + 2)];
-    private static final int[] LOOKUP_IJ = new int[1 << (2 * LOOKUP_BITS + 2)];
-    private static final int[] POS_TO_ORIENTATION = {SWAP_MASK, 0, 0, INVERT_MASK + SWAP_MASK};
-    private static final int[][] POS_TO_IJ =
-            {{0, 1, 3, 2}, {0, 2, 3, 1}, {3, 2, 0, 1}, {3, 1, 0, 2}};
-    private static final double UV_LIMIT = calculateUvLimit();
-    private static final UvTransform[] UV_TRANSFORMS = createUvTransforms();
-    private static final XyzTransform[] XYZ_TRANSFORMS = createXyzTransforms();
-
-    // Used to encode (i, j, o) coordinates into primitive longs.
-    private static final int I_SHIFT = 33;
-    private static final int J_SHIFT = 2;
-    private static final long J_MASK = (1L << 31) - 1;
-
-    static {
-        initLookupCells();
-    }
-
-    /** Prevents instantiation. */
-    private S2CellIdUtils() {
-    }
-
-    /**
-     * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
-     * degrees.
-     */
-    public static long fromLatLngDegrees(double latDegrees, double lngDegrees) {
-        return fromLatLngRadians(Math.toRadians(latDegrees), Math.toRadians(lngDegrees));
-    }
-
-    /** Returns the leaf S2 cell ID of the specified (face, i, j) coordinate. */
-    public static long fromFij(int face, int i, int j) {
-        int bits = (face & SWAP_MASK);
-        // Update most significant bits.
-        long msb = ((long) face) << (POS_BITS - 33);
-        for (int k = 7; k >= 4; --k) {
-            bits = lookupBits(i, j, k, bits);
-            msb = updateBits(msb, k, bits);
-            bits = maskBits(bits);
-        }
-        // Update least significant bits.
-        long lsb = 0;
-        for (int k = 3; k >= 0; --k) {
-            bits = lookupBits(i, j, k, bits);
-            lsb = updateBits(lsb, k, bits);
-            bits = maskBits(bits);
-        }
-        return (((msb << 32) + lsb) << 1) + 1;
-    }
-
-    /**
-     * Returns the face of the specified S2 cell. The returned face is in [0, 5] for valid S2 cell
-     * IDs. Behavior is undefined for invalid S2 cell IDs.
-     */
-    public static int getFace(long s2CellId) {
-        return (int) (s2CellId >>> POS_BITS);
-    }
-
-    /**
-     * Returns the ID of the parent of the specified S2 cell at the specified parent level.
-     * Behavior is undefined for invalid S2 cell IDs or parent levels not in
-     * [0, {@code getLevel(s2CellId)}[.
-     */
-    public static long getParent(long s2CellId, int level) {
-        long newLsb = getLowestOnBitForLevel(level);
-        return (s2CellId & -newLsb) | newLsb;
-    }
-
-    /**
-     * Inserts into {@code neighbors} the four S2 cell IDs corresponding to the neighboring
-     * cells adjacent across the specified cell's four edges. This array must be of minimum
-     * length four, and elements at the tail end of the array not corresponding to a neighbor
-     * are set to zero. A reference to this array is returned.
-     *
-     * <p>Inserts in the order of down, right, up, and left directions, in that order. All
-     * neighbors are guaranteed to be distinct.
-     */
-    public static void getEdgeNeighbors(long s2CellId, @NonNull long[] neighbors) {
-        int level = getLevel(s2CellId);
-        int size = levelToSizeIj(level);
-        int face = getFace(s2CellId);
-        long ijo = toIjo(s2CellId);
-        int i = ijoToI(ijo);
-        int j = ijoToJ(ijo);
-
-        int iPlusSize = i + size;
-        int iMinusSize = i - size;
-        int jPlusSize = j + size;
-        int jMinusSize = j - size;
-        boolean iPlusSizeLtMax = iPlusSize < MAX_SIZE;
-        boolean iMinusSizeGteZero = iMinusSize >= 0;
-        boolean jPlusSizeLtMax = jPlusSize < MAX_SIZE;
-        boolean jMinusSizeGteZero = jMinusSize >= 0;
-
-        int index = 0;
-        // Down direction.
-        neighbors[index++] = getParent(fromFijSame(face, i, jMinusSize, jMinusSizeGteZero),
-                level);
-        // Right direction.
-        neighbors[index++] = getParent(fromFijSame(face, iPlusSize, j, iPlusSizeLtMax), level);
-        // Up direction.
-        neighbors[index++] = getParent(fromFijSame(face, i, jPlusSize, jPlusSizeLtMax), level);
-        // Left direction.
-        neighbors[index++] = getParent(fromFijSame(face, iMinusSize, j, iMinusSizeGteZero),
-                level);
-
-        // Pad end of neighbor array with zeros.
-        Arrays.fill(neighbors, index, neighbors.length, 0);
-    }
-
-    /** Returns the "i" coordinate for the specified S2 cell. */
-    public static int getI(long s2CellId) {
-        return ijoToI(toIjo(s2CellId));
-    }
-
-    /** Returns the "j" coordinate for the specified S2 cell. */
-    public static int getJ(long s2CellId) {
-        return ijoToJ(toIjo(s2CellId));
-    }
-
-    /**
-     * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
-     * radians.
-     */
-    private static long fromLatLngRadians(double latRadians, double lngRadians) {
-        double cosLat = Math.cos(latRadians);
-        double x = Math.cos(lngRadians) * cosLat;
-        double y = Math.sin(lngRadians) * cosLat;
-        double z = Math.sin(latRadians);
-        return fromXyz(x, y, z);
-    }
-
-    /**
-     * Returns the level of the specified S2 cell. The returned level is in [0, 30] for valid
-     * S2 cell IDs. Behavior is undefined for invalid S2 cell IDs.
-     */
-    static int getLevel(long s2CellId) {
-        if (isLeaf(s2CellId)) {
-            return MAX_LEVEL;
-        }
-        return MAX_LEVEL - (Long.numberOfTrailingZeros(s2CellId) >> 1);
-    }
-
-    /** Returns the lowest-numbered bit that is on for the specified S2 cell. */
-    static long getLowestOnBit(long s2CellId) {
-        return s2CellId & -s2CellId;
-    }
-
-    /** Returns the lowest-numbered bit that is on for any S2 cell on the specified level. */
-    static long getLowestOnBitForLevel(int level) {
-        return 1L << (2 * (MAX_LEVEL - level));
-    }
-
-    /**
-     * Returns the ID of the first S2 cell in a traversal of the children S2 cells at the specified
-     * level, in Hilbert curve order.
-     */
-    static long getTraversalStart(long s2CellId, int level) {
-        return s2CellId - getLowestOnBit(s2CellId) + getLowestOnBitForLevel(level);
-    }
-
-    /** Returns the ID of the next S2 cell at the same level along the Hilbert curve. */
-    static long getTraversalNext(long s2CellId) {
-        return s2CellId + (getLowestOnBit(s2CellId) << 1);
-    }
-
-    /**
-     * Encodes the S2 cell id to compact text strings suitable for display or indexing. Cells at
-     * lower levels (i.e., larger cells) are encoded into fewer characters.
-     */
-    @NonNull
-    static String getToken(long s2CellId) {
-        if (s2CellId == 0) {
-            return "X";
-        }
-
-        // Convert to a hex string with as many digits as necessary.
-        String hex = Long.toHexString(s2CellId).toLowerCase(Locale.US);
-        // Prefix 0s to get a length 16 string.
-        String padded = padStart(hex);
-        // Trim zeroes off the end.
-        return padded.replaceAll("0*$", "");
-    }
-
-    private static String padStart(String string) {
-        if (string.length() >= 16) {
-            return string;
-        }
-        return "0".repeat(16 - string.length()) + string;
-    }
-
-    /** Returns the leaf S2 cell ID of the specified (x, y, z) coordinate. */
-    private static long fromXyz(double x, double y, double z) {
-        int face = xyzToFace(x, y, z);
-        UvTransform uvTransform = UV_TRANSFORMS[face];
-        double u = uvTransform.xyzToU(x, y, z);
-        double v = uvTransform.xyzToV(x, y, z);
-        return fromFuv(face, u, v);
-    }
-
-    /** Returns the leaf S2 cell ID of the specified (face, u, v) coordinate. */
-    private static long fromFuv(int face, double u, double v) {
-        int i = uToI(u);
-        int j = vToJ(v);
-        return fromFij(face, i, j);
-    }
-
-    private static long fromFijWrap(int face, int i, int j) {
-        double u = iToU(i);
-        double v = jToV(j);
-
-        XyzTransform xyzTransform = XYZ_TRANSFORMS[face];
-        double x = xyzTransform.uvToX(u, v);
-        double y = xyzTransform.uvToY(u, v);
-        double z = xyzTransform.uvToZ(u, v);
-
-        int newFace = xyzToFace(x, y, z);
-        UvTransform uvTransform = UV_TRANSFORMS[newFace];
-        double newU = uvTransform.xyzToU(x, y, z);
-        double newV = uvTransform.xyzToV(x, y, z);
-
-        int newI = uShiftIntoI(newU);
-        int newJ = vShiftIntoJ(newV);
-        return fromFij(newFace, newI, newJ);
-    }
-
-    private static long fromFijSame(int face, int i, int j, boolean isSameFace) {
-        if (isSameFace) {
-            return fromFij(face, i, j);
-        }
-        return fromFijWrap(face, i, j);
-    }
-
-    /**
-     * Returns the face associated with the specified (x, y, z) coordinate. For a coordinate
-     * on a face boundary, the returned face is arbitrary but repeatable.
-     */
-    private static int xyzToFace(double x, double y, double z) {
-        double absX = Math.abs(x);
-        double absY = Math.abs(y);
-        double absZ = Math.abs(z);
-        if (absX > absY) {
-            if (absX > absZ) {
-                return (x < 0) ? 3 : 0;
-            }
-            return (z < 0) ? 5 : 2;
-        }
-        if (absY > absZ) {
-            return (y < 0) ? 4 : 1;
-        }
-        return (z < 0) ? 5 : 2;
-    }
-
-    private static int uToI(double u) {
-        double s;
-        if (u >= 0) {
-            s = 0.5 * Math.sqrt(1 + 3 * u);
-        } else {
-            s = 1 - 0.5 * Math.sqrt(1 - 3 * u);
-        }
-        return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
-    }
-
-    private static int vToJ(double v) {
-        // Same calculation as uToI.
-        return uToI(v);
-    }
-
-    private static int lookupBits(int i, int j, int k, int bits) {
-        bits += ((i >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << (LOOKUP_BITS + 2);
-        bits += ((j >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << 2;
-        return LOOKUP_POS[bits];
-    }
-
-    private static long updateBits(long sb, int k, int bits) {
-        return sb | ((((long) bits) >> 2) << ((k & 0x3) * 2 * LOOKUP_BITS));
-    }
-
-    private static int maskBits(int bits) {
-        return bits & (SWAP_MASK | INVERT_MASK);
-    }
-
-    private static boolean isLeaf(long s2CellId) {
-        return ((int) s2CellId & LEAF_MASK) != 0;
-    }
-
-    private static double iToU(int i) {
-        int satI = Math.max(-1, Math.min(MAX_SIZE, i));
-        return Math.max(
-                -UV_LIMIT,
-                Math.min(UV_LIMIT, ONE_OVER_MAX_SIZE * ((satI << 1) + 1 - MAX_SIZE)));
-    }
-
-    private static double jToV(int j) {
-        // Same calculation as iToU.
-        return iToU(j);
-    }
-
-    private static long toIjo(long s2CellId) {
-        int face = getFace(s2CellId);
-        int bits = face & SWAP_MASK;
-        int i = 0;
-        int j = 0;
-        for (int k = 7; k >= 0; --k) {
-            int nbits = (k == 7) ? (MAX_LEVEL - 7 * LOOKUP_BITS) : LOOKUP_BITS;
-            bits += ((int) (s2CellId >>> (k * 2 * LOOKUP_BITS + 1)) & ((1 << (2 * nbits))
-                    - 1)) << 2;
-            bits = LOOKUP_IJ[bits];
-            i += (bits >> (LOOKUP_BITS + 2)) << (k * LOOKUP_BITS);
-            j += ((bits >> 2) & ((1 << LOOKUP_BITS) - 1)) << (k * LOOKUP_BITS);
-            bits &= (SWAP_MASK | INVERT_MASK);
-        }
-        int orientation =
-                ((getLowestOnBit(s2CellId) & 0x1111111111111110L) != 0) ? (bits ^ SWAP_MASK)
-                        : bits;
-        return (((long) i) << I_SHIFT) | (((long) j) << J_SHIFT) | orientation;
-    }
-
-    private static int ijoToI(long ijo) {
-        return (int) (ijo >>> I_SHIFT);
-    }
-
-    private static int ijoToJ(long ijo) {
-        return (int) ((ijo >>> J_SHIFT) & J_MASK);
-    }
-
-    private static int uShiftIntoI(double u) {
-        double s = 0.5 * (u + 1);
-        return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
-    }
-
-    private static int vShiftIntoJ(double v) {
-        // Same calculation as uShiftIntoI.
-        return uShiftIntoI(v);
-    }
-
-    private static int levelToSizeIj(int level) {
-        return 1 << (MAX_LEVEL - level);
-    }
-
-    private static void initLookupCells() {
-        initLookupCell(0, 0, 0, 0, 0, 0);
-        initLookupCell(0, 0, 0, SWAP_MASK, 0, SWAP_MASK);
-        initLookupCell(0, 0, 0, INVERT_MASK, 0, INVERT_MASK);
-        initLookupCell(0, 0, 0, SWAP_MASK | INVERT_MASK, 0, SWAP_MASK | INVERT_MASK);
-    }
-
-    private static void initLookupCell(
-            int level, int i, int j, int origOrientation, int pos, int orientation) {
-        if (level == LOOKUP_BITS) {
-            int ij = (i << LOOKUP_BITS) + j;
-            LOOKUP_POS[(ij << 2) + origOrientation] = (pos << 2) + orientation;
-            LOOKUP_IJ[(pos << 2) + origOrientation] = (ij << 2) + orientation;
-        } else {
-            level++;
-            i <<= 1;
-            j <<= 1;
-            pos <<= 2;
-            for (int subPos = 0; subPos < 4; subPos++) {
-                int ij = POS_TO_IJ[orientation][subPos];
-                int orientationMask = POS_TO_ORIENTATION[subPos];
-                initLookupCell(
-                        level,
-                        i + (ij >>> 1),
-                        j + (ij & 0x1),
-                        origOrientation,
-                        pos + subPos,
-                        orientation ^ orientationMask);
-            }
-        }
-    }
-
-    private static double calculateUvLimit() {
-        double machEps = 1.0;
-        do {
-            machEps /= 2.0f;
-        } while ((1.0 + (machEps / 2.0)) != 1.0);
-        return 1.0 + machEps;
-    }
-
-    @NonNull
-    private static UvTransform[] createUvTransforms() {
-        UvTransform[] uvTransforms = new UvTransform[NUM_FACES];
-        uvTransforms[0] =
-                new UvTransform() {
-
-                    @Override
-                    public double xyzToU(double x, double y, double z) {
-                        return y / x;
-                    }
-
-                    @Override
-                    public double xyzToV(double x, double y, double z) {
-                        return z / x;
-                    }
-                };
-        uvTransforms[1] =
-                new UvTransform() {
-
-                    @Override
-                    public double xyzToU(double x, double y, double z) {
-                        return -x / y;
-                    }
-
-                    @Override
-                    public double xyzToV(double x, double y, double z) {
-                        return z / y;
-                    }
-                };
-        uvTransforms[2] =
-                new UvTransform() {
-
-                    @Override
-                    public double xyzToU(double x, double y, double z) {
-                        return -x / z;
-                    }
-
-                    @Override
-                    public double xyzToV(double x, double y, double z) {
-                        return -y / z;
-                    }
-                };
-        uvTransforms[3] =
-                new UvTransform() {
-
-                    @Override
-                    public double xyzToU(double x, double y, double z) {
-                        return z / x;
-                    }
-
-                    @Override
-                    public double xyzToV(double x, double y, double z) {
-                        return y / x;
-                    }
-                };
-        uvTransforms[4] =
-                new UvTransform() {
-
-                    @Override
-                    public double xyzToU(double x, double y, double z) {
-                        return z / y;
-                    }
-
-                    @Override
-                    public double xyzToV(double x, double y, double z) {
-                        return -x / y;
-                    }
-                };
-        uvTransforms[5] =
-                new UvTransform() {
-
-                    @Override
-                    public double xyzToU(double x, double y, double z) {
-                        return -y / z;
-                    }
-
-                    @Override
-                    public double xyzToV(double x, double y, double z) {
-                        return -x / z;
-                    }
-                };
-        return uvTransforms;
-    }
-
-    @NonNull
-    private static XyzTransform[] createXyzTransforms() {
-        XyzTransform[] xyzTransforms = new XyzTransform[NUM_FACES];
-        xyzTransforms[0] =
-                new XyzTransform() {
-
-                    @Override
-                    public double uvToX(double u, double v) {
-                        return 1;
-                    }
-
-                    @Override
-                    public double uvToY(double u, double v) {
-                        return u;
-                    }
-
-                    @Override
-                    public double uvToZ(double u, double v) {
-                        return v;
-                    }
-                };
-        xyzTransforms[1] =
-                new XyzTransform() {
-
-                    @Override
-                    public double uvToX(double u, double v) {
-                        return -u;
-                    }
-
-                    @Override
-                    public double uvToY(double u, double v) {
-                        return 1;
-                    }
-
-                    @Override
-                    public double uvToZ(double u, double v) {
-                        return v;
-                    }
-                };
-        xyzTransforms[2] =
-                new XyzTransform() {
-
-                    @Override
-                    public double uvToX(double u, double v) {
-                        return -u;
-                    }
-
-                    @Override
-                    public double uvToY(double u, double v) {
-                        return -v;
-                    }
-
-                    @Override
-                    public double uvToZ(double u, double v) {
-                        return 1;
-                    }
-                };
-        xyzTransforms[3] =
-                new XyzTransform() {
-
-                    @Override
-                    public double uvToX(double u, double v) {
-                        return -1;
-                    }
-
-                    @Override
-                    public double uvToY(double u, double v) {
-                        return -v;
-                    }
-
-                    @Override
-                    public double uvToZ(double u, double v) {
-                        return -u;
-                    }
-                };
-        xyzTransforms[4] =
-                new XyzTransform() {
-
-                    @Override
-                    public double uvToX(double u, double v) {
-                        return v;
-                    }
-
-                    @Override
-                    public double uvToY(double u, double v) {
-                        return -1;
-                    }
-
-                    @Override
-                    public double uvToZ(double u, double v) {
-                        return -u;
-                    }
-                };
-        xyzTransforms[5] =
-                new XyzTransform() {
-
-                    @Override
-                    public double uvToX(double u, double v) {
-                        return v;
-                    }
-
-                    @Override
-                    public double uvToY(double u, double v) {
-                        return u;
-                    }
-
-                    @Override
-                    public double uvToZ(double u, double v) {
-                        return -1;
-                    }
-                };
-        return xyzTransforms;
-    }
-
-    /**
-     * Transform from (x, y, z) coordinates to (u, v) coordinates, indexed by face. For a
-     * (x, y, z) coordinate within a face, each element of the resulting (u, v) coordinate
-     * should lie in the inclusive range [-1, 1], with the face center having a (u, v)
-     * coordinate equal to (0, 0).
-     */
-    private interface UvTransform {
-
-        /**
-         * Returns for the specified (x, y, z) coordinate the corresponding u-coordinate
-         * (which may lie outside the range [-1, 1]).
-         */
-        double xyzToU(double x, double y, double z);
-
-        /**
-         * Returns for the specified (x, y, z) coordinate the corresponding v-coordinate
-         * (which may lie outside the range [-1, 1]).
-         */
-        double xyzToV(double x, double y, double z);
-    }
-
-    /**
-     * Transform from (u, v) coordinates to (x, y, z) coordinates, indexed by face. The
-     * resulting vectors are not necessarily of unit length.
-     */
-    private interface XyzTransform {
-
-        /** Returns for the specified (u, v) coordinate the corresponding x-coordinate. */
-        double uvToX(double u, double v);
-
-        /** Returns for the specified (u, v) coordinate the corresponding y-coordinate. */
-        double uvToY(double u, double v);
-
-        /** Returns for the specified (u, v) coordinate the corresponding z-coordinate. */
-        double uvToZ(double u, double v);
-    }
-}
diff --git a/location/java/com/android/internal/location/geometry/S2CellIdUtils.java b/location/java/com/android/internal/location/geometry/S2CellIdUtils.java
new file mode 100644
index 0000000..fbdaf49
--- /dev/null
+++ b/location/java/com/android/internal/location/geometry/S2CellIdUtils.java
@@ -0,0 +1,791 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.location.geometry;
+
+import android.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Provides lightweight S2 cell ID utilities without traditional geometry dependencies.
+ *
+ * <p>See <a href="https://s2geometry.io/">the S2 Geometry Library website</a> for more details.
+ */
+public final class S2CellIdUtils {
+
+    /** The level of all leaf S2 cells. */
+    public static final int MAX_LEVEL = 30;
+
+    private static final int MAX_SIZE = 1 << MAX_LEVEL;
+    private static final double ONE_OVER_MAX_SIZE = 1.0 / MAX_SIZE;
+    private static final int NUM_FACES = 6;
+    private static final int POS_BITS = 2 * MAX_LEVEL + 1;
+    private static final int SWAP_MASK = 0x1;
+    private static final int LOOKUP_BITS = 4;
+    private static final int LOOKUP_MASK = (1 << LOOKUP_BITS) - 1;
+    private static final int INVERT_MASK = 0x2;
+    private static final int LEAF_MASK = 0x1;
+    private static final int[] LOOKUP_POS = new int[1 << (2 * LOOKUP_BITS + 2)];
+    private static final int[] LOOKUP_IJ = new int[1 << (2 * LOOKUP_BITS + 2)];
+    private static final int[] POS_TO_ORIENTATION = {SWAP_MASK, 0, 0, INVERT_MASK + SWAP_MASK};
+    private static final int[][] POS_TO_IJ =
+            {{0, 1, 3, 2}, {0, 2, 3, 1}, {3, 2, 0, 1}, {3, 1, 0, 2}};
+    private static final double UV_LIMIT = calculateUvLimit();
+    private static final UvTransform[] UV_TRANSFORMS = createUvTransforms();
+    private static final XyzTransform[] XYZ_TRANSFORMS = createXyzTransforms();
+    private static final long MAX_SI_TI = 1L << (MAX_LEVEL + 1);
+
+    // Used to encode (i, j, o) coordinates into primitive longs.
+    private static final int I_SHIFT = 33;
+    private static final int J_SHIFT = 2;
+    private static final long J_MASK = (1L << 31) - 1;
+
+    // Used to insert latitude and longitude values into arrays.
+    public static final int LAT_LNG_MIN_LENGTH = 2;
+    public static final int LAT_INDEX = 0;
+    public static final int LNG_INDEX = 1;
+
+    // Used to encode (si, ti) coordinates into primitive longs.
+    private static final int SI_SHIFT = 32;
+    private static final long TI_MASK = (1L << 32) - 1;
+
+    static {
+        initLookupCells();
+    }
+
+    /** Prevents instantiation. */
+    private S2CellIdUtils() {
+    }
+
+    /**
+     * Inserts into {@code latLngDegrees} the centroid latitude and longitude, in that order and
+     * both measured in degrees, for the specified S2 cell ID. This array must be non-null and of
+     * minimum length two. A reference to this array is returned.
+     *
+     * <p>Behavior is undefined for invalid S2 cell IDs.
+     */
+    public static double[] toLatLngDegrees(long s2CellId, double[] latLngDegrees) {
+        // Used latLngDegrees as scratchpad for toLatLngRadians(long, double[]).
+        final double[] latLngRadians = latLngDegrees;
+        toLatLngRadians(s2CellId, latLngRadians);
+        latLngDegrees[LAT_INDEX] = Math.toDegrees(latLngRadians[LAT_INDEX]);
+        latLngDegrees[LNG_INDEX] = Math.toDegrees(latLngRadians[LNG_INDEX]);
+        return latLngDegrees;
+    }
+
+
+    /**
+     * Inserts into {@code latLngRadians} the centroid latitude and longitude, in that order and
+     * both measured in radians, for the specified S2 cell ID. This array must be non-null and of
+     * minimum length two. A reference to this array is returned.
+     *
+     * <p>Behavior is undefined for invalid S2 cell IDs.
+     */
+    public static double[] toLatLngRadians(long s2CellId, double[] latLngRadians) {
+        checkNotNull(latLngRadians);
+        checkLengthGreaterThanOrEqualTo(LAT_LNG_MIN_LENGTH, latLngRadians.length);
+
+        final long siTi = toSiTi(s2CellId);
+        final double u = siTiToU(siTi);
+        final double v = siTiToV(siTi);
+
+        final int face = getFace(s2CellId);
+        final XyzTransform xyzTransform = faceToXyzTransform(face);
+        final double x = xyzTransform.uvToX(u, v);
+        final double y = xyzTransform.uvToY(u, v);
+        final double z = xyzTransform.uvToZ(u, v);
+
+        latLngRadians[LAT_INDEX] = xyzToLatRadians(x, y, z);
+        latLngRadians[LNG_INDEX] = xyzToLngRadians(x, y);
+        return latLngRadians;
+    }
+
+    private static long toSiTi(long s2CellId) {
+        final long ijo = toIjo(s2CellId);
+        final int i = ijoToI(ijo);
+        final int j = ijoToJ(ijo);
+        int delta = isLeaf(s2CellId) ? 1 : (((i ^ (((int) s2CellId) >>> 2)) & 1) != 0) ? 2 : 0;
+        return (((long) (2 * i + delta)) << SI_SHIFT) | ((2 * j + delta) & TI_MASK);
+    }
+
+    private static int siTiToSi(long siTi) {
+        return (int) (siTi >> SI_SHIFT);
+    }
+
+    private static int siTiToTi(long siTi) {
+        return (int) siTi;
+    }
+
+    private static double siTiToU(long siTi) {
+        final int si = siTiToSi(siTi);
+        return siToU(si);
+    }
+
+    private static double siTiToV(long siTi) {
+        final int ti = siTiToTi(siTi);
+        return tiToV(ti);
+    }
+
+    private static double siToU(long si) {
+        final double s = (1.0 / MAX_SI_TI) * si;
+        if (s >= 0.5) {
+            return (1 / 3.) * (4 * s * s - 1);
+        }
+        return (1 / 3.) * (1 - 4 * (1 - s) * (1 - s));
+    }
+
+    private static double tiToV(long ti) {
+        // Same calculation as siToU.
+        return siToU(ti);
+    }
+
+    private static XyzTransform faceToXyzTransform(int face) {
+        // We map illegal face indices to the largest face index to preserve legacy behavior, i.e.,
+        // we do not want to throw an index out of bounds exception. Note that getFace(s2CellId) is
+        // guaranteed to return a non-negative face index even for invalid S2 cells, so it is
+        // sufficient to just map all face indices greater than the largest face index to the
+        // largest face index.
+        return XYZ_TRANSFORMS[Math.min(NUM_FACES - 1, face)];
+    }
+
+    private static double xyzToLngRadians(double x, double y) {
+        return Math.atan2(y, x);
+    }
+
+    private static double xyzToLatRadians(double x, double y, double z) {
+        return Math.atan2(z, Math.sqrt(x * x + y * y));
+    }
+
+    private static void checkNotNull(Object object) {
+        if (object == null) {
+            throw new NullPointerException("Given array cannot be null.");
+        }
+    }
+
+    private static void checkLengthGreaterThanOrEqualTo(int minLength, int actualLength) {
+        if (actualLength < minLength) {
+            throw new IllegalArgumentException(
+                "Given array of length " + actualLength + " needs to be of minimum length "
+                + minLength);
+        }
+    }
+
+    /**
+     * Returns true if the provided S2 cell contains the provided latitude/longitude, both measured
+     * in degrees.
+     */
+    public static boolean containsLatLngDegrees(long s2CellId, double latDegrees,
+            double lngDegrees) {
+        int level = getLevel(s2CellId);
+        long leafCellId = fromLatLngDegrees(latDegrees, lngDegrees);
+        return (getParent(leafCellId, level) == s2CellId);
+    }
+
+    /**
+     * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
+     * degrees.
+     */
+    public static long fromLatLngDegrees(double latDegrees, double lngDegrees) {
+        return fromLatLngRadians(Math.toRadians(latDegrees), Math.toRadians(lngDegrees));
+    }
+
+    /** Returns the leaf S2 cell ID of the specified (face, i, j) coordinate. */
+    public static long fromFij(int face, int i, int j) {
+        int bits = (face & SWAP_MASK);
+        // Update most significant bits.
+        long msb = ((long) face) << (POS_BITS - 33);
+        for (int k = 7; k >= 4; --k) {
+            bits = lookupBits(i, j, k, bits);
+            msb = updateBits(msb, k, bits);
+            bits = maskBits(bits);
+        }
+        // Update least significant bits.
+        long lsb = 0;
+        for (int k = 3; k >= 0; --k) {
+            bits = lookupBits(i, j, k, bits);
+            lsb = updateBits(lsb, k, bits);
+            bits = maskBits(bits);
+        }
+        return (((msb << 32) + lsb) << 1) + 1;
+    }
+
+    /**
+     * Returns the face of the specified S2 cell. The returned face is in [0, 5] for valid S2 cell
+     * IDs. Behavior is undefined for invalid S2 cell IDs.
+     */
+    public static int getFace(long s2CellId) {
+        return (int) (s2CellId >>> POS_BITS);
+    }
+
+    /**
+     * Returns the ID of the parent of the specified S2 cell at the specified parent level.
+     * Behavior is undefined for invalid S2 cell IDs or parent levels not in
+     * [0, {@code getLevel(s2CellId)}[.
+     */
+    public static long getParent(long s2CellId, int level) {
+        long newLsb = getLowestOnBitForLevel(level);
+        return (s2CellId & -newLsb) | newLsb;
+    }
+
+    /**
+     * Inserts into {@code neighbors} the four S2 cell IDs corresponding to the neighboring
+     * cells adjacent across the specified cell's four edges. This array must be of minimum
+     * length four, and elements at the tail end of the array not corresponding to a neighbor
+     * are set to zero. A reference to this array is returned.
+     *
+     * <p>Inserts in the order of down, right, up, and left directions, in that order. All
+     * neighbors are guaranteed to be distinct.
+     */
+    public static void getEdgeNeighbors(long s2CellId, @NonNull long[] neighbors) {
+        int level = getLevel(s2CellId);
+        int size = levelToSizeIj(level);
+        int face = getFace(s2CellId);
+        long ijo = toIjo(s2CellId);
+        int i = ijoToI(ijo);
+        int j = ijoToJ(ijo);
+
+        int iPlusSize = i + size;
+        int iMinusSize = i - size;
+        int jPlusSize = j + size;
+        int jMinusSize = j - size;
+        boolean iPlusSizeLtMax = iPlusSize < MAX_SIZE;
+        boolean iMinusSizeGteZero = iMinusSize >= 0;
+        boolean jPlusSizeLtMax = jPlusSize < MAX_SIZE;
+        boolean jMinusSizeGteZero = jMinusSize >= 0;
+
+        int index = 0;
+        // Down direction.
+        neighbors[index++] = getParent(fromFijSame(face, i, jMinusSize, jMinusSizeGteZero),
+                level);
+        // Right direction.
+        neighbors[index++] = getParent(fromFijSame(face, iPlusSize, j, iPlusSizeLtMax), level);
+        // Up direction.
+        neighbors[index++] = getParent(fromFijSame(face, i, jPlusSize, jPlusSizeLtMax), level);
+        // Left direction.
+        neighbors[index++] = getParent(fromFijSame(face, iMinusSize, j, iMinusSizeGteZero),
+                level);
+
+        // Pad end of neighbor array with zeros.
+        Arrays.fill(neighbors, index, neighbors.length, 0);
+    }
+
+    /** Returns the "i" coordinate for the specified S2 cell. */
+    public static int getI(long s2CellId) {
+        return ijoToI(toIjo(s2CellId));
+    }
+
+    /** Returns the "j" coordinate for the specified S2 cell. */
+    public static int getJ(long s2CellId) {
+        return ijoToJ(toIjo(s2CellId));
+    }
+
+    /**
+     * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
+     * radians.
+     */
+    private static long fromLatLngRadians(double latRadians, double lngRadians) {
+        double cosLat = Math.cos(latRadians);
+        double x = Math.cos(lngRadians) * cosLat;
+        double y = Math.sin(lngRadians) * cosLat;
+        double z = Math.sin(latRadians);
+        return fromXyz(x, y, z);
+    }
+
+    /**
+     * Returns the level of the specified S2 cell. The returned level is in [0, 30] for valid
+     * S2 cell IDs. Behavior is undefined for invalid S2 cell IDs.
+     */
+    public static int getLevel(long s2CellId) {
+        if (isLeaf(s2CellId)) {
+            return MAX_LEVEL;
+        }
+        return MAX_LEVEL - (Long.numberOfTrailingZeros(s2CellId) >> 1);
+    }
+
+    /** Returns the lowest-numbered bit that is on for the specified S2 cell. */
+    static long getLowestOnBit(long s2CellId) {
+        return s2CellId & -s2CellId;
+    }
+
+    /** Returns the lowest-numbered bit that is on for any S2 cell on the specified level. */
+    static long getLowestOnBitForLevel(int level) {
+        return 1L << (2 * (MAX_LEVEL - level));
+    }
+
+    /**
+     * Returns the ID of the first S2 cell in a traversal of the children S2 cells at the specified
+     * level, in Hilbert curve order.
+     */
+    public static long getTraversalStart(long s2CellId, int level) {
+        return s2CellId - getLowestOnBit(s2CellId) + getLowestOnBitForLevel(level);
+    }
+
+    /** Returns the ID of the next S2 cell at the same level along the Hilbert curve. */
+    public static long getTraversalNext(long s2CellId) {
+        return s2CellId + (getLowestOnBit(s2CellId) << 1);
+    }
+
+    /**
+     * Encodes the S2 cell id to compact text strings suitable for display or indexing. Cells at
+     * lower levels (i.e., larger cells) are encoded into fewer characters.
+     */
+    @NonNull
+    public static String getToken(long s2CellId) {
+        if (s2CellId == 0) {
+            return "X";
+        }
+
+        // Convert to a hex string with as many digits as necessary.
+        String hex = Long.toHexString(s2CellId).toLowerCase(Locale.US);
+        // Prefix 0s to get a length 16 string.
+        String padded = padStart(hex);
+        // Trim zeroes off the end.
+        return padded.replaceAll("0*$", "");
+    }
+
+    private static String padStart(String string) {
+        if (string.length() >= 16) {
+            return string;
+        }
+        return "0".repeat(16 - string.length()) + string;
+    }
+
+    /** Returns the leaf S2 cell ID of the specified (x, y, z) coordinate. */
+    private static long fromXyz(double x, double y, double z) {
+        int face = xyzToFace(x, y, z);
+        UvTransform uvTransform = UV_TRANSFORMS[face];
+        double u = uvTransform.xyzToU(x, y, z);
+        double v = uvTransform.xyzToV(x, y, z);
+        return fromFuv(face, u, v);
+    }
+
+    /** Returns the leaf S2 cell ID of the specified (face, u, v) coordinate. */
+    private static long fromFuv(int face, double u, double v) {
+        int i = uToI(u);
+        int j = vToJ(v);
+        return fromFij(face, i, j);
+    }
+
+    private static long fromFijWrap(int face, int i, int j) {
+        double u = iToU(i);
+        double v = jToV(j);
+
+        XyzTransform xyzTransform = XYZ_TRANSFORMS[face];
+        double x = xyzTransform.uvToX(u, v);
+        double y = xyzTransform.uvToY(u, v);
+        double z = xyzTransform.uvToZ(u, v);
+
+        int newFace = xyzToFace(x, y, z);
+        UvTransform uvTransform = UV_TRANSFORMS[newFace];
+        double newU = uvTransform.xyzToU(x, y, z);
+        double newV = uvTransform.xyzToV(x, y, z);
+
+        int newI = uShiftIntoI(newU);
+        int newJ = vShiftIntoJ(newV);
+        return fromFij(newFace, newI, newJ);
+    }
+
+    private static long fromFijSame(int face, int i, int j, boolean isSameFace) {
+        if (isSameFace) {
+            return fromFij(face, i, j);
+        }
+        return fromFijWrap(face, i, j);
+    }
+
+    /**
+     * Returns the face associated with the specified (x, y, z) coordinate. For a coordinate
+     * on a face boundary, the returned face is arbitrary but repeatable.
+     */
+    private static int xyzToFace(double x, double y, double z) {
+        double absX = Math.abs(x);
+        double absY = Math.abs(y);
+        double absZ = Math.abs(z);
+        if (absX > absY) {
+            if (absX > absZ) {
+                return (x < 0) ? 3 : 0;
+            }
+            return (z < 0) ? 5 : 2;
+        }
+        if (absY > absZ) {
+            return (y < 0) ? 4 : 1;
+        }
+        return (z < 0) ? 5 : 2;
+    }
+
+    private static int uToI(double u) {
+        double s;
+        if (u >= 0) {
+            s = 0.5 * Math.sqrt(1 + 3 * u);
+        } else {
+            s = 1 - 0.5 * Math.sqrt(1 - 3 * u);
+        }
+        return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
+    }
+
+    private static int vToJ(double v) {
+        // Same calculation as uToI.
+        return uToI(v);
+    }
+
+    private static int lookupBits(int i, int j, int k, int bits) {
+        bits += ((i >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << (LOOKUP_BITS + 2);
+        bits += ((j >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << 2;
+        return LOOKUP_POS[bits];
+    }
+
+    private static long updateBits(long sb, int k, int bits) {
+        return sb | ((((long) bits) >> 2) << ((k & 0x3) * 2 * LOOKUP_BITS));
+    }
+
+    private static int maskBits(int bits) {
+        return bits & (SWAP_MASK | INVERT_MASK);
+    }
+
+    private static boolean isLeaf(long s2CellId) {
+        return ((int) s2CellId & LEAF_MASK) != 0;
+    }
+
+    private static double iToU(int i) {
+        int satI = Math.max(-1, Math.min(MAX_SIZE, i));
+        return Math.max(
+                -UV_LIMIT,
+                Math.min(UV_LIMIT, ONE_OVER_MAX_SIZE * ((satI << 1) + 1 - MAX_SIZE)));
+    }
+
+    private static double jToV(int j) {
+        // Same calculation as iToU.
+        return iToU(j);
+    }
+
+    private static long toIjo(long s2CellId) {
+        int face = getFace(s2CellId);
+        int bits = face & SWAP_MASK;
+        int i = 0;
+        int j = 0;
+        for (int k = 7; k >= 0; --k) {
+            int nbits = (k == 7) ? (MAX_LEVEL - 7 * LOOKUP_BITS) : LOOKUP_BITS;
+            bits += ((int) (s2CellId >>> (k * 2 * LOOKUP_BITS + 1)) & ((1 << (2 * nbits))
+                    - 1)) << 2;
+            bits = LOOKUP_IJ[bits];
+            i += (bits >> (LOOKUP_BITS + 2)) << (k * LOOKUP_BITS);
+            j += ((bits >> 2) & ((1 << LOOKUP_BITS) - 1)) << (k * LOOKUP_BITS);
+            bits &= (SWAP_MASK | INVERT_MASK);
+        }
+        int orientation =
+                ((getLowestOnBit(s2CellId) & 0x1111111111111110L) != 0) ? (bits ^ SWAP_MASK)
+                        : bits;
+        return (((long) i) << I_SHIFT) | (((long) j) << J_SHIFT) | orientation;
+    }
+
+    private static int ijoToI(long ijo) {
+        return (int) (ijo >>> I_SHIFT);
+    }
+
+    private static int ijoToJ(long ijo) {
+        return (int) ((ijo >>> J_SHIFT) & J_MASK);
+    }
+
+    private static int uShiftIntoI(double u) {
+        double s = 0.5 * (u + 1);
+        return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
+    }
+
+    private static int vShiftIntoJ(double v) {
+        // Same calculation as uShiftIntoI.
+        return uShiftIntoI(v);
+    }
+
+    private static int levelToSizeIj(int level) {
+        return 1 << (MAX_LEVEL - level);
+    }
+
+    private static void initLookupCells() {
+        initLookupCell(0, 0, 0, 0, 0, 0);
+        initLookupCell(0, 0, 0, SWAP_MASK, 0, SWAP_MASK);
+        initLookupCell(0, 0, 0, INVERT_MASK, 0, INVERT_MASK);
+        initLookupCell(0, 0, 0, SWAP_MASK | INVERT_MASK, 0, SWAP_MASK | INVERT_MASK);
+    }
+
+    private static void initLookupCell(
+            int level, int i, int j, int origOrientation, int pos, int orientation) {
+        if (level == LOOKUP_BITS) {
+            int ij = (i << LOOKUP_BITS) + j;
+            LOOKUP_POS[(ij << 2) + origOrientation] = (pos << 2) + orientation;
+            LOOKUP_IJ[(pos << 2) + origOrientation] = (ij << 2) + orientation;
+        } else {
+            level++;
+            i <<= 1;
+            j <<= 1;
+            pos <<= 2;
+            for (int subPos = 0; subPos < 4; subPos++) {
+                int ij = POS_TO_IJ[orientation][subPos];
+                int orientationMask = POS_TO_ORIENTATION[subPos];
+                initLookupCell(
+                        level,
+                        i + (ij >>> 1),
+                        j + (ij & 0x1),
+                        origOrientation,
+                        pos + subPos,
+                        orientation ^ orientationMask);
+            }
+        }
+    }
+
+    private static double calculateUvLimit() {
+        double machEps = 1.0;
+        do {
+            machEps /= 2.0f;
+        } while ((1.0 + (machEps / 2.0)) != 1.0);
+        return 1.0 + machEps;
+    }
+
+    @NonNull
+    private static UvTransform[] createUvTransforms() {
+        UvTransform[] uvTransforms = new UvTransform[NUM_FACES];
+        uvTransforms[0] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return y / x;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return z / x;
+                    }
+                };
+        uvTransforms[1] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return -x / y;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return z / y;
+                    }
+                };
+        uvTransforms[2] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return -x / z;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return -y / z;
+                    }
+                };
+        uvTransforms[3] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return z / x;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return y / x;
+                    }
+                };
+        uvTransforms[4] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return z / y;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return -x / y;
+                    }
+                };
+        uvTransforms[5] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return -y / z;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return -x / z;
+                    }
+                };
+        return uvTransforms;
+    }
+
+    @NonNull
+    private static XyzTransform[] createXyzTransforms() {
+        XyzTransform[] xyzTransforms = new XyzTransform[NUM_FACES];
+        xyzTransforms[0] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return 1;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return u;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return v;
+                    }
+                };
+        xyzTransforms[1] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return -u;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return 1;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return v;
+                    }
+                };
+        xyzTransforms[2] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return -u;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return -v;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return 1;
+                    }
+                };
+        xyzTransforms[3] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return -1;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return -v;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return -u;
+                    }
+                };
+        xyzTransforms[4] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return v;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return -1;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return -u;
+                    }
+                };
+        xyzTransforms[5] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return v;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return u;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return -1;
+                    }
+                };
+        return xyzTransforms;
+    }
+
+    /**
+     * Transform from (x, y, z) coordinates to (u, v) coordinates, indexed by face. For a
+     * (x, y, z) coordinate within a face, each element of the resulting (u, v) coordinate
+     * should lie in the inclusive range [-1, 1], with the face center having a (u, v)
+     * coordinate equal to (0, 0).
+     */
+    private interface UvTransform {
+
+        /**
+         * Returns for the specified (x, y, z) coordinate the corresponding u-coordinate
+         * (which may lie outside the range [-1, 1]).
+         */
+        double xyzToU(double x, double y, double z);
+
+        /**
+         * Returns for the specified (x, y, z) coordinate the corresponding v-coordinate
+         * (which may lie outside the range [-1, 1]).
+         */
+        double xyzToV(double x, double y, double z);
+    }
+
+    /**
+     * Transform from (u, v) coordinates to (x, y, z) coordinates, indexed by face. The
+     * resulting vectors are not necessarily of unit length.
+     */
+    private interface XyzTransform {
+
+        /** Returns for the specified (u, v) coordinate the corresponding x-coordinate. */
+        double uvToX(double u, double v);
+
+        /** Returns for the specified (u, v) coordinate the corresponding y-coordinate. */
+        double uvToY(double u, double v);
+
+        /** Returns for the specified (u, v) coordinate the corresponding z-coordinate. */
+        double uvToZ(double u, double v);
+    }
+}
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 0da8371b..c72a74e 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -714,7 +714,7 @@
     /**
      * @hide
      * Return a channel mask ready to be used by native code
-     * @param mask a combination of the CHANNEL_OUT_* definitions, but not CHANNEL_OUT_DEFAULT
+     * @param javaMask a combination of the CHANNEL_OUT_* definitions, but not CHANNEL_OUT_DEFAULT
      * @return a native channel mask
      */
     public static int convertChannelOutMaskToNativeMask(int javaMask) {
@@ -724,13 +724,98 @@
     /**
      * @hide
      * Return a java output channel mask
-     * @param mask a native channel mask
+     * @param nativeMask a native channel mask
      * @return a combination of the CHANNEL_OUT_* definitions
      */
     public static int convertNativeChannelMaskToOutMask(int nativeMask) {
         return (nativeMask << 2);
     }
 
+    /**
+     * @hide
+     * Return a human-readable string from a java channel mask
+     * @param javaMask a bit field of CHANNEL_OUT_* values
+     * @return a string in the "mono", "stereo", "5.1" style, or the hex version when not a standard
+     *   mask.
+     */
+    public static String javaChannelOutMaskToString(int javaMask) {
+        // save haptics info for end of string
+        int haptics = javaMask & (CHANNEL_OUT_HAPTIC_A | CHANNEL_OUT_HAPTIC_B);
+        // continue without looking at haptic channels
+        javaMask &= ~(CHANNEL_OUT_HAPTIC_A | CHANNEL_OUT_HAPTIC_B);
+        StringBuilder result = new StringBuilder("");
+        switch (javaMask) {
+            case CHANNEL_OUT_MONO:
+                result.append("mono");
+                break;
+            case CHANNEL_OUT_STEREO:
+                result.append("stereo");
+                break;
+            case CHANNEL_OUT_QUAD:
+                result.append("quad");
+                break;
+            case CHANNEL_OUT_QUAD_SIDE:
+                result.append("quad side");
+                break;
+            case CHANNEL_OUT_SURROUND:
+                result.append("4.0");
+                break;
+            case CHANNEL_OUT_5POINT1:
+                result.append("5.1");
+                break;
+            case CHANNEL_OUT_6POINT1:
+                result.append("6.1");
+                break;
+            case CHANNEL_OUT_5POINT1_SIDE:
+                result.append("5.1 side");
+                break;
+            case CHANNEL_OUT_7POINT1:
+                result.append("7.1 (5 fronts)");
+                break;
+            case CHANNEL_OUT_7POINT1_SURROUND:
+                result.append("7.1");
+                break;
+            case CHANNEL_OUT_5POINT1POINT2:
+                result.append("5.1.2");
+                break;
+            case CHANNEL_OUT_5POINT1POINT4:
+                result.append("5.1.4");
+                break;
+            case CHANNEL_OUT_7POINT1POINT2:
+                result.append("7.1.2");
+                break;
+            case CHANNEL_OUT_7POINT1POINT4:
+                result.append("7.1.4");
+                break;
+            case CHANNEL_OUT_9POINT1POINT4:
+                result.append("9.1.4");
+                break;
+            case CHANNEL_OUT_9POINT1POINT6:
+                result.append("9.1.6");
+                break;
+            case CHANNEL_OUT_13POINT_360RA:
+                result.append("360RA 13ch");
+                break;
+            case CHANNEL_OUT_22POINT2:
+                result.append("22.2");
+                break;
+            default:
+                result.append("0x").append(Integer.toHexString(javaMask));
+                break;
+        }
+        if ((haptics & (CHANNEL_OUT_HAPTIC_A | CHANNEL_OUT_HAPTIC_B)) != 0) {
+            result.append("(+haptic ");
+            if ((haptics & CHANNEL_OUT_HAPTIC_A) == CHANNEL_OUT_HAPTIC_A) {
+                result.append("A");
+            }
+            if ((haptics & CHANNEL_OUT_HAPTIC_B) == CHANNEL_OUT_HAPTIC_B) {
+                result.append("B");
+            }
+            result.append(")");
+        }
+        return result.toString();
+    }
+
     public static final int CHANNEL_IN_DEFAULT = 1;
     // These directly match native
     public static final int CHANNEL_IN_LEFT = 0x4;
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index c085b89..dba9cc9 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -283,8 +283,19 @@
      * Flag used when playback is muted by AppOpsManager#OP_PLAY_AUDIO.
      */
     @SystemApi
+    @FlaggedApi(FLAG_MUTED_BY_PORT_VOLUME_API)
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public static final int MUTED_BY_APP_OPS = (1 << 3);
+    public static final int MUTED_BY_OP_PLAY_AUDIO = (1 << 3);
+    /**
+     * @hide
+     * Flag used when playback is muted by AppOpsManager#OP_PLAY_AUDIO.
+     * @deprecated see {@link MUTED_BY_OP_PLAY_AUDIO}
+     */
+    @SystemApi
+    @Deprecated
+    @FlaggedApi(FLAG_MUTED_BY_PORT_VOLUME_API)
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_APP_OPS = MUTED_BY_OP_PLAY_AUDIO;
     /**
      * @hide
      * Flag used when muted by client volume.
@@ -311,12 +322,21 @@
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public static final int MUTED_BY_PORT_VOLUME = (1 << 6);
 
+    /**
+     * @hide
+     * Flag used when playback is muted by AppOpsManager#OP_CONTROL_AUDIO.
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_MUTED_BY_PORT_VOLUME_API)
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_OP_CONTROL_AUDIO = (1 << 7);
+
     /** @hide */
     @IntDef(
             flag = true,
             value = {MUTED_BY_MASTER, MUTED_BY_STREAM_VOLUME, MUTED_BY_STREAM_MUTED,
-                    MUTED_BY_APP_OPS, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER,
-                    MUTED_BY_PORT_VOLUME})
+                    MUTED_BY_OP_PLAY_AUDIO, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER,
+                    MUTED_BY_PORT_VOLUME, MUTED_BY_OP_CONTROL_AUDIO})
     @Retention(RetentionPolicy.SOURCE)
     public @interface PlayerMuteEvent {
     }
@@ -761,7 +781,7 @@
     private boolean isMuteAffectingActiveState() {
         return (mMutedState & MUTED_BY_CLIENT_VOLUME) != 0
                 || (mMutedState & MUTED_BY_VOLUME_SHAPER) != 0
-                || (mMutedState & MUTED_BY_APP_OPS) != 0;
+                || (mMutedState & MUTED_BY_OP_PLAY_AUDIO) != 0;
     }
 
     /**
@@ -902,8 +922,8 @@
                 if ((mMutedState & MUTED_BY_STREAM_MUTED) != 0) {
                     apcToString.append("streamMute ");
                 }
-                if ((mMutedState & MUTED_BY_APP_OPS) != 0) {
-                    apcToString.append("appOps ");
+                if ((mMutedState & MUTED_BY_OP_PLAY_AUDIO) != 0) {
+                    apcToString.append("opPlayAudio ");
                 }
                 if ((mMutedState & MUTED_BY_CLIENT_VOLUME) != 0) {
                     apcToString.append("clientVolume ");
@@ -914,6 +934,9 @@
                 if ((mMutedState & MUTED_BY_PORT_VOLUME) != 0) {
                     apcToString.append("portVolume ");
                 }
+                if ((mMutedState & MUTED_BY_OP_CONTROL_AUDIO) != 0) {
+                    apcToString.append("opControlAudio ");
+                }
             }
             apcToString.append(" ").append(mFormatInfo);
         }
diff --git a/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl b/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
index 63c52a1..8f03057 100644
--- a/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
@@ -25,7 +25,6 @@
  * @hide
  */
 oneway interface IMediaRoute2ProviderServiceCallback {
-    // TODO: Change it to updateRoutes?
     void notifyProviderUpdated(in MediaRoute2ProviderInfo providerInfo);
     void notifySessionCreated(long requestId, in RoutingSessionInfo sessionInfo);
     void notifySessionsUpdated(in List<RoutingSessionInfo> sessionInfo);
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 1ecba31..3efb5f9 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -1010,17 +1010,17 @@
      * scenario, when both resource holder and resource challenger have same processId and same
      * priority.
      *
-     * @param resourceHolderRetain Set to {@code true} to allow the resource holder to retain
-     *     ownership, or false to allow the resource challenger to acquire the resource.
-     *     If not explicitly set, resourceHolderRetain is set to {@code false}.
+     *@param enabled Set to {@code true} to allow the resource holder to retain ownership,
+     *     or false to allow the resource challenger to acquire the resource.
+     *     If not explicitly set, enabled is set to {@code false}.
      * @hide
      */
     @FlaggedApi(FLAG_SET_RESOURCE_HOLDER_RETAIN)
     @SystemApi
     @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
-    public void setResourceHolderRetain(boolean resourceHolderRetain) {
+    public void setResourceOwnershipRetention(boolean enabled) {
         if (mTunerResourceManager != null) {
-            mTunerResourceManager.setResourceHolderRetain(mClientId, resourceHolderRetain);
+            mTunerResourceManager.setResourceOwnershipRetention(mClientId, enabled);
         }
     }
 
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 0902278..dd5067a 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -21,8 +21,10 @@
 
 import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
 import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
+import static com.android.media.flags.Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2;
 import static com.android.media.flags.Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES;
 import static com.android.media.flags.Flags.FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES;
+import static com.android.media.flags.Flags.FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API;
 
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
@@ -421,6 +423,51 @@
      */
     public static final int TYPE_GROUP = 2000;
 
+    /** @hide */
+    @IntDef(
+            prefix = {"ROUTING_TYPE_"},
+            value = {
+                FLAG_ROUTING_TYPE_SYSTEM_AUDIO,
+                FLAG_ROUTING_TYPE_SYSTEM_VIDEO,
+                FLAG_ROUTING_TYPE_REMOTE
+            },
+            flag = true)
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RoutingType {}
+
+    /**
+     * Indicates that a route supports routing of the system audio.
+     *
+     * <p>Providers that support this type of routing require the {@link
+     * android.Manifest.permission#MODIFY_AUDIO_ROUTING} permission.
+     */
+    @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    public static final int FLAG_ROUTING_TYPE_SYSTEM_AUDIO = 1;
+
+    /**
+     * Indicates that a route supports routing of the system video.
+     *
+     * @hide
+     */
+    // TODO: b/380431086 - Enable this API once we add support for system video routing.
+    @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    public static final int FLAG_ROUTING_TYPE_SYSTEM_VIDEO = 1 << 1;
+
+    /**
+     * Indicates that a route supports routing playback to remote routes through control commands.
+     *
+     * <p>This type of routing does not affect affect this system's audio or video, but instead
+     * relies on the device that corresponds to this route to fetch and play the media. It also
+     * requires the media app to take care of initializing and controlling playback.
+     */
+    @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    public static final int FLAG_ROUTING_TYPE_REMOTE = 1 << 2;
+
+    private static final int FLAG_ROUTING_TYPE_ALL =
+            FLAG_ROUTING_TYPE_SYSTEM_AUDIO
+                    | FLAG_ROUTING_TYPE_SYSTEM_VIDEO
+                    | FLAG_ROUTING_TYPE_REMOTE;
+
     /**
      * Route feature: Live audio.
      * <p>
@@ -553,6 +600,7 @@
     private final List<String> mFeatures;
     @Type
     private final int mType;
+    @RoutingType private final int mRoutingTypeFlags;
     private final boolean mIsSystem;
     private final Uri mIconUri;
     private final CharSequence mDescription;
@@ -569,6 +617,7 @@
     private final String mProviderId;
     private final boolean mIsVisibilityRestricted;
     private final Set<String> mAllowedPackages;
+    private final Set<String> mRequiredPermissions;
     @SuitabilityStatus private final int mSuitabilityStatus;
 
     MediaRoute2Info(@NonNull Builder builder) {
@@ -576,6 +625,7 @@
         mName = builder.mName;
         mFeatures = builder.mFeatures;
         mType = builder.mType;
+        mRoutingTypeFlags = builder.mRoutingTypeFlags;
         mIsSystem = builder.mIsSystem;
         mIconUri = builder.mIconUri;
         mDescription = builder.mDescription;
@@ -592,6 +642,7 @@
         mIsVisibilityRestricted = builder.mIsVisibilityRestricted;
         mAllowedPackages = builder.mAllowedPackages;
         mSuitabilityStatus = builder.mSuitabilityStatus;
+        mRequiredPermissions = Set.copyOf(builder.mRequiredPermissions);
     }
 
     MediaRoute2Info(@NonNull Parcel in) {
@@ -600,6 +651,7 @@
         mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mFeatures = in.createStringArrayList();
         mType = in.readInt();
+        mRoutingTypeFlags = validateRoutingTypeFlags(in.readInt());
         mIsSystem = in.readBoolean();
         mIconUri = in.readParcelable(null, android.net.Uri.class);
         mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
@@ -615,6 +667,7 @@
         mProviderId = in.readString();
         mIsVisibilityRestricted = in.readBoolean();
         mAllowedPackages = Set.of(in.createString8Array());
+        mRequiredPermissions = Set.of(in.createString8Array());
         mSuitabilityStatus = in.readInt();
     }
 
@@ -660,6 +713,13 @@
         return mType;
     }
 
+    /** Returns the flags that indicate the routing types supported by this route. */
+    @RoutingType
+    @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    public int getSupportedRoutingTypes() {
+        return mRoutingTypeFlags;
+    }
+
     /**
      * Returns whether the route is a system route or not.
      * <p>
@@ -851,6 +911,15 @@
     }
 
     /**
+     * @return the set of permissions which must be held to see this route
+     */
+    @NonNull
+    @FlaggedApi(FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API)
+    public Set<String> getRequiredPermissions() {
+        return mRequiredPermissions;
+    }
+
+    /**
      * Returns whether this route's type can only be published by the system route provider.
      *
      * @see #isSystemRoute()
@@ -904,6 +973,7 @@
         pw.println(indent + "mName=" + mName);
         pw.println(indent + "mFeatures=" + mFeatures);
         pw.println(indent + "mType=" + getDeviceTypeString(mType));
+        pw.println(indent + "mRoutingTypeFlags=" + getRoutingTypeFlagsString(mRoutingTypeFlags));
         pw.println(indent + "mIsSystem=" + mIsSystem);
         pw.println(indent + "mIconUri=" + mIconUri);
         pw.println(indent + "mDescription=" + mDescription);
@@ -920,6 +990,7 @@
         pw.println(indent + "mIsVisibilityRestricted=" + mIsVisibilityRestricted);
         pw.println(indent + "mAllowedPackages=" + mAllowedPackages);
         pw.println(indent + "mSuitabilityStatus=" + mSuitabilityStatus);
+        pw.println(indent + "mRequiredPermissions=" + mRequiredPermissions);
     }
 
     private void dumpVolume(@NonNull PrintWriter pw, @NonNull String prefix) {
@@ -941,6 +1012,7 @@
                 && Objects.equals(mName, other.mName)
                 && Objects.equals(mFeatures, other.mFeatures)
                 && (mType == other.mType)
+                && (mRoutingTypeFlags == other.mRoutingTypeFlags)
                 && (mIsSystem == other.mIsSystem)
                 && Objects.equals(mIconUri, other.mIconUri)
                 && Objects.equals(mDescription, other.mDescription)
@@ -955,6 +1027,7 @@
                 && Objects.equals(mProviderId, other.mProviderId)
                 && (mIsVisibilityRestricted == other.mIsVisibilityRestricted)
                 && Objects.equals(mAllowedPackages, other.mAllowedPackages)
+                && Objects.equals(mRequiredPermissions, other.mRequiredPermissions)
                 && mSuitabilityStatus == other.mSuitabilityStatus;
     }
 
@@ -966,6 +1039,7 @@
                 mName,
                 mFeatures,
                 mType,
+                mRoutingTypeFlags,
                 mIsSystem,
                 mIconUri,
                 mDescription,
@@ -980,6 +1054,7 @@
                 mProviderId,
                 mIsVisibilityRestricted,
                 mAllowedPackages,
+                mRequiredPermissions,
                 mSuitabilityStatus);
     }
 
@@ -994,6 +1069,8 @@
                 .append(getName())
                 .append(", type=")
                 .append(getDeviceTypeString(getType()))
+                .append(", routingTypes=")
+                .append(getRoutingTypeFlagsString(getSupportedRoutingTypes()))
                 .append(", isSystem=")
                 .append(isSystemRoute())
                 .append(", features=")
@@ -1018,6 +1095,8 @@
                 .append(mIsVisibilityRestricted)
                 .append(", allowedPackages=")
                 .append(String.join(",", mAllowedPackages))
+                .append(", mRequiredPermissions=")
+                .append(String.join(",", mRequiredPermissions))
                 .append(", suitabilityStatus=")
                 .append(mSuitabilityStatus)
                 .append(" }")
@@ -1035,6 +1114,7 @@
         TextUtils.writeToParcel(mName, dest, flags);
         dest.writeStringList(mFeatures);
         dest.writeInt(mType);
+        dest.writeInt(mRoutingTypeFlags);
         dest.writeBoolean(mIsSystem);
         dest.writeParcelable(mIconUri, flags);
         TextUtils.writeToParcel(mDescription, dest, flags);
@@ -1050,6 +1130,7 @@
         dest.writeString(mProviderId);
         dest.writeBoolean(mIsVisibilityRestricted);
         dest.writeString8Array(mAllowedPackages.toArray(new String[0]));
+        dest.writeString8Array(mRequiredPermissions.toArray(new String[0]));
         dest.writeInt(mSuitabilityStatus);
     }
 
@@ -1143,6 +1224,34 @@
         }
     }
 
+    /** Returns a human-readable representation of the given {@code routingTypeFlags}. */
+    private static String getRoutingTypeFlagsString(@RoutingType int routingTypeFlags) {
+        List<String> typeStrings = new ArrayList<>();
+        if ((routingTypeFlags & FLAG_ROUTING_TYPE_SYSTEM_AUDIO) != 0) {
+            typeStrings.add("SYSTEM_AUDIO");
+        }
+        if ((routingTypeFlags & FLAG_ROUTING_TYPE_SYSTEM_VIDEO) != 0) {
+            typeStrings.add("SYSTEM_VIDEO");
+        }
+        if ((routingTypeFlags & FLAG_ROUTING_TYPE_REMOTE) != 0) {
+            typeStrings.add("REMOTE");
+        }
+        return String.join(/* delimiter= */ "|", typeStrings);
+    }
+
+    /**
+     * Throws an {@link IllegalArgumentException} if the provided {@code routingTypeFlags} are not
+     * valid. Otherwise, returns the provided value.
+     */
+    private static int validateRoutingTypeFlags(@RoutingType int routingTypeFlags) {
+        if (routingTypeFlags == 0 || (routingTypeFlags & ~FLAG_ROUTING_TYPE_ALL) != 0) {
+            throw new IllegalArgumentException(
+                    "Invalid routing type flags: " + Integer.toHexString(routingTypeFlags));
+        } else {
+            return routingTypeFlags;
+        }
+    }
+
     /**
      * Builder for {@link MediaRoute2Info media route info}.
      */
@@ -1153,6 +1262,7 @@
 
         @Type
         private int mType = TYPE_UNKNOWN;
+        @RoutingType private int mRoutingTypeFlags = FLAG_ROUTING_TYPE_REMOTE;
         private boolean mIsSystem;
         private Uri mIconUri;
         private CharSequence mDescription;
@@ -1169,6 +1279,7 @@
         private String mProviderId;
         private boolean mIsVisibilityRestricted;
         private Set<String> mAllowedPackages;
+        private Set<String> mRequiredPermissions;
         @SuitabilityStatus private int mSuitabilityStatus;
 
         /**
@@ -1194,6 +1305,7 @@
             mDeduplicationIds = Set.of();
             mAllowedPackages = Set.of();
             mSuitabilityStatus = SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
+            mRequiredPermissions = Set.of();
         }
 
         /**
@@ -1224,6 +1336,7 @@
             mName = routeInfo.mName;
             mFeatures = new ArrayList<>(routeInfo.mFeatures);
             mType = routeInfo.mType;
+            mRoutingTypeFlags = routeInfo.mRoutingTypeFlags;
             mIsSystem = routeInfo.mIsSystem;
             mIconUri = routeInfo.mIconUri;
             mDescription = routeInfo.mDescription;
@@ -1242,6 +1355,7 @@
             mIsVisibilityRestricted = routeInfo.mIsVisibilityRestricted;
             mAllowedPackages = routeInfo.mAllowedPackages;
             mSuitabilityStatus = routeInfo.mSuitabilityStatus;
+            mRequiredPermissions = routeInfo.mRequiredPermissions;
         }
 
         /**
@@ -1301,6 +1415,18 @@
         }
 
         /**
+         * Sets the routing types that this route supports.
+         *
+         * @see MediaRoute2Info#getSupportedRoutingTypes()
+         */
+        @NonNull
+        @FlaggedApi(FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+        public Builder setSupportedRoutingTypes(@RoutingType int routingTypeFlags) {
+            mRoutingTypeFlags = validateRoutingTypeFlags(routingTypeFlags);
+            return this;
+        }
+
+        /**
          * Sets whether the route is a system route or not.
          * @hide
          */
@@ -1461,6 +1587,7 @@
         public Builder setVisibilityPublic() {
             mIsVisibilityRestricted = false;
             mAllowedPackages = Set.of();
+            mRequiredPermissions = Set.of();
             return this;
         }
 
@@ -1485,6 +1612,19 @@
         }
 
         /**
+         * Limits the visibility of this route to holders of a set of permissions.
+         *
+         * @param requiredPermissions the list of all permissions which must be held in order to
+         *                            see this route.
+         */
+        @NonNull
+        @FlaggedApi(FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API)
+        public Builder setRequiredPermissions(@NonNull Set<String> requiredPermissions) {
+            mRequiredPermissions = Set.copyOf(requiredPermissions);
+            return this;
+        }
+
+        /**
          * Sets route suitability status.
          *
          * <p>The default value is {@link
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index a14f1fd..547099f 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -19,6 +19,7 @@
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -37,6 +38,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.media.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -81,6 +83,22 @@
     public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
 
     /**
+     * {@link Intent} action that indicates that the declaring service supports routing of the
+     * system media.
+     *
+     * <p>Providers must include this action if they intend to publish routes that support the
+     * system media, as described by {@link MediaRoute2Info#getSupportedRoutingTypes()}.
+     *
+     * @see #onCreateSystemRoutingSession
+     * @hide
+     */
+    // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+    @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE_SYSTEM_MEDIA =
+            "android.media.MediaRoute2ProviderService.SYSTEM_MEDIA";
+
+    /**
      * A category indicating that the associated provider is only intended for use within the app
      * that hosts the provider.
      *
@@ -138,12 +156,26 @@
     public static final int REASON_INVALID_COMMAND = 4;
 
     /**
+     * The request has failed because the requested operation is not implemented by the provider.
+     *
+     * @see #notifyRequestFailed
      * @hide
      */
-    @IntDef(prefix = "REASON_", value = {
-            REASON_UNKNOWN_ERROR, REASON_REJECTED, REASON_NETWORK_ERROR, REASON_ROUTE_NOT_AVAILABLE,
-            REASON_INVALID_COMMAND
-    })
+    // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+    @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    public static final int REASON_UNIMPLEMENTED = 5;
+
+    /** @hide */
+    @IntDef(
+            prefix = "REASON_",
+            value = {
+                REASON_UNKNOWN_ERROR,
+                REASON_REJECTED,
+                REASON_NETWORK_ERROR,
+                REASON_ROUTE_NOT_AVAILABLE,
+                REASON_INVALID_COMMAND,
+                REASON_UNIMPLEMENTED
+            })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Reason {}
 
@@ -282,6 +314,32 @@
     }
 
     /**
+     * Notifies the system of the successful creation of a system media routing session.
+     *
+     * <p>This method can only be called as the result of a prior call to {@link
+     * #onCreateSystemRoutingSession}.
+     *
+     * @param requestId the ID of the {@link #onCreateSystemRoutingSession} request which this call
+     *     is in response to.
+     * @param sessionInfo a {@link RoutingSessionInfo} that describes the newly created routing
+     *     session.
+     * @param formats the {@link MediaStreamsFormats} that describes the format for the {@link
+     *     MediaStreams} to return.
+     * @return a {@link MediaStreams} instance that holds the media streams to route as part of the
+     *     newly created routing session.
+     * @hide
+     */
+    // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+    @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    @NonNull
+    public final MediaStreams notifySystemMediaSessionCreated(
+            long requestId,
+            @NonNull RoutingSessionInfo sessionInfo,
+            @NonNull MediaStreamsFormats formats) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Notifies the existing session is updated. For example, when
      * {@link RoutingSessionInfo#getSelectedRoutes() selected routes} are changed.
      */
@@ -399,6 +457,43 @@
             @NonNull String routeId, @Nullable Bundle sessionHints);
 
     /**
+     * Called when the service receives a request to create a system routing session.
+     *
+     * <p>This method will only be called for routes that support routing of the system media, as
+     * described by {@link MediaRoute2Info#getSupportedRoutingTypes()}.
+     *
+     * <p>Implementors of this method must call {@link #notifySystemMediaSessionCreated} with the
+     * given {@code requestId} to indicate a successful session creation. If the session creation
+     * fails (for example, if the connection to the receiver device fails), the implementor must
+     * call {@link #notifyRequestFailed}, passing the {@code requestId}.
+     *
+     * <p>Unlike {@link #onCreateSession}, system sessions route the system media (for example,
+     * audio and/or video) which is to be retrieved by calling {@link
+     * #notifySystemMediaSessionCreated}.
+     *
+     * <p>Changes to the session can be notified by calling {@link #notifySessionUpdated}.
+     *
+     * @param requestId the ID of this request
+     * @param packageName the package name of the application whose media to route.
+     * @param routeId the ID of the route initially being {@link
+     *     RoutingSessionInfo#getSelectedRoutes() selected}.
+     * @param sessionHints an optional bundle of arguments sent by {@link MediaRouter2}, or null if
+     *     none.
+     * @see RoutingSessionInfo.Builder
+     * @see #notifySystemMediaSessionCreated
+     * @hide
+     */
+    // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+    @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    public void onCreateSystemRoutingSession(
+            long requestId,
+            @NonNull String packageName,
+            @NonNull String routeId,
+            @Nullable Bundle sessionHints) {
+        mHandler.post(() -> notifyRequestFailed(requestId, REASON_UNIMPLEMENTED));
+    }
+
+    /**
      * Called when the session should be released. A client of the session or system can request
      * a session to be released.
      * <p>
@@ -735,4 +830,100 @@
                     MediaRoute2ProviderService.this, requestId, sessionId));
         }
     }
+
+    /**
+     * Holds the streams to be routed as part of a system media routing session.
+     *
+     * <p>The encoded data format matches the {@link MediaStreamsFormats} passed to {@link
+     * #notifySystemMediaSessionCreated}.
+     *
+     * @hide
+     */
+    // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+    @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    public static final class MediaStreams {
+
+        private final AudioRecord mAudioRecord;
+
+        // TODO: b/380431086: Add the video equivalent.
+
+        private MediaStreams(AudioRecord mAudioRecord) {
+            this.mAudioRecord = mAudioRecord;
+        }
+
+        /**
+         * Returns the {@link AudioRecord} from which to read the audio data to route, or null if
+         * the routing session doesn't include audio.
+         */
+        @Nullable
+        public AudioRecord getAudioRecord() {
+            return mAudioRecord;
+        }
+    }
+
+    /**
+     * Holds the formats to encode media data to be read from {@link MediaStreams}.
+     *
+     * @see MediaStreams
+     * @see #notifySystemMediaSessionCreated
+     * @hide
+     */
+    // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+    @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    public static final class MediaStreamsFormats {
+
+        private final AudioFormat mAudioFormat;
+
+        // TODO: b/380431086: Add the video equivalent.
+
+        private MediaStreamsFormats(Builder builder) {
+            this.mAudioFormat = builder.mAudioFormat;
+        }
+
+        /**
+         * Returns the audio format to use for creating the {@link MediaStreams#getAudioRecord} to
+         * return from {@link #notifySystemMediaSessionCreated}.
+         *
+         * @hide
+         */
+        // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+        @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+        public AudioFormat getAudioFormat() {
+            return mAudioFormat;
+        }
+
+        /**
+         * Builder for {@link MediaStreamsFormats}
+         *
+         * @hide
+         */
+        // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+        @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+        public static final class Builder {
+            private AudioFormat mAudioFormat;
+
+            /**
+             * Sets the audio format to use for creating the {@link MediaStreams#getAudioRecord} to
+             * return from {@link #notifySystemMediaSessionCreated}.
+             *
+             * @param audioFormat the audio format
+             * @return this builder
+             */
+            @NonNull
+            public Builder setAudioFormat(@NonNull AudioFormat audioFormat) {
+                this.mAudioFormat = Objects.requireNonNull(audioFormat);
+                return this;
+            }
+
+            /**
+             * Builds the {@link MediaStreamsFormats} instance.
+             *
+             * @return the built {@link MediaStreamsFormats} instance
+             */
+            @NonNull
+            public MediaStreamsFormats build() {
+                return new MediaStreamsFormats(this);
+            }
+        }
+    }
 }
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index d8a8c8b..bbe8e4e 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -141,6 +141,14 @@
 }
 
 flag {
+    name: "enable_route_visibility_control_api"
+    namespace: "media_better_together"
+    description: "API changes to allow more control over route visibility by route providers"
+    bug: "367799834"
+    is_exported: true
+}
+
+flag {
     name: "enable_screen_off_scanning"
     is_exported: true
     namespace: "media_solutions"
diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig
index b4dee0c..fa1349c 100644
--- a/media/java/android/media/flags/projection.aconfig
+++ b/media/java/android/media/flags/projection.aconfig
@@ -16,4 +16,16 @@
      name: "stop_media_projection_on_call_end"
      description: "Stops MediaProjection sessions when a call ends"
      bug: "368336349"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "media_projection_connected_display_no_virtual_device"
+    namespace: "media_projection"
+    description: "Filter out display associated with a virtual device for media projection use case"
+    bug: "362720120"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+    is_exported: true
+}
+
diff --git a/media/java/android/media/projection/IMediaProjection.aidl b/media/java/android/media/projection/IMediaProjection.aidl
index 8ee966d..dacd1bd 100644
--- a/media/java/android/media/projection/IMediaProjection.aidl
+++ b/media/java/android/media/projection/IMediaProjection.aidl
@@ -17,13 +17,14 @@
 package android.media.projection;
 
 import android.media.projection.IMediaProjectionCallback;
+import android.media.projection.StopReason;
 import android.os.IBinder;
 import android.app.ActivityOptions.LaunchCookie;
 
 /** {@hide} */
 interface IMediaProjection {
     void start(IMediaProjectionCallback callback);
-    void stop();
+    void stop(StopReason stopReason);
 
     boolean canProjectAudio();
     boolean canProjectVideo();
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index b104972..1d92eab 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -16,11 +16,13 @@
 
 package android.media.projection;
 
+import android.graphics.Rect;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionCallback;
 import android.media.projection.IMediaProjectionWatcherCallback;
 import android.media.projection.MediaProjectionInfo;
 import android.media.projection.ReviewGrantedConsentResult;
+import android.media.projection.StopReason;
 import android.os.IBinder;
 import android.view.ContentRecordingSession;
 
@@ -107,12 +109,7 @@
     @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
-    void stopActiveProjection();
-
-    @EnforcePermission("MANAGE_MEDIA_PROJECTION")
-    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
-            + ".permission.MANAGE_MEDIA_PROJECTION)")
-    void notifyActiveProjectionCapturedContentResized(int width, int height);
+    void stopActiveProjection(in StopReason stopReason);
 
     @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
@@ -227,5 +224,11 @@
     @EnforcePermission("MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
-    void notifyWindowingModeChanged(int contentToRecord, int targetProcessUid, int windowingMode);
+    oneway void notifyWindowingModeChanged(int contentToRecord, int targetProcessUid, int windowingMode);
+
+    @EnforcePermission("MANAGE_MEDIA_PROJECTION")
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+            + ".permission.MANAGE_MEDIA_PROJECTION)")
+    oneway void notifyCaptureBoundsChanged(int contentToRecord, int targetProcessUid,
+            in Rect captureBounds);
 }
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 4114f53..4f7132a 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -317,7 +317,20 @@
     public void stop() {
         try {
             Log.d(TAG, "Content Recording: stopping projection");
-            mImpl.stop();
+            mImpl.stop(StopReason.STOP_HOST_APP);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to stop projection", e);
+        }
+    }
+
+    /**
+     * Stops projection.
+     * @hide
+     */
+    public void stop(@StopReason int stopReason) {
+        try {
+            Log.d(TAG, "Content Recording: stopping projection");
+            mImpl.stop(stopReason);
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to stop projection", e);
         }
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index dc55e41..9cc2cca 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -297,10 +297,10 @@
      * Stop the current projection if there is one.
      * @hide
      */
-    public void stopActiveProjection() {
+    public void stopActiveProjection(@StopReason int stopReason) {
         try {
             Log.d(TAG, "Content Recording: stopping active projection");
-            mService.stopActiveProjection();
+            mService.stopActiveProjection(stopReason);
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to stop the currently active media projection", e);
         }
diff --git a/media/java/android/media/projection/StopReason.aidl b/media/java/android/media/projection/StopReason.aidl
new file mode 100644
index 0000000..8611def
--- /dev/null
+++ b/media/java/android/media/projection/StopReason.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.media.projection;
+
+/**
+ * Identifies the reason for a MediaProjection being stopped (for metric logging purposes)
+ * @hide
+ */
+@Backing(type="int")
+enum StopReason {
+    STOP_UNKNOWN = 0,
+    STOP_HOST_APP = 1,
+    STOP_TARGET_REMOVED = 2,
+    STOP_DEVICE_LOCKED = 3,
+    STOP_PRIVACY_CHIP = 4,
+    STOP_QS_TILE = 5,
+    STOP_USER_SWITCH = 6,
+    STOP_FOREGROUND_SERVICE_CHANGE = 7,
+    STOP_NEW_PROJECTION = 8,
+    STOP_NEW_MEDIA_ROUTE = 9,
+    STOP_ERROR = 10,
+}
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.java b/media/java/android/media/quality/AmbientBacklightMetadata.java
index ad19d04..c295946 100644
--- a/media/java/android/media/quality/AmbientBacklightMetadata.java
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.java
@@ -110,9 +110,10 @@
     /**
      * Gets the number of horizontal color zones.
      *
-     * <p>A color zone is a group of lights that always display the same color.
+     * <p>A color zone is represented by one single aggregated color. The number should not be
+     * larger than 128.
      */
-    @IntRange(from = 0)
+    @IntRange(from = 0, to = 128)
     public int getHorizontalZonesNumber() {
         return mHorizontalZonesNumber;
     }
@@ -120,9 +121,10 @@
     /**
      * Gets the number of vertical color zones.
      *
-     * <p>A color zone is a group of lights that always display the same color.
+     * <p>A color zone is represented by one single aggregated color. The number should not be
+     * larger than 80.
      */
-    @IntRange(from = 0)
+    @IntRange(from = 0, to = 80)
     public int getVerticalZonesNumber() {
         return mVerticalZonesNumber;
     }
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index b7e75b7..70211ca 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -30,42 +30,44 @@
  * @hide
  */
 interface IMediaQualityManager {
-    PictureProfile createPictureProfile(in PictureProfile pp);
-    void updatePictureProfile(in String id, in PictureProfile pp);
-    void removePictureProfile(in String id);
-    PictureProfile getPictureProfile(in int type, in String name);
-    List<PictureProfile> getPictureProfilesByPackage(in String packageName);
-    List<PictureProfile> getAvailablePictureProfiles();
-    List<String> getPictureProfilePackageNames();
-    List<String> getPictureProfileAllowList();
-    void setPictureProfileAllowList(in List<String> packages);
-    PictureProfileHandle getPictureProfileHandle(in String id);
+    PictureProfile createPictureProfile(in PictureProfile pp, int userId);
+    void updatePictureProfile(in String id, in PictureProfile pp, int userId);
+    void removePictureProfile(in String id, int userId);
+    PictureProfile getPictureProfile(in int type, in String name, int userId);
+    List<PictureProfile> getPictureProfilesByPackage(in String packageName, int userId);
+    List<PictureProfile> getAvailablePictureProfiles(int userId);
+    boolean setDefaultPictureProfile(in String id, int userId);
+    List<String> getPictureProfilePackageNames(int userId);
+    List<String> getPictureProfileAllowList(int userId);
+    void setPictureProfileAllowList(in List<String> packages, int userId);
+    PictureProfileHandle getPictureProfileHandle(in String id, int userId);
 
-    SoundProfile createSoundProfile(in SoundProfile pp);
-    void updateSoundProfile(in String id, in SoundProfile pp);
-    void removeSoundProfile(in String id);
-    SoundProfile getSoundProfile(in int type, in String name);
-    List<SoundProfile> getSoundProfilesByPackage(in String packageName);
-    List<SoundProfile> getAvailableSoundProfiles();
-    List<String> getSoundProfilePackageNames();
-    List<String> getSoundProfileAllowList();
-    void setSoundProfileAllowList(in List<String> packages);
+    SoundProfile createSoundProfile(in SoundProfile pp, int userId);
+    void updateSoundProfile(in String id, in SoundProfile pp, int userId);
+    void removeSoundProfile(in String id, int userId);
+    SoundProfile getSoundProfile(in int type, in String name, int userId);
+    List<SoundProfile> getSoundProfilesByPackage(in String packageName, int userId);
+    List<SoundProfile> getAvailableSoundProfiles(int userId);
+    boolean setDefaultSoundProfile(in String id, int userId);
+    List<String> getSoundProfilePackageNames(int userId);
+    List<String> getSoundProfileAllowList(int userId);
+    void setSoundProfileAllowList(in List<String> packages, int userId);
 
     void registerPictureProfileCallback(in IPictureProfileCallback cb);
     void registerSoundProfileCallback(in ISoundProfileCallback cb);
     void registerAmbientBacklightCallback(in IAmbientBacklightCallback cb);
 
-    List<ParamCapability> getParamCapabilities(in List<String> names);
+    List<ParamCapability> getParamCapabilities(in List<String> names, int userId);
 
-    boolean isSupported();
-    void setAutoPictureQualityEnabled(in boolean enabled);
-    boolean isAutoPictureQualityEnabled();
-    void setSuperResolutionEnabled(in boolean enabled);
-    boolean isSuperResolutionEnabled();
-    void setAutoSoundQualityEnabled(in boolean enabled);
-    boolean isAutoSoundQualityEnabled();
+    boolean isSupported(int userId);
+    void setAutoPictureQualityEnabled(in boolean enabled, int userId);
+    boolean isAutoPictureQualityEnabled(int userId);
+    void setSuperResolutionEnabled(in boolean enabled, int userId);
+    boolean isSuperResolutionEnabled(int userId);
+    void setAutoSoundQualityEnabled(in boolean enabled, int userId);
+    boolean isAutoSoundQualityEnabled(int userId);
 
-    void setAmbientBacklightSettings(in AmbientBacklightSettings settings);
-    void setAmbientBacklightEnabled(in boolean enabled);
-    boolean isAmbientBacklightEnabled();
+    void setAmbientBacklightSettings(in AmbientBacklightSettings settings, int userId);
+    void setAmbientBacklightEnabled(in boolean enabled, int userId);
+    boolean isAmbientBacklightEnabled(int userId);
 }
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index 3fac74b..7b0bd04 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -74,6 +74,130 @@
          */
         public static final String PARAMETER_SATURATION = "saturation";
 
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_COLOR = "color";
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_HUE = "hue";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_BACKLIGHT = "backlight";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_COLOR_TUNER_BRIGHTNESS = "color_tuner_brightness";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_COLOR_TUNER_SATURATION = "color_tuner_saturation";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_COLOR_TUNER_HUE = "color_tuner_hue";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_COLOR_TUNER_REDO_FFSET = "color_tuner_red_offset";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_COLOR_TUNER_GREEN_OFFSET = "color_tuner_green_offset";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_COLOR_TUNER_BLUE_OFFSET = "color_tuner_blue_offset";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_COLOR_TUNER_RED_GAIN = "color_tuner_red_gain";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_COLOR_TUNER_GREEN_GAIN = "color_tuner_green_gain";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_COLOR_TUNER_BLUE_GAIN = "color_tuner_blue_gain";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_AI_PQ = "ai_pq";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_AI_SUPER_RESOLUTION = "ai_super_resolution";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_NOISE_REDUCTION = "noise_reduction";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_MPEG_NOISE_REDUCTION = "mpeg_noise_reduction";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_FLESH_TONE = "flesh_tone";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DECONTOUR = "decontour";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DYNAMIC_LUMA_CONTROL = "dynamic_luma_control";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_FILM_MODE = "film_mode";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_BLACK_STRETCH = "black_stretch";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_BLUE_STRETCH = "blue_stretch";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_COLOR_TUNE = "color_tune";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_COLOR_TEMPERATURE = "color_temperature";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_GLOBAL_DIMMING = "global_dimming";
+
         private PictureQuality() {
         }
     }
@@ -105,6 +229,129 @@
          */
         public static final String PARAMETER_TREBLE = "treble";
 
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_SOUND_MODE = "sound_mode";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_SURROUND_SOUND = "surround_sound";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_EQUALIZER_DETAIL = "equalizer_detail";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_SPEAKERS = "speakers";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_SPEAKERS_DELAY = "speakers_delay";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_EARC = "earc";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_AUTO_VOLUME_CONTROL = "auto_volume_control";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DOWN_MIX_MODE = "down_mix_mode";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DTS_DRC = "dts_drc";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DOLBY_AUDIO_PROCESSING = "dolby_audio_processing";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DOLBY_AUDIO_PROCESSING_SOUND_MODE =
+                "dolby_audio_processing_sound_mode";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DOLBY_AUDIO_PROCESSING_VOLUME_LEVELER =
+                "dolby_audio_processing_volume_leveler";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DOLBY_AUDIO_PROCESSING_SURROUND_VIRTUALIZER =
+                "dolby_audio_processing_surround_virtualizer";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DOLBY_AUDIO_PROCESSING_DOLBY_ATMOS =
+                "dolby_audio_processing_dolby_atmos";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DIALOGUE_ENHANCER = "dialogue_enhancer";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DTS_VIRTUAL_X = "dts_virtual_x";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DTS_VIRTUAL_X_TBHDX = "dts_virtual_x_tbhdx";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DTS_VIRTUAL_X_LIMITER = "dts_virtual_x_limiter";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DTS_VIRTUAL_X_TRU_SURROUND_X =
+                "dts_virtual_x_tru_surround_x";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DTS_VIRTUAL_X_TRU_VOLUME_HD =
+                "dts_virtual_x_tru_volume_hd";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DTS_VIRTUAL_X_DIALOG_CLARITY =
+                "dts_virtual_x_dialog_clarity";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DTS_VIRTUAL_X_DEFINITION = "dts_virtual_x_definition";
+
+        /**
+         * @hide
+         */
+        public static final String PARAMETER_DTS_VIRTUAL_X_HEIGHT = "dts_virtual_x_height";
+
+
         private SoundQuality() {
         }
     }
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 5005597..9d66086 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -47,6 +47,7 @@
 
     private final IMediaQualityManager mService;
     private final Context mContext;
+    private final int mUserId;
     private final Object mLock = new Object();
     // @GuardedBy("mLock")
     private final List<PictureProfileCallbackRecord> mPpCallbackRecords = new ArrayList<>();
@@ -61,6 +62,7 @@
      */
     public MediaQualityManager(Context context, IMediaQualityManager service) {
         mContext = context;
+        mUserId = context.getUserId();
         mService = service;
         IPictureProfileCallback ppCallback = new IPictureProfileCallback.Stub() {
             @Override
@@ -219,7 +221,7 @@
     public PictureProfile getPictureProfile(
             @PictureProfile.ProfileType int type, @NonNull String name) {
         try {
-            return mService.getPictureProfile(type, name);
+            return mService.getPictureProfile(type, name, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -236,7 +238,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
     public List<PictureProfile> getPictureProfilesByPackage(@NonNull String packageName) {
         try {
-            return mService.getPictureProfilesByPackage(packageName);
+            return mService.getPictureProfilesByPackage(packageName, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -248,7 +250,25 @@
     @NonNull
     public List<PictureProfile> getAvailablePictureProfiles() {
         try {
-            return mService.getAvailablePictureProfiles();
+            return mService.getAvailablePictureProfiles(mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets preferred default picture profile.
+     *
+     * @param id the ID of the default profile. {@code null} to unset the default profile.
+     * @return {@code true} if it's set successfully; {@code false} otherwise.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+    public boolean setDefaultPictureProfile(@Nullable String id) {
+        try {
+            return mService.setDefaultPictureProfile(id, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -265,7 +285,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
     public List<String> getPictureProfilePackageNames() {
         try {
-            return mService.getPictureProfilePackageNames();
+            return mService.getPictureProfilePackageNames(mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -277,7 +297,7 @@
      */
     public PictureProfileHandle getPictureProfileHandle(String id) {
         try {
-            return mService.getPictureProfileHandle(id);
+            return mService.getPictureProfileHandle(id, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -291,7 +311,7 @@
      */
     public void createPictureProfile(@NonNull PictureProfile pp) {
         try {
-            mService.createPictureProfile(pp);
+            mService.createPictureProfile(pp, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -303,7 +323,7 @@
      */
     public void updatePictureProfile(@NonNull String profileId, @NonNull PictureProfile pp) {
         try {
-            mService.updatePictureProfile(profileId, pp);
+            mService.updatePictureProfile(profileId, pp, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -315,7 +335,7 @@
      */
     public void removePictureProfile(@NonNull String profileId) {
         try {
-            mService.removePictureProfile(profileId);
+            mService.removePictureProfile(profileId, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -362,7 +382,7 @@
     public SoundProfile getSoundProfile(
             @SoundProfile.ProfileType int type, @NonNull String name) {
         try {
-            return mService.getSoundProfile(type, name);
+            return mService.getSoundProfile(type, name, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -379,7 +399,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
     public List<SoundProfile> getSoundProfilesByPackage(@NonNull String packageName) {
         try {
-            return mService.getSoundProfilesByPackage(packageName);
+            return mService.getSoundProfilesByPackage(packageName, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -391,7 +411,25 @@
     @NonNull
     public List<SoundProfile> getAvailableSoundProfiles() {
         try {
-            return mService.getAvailableSoundProfiles();
+            return mService.getAvailableSoundProfiles(mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets preferred default sound profile.
+     *
+     * @param id the ID of the default profile. {@code null} to unset the default profile.
+     * @return {@code true} if it's set successfully; {@code false} otherwise.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+    public boolean setDefaultSoundProfile(@Nullable String id) {
+        try {
+            return mService.setDefaultSoundProfile(id, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -409,7 +447,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
     public List<String> getSoundProfilePackageNames() {
         try {
-            return mService.getSoundProfilePackageNames();
+            return mService.getSoundProfilePackageNames(mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -424,7 +462,7 @@
      */
     public void createSoundProfile(@NonNull SoundProfile sp) {
         try {
-            mService.createSoundProfile(sp);
+            mService.createSoundProfile(sp, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -436,7 +474,7 @@
      */
     public void updateSoundProfile(@NonNull String profileId, @NonNull SoundProfile sp) {
         try {
-            mService.updateSoundProfile(profileId, sp);
+            mService.updateSoundProfile(profileId, sp, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -448,7 +486,7 @@
      */
     public void removeSoundProfile(@NonNull String profileId) {
         try {
-            mService.removeSoundProfile(profileId);
+            mService.removeSoundProfile(profileId, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -460,7 +498,7 @@
     @NonNull
     public List<ParamCapability> getParamCapabilities(@NonNull List<String> names) {
         try {
-            return mService.getParamCapabilities(names);
+            return mService.getParamCapabilities(names, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -478,7 +516,7 @@
     @NonNull
     public List<String> getPictureProfileAllowList() {
         try {
-            return mService.getPictureProfileAllowList();
+            return mService.getPictureProfileAllowList(mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -492,7 +530,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
     public void setPictureProfileAllowList(@NonNull List<String> packageNames) {
         try {
-            mService.setPictureProfileAllowList(packageNames);
+            mService.setPictureProfileAllowList(packageNames, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -510,7 +548,7 @@
     @NonNull
     public List<String> getSoundProfileAllowList() {
         try {
-            return mService.getSoundProfileAllowList();
+            return mService.getSoundProfileAllowList(mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -524,7 +562,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
     public void setSoundProfileAllowList(@NonNull List<String> packageNames) {
         try {
-            mService.setSoundProfileAllowList(packageNames);
+            mService.setSoundProfileAllowList(packageNames, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -536,7 +574,7 @@
      */
     public boolean isSupported() {
         try {
-            return mService.isSupported();
+            return mService.isSupported(mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -554,7 +592,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
     public void setAutoPictureQualityEnabled(boolean enabled) {
         try {
-            mService.setAutoPictureQualityEnabled(enabled);
+            mService.setAutoPictureQualityEnabled(enabled, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -565,7 +603,7 @@
      */
     public boolean isAutoPictureQualityEnabled() {
         try {
-            return mService.isAutoPictureQualityEnabled();
+            return mService.isAutoPictureQualityEnabled(mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -582,7 +620,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
     public void setSuperResolutionEnabled(boolean enabled) {
         try {
-            mService.setSuperResolutionEnabled(enabled);
+            mService.setSuperResolutionEnabled(enabled, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -593,7 +631,7 @@
      */
     public boolean isSuperResolutionEnabled() {
         try {
-            return mService.isSuperResolutionEnabled();
+            return mService.isSuperResolutionEnabled(mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -611,7 +649,7 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
     public void setAutoSoundQualityEnabled(boolean enabled) {
         try {
-            mService.setAutoSoundQualityEnabled(enabled);
+            mService.setAutoSoundQualityEnabled(enabled, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -622,7 +660,7 @@
      */
     public boolean isAutoSoundQualityEnabled() {
         try {
-            return mService.isAutoSoundQualityEnabled();
+            return mService.isAutoSoundQualityEnabled(mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -631,6 +669,7 @@
     /**
      * Registers a {@link AmbientBacklightCallback}.
      */
+    @RequiresPermission(android.Manifest.permission.READ_COLOR_ZONES)
     public void registerAmbientBacklightCallback(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AmbientBacklightCallback callback) {
@@ -644,6 +683,7 @@
     /**
      * Unregisters the existing {@link AmbientBacklightCallback}.
      */
+    @RequiresPermission(android.Manifest.permission.READ_COLOR_ZONES)
     public void unregisterAmbientBacklightCallback(
             @NonNull final AmbientBacklightCallback callback) {
         Preconditions.checkNotNull(callback);
@@ -664,11 +704,12 @@
      *
      * @param settings The settings to use for the backlight detector.
      */
+    @RequiresPermission(android.Manifest.permission.READ_COLOR_ZONES)
     public void setAmbientBacklightSettings(
             @NonNull AmbientBacklightSettings settings) {
         Preconditions.checkNotNull(settings);
         try {
-            mService.setAmbientBacklightSettings(settings);
+            mService.setAmbientBacklightSettings(settings, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -679,7 +720,7 @@
      */
     public boolean isAmbientBacklightEnabled() {
         try {
-            return mService.isAmbientBacklightEnabled();
+            return mService.isAmbientBacklightEnabled(mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -690,9 +731,10 @@
      *
      * @param enabled {@code true} to enable, {@code false} to disable.
      */
+    @RequiresPermission(android.Manifest.permission.READ_COLOR_ZONES)
     public void setAmbientBacklightEnabled(boolean enabled) {
         try {
-            mService.setAmbientBacklightEnabled(enabled);
+            mService.setAmbientBacklightEnabled(enabled, mUserId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 65e83b9..8fe5436 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -328,7 +328,7 @@
                 mRecognitionCallback,
                 new RecognitionConfig.Builder()
                     .setCaptureRequested(captureTriggerAudio)
-                    .setAllowMultipleTriggers(allowMultipleTriggers)
+                    .setMultipleTriggersAllowed(allowMultipleTriggers)
                     .setAudioCapabilities(audioCapabilities)
                     .build(),
                 runInBatterySaver);
diff --git a/media/java/android/media/tv/extension/clienttoken/IClientToken.aidl b/media/java/android/media/tv/extension/clienttoken/IClientToken.aidl
new file mode 100644
index 0000000..fa701b3
--- /dev/null
+++ b/media/java/android/media/tv/extension/clienttoken/IClientToken.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.extension.clienttoken;
+
+/**
+ * @hide
+ */
+interface IClientToken {
+    String generateClientToken();
+}
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 4b832ae..3451dfc 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -93,7 +93,7 @@
     name: "set_resource_holder_retain"
     is_exported: true
     namespace: "media_tv"
-    description: "Feature flag to add setResourceHolderRetain api to MediaCas and Tuner JAVA."
+    description: "Feature flag to add setResourceOwnershipRetention api to MediaCas and Tuner JAVA."
     bug: "372973197"
 }
 
@@ -112,3 +112,11 @@
     description : "Feature flag to enable APIs for applying picture profiles"
     bug: "337330263"
 }
+
+flag {
+    name: "hdmi_control_collect_physical_address"
+    is_exported: true
+    namespace: "media_tv"
+    description: "Collect physical address from HDMI-CEC messages in metrics"
+    bug: "376001043"
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index b1adb77..7f7a239 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -757,14 +757,14 @@
      * scenario, when both resource holder and resource challenger have same processId and same
      * priority.
      *
-     * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or
-     *     false to allow the resource challenger to acquire the resource. If not explicitly set,
-     *     resourceHolderRetain is set to false.
+     * @param enabled Set to {@code true} to allow the resource holder to retain ownership,
+     *     or false to allow the resource challenger to acquire the resource.
+     *     If not explicitly set, enabled is set to {@code false}.
      */
     @FlaggedApi(FLAG_SET_RESOURCE_HOLDER_RETAIN)
     @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
-    public void setResourceHolderRetain(boolean resourceHolderRetain) {
-        mTunerResourceManager.setResourceHolderRetain(mClientId, resourceHolderRetain);
+    public void setResourceOwnershipRetention(boolean enabled) {
+        mTunerResourceManager.setResourceOwnershipRetention(mClientId, enabled);
     }
 
     /**
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index be65ad9..2ed642e 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -227,15 +228,16 @@
      * scenario, when both resource holder and resource challenger have same processId and same
      * priority.
      *
-     * @param clientId The client id used to set ownership of resource to owner in case of resource
+     * @param clientId The client id used to set ownership of resource in case of resource
      *     challenger situation.
-     * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or
-     *     false to allow the resource challenger to acquire the resource. If not explicitly set,
-     *     resourceHolderRetain is set to false.
+     * @param enabled Set to {@code true} to allow the resource holder to retain ownership,
+     *     or false to allow the resource challenger to acquire the resource.
+     *     If not explicitly set, enabled is set to {@code false}.
      */
-    public void setResourceHolderRetain(int clientId, boolean resourceHolderRetain) {
+    @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+    public void setResourceOwnershipRetention(int clientId, boolean enabled) {
         try {
-            mService.setResourceHolderRetain(clientId, resourceHolderRetain);
+            mService.setResourceOwnershipRetention(clientId, enabled);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index c57be1b0..50f9fe5 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -156,12 +156,13 @@
      * scenario, when both Resource Holder and Resource Challenger have same processId and same
      * priority.
      *
-     * @param clientId The resourceHolderRetain of the client is updated using client ID.
-     * @param resourceHolderRetain set to true to allow the Resource Holder to retain ownership, or
-     *     false to allow the Resource Challenger to acquire the resource. If not explicitly set,
-     *     resourceHolderRetain is set to false.
+     * @param clientId The client id used to set ownership of resource in case of resource
+     *     challenger situation.
+     * @param enabled Set to {@code true} to allow the Resource Holder to retain ownership,
+     *     or false to allow the Resource Challenger to acquire the resource.
+     *     If not explicitly set, enabled is set to {@code false}.
      */
-    void setResourceHolderRetain(int clientId, boolean resourceHolderRetain);
+    void setResourceOwnershipRetention(int clientId, boolean enabled);
 
     /*
      * This API is used by the Tuner framework to request a frontend from the TunerHAL.
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index 88c1c43..ec336d5 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -27,6 +27,7 @@
 import android.hardware.ICameraServiceListener;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.CameraMetadataInfo;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.CaptureResultExtras;
 import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
@@ -217,7 +218,7 @@
          * android.hardware.camera2.CaptureResultExtras)
          */
         @Override
-        public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras,
+        public void onResultReceived(CameraMetadataInfo result, CaptureResultExtras resultExtras,
                 PhysicalCaptureResultInfo physicalResults[]) throws RemoteException {
             // TODO Auto-generated method stub
         }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 3758c51..7d1e5f8 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -33,6 +33,7 @@
 import android.hardware.ICameraService;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraMetadataInfo;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.ICameraDeviceUser;
@@ -141,7 +142,7 @@
          * android.hardware.camera2.impl.CameraMetadataNative,
          * android.hardware.camera2.CaptureResultExtras)
          */
-        public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras,
+        public void onResultReceived(CameraMetadataInfo result, CaptureResultExtras resultExtras,
                 PhysicalCaptureResultInfo physicalResults[]) throws RemoteException {
             // TODO Auto-generated method stub
 
@@ -186,10 +187,14 @@
         }
     }
 
-    class IsMetadataNotEmpty implements ArgumentMatcher<CameraMetadataNative> {
+    class IsMetadataNotEmpty implements ArgumentMatcher<CameraMetadataInfo> {
         @Override
-        public boolean matches(CameraMetadataNative obj) {
-            return !obj.isEmpty();
+        public boolean matches(CameraMetadataInfo obj) {
+            if (obj.getTag() == CameraMetadataInfo.metadata) {
+                return !(obj.getMetadata().isEmpty());
+            } else {
+                return (obj.getFmqSize() != 0);
+            }
         }
     }
 
diff --git a/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java b/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
index c9807e6..58aa56b 100644
--- a/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
+++ b/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
@@ -46,7 +46,7 @@
     }
 
     @Override
-    public void stop() throws RemoteException {
+    public void stop(@StopReason int stopReason) throws RemoteException {
         // Pass along to the client's callback wrapper.
         mIMediaProjectionCallback.onStop();
     }
diff --git a/native/android/Android.bp b/native/android/Android.bp
index da29c49..cd6de5a 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -55,6 +55,7 @@
         "surface_control_input_receiver.cpp",
         "choreographer.cpp",
         "configuration.cpp",
+        "display_luts.cpp",
         "dynamic_instrumentation_manager.cpp",
         "hardware_buffer_jni.cpp",
         "input.cpp",
diff --git a/native/android/OWNERS b/native/android/OWNERS
index 9a3527d..1fde7d2 100644
--- a/native/android/OWNERS
+++ b/native/android/OWNERS
@@ -2,7 +2,7 @@
 
 # General NDK API reviewers
 per-file libandroid.map.txt = danalbert@google.com, etalvala@google.com, michaelwr@google.com
-per-file libandroid.map.txt = jreck@google.com, zyy@google.com
+per-file libandroid.map.txt = jreck@google.com, zyy@google.com, mattbuckley@google.com
 
 # Networking
 per-file libandroid_net.map.txt, net.c = set noparent
@@ -31,3 +31,4 @@
 
 # PerformanceHint
 per-file performance_hint.cpp = file:/ADPF_OWNERS
+per-file thermal.cpp = file:/ADPF_OWNERS
diff --git a/native/android/display_luts.cpp b/native/android/display_luts.cpp
new file mode 100644
index 0000000..b03a718
--- /dev/null
+++ b/native/android/display_luts.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "DisplayLuts"
+
+#include <android/display_luts.h>
+#include <display_luts_private.h>
+#include <utils/Log.h>
+
+#include <cmath>
+
+#define ADISPLAYLUTS_BUFFER_LENGTH_LIMIT (100000)
+
+#define CHECK_NOT_NULL(name) \
+    LOG_ALWAYS_FATAL_IF(name == nullptr, "nullptr passed as " #name " argument");
+
+ADisplayLutsEntry* ADisplayLutsEntry_createEntry(float* buffer, int32_t length,
+                                                 ADisplayLuts_Dimension dimension,
+                                                 ADisplayLuts_SamplingKey key) {
+    CHECK_NOT_NULL(buffer);
+    LOG_ALWAYS_FATAL_IF(length >= ADISPLAYLUTS_BUFFER_LENGTH_LIMIT,
+                        "the lut raw buffer length is too big to handle");
+    if (dimension != ADISPLAYLUTS_ONE_DIMENSION && dimension != ADISPLAYLUTS_THREE_DIMENSION) {
+        LOG_ALWAYS_FATAL("the lut dimension is be either 1 or 3");
+    }
+    int32_t size = 0;
+    if (dimension == ADISPLAYLUTS_THREE_DIMENSION) {
+        LOG_ALWAYS_FATAL_IF(length % 3 != 0, "the 3d lut raw buffer is not divisible by 3");
+        int32_t lengthPerChannel = length / 3;
+        float sizeForDim = std::cbrt(static_cast<float>(lengthPerChannel));
+        LOG_ALWAYS_FATAL_IF(sizeForDim != (int)(sizeForDim),
+                            "the 3d lut buffer length is incorrect");
+        size = (int)sizeForDim;
+    } else {
+        size = length;
+    }
+    LOG_ALWAYS_FATAL_IF(size < 2, "the lut size for each dimension is too small");
+
+    ADisplayLutsEntry* entry = new ADisplayLutsEntry();
+    entry->buffer.data.resize(length);
+    std::copy(buffer, buffer + length, entry->buffer.data.begin());
+    entry->properties = {dimension, size, key};
+
+    entry->incStrong((void*)ADisplayLutsEntry_createEntry);
+    return static_cast<ADisplayLutsEntry*>(entry);
+}
+
+void ADisplayLutsEntry_destroy(ADisplayLutsEntry* entry) {
+    if (entry != NULL) {
+        entry->decStrong((void*)ADisplayLutsEntry_createEntry);
+    }
+}
+
+ADisplayLuts_Dimension ADisplayLutsEntry_getDimension(const ADisplayLutsEntry* entry) {
+    CHECK_NOT_NULL(entry);
+    return entry->properties.dimension;
+}
+
+int32_t ADisplayLutsEntry_getSize(const ADisplayLutsEntry* entry) {
+    CHECK_NOT_NULL(entry);
+    return entry->properties.size;
+}
+
+ADisplayLuts_SamplingKey ADisplayLutsEntry_getSamplingKey(const ADisplayLutsEntry* entry) {
+    CHECK_NOT_NULL(entry);
+    return entry->properties.samplingKey;
+}
+
+const float* ADisplayLutsEntry_getBuffer(const ADisplayLutsEntry* _Nonnull entry) {
+    CHECK_NOT_NULL(entry);
+    return entry->buffer.data.data();
+}
+
+ADisplayLuts* ADisplayLuts_create() {
+    ADisplayLuts* luts = new ADisplayLuts();
+    if (luts == NULL) {
+        delete luts;
+        return NULL;
+    }
+    luts->incStrong((void*)ADisplayLuts_create);
+    return static_cast<ADisplayLuts*>(luts);
+}
+
+void ADisplayLuts_clearLuts(ADisplayLuts* luts) {
+    for (auto& entry : luts->entries) {
+        entry->decStrong((void*)ADisplayLuts_setEntries); // Decrement ref count
+    }
+    luts->entries.clear();
+    luts->offsets.clear();
+    luts->totalBufferSize = 0;
+}
+
+void ADisplayLuts_destroy(ADisplayLuts* luts) {
+    if (luts != NULL) {
+        ADisplayLuts_clearLuts(luts);
+        luts->decStrong((void*)ADisplayLuts_create);
+    }
+}
+
+void ADisplayLuts_setEntries(ADisplayLuts* luts, ADisplayLutsEntry** entries, int32_t numEntries) {
+    CHECK_NOT_NULL(luts);
+    // always clear the previously set lut(s)
+    ADisplayLuts_clearLuts(luts);
+
+    // do nothing
+    if (!entries || numEntries == 0) {
+        return;
+    }
+
+    LOG_ALWAYS_FATAL_IF(numEntries > 2, "The number of entries should be not over 2!");
+    if (numEntries == 2 && entries[0]->properties.dimension != ADISPLAYLUTS_ONE_DIMENSION &&
+        entries[1]->properties.dimension != ADISPLAYLUTS_THREE_DIMENSION) {
+        LOG_ALWAYS_FATAL("The entries should be 1D and 3D in order!");
+    }
+
+    luts->offsets.reserve(numEntries);
+    luts->entries.reserve(numEntries);
+    for (int32_t i = 0; i < numEntries; i++) {
+        luts->offsets.emplace_back(luts->totalBufferSize);
+        luts->totalBufferSize += entries[i]->buffer.data.size();
+        luts->entries.emplace_back(entries[i]);
+        luts->entries.back()->incStrong((void*)ADisplayLuts_setEntries);
+    }
+}
\ No newline at end of file
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 9da7bec..78a7ddb 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -95,6 +95,15 @@
     AConfiguration_setTouchscreen;
     AConfiguration_setUiModeNight;
     AConfiguration_setUiModeType;
+    ADisplayLuts_create; # introduced=36
+    ADisplayLuts_setEntries; # introduced=36
+    ADisplayLuts_destroy; # introduced=36
+    ADisplayLutsEntry_createEntry; # introduced=36
+    ADisplayLutsEntry_getDimension; # introduced=36
+    ADisplayLutsEntry_getSize; # introduced=36
+    ADisplayLutsEntry_getSamplingKey; # introduced=36
+    ADisplayLutsEntry_getBuffer; # introduced=36
+    ADisplayLutsEntry_destroy; # introduced=36
     AInputEvent_getDeviceId;
     AInputEvent_getSource;
     AInputEvent_getType;
@@ -292,7 +301,6 @@
     ASurfaceTransaction_setEnableBackPressure; # introduced=31
     ASurfaceTransaction_setFrameRate; # introduced=30
     ASurfaceTransaction_setFrameRateWithChangeStrategy; # introduced=31
-    ASurfaceTransaction_setFrameRateParams; # introduced=36
     ASurfaceTransaction_clearFrameRate; # introduced=34
     ASurfaceTransaction_setFrameTimeline; # introduced=Tiramisu
     ASurfaceTransaction_setGeometry; # introduced=29
@@ -300,6 +308,7 @@
     ASurfaceTransaction_setHdrMetadata_smpte2086; # introduced=29
     ASurfaceTransaction_setExtendedRangeBrightness; # introduced=UpsideDownCake
     ASurfaceTransaction_setDesiredHdrHeadroom; # introduced=VanillaIceCream
+    ASurfaceTransaction_setLuts; # introduced=36
     ASurfaceTransaction_setOnComplete; # introduced=29
     ASurfaceTransaction_setOnCommit; # introduced=31
     ASurfaceTransaction_setPosition; # introduced=31
@@ -352,6 +361,8 @@
     AThermal_unregisterThermalStatusListener; # introduced=30
     AThermal_getThermalHeadroom; # introduced=31
     AThermal_getThermalHeadroomThresholds; # introduced=VanillaIceCream
+    AThermal_registerThermalHeadroomListener; # introduced=36
+    AThermal_unregisterThermalHeadroomListener; # introduced=36
     APerformanceHint_getManager; # introduced=Tiramisu
     APerformanceHint_createSession; # introduced=Tiramisu
     APerformanceHint_getPreferredUpdateRateNanos; # introduced=Tiramisu
@@ -366,6 +377,7 @@
     APerformanceHint_notifyWorkloadIncrease; # introduced=36
     APerformanceHint_notifyWorkloadReset; # introduced=36
     APerformanceHint_borrowSessionFromJava; # introduced=36
+    APerformanceHint_setNativeSurfaces; # introduced=36
     AWorkDuration_create; # introduced=VanillaIceCream
     AWorkDuration_release; # introduced=VanillaIceCream
     AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream
@@ -378,6 +390,8 @@
     ASessionCreationConfig_setTargetWorkDurationNanos; # introduced=36
     ASessionCreationConfig_setPreferPowerEfficiency; # introduced=36
     ASessionCreationConfig_setGraphicsPipeline; # introduced=36
+    ASessionCreationConfig_setNativeSurfaces; # introduced=36
+    ASessionCreationConfig_setUseAutoTiming; # introduced=36
   local:
     *;
 };
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 883e139..608c01c 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -29,13 +29,19 @@
 #include <aidl/android/os/SessionCreationConfig.h>
 #include <android-base/stringprintf.h>
 #include <android-base/thread_annotations.h>
+#include <android/binder_libbinder.h>
 #include <android/binder_manager.h>
 #include <android/binder_status.h>
+#include <android/native_window.h>
 #include <android/performance_hint.h>
+#include <android/surface_control.h>
 #include <android/trace.h>
 #include <android_os.h>
 #include <cutils/trace.h>
 #include <fmq/AidlMessageQueue.h>
+#include <gui/Surface.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/SurfaceControl.h>
 #include <inttypes.h>
 #include <jni_wrappers.h>
 #include <performance_hint_private.h>
@@ -66,7 +72,12 @@
 
 constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
 struct AWorkDuration : public hal::WorkDuration {};
-struct ASessionCreationConfig : public SessionCreationConfig {};
+struct ASessionCreationConfig : public SessionCreationConfig {
+    std::vector<wp<IBinder>> layers{};
+    bool hasMode(hal::SessionMode&& mode) {
+        return std::find(modesToEnable.begin(), modesToEnable.end(), mode) != modesToEnable.end();
+    }
+};
 
 bool kForceGraphicsPipeline = false;
 
@@ -158,6 +169,11 @@
     FMQWrapper& getFMQWrapper();
     bool canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) REQUIRES(sHintMutex);
     void initJava(JNIEnv* _Nonnull env);
+    ndk::ScopedAIBinder_Weak x;
+    template <class T>
+    static void layersFromNativeSurfaces(ANativeWindow** windows, int numWindows,
+                                         ASurfaceControl** controls, int numSurfaceControls,
+                                         std::vector<T>& out);
 
 private:
     // Necessary to create an empty binder object
@@ -203,6 +219,8 @@
     int setPreferPowerEfficiency(bool enabled);
     int reportActualWorkDuration(AWorkDuration* workDuration);
     bool isJava();
+    status_t setNativeSurfaces(ANativeWindow** windows, int numWindows, ASurfaceControl** controls,
+                               int numSurfaceControls);
 
 private:
     friend struct APerformanceHintManager;
@@ -231,7 +249,7 @@
     static int64_t sIDCounter GUARDED_BY(sHintMutex);
     // The most recent set of thread IDs
     std::vector<int32_t> mLastThreadIDs GUARDED_BY(sHintMutex);
-    std::optional<hal::SessionConfig> mSessionConfig GUARDED_BY(sHintMutex);
+    std::optional<hal::SessionConfig> mSessionConfig;
     // Tracing helpers
     void traceThreads(const std::vector<int32_t>& tids) REQUIRES(sHintMutex);
     void tracePowerEfficient(bool powerEfficient);
@@ -329,14 +347,12 @@
     ndk::ScopedAStatus ret;
     hal::SessionConfig sessionConfig{.id = -1};
 
-    SessionCreationConfig creationConfig{
+    ASessionCreationConfig creationConfig{{
             .tids = std::vector<int32_t>(threadIds, threadIds + size),
             .targetWorkDurationNanos = initialTargetWorkDurationNanos,
-    };
+    }};
 
-    return APerformanceHintManager::createSessionUsingConfig(static_cast<ASessionCreationConfig*>(
-                                                                     &creationConfig),
-                                                             tag, isJava);
+    return APerformanceHintManager::createSessionUsingConfig(&creationConfig, tag, isJava);
 }
 
 APerformanceHintSession* APerformanceHintManager::createSessionUsingConfig(
@@ -345,11 +361,29 @@
     hal::SessionConfig sessionConfig{.id = -1};
     ndk::ScopedAStatus ret;
 
+    // Hold the tokens weakly until we actually need them,
+    // then promote them, then drop all strong refs after
+    if (!sessionCreationConfig->layers.empty()) {
+        for (auto&& layerIter = sessionCreationConfig->layers.begin();
+             layerIter != sessionCreationConfig->layers.end();) {
+            sp<IBinder> promoted = layerIter->promote();
+            if (promoted == nullptr) {
+                layerIter = sessionCreationConfig->layers.erase(layerIter);
+            } else {
+                sessionCreationConfig->layerTokens.push_back(
+                        ndk::SpAIBinder(AIBinder_fromPlatformBinder(promoted.get())));
+                ++layerIter;
+            }
+        }
+    }
+
     ret = mHintManager->createHintSessionWithConfig(mToken, tag,
                                                     *static_cast<SessionCreationConfig*>(
                                                             sessionCreationConfig),
                                                     &sessionConfig, &session);
 
+    sessionCreationConfig->layerTokens.clear();
+
     if (!ret.isOk() || !session) {
         ALOGE("%s: PerformanceHint cannot create session. %s", __FUNCTION__, ret.getMessage());
         return nullptr;
@@ -679,6 +713,57 @@
     return 0;
 }
 
+status_t APerformanceHintSession::setNativeSurfaces(ANativeWindow** windows, int numWindows,
+                                                    ASurfaceControl** controls,
+                                                    int numSurfaceControls) {
+    if (!mSessionConfig.has_value()) {
+        return ENOTSUP;
+    }
+
+    std::vector<sp<IBinder>> layerHandles;
+    APerformanceHintManager::layersFromNativeSurfaces<sp<IBinder>>(windows, numWindows, controls,
+                                                                   numSurfaceControls,
+                                                                   layerHandles);
+
+    std::vector<ndk::SpAIBinder> ndkLayerHandles;
+    for (auto&& handle : layerHandles) {
+        ndkLayerHandles.emplace_back(ndk::SpAIBinder(AIBinder_fromPlatformBinder(handle)));
+    }
+
+    mHintSession->associateToLayers(ndkLayerHandles);
+    return 0;
+}
+
+template <class T>
+void APerformanceHintManager::layersFromNativeSurfaces(ANativeWindow** windows, int numWindows,
+                                                       ASurfaceControl** controls,
+                                                       int numSurfaceControls,
+                                                       std::vector<T>& out) {
+    std::scoped_lock lock(sHintMutex);
+    if (windows != nullptr) {
+        std::vector<ANativeWindow*> windowVec(windows, windows + numWindows);
+        for (auto&& window : windowVec) {
+            Surface* surface = static_cast<Surface*>(window);
+            if (Surface::isValid(surface)) {
+                const sp<IBinder>& handle = surface->getSurfaceControlHandle();
+                if (handle != nullptr) {
+                    out.push_back(handle);
+                }
+            }
+        }
+    }
+
+    if (controls != nullptr) {
+        std::vector<ASurfaceControl*> controlVec(controls, controls + numSurfaceControls);
+        for (auto&& aSurfaceControl : controlVec) {
+            SurfaceControl* control = reinterpret_cast<SurfaceControl*>(aSurfaceControl);
+            if (control->isValid()) {
+                out.push_back(control->getHandle());
+            }
+        }
+    }
+}
+
 // ===================================== FMQ wrapper implementation
 
 bool FMQWrapper::isActive() {
@@ -963,8 +1048,7 @@
                                   hal::SessionTag::APP, true);
 }
 
-APerformanceHintSession* APerformanceHint_borrowSessionFromJava(JNIEnv* env,
-                                                                    jobject sessionObj) {
+APerformanceHintSession* APerformanceHint_borrowSessionFromJava(JNIEnv* env, jobject sessionObj) {
     VALIDATE_PTR(env)
     VALIDATE_PTR(sessionObj)
     return APerformanceHintManager::getInstance()->getSessionFromJava(env, sessionObj);
@@ -1065,6 +1149,14 @@
     return session->notifyWorkloadReset(cpu, gpu, debugName);
 }
 
+int APerformanceHint_setNativeSurfaces(APerformanceHintSession* session,
+                                       ANativeWindow** nativeWindows, int nativeWindowsSize,
+                                       ASurfaceControl** surfaceControls, int surfaceControlsSize) {
+    VALIDATE_PTR(session)
+    return session->setNativeSurfaces(nativeWindows, nativeWindowsSize, surfaceControls,
+                                      surfaceControlsSize);
+}
+
 AWorkDuration* AWorkDuration_create() {
     return new AWorkDuration();
 }
@@ -1180,6 +1272,11 @@
         config->modesToEnable.push_back(hal::SessionMode::GRAPHICS_PIPELINE);
     } else {
         std::erase(config->modesToEnable, hal::SessionMode::GRAPHICS_PIPELINE);
+
+        // Remove automatic timing modes if we turn off GRAPHICS_PIPELINE,
+        // as it is a strict pre-requisite for these to run
+        std::erase(config->modesToEnable, hal::SessionMode::AUTO_CPU);
+        std::erase(config->modesToEnable, hal::SessionMode::AUTO_GPU);
     }
     return 0;
 }
@@ -1197,3 +1294,48 @@
 void APerformanceHint_setUseNewLoadHintBehaviorForTesting(bool newBehavior) {
     kForceNewHintBehavior = newBehavior;
 }
+
+int ASessionCreationConfig_setNativeSurfaces(ASessionCreationConfig* config,
+                                             ANativeWindow** nativeWindows, int nativeWindowsSize,
+                                             ASurfaceControl** surfaceControls,
+                                             int surfaceControlsSize) {
+    VALIDATE_PTR(config)
+
+    APerformanceHintManager::layersFromNativeSurfaces<wp<IBinder>>(nativeWindows, nativeWindowsSize,
+                                                                   surfaceControls,
+                                                                   surfaceControlsSize,
+                                                                   config->layers);
+
+    if (config->layers.empty()) {
+        return EINVAL;
+    }
+
+    return 0;
+}
+
+int ASessionCreationConfig_setUseAutoTiming(ASessionCreationConfig* _Nonnull config, bool cpu,
+                                            bool gpu) {
+    VALIDATE_PTR(config)
+    if ((cpu || gpu) && !config->hasMode(hal::SessionMode::GRAPHICS_PIPELINE)) {
+        ALOGE("Automatic timing is not supported unless graphics pipeline mode is enabled first");
+        return ENOTSUP;
+    }
+
+    if (config->hasMode(hal::SessionMode::AUTO_CPU)) {
+        if (!cpu) {
+            std::erase(config->modesToEnable, hal::SessionMode::AUTO_CPU);
+        }
+    } else if (cpu) {
+        config->modesToEnable.push_back(static_cast<hal::SessionMode>(hal::SessionMode::AUTO_CPU));
+    }
+
+    if (config->hasMode(hal::SessionMode::AUTO_GPU)) {
+        if (!gpu) {
+            std::erase(config->modesToEnable, hal::SessionMode::AUTO_GPU);
+        }
+    } else if (gpu) {
+        config->modesToEnable.push_back(static_cast<hal::SessionMode>(hal::SessionMode::AUTO_GPU));
+    }
+
+    return 0;
+}
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 698bc84..4fe0b80 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
+#include <android/gui/LutProperties.h>
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <android/native_window.h>
 #include <android/surface_control.h>
 #include <android/surface_control_jni.h>
 #include <android_runtime/android_view_SurfaceControl.h>
 #include <configstore/Utils.h>
+#include <cutils/ashmem.h>
+#include <display_luts_private.h>
 #include <gui/HdrMetadata.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/Surface.h>
@@ -53,6 +56,16 @@
 static_assert(static_cast<int>(ADATASPACE_DISPLAY_P3) ==
               static_cast<int>(HAL_DATASPACE_DISPLAY_P3));
 static_assert(static_cast<int>(ADATASPACE_BT2020_PQ) == static_cast<int>(HAL_DATASPACE_BT2020_PQ));
+static_assert(static_cast<int>(ADISPLAYLUTS_ONE_DIMENSION) ==
+              static_cast<int>(android::gui::LutProperties::Dimension::ONE_D));
+static_assert(static_cast<int>(ADISPLAYLUTS_THREE_DIMENSION) ==
+              static_cast<int>(android::gui::LutProperties::Dimension::THREE_D));
+static_assert(static_cast<int>(ADISPLAYLUTS_SAMPLINGKEY_RGB) ==
+              static_cast<int>(android::gui::LutProperties::SamplingKey::RGB));
+static_assert(static_cast<int>(ADISPLAYLUTS_SAMPLINGKEY_MAX_RGB) ==
+              static_cast<int>(android::gui::LutProperties::SamplingKey::MAX_RGB));
+static_assert(static_cast<int>(ADISPLAYLUTS_SAMPLINGKEY_CIE_Y) ==
+              static_cast<int>(android::gui::LutProperties::SamplingKey::CIE_Y));
 
 Transaction* ASurfaceTransaction_to_Transaction(ASurfaceTransaction* aSurfaceTransaction) {
     return reinterpret_cast<Transaction*>(aSurfaceTransaction);
@@ -693,6 +706,58 @@
     transaction->setDesiredHdrHeadroom(surfaceControl, desiredRatio);
 }
 
+void ASurfaceTransaction_setLuts(ASurfaceTransaction* aSurfaceTransaction,
+                                 ASurfaceControl* aSurfaceControl,
+                                 const struct ADisplayLuts* luts) {
+    CHECK_NOT_NULL(aSurfaceTransaction);
+    CHECK_NOT_NULL(aSurfaceControl);
+
+    sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+    Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+    int fd = -1;
+    std::vector<int32_t> offsets;
+    std::vector<int32_t> dimensions;
+    std::vector<int32_t> sizes;
+    std::vector<int32_t> samplingKeys;
+
+    if (luts) {
+        std::vector<float> buffer(luts->totalBufferSize);
+        int32_t count = luts->offsets.size();
+        offsets = luts->offsets;
+
+        dimensions.reserve(count);
+        sizes.reserve(count);
+        samplingKeys.reserve(count);
+        for (int32_t i = 0; i < count; i++) {
+            dimensions.emplace_back(luts->entries[i]->properties.dimension);
+            sizes.emplace_back(luts->entries[i]->properties.size);
+            samplingKeys.emplace_back(luts->entries[i]->properties.samplingKey);
+            std::copy(luts->entries[i]->buffer.data.begin(), luts->entries[i]->buffer.data.end(),
+                      buffer.begin() + offsets[i]);
+        }
+
+        // mmap
+        fd = ashmem_create_region("lut_shared_mem", luts->totalBufferSize * sizeof(float));
+        if (fd < 0) {
+            LOG_ALWAYS_FATAL("setLuts, ashmem_create_region() failed");
+            return;
+        }
+        void* ptr = mmap(nullptr, luts->totalBufferSize * sizeof(float), PROT_READ | PROT_WRITE,
+                         MAP_SHARED, fd, 0);
+        if (ptr == MAP_FAILED) {
+            LOG_ALWAYS_FATAL("setLuts, Failed to map the shared memory");
+            return;
+        }
+
+        memcpy(ptr, buffer.data(), luts->totalBufferSize * sizeof(float));
+        munmap(ptr, luts->totalBufferSize * sizeof(float));
+    }
+
+    transaction->setLuts(surfaceControl, base::unique_fd(fd), offsets, dimensions, sizes,
+                         samplingKeys);
+}
+
 void ASurfaceTransaction_setColor(ASurfaceTransaction* aSurfaceTransaction,
                                   ASurfaceControl* aSurfaceControl,
                                   float r, float g, float b, float alpha,
@@ -731,28 +796,6 @@
     transaction->setFrameRate(surfaceControl, frameRate, compatibility, changeFrameRateStrategy);
 }
 
-void ASurfaceTransaction_setFrameRateParams(
-        ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
-        float desiredMinRate, float desiredMaxRate, float fixedSourceRate,
-        ANativeWindow_ChangeFrameRateStrategy changeFrameRateStrategy) {
-    CHECK_NOT_NULL(aSurfaceTransaction);
-    CHECK_NOT_NULL(aSurfaceControl);
-    Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
-    sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
-
-    if (desiredMaxRate < desiredMinRate) {
-        ALOGW("desiredMaxRate must be greater than or equal to desiredMinRate");
-        return;
-    }
-    // TODO(b/362798998): Fix plumbing to send modern params
-    int compatibility = fixedSourceRate == 0 ? ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT
-                                             : ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
-    double frameRate = compatibility == ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
-            ? fixedSourceRate
-            : desiredMinRate;
-    transaction->setFrameRate(surfaceControl, frameRate, compatibility, changeFrameRateStrategy);
-}
-
 void ASurfaceTransaction_clearFrameRate(ASurfaceTransaction* aSurfaceTransaction,
                                         ASurfaceControl* aSurfaceControl) {
     CHECK_NOT_NULL(aSurfaceTransaction);
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index c5fb808..b8f574f 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -73,16 +73,17 @@
     MOCK_METHOD(ScopedAStatus, closeSessionChannel, (), (override));
     MOCK_METHOD(ScopedAStatus, getCpuHeadroom,
                 (const ::aidl::android::os::CpuHeadroomParamsInternal& in_params,
-                 std::vector<float>* _aidl_return),
+                 std::optional<hal::CpuHeadroomResult>* _aidl_return),
                 (override));
     MOCK_METHOD(ScopedAStatus, getCpuHeadroomMinIntervalMillis, (int64_t* _aidl_return),
                 (override));
     MOCK_METHOD(ScopedAStatus, getGpuHeadroom,
                 (const ::aidl::android::os::GpuHeadroomParamsInternal& in_params,
-                 float* _aidl_return),
+                 std::optional<hal::GpuHeadroomResult>* _aidl_return),
                 (override));
     MOCK_METHOD(ScopedAStatus, getGpuHeadroomMinIntervalMillis, (int64_t* _aidl_return),
                 (override));
+    MOCK_METHOD(ScopedAStatus, passSessionManagerBinder, (const SpAIBinder& sessionManager));
     MOCK_METHOD(SpAIBinder, asBinder, (), (override));
     MOCK_METHOD(bool, isRemote, (), (override));
 };
@@ -99,6 +100,8 @@
     MOCK_METHOD(ScopedAStatus, close, (), (override));
     MOCK_METHOD(ScopedAStatus, reportActualWorkDuration2,
                 (const ::std::vector<hal::WorkDuration>& workDurations), (override));
+    MOCK_METHOD(ScopedAStatus, associateToLayers,
+                (const std::vector<::ndk::SpAIBinder>& in_layerTokens), (override));
     MOCK_METHOD(SpAIBinder, asBinder, (), (override));
     MOCK_METHOD(bool, isRemote, (), (override));
 };
diff --git a/native/android/tests/thermal/NativeThermalUnitTest.cpp b/native/android/tests/thermal/NativeThermalUnitTest.cpp
index 4e319fc..923ad01 100644
--- a/native/android/tests/thermal/NativeThermalUnitTest.cpp
+++ b/native/android/tests/thermal/NativeThermalUnitTest.cpp
@@ -77,12 +77,62 @@
                 (override));
 };
 
+struct HeadroomCallbackData {
+    void* data;
+    float headroom;
+    float forecast;
+    int32_t forecastSeconds;
+    const std::vector<float> thresholds;
+};
+
+struct StatusCallbackData {
+    void* data;
+    AThermalStatus status;
+};
+
+static std::optional<HeadroomCallbackData> headroomCalled1;
+static std::optional<HeadroomCallbackData> headroomCalled2;
+static std::optional<StatusCallbackData> statusCalled1;
+static std::optional<StatusCallbackData> statusCalled2;
+
+static std::vector<float> convertThresholds(const AThermalHeadroomThreshold* thresholds,
+                                            size_t size) {
+    std::vector<float> ret;
+    for (int i = 0; i < (int)size; i++) {
+        ret.emplace_back(thresholds[i].headroom);
+    }
+    return ret;
+};
+
+static void onHeadroomChange1(void* data, float headroom, float forecast, int32_t forecastSeconds,
+                              const AThermalHeadroomThreshold* thresholds, size_t size) {
+    headroomCalled1.emplace(data, headroom, forecast, forecastSeconds,
+                            convertThresholds(thresholds, size));
+}
+
+static void onHeadroomChange2(void* data, float headroom, float forecast, int32_t forecastSeconds,
+                              const AThermalHeadroomThreshold* thresholds, size_t size) {
+    headroomCalled2.emplace(data, headroom, forecast, forecastSeconds,
+                            convertThresholds(thresholds, size));
+}
+
+static void onStatusChange1(void* data, AThermalStatus status) {
+    statusCalled1.emplace(data, status);
+}
+static void onStatusChange2(void* data, AThermalStatus status) {
+    statusCalled2.emplace(data, status);
+}
+
 class NativeThermalUnitTest : public Test {
 public:
     void SetUp() override {
         mMockIThermalService = new StrictMock<MockIThermalService>();
         AThermal_setIThermalServiceForTesting(mMockIThermalService);
         mThermalManager = AThermal_acquireManager();
+        headroomCalled1.reset();
+        headroomCalled2.reset();
+        statusCalled1.reset();
+        statusCalled2.reset();
     }
 
     void TearDown() override {
@@ -117,9 +167,11 @@
     size_t size1;
     ASSERT_EQ(OK, AThermal_getThermalHeadroomThresholds(mThermalManager, &thresholds1, &size1));
     checkThermalHeadroomThresholds(expected, thresholds1, size1);
-    // following calls should be cached
-    EXPECT_CALL(*mMockIThermalService, getThermalHeadroomThresholds(_)).Times(0);
-
+    // following calls should not be cached
+    expected = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
+    EXPECT_CALL(*mMockIThermalService, getThermalHeadroomThresholds(_))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<0>(expected), Return(Status())));
     const AThermalHeadroomThreshold* thresholds2 = nullptr;
     size_t size2;
     ASSERT_EQ(OK, AThermal_getThermalHeadroomThresholds(mThermalManager, &thresholds2, &size2));
@@ -164,3 +216,248 @@
     ASSERT_EQ(EINVAL, AThermal_getThermalHeadroomThresholds(mThermalManager, &initialized, &size));
     delete[] initialized;
 }
+
+TEST_F(NativeThermalUnitTest, TestRegisterThermalHeadroomListener) {
+    EXPECT_CALL(*mMockIThermalService, registerThermalHeadroomListener(_, _))
+            .Times(Exactly(2))
+            .WillOnce(Return(
+                    Status::fromExceptionCode(binder::Status::Exception::EX_TRANSACTION_FAILED)));
+    float data1 = 1.0f;
+    float data2 = 2.0f;
+    ASSERT_EQ(EPIPE,
+              AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1));
+    ASSERT_EQ(EPIPE,
+              AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange2, &data2));
+
+    // verify only 1 service call to register a global listener
+    sp<IThermalHeadroomListener> capturedServiceListener;
+    Mock::VerifyAndClearExpectations(mMockIThermalService);
+    EXPECT_CALL(*mMockIThermalService, registerThermalHeadroomListener(_, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener),
+                            testing::Invoke([](const sp<IThermalHeadroomListener>&,
+                                               bool* aidl_return) { *aidl_return = true; }),
+                            Return(Status::ok())));
+    ASSERT_EQ(0,
+              AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1));
+    ASSERT_EQ(EINVAL,
+              AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1));
+    ASSERT_EQ(0,
+              AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange2, &data2));
+    const ::std::vector<float> thresholds = {0.1f, 0.2f};
+    capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, thresholds);
+    ASSERT_TRUE(headroomCalled1.has_value());
+    EXPECT_EQ(headroomCalled1->data, &data1);
+    EXPECT_EQ(headroomCalled1->headroom, 0.1f);
+    EXPECT_EQ(headroomCalled1->forecast, 0.3f);
+    EXPECT_EQ(headroomCalled1->forecastSeconds, 20);
+    EXPECT_EQ(headroomCalled1->thresholds, thresholds);
+    ASSERT_TRUE(headroomCalled2.has_value());
+    EXPECT_EQ(headroomCalled2->data, &data2);
+    EXPECT_EQ(headroomCalled2->headroom, 0.1f);
+    EXPECT_EQ(headroomCalled2->forecast, 0.3f);
+    EXPECT_EQ(headroomCalled2->forecastSeconds, 20);
+    EXPECT_EQ(headroomCalled2->thresholds, thresholds);
+
+    // after test finished the global service listener should be unregistered
+    EXPECT_CALL(*mMockIThermalService, unregisterThermalHeadroomListener(_, _))
+            .Times(Exactly(1))
+            .WillOnce(Return(binder::Status::ok()));
+}
+
+TEST_F(NativeThermalUnitTest, TestUnregisterThermalHeadroomListener) {
+    sp<IThermalHeadroomListener> capturedServiceListener;
+    EXPECT_CALL(*mMockIThermalService, registerThermalHeadroomListener(_, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener),
+                            testing::Invoke([](const sp<IThermalHeadroomListener>&,
+                                               bool* aidl_return) { *aidl_return = true; }),
+                            Return(Status::ok())));
+    float data1 = 1.0f;
+    float data2 = 2.0f;
+    ASSERT_EQ(0,
+              AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1));
+    ASSERT_EQ(0,
+              AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange2, &data2));
+    capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, {});
+    ASSERT_TRUE(headroomCalled1.has_value());
+    ASSERT_TRUE(headroomCalled2.has_value());
+
+    EXPECT_CALL(*mMockIThermalService, unregisterThermalHeadroomListener(_, _))
+            .Times(Exactly(1))
+            .WillRepeatedly(Return(
+                    Status::fromExceptionCode(binder::Status::Exception::EX_TRANSACTION_FAILED)));
+
+    // callback 1 should be unregistered and callback 2 unregistration should fail due to service
+    // listener unregistration call failure
+    ASSERT_EQ(0,
+              AThermal_unregisterThermalHeadroomListener(mThermalManager, onHeadroomChange1,
+                                                         &data1));
+    ASSERT_EQ(EPIPE,
+              AThermal_unregisterThermalHeadroomListener(mThermalManager, onHeadroomChange2,
+                                                         &data2));
+    // verify only callback 2 is called after callback 1 is unregistered
+    std::vector<float> thresholds = {0.1f, 0.2f};
+    headroomCalled1.reset();
+    headroomCalled2.reset();
+    capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, thresholds);
+    ASSERT_TRUE(!headroomCalled1.has_value());
+    ASSERT_TRUE(headroomCalled2.has_value());
+
+    // verify only 1 service call to unregister global service listener
+    Mock::VerifyAndClearExpectations(mMockIThermalService);
+    EXPECT_CALL(*mMockIThermalService, unregisterThermalHeadroomListener(_, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(testing::Invoke([](const sp<IThermalHeadroomListener>&,
+                                               bool* aidl_return) { *aidl_return = true; }),
+                            Return(Status::ok())));
+    ASSERT_EQ(EINVAL,
+              AThermal_unregisterThermalHeadroomListener(mThermalManager, onHeadroomChange1,
+                                                         &data1));
+    ASSERT_EQ(0,
+              AThermal_unregisterThermalHeadroomListener(mThermalManager, onHeadroomChange2,
+                                                         &data2));
+    // verify neither callback is called after global service listener is unregistered
+    headroomCalled1.reset();
+    headroomCalled2.reset();
+    capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, thresholds);
+    ASSERT_TRUE(!headroomCalled1.has_value());
+    ASSERT_TRUE(!headroomCalled2.has_value());
+
+    // verify adding a new callback will still work
+    Mock::VerifyAndClearExpectations(mMockIThermalService);
+    EXPECT_CALL(*mMockIThermalService, registerThermalHeadroomListener(_, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener),
+                            testing::Invoke([](const sp<IThermalHeadroomListener>&,
+                                               bool* aidl_return) { *aidl_return = true; }),
+                            Return(Status::ok())));
+    ASSERT_EQ(0,
+              AThermal_registerThermalHeadroomListener(mThermalManager, onHeadroomChange1, &data1));
+    headroomCalled1.reset();
+    capturedServiceListener->onHeadroomChange(0.1f, 0.3f, 20, thresholds);
+    ASSERT_TRUE(headroomCalled1.has_value());
+    EXPECT_EQ(headroomCalled1->data, &data1);
+    EXPECT_EQ(headroomCalled1->headroom, 0.1f);
+    EXPECT_EQ(headroomCalled1->forecast, 0.3f);
+    EXPECT_EQ(headroomCalled1->forecastSeconds, 20);
+    EXPECT_EQ(headroomCalled1->thresholds, thresholds);
+
+    // after test finished the global service listener should be unregistered
+    EXPECT_CALL(*mMockIThermalService, unregisterThermalHeadroomListener(_, _))
+            .Times(Exactly(1))
+            .WillOnce(Return(binder::Status::ok()));
+}
+
+TEST_F(NativeThermalUnitTest, TestRegisterThermalStatusListener) {
+    EXPECT_CALL(*mMockIThermalService, registerThermalStatusListener(_, _))
+            .Times(Exactly(2))
+            .WillOnce(Return(
+                    Status::fromExceptionCode(binder::Status::Exception::EX_TRANSACTION_FAILED)));
+    int data1 = 1;
+    int data2 = 2;
+    ASSERT_EQ(EPIPE,
+              AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1));
+    ASSERT_EQ(EPIPE,
+              AThermal_registerThermalStatusListener(mThermalManager, onStatusChange2, &data2));
+
+    // verify only 1 service call to register a global listener
+    sp<IThermalStatusListener> capturedServiceListener;
+    Mock::VerifyAndClearExpectations(mMockIThermalService);
+    EXPECT_CALL(*mMockIThermalService, registerThermalStatusListener(_, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener),
+                            testing::Invoke([](const sp<IThermalStatusListener>&,
+                                               bool* aidl_return) { *aidl_return = true; }),
+                            Return(Status::ok())));
+    ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1));
+    ASSERT_EQ(EINVAL,
+              AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1));
+    ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange2, &data2));
+
+    capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT);
+    ASSERT_TRUE(statusCalled1.has_value());
+    EXPECT_EQ(statusCalled1->data, &data1);
+    EXPECT_EQ(statusCalled1->status, AThermalStatus::ATHERMAL_STATUS_LIGHT);
+    ASSERT_TRUE(statusCalled2.has_value());
+    EXPECT_EQ(statusCalled2->data, &data2);
+    EXPECT_EQ(statusCalled2->status, AThermalStatus::ATHERMAL_STATUS_LIGHT);
+
+    // after test finished the callback should be unregistered
+    EXPECT_CALL(*mMockIThermalService, unregisterThermalStatusListener(_, _))
+            .Times(Exactly(1))
+            .WillOnce(Return(binder::Status::ok()));
+}
+
+TEST_F(NativeThermalUnitTest, TestUnregisterThermalStatusListener) {
+    sp<IThermalStatusListener> capturedServiceListener;
+    EXPECT_CALL(*mMockIThermalService, registerThermalStatusListener(_, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener),
+                            testing::Invoke([](const sp<IThermalStatusListener>&,
+                                               bool* aidl_return) { *aidl_return = true; }),
+                            Return(Status::ok())));
+    int data1 = 1;
+    int data2 = 2;
+    ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1));
+    ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange2, &data2));
+    capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT);
+    ASSERT_TRUE(statusCalled1.has_value());
+    ASSERT_TRUE(statusCalled2.has_value());
+
+    EXPECT_CALL(*mMockIThermalService, unregisterThermalStatusListener(_, _))
+            .Times(Exactly(1))
+            .WillOnce(Return(
+                    Status::fromExceptionCode(binder::Status::Exception::EX_TRANSACTION_FAILED)));
+    // callback 1 should be unregistered and callback 2 unregistration should fail due to service
+    // listener unregistration call failure
+    ASSERT_EQ(0,
+              AThermal_unregisterThermalStatusListener(mThermalManager, onStatusChange1, &data1));
+    ASSERT_EQ(EPIPE,
+              AThermal_unregisterThermalStatusListener(mThermalManager, onStatusChange2, &data2));
+
+    // verify only callback 2 is called after callback 1 is unregistered
+    statusCalled1.reset();
+    statusCalled2.reset();
+    capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT);
+    ASSERT_TRUE(!statusCalled1.has_value());
+    ASSERT_TRUE(statusCalled2.has_value());
+
+    // verify only 1 service call to unregister global service listener
+    Mock::VerifyAndClearExpectations(mMockIThermalService);
+    EXPECT_CALL(*mMockIThermalService, unregisterThermalStatusListener(_, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(testing::Invoke([](const sp<IThermalStatusListener>&,
+                                               bool* aidl_return) { *aidl_return = true; }),
+                            Return(Status::ok())));
+    ASSERT_EQ(EINVAL,
+              AThermal_unregisterThermalStatusListener(mThermalManager, onStatusChange1, &data1));
+    ASSERT_EQ(0,
+              AThermal_unregisterThermalStatusListener(mThermalManager, onStatusChange2, &data2));
+    // verify neither callback is called after global service listener is unregistered
+    statusCalled1.reset();
+    statusCalled2.reset();
+    capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT);
+    ASSERT_TRUE(!statusCalled1.has_value());
+    ASSERT_TRUE(!statusCalled2.has_value());
+
+    // verify adding a new callback will still work
+    Mock::VerifyAndClearExpectations(mMockIThermalService);
+    EXPECT_CALL(*mMockIThermalService, registerThermalStatusListener(_, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(testing::SaveArg<0>(&capturedServiceListener),
+                            testing::Invoke([](const sp<IThermalStatusListener>&,
+                                               bool* aidl_return) { *aidl_return = true; }),
+                            Return(Status::ok())));
+    ASSERT_EQ(0, AThermal_registerThermalStatusListener(mThermalManager, onStatusChange1, &data1));
+    statusCalled1.reset();
+    capturedServiceListener->onStatusChange(AThermalStatus::ATHERMAL_STATUS_LIGHT);
+    ASSERT_TRUE(statusCalled1.has_value());
+    EXPECT_EQ(statusCalled1->data, &data1);
+    EXPECT_EQ(statusCalled1->status, AThermalStatus::ATHERMAL_STATUS_LIGHT);
+
+    // after test finished the global service listener should be unregistered
+    EXPECT_CALL(*mMockIThermalService, unregisterThermalStatusListener(_, _))
+            .Times(Exactly(1))
+            .WillOnce(Return(binder::Status::ok()));
+}
diff --git a/native/android/thermal.cpp b/native/android/thermal.cpp
index f7a3537..cefcaf7 100644
--- a/native/android/thermal.cpp
+++ b/native/android/thermal.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "thermal"
 
 #include <android-base/thread_annotations.h>
+#include <android/os/BnThermalHeadroomListener.h>
 #include <android/os/BnThermalStatusListener.h>
 #include <android/os/IThermalService.h>
 #include <android/thermal.h>
@@ -33,10 +34,10 @@
 using namespace android;
 using namespace android::os;
 
-struct ThermalServiceListener : public BnThermalStatusListener {
+struct ThermalServiceStatusListener : public BnThermalStatusListener {
 public:
     virtual binder::Status onStatusChange(int32_t status) override;
-    ThermalServiceListener(AThermalManager *manager) {
+    ThermalServiceStatusListener(AThermalManager *manager) {
         mMgr = manager;
     }
 
@@ -44,11 +45,29 @@
     AThermalManager *mMgr;
 };
 
-struct ListenerCallback {
+struct ThermalServiceHeadroomListener : public BnThermalHeadroomListener {
+public:
+    virtual binder::Status onHeadroomChange(float headroom, float forecastHeadroom,
+                                            int32_t forecastSeconds,
+                                            const ::std::vector<float> &thresholds) override;
+    ThermalServiceHeadroomListener(AThermalManager *manager) {
+        mMgr = manager;
+    }
+
+private:
+    AThermalManager *mMgr;
+};
+
+struct StatusListenerCallback {
     AThermal_StatusCallback callback;
     void* data;
 };
 
+struct HeadroomListenerCallback {
+    AThermal_HeadroomCallback callback;
+    void *data;
+};
+
 static IThermalService *gIThermalServiceForTesting = nullptr;
 
 struct AThermalManager {
@@ -57,30 +76,44 @@
     AThermalManager() = delete;
     ~AThermalManager();
     status_t notifyStateChange(int32_t status);
+    status_t notifyHeadroomChange(float headroom, float forecastHeadroom, int32_t forecastSeconds,
+                                  const ::std::vector<float> &thresholds);
     status_t getCurrentThermalStatus(int32_t *status);
-    status_t addListener(AThermal_StatusCallback, void *data);
-    status_t removeListener(AThermal_StatusCallback, void *data);
+    status_t addStatusListener(AThermal_StatusCallback, void *data);
+    status_t removeStatusListener(AThermal_StatusCallback, void *data);
     status_t getThermalHeadroom(int32_t forecastSeconds, float *result);
     status_t getThermalHeadroomThresholds(const AThermalHeadroomThreshold **, size_t *size);
+    status_t addHeadroomListener(AThermal_HeadroomCallback, void *data);
+    status_t removeHeadroomListener(AThermal_HeadroomCallback, void *data);
 
 private:
     AThermalManager(sp<IThermalService> service);
     sp<IThermalService> mThermalSvc;
-    std::mutex mListenerMutex;
-    sp<ThermalServiceListener> mServiceListener GUARDED_BY(mListenerMutex);
-    std::vector<ListenerCallback> mListeners GUARDED_BY(mListenerMutex);
-    std::mutex mThresholdsMutex;
-    const AThermalHeadroomThreshold *mThresholds = nullptr; // GUARDED_BY(mThresholdsMutex)
-    size_t mThresholdsCount GUARDED_BY(mThresholdsMutex);
+    std::mutex mStatusListenerMutex;
+    sp<ThermalServiceStatusListener> mServiceStatusListener GUARDED_BY(mStatusListenerMutex);
+    std::vector<StatusListenerCallback> mStatusListeners GUARDED_BY(mStatusListenerMutex);
+
+    std::mutex mHeadroomListenerMutex;
+    sp<ThermalServiceHeadroomListener> mServiceHeadroomListener GUARDED_BY(mHeadroomListenerMutex);
+    std::vector<HeadroomListenerCallback> mHeadroomListeners GUARDED_BY(mHeadroomListenerMutex);
 };
 
-binder::Status ThermalServiceListener::onStatusChange(int32_t status) {
+binder::Status ThermalServiceStatusListener::onStatusChange(int32_t status) {
     if (mMgr != nullptr) {
         mMgr->notifyStateChange(status);
     }
     return binder::Status::ok();
 }
 
+binder::Status ThermalServiceHeadroomListener::onHeadroomChange(
+        float headroom, float forecastHeadroom, int32_t forecastSeconds,
+        const ::std::vector<float> &thresholds) {
+    if (mMgr != nullptr) {
+        mMgr->notifyHeadroomChange(headroom, forecastHeadroom, forecastSeconds, thresholds);
+    }
+    return binder::Status::ok();
+}
+
 AThermalManager* AThermalManager::createAThermalManager() {
     if (gIThermalServiceForTesting) {
         return new AThermalManager(gIThermalServiceForTesting);
@@ -96,89 +129,113 @@
 }
 
 AThermalManager::AThermalManager(sp<IThermalService> service)
-      : mThermalSvc(std::move(service)), mServiceListener(nullptr) {}
+      : mThermalSvc(std::move(service)),
+        mServiceStatusListener(nullptr),
+        mServiceHeadroomListener(nullptr) {}
 
 AThermalManager::~AThermalManager() {
     {
-        std::scoped_lock<std::mutex> listenerLock(mListenerMutex);
-        mListeners.clear();
-        if (mServiceListener != nullptr) {
+        std::scoped_lock<std::mutex> listenerLock(mStatusListenerMutex);
+        mStatusListeners.clear();
+        if (mServiceStatusListener != nullptr) {
             bool success = false;
-            mThermalSvc->unregisterThermalStatusListener(mServiceListener, &success);
-            mServiceListener = nullptr;
+            mThermalSvc->unregisterThermalStatusListener(mServiceStatusListener, &success);
+            mServiceStatusListener = nullptr;
         }
     }
-    std::scoped_lock<std::mutex> lock(mThresholdsMutex);
-    delete[] mThresholds;
+    {
+        std::scoped_lock<std::mutex> headroomListenerLock(mHeadroomListenerMutex);
+        mHeadroomListeners.clear();
+        if (mServiceHeadroomListener != nullptr) {
+            bool success = false;
+            mThermalSvc->unregisterThermalHeadroomListener(mServiceHeadroomListener, &success);
+            mServiceHeadroomListener = nullptr;
+        }
+    }
 }
 
 status_t AThermalManager::notifyStateChange(int32_t status) {
-    std::scoped_lock<std::mutex> lock(mListenerMutex);
+    std::scoped_lock<std::mutex> lock(mStatusListenerMutex);
     AThermalStatus thermalStatus = static_cast<AThermalStatus>(status);
 
-    for (auto listener : mListeners) {
+    for (auto listener : mStatusListeners) {
         listener.callback(listener.data, thermalStatus);
     }
     return OK;
 }
 
-status_t AThermalManager::addListener(AThermal_StatusCallback callback, void *data) {
-    std::scoped_lock<std::mutex> lock(mListenerMutex);
+status_t AThermalManager::notifyHeadroomChange(float headroom, float forecastHeadroom,
+                                               int32_t forecastSeconds,
+                                               const ::std::vector<float> &thresholds) {
+    std::scoped_lock<std::mutex> lock(mHeadroomListenerMutex);
+    size_t thresholdsCount = thresholds.size();
+    auto t = new AThermalHeadroomThreshold[thresholdsCount];
+    for (int i = 0; i < (int)thresholdsCount; i++) {
+        t[i].headroom = thresholds[i];
+        t[i].thermalStatus = static_cast<AThermalStatus>(i);
+    }
+    for (auto listener : mHeadroomListeners) {
+        listener.callback(listener.data, headroom, forecastHeadroom, forecastSeconds, t,
+                          thresholdsCount);
+    }
+    delete[] t;
+    return OK;
+}
+
+status_t AThermalManager::addStatusListener(AThermal_StatusCallback callback, void *data) {
+    std::scoped_lock<std::mutex> lock(mStatusListenerMutex);
 
     if (callback == nullptr) {
         // Callback can not be nullptr
         return EINVAL;
     }
-    for (const auto& cb : mListeners) {
+    for (const auto &cb : mStatusListeners) {
         // Don't re-add callbacks.
         if (callback == cb.callback && data == cb.data) {
             return EINVAL;
         }
     }
-    mListeners.emplace_back(ListenerCallback{callback, data});
 
-    if (mServiceListener != nullptr) {
+    if (mServiceStatusListener != nullptr) {
+        mStatusListeners.emplace_back(StatusListenerCallback{callback, data});
         return OK;
     }
     bool success = false;
-    mServiceListener = new ThermalServiceListener(this);
-    if (mServiceListener == nullptr) {
+    mServiceStatusListener = new ThermalServiceStatusListener(this);
+    if (mServiceStatusListener == nullptr) {
         return ENOMEM;
     }
-    auto ret = mThermalSvc->registerThermalStatusListener(mServiceListener, &success);
+    auto ret = mThermalSvc->registerThermalStatusListener(mServiceStatusListener, &success);
     if (!success || !ret.isOk()) {
+        mServiceStatusListener = nullptr;
         ALOGE("Failed in registerThermalStatusListener %d", success);
         if (ret.exceptionCode() == binder::Status::EX_SECURITY) {
             return EPERM;
         }
         return EPIPE;
     }
+    mStatusListeners.emplace_back(StatusListenerCallback{callback, data});
     return OK;
 }
 
-status_t AThermalManager::removeListener(AThermal_StatusCallback callback, void *data) {
-    std::scoped_lock<std::mutex> lock(mListenerMutex);
+status_t AThermalManager::removeStatusListener(AThermal_StatusCallback callback, void *data) {
+    std::scoped_lock<std::mutex> lock(mStatusListenerMutex);
 
-    auto it = std::remove_if(mListeners.begin(),
-                             mListeners.end(),
-                             [&](const ListenerCallback& cb) {
-                                    return callback == cb.callback &&
-                                           data == cb.data;
+    auto it = std::remove_if(mStatusListeners.begin(), mStatusListeners.end(),
+                             [&](const StatusListenerCallback &cb) {
+                                 return callback == cb.callback && data == cb.data;
                              });
-    if (it == mListeners.end()) {
+    if (it == mStatusListeners.end()) {
         // If the listener and data pointer were not previously added.
         return EINVAL;
     }
-    mListeners.erase(it, mListeners.end());
+    if (mServiceStatusListener == nullptr || mStatusListeners.size() > 1) {
+        mStatusListeners.erase(it, mStatusListeners.end());
+        return OK;
+    }
 
-    if (!mListeners.empty()) {
-        return OK;
-    }
-    if (mServiceListener == nullptr) {
-        return OK;
-    }
     bool success = false;
-    auto ret = mThermalSvc->unregisterThermalStatusListener(mServiceListener, &success);
+    auto ret = mThermalSvc->unregisterThermalStatusListener(mServiceStatusListener, &success);
     if (!success || !ret.isOk()) {
         ALOGE("Failed in unregisterThermalStatusListener %d", success);
         if (ret.exceptionCode() == binder::Status::EX_SECURITY) {
@@ -186,7 +243,69 @@
         }
         return EPIPE;
     }
-    mServiceListener = nullptr;
+    mServiceStatusListener = nullptr;
+    mStatusListeners.erase(it, mStatusListeners.end());
+    return OK;
+}
+
+status_t AThermalManager::addHeadroomListener(AThermal_HeadroomCallback callback, void *data) {
+    std::scoped_lock<std::mutex> lock(mHeadroomListenerMutex);
+    if (callback == nullptr) {
+        return EINVAL;
+    }
+    for (const auto &cb : mHeadroomListeners) {
+        if (callback == cb.callback && data == cb.data) {
+            return EINVAL;
+        }
+    }
+
+    if (mServiceHeadroomListener != nullptr) {
+        mHeadroomListeners.emplace_back(HeadroomListenerCallback{callback, data});
+        return OK;
+    }
+    bool success = false;
+    mServiceHeadroomListener = new ThermalServiceHeadroomListener(this);
+    if (mServiceHeadroomListener == nullptr) {
+        return ENOMEM;
+    }
+    auto ret = mThermalSvc->registerThermalHeadroomListener(mServiceHeadroomListener, &success);
+    if (!success || !ret.isOk()) {
+        ALOGE("Failed in registerThermalHeadroomListener %d", success);
+        mServiceHeadroomListener = nullptr;
+        if (ret.exceptionCode() == binder::Status::EX_SECURITY) {
+            return EPERM;
+        }
+        return EPIPE;
+    }
+    mHeadroomListeners.emplace_back(HeadroomListenerCallback{callback, data});
+    return OK;
+}
+
+status_t AThermalManager::removeHeadroomListener(AThermal_HeadroomCallback callback, void *data) {
+    std::scoped_lock<std::mutex> lock(mHeadroomListenerMutex);
+
+    auto it = std::remove_if(mHeadroomListeners.begin(), mHeadroomListeners.end(),
+                             [&](const HeadroomListenerCallback &cb) {
+                                 return callback == cb.callback && data == cb.data;
+                             });
+    if (it == mHeadroomListeners.end()) {
+        return EINVAL;
+    }
+    if (mServiceHeadroomListener == nullptr || mHeadroomListeners.size() > 1) {
+        mHeadroomListeners.erase(it, mHeadroomListeners.end());
+        return OK;
+    }
+    bool success = false;
+    auto ret = mThermalSvc->unregisterThermalHeadroomListener(mServiceHeadroomListener, &success);
+    if (!success || !ret.isOk()) {
+        ALOGE("Failed in unregisterThermalHeadroomListener %d", success);
+        if (ret.exceptionCode() == binder::Status::EX_SECURITY) {
+            return EPERM;
+        }
+        return EPIPE;
+    }
+    mServiceHeadroomListener = nullptr;
+    mHeadroomListeners.erase(it, mHeadroomListeners.end());
     return OK;
 }
 
@@ -216,61 +335,36 @@
 
 status_t AThermalManager::getThermalHeadroomThresholds(const AThermalHeadroomThreshold **result,
                                                        size_t *size) {
-    std::scoped_lock<std::mutex> lock(mThresholdsMutex);
-    if (mThresholds == nullptr) {
-        auto thresholds = std::make_unique<std::vector<float>>();
-        binder::Status ret = mThermalSvc->getThermalHeadroomThresholds(thresholds.get());
-        if (!ret.isOk()) {
-            if (ret.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
-                // feature is not enabled
-                return ENOSYS;
-            }
-            return EPIPE;
+    auto thresholds = std::make_unique<std::vector<float>>();
+    binder::Status ret = mThermalSvc->getThermalHeadroomThresholds(thresholds.get());
+    if (!ret.isOk()) {
+        if (ret.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+            // feature is not enabled
+            return ENOSYS;
         }
-        mThresholdsCount = thresholds->size();
-        auto t = new AThermalHeadroomThreshold[mThresholdsCount];
-        for (int i = 0; i < (int)mThresholdsCount; i++) {
-            t[i].headroom = (*thresholds)[i];
-            t[i].thermalStatus = static_cast<AThermalStatus>(i);
-        }
-        mThresholds = t;
+        return EPIPE;
     }
-    *size = mThresholdsCount;
-    *result = mThresholds;
+    size_t thresholdsCount = thresholds->size();
+    auto t = new AThermalHeadroomThreshold[thresholdsCount];
+    for (int i = 0; i < (int)thresholdsCount; i++) {
+        t[i].headroom = (*thresholds)[i];
+        t[i].thermalStatus = static_cast<AThermalStatus>(i);
+    }
+    *size = thresholdsCount;
+    *result = t;
     return OK;
 }
 
-/**
-  * Acquire an instance of the thermal manager. This must be freed using
-  * {@link AThermal_releaseManager}.
-  *
-  * @return manager instance on success, nullptr on failure.
- */
 AThermalManager* AThermal_acquireManager() {
     auto manager = AThermalManager::createAThermalManager();
 
     return manager;
 }
 
-/**
- * Release the thermal manager pointer acquired by
- * {@link AThermal_acquireManager}.
- *
- * @param manager The manager to be released.
- *
- */
 void AThermal_releaseManager(AThermalManager *manager) {
     delete manager;
 }
 
-/**
-  * Gets the current thermal status.
-  *
-  * @param manager The manager instance to use to query the thermal status,
-  * acquired by {@link AThermal_acquireManager}.
-  *
-  * @return current thermal status, ATHERMAL_STATUS_ERROR on failure.
-*/
 AThermalStatus AThermal_getCurrentThermalStatus(AThermalManager *manager) {
     int32_t status = 0;
     status_t ret = manager->getCurrentThermalStatus(&status);
@@ -280,59 +374,16 @@
     return static_cast<AThermalStatus>(status);
 }
 
-/**
- * Register the thermal status listener for thermal status change.
- *
- * @param manager The manager instance to use to register.
- * acquired by {@link AThermal_acquireManager}.
- * @param callback The callback function to be called when thermal status updated.
- * @param data The data pointer to be passed when callback is called.
- *
- * @return 0 on success
- *         EINVAL if the listener and data pointer were previously added and not removed.
- *         EPERM if the required permission is not held.
- *         EPIPE if communication with the system service has failed.
- */
 int AThermal_registerThermalStatusListener(AThermalManager *manager,
-        AThermal_StatusCallback callback, void *data) {
-    return manager->addListener(callback, data);
+                                           AThermal_StatusCallback callback, void *data) {
+    return manager->addStatusListener(callback, data);
 }
 
-/**
- * Unregister the thermal status listener previously resgistered.
- *
- * @param manager The manager instance to use to unregister.
- * acquired by {@link AThermal_acquireManager}.
- * @param callback The callback function to be called when thermal status updated.
- * @param data The data pointer to be passed when callback is called.
- *
- * @return 0 on success
- *         EINVAL if the listener and data pointer were not previously added.
- *         EPERM if the required permission is not held.
- *         EPIPE if communication with the system service has failed.
- */
 int AThermal_unregisterThermalStatusListener(AThermalManager *manager,
-        AThermal_StatusCallback callback, void *data) {
-    return manager->removeListener(callback, data);
+                                             AThermal_StatusCallback callback, void *data) {
+    return manager->removeStatusListener(callback, data);
 }
 
-/**
- * Provides an estimate of how much thermal headroom the device currently has
- * before hitting severe throttling.
- *
- * Note that this only attempts to track the headroom of slow-moving sensors,
- * such as the skin temperature sensor. This means that there is no benefit to
- * calling this function more frequently than about once per second, and attempts
- * to call significantly more frequently may result in the function returning {@code NaN}.
- *
- * See also PowerManager#getThermalHeadroom.
- *
- * @param manager The manager instance to use
- * @param forecastSeconds how many seconds in the future to forecast
- * @return a value greater than or equal to 0.0 where 1.0 indicates the SEVERE throttling
- *  	   threshold. Returns NaN if the device does not support this functionality or if
- * 	       this function is called significantly faster than once per second.
- */
 float AThermal_getThermalHeadroom(AThermalManager *manager, int forecastSeconds) {
     float result = 0.0f;
     status_t ret = manager->getThermalHeadroom(forecastSeconds, &result);
@@ -354,3 +405,13 @@
 void AThermal_setIThermalServiceForTesting(void *iThermalService) {
     gIThermalServiceForTesting = static_cast<IThermalService *>(iThermalService);
 }
+
+int AThermal_registerThermalHeadroomListener(AThermalManager *manager,
+                                             AThermal_HeadroomCallback callback, void *data) {
+    return manager->addHeadroomListener(callback, data);
+}
+
+int AThermal_unregisterThermalHeadroomListener(AThermalManager *manager,
+                                               AThermal_HeadroomCallback callback, void *data) {
+    return manager->removeHeadroomListener(callback, data);
+}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 2aa73db..0ee81cb 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -233,6 +233,7 @@
     field @FlaggedApi("android.nfc.nfc_event_listener") public static final int NFC_INTERNAL_ERROR_NFC_CRASH_RESTART = 1; // 0x1
     field @FlaggedApi("android.nfc.nfc_event_listener") public static final int NFC_INTERNAL_ERROR_NFC_HARDWARE_ERROR = 2; // 0x2
     field @FlaggedApi("android.nfc.nfc_event_listener") public static final int NFC_INTERNAL_ERROR_UNKNOWN = 0; // 0x0
+    field @FlaggedApi("android.nfc.nfc_associated_role_services") public static final String PROPERTY_ALLOW_SHARED_ROLE_PRIORITY = "android.nfc.cardemulation.PROPERTY_ALLOW_SHARED_ROLE_PRIORITY";
     field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DEFAULT = 3; // 0x3
     field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_DH = 0; // 0x0
     field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final int PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE = 1; // 0x1
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 1019a10..e97b15d 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -57,7 +57,8 @@
   @FlaggedApi("android.nfc.nfc_oem_extension") public final class NfcOemExtension {
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int forceRoutingTableCommit();
-    method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList();
+    method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getActiveNfceeList();
+    method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public long getMaxPausePollingTimeoutMills();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.nfc.RoutingStatus getRoutingStatus();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.List<android.nfc.NfcRoutingTableEntry> getRoutingTable();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public android.nfc.T4tNdefNfcee getT4tNdefNfcee();
@@ -66,7 +67,7 @@
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagPresent();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void overwriteRoutingTable(int, int, int, int);
-    method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int pausePolling(int);
+    method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int pausePolling(long);
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int resumePolling();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAutoChangeEnabled(boolean);
@@ -123,6 +124,7 @@
 
   @FlaggedApi("android.nfc.nfc_oem_extension") public abstract class NfcRoutingTableEntry {
     method public int getNfceeId();
+    method public int getRouteType();
     method public int getType();
     field public static final int TYPE_AID = 0; // 0x0
     field public static final int TYPE_PROTOCOL = 1; // 0x1
diff --git a/nfc/java/android/nfc/Entry.java b/nfc/java/android/nfc/Entry.java
index 49d0f10..aa5ba58 100644
--- a/nfc/java/android/nfc/Entry.java
+++ b/nfc/java/android/nfc/Entry.java
@@ -25,11 +25,13 @@
     private final byte mType;
     private final byte mNfceeId;
     private final String mEntry;
+    private final String mRoutingType;
 
-    public Entry(String entry, byte type, byte nfceeId) {
+    public Entry(String entry, byte type, byte nfceeId, String routingType) {
         mEntry = entry;
         mType = type;
         mNfceeId = nfceeId;
+        mRoutingType = routingType;
     }
 
     public byte getType() {
@@ -44,6 +46,10 @@
         return mEntry;
     }
 
+    public String getRoutingType() {
+        return mRoutingType;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -53,6 +59,7 @@
         this.mEntry = in.readString();
         this.mNfceeId = in.readByte();
         this.mType = in.readByte();
+        this.mRoutingType = in.readString();
     }
 
     public static final @NonNull Parcelable.Creator<Entry> CREATOR =
@@ -73,5 +80,6 @@
         dest.writeString(mEntry);
         dest.writeByte(mNfceeId);
         dest.writeByte(mType);
+        dest.writeString(mRoutingType);
     }
 }
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 13e6734..ac0a5aa 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -53,7 +53,7 @@
     int getState();
     boolean disable(boolean saveState, in String pkg);
     boolean enable(in String pkg);
-    int pausePolling(int timeoutInMs);
+    int pausePolling(long timeoutInMs);
     int resumePolling();
 
     void setForegroundDispatch(in PendingIntent intent,
@@ -115,7 +115,7 @@
     void clearPreference();
     void setScreenState();
     void checkFirmware();
-    List<String> fetchActiveNfceeList();
+    Map fetchActiveNfceeList();
     void triggerInitialization();
     boolean getSettingStatus();
     boolean isTagPresent();
@@ -124,4 +124,5 @@
     int commitRouting();
     boolean isTagIntentAllowed(in String pkg, in int Userid);
     IT4tNdefNfcee getT4tNdefNfceeInterface();
+    long getMaxPausePollingTimeoutMs();
 }
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index ac7b8bb..fb11875 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -39,6 +39,7 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.se.omapi.Reader;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -148,6 +149,48 @@
     public @interface ControllerMode{}
 
     /**
+     * Technology Type for {@link #getActiveNfceeList()}.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+    public static final int NFCEE_TECH_NONE = 0;
+
+    /**
+     * Technology Type for {@link #getActiveNfceeList()}.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+    public static final int NFCEE_TECH_A = 1;
+
+    /**
+     * Technology Type for {@link #getActiveNfceeList()}.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+    public static final int NFCEE_TECH_B = 1 << 1;
+
+    /**
+     * Technology Type for {@link #getActiveNfceeList()}.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+    public static final int NFCEE_TECH_F = 1 << 2;
+
+    /**
+     * Nfc technology flags for {@link #getActiveNfceeList()}.
+     *
+     * @hide
+     */
+    @IntDef(flag = true, value = {
+        NFCEE_TECH_NONE,
+        NFCEE_TECH_A,
+        NFCEE_TECH_B,
+        NFCEE_TECH_F,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NfceeTechnology {}
+
+    /**
      * Event that Host Card Emulation is activated.
      */
     public static final int HCE_ACTIVATE = 1;
@@ -627,14 +670,18 @@
     /**
      * Get the Active NFCEE (NFC Execution Environment) List
      *
-     * @return List of activated secure elements on success
-     *         which can contain "eSE" and "UICC", otherwise empty list.
+     * @see Reader#getName() for the list of possible NFCEE names.
+     *
+     * @return Map< String, @NfceeTechnology Integer >
+     *         A HashMap where keys are activated secure elements and
+     *         the values are bitmap of technologies supported by each secure element
+     *         on success keys can contain "eSE" and "UICC", otherwise empty map.
      */
     @NonNull
     @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
-    public List<String> getActiveNfceeList() {
+    public Map<String, Integer> getActiveNfceeList() {
         return NfcAdapter.callServiceReturn(() ->
-            NfcAdapter.sService.fetchActiveNfceeList(), new ArrayList<String>());
+            NfcAdapter.sService.fetchActiveNfceeList(), new HashMap<String, Integer>());
     }
 
     /**
@@ -701,15 +748,16 @@
     /**
      * Pauses NFC tag reader mode polling for a {@code timeoutInMs} millisecond.
      * In case of {@code timeoutInMs} is zero or invalid polling will be stopped indefinitely.
-     * Use {@link #resumePolling() to resume the polling.
-     * @param timeoutInMs the pause polling duration in millisecond, ranging from 0 to 40000.
+     * Use {@link #resumePolling()} to resume the polling.
+     * Use {@link #getMaxPausePollingTimeoutMs()} to check the max timeout value.
+     * @param timeoutInMs the pause polling duration in millisecond.
      * @return status of the operation
      * @throws IllegalArgumentException if timeoutInMs value is invalid
      *         (0 < timeoutInMs < max).
      */
     @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public @PollingStateChangeStatusCode int pausePolling(@DurationMillisLong int timeoutInMs) {
+    public @PollingStateChangeStatusCode int pausePolling(@DurationMillisLong long timeoutInMs) {
         return NfcAdapter.callServiceReturn(() ->
                 NfcAdapter.sService.pausePolling(timeoutInMs),
                 POLLING_STATE_CHANGE_ALREADY_IN_REQUESTED_STATE);
@@ -729,6 +777,18 @@
     }
 
     /**
+     * Gets the max pause polling timeout value in millisecond.
+     * @return long integer representing the max timeout
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @DurationMillisLong
+    public long getMaxPausePollingTimeoutMills() {
+        return NfcAdapter.callServiceReturn(() ->
+                NfcAdapter.sService.getMaxPausePollingTimeoutMs(), 0L);
+    }
+
+    /**
      * Set whether to enable auto routing change or not (enabled by default).
      * If disabled, routing targets are limited to a single off-host destination.
      *
@@ -827,18 +887,22 @@
             switch (entry.getType()) {
                 case TYPE_TECHNOLOGY -> result.add(
                         new RoutingTableTechnologyEntry(entry.getNfceeId(),
-                                RoutingTableTechnologyEntry.techStringToInt(entry.getEntry()))
+                                RoutingTableTechnologyEntry.techStringToInt(entry.getEntry()),
+                                routeStringToInt(entry.getRoutingType()))
                 );
                 case TYPE_PROTOCOL -> result.add(
                         new RoutingTableProtocolEntry(entry.getNfceeId(),
-                                RoutingTableProtocolEntry.protocolStringToInt(entry.getEntry()))
+                                RoutingTableProtocolEntry.protocolStringToInt(entry.getEntry()),
+                                routeStringToInt(entry.getRoutingType()))
                 );
                 case TYPE_AID -> result.add(
-                        new RoutingTableAidEntry(entry.getNfceeId(), entry.getEntry())
+                        new RoutingTableAidEntry(entry.getNfceeId(), entry.getEntry(),
+                                routeStringToInt(entry.getRoutingType()))
                 );
                 case TYPE_SYSTEMCODE -> result.add(
                         new RoutingTableSystemCodeEntry(entry.getNfceeId(),
-                                entry.getEntry().getBytes(StandardCharsets.UTF_8))
+                                entry.getEntry().getBytes(StandardCharsets.UTF_8),
+                                routeStringToInt(entry.getRoutingType()))
                 );
             }
         }
diff --git a/nfc/java/android/nfc/NfcRoutingTableEntry.java b/nfc/java/android/nfc/NfcRoutingTableEntry.java
index c2cbbed..4153779 100644
--- a/nfc/java/android/nfc/NfcRoutingTableEntry.java
+++ b/nfc/java/android/nfc/NfcRoutingTableEntry.java
@@ -19,6 +19,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.SystemApi;
+import android.nfc.cardemulation.CardEmulation;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -35,6 +36,7 @@
 public abstract class NfcRoutingTableEntry {
     private final int mNfceeId;
     private final int mType;
+    private final int mRouteType;
 
     /**
      * AID routing table type.
@@ -67,9 +69,11 @@
     public @interface RoutingTableType {}
 
     /** @hide */
-    protected NfcRoutingTableEntry(int nfceeId, @RoutingTableType int type) {
+    protected NfcRoutingTableEntry(int nfceeId, @RoutingTableType int type,
+            @CardEmulation.ProtocolAndTechnologyRoute int routeType) {
         mNfceeId = nfceeId;
         mType = type;
+        mRouteType = routeType;
     }
 
     /**
@@ -88,4 +92,14 @@
     public int getType() {
         return mType;
     }
+
+    /**
+     * Get the route type of this entry.
+     * @return an integer defined in
+     * {@link android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute}
+     */
+    @CardEmulation.ProtocolAndTechnologyRoute
+    public int getRouteType() {
+        return mRouteType;
+    }
 }
diff --git a/nfc/java/android/nfc/RoutingTableAidEntry.java b/nfc/java/android/nfc/RoutingTableAidEntry.java
index bf697d6..be94f9f 100644
--- a/nfc/java/android/nfc/RoutingTableAidEntry.java
+++ b/nfc/java/android/nfc/RoutingTableAidEntry.java
@@ -18,6 +18,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.nfc.cardemulation.CardEmulation;
 
 /**
  * Represents an Application ID (AID) entry in current routing table.
@@ -29,8 +30,9 @@
     private final String mValue;
 
     /** @hide */
-    public RoutingTableAidEntry(int nfceeId, String value) {
-        super(nfceeId, TYPE_AID);
+    public RoutingTableAidEntry(int nfceeId, String value,
+            @CardEmulation.ProtocolAndTechnologyRoute int routeType) {
+        super(nfceeId, TYPE_AID, routeType);
         this.mValue = value;
     }
 
diff --git a/nfc/java/android/nfc/RoutingTableProtocolEntry.java b/nfc/java/android/nfc/RoutingTableProtocolEntry.java
index 536de4d..a68d8c1 100644
--- a/nfc/java/android/nfc/RoutingTableProtocolEntry.java
+++ b/nfc/java/android/nfc/RoutingTableProtocolEntry.java
@@ -18,6 +18,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.SystemApi;
+import android.nfc.cardemulation.CardEmulation;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -96,8 +97,9 @@
     private final @ProtocolValue int mValue;
 
     /** @hide */
-    public RoutingTableProtocolEntry(int nfceeId, @ProtocolValue int value) {
-        super(nfceeId, TYPE_PROTOCOL);
+    public RoutingTableProtocolEntry(int nfceeId, @ProtocolValue int value,
+            @CardEmulation.ProtocolAndTechnologyRoute int routeType) {
+        super(nfceeId, TYPE_PROTOCOL, routeType);
         this.mValue = value;
     }
 
diff --git a/nfc/java/android/nfc/RoutingTableSystemCodeEntry.java b/nfc/java/android/nfc/RoutingTableSystemCodeEntry.java
index f61892d..06cc0a5 100644
--- a/nfc/java/android/nfc/RoutingTableSystemCodeEntry.java
+++ b/nfc/java/android/nfc/RoutingTableSystemCodeEntry.java
@@ -18,6 +18,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.nfc.cardemulation.CardEmulation;
 
 /**
  * Represents a system code entry in current routing table, where system codes are two-byte values
@@ -31,8 +32,9 @@
     private final byte[] mValue;
 
     /** @hide */
-    public RoutingTableSystemCodeEntry(int nfceeId, byte[] value) {
-        super(nfceeId, TYPE_SYSTEM_CODE);
+    public RoutingTableSystemCodeEntry(int nfceeId, byte[] value,
+            @CardEmulation.ProtocolAndTechnologyRoute int routeType) {
+        super(nfceeId, TYPE_SYSTEM_CODE, routeType);
         this.mValue = value;
     }
 
diff --git a/nfc/java/android/nfc/RoutingTableTechnologyEntry.java b/nfc/java/android/nfc/RoutingTableTechnologyEntry.java
index 2dbc942..86239ce 100644
--- a/nfc/java/android/nfc/RoutingTableTechnologyEntry.java
+++ b/nfc/java/android/nfc/RoutingTableTechnologyEntry.java
@@ -18,6 +18,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.SystemApi;
+import android.nfc.cardemulation.CardEmulation;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -77,8 +78,9 @@
     private final @TechnologyValue int mValue;
 
     /** @hide */
-    public RoutingTableTechnologyEntry(int nfceeId, @TechnologyValue int value) {
-        super(nfceeId, TYPE_TECHNOLOGY);
+    public RoutingTableTechnologyEntry(int nfceeId, @TechnologyValue int value,
+            @CardEmulation.ProtocolAndTechnologyRoute int routeType) {
+        super(nfceeId, TYPE_TECHNOLOGY, routeType);
         this.mValue = value;
     }
 
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index 9ff83fe..308b5d1 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -171,6 +171,12 @@
     private boolean mShouldDefaultToObserveMode;
 
     /**
+     * Whether or not this service wants to share the same routing priority as the
+     * Wallet role owner.
+     */
+    private boolean mShareRolePriority;
+
+    /**
      * @hide
      */
     @UnsupportedAppUsage
@@ -307,6 +313,12 @@
                 mShouldDefaultToObserveMode = sa.getBoolean(
                         R.styleable.HostApduService_shouldDefaultToObserveMode,
                         false);
+                if (Flags.nfcAssociatedRoleServices()) {
+                    mShareRolePriority = sa.getBoolean(
+                            R.styleable.HostApduService_shareRolePriority,
+                            false
+                    );
+                }
                 sa.recycle();
             } else {
                 TypedArray sa = res.obtainAttributes(attrs,
@@ -337,6 +349,12 @@
                     }
                 }
                 mStaticOffHostName = mOffHostName;
+                if (Flags.nfcAssociatedRoleServices()) {
+                    mShareRolePriority = sa.getBoolean(
+                            R.styleable.OffHostApduService_shareRolePriority,
+                            false
+                    );
+                }
                 sa.recycle();
             }
 
@@ -728,6 +746,17 @@
     }
 
     /**
+     * Returns whether or not this service wants to share the Wallet role holder priority
+     * with other packages/services with the same signature.
+     *
+     * @return whether or not this service wants to share priority
+     */
+    @FlaggedApi(Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES)
+    public boolean shareRolePriority() {
+        return mShareRolePriority;
+    }
+
+    /**
      * Returns description of service.
      * @return user readable description of service
      */
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index cb364fb..8037702 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -246,6 +246,25 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface SetServiceEnabledStatusCode {}
 
+    /**
+     * Property name used to indicate that an application wants to allow associated services
+     * to share the same AID routing priority when this application is the role holder.
+     * <p>
+     * Example:
+     * <pre>
+     *     {@code
+     *     <application>
+     *       ...
+     *       <property android:name="android.nfc.cardemulation.PROPERTY_ALLOW_SHARED_ROLE_PRIORITY"
+     *         android:value="true"/>
+     *     </application>
+     *     }
+     * </pre>
+     */
+    @FlaggedApi(Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES)
+    public static final String PROPERTY_ALLOW_SHARED_ROLE_PRIORITY =
+            "android.nfc.cardemulation.PROPERTY_ALLOW_SHARED_ROLE_PRIORITY";
+
     static boolean sIsInitialized = false;
     static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
     static INfcCardEmulation sService;
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index 4f601f0..db1f6a2 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -107,7 +107,7 @@
  *     &lt;intent-filter&gt;
  *         &lt;action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/&gt;
  *     &lt;/intent-filter&gt;
- *     &lt;meta-data android:name="android.nfc.cardemulation.host_apdu_ervice" android:resource="@xml/apduservice"/&gt;
+ *     &lt;meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/apduservice"/&gt;
  * &lt;/service&gt;</pre>
  *
  * This meta-data tag points to an apduservice.xml file.
diff --git a/nfc/java/android/nfc/cardemulation/OffHostApduService.java b/nfc/java/android/nfc/cardemulation/OffHostApduService.java
index 2286e84..8d8a172 100644
--- a/nfc/java/android/nfc/cardemulation/OffHostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/OffHostApduService.java
@@ -96,7 +96,7 @@
  *     &lt;intent-filter&gt;
  *         &lt;action android:name="android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE"/&gt;
  *     &lt;/intent-filter&gt;
- *     &lt;meta-data android:name="android.nfc.cardemulation.off_host_apdu_ervice" android:resource="@xml/apduservice"/&gt;
+ *     &lt;meta-data android:name="android.nfc.cardemulation.off_host_apdu_service" android:resource="@xml/apduservice"/&gt;
  * &lt;/service&gt;</pre>
  *
  * This meta-data tag points to an apduservice.xml file.
diff --git a/nfc/tests/src/android/nfc/NdefMessageTest.java b/nfc/tests/src/android/nfc/NdefMessageTest.java
new file mode 100644
index 0000000..9ca295d
--- /dev/null
+++ b/nfc/tests/src/android/nfc/NdefMessageTest.java
@@ -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 android.nfc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class NdefMessageTest {
+    private NdefMessage mNdefMessage;
+    private NdefRecord mNdefRecord;
+
+    @Before
+    public void setUp() {
+        mNdefRecord = NdefRecord.createUri("http://www.example.com");
+        mNdefMessage = new NdefMessage(mNdefRecord);
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    @Test
+    public void testGetRecords() {
+        NdefRecord[] records = mNdefMessage.getRecords();
+        assertThat(records).isNotNull();
+        assertThat(records).hasLength(1);
+        assertThat(records[0]).isEqualTo(mNdefRecord);
+    }
+
+    @Test
+    public void testToByteArray() throws FormatException {
+        byte[] bytes = mNdefMessage.toByteArray();
+        assertThat(bytes).isNotNull();
+        assertThat(bytes.length).isGreaterThan(0);
+        NdefMessage ndefMessage = new NdefMessage(bytes);
+        assertThat(ndefMessage).isNotNull();
+    }
+}
diff --git a/nfc/tests/src/android/nfc/NdefRecordTest.java b/nfc/tests/src/android/nfc/NdefRecordTest.java
new file mode 100644
index 0000000..044c674
--- /dev/null
+++ b/nfc/tests/src/android/nfc/NdefRecordTest.java
@@ -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 android.nfc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NdefRecordTest {
+
+    @Test
+    public void testNdefRecordConstructor() throws FormatException {
+        NdefRecord applicationRecord = NdefRecord
+                .createApplicationRecord("com.android.test");
+        NdefRecord ndefRecord = new NdefRecord(applicationRecord.toByteArray());
+        assertThat(ndefRecord).isNotNull();
+        assertThat(ndefRecord.toByteArray().length).isGreaterThan(0);
+        assertThat(ndefRecord.getType()).isEqualTo("android.com:pkg".getBytes());
+        assertThat(ndefRecord.getPayload()).isEqualTo("com.android.test".getBytes());
+    }
+
+    @Test
+    public void testCreateExternal() {
+        NdefRecord ndefRecord = NdefRecord.createExternal("test",
+                "android.com:pkg", "com.android.test".getBytes());
+        assertThat(ndefRecord).isNotNull();
+        assertThat(ndefRecord.getType()).isEqualTo("test:android.com:pkg".getBytes());
+        assertThat(ndefRecord.getPayload()).isEqualTo("com.android.test".getBytes());
+    }
+
+    @Test
+    public void testCreateUri() {
+        NdefRecord ndefRecord = NdefRecord.createUri("http://www.example.com");
+        assertThat(ndefRecord).isNotNull();
+        assertThat(ndefRecord.getTnf()).isEqualTo(NdefRecord.TNF_WELL_KNOWN);
+        assertThat(ndefRecord.getType()).isEqualTo(NdefRecord.RTD_URI);
+    }
+
+    @Test
+    public void testCreateMime() {
+        NdefRecord ndefRecord = NdefRecord.createMime("text/plain", "example".getBytes());
+        assertThat(ndefRecord).isNotNull();
+        assertThat(ndefRecord.getTnf()).isEqualTo(NdefRecord.TNF_MIME_MEDIA);
+    }
+
+    @Test
+    public void testCreateTextRecord() {
+        String languageCode = Locale.getDefault().getLanguage();
+        NdefRecord ndefRecord = NdefRecord.createTextRecord(languageCode, "testdata");
+        assertThat(ndefRecord).isNotNull();
+        assertThat(ndefRecord.getTnf()).isEqualTo(NdefRecord.TNF_WELL_KNOWN);
+        assertThat(ndefRecord.getType()).isEqualTo(NdefRecord.RTD_TEXT);
+    }
+
+}
diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp
index e71597a..3916bf3 100644
--- a/omapi/aidl/Android.bp
+++ b/omapi/aidl/Android.bp
@@ -24,6 +24,7 @@
     backend: {
         java: {
             sdk_version: "module_current",
+            min_sdk_version: "35", // Make it 36 once available.
             apex_available: [
                 "//apex_available:platform",
                 "com.android.nfcservices",
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 8f9730a..6fdd73a 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -50,6 +50,7 @@
             android:name="com.android.carrierdefaultapp.CaptivePortalLoginActivity"
             android:label="@string/action_bar_label"
             android:exported="true"
+            android:enableOnBackInvokedCallback="false"
             android:permission="android.permission.MODIFY_PHONE_STATE"
             android:theme="@style/AppTheme"
             android:configChanges="keyboardHidden|orientation|screenSize">
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
index 8e7b8dd5..2ba93f1 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
@@ -731,6 +731,25 @@
     }
 
     /**
+     * The minimum value that can be returned by any observer.
+     * It represents that no mitigations were available.
+     */
+    public static final int LEAST_PACKAGE_HEALTH_OBSERVER_IMPACT =
+            PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+
+    /**
+     * The mitigation impact beyond which the user will start noticing the mitigations.
+     */
+    public static final int MEDIUM_USER_IMPACT_THRESHOLD =
+            PackageHealthObserverImpact.USER_IMPACT_LEVEL_20;
+
+    /**
+     * The mitigation impact beyond which the user impact is severely high.
+     */
+    public static final int HIGH_USER_IMPACT_THRESHOLD =
+            PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
+
+    /**
      * Possible severity values of the user impact of a
      * {@link PackageHealthObserver#onExecuteHealthCheckMitigation}.
      * @hide
@@ -773,6 +792,11 @@
         /**
          * Called when health check fails for the {@code versionedPackage}.
          *
+         * Note: if the returned user impact is higher than
+         * {@link #DEFAULT_HIGH_USER_IMPACT_THRESHOLD}, then
+         * {@link #onExecuteHealthCheckMitigation} would be called only in severe device conditions
+         * like boot-loop or network failure.
+         *
          * @param versionedPackage the package that is failing. This may be null if a native
          *                          service is crashing.
          * @param failureReason   the type of failure that is occurring.
@@ -780,8 +804,8 @@
          *                        (including this time).
          *
          *
-         * @return any one of {@link PackageHealthObserverImpact} to express the impact
-         * to the user on {@link #onExecuteHealthCheckMitigation}
+         * @return any value greater than {@link #LEAST_PACKAGE_HEALTH_OBSERVER_IMPACT} to express
+         * the impact of mitigation on the user in {@link #onExecuteHealthCheckMitigation}
          */
         @PackageHealthObserverImpact int onHealthCheckFailed(
                 @Nullable VersionedPackage versionedPackage,
@@ -790,9 +814,8 @@
 
         /**
          * This would be called after {@link #onHealthCheckFailed}.
-         * This is called only if current observer returned least
-         * {@link PackageHealthObserverImpact} mitigation for failed health
-         * check.
+         * This is called only if current observer returned least impact mitigation for failed
+         * health check.
          *
          * @param versionedPackage the package that is failing. This may be null if a native
          *                          service is crashing.
@@ -811,6 +834,9 @@
          *
          * @param mitigationCount the number of times mitigation has been attempted for this
          *                        boot loop (including this time).
+         *
+         * @return any value greater than {@link #LEAST_PACKAGE_HEALTH_OBSERVER_IMPACT} to express
+         * the impact of mitigation on the user in {@link #onExecuteBootLoopMitigation}
          */
         default @PackageHealthObserverImpact int onBootLoop(int mitigationCount) {
             return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
@@ -818,11 +844,13 @@
 
         /**
          * This would be called after {@link #onBootLoop}.
-         * This is called only if current observer returned least
-         * {@link PackageHealthObserverImpact} mitigation for fixing boot loop
+         * This is called only if current observer returned least impact mitigation for fixing
+         * boot loop.
          *
          * @param mitigationCount the number of times mitigation has been attempted for this
          *                        boot loop (including this time).
+         *
+         * @return {@code true} if action was executed successfully, {@code false} otherwise
          */
         default boolean onExecuteBootLoopMitigation(int mitigationCount) {
             return false;
diff --git a/packages/CredentialManager/res/values-fr/strings.xml b/packages/CredentialManager/res/values-fr/strings.xml
index 8617c6a..44dd306 100644
--- a/packages/CredentialManager/res/values-fr/strings.xml
+++ b/packages/CredentialManager/res/values-fr/strings.xml
@@ -83,7 +83,7 @@
     <string name="snackbar_action" msgid="37373514216505085">"Voir les options"</string>
     <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuer"</string>
     <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Options de connexion"</string>
-    <string name="button_label_view_more" msgid="3429098227286495651">"Afficher plus"</string>
+    <string name="button_label_view_more" msgid="3429098227286495651">"Voir plus"</string>
     <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pour <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
     <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestionnaires de mots de passe verrouillés"</string>
     <string name="locked_credential_entry_label_subtext_tap_to_unlock" msgid="6390367581393605009">"Appuyer pour déverrouiller"</string>
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 6a4bb21..a3b06e8 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -16,8 +16,10 @@
 
 package com.android.localtransport;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.backup.BackupAgent;
+import android.app.backup.BackupAnnotations;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
 import android.app.backup.BackupManagerMonitor;
@@ -52,6 +54,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 
 /**
  * Backup transport for stashing stuff into a known location on disk, and
@@ -939,4 +942,15 @@
             }
         }
     }
+
+    @NonNull
+    @Override
+    public List<String> getPackagesThatShouldNotUseRestrictedMode(
+            @NonNull List<String> packageNames,
+            @BackupAnnotations.OperationType int operationType) {
+        if (DEBUG) {
+            Log.d(TAG, "No restricted mode packages: " + mParameters.noRestrictedModePackages());
+        }
+        return mParameters.noRestrictedModePackages();
+    }
 }
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
index aaa18bf..c980913 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
@@ -16,26 +16,33 @@
 
 package com.android.localtransport;
 
-import android.util.KeyValueSettingObserver;
 import android.content.ContentResolver;
 import android.os.Handler;
 import android.provider.Settings;
 import android.util.KeyValueListParser;
+import android.util.KeyValueSettingObserver;
+
+import java.util.Arrays;
+import java.util.List;
 
 public class LocalTransportParameters extends KeyValueSettingObserver {
-    private static final String TAG = "LocalTransportParams";
     private static final String SETTING = Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS;
     private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag";
     private static final String KEY_NON_INCREMENTAL_ONLY = "non_incremental_only";
     private static final String KEY_IS_DEVICE_TRANSFER = "is_device_transfer";
     private static final String KEY_IS_ENCRYPTED = "is_encrypted";
     private static final String KEY_LOG_AGENT_RESULTS = "log_agent_results";
+    // This needs to be a list of package names separated by semicolons. For example:
+    // "com.package1;com.package2;com.package3". We can't use commas because the base class uses
+    // commas to split Key/Value pairs.
+    private static final String KEY_NO_RESTRICTED_MODE_PACKAGES = "no_restricted_mode_packages";
 
     private boolean mFakeEncryptionFlag;
     private boolean mIsNonIncrementalOnly;
     private boolean mIsDeviceTransfer;
     private boolean mIsEncrypted;
     private boolean mLogAgentResults;
+    private String mNoRestrictedModePackages;
 
     public LocalTransportParameters(Handler handler, ContentResolver resolver) {
         super(handler, resolver, Settings.Secure.getUriFor(SETTING));
@@ -61,6 +68,13 @@
         return mLogAgentResults;
     }
 
+    List<String> noRestrictedModePackages() {
+        if (mNoRestrictedModePackages == null) {
+            return List.of();
+        }
+        return Arrays.stream(mNoRestrictedModePackages.split(";")).toList();
+    }
+
     public String getSettingValue(ContentResolver resolver) {
         return Settings.Secure.getString(resolver, SETTING);
     }
@@ -71,5 +85,6 @@
         mIsDeviceTransfer = parser.getBoolean(KEY_IS_DEVICE_TRANSFER, false);
         mIsEncrypted = parser.getBoolean(KEY_IS_ENCRYPTED, false);
         mLogAgentResults = parser.getBoolean(KEY_LOG_AGENT_RESULTS, /* def */ false);
+        mNoRestrictedModePackages = parser.getString(KEY_NO_RESTRICTED_MODE_PACKAGES, /* def */ "");
     }
 }
diff --git a/packages/NeuralNetworks/OWNERS b/packages/NeuralNetworks/OWNERS
new file mode 100644
index 0000000..6b39150
--- /dev/null
+++ b/packages/NeuralNetworks/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 195575
+
+sandeepbandaru@google.com
+shivanker@google.com
+shiqing@google.com
\ No newline at end of file
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 68443a7..e029f3a 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -81,10 +81,12 @@
             android:exported="false" />
 
         <activity android:name=".PackageInstallerActivity"
-                android:exported="false" />
+                android:exported="false"
+                android:enableOnBackInvokedCallback="false" />
 
         <activity android:name=".InstallInstalling"
-                android:exported="false" />
+                android:exported="false"
+                android:enableOnBackInvokedCallback="false" />
 
         <receiver android:name=".common.InstallEventReceiver"
                 android:permission="android.permission.INSTALL_PACKAGES"
@@ -138,6 +140,7 @@
 
         <activity android:name=".UninstallUninstalling"
             android:excludeFromRecents="true"
+            android:enableOnBackInvokedCallback="false"
             android:exported="false" />
 
         <receiver android:name=".UninstallFinish"
diff --git a/packages/PackageInstaller/res/values-af/strings.xml b/packages/PackageInstaller/res/values-af/strings.xml
index 66303ed..77fad55 100644
--- a/packages/PackageInstaller/res/values-af/strings.xml
+++ b/packages/PackageInstaller/res/values-af/strings.xml
@@ -23,19 +23,19 @@
     <string name="cancel" msgid="1018267193425558088">"Kanselleer"</string>
     <string name="installing" msgid="4921993079741206516">"Installeer tans …"</string>
     <string name="installing_app" msgid="1165095864863849422">"Installeer tans <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> …"</string>
-    <string name="install_done" msgid="5987363587661783896">"Program geïnstalleer."</string>
+    <string name="install_done" msgid="5987363587661783896">"App geïnstalleer."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Wil jy hierdie program installeer?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Wil jy hierdie program opdateer?"</string>
     <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="7994800761970572198">"&lt;p&gt;Dateer hierdie app op vanaf &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Hierdie app ontvang gewoonlik opdaterings vanaf &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. As jy vanaf ’n ander bron opdateer, kan jy in die toekoms dalk opdaterings vanaf enige bron op jou tablet kry. Appfunksies kan verander.&lt;/p&gt;"</string>
     <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="2435174886412089791">"&lt;p&gt;Dateer hierdie app op vanaf &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Hierdie app ontvang gewoonlik opdaterings vanaf &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. As jy vanaf ’n ander bron opdateer, kan jy in die toekoms dalk opdaterings vanaf enige bron op jou TV kry. Appfunksies kan verander.&lt;/p&gt;"</string>
     <string name="install_confirm_question_update_owner_reminder" product="default" msgid="7155138616126795839">"&lt;p&gt;Dateer hierdie app op vanaf &lt;b&gt;<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>&lt;/b&gt;?&lt;/p&gt;&lt;p&gt;Hierdie app ontvang gewoonlik opdaterings vanaf &lt;b&gt;<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>&lt;/b&gt;. As jy vanaf ’n ander bron opdateer, kan jy in die toekoms dalk opdaterings vanaf enige bron op jou foon kry. Appfunksies kan verander.&lt;/p&gt;"</string>
-    <string name="install_failed" msgid="5777824004474125469">"Program nie geïnstalleer nie."</string>
+    <string name="install_failed" msgid="5777824004474125469">"App nie geïnstalleer nie."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Die installering van die pakket is geblokkeer."</string>
-    <string name="install_failed_conflict" msgid="3493184212162521426">"Program is nie geïnstalleer nie omdat pakket met \'n bestaande pakket bots."</string>
-    <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"Program is nie geïnstalleer nie omdat dit nie met jou tablet versoenbaar is nie."</string>
-    <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"Hierdie program is nie met jou TV versoenbaar nie."</string>
-    <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"Program is nie geïnstalleer nie omdat dit nie met jou foon versoenbaar is nie."</string>
-    <string name="install_failed_invalid_apk" msgid="8581007676422623930">"Program is nie geïnstalleer nie omdat pakket ongeldig blyk te wees."</string>
+    <string name="install_failed_conflict" msgid="3493184212162521426">"App is nie geïnstalleer nie omdat pakket met \'n bestaande pakket bots."</string>
+    <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"App is nie geïnstalleer nie omdat dit nie met jou tablet versoenbaar is nie."</string>
+    <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"Hierdie app is nie met jou TV versoenbaar nie."</string>
+    <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"App is nie geïnstalleer nie omdat dit nie met jou foon versoenbaar is nie."</string>
+    <string name="install_failed_invalid_apk" msgid="8581007676422623930">"App is nie geïnstalleer nie omdat pakket ongeldig blyk te wees."</string>
     <string name="install_failed_msg" product="tablet" msgid="6298387264270562442">"<xliff:g id="APP_NAME">%1$s</xliff:g> kon nie op jou tablet geïnstalleer word nie."</string>
     <string name="install_failed_msg" product="tv" msgid="1920009940048975221">"<xliff:g id="APP_NAME">%1$s</xliff:g> kon nie op jou TV geïnstalleer word nie."</string>
     <string name="install_failed_msg" product="default" msgid="6484461562647915707">"<xliff:g id="APP_NAME">%1$s</xliff:g> kon nie op jou foon geïnstalleer word nie."</string>
@@ -49,25 +49,25 @@
     <string name="manage_applications" msgid="5400164782453975580">"Bestuur programme"</string>
     <string name="out_of_space_dlg_title" msgid="4156690013884649502">"Geen spasie oor nie"</string>
     <string name="out_of_space_dlg_text" msgid="8727714096031856231">"<xliff:g id="APP_NAME">%1$s</xliff:g> kon nie geïnstalleer word nie. Maak spasie beskikbaar en probeer weer."</string>
-    <string name="app_not_found_dlg_title" msgid="5107924008597470285">"Program nie gevind nie"</string>
-    <string name="app_not_found_dlg_text" msgid="5219983779377811611">"Die program is nie in die lys geïnstalleerde programme gevind nie."</string>
+    <string name="app_not_found_dlg_title" msgid="5107924008597470285">"App nie gevind nie"</string>
+    <string name="app_not_found_dlg_text" msgid="5219983779377811611">"Die app is nie in die lys geïnstalleerde programme gevind nie."</string>
     <string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"Nie toegelaat nie"</string>
     <string name="user_is_not_allowed_dlg_text" msgid="3468447791330611681">"Die huidige gebruiker mag nie hierdie deïnstallering uitvoer nie."</string>
     <string name="generic_error_dlg_title" msgid="5863195085927067752">"Fout"</string>
-    <string name="generic_error_dlg_text" msgid="5287861443265795232">"Program kon nie gedeïnstalleer word nie."</string>
-    <string name="uninstall_application_title" msgid="4045420072401428123">"Deïnstalleer program"</string>
+    <string name="generic_error_dlg_text" msgid="5287861443265795232">"App kon nie gedeïnstalleer word nie."</string>
+    <string name="uninstall_application_title" msgid="4045420072401428123">"Deïnstalleer app"</string>
     <string name="uninstall_update_title" msgid="824411791011583031">"Deïnstalleer opdatering"</string>
-    <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> is deel van die volgende program:"</string>
+    <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> is deel van die volgende app:"</string>
     <string name="uninstall_application_text" msgid="3816830743706143980">"Wil jy hierdie app deïnstalleer?"</string>
     <string name="archive_application_text" msgid="8482325710714386348">"Jou persoonlike data sal gestoor word"</string>
     <string name="archive_application_text_all_users" msgid="3151229641681672580">"Argiveer hierdie app vir alle gebruikers? Jou persoonlike data sal gestoor word"</string>
     <string name="archive_application_text_current_user_work_profile" msgid="1450487362134779752">"Argiveer hierdie app op jou werkprofiel? Jou persoonlike data sal gestoor word"</string>
     <string name="archive_application_text_user" msgid="2586558895535581451">"Argiveer hierdie app vir <xliff:g id="USERNAME">%1$s</xliff:g>? Jou persoonlike data sal gestoor word"</string>
     <string name="archive_application_text_current_user_private_profile" msgid="1958423158655599132">"Wil jy hierdie app wat in jou privaat ruimte is, argiveer? Jou persoonlike data sal gestoor word"</string>
-    <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Wil jy hierdie program vir "<b>"alle"</b>" gebruikers deïnstalleer? Die program en sy data sal van "<b>"alle"</b>" gebruikers op hierdie toestel verwyder word."</string>
-    <string name="uninstall_application_text_user" msgid="498072714173920526">"Wil jy hierdie program vir die gebruiker <xliff:g id="USERNAME">%1$s</xliff:g> deïnstalleer?"</string>
+    <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Wil jy hierdie app vir "<b>"alle"</b>" gebruikers deïnstalleer? Die app en sy data sal van "<b>"alle"</b>" gebruikers op hierdie toestel verwyder word."</string>
+    <string name="uninstall_application_text_user" msgid="498072714173920526">"Wil jy hierdie app vir die gebruiker <xliff:g id="USERNAME">%1$s</xliff:g> deïnstalleer?"</string>
     <string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"Wil jy hierdie program op jou werkprofiel deïnstalleer?"</string>
-    <string name="uninstall_update_text" msgid="863648314632448705">"Vervang hierdie program met die fabriekweergawe? Alle data sal verwyder word."</string>
+    <string name="uninstall_update_text" msgid="863648314632448705">"Vervang hierdie app met die fabriekweergawe? Alle data sal verwyder word."</string>
     <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"Vervang hierdie program met die fabriekweergawe? Alle data sal verwyder word. Dit beïnvloed alle gebruikers van hierdie toestel, insluitend dié met werkprofiele."</string>
     <string name="uninstall_keep_data" msgid="7002379587465487550">"Hou <xliff:g id="SIZE">%1$s</xliff:g> se programdata."</string>
     <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Wil jy hierdie app uitvee?"</string>
@@ -85,28 +85,28 @@
     <string name="uninstalling_cloned_app" msgid="1826380164974984870">"Vee tans <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>-kloon uit …"</string>
     <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"Kan nie aktiewe toesteladministrasie-app deïnstalleer nie"</string>
     <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"Kan nie aktiewe toesteladministrasie-app vir <xliff:g id="USERNAME">%1$s</xliff:g> deïnstalleer nie"</string>
-    <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"Dié program word vir sommige gebruikers of profiele vereis en is vir ander gedeïnstalleer"</string>
-    <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"Hierdie program is nodig vir jou profiel en kan nie gedeïnstalleer word nie."</string>
-    <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"Jou toesteladministrateur vereis die program; kan nie gedeïnstalleer word nie."</string>
+    <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"Dié app word vir sommige gebruikers of profiele vereis en is vir ander gedeïnstalleer"</string>
+    <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"Hierdie app is nodig vir jou profiel en kan nie gedeïnstalleer word nie."</string>
+    <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"Jou toesteladministrateur vereis dié app; dit kan nie gedeïnstalleer word nie."</string>
     <string name="manage_device_administrators" msgid="3092696419363842816">"Bestuur toesteladministrasie-apps"</string>
     <string name="manage_users" msgid="1243995386982560813">"Bestuur gebruikers"</string>
     <string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g> kon nie gedeïnstalleer word nie."</string>
     <string name="Parse_error_dlg_text" msgid="1661404001063076789">"Kon nie die pakket ontleed nie."</string>
-    <string name="message_staging" msgid="8032722385658438567">"Voer tans program uit …"</string>
+    <string name="message_staging" msgid="8032722385658438567">"Voer tans app uit …"</string>
     <string name="app_name_unknown" msgid="6881210203354323926">"Onbekend"</string>
     <string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"Jou tablet word vir jou veiligheid tans nie toegelaat om onbekende programme van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"Jou TV word vir jou veiligheid tans nie toegelaat om onbekende programme van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
     <string name="untrusted_external_source_warning" product="watch" msgid="7195163388090818636">"Jou horlosie word vir jou veiligheid tans nie toegelaat om onbekende apps van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"Jou foon word vir jou veiligheid tans nie toegelaat om onbekende programme van hierdie bron af te installeer nie. Jy kan dit in Instellings verander."</string>
-    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Jou foon en persoonlike data is meer kwesbaar vir aanvalle deur onbekende programme. Deur hierdie program te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou foon of verlies van data wat uit sy gebruik kan spruit."</string>
-    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Jou tablet en persoonlike data is meer kwesbaar vir aanvalle deur onbekende programme. Deur hierdie program te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou tablet of verlies van data wat uit sy gebruik kan spruit."</string>
-    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Jou TV en persoonlike data is meer kwesbaar vir aanvalle deur onbekende programme. Deur hierdie program te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou TV of verlies van data wat uit sy gebruik kan spruit."</string>
+    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Jou foon en persoonlike data is meer kwesbaar vir aanvalle deur onbekende apps. Deur hierdie app te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou foon of verlies van data wat uit sy gebruik kan spruit."</string>
+    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Jou tablet en persoonlike data is meer kwesbaar vir aanvalle deur onbekende apps. Deur hierdie app te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou tablet of verlies van data wat uit sy gebruik kan spruit."</string>
+    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Jou TV en persoonlike data is meer kwesbaar vir aanvalle deur onbekende apps. Deur hierdie app te installeer, stem jy in dat jy verantwoordelik is vir enige skade aan jou TV of verlies van data wat uit sy gebruik kan spruit."</string>
     <string name="cloned_app_label" msgid="7503612829833756160">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>-kloon"</string>
     <string name="archiving_app_label" msgid="1127085259724124725">"Argiveer <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>?"</string>
     <string name="anonymous_source_continue" msgid="4375745439457209366">"Gaan voort"</string>
     <string name="external_sources_settings" msgid="4046964413071713807">"Instellings"</string>
     <string name="wear_app_channel" msgid="1960809674709107850">"Installeer/deïnstalleer Wear-programme"</string>
-    <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Kennisgewing dat program geïnstalleer is"</string>
+    <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Kennisgewing dat app geïnstalleer is"</string>
     <string name="notification_installation_success_message" msgid="6450467996056038442">"Suksesvol geïnstalleer"</string>
     <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” is suksesvol geïnstalleer"</string>
     <string name="unarchive_application_title" msgid="7958278328280721421">"Stel <xliff:g id="APPNAME">%1$s</xliff:g> terug vanaf <xliff:g id="INSTALLERNAME">%2$s</xliff:g>?"</string>
diff --git a/packages/PackageInstaller/res/values-it/strings.xml b/packages/PackageInstaller/res/values-it/strings.xml
index 1561e79..ec6d3fc 100644
--- a/packages/PackageInstaller/res/values-it/strings.xml
+++ b/packages/PackageInstaller/res/values-it/strings.xml
@@ -43,7 +43,7 @@
     <string name="unknown_apps_admin_dlg_text" msgid="4456572224020176095">"L\'amministratore non consente l\'installazione di app ottenute da origini sconosciute"</string>
     <string name="unknown_apps_user_restriction_dlg_text" msgid="151020786933988344">"Questo utente non può installare app sconosciute"</string>
     <string name="install_apps_user_restriction_dlg_text" msgid="2154119597001074022">"L\'utente non dispone dell\'autorizzazione a installare app"</string>
-    <string name="ok" msgid="7871959885003339302">"OK"</string>
+    <string name="ok" msgid="7871959885003339302">"Ok"</string>
     <string name="archive" msgid="4447791830199354721">"Archivia"</string>
     <string name="update_anyway" msgid="8792432341346261969">"Aggiorna comunque"</string>
     <string name="manage_applications" msgid="5400164782453975580">"Gestisci app"</string>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-af/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-af/strings.xml
index 38cae30..7209cc2 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-af/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-af/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Kies \'n prent"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Neem \'n foto"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Kies ’n profielprent"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Verstekgebruikerikoon"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Klaar"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-am/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-am/strings.xml
index 917a2fd..713e0d95 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-am/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-am/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"ምስል ይምረጡ"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"ፎቶ ያንሱ"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"የመገለጫ ሥዕል ይምረጡ"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"ነባሪ የተጠቃሚ አዶ"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"ተከናውኗል"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ar/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ar/strings.xml
index 8e5231b..1b5dbc0 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ar/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ar/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"اختيار صورة"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"التقاط صورة"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"اختيار صورة للملف الشخصي"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"رمز المستخدم التلقائي"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"تم"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-as/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-as/strings.xml
index c77b573..6281328 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-as/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-as/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"এখন প্ৰতিচ্ছবি বাছনি কৰক"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"এখন ফট’ তোলক"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"এখন প্ৰ’ফাইল চিত্ৰ বাছনি কৰক"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"ডিফ’ল্ট ব্যৱহাৰকাৰীৰ চিহ্ন"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"কৰা হ’ল"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-az/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-az/strings.xml
index 6871473..d47ffce 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-az/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-az/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Şəkil seçin"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Foto çəkin"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Profil şəkli seçin"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Defolt istifadəçi ikonası"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Hazırdır"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-b+sr+Latn/strings.xml
index 42fb478..886bd5d 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-b+sr+Latn/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Odaberite sliku"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Slikajte"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Odaberite sliku profila"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Podrazumevana ikona korisnika"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Gotovo"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-be/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-be/strings.xml
index c2ef4db..38a1340 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-be/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-be/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Выбраць відарыс"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Зрабіць фота"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Выберыце відарыс профілю"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Стандартны карыстальніцкі значок"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Гатова"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-bg/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-bg/strings.xml
index eeca7d8..90e78b5 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-bg/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-bg/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Избиране на изображение"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Правене на снимка"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Изберете снимка на потребителския профил"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Икона за основния потребител"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Готово"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-bn/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-bn/strings.xml
index 6d4d29f..c56c9da 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-bn/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-bn/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"একটি ছবি বেছে নিন"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"ফটো তুলুন"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"কোনও একটি প্রোফাইল ছবি বেছে নিন"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"ডিফল্ট ব্যবহারকারীর আইকন"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"হয়ে গেছে"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-bs/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-bs/strings.xml
index def1b45..1dd707d 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-bs/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-bs/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Odaberite sliku"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Snimite fotografiju"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Odaberite sliku profila"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Zadana ikona korisnika"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Gotovo"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ca/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ca/strings.xml
index 1b613ca..d8d3171 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ca/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ca/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Tria una imatge"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Fes una foto"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Tria una foto de perfil"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Icona d\'usuari predeterminat"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Fet"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-cs/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-cs/strings.xml
index ffe2f47..6ee5b3e 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-cs/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-cs/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Zvolit obrázek"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Vyfotit"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Vyberte profilový obrázek"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Výchozí uživatelská ikona"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Hotovo"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-da/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-da/strings.xml
index ab01eef..0583399 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-da/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-da/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Vælg et billede"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Tag et billede"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Vælg et profilbillede"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Ikon for standardbruger"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Udfør"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-de/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-de/strings.xml
index 4d6c651a..345b4fa 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-de/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-de/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Bild auswählen"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Foto aufnehmen"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Profilbild auswählen"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Standardmäßiges Nutzersymbol"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Fertig"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-el/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-el/strings.xml
index 9e6813f..7cecd45 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-el/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-el/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Επιλέξτε μια εικόνα"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Λήψη φωτογραφίας"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Επιλογή φωτογραφ­ίας προφίλ"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Προεπιλεγμένο εικονίδιο χρήστη"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Τέλος"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-en-rAU/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-en-rAU/strings.xml
index d0aae79..f9905d0 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-en-rAU/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Choose an image"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Take a photo"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Choose a profile picture"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Default user icon"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Done"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-en-rGB/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-en-rGB/strings.xml
index d0aae79..f9905d0 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-en-rGB/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Choose an image"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Take a photo"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Choose a profile picture"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Default user icon"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Done"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-en-rIN/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-en-rIN/strings.xml
index d0aae79..f9905d0 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-en-rIN/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Choose an image"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Take a photo"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Choose a profile picture"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Default user icon"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Done"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-es-rUS/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-es-rUS/strings.xml
index 296dcc8..8e9d407 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-es-rUS/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Elegir una imagen"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Tomar una foto"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Elige una foto de perfil"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Ícono de usuario predeterminado"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Listo"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-es/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-es/strings.xml
index 5d4a8d2..a7da134 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-es/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-es/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Seleccionar una imagen"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Hacer una foto"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Elige una imagen de perfil"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Icono de usuario predeterminado"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Hecho"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-et/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-et/strings.xml
index ecd3a83..ecf140d 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-et/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-et/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Vali pilt"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Pildista"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Profiilipildi valimine"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Vaikekasutajaikoon"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Valmis"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-eu/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-eu/strings.xml
index e29bc11..c55d559 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-eu/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-eu/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Aukeratu irudi bat"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Atera argazki bat"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Aukeratu profileko argazki bat"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Erabiltzaile lehenetsiaren ikonoa"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Eginda"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-fa/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-fa/strings.xml
index 25efac4..7db55cf 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-fa/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-fa/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"انتخاب تصویر"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"عکس گرفتن"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"انتخاب عکس نمایه"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"نماد کاربر پیش‌فرض"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"تمام"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-fi/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-fi/strings.xml
index 4d805ed..7a0cca1 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-fi/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-fi/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Valitse kuva"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Ota kuva"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Valitse profiilikuva"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Oletuskäyttäjäkuvake"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Valmis"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-fr-rCA/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-fr-rCA/strings.xml
index fb32c5b..e439471 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-fr-rCA/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Sélectionner une image"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Prendre une photo"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Choisir une photo de profil"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Icône d\'utilisateur par défaut"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Terminé"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-fr/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-fr/strings.xml
index 79620e9..2ffeb43 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-fr/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-fr/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Choisir une image"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Prendre une photo"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Choisir une photo de profil"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Icône de l\'utilisateur par défaut"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"OK"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-gl/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-gl/strings.xml
index bbfa18b..5e46474 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-gl/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-gl/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Escoller unha imaxe"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Tirar unha foto"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Escoller unha imaxe do perfil"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Icona do usuario predeterminado"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Feito"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-gu/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-gu/strings.xml
index 247afe1..96e6753 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-gu/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-gu/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"છબી પસંદ કરો"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"ફોટો લો"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"પ્રોફાઇલ ફોટો પસંદ કરો"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"ડિફૉલ્ટ વપરાશકર્તાનું આઇકન"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"થઈ ગયું"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-hi/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-hi/strings.xml
index 3c51576..0878ff2 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-hi/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-hi/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"इमेज चुनें"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"फ़ोटो खींचें"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"प्रोफ़ाइल फ़ोटो चुनें"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"उपयोगकर्ता के लिए डिफ़ॉल्ट आइकॉन"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"हो गया"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-hr/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-hr/strings.xml
index e9a56bd..b599ddf 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-hr/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-hr/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Odaberite sliku"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Snimi fotografiju"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Odaberite profilnu sliku"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Ikona zadanog korisnika"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Gotovo"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-hu/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-hu/strings.xml
index f3d53cd..55f4a60 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-hu/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-hu/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Kép kiválasztása"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Fotó készítése"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Profilkép választása"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Alapértelmezett felhasználó ikonja"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Kész"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-in/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-in/strings.xml
index bfa0d93..9b5cbf3 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-in/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-in/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Pilih gambar"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Ambil foto"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Pilih foto profil"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Ikon pengguna default"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Selesai"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-is/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-is/strings.xml
index d496524..4007c38 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-is/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-is/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Velja mynd"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Taka mynd"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Veldu prófílmynd"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Tákn sjálfgefins notanda"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Lokið"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-it/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-it/strings.xml
index 3ba0a01..caeb93b 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-it/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-it/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Scegli un\'immagine"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Scatta una foto"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Scegli un\'immagine del profilo"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Icona dell\'utente predefinito"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Fine"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-iw/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-iw/strings.xml
index 903989d..03ca68e 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-iw/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-iw/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"לבחירת תמונה"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"צילום תמונה"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"בחירה של תמונת הפרופיל"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"סמל המשתמש שמוגדר כברירת מחדל"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"סיום"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ka/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ka/strings.xml
index 817180b..ae61afc 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ka/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ka/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"სურათის არჩევა"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"ფოტოს გადაღება"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"პროფილის სურათის არჩევა"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"მომხმარებლის ნაგულისხმევი ხატულა"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"მზადაა"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-kk/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-kk/strings.xml
index ec01801..f1e8950 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-kk/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-kk/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Кескін таңдау"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Суретке түсіру"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Профиль суретін таңдау"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Әдепкі пайдаланушы белгішесі"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Дайын"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-km/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-km/strings.xml
index 7a31122..a86491e 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-km/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-km/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"ជ្រើសរើស​រូបភាព"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"ថតរូប"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"ជ្រើសរើសរូបភាពកម្រងព័ត៌មាន"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"រូបអ្នកប្រើប្រាស់លំនាំដើម"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"រួចរាល់"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ko/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ko/strings.xml
index 45bfb53..682ebfd 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ko/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ko/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"이미지 선택"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"사진 찍기"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"프로필 사진 선택"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"기본 사용자 아이콘"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"완료"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ky/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ky/strings.xml
index b3d39de..3d8e7bf 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ky/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ky/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Сүрөт тандоо"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Сүрөткө тартуу"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Профилдин сүрөтүн тандоо"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Демейки колдонуучунун сүрөтчөсү"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Бүттү"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-lo/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-lo/strings.xml
index 3ba6709..64bd871 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-lo/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-lo/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"ເລືອກຮູບ"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"ຖ່າຍຮູບ"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"ເລືອກຮູບໂປຣໄຟລ໌"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"ໄອຄອນຜູ້ໃຊ້ເລີ່ມຕົ້ນ"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"ແລ້ວໆ"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-lt/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-lt/strings.xml
index b0c0c39..4bc183b 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-lt/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-lt/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Pasirinkti vaizdą"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Fotografuoti"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Profilio nuotraukos pasirinkimas"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Numatytojo naudotojo piktograma"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Atlikta"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-lv/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-lv/strings.xml
index e664c80..72e6ec4 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-lv/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-lv/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Izvēlēties attēlu"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Uzņemt fotoattēlu"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Profila attēla izvēle"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Noklusējuma lietotāja ikona"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Gatavs"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-mk/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-mk/strings.xml
index bb0e247..4b6939e 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-mk/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-mk/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Изберете слика"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Фотографирај"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Изберете профилна слика"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Икона за стандарден корисник"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Готово"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ml/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ml/strings.xml
index 674b4f5..aec39b1 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ml/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ml/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"ഒരു ചിത്രം തിരഞ്ഞെടുക്കുക"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"ഒരു ഫോട്ടോ എടുക്കുക"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"പ്രൊഫൈൽ ചിത്രം തിരഞ്ഞെടുക്കുക"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"ഡിഫോൾട്ട് ഉപയോക്തൃ ഐക്കൺ"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"പൂർത്തിയായി"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-mn/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-mn/strings.xml
index 61553b5..4d13e87 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-mn/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-mn/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Зураг сонгох"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Зураг авах"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Профайл зураг сонгох"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Өгөгдмөл хэрэглэгчийн дүрс тэмдэг"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Болсон"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ms/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ms/strings.xml
index 2599696..45f1b36 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ms/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ms/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Pilih imej"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Ambil foto"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Pilih gambar profil"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Ikon pengguna lalai"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Selesai"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-my/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-my/strings.xml
index f9f697b..6973084 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-my/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-my/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"ပုံရွေးရန်"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"ဓာတ်ပုံရိုက်ရန်"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"ပရိုဖိုင်ပုံ ရွေးပါ"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"မူရင်းအသုံးပြုသူ သင်္ကေတ"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"ပြီးပြီ"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-nb/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-nb/strings.xml
index c1b7631..8a55006 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-nb/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-nb/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Velg et bilde"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Ta et bilde"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Velg et profilbilde"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Standard brukerikon"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Ferdig"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ne/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ne/strings.xml
index a8c02b9..8373058 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ne/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ne/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"कुनै फोटो छनौट गर्नुहोस्"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"फोटो खिच्नुहोस्"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"प्रोफाइल फोटो छान्नुहोस्"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"प्रयोगकर्ताको डिफल्ट आइकन"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"सम्पन्न भयो"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-nl/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-nl/strings.xml
index 47352bc..cb3bd51 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-nl/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-nl/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Een afbeelding kiezen"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Een foto maken"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Kies een profielfoto"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Standaard gebruikersicoon"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Klaar"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-or/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-or/strings.xml
index 132b97a..dd54ac3 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-or/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-or/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"ଗୋଟିଏ ଛବି ବାଛନ୍ତୁ"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"ଗୋଟିଏ ଫଟୋ ଉଠାନ୍ତୁ"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"ଏକ ପ୍ରୋଫାଇଲ ଛବି ବାଛନ୍ତୁ"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"ଡିଫଲ୍ଟ ୟୁଜର ଆଇକନ"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"ହୋଇଗଲା"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-pl/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-pl/strings.xml
index 7db7904..35787c9 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-pl/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-pl/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Wybierz obraz"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Zrób zdjęcie"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Wybierz zdjęcie profilowe"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Ikona domyślnego użytkownika"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Gotowe"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-pt-rBR/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-pt-rBR/strings.xml
index ae3e6e5..ba1ce2a 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-pt-rBR/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Escolher uma imagem"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Tirar uma foto"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Escolha uma foto de perfil"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Ícone de usuário padrão"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Concluir"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-pt/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-pt/strings.xml
index ae3e6e5..ba1ce2a 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-pt/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-pt/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Escolher uma imagem"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Tirar uma foto"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Escolha uma foto de perfil"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Ícone de usuário padrão"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Concluir"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ro/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ro/strings.xml
index ce662d4..51bcd57 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ro/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ro/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Alege o imagine"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Fă o fotografie"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Alege o fotografie de profil"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Pictograma prestabilită a utilizatorului"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Gata"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-ru/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-ru/strings.xml
index 47f8a83..59fb913 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-ru/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-ru/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Выбрать фото"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Сделать снимок"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Выберите фото профиля"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Значок пользователя по умолчанию"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Готово"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-si/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-si/strings.xml
index aaba442..db7e0b8 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-si/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-si/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"රූපයක් තෝරන්න"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"ඡායාරූපයක් ගන්න"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"පැතිකඩ පින්තූරයක් තේරීම"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"පෙරනිමි පරිශීලක නිරූපකය"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"නිමයි"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-sk/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-sk/strings.xml
index 3f801a3..36e94f3 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-sk/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-sk/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Vybrať obrázok"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Odfotiť"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Vyberte profilovú fotku"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Predvolená ikona používateľa"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Hotovo"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-sq/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-sq/strings.xml
index 0b8a58d..0093380 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-sq/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-sq/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Zgjidh një imazh"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Bëj një fotografi"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Zgjidh një fotografi profili"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Ikona e parazgjedhur e përdoruesit"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"U krye"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-sr/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-sr/strings.xml
index 1014df4..971d8c5 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-sr/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-sr/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Одаберите слику"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Сликајте"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Одаберите слику профила"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Подразумевана икона корисника"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Готово"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-sv/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-sv/strings.xml
index 8203409..9196e2a 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-sv/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-sv/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Välj en bild"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Ta ett foto"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Välj en profilbild"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Ikon för standardanvändare"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Klar"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-sw/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-sw/strings.xml
index b0e8c44..a0bc554 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-sw/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-sw/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Chagua picha"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Piga picha"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Chagua picha ya wasifu"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Aikoni chaguomsingi ya mtumiaji"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Nimemaliza"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-th/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-th/strings.xml
index 31e753e..b8b4324 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-th/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-th/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"เลือกรูปภาพ"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"ถ่ายรูป"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"เลือกรูปโปรไฟล์"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"ไอคอนผู้ใช้เริ่มต้น"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"เสร็จสิ้น"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-tr/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-tr/strings.xml
index 26d96c2..3e10257 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-tr/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-tr/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Resim seç"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Fotoğraf çek"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Profil fotoğrafı seçin"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Varsayılan kullanıcı simgesi"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Bitti"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-uk/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-uk/strings.xml
index 1fbe5fb..ee4a2fc 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-uk/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-uk/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Вибрати зображення"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Зробити фото"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Виберіть зображення профілю"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Значок користувача за умовчанням"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Готово"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-vi/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-vi/strings.xml
index 036c006..c6b4ef5 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-vi/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-vi/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Chọn hình ảnh"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Chụp ảnh"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Chọn một ảnh hồ sơ"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Biểu tượng người dùng mặc định"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Xong"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-zh-rCN/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-zh-rCN/strings.xml
index 58488b3..684449f 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-zh-rCN/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"选择图片"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"拍照"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"选择个人资料照片"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"默认用户图标"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"完成"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-zh-rHK/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-zh-rHK/strings.xml
index e5f3752..e0d4052 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-zh-rHK/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"選擇圖片"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"拍攝相片"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"選擇個人檔案相片"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"預設使用者圖示"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"完成"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-zh-rTW/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-zh-rTW/strings.xml
index 4a58180b..d82ad7e 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-zh-rTW/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"選擇圖片"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"拍照"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"選擇個人資料相片"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"預設使用者圖示"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"完成"</string>
 </resources>
diff --git a/packages/SettingsLib/AvatarPicker/res/values-zu/strings.xml b/packages/SettingsLib/AvatarPicker/res/values-zu/strings.xml
index ad15f89..8678332 100644
--- a/packages/SettingsLib/AvatarPicker/res/values-zu/strings.xml
+++ b/packages/SettingsLib/AvatarPicker/res/values-zu/strings.xml
@@ -19,9 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="user_image_choose_photo" msgid="5630717762469961028">"Khetha isithombe"</string>
     <string name="user_image_take_photo" msgid="3147097821937166738">"Thatha isithombe"</string>
-    <!-- no translation found for avatar_picker_title (7478146965334560463) -->
-    <skip />
+    <string name="avatar_picker_title" msgid="7478146965334560463">"Khetha isithombe sephrofayela"</string>
     <string name="default_user_icon_description" msgid="6018582161341388812">"Isithonjana somsebenzisi sokuzenzakalelayo"</string>
-    <!-- no translation found for done (3587741621903511576) -->
-    <skip />
+    <string name="done" msgid="3587741621903511576">"Kwenziwe"</string>
 </resources>
diff --git a/packages/SettingsLib/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto
index 6b93cd7..f611793 100644
--- a/packages/SettingsLib/Graph/graph.proto
+++ b/packages/SettingsLib/Graph/graph.proto
@@ -79,6 +79,8 @@
   optional IntentProto launch_intent = 14;
   // Descriptor of the preference value.
   optional PreferenceValueDescriptorProto value_descriptor = 15;
+  // Indicate how sensitive of the preference.
+  optional int32 sensitivity_level = 16;
 
   // Target of an Intent
   message ActionTarget {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index a65d24c..a768b5e 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -55,9 +55,9 @@
 import com.android.settingslib.metadata.ReadWritePermit
 import com.android.settingslib.preference.PreferenceScreenFactory
 import com.android.settingslib.preference.PreferenceScreenProvider
-import java.util.Locale
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
+import java.util.Locale
 
 private const val TAG = "PreferenceGraphBuilder"
 
@@ -387,6 +387,7 @@
     }
     persistent = metadata.isPersistent(context)
     if (persistent) {
+        if (metadata is PersistentPreference<*>) sensitivityLevel = metadata.sensitivityLevel
         if (
             flags.includeValue() &&
                 enabled &&
diff --git a/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt b/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
index 0cdfc66..f9931cf 100644
--- a/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
+++ b/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
@@ -31,7 +31,7 @@
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
     defStyleRes: Int = 0
-) : Preference(context, attrs, defStyleAttr, defStyleRes) {
+) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
 
     private var isCollapsable: Boolean = false
     private var minLines: Int = 2
@@ -68,6 +68,7 @@
         (holder.findViewById(R.id.collapsable_summary) as? CollapsableTextView)?.apply {
             setCollapsable(isCollapsable)
             setMinLines(minLines)
+            visibility = if (summary.isNullOrEmpty()) View.GONE else View.VISIBLE
             setText(summary.toString())
             if (hyperlinkListener != null) {
                 setHyperlinkListener(hyperlinkListener)
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
index 6c11e69..668f981 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
@@ -42,6 +42,19 @@
     }
 }
 
+/** Indicates how sensitive of the data. */
+@Retention(AnnotationRetention.SOURCE)
+@Target(AnnotationTarget.TYPE)
+annotation class SensitivityLevel {
+    companion object {
+        const val UNKNOWN_SENSITIVITY = 0
+        const val NO_SENSITIVITY = 1
+        const val LOW_SENSITIVITY = 2
+        const val MEDIUM_SENSITIVITY = 3
+        const val HIGH_SENSITIVITY = 4
+    }
+}
+
 /** Preference interface that has a value persisted in datastore. */
 interface PersistentPreference<T> {
 
@@ -86,6 +99,10 @@
             callingUid,
             this as PreferenceMetadata,
         )
+
+    /** The sensitivity level of the preference. */
+    val sensitivityLevel: @SensitivityLevel Int
+        get() = SensitivityLevel.UNKNOWN_SENSITIVITY
 }
 
 /** Descriptor of values. */
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
index 386b6d9..6e86fa7 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
@@ -175,7 +175,12 @@
 
 /** Metadata of preference group. */
 @AnyThread
-open class PreferenceGroup(override val key: String, override val title: Int) : PreferenceMetadata
+interface PreferenceGroup : PreferenceMetadata
+
+/** Metadata of preference category. */
+@AnyThread
+open class PreferenceCategory(override val key: String, override val title: Int) :
+    PreferenceGroup
 
 /** Metadata of preference screen. */
 @AnyThread
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt
index a3709c1..6704ecc 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceStateProviders.kt
@@ -20,6 +20,8 @@
 import android.content.ContextWrapper
 import android.content.Intent
 import android.os.Bundle
+import androidx.lifecycle.LifecycleCoroutineScope
+import kotlinx.coroutines.CoroutineScope
 
 /**
  * Interface to provide dynamic preference title.
@@ -123,7 +125,12 @@
      *
      * @return true if the result is handled
      */
-    fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean = false
+    fun onActivityResult(
+        context: PreferenceLifecycleContext,
+        requestCode: Int,
+        resultCode: Int,
+        data: Intent?,
+    ): Boolean = false
 }
 
 /**
@@ -133,9 +140,23 @@
  */
 abstract class PreferenceLifecycleContext(context: Context) : ContextWrapper(context) {
 
+    /**
+     * [CoroutineScope] tied to the lifecycle, which is cancelled when the lifecycle is destroyed.
+     *
+     * @see [androidx.lifecycle.lifecycleScope]
+     */
+    abstract val lifecycleScope: LifecycleCoroutineScope
+
     /** Returns the preference widget object associated with given key. */
     abstract fun <T> findPreference(key: String): T?
 
+    /**
+     * Returns the preference widget object associated with given key.
+     *
+     * @throws NullPointerException if preference is not found
+     */
+    abstract fun <T : Any> requirePreference(key: String): T
+
     /** Notifies that preference state of given key is changed and updates preference widget UI. */
     abstract fun notifyPreferenceChange(key: String)
 
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
index 51b9aac..6287fda 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
@@ -18,7 +18,7 @@
 
 import androidx.preference.Preference
 import com.android.settingslib.metadata.MainSwitchPreference
-import com.android.settingslib.metadata.PreferenceGroup
+import com.android.settingslib.metadata.PreferenceCategory
 import com.android.settingslib.metadata.PreferenceHierarchyNode
 import com.android.settingslib.metadata.PreferenceMetadata
 import com.android.settingslib.metadata.SwitchPreference
@@ -59,7 +59,7 @@
         metadata as? PreferenceBinding
             ?: when (metadata) {
                 is SwitchPreference -> SwitchPreferenceBinding.INSTANCE
-                is PreferenceGroup -> PreferenceGroupBinding.INSTANCE
+                is PreferenceCategory -> PreferenceCategoryBinding.INSTANCE
                 is PreferenceScreenCreator -> PreferenceScreenBinding.INSTANCE
                 is MainSwitchPreference -> MainSwitchPreferenceBinding.INSTANCE
                 else -> DefaultPreferenceBinding
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
index 762f5ea..bd5d17c 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
@@ -55,13 +55,13 @@
     }
 }
 
-/** Binding of preference group associated with [PreferenceCategory]. */
-interface PreferenceGroupBinding : PreferenceBinding {
+/** Binding of preference category associated with [PreferenceCategory]. */
+interface PreferenceCategoryBinding : PreferenceBinding {
 
     override fun createWidget(context: Context) = PreferenceCategory(context)
 
     companion object {
-        @JvmStatic val INSTANCE = object : PreferenceGroupBinding {}
+        @JvmStatic val INSTANCE = object : PreferenceCategoryBinding {}
     }
 }
 
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreAdapter.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreAdapter.kt
index c2728b4..7601b9a 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreAdapter.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreAdapter.kt
@@ -20,7 +20,7 @@
 import com.android.settingslib.datastore.KeyValueStore
 
 /** Adapter to translate [KeyValueStore] into [PreferenceDataStore]. */
-class PreferenceDataStoreAdapter(private val keyValueStore: KeyValueStore) : PreferenceDataStore() {
+class PreferenceDataStoreAdapter(val keyValueStore: KeyValueStore) : PreferenceDataStore() {
 
     override fun getBoolean(key: String, defValue: Boolean): Boolean =
         keyValueStore.getValue(key, Boolean::class.javaObjectType) ?: defValue
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index 62ac3ad..6fc9357 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -19,6 +19,8 @@
 import android.content.Context
 import android.content.Intent
 import android.os.Bundle
+import androidx.lifecycle.LifecycleCoroutineScope
+import androidx.lifecycle.lifecycleScope
 import androidx.preference.Preference
 import androidx.preference.PreferenceDataStore
 import androidx.preference.PreferenceGroup
@@ -57,9 +59,14 @@
 
     private val preferenceLifecycleContext =
         object : PreferenceLifecycleContext(context) {
+            override val lifecycleScope: LifecycleCoroutineScope
+                get() = fragment.lifecycleScope
+
             override fun <T> findPreference(key: String) =
                 preferenceScreen.findPreference(key) as T?
 
+            override fun <T : Any> requirePreference(key: String) = findPreference<T>(key)!!
+
             override fun notifyPreferenceChange(key: String) =
                 notifyChange(key, CHANGE_REASON_STATE)
 
@@ -74,16 +81,12 @@
     private val preferences: ImmutableMap<String, PreferenceHierarchyNode>
     private val dependencies: ImmutableMultimap<String, String>
     private val lifecycleAwarePreferences: Array<PreferenceLifecycleProvider>
-    private val storages = mutableSetOf<KeyedObservable<String>>()
+    private val storages = mutableMapOf<String, KeyedObservable<String>>()
 
     private val preferenceObserver: KeyedObserver<String?>
 
     private val storageObserver =
-        KeyedObserver<String?> { key, _ ->
-            if (key != null) {
-                notifyChange(key, CHANGE_REASON_VALUE)
-            }
-        }
+        KeyedObserver<String> { key, _ -> notifyChange(key, CHANGE_REASON_VALUE) }
 
     init {
         val preferencesBuilder = ImmutableMap.builder<String, PreferenceHierarchyNode>()
@@ -98,7 +101,6 @@
                 preferencesBuilder.put(it.key, this)
                 it.dependencyOfEnabledState(context)?.addDependency(it)
                 if (it is PreferenceLifecycleProvider) lifecycleAwarePreferences.add(it)
-                if (it is PersistentPreference<*>) storages.add(it.storage(context))
             }
         }
 
@@ -120,7 +122,16 @@
 
         preferenceObserver = KeyedObserver { key, reason -> onPreferenceChange(key, reason) }
         addObserver(preferenceObserver, mainExecutor)
-        for (storage in storages) storage.addObserver(storageObserver, mainExecutor)
+
+        preferenceScreen.forEachRecursively {
+            val preferenceDataStore = it.preferenceDataStore
+            if (preferenceDataStore is PreferenceDataStoreAdapter) {
+                val key = it.key
+                val keyValueStore = preferenceDataStore.keyValueStore
+                storages[key] = keyValueStore
+                keyValueStore.addObserver(key, storageObserver, mainExecutor)
+            }
+        }
     }
 
     private fun onPreferenceChange(key: String?, reason: Int) {
@@ -181,15 +192,15 @@
 
     fun onDestroy() {
         removeObserver(preferenceObserver)
-        for (storage in storages) storage.removeObserver(storageObserver)
+        for ((key, storage) in storages) storage.removeObserver(key, storageObserver)
         for (preference in lifecycleAwarePreferences) {
             preference.onDestroy(preferenceLifecycleContext)
         }
     }
 
     fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-        for (preference in lifecycleAwarePreferences) {
-            if (preference.onActivityResult(requestCode, resultCode, data)) break
+        lifecycleAwarePreferences.firstOrNull {
+            it.onActivityResult(preferenceLifecycleContext, requestCode, resultCode, data)
         }
     }
 
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt
new file mode 100644
index 0000000..2e7221b
--- /dev/null
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.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.settingslib.preference
+
+import androidx.preference.Preference
+import androidx.preference.PreferenceGroup
+
+/** Traversals preference hierarchy recursively and applies an action. */
+fun PreferenceGroup.forEachRecursively(action: (Preference) -> Unit) {
+    action.invoke(this)
+    for (index in 0 until preferenceCount) {
+        val preference = getPreference(index)
+        if (preference is PreferenceGroup) {
+            preference.forEachRecursively(action)
+        } else {
+            action.invoke(preference)
+        }
+    }
+}
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-af/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-af/strings.xml
index 9d3092d..4bd1d45 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-af/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-af/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Geaktiveer deur administrateur"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Gedeaktiveer deur administrateur"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-am/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-am/strings.xml
index 9617aca..ef380fd 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-am/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-am/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"በአስተዳዳሪ ነቅቷል"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"በአስተዳዳሪ ተሰናክሏል"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ar/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ar/strings.xml
index 581b914..57cf30f 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ar/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ar/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"يفعِّل المشرف هذا الإعداد."</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"أوقف المشرف هذا الإعداد"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-as/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-as/strings.xml
index 5824abd..21fe73a 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-as/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-as/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"প্ৰশাসকে সক্ষম কৰিছে"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"প্ৰশাসকে অক্ষম কৰিছে"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-az/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-az/strings.xml
index f07e054..c7c4c38 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-az/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-az/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Admin tərəfindən aktiv edildi"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Admin tərəfindən deaktiv edildi"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-b+sr+Latn/strings.xml
index e09afbf..a7d1395 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-b+sr+Latn/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Administrator je omogućio"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Administrator je onemogućio"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-be/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-be/strings.xml
index a64734b..60b9fc6 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-be/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-be/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Уключана адміністратарам"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Адключана адміністратарам"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-bg/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-bg/strings.xml
index ccaa656..f98a8d4 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-bg/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-bg/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Активирано от администратора"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Деактивирано от администратора"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-bn/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-bn/strings.xml
index 0a48aa2..87e8bd2 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-bn/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-bn/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"অ্যাডমিন চালু করেছেন"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"অ্যাডমিন বন্ধ করেছেন"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-bs/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-bs/strings.xml
index eebcebf..a56917f 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-bs/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-bs/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Omogućio administrator"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Onemogućio administrator"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ca/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ca/strings.xml
index 51e3fa9..ac38bd1 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ca/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ca/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Activat per l\'administrador"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Desactivat per l\'administrador"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-cs/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-cs/strings.xml
index a5db609..1840ecd 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-cs/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-cs/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Zapnuto administrátorem"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Vypnuto administrátorem"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-da/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-da/strings.xml
index 7f10edf..2e6dd0f 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-da/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-da/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Aktiveret af administratoren"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Deaktiveret af administrator"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-de/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-de/strings.xml
index 604593b..88c146c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-de/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-de/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Vom Administrator aktiviert"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Vom Administrator deaktiviert"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-el/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-el/strings.xml
index 79b4016..941d4ba 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-el/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-el/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Ενεργοποιήθηκε από τον διαχειριστή"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Απενεργοποιήθηκε από τον διαχειριστή"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rAU/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rAU/strings.xml
index 14b9272..91268ec 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rAU/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Enabled by admin"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Disabled by admin"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rCA/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rCA/strings.xml
index 14b9272..91268ec 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rCA/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Enabled by admin"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Disabled by admin"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rGB/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rGB/strings.xml
index 14b9272..91268ec 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rGB/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Enabled by admin"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Disabled by admin"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rIN/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rIN/strings.xml
index 14b9272..91268ec 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-en-rIN/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Enabled by admin"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Disabled by admin"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-es-rUS/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-es-rUS/strings.xml
index 616b568..9ea88df 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-es-rUS/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"El administrador habilitó la opción"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"El administrador inhabilitó la opción"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-es/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-es/strings.xml
index 351f16c..35777a2 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-es/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-es/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Habilitado por el administrador"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Inhabilitado por el administrador"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-et/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-et/strings.xml
index c59d645..806d4b8 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-et/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-et/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Administraatori lubatud"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Administraatori keelatud"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-eu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-eu/strings.xml
index 2a88124..4fe9b23 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-eu/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-eu/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Administratzaileak gaitu egin du"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Administratzaileak desgaitu du"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-fa/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-fa/strings.xml
index 9c39f98..774cb9f 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-fa/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-fa/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"توسط سرپرست فعال شده"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"توسط سرپرست غیرفعال شده"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-fi/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-fi/strings.xml
index 41fef5a..c15882b 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-fi/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-fi/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Järjestelmänvalvojan sallima"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Järjestelmänvalvojan estämä"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-fr-rCA/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-fr-rCA/strings.xml
index 9ff1174..188e9cb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-fr-rCA/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Activé par l\'administrateur"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Désactivé par l\'administrateur"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-fr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-fr/strings.xml
index 9ff1174..188e9cb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-fr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-fr/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Activé par l\'administrateur"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Désactivé par l\'administrateur"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-gl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-gl/strings.xml
index dbf8f7d..de603d0 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-gl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-gl/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Opción activada polo administrador"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Opción desactivada polo administrador"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
index 4fc4ab4..3ea8c49 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"વ્યવસ્થાપકે ચાલુ કરેલ"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"ઍડમિને બંધ કરેલું"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-hi/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-hi/strings.xml
index b5534b9..93ac326 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-hi/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-hi/strings.xml
@@ -18,5 +18,9 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"एडमिन की ओर से चालू किया गया"</string>
-    <string name="disabled_by_admin" msgid="4023569940620832713">"एडमिन ने यह सुविधा बंद की है"</string>
+    <string name="disabled_by_admin" msgid="4023569940620832713">"एडमिन ने यह सुविधा बंद की हुई है"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-hr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-hr/strings.xml
index eebcebf..a56917f 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-hr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-hr/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Omogućio administrator"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Onemogućio administrator"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-hu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-hu/strings.xml
index 2ab8142..897729d 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-hu/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-hu/strings.xml
@@ -18,5 +18,9 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"A rendszergazda bekapcsolta"</string>
-    <string name="disabled_by_admin" msgid="4023569940620832713">"A rendszergazda kikapcsolta"</string>
+    <string name="disabled_by_admin" msgid="4023569940620832713">"A rendszergazda letiltotta"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-hy/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-hy/strings.xml
index 23a2a6b..7c8e349 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-hy/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-hy/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Միացված է ադմինիստրատորի կողմից"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Անջատվել է ադմինիստրատորի կողմից"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-in/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-in/strings.xml
index ae8ec82..0b08fdb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-in/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-in/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Diaktifkan oleh admin"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Dinonaktifkan oleh admin"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-is/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-is/strings.xml
index 55380b3..6a041c7 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-is/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-is/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Gert virkt af kerfisstjóra"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Gert óvirkt af kerfisstjóra"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
index bddf43c..ee78ae8f 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Attivata dall\'amministratore"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Opzione disattivata dall\'amministratore"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-iw/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-iw/strings.xml
index 007de06..c243318 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-iw/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-iw/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"מופעל על ידי מנהל המכשיר"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"האפשרות הושבתה על ידי האדמין"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ja/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ja/strings.xml
index 490efd0..e4aa5c9 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ja/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ja/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"管理者によって有効にされています"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"管理者により無効にされています"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ka/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ka/strings.xml
index 5c394b8..86248f6 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ka/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ka/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"ჩართულია ადმინისტრატორის მიერ"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"გათიშულია ადმინისტრატორის მიერ"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-kk/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-kk/strings.xml
index eff7e44..b945c07 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-kk/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-kk/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Әкімші қосқан"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Әкімші өшірген"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-km/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-km/strings.xml
index 5a4f074..9deadfd 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-km/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-km/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"បើកដោយ​អ្នកគ្រប់គ្រង"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"បានបិទដោយអ្នកគ្រប់គ្រង"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-kn/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-kn/strings.xml
index 9b7a0d8..2af7fe2 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-kn/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-kn/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"ನಿರ್ವಾಹಕರು ಸಕ್ರಿಯಗೊಳಿಸಿದ್ದಾರೆ"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"ನಿರ್ವಾಹಕರು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿದ್ದಾರೆ"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ko/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ko/strings.xml
index d4f134c..62b781f 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ko/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ko/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"관리자가 사용 설정함"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"관리자가 사용 중지함"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ky/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ky/strings.xml
index a934b51..8437e9e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ky/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ky/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Администратор иштетип койгон"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Администратор өчүрүп койгон"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-lo/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-lo/strings.xml
index c2d80f2..be21bf4 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-lo/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-lo/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"ເປີດນຳໃຊ້ໂດຍຜູ້ເບິ່ງແຍງລະບົບ"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"ຖືກຜູ້ເບິ່ງແຍງລະບົບປິດໄວ້"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-lt/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-lt/strings.xml
index 2e96a0a..b290a54 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-lt/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-lt/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Įgalino administratorius"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Išjungė administratorius"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-lv/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-lv/strings.xml
index 1d2bcb0..5f86f98 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-lv/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-lv/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Iespējoja administrators"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Atspējoja administrators"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-mk/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-mk/strings.xml
index 1c8f1d1..e3f0e75 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-mk/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-mk/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Овозможено од администраторот"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Оневозможено од администраторот"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ml/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ml/strings.xml
index c4ee224..fe1a4ae 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ml/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ml/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"അഡ്‌മിൻ പ്രവർത്തനക്ഷമമാക്കി"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"അഡ്‌മിൻ പ്രവർത്തനരഹിതമാക്കി"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-mn/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-mn/strings.xml
index 472c50a..c3799c3 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-mn/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-mn/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Админ идэвхжүүлсэн"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Админ цуцалсан"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-mr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-mr/strings.xml
index d01bfc4..4e4660b 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-mr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-mr/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"अ‍ॅडमिनने सुरू केलेले"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"अ‍ॅडमिनने बंद केलेले"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ms/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ms/strings.xml
index 618ea8c..4bfcf2e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ms/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ms/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Didayakan oleh pentadbir"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Dilumpuhkan oleh pentadbir"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-my/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-my/strings.xml
index e462600..ef9ab1a9 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-my/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-my/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"စီမံခန့်ခွဲသူက ဖွင့်ထားသည်"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"စီမံခန့်ခွဲသူက ပိတ်ထားသည်"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-nb/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-nb/strings.xml
index 509e70b..423d7ce 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-nb/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-nb/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Aktivert av administratoren"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Deaktivert av administratoren"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ne/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ne/strings.xml
index 15bb85c..8793a81 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ne/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ne/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"प्रशासकद्वारा सक्षम पारिएको"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"एडमिनले अफ गरेको"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-nl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-nl/strings.xml
index a73deaf..60bb021 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-nl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-nl/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Aangezet door beheerder"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Uitgezet door beheerder"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml
index 4ce6460..92004fb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-or/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"ଆଡମିନଙ୍କ ଦ୍ୱାରା ସକ୍ଷମ କରାଯାଇଛି"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"ଆଡମିନଙ୍କ ଦ୍ଵାରା ଅକ୍ଷମ କରାଯାଇଛି"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pa/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pa/strings.xml
index 1a3a133..144dd4a 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pa/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pa/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pl/strings.xml
index 0523e2b..13814e1 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pl/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Włączone przez administratora"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Wyłączone przez administratora"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rBR/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rBR/strings.xml
index 908e2fb..8e97ffc 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rBR/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Ativado pelo administrador"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Desativado pelo administrador"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml
index 908e2fb..8e97ffc 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Ativado pelo administrador"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Desativado pelo administrador"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pt/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pt/strings.xml
index 908e2fb..8e97ffc 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pt/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pt/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Ativado pelo administrador"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Desativado pelo administrador"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ro/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ro/strings.xml
index ad41605..6120ef6 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ro/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ro/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Activat de administrator"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Dezactivat de administrator"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ru/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ru/strings.xml
index 5900644..f06aa06 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ru/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ru/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Включено администратором"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Отключено администратором"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-si/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-si/strings.xml
index de89710..c9a9e23 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-si/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-si/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"පරිපාලක විසින් සබල කර ඇත"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"ඔබගේ පරිපාලක විසින් අබල කර ඇත"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sk/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sk/strings.xml
index b8bb919..f6c1b66 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sk/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sk/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Povolené správcom"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Zakázané správcom"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sl/strings.xml
index 1d8ee57..029cdef 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sl/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Omogočil skrbnik"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Onemogočil skrbnik"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sq/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sq/strings.xml
index 4ee40bf..341d6bb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sq/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sq/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Aktivizuar nga administratori"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Çaktivizuar nga administratori"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sr/strings.xml
index 9d006a7..a783a02 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sr/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Администратор је омогућио"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Администратор је онемогућио"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sv/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sv/strings.xml
index faea070..c930d47 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sv/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sv/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Aktiverad av administratör"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Inaktiverad av administratören"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-sw/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-sw/strings.xml
index 59f511a..d61c8fb 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-sw/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-sw/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Imewashwa na msimamizi"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Imezimwa na msimamizi"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ta/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ta/strings.xml
index 3ef5f77..b39f76f 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ta/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ta/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"நிர்வாகி இயக்கியுள்ளார்"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"நிர்வாகி முடக்கியுள்ளார்"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-te/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-te/strings.xml
index 8f17dc5..4b8cd74e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-te/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-te/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"అడ్మిన్ ఎనేబుల్ చేశారు"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"అడ్మిన్ డిజేబుల్ చేశారు"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-th/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-th/strings.xml
index 80fd0c0..414d3c1 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-th/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-th/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"เปิดใช้โดยผู้ดูแลระบบ"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"ปิดใช้โดยผู้ดูแลระบบ"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-tl/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-tl/strings.xml
index a4a538d..34a057d 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-tl/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-tl/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Na-enable ng admin"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Na-disable ng admin"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-tr/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-tr/strings.xml
index ac5ed6a..f8d1f55 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-tr/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-tr/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Yönetici tarafından etkinleştirildi"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Yönetici tarafından devre dışı bırakıldı"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-uk/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-uk/strings.xml
index 32f02a4..2fa4634 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-uk/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-uk/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Увімкнено адміністратором"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Вимкнено адміністратором"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-ur/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-ur/strings.xml
index f3752d9..9401309 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-ur/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-ur/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"منتظم کی طرف سے فعال کردہ"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"منتظم کی طرف سے غیر فعال کردہ"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-uz/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-uz/strings.xml
index e2e9f42..a726e6c 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-uz/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-uz/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Administrator tomonidan yoqilgan"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Administrator tomonidan faolsizlantirilgan"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-vi/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-vi/strings.xml
index dd654b2..63bb27a 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-vi/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-vi/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Do quản trị viên bật"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Quản trị viên đã vô hiệu hóa chế độ này"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rCN/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rCN/strings.xml
index 8fa969e..5698cf3 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rCN/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"已被管理员启用"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"已被管理员停用"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rHK/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rHK/strings.xml
index 501f860..f37ccd8 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rHK/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"已由管理員啟用"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"已由管理員停用"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rTW/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rTW/strings.xml
index 501f860..f37ccd8 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-zh-rTW/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"已由管理員啟用"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"已由管理員停用"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-zu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-zu/strings.xml
index 86a6acb..8489925 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-zu/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-zu/strings.xml
@@ -19,4 +19,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="enabled_by_admin" msgid="6630472777476410137">"Kunikwe amandla umlawuli"</string>
     <string name="disabled_by_admin" msgid="4023569940620832713">"Kukhutshazwe umlawuli"</string>
+    <!-- no translation found for enabled_by_advanced_protection (6236917660829422499) -->
+    <skip />
+    <!-- no translation found for disabled_by_advanced_protection (369596009193239632) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml
index 7e4460b..7580973 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml
@@ -17,9 +17,12 @@
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
-    <!-- Summary for switch preference to denote it is switched on [CHAR LIMIT=50] -->
+    <!-- Summary for switch preference to denote it is switched on by an admin [CHAR LIMIT=50] -->
     <string name="enabled_by_admin">Enabled by admin</string>
-    <!-- Summary for switch preference to denote it is switched off [CHAR LIMIT=50] -->
+    <!-- Summary for switch preference to denote it is switched off by an admin [CHAR LIMIT=50] -->
     <string name="disabled_by_admin">Disabled by admin</string>
-
-</resources>
\ No newline at end of file
+    <!-- Summary for switch preference to denote it is switched on by Advanced protection [CHAR LIMIT=50] -->
+    <string name="enabled_by_advanced_protection">Enabled by Advanced Protection</string>
+    <!-- Summary for switch preference to denote it is switched off by Advanced protection [CHAR LIMIT=50] -->
+    <string name="disabled_by_advanced_protection">Disabled by Advanced Protection</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
index 0cd0b3c..19818e0 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
@@ -22,7 +22,7 @@
     android:minWidth="@dimen/settingslib_expressive_space_medium3"
     android:minHeight="@dimen/settingslib_expressive_space_medium3"
     android:gravity="center"
-    android:layout_marginEnd="-4dp"
+    android:layout_marginEnd="@dimen/settingslib_expressive_space_extrasmall6"
     android:filterTouchesWhenObscured="false">
 
     <androidx.preference.internal.PreferenceImageView
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
index 944bef6..c837ff4 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
@@ -21,7 +21,8 @@
     android:layout_height="wrap_content"
     android:layout_weight="1"
     android:paddingVertical="@dimen/settingslib_expressive_space_small1"
-    android:paddingHorizontal="@dimen/settingslib_expressive_space_small1"
+    android:paddingStart="@dimen/settingslib_expressive_space_none"
+    android:paddingEnd="@dimen/settingslib_expressive_space_small1"
     android:filterTouchesWhenObscured="false">
 
     <TextView
diff --git a/packages/SettingsLib/SettingsTheme/res/values-af/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-af/strings.xml
new file mode 100644
index 0000000..eb48b4e
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-af/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Vou uit"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Vou in"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-am/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-am/strings.xml
new file mode 100644
index 0000000..4428192
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-am/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"ዘርጋ"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"ሰብስብ"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ar/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ar/strings.xml
new file mode 100644
index 0000000..e6d4c7b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ar/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"توسيع"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"تصغير"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-as/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-as/strings.xml
new file mode 100644
index 0000000..2b5a5c9
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-as/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"বিস্তাৰ কৰক"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"সংকোচন কৰক"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-az/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-az/strings.xml
new file mode 100644
index 0000000..c7adee3
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-az/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Genişləndirin"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Yığcamlaşdırın"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..8b245a6
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Proširi"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Skupi"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-be/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-be/strings.xml
new file mode 100644
index 0000000..b468f81
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-be/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Разгарнуць"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Згарнуць"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-bg/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-bg/strings.xml
new file mode 100644
index 0000000..b177fa7
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-bg/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Разгъване"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Свиване"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-bn/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-bn/strings.xml
new file mode 100644
index 0000000..67bb59f
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-bn/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"বড় করুন"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"আড়াল করুন"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-bs/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-bs/strings.xml
new file mode 100644
index 0000000..31afc8b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-bs/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Proširi"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Suzi"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ca/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ca/strings.xml
new file mode 100644
index 0000000..0f999c9
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ca/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Desplega"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Replega"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-cs/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-cs/strings.xml
new file mode 100644
index 0000000..144dba8
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-cs/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Rozbalit"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Sbalit"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-da/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-da/strings.xml
new file mode 100644
index 0000000..85497f1
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-da/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Udvid"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Skjul"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-de/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-de/strings.xml
new file mode 100644
index 0000000..9e47741
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-de/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Maximieren"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Minimieren"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-el/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-el/strings.xml
new file mode 100644
index 0000000..0b325b5
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-el/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Ανάπτυξη"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Σύμπτυξη"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-en-rAU/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..2539aa0
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-en-rAU/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Expand"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Collapse"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-en-rGB/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..2539aa0
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-en-rGB/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Expand"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Collapse"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-en-rIN/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..2539aa0
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-en-rIN/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Expand"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Collapse"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-es-rUS/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..c976a2e
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-es-rUS/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Expandir"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Contraer"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-es/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-es/strings.xml
new file mode 100644
index 0000000..72ba9d1
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-es/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Mostrar"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Ocultar"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-et/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-et/strings.xml
new file mode 100644
index 0000000..8563606
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-et/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Laienda"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Ahenda"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-eu/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-eu/strings.xml
new file mode 100644
index 0000000..d8c2c35
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-eu/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Zabaldu"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Tolestu"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-fa/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-fa/strings.xml
new file mode 100644
index 0000000..2408741
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-fa/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"ازهم بازکردن"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"جمع کردن"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-fi/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-fi/strings.xml
new file mode 100644
index 0000000..0d226bf
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-fi/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Laajenna"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Tiivistä"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-fr-rCA/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..fecfaa2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-fr-rCA/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Développer"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Réduire"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-fr/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-fr/strings.xml
new file mode 100644
index 0000000..fecfaa2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-fr/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Développer"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Réduire"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-gl/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-gl/strings.xml
new file mode 100644
index 0000000..7999aa1
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-gl/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Despregar"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Contraer"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-gu/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-gu/strings.xml
new file mode 100644
index 0000000..1457d34
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-gu/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"મોટું કરો"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"નાનું કરો"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-hi/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-hi/strings.xml
new file mode 100644
index 0000000..856379a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-hi/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"बड़ा करें"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"छोटा करें"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-hr/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-hr/strings.xml
new file mode 100644
index 0000000..2d637f4
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-hr/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Proširi"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Sažmi"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-hu/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-hu/strings.xml
new file mode 100644
index 0000000..4273163
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-hu/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Kibontás"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Összecsukás"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-in/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-in/strings.xml
new file mode 100644
index 0000000..97c1d602
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-in/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Luaskan"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Ciutkan"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-is/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-is/strings.xml
new file mode 100644
index 0000000..cc4d05b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-is/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Stækka"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Minnka"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-it/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-it/strings.xml
new file mode 100644
index 0000000..8edcf24
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-it/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Espandi"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Comprimi"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-iw/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-iw/strings.xml
new file mode 100644
index 0000000..784bd8e
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-iw/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"הרחבה"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"כיווץ"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ka/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ka/strings.xml
new file mode 100644
index 0000000..ec8f1dd
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ka/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"გაფართოება"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"ჩაკეცვა"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-kk/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-kk/strings.xml
new file mode 100644
index 0000000..329ccbb
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-kk/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Жаю"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Жию"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-km/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-km/strings.xml
new file mode 100644
index 0000000..5ca0269
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-km/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"ពង្រីក"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"បង្រួម"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ko/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ko/strings.xml
new file mode 100644
index 0000000..05d4921
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ko/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"펼치기"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"접기"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ky/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ky/strings.xml
new file mode 100644
index 0000000..4f5b8dc
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ky/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Жайып көрсөтүү"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Жыйыштыруу"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-lo/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-lo/strings.xml
new file mode 100644
index 0000000..d6ec479
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-lo/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"ຂະຫຍາຍ"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"ຫຍໍ້ລົງ"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-lt/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-lt/strings.xml
new file mode 100644
index 0000000..54076c4
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-lt/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Išskleisti"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Sutraukti"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-lv/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-lv/strings.xml
new file mode 100644
index 0000000..2f87b0e
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-lv/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Izvērst"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Sakļaut"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-mk/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-mk/strings.xml
new file mode 100644
index 0000000..b4f8fb9
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-mk/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Прошири"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Собери"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ml/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ml/strings.xml
new file mode 100644
index 0000000..c68141e
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ml/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"വികസിപ്പിക്കുക"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"ചുരുക്കുക"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-mn/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-mn/strings.xml
new file mode 100644
index 0000000..86b333d
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-mn/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Дэлгэх"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Хураах"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ms/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ms/strings.xml
new file mode 100644
index 0000000..1ffa5a0
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ms/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Kembangkan"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Kuncupkan"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-my/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-my/strings.xml
new file mode 100644
index 0000000..6f79acc
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-my/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"ပိုပြပါ"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"လျှော့ပြပါ"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-nb/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-nb/strings.xml
new file mode 100644
index 0000000..359c9ab
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-nb/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Vis"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Skjul"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ne/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ne/strings.xml
new file mode 100644
index 0000000..374fd31
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ne/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"एक्स्पान्ड गर्नुहोस्"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"कोल्याप्स गर्नुहोस्"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-nl/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-nl/strings.xml
new file mode 100644
index 0000000..76a4f99
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-nl/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Uitvouwen"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Samenvouwen"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-or/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-or/strings.xml
new file mode 100644
index 0000000..1e1e870
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-or/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"ବିସ୍ତାର କରନ୍ତୁ"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-pl/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-pl/strings.xml
new file mode 100644
index 0000000..da273b3
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-pl/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Rozwiń"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Zwiń"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-pt-rBR/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..4e3d0e6
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-pt-rBR/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Abrir"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Fechar"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-pt/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-pt/strings.xml
new file mode 100644
index 0000000..4e3d0e6
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-pt/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Abrir"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Fechar"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ro/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ro/strings.xml
new file mode 100644
index 0000000..ec20884
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ro/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Extinde"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Restrânge"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-ru/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ru/strings.xml
new file mode 100644
index 0000000..ba6ab96
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-ru/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Развернуть"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Свернуть"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-si/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-si/strings.xml
new file mode 100644
index 0000000..9adb646
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-si/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"දිග හරින්න"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"හකුළන්න"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-sk/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sk/strings.xml
new file mode 100644
index 0000000..574ee83
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-sk/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Rozbaliť"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Zbaliť"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-sq/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sq/strings.xml
new file mode 100644
index 0000000..e02ecbf
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-sq/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Zgjero"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Palos"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-sr/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sr/strings.xml
new file mode 100644
index 0000000..35f6aa3
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-sr/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Прошири"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Скупи"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-sv/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sv/strings.xml
new file mode 100644
index 0000000..2416839
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-sv/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Utöka"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Komprimera"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-sw/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sw/strings.xml
new file mode 100644
index 0000000..9a60758
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-sw/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Panua"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Kunja"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-th/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-th/strings.xml
new file mode 100644
index 0000000..d6dce9c
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-th/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"ขยาย"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"ยุบ"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-tr/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-tr/strings.xml
new file mode 100644
index 0000000..8c7dbcf
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-tr/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Genişlet"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Daralt"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-uk/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-uk/strings.xml
new file mode 100644
index 0000000..6da0ca8
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-uk/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Розгорнути"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Згорнути"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-vi/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-vi/strings.xml
new file mode 100644
index 0000000..46f3351
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-vi/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Mở rộng"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Thu gọn"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-zh-rCN/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..ea5f29b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-zh-rCN/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"展开"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"收起"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-zh-rHK/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..3620313
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-zh-rHK/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"展開"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"收合"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-zh-rTW/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..3620313
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-zh-rTW/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"展開"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"收合"</string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-zu/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-zu/strings.xml
new file mode 100644
index 0000000..725d8bc
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-zu/strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Nweba"</string>
+    <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Goqa"</string>
+</resources>
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 73d0bec..02e1904 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.3"
+    extra["jetpackComposeVersion"] = "1.8.0-alpha06"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 272dc2d..74811d3 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
 #
 
 [versions]
-agp = "8.6.1"
+agp = "8.7.2"
 compose-compiler = "1.5.11"
 dexmaker-mockito = "2.28.3"
 jvm = "17"
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10.2-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10.2-bin.zip
deleted file mode 100644
index 45f0424..0000000
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10.2-bin.zip
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.11.1-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.11.1-bin.zip
new file mode 100644
index 0000000..f8c4ecb
--- /dev/null
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.11.1-bin.zip
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index 1c25e974..ca510eb 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,6 +16,6 @@
 
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=gradle-8.10.2-bin.zip
+distributionUrl=gradle-8.11.1-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 914f06c..1f32ad6 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -54,14 +54,14 @@
 dependencies {
     api(project(":SettingsLibColor"))
     api("androidx.appcompat:appcompat:1.7.0")
-    api("androidx.compose.material3:material3:1.4.0-alpha01")
-    api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
+    api("androidx.compose.material3:material3:1.4.0-alpha04")
+    api("androidx.compose.material:material-icons-extended")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
     api("androidx.graphics:graphics-shapes-android:1.0.1")
     api("androidx.lifecycle:lifecycle-livedata-ktx")
     api("androidx.lifecycle:lifecycle-runtime-compose")
-    api("androidx.navigation:navigation-compose:2.8.1")
+    api("androidx.navigation:navigation-compose:2.9.0-alpha03")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
     api("com.google.android.material:material:1.12.0")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
index 5baf7be..b5a6ffa 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
@@ -22,10 +22,14 @@
 import android.content.Context
 import android.content.pm.UserInfo
 import com.android.settingslib.R
+import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.RestrictedLockUtilsInternal
 import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
 
 interface IEnterpriseRepository {
     fun getEnterpriseString(updatableStringId: String, resId: Int): String
+    fun getAdminSummaryString(advancedProtectionStringId: Int, updatableStringId: String,
+        resId: Int, enforcedAdmin: RestrictedLockUtils.EnforcedAdmin?, userId: Int): String
 }
 
 class EnterpriseRepository(private val context: Context) : IEnterpriseRepository {
@@ -34,6 +38,21 @@
     override fun getEnterpriseString(updatableStringId: String, resId: Int): String =
         checkNotNull(resources.getString(updatableStringId) { context.getString(resId) })
 
+    override fun getAdminSummaryString(
+        advancedProtectionStringId: Int,
+        updatableStringId: String,
+        resId: Int,
+        enforcedAdmin: RestrictedLockUtils.EnforcedAdmin?,
+        userId: Int
+    ): String {
+        return if (RestrictedLockUtilsInternal.isPolicyEnforcedByAdvancedProtection(context,
+                enforcedAdmin?.enforcedRestriction, userId)) {
+            context.getString(advancedProtectionStringId)
+        } else {
+            getEnterpriseString(updatableStringId, resId)
+        }
+    }
+
     fun getProfileTitle(userInfo: UserInfo): String = if (userInfo.isManagedProfile) {
         getEnterpriseString(WORK_CATEGORY_HEADER, R.string.category_work)
     } else if (userInfo.isPrivateProfile) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
index b6d9242..a140eb8 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
@@ -37,21 +37,27 @@
     fun showRestrictedSettingsDetails()
 }
 
-
 internal data class BlockedByAdminImpl(
     private val context: Context,
     private val enforcedAdmin: RestrictedLockUtils.EnforcedAdmin,
+    private val userId: Int,
     private val enterpriseRepository: IEnterpriseRepository = EnterpriseRepository(context),
 ) : BlockedByAdmin {
     override fun getSummary(checked: Boolean?) = when (checked) {
-        true -> enterpriseRepository.getEnterpriseString(
+        true -> enterpriseRepository.getAdminSummaryString(
+            advancedProtectionStringId = R.string.enabled_by_advanced_protection,
             updatableStringId = Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY,
             resId = R.string.enabled_by_admin,
+            enforcedAdmin = enforcedAdmin,
+            userId = userId,
         )
 
-        false -> enterpriseRepository.getEnterpriseString(
+        false -> enterpriseRepository.getAdminSummaryString(
+            advancedProtectionStringId = R.string.disabled_by_advanced_protection,
             updatableStringId = Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY,
             resId = R.string.disabled_by_admin,
+            enforcedAdmin = enforcedAdmin,
+            userId = userId,
         )
 
         else -> ""
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index 6b1893c..3309faa 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -84,7 +84,11 @@
         for (key in restrictions.keys) {
             RestrictedLockUtilsInternal
                 .checkIfRestrictionEnforced(context, key, restrictions.userId)
-                ?.let { return BlockedByAdminImpl(context = context, enforcedAdmin = it) }
+                ?.let { return BlockedByAdminImpl(
+                    context = context,
+                    enforcedAdmin = it,
+                    userId = restrictions.userId
+                ) }
         }
 
         restrictions.enhancedConfirmation?.let { ec ->
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index ea6a272..7466f95 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -41,8 +41,6 @@
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.model.app.PackageManagers
 import com.android.settingslib.spaprivileged.model.app.toRoute
-import com.android.settingslib.spaprivileged.model.enterprise.EnhancedConfirmation
-import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
 import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
@@ -155,12 +153,12 @@
             override val changeable = { isChangeable }
             override val onCheckedChange: (Boolean) -> Unit = { setAllowed(record, it) }
         }
-        val restrictions = Restrictions(userId = userId,
-            keys = switchRestrictionKeys,
-            enhancedConfirmation = enhancedConfirmationKey?.let { EnhancedConfirmation(
-                key = it,
-                packageName = packageName) })
-        RestrictedSwitchPreference(switchModel, restrictions, restrictionsProviderFactory)
+        RestrictedSwitchPreference(
+            model = switchModel,
+            restrictions = getRestrictions(userId, packageName),
+            ifBlockedByAdminOverrideCheckedValueTo = switchifBlockedByAdminOverrideCheckedValueTo,
+            restrictionsProviderFactory = restrictionsProviderFactory,
+        )
         InfoPageAdditionalContent(record, isAllowed)
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index 627b248..d2867af1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -26,6 +26,8 @@
 import com.android.settingslib.spa.framework.compose.rememberContext
 import com.android.settingslib.spa.framework.util.asyncMapItem
 import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.enterprise.EnhancedConfirmation
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import kotlinx.coroutines.flow.Flow
 
 /**
@@ -38,6 +40,16 @@
     val switchRestrictionKeys: List<String>
         get() = emptyList()
 
+    /**
+     * If this is not null, and on a switch UI restricted by admin, the switch's checked status will
+     * be overridden.
+     *
+     * And if there is an admin summary, such as "Enabled by admin" or "Disabled by admin", will
+     * also be overridden.
+     */
+    val switchifBlockedByAdminOverrideCheckedValueTo: Boolean?
+        get() = null
+
     val enhancedConfirmationKey: String?
         get() = null
 
@@ -101,6 +113,16 @@
     record: T,
 ): Boolean = !record.isSystemOrRootUid() && isChangeable(record)
 
+fun <T : AppRecord> TogglePermissionAppListModel<T>.getRestrictions(
+    userId: Int,
+    packageName: String,
+) =
+    Restrictions(
+        userId = userId,
+        keys = switchRestrictionKeys,
+        enhancedConfirmation =
+            enhancedConfirmationKey?.let { key -> EnhancedConfirmation(key, packageName) },
+    )
 
 interface TogglePermissionAppListProvider {
     val permissionType: String
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 57102ba..ec44d2a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -42,8 +42,6 @@
 import com.android.settingslib.spaprivileged.model.app.AppListModel
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.userId
-import com.android.settingslib.spaprivileged.model.enterprise.EnhancedConfirmation
-import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
 import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
@@ -151,23 +149,19 @@
 
     @Composable
     fun getSummary(record: T): () -> String {
-        val restrictions = remember(record.app.userId, record.app.packageName) {
-            Restrictions(
+        val restrictions =
+            listModel.getRestrictions(
                 userId = record.app.userId,
-                keys = listModel.switchRestrictionKeys,
-                enhancedConfirmation = listModel.enhancedConfirmationKey?.let {
-                    EnhancedConfirmation(
-                        key = it,
-                        packageName = record.app.packageName)
-                })
-        }
+                packageName = record.app.packageName,
+            )
         val restrictedMode by restrictionsProviderFactory.rememberRestrictedMode(restrictions)
         val allowed = listModel.isAllowed(record)
         return RestrictedSwitchPreferenceModel.getSummary(
             context = context,
-            restrictedModeSupplier = { restrictedMode },
             summaryIfNoRestricted = { getSummaryIfNoRestricted(allowed()) },
-            checked = allowed,
+            checkedIfNoRestricted = allowed,
+            checkedIfBlockedByAdmin = listModel.switchifBlockedByAdminOverrideCheckedValueTo,
+            restrictedModeSupplier = { restrictedMode },
         )
     }
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index cd72025..5fec110 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -25,12 +25,25 @@
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
 import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreferenceModel.Companion.RestrictedSwitchWrapper
 
+/**
+ * @param ifBlockedByAdminOverrideCheckedValueTo if this is not null and there is an admin
+ *   restriction, the switch's checked status will be overridden.
+ *
+ *   And if there is an admin summary, such as "Enabled by admin" or "Disabled by admin", will also
+ *   be overridden.
+ */
 @Composable
 fun RestrictedSwitchPreference(
     model: SwitchPreferenceModel,
     restrictions: Restrictions,
+    ifBlockedByAdminOverrideCheckedValueTo: Boolean? = null,
 ) {
-    RestrictedSwitchPreference(model, restrictions, ::RestrictionsProviderImpl)
+    RestrictedSwitchPreference(
+        model = model,
+        restrictions = restrictions,
+        ifBlockedByAdminOverrideCheckedValueTo = ifBlockedByAdminOverrideCheckedValueTo,
+        restrictionsProviderFactory = ::RestrictionsProviderImpl,
+    )
 }
 
 @VisibleForTesting
@@ -38,13 +51,18 @@
 internal fun RestrictedSwitchPreference(
     model: SwitchPreferenceModel,
     restrictions: Restrictions,
+    ifBlockedByAdminOverrideCheckedValueTo: Boolean? = null,
     restrictionsProviderFactory: RestrictionsProviderFactory,
 ) {
     if (restrictions.isEmpty()) {
         SwitchPreference(model)
         return
     }
-    restrictionsProviderFactory.RestrictedSwitchWrapper(model, restrictions) {
+    restrictionsProviderFactory.RestrictedSwitchWrapper(
+        model = model,
+        restrictions = restrictions,
+        ifBlockedByAdminOverrideCheckedValueTo = ifBlockedByAdminOverrideCheckedValueTo,
+    ) {
         SwitchPreference(it)
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
index fb23637..0bb92ce 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -42,26 +42,29 @@
     context: Context,
     model: SwitchPreferenceModel,
     private val restrictedMode: RestrictedMode?,
+    private val ifBlockedByAdminOverrideCheckedValueTo: Boolean?,
 ) : SwitchPreferenceModel {
     override val title = model.title
 
-    override val summary = getSummary(
-        context = context,
-        restrictedModeSupplier = { restrictedMode },
-        summaryIfNoRestricted = model.summary,
-        checked = model.checked,
-    )
+    override val checked =
+        when (restrictedMode) {
+            null -> ({ null })
+            is NoRestricted -> model.checked
+            is BaseUserRestricted -> ({ false })
+            is BlockedByAdmin -> ({ ifBlockedByAdminOverrideCheckedValueTo ?: model.checked() })
+            is BlockedByEcm -> model.checked
+        }
+
+    override val summary =
+        getSummary(
+            context = context,
+            restrictedModeSupplier = { restrictedMode },
+            summaryIfNoRestricted = model.summary,
+            checkedIfNoRestricted = checked,
+        )
 
     override val icon = model.icon
 
-    override val checked = when (restrictedMode) {
-        null -> ({ null })
-        is NoRestricted -> model.checked
-        is BaseUserRestricted -> ({ false })
-        is BlockedByAdmin -> model.checked
-        is BlockedByEcm -> model.checked
-    }
-
     override val changeable = restrictedMode.restrictEnabled(model.changeable)
 
     override val onCheckedChange = restrictedMode.restrictOnClick(model.onCheckedChange)
@@ -112,12 +115,20 @@
         fun RestrictedSwitchWrapper(
             model: SwitchPreferenceModel,
             restrictedMode: RestrictedMode?,
+            ifBlockedByAdminOverrideCheckedValueTo: Boolean? = null,
             content: @Composable (SwitchPreferenceModel) -> Unit,
         ) {
             val context = LocalContext.current
-            val restrictedSwitchPreferenceModel = remember(restrictedMode, model) {
-                RestrictedSwitchPreferenceModel(context, model, restrictedMode)
-            }
+            val restrictedSwitchPreferenceModel =
+                remember(restrictedMode, model) {
+                    RestrictedSwitchPreferenceModel(
+                        context = context,
+                        model = model,
+                        restrictedMode = restrictedMode,
+                        ifBlockedByAdminOverrideCheckedValueTo =
+                            ifBlockedByAdminOverrideCheckedValueTo,
+                    )
+                }
             restrictedSwitchPreferenceModel.RestrictionWrapper {
                 content(restrictedSwitchPreferenceModel)
             }
@@ -127,23 +138,31 @@
         fun RestrictionsProviderFactory.RestrictedSwitchWrapper(
             model: SwitchPreferenceModel,
             restrictions: Restrictions,
+            ifBlockedByAdminOverrideCheckedValueTo: Boolean? = null,
             content: @Composable (SwitchPreferenceModel) -> Unit,
         ) {
-            RestrictedSwitchWrapper(model, rememberRestrictedMode(restrictions).value, content)
+            RestrictedSwitchWrapper(
+                model = model,
+                restrictedMode = rememberRestrictedMode(restrictions).value,
+                ifBlockedByAdminOverrideCheckedValueTo = ifBlockedByAdminOverrideCheckedValueTo,
+                content = content,
+            )
         }
 
         fun getSummary(
             context: Context,
-            restrictedModeSupplier: () -> RestrictedMode?,
             summaryIfNoRestricted: () -> String,
-            checked: () -> Boolean?,
+            checkedIfNoRestricted: () -> Boolean?,
+            checkedIfBlockedByAdmin: Boolean? = null,
+            restrictedModeSupplier: () -> RestrictedMode?,
         ): () -> String = {
             when (val restrictedMode = restrictedModeSupplier()) {
                 is NoRestricted -> summaryIfNoRestricted()
                 is BaseUserRestricted ->
                     context.getString(com.android.settingslib.R.string.disabled)
 
-                is BlockedByAdmin -> restrictedMode.getSummary(checked())
+                is BlockedByAdmin ->
+                    restrictedMode.getSummary(checkedIfBlockedByAdmin ?: checkedIfNoRestricted())
                 is BlockedByEcm ->
                     context.getString(com.android.settingslib.R.string.disabled)
 
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt
index 8fd16b3..f3245c9 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt
@@ -16,19 +16,49 @@
 
 package com.android.settingslib.spaprivileged.model.enterprise
 
+import android.app.admin.DevicePolicyManager
 import android.app.admin.DevicePolicyResources.Strings.Settings
+import android.app.admin.EnforcingAdmin
 import android.content.Context
+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.security.Flags
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.RestrictedLockUtilsInternal
+import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
+import com.android.settingslib.spaprivileged.tests.testutils.getEnforcingAdminAdvancedProtection
+import com.android.settingslib.spaprivileged.tests.testutils.getEnforcingAdminNotAdvancedProtection
+import com.android.settingslib.widget.restricted.R
 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.whenever
 
 @RunWith(AndroidJUnit4::class)
 class RestrictedModeTest {
+    @Rule
+    @JvmField
+    val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Spy
     private val context: Context = ApplicationProvider.getApplicationContext()
 
+    @Mock
+    private lateinit var devicePolicyManager: DevicePolicyManager
+
     private val fakeEnterpriseRepository = object : IEnterpriseRepository {
         override fun getEnterpriseString(updatableStringId: String, resId: Int): String =
             when (updatableStringId) {
@@ -36,20 +66,123 @@
                 Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY -> DISABLED_BY_ADMIN
                 else -> ""
             }
+
+        override fun getAdminSummaryString(
+            advancedProtectionStringId: Int,
+            updatableStringId: String,
+            resId: Int,
+            enforcedAdmin: RestrictedLockUtils.EnforcedAdmin?,
+            userId: Int
+        ): String {
+            if (RestrictedLockUtilsInternal.isPolicyEnforcedByAdvancedProtection(context,
+                    RESTRICTION, userId)) {
+                return when (advancedProtectionStringId) {
+                    R.string.enabled_by_advanced_protection -> ENABLED_BY_ADVANCED_PROTECTION
+                    R.string.disabled_by_advanced_protection -> DISABLED_BY_ADVANCED_PROTECTION
+                    else -> ""
+                }
+            }
+            return getEnterpriseString(updatableStringId, resId)
+        }
     }
 
+    @Before
+    fun setUp() {
+        whenever(context.devicePolicyManager).thenReturn(devicePolicyManager)
+    }
+
+    @RequiresFlagsDisabled(Flags.FLAG_AAPM_API)
     @Test
     fun blockedByAdmin_getSummaryWhenChecked() {
-        val blockedByAdmin = BlockedByAdminImpl(context, ENFORCED_ADMIN, fakeEnterpriseRepository)
+        val blockedByAdmin = BlockedByAdminImpl(context, ENFORCED_ADMIN, USER_ID,
+            fakeEnterpriseRepository)
 
         val summary = blockedByAdmin.getSummary(true)
 
         assertThat(summary).isEqualTo(ENABLED_BY_ADMIN)
     }
 
+    @RequiresFlagsDisabled(Flags.FLAG_AAPM_API)
     @Test
     fun blockedByAdmin_getSummaryNotWhenChecked() {
-        val blockedByAdmin = BlockedByAdminImpl(context, ENFORCED_ADMIN, fakeEnterpriseRepository)
+        val blockedByAdmin = BlockedByAdminImpl(context, ENFORCED_ADMIN, USER_ID,
+            fakeEnterpriseRepository)
+
+        val summary = blockedByAdmin.getSummary(false)
+
+        assertThat(summary).isEqualTo(DISABLED_BY_ADMIN)
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
+    @Test
+    fun blockedByAdmin_disabledByAdvancedProtection_getSummaryWhenChecked() {
+        val blockedByAdmin =
+            BlockedByAdminImpl(
+                context = context,
+                enforcedAdmin = ENFORCED_ADMIN,
+                enterpriseRepository = fakeEnterpriseRepository,
+                userId = USER_ID,
+            )
+
+        whenever(devicePolicyManager.getEnforcingAdmin(USER_ID, RESTRICTION))
+            .thenReturn(ENFORCING_ADMIN_ADVANCED_PROTECTION)
+
+        val summary = blockedByAdmin.getSummary(true)
+
+        assertThat(summary).isEqualTo(ENABLED_BY_ADVANCED_PROTECTION)
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
+    @Test
+    fun blockedByAdmin_disabledByAdvancedProtection_getSummaryWhenNotChecked() {
+        val blockedByAdmin =
+            BlockedByAdminImpl(
+                context = context,
+                enforcedAdmin = ENFORCED_ADMIN,
+                enterpriseRepository = fakeEnterpriseRepository,
+                userId = USER_ID,
+            )
+
+        whenever(devicePolicyManager.getEnforcingAdmin(USER_ID, RESTRICTION))
+            .thenReturn(ENFORCING_ADMIN_ADVANCED_PROTECTION)
+
+        val summary = blockedByAdmin.getSummary(false)
+
+        assertThat(summary).isEqualTo(DISABLED_BY_ADVANCED_PROTECTION)
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
+    @Test
+    fun blockedByAdmin_notDisabledByAdvancedProtection_getSummaryWhenChecked() {
+        val blockedByAdmin =
+            BlockedByAdminImpl(
+                context = context,
+                enforcedAdmin = ENFORCED_ADMIN,
+                enterpriseRepository = fakeEnterpriseRepository,
+                userId = USER_ID,
+            )
+
+        whenever(devicePolicyManager.getEnforcingAdmin(USER_ID, RESTRICTION))
+            .thenReturn(ENFORCING_ADMIN_NOT_ADVANCED_PROTECTION)
+
+        val summary = blockedByAdmin.getSummary(true)
+
+        assertThat(summary).isEqualTo(ENABLED_BY_ADMIN)
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
+    @Test
+    fun blockedByAdmin_notDisabledByAdvancedProtection_getSummaryWhenNotChecked() {
+        val blockedByAdmin =
+            BlockedByAdminImpl(
+                context = context,
+                enforcedAdmin = ENFORCED_ADMIN,
+                enterpriseRepository = fakeEnterpriseRepository,
+                userId = USER_ID,
+            )
+
+        whenever(devicePolicyManager.getEnforcingAdmin(USER_ID, RESTRICTION))
+            .thenReturn(ENFORCING_ADMIN_NOT_ADVANCED_PROTECTION)
 
         val summary = blockedByAdmin.getSummary(false)
 
@@ -57,11 +190,19 @@
     }
 
     private companion object {
+        const val PACKAGE_NAME = "package.name"
         const val RESTRICTION = "restriction"
+        const val USER_ID = 0
         val ENFORCED_ADMIN: RestrictedLockUtils.EnforcedAdmin =
             RestrictedLockUtils.EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION)
+        val ENFORCING_ADMIN_ADVANCED_PROTECTION: EnforcingAdmin =
+            getEnforcingAdminAdvancedProtection(PACKAGE_NAME, USER_ID)
+        val ENFORCING_ADMIN_NOT_ADVANCED_PROTECTION: EnforcingAdmin =
+            getEnforcingAdminNotAdvancedProtection(PACKAGE_NAME, USER_ID)
 
         const val ENABLED_BY_ADMIN = "Enabled by admin"
         const val DISABLED_BY_ADMIN = "Disabled by admin"
+        const val ENABLED_BY_ADVANCED_PROTECTION = "Enabled by advanced protection"
+        const val DISABLED_BY_ADVANCED_PROTECTION = "Disabled by advanced protection"
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
index bf0ad0b..79085af 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -16,8 +16,17 @@
 
 package com.android.settingslib.spaprivileged.template.app
 
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources.Strings.Settings
+import android.app.admin.DevicePolicyResourcesManager
+import android.app.admin.EnforcingAdmin
 import android.content.Context
 import android.content.pm.ApplicationInfo
+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.security.Flags
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.test.assertIsDisplayed
@@ -26,29 +35,62 @@
 import androidx.compose.ui.test.performClick
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.RestrictedLockUtils
 import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
 import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
 import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdminImpl
 import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
 import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
 import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
 import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.getEnforcingAdminAdvancedProtection
+import com.android.settingslib.spaprivileged.tests.testutils.getEnforcingAdminNotAdvancedProtection
 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.eq
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class TogglePermissionAppListPageTest {
+    @Rule
+    @JvmField
+    val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
     @get:Rule
     val composeTestRule = createComposeRule()
 
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    private lateinit var devicePolicyManager: DevicePolicyManager
+
+    @Mock
+    private lateinit var devicePolicyResourcesManager: DevicePolicyResourcesManager
+
+    @Spy
     private val context: Context = ApplicationProvider.getApplicationContext()
 
     private val fakeNavControllerWrapper = FakeNavControllerWrapper()
 
     private val fakeRestrictionsProvider = FakeRestrictionsProvider()
 
+    @Before
+    fun setUp() {
+        whenever(context.devicePolicyManager).thenReturn(devicePolicyManager)
+        whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
+    }
+
     @Test
     fun pageTitle() {
         val listModel = TestTogglePermissionAppListModel()
@@ -96,6 +138,81 @@
         assertThat(summary).isEqualTo(context.getPlaceholder())
     }
 
+    @RequiresFlagsDisabled(Flags.FLAG_AAPM_API)
+    @Test
+    fun summary_whenAllowedButAdminOverrideToNotAllowed() {
+        fakeRestrictionsProvider.restrictedMode =
+            BlockedByAdminImpl(context = context, enforcedAdmin = ENFORCED_ADMIN, userId = USER_ID)
+        val listModel =
+            TestTogglePermissionAppListModel(
+                isAllowed = true,
+                switchifBlockedByAdminOverrideCheckedValueTo = false,
+            )
+
+        val summary = getSummary(listModel)
+
+        assertThat(summary)
+            .isEqualTo(
+                context.getString(
+                    com.android.settingslib.widget.restricted.R.string.disabled_by_admin
+                )
+            )
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
+    @Test
+    fun summary_disabledByAdvancedProtection_whenAllowedButAdminOverrideToNotAllowed() {
+        whenever(devicePolicyManager.getEnforcingAdmin(USER_ID, RESTRICTION))
+            .thenReturn(ENFORCING_ADMIN_ADVANCED_PROTECTION)
+
+        fakeRestrictionsProvider.restrictedMode =
+            BlockedByAdminImpl(context = context, enforcedAdmin = ENFORCED_ADMIN, userId = USER_ID)
+        val listModel =
+            TestTogglePermissionAppListModel(
+                isAllowed = true,
+                switchifBlockedByAdminOverrideCheckedValueTo = false,
+            )
+
+        val summary = getSummary(listModel)
+
+        assertThat(summary)
+            .isEqualTo(
+                context.getString(
+                    com.android.settingslib.widget.restricted.R.string
+                        .disabled_by_advanced_protection
+                )
+            )
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_AAPM_API)
+    @Test
+    fun summary_notDisabledByAdvancedProtection_whenAllowedButAdminOverrideToNotAllowed() {
+        val disabledByAdminText = context.getString(
+            com.android.settingslib.widget.restricted.R.string.disabled_by_admin
+        )
+        whenever(devicePolicyManager.getEnforcingAdmin(USER_ID, RESTRICTION))
+            .thenReturn(ENFORCING_ADMIN_NOT_ADVANCED_PROTECTION)
+        whenever(devicePolicyResourcesManager.getString(
+            eq(Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY), any())).thenReturn(disabledByAdminText)
+
+        fakeRestrictionsProvider.restrictedMode =
+            BlockedByAdminImpl(context = context, enforcedAdmin = ENFORCED_ADMIN, userId = USER_ID)
+        val listModel =
+            TestTogglePermissionAppListModel(
+                isAllowed = true,
+                switchifBlockedByAdminOverrideCheckedValueTo = false,
+            )
+
+        val summary = getSummary(listModel)
+
+        assertThat(summary)
+            .isEqualTo(
+                context.getString(
+                    com.android.settingslib.widget.restricted.R.string.disabled_by_admin
+                )
+            )
+    }
+
     @Test
     fun appListItem_onClick_navigate() {
         val listModel = TestTogglePermissionAppListModel()
@@ -162,8 +279,14 @@
         const val PACKAGE_NAME = "package.name"
         const val LABEL = "Label"
         const val SUMMARY = "Summary"
-        val APP = ApplicationInfo().apply {
-            packageName = PACKAGE_NAME
-        }
+        val APP = ApplicationInfo().apply { packageName = PACKAGE_NAME }
+        const val RESTRICTION = "restriction"
+        const val USER_ID = 0
+        val ENFORCED_ADMIN: RestrictedLockUtils.EnforcedAdmin =
+            RestrictedLockUtils.EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION)
+        val ENFORCING_ADMIN_ADVANCED_PROTECTION: EnforcingAdmin =
+            getEnforcingAdminAdvancedProtection(PACKAGE_NAME, USER_ID)
+        val ENFORCING_ADMIN_NOT_ADVANCED_PROTECTION: EnforcingAdmin =
+            getEnforcingAdminNotAdvancedProtection(PACKAGE_NAME, USER_ID)
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
index b88d1c5..1fd7ecf 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
@@ -121,7 +121,7 @@
     }
 
     @Test
-    fun whenBlockedByAdmin_disabled() {
+    fun whenBlockedByAdmin_notOverrideChecked() {
         val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
         fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
 
@@ -133,6 +133,18 @@
     }
 
     @Test
+    fun whenBlockedByAdmin_overrideCheckedToFalse() {
+        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+        setContent(restrictions, ifBlockedByAdminOverrideCheckedValueTo = false)
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+        composeTestRule.onNodeWithText(FakeBlockedByAdmin.SUMMARY).assertIsDisplayed()
+        composeTestRule.onNode(isOff()).assertIsDisplayed()
+    }
+
+    @Test
     fun whenBlockedByAdmin_click() {
         val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
         fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
@@ -166,9 +178,16 @@
         assertThat(fakeBlockedByEcm.showRestrictedSettingsDetailsIsCalled).isTrue()
     }
 
-    private fun setContent(restrictions: Restrictions) {
+    private fun setContent(
+        restrictions: Restrictions,
+        ifBlockedByAdminOverrideCheckedValueTo: Boolean? = null,
+    ) {
         composeTestRule.setContent {
-            RestrictedSwitchPreference(switchPreferenceModel, restrictions) { _, _ ->
+            RestrictedSwitchPreference(
+                model = switchPreferenceModel,
+                restrictions = restrictions,
+                ifBlockedByAdminOverrideCheckedValueTo = ifBlockedByAdminOverrideCheckedValueTo,
+            ) { _, _ ->
                 fakeRestrictionsProvider
             }
         }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
index f8ca2a0..d5e8d6a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
@@ -16,6 +16,10 @@
 
 package com.android.settingslib.spaprivileged.tests.testutils
 
+import android.app.admin.EnforcingAdmin
+import android.app.admin.UnknownAuthority
+import android.os.UserHandle
+import android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY
 import androidx.compose.runtime.Composable
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
@@ -55,3 +59,10 @@
     @Composable
     override fun restrictedModeState() = stateOf(restrictedMode)
 }
+
+fun getEnforcingAdminAdvancedProtection(packageName: String, userId: Int): EnforcingAdmin =
+    EnforcingAdmin(packageName, UnknownAuthority(ADVANCED_PROTECTION_SYSTEM_ENTITY),
+        UserHandle.of(userId))
+
+fun getEnforcingAdminNotAdvancedProtection(packageName: String, userId: Int): EnforcingAdmin =
+    EnforcingAdmin(packageName, UnknownAuthority.UNKNOWN_AUTHORITY, UserHandle.of(userId))
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
index 1790313..000743e 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
@@ -28,6 +28,7 @@
 class TestTogglePermissionAppListModel(
     isAllowed: Boolean? = null,
     private val isChangeable: Boolean = false,
+    override val switchifBlockedByAdminOverrideCheckedValueTo: Boolean? = null,
 ) : TogglePermissionAppListModel<TestAppRecord> {
     override val pageTitleResId = R.string.test_permission_title
     override val switchTitleResId = R.string.test_permission_switch_title
diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
index 9764e64..4428480 100644
--- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
+++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
@@ -75,6 +75,7 @@
         (holder.findViewById(R.id.collapsable_text_view) as? CollapsableTextView)?.apply {
             setCollapsable(isCollapsable)
             setMinLines(minLines)
+            visibility = if (title.isNullOrEmpty()) View.GONE else View.VISIBLE
             setText(title.toString())
             if (hyperlinkListener != null) {
                 setHyperlinkListener(hyperlinkListener)
diff --git a/packages/SettingsLib/UsageProgressBarPreference/Android.bp b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
index 0a83aab..bf4cb99 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/Android.bp
+++ b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
@@ -20,6 +20,7 @@
     static_libs: [
         "androidx.preference_preference",
         "androidx-constraintlayout_constraintlayout",
+        "SettingsLibSettingsTheme",
     ],
 
     sdk_version: "system_current",
diff --git a/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml b/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
index ea033a3..7d366f3 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
+++ b/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
@@ -22,9 +22,9 @@
     android:layout_height="wrap_content"
     android:gravity="center_vertical"
     android:orientation="vertical"
-    android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
-    android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:paddingBottom="16dp">
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingBottom="@dimen/settingslib_expressive_space_small1">
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
diff --git a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
index 0e71a1b..4c45c53 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
+++ b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
@@ -42,7 +42,7 @@
  *
  * <p>This preference shows number in usage summary with enlarged font size.
  */
-public class UsageProgressBarPreference extends Preference {
+public class UsageProgressBarPreference extends Preference implements GroupSectionDividerMixin {
 
     private final Pattern mNumberPattern = Pattern.compile("[\\d]*[\\٫.,]?[\\d]+");
 
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 076f82a..c1f254a 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -98,6 +98,7 @@
     namespace: "android_settings"
     description: "Settings catalyst project migration"
     bug: "323791114"
+    is_exported: true
 }
 
 flag {
@@ -106,6 +107,7 @@
     namespace: "android_settings"
     description: "Enable WRITE_SYSTEM_PREFERENCE permission and appop"
     bug: "375193223"
+    is_exported: true
 }
 
 flag {
@@ -178,3 +180,10 @@
     description: "Enable the input routing control in device details and hearing devices dialog."
     bug: "349255906"
 }
+
+flag {
+    name: "hearing_device_set_connection_status_report"
+    namespace: "accessibility"
+    description: "Enable the connection status report for a set of hearing device."
+    bug: "357882387"
+}
diff --git a/packages/SettingsLib/res/drawable/ic_tv_box_internal_speaker.xml b/packages/SettingsLib/res/drawable/ic_tv_box_internal_speaker.xml
new file mode 100644
index 0000000..2a90e05
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_tv_box_internal_speaker.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/textColorPrimary"
+    android:autoMirrored="true">
+    <path android:fillColor="#FFFFFFFF"
+        android:pathData="M14,20.725V18.675Q16.25,18.025 17.625,16.175Q19,14.325 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,15.15 19.05,17.587Q17.1,20.025 14,20.725ZM3,15V9H7L12,4V20L7,15ZM14,16V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,13.275 15.838,14.362Q15.175,15.45 14,16ZM10,8.85 L7.85,11H5V13H7.85L10,15.15ZM7.5,12Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 3225985..ae04ca1 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -366,10 +366,10 @@
     <string name="debug_app" msgid="8903350241392391766">"Kies ontfoutprogram"</string>
     <string name="debug_app_not_set" msgid="1934083001283807188">"Geen ontfoutprogram gestel nie"</string>
     <string name="debug_app_set" msgid="6599535090477753651">"Ontfoutingsprogram: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
-    <string name="select_application" msgid="2543228890535466325">"Kies program"</string>
+    <string name="select_application" msgid="2543228890535466325">"Kies app"</string>
     <string name="no_application" msgid="9038334538870247690">"Niks"</string>
     <string name="wait_for_debugger" msgid="7461199843335409809">"Wag vir ontfouter"</string>
-    <string name="wait_for_debugger_summary" msgid="6846330006113363286">"Ontfoutde program wag vir ontfouter om te heg voordat dit uitgevoer word"</string>
+    <string name="wait_for_debugger_summary" msgid="6846330006113363286">"Ontfoute app wag vir ontfouter om te heg voordat dit uitgevoer word"</string>
     <string name="debug_input_category" msgid="7349460906970849771">"Invoer"</string>
     <string name="debug_drawing_category" msgid="5066171112313666619">"Skets"</string>
     <string name="debug_hw_drawing_category" msgid="5830815169336975162">"Hardeware-versnelde lewering"</string>
@@ -428,11 +428,11 @@
     <string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"Vernietig elke aktiwiteit sodra die gebruiker dit verlaat"</string>
     <string name="app_process_limit_title" msgid="8361367869453043007">"Agtergrondproseslimiet"</string>
     <string name="show_all_anrs" msgid="9160563836616468726">"Wys agtergrond-ANR\'e"</string>
-    <string name="show_all_anrs_summary" msgid="8562788834431971392">"Wys Program Reageer Nie-dialoog vir agtergrondprogramme"</string>
+    <string name="show_all_anrs_summary" msgid="8562788834431971392">"Wys App Reageer Nie-dialoog vir agtergrondapps"</string>
     <string name="show_notification_channel_warnings" msgid="3448282400127597331">"Wys kennisgewingkanaalwaarskuwings"</string>
-    <string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"Wys waarskuwing op skerm wanneer \'n program \'n kennisgewing sonder \'n geldige kanaal plaas"</string>
+    <string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"Wys waarskuwing op skerm wanneer \'n app \'n kennisgewing sonder \'n geldige kanaal plaas"</string>
     <string name="force_allow_on_external" msgid="9187902444231637880">"Dwing toelating op eksterne berging"</string>
-    <string name="force_allow_on_external_summary" msgid="8525425782530728238">"Maak dat enige program na eksterne berging geskryf kan word, ongeag manifeswaardes"</string>
+    <string name="force_allow_on_external_summary" msgid="8525425782530728238">"Maak dat enige app na eksterne berging geskryf kan word, ongeag manifeswaardes"</string>
     <string name="force_resizable_activities" msgid="7143612144399959606">"Dwing aktiwiteite om verstelbaar te wees"</string>
     <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Maak die groottes van alle aktiwiteite verstelbaar vir veelvuldige vensters, ongeag manifeswaardes."</string>
     <string name="enable_freeform_support" msgid="7599125687603914253">"Aktiveer vormvrye vensters"</string>
@@ -546,7 +546,7 @@
     <string name="active_input_method_subtypes" msgid="4232680535471633046">"Aktiewe invoermetodes"</string>
     <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Gebruik stelseltale"</string>
     <string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"Kon nie instellings vir <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> oopmaak nie"</string>
-    <string name="ime_security_warning" msgid="6547562217880551450">"Die invoermetode kan dalk alle teks wat jy invoer, versamel, insluitend persoonlike data soos wagwoorde en kredietkaartnommers. Dit kom van die program <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Wil jy dié invoermetode gebruik?"</string>
+    <string name="ime_security_warning" msgid="6547562217880551450">"Die invoermetode kan dalk alle teks wat jy invoer, versamel, insluitend persoonlike data soos wagwoorde en kredietkaartnommers. Dit kom van die app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Wil jy dié invoermetode gebruik?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"Let wel: Ná \'n herselflaai kan hierdie app nie begin voordat jy jou foon ontsluit het nie"</string>
     <string name="ims_reg_title" msgid="8197592958123671062">"IMS-registrasiestaat"</string>
     <string name="ims_reg_status_registered" msgid="884916398194885457">"Geregistreer"</string>
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Hierdie foon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Hierdie tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Hierdie rekenaar (intern)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Hierdie TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dokluidspreker"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Eksterne toestel"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Gekoppelde toestel"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Gekoppel deur ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Gekoppel deur eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV-verstek"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-uitset"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interne luidsprekers"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ingeboude luidspreker"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV-oudio"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Kan nie koppel nie. Skakel toestel af en weer aan"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Bedrade oudiotoestel"</string>
     <string name="help_label" msgid="3528360748637781274">"Hulp en terugvoer"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 34f37b4..03cde01 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"ይህ ስልክ"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ይህ ጡባዊ"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ይህ ኮምፒውተር (ውስጣዊ)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ይህ ቲቪ"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"የመትከያ ድምፅ ማውጫ"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"የውጭ መሣሪያ"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"የተገናኘ መሣሪያ"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI ኢኤአርሲ"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"በኤአርሲ በኩል ተገናኝቷል"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"በኢኤአርሲ በኩል ተገናኝቷል"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"የቲቪ ነባሪ"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"የHDMI ውጤት"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ውስጣዊ ድምፅ ማውጫዎች"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"አብሮ የተሰራ ድምፅ ማውጫ"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"የTV ኦዲዮ"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"መገናኘት ላይ ችግር። መሳሪያውን ያጥፉት እና እንደገና ያብሩት"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ባለገመድ የኦዲዮ መሣሪያ"</string>
     <string name="help_label" msgid="3528360748637781274">"እገዛ እና ግብረመልስ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 2ea1486..87a8241 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"هذا الهاتف"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"هذا الجهاز اللوحي"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"هذا الكمبيوتر (داخلي)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"جهاز التلفزيون هذا"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"مكبّر صوت بقاعدة إرساء"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"جهاز خارجي"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"جهاز متّصل"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"‏متّصل من خلال ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"‏متّصل من خلال eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"الجهاز التلقائي لإخراج صوت التلفزيون"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"‏إخراج الصوت من خلال HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"مكبّرات الصوت الداخلية"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"مكبِّر الصوت المُدمَج"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"صوت التلفزيون"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"حدثت مشكلة أثناء الاتصال. يُرجى إيقاف الجهاز ثم إعادة تشغيله."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"جهاز سماعي سلكي"</string>
     <string name="help_label" msgid="3528360748637781274">"المساعدة والملاحظات"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 432d443..4aab7d6 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"এই ফ’নটো"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"এই টেবলেটটো"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"এই কম্পিউটাৰ (অভ্যন্তৰীণ)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"এই টিভিটো"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ড’ক স্পীকাৰ"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"বাহ্যিক ডিভাইচ"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"সংযোগ হৈ থকা ডিভাইচ"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARCৰ জৰিয়তে সংযুক্ত"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARCৰ জৰিয়তে সংযুক্ত"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"টিভি ডিফ’ল্ট"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI আউটপুট"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"অভ্যন্তৰীণ স্পীকাৰ"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"বিল্ট-ইন স্পীকাৰ"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"টিভিৰ অডিঅ’"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"সংযোগ হোৱাত সমস্যা হৈছে। ডিভাইচটো অফ কৰি পুনৰ অন কৰক"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"তাঁৰযুক্ত অডিঅ’ ডিভাইচ"</string>
     <string name="help_label" msgid="3528360748637781274">"সহায় আৰু মতামত"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 330532e..5841bda 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Bu telefon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Bu planşet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Bu kompüter (daxili)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Bu TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dok dinamiki"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Xarici cihaz"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Qoşulmuş cihaz"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Chrome-da Tətbiqin İşləmə Müddəti vasitəsilə qoşulub"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC vasitəsilə qoşulub"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV defoltu"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI çıxışı"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Daxili dinamiklər"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Daxili dinamik"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV audiosu"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Qoşulmaqla bağlı problem. Cihazı deaktiv edin, sonra yenidən aktiv edin"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Simli audio cihaz"</string>
     <string name="help_label" msgid="3528360748637781274">"Yardım və rəy"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 4dfd253..d36a864 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ovaj telefon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ovaj tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ovaj računar (interno)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ovaj TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Zvučnik bazne stanice"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Spoljni uređaj"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Povezani uređaj"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Povezano preko ARC-a"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Povezano preko eARC-a"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Podrazumevana vrednost za TV"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI izlaz"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Unutrašnji zvučnici"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ugrađeni zvučnik"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Zvuk TV-a"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem pri povezivanju. Isključite uređaj, pa ga ponovo uključite"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Žičani audio uređaj"</string>
     <string name="help_label" msgid="3528360748637781274">"Pomoć i povratne informacije"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index de25460..29f5542 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Гэты тэлефон"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Гэты планшэт"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Гэты камп’ютар (унутраны)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Гэты тэлевізар"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Дынамік док-станцыі"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Знешняя прылада"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Падключаная прылада"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Падключана праз ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Падключана праз eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Стандартны аўдыявыхад тэлевізара"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Выхад HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Унутраныя дынамікі"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Убудаваны дынамік"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Аўдыя тэлевізара"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Праблема з падключэннем. Выключыце і зноў уключыце прыладу"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Правадная аўдыяпрылада"</string>
     <string name="help_label" msgid="3528360748637781274">"Даведка і водгукі"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 47ce37c..8d261a2 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Този телефон"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Този таблет"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Този компютър (вграден)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Този телевизор"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Високоговорител докинг станция"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Външно устройство"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Свързано устройство"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Свързано посредством ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Свързано посредством eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Стандартното за телевизора"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI изход"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Вградени високоговорители"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Вграден високоговорител"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Аудио на телевизора"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"При свързването възникна проблем. Изключете устройството и го включете отново"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Аудиоустройство с кабел"</string>
     <string name="help_label" msgid="3528360748637781274">"Помощ и отзиви"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 3b77cfd..0ad50cc 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"এই ফোন"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"এই ট্যাবলেট"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"এই কম্পিউটার (ইন্টার্নাল)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"এই টিভি"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ডক স্পিকার"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"এক্সটার্নাল ডিভাইস"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"কানেক্ট থাকা ডিভাইস"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC-এর মাধ্যমে কানেক্ট করা হয়েছে"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC-এর মাধ্যমে কানেক্ট করা হয়েছে"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"টিভির ডিফল্ট সেটিং"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI আউটপুট"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ইন্টার্নাল স্পিকার"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"বিল্ট-ইন স্পিকার"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"টিভি অডিও"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"কানেক্ট করতে সমস্যা হচ্ছে। ডিভাইস বন্ধ করে আবার চালু করুন"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ওয়্যার অডিও ডিভাইস"</string>
     <string name="help_label" msgid="3528360748637781274">"সহায়তা ও মতামত"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 65eb681..a30bcfa 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ovaj telefon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ovaj tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ovaj računar (interno)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ovaj TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Zvučnik priključne stanice"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Vanjski uređaj"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Povezani uređaj"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Povezano je putem ARC-a"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Povezano je putem eARC-a"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Zadano za TV"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI izlaz"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interni zvučnici"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ugrađeni zvučnik"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Zvuk TV-a"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Došlo je do problema prilikom povezivanja. Isključite, pa ponovo uključite uređaj"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Žičani audio uređaj"</string>
     <string name="help_label" msgid="3528360748637781274">"Pomoć i povratne informacije"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index a7c3faf..ba733de 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Aquest telèfon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Aquesta tauleta"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Aquest ordinador (intern)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Aquest televisor"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Base d\'altaveu"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositiu extern"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositiu connectat"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connectat mitjançant ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connectat mitjançant eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Televisor predeterminat"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Sortida HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Altaveus interns"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Altaveu integrat"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Àudio de la televisió"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Hi ha hagut un problema amb la connexió. Apaga el dispositiu i torna\'l a encendre."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositiu d\'àudio amb cable"</string>
     <string name="help_label" msgid="3528360748637781274">"Ajuda i suggeriments"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 832d320..1a239a1 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Tento telefon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tento tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Tento počítač (interní)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Tato televize"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Reproduktor doku"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Externí zařízení"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Připojené zařízení"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Připojeno přes ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Připojeno přes eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Výchozí nastavení televize"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Výstup HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interní reproduktory"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Vestavěný reproduktor"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Zvuk televize"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problém s připojením. Vypněte zařízení a znovu jej zapněte"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Kabelové audiozařízení"</string>
     <string name="help_label" msgid="3528360748637781274">"Nápověda a zpětná vazba"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 708d5df..3753e033 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Denne telefon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Denne tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Denne computer (intern)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Dette fjernsyn"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dockhøjttaler"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Ekstern enhed"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Forbundet enhed"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Forbundet via ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Forbundet via eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Standardindstillinger for fjernsyn"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-udgang"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interne højttalere"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Indbygget højttaler"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV-lyd"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Der kunne ikke oprettes forbindelse. Sluk og tænd enheden"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Lydenhed med ledning"</string>
     <string name="help_label" msgid="3528360748637781274">"Hjælp og feedback"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 069201a..7d0b848 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Dieses Smartphone"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Dieses Tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Dieser Computer (intern)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Dieser Fernseher"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dock-Lautsprecher"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Externes Gerät"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Verbundenes Gerät"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Per ARC verbunden"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Per eARC verbunden"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Standardeinstellung: Fernseher"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-Ausgang"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interne Lautsprecher"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Integrierter Lautsprecher"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV‑Audio"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Verbindung kann nicht hergestellt werden. Schalte das Gerät aus und wieder ein."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Netzbetriebenes Audiogerät"</string>
     <string name="help_label" msgid="3528360748637781274">"Hilfe und Feedback"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 8577a9a..915cabb 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Αυτό το τηλέφωνο"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Αυτό το tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Αυτός ο υπολογιστής (εσωτερ.)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Αυτή η τηλεόραση"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Ηχείο βάσης σύνδεσης"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Εξωτερική συσκευή"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Συνδεδεμένη συσκευή"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Συνδέθηκε μέσω ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Συνδέθηκε μέσω eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Προεπιλογή τηλεόρασης"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Έξοδος HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Εσωτερικά ηχεία"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ενσωματωμένο ηχείο"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Ήχος τηλεόρασης"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Πρόβλημα κατά τη σύνδεση. Απενεργοποιήστε τη συσκευή και ενεργοποιήστε την ξανά"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Ενσύρματη συσκευή ήχου"</string>
     <string name="help_label" msgid="3528360748637781274">"Βοήθεια και σχόλια"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index f9d2f08..d9c35f5 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"This phone"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"This tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"This computer (internal)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"This TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dock speaker"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"External device"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Connected device"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connected via ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connected via eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV default"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI output"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Internal speakers"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Built-in speaker"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV audio"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off and back on"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired audio device"</string>
     <string name="help_label" msgid="3528360748637781274">"Help and feedback"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 2cf98ef..887841c 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"This phone"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"This tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"This computer (internal)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"This TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dock speaker"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"External Device"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Connected device"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connected via ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connected via eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV default"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI output"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Internal speakers"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Built-in speaker"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV Audio"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off &amp; back on"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired audio device"</string>
     <string name="help_label" msgid="3528360748637781274">"Help and feedback"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index f9d2f08..d9c35f5 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"This phone"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"This tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"This computer (internal)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"This TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dock speaker"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"External device"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Connected device"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connected via ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connected via eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV default"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI output"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Internal speakers"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Built-in speaker"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV audio"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off and back on"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired audio device"</string>
     <string name="help_label" msgid="3528360748637781274">"Help and feedback"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index f9d2f08..d9c35f5 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"This phone"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"This tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"This computer (internal)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"This TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dock speaker"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"External device"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Connected device"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connected via ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connected via eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV default"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI output"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Internal speakers"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Built-in speaker"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV audio"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off and back on"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired audio device"</string>
     <string name="help_label" msgid="3528360748637781274">"Help and feedback"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index a4834d8..191a8eb 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este teléfono"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Esta tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Esta computadora (interna)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Esta TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Bocina de la estación de carga"</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>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Se estableció conexión con un cable ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Se estableció conexión con un cable eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Configuración predeterminada de la TV"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Salida de HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Bocinas internas"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Bocina integrada"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio de la TV"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Error al establecer la conexión. Apaga el dispositivo y vuelve a encenderlo."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de audio con cable"</string>
     <string name="help_label" msgid="3528360748637781274">"Ayuda y comentarios"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 1792fd1..95ebbfa 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este teléfono"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Esta tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Este ordenador (interno)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Esta televisión"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Altavoz de la 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>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Conectado mediante ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Conectado mediante eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Predeterminada de la televisión"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Salida HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Altavoces internos"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Altavoz integrado"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio de la televisión"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"No se ha podido conectar; reinicia el dispositivo"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de audio con cable"</string>
     <string name="help_label" msgid="3528360748637781274">"Ayuda y comentarios"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 5411d0b..802c1e2 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"See telefon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"See tahvelarvuti"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"See arvuti (sisemine)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"See teler"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Doki kõlar"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Väline seade"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Ühendatud seade"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Ühendatud ARC-i kaudu"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Ühendatud eARC-i kaudu"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Teleri vaikeväljund"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-väljund"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Sisemised kõlarid"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Sisseehitatud kõlar"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Teleri heli"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Probleem ühendamisel. Lülitage seade välja ja uuesti sisse"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Juhtmega heliseade"</string>
     <string name="help_label" msgid="3528360748637781274">"Abi ja tagasiside"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 090d99d..16dd1f6 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -562,9 +562,9 @@
     <string name="save" msgid="3745809743277153149">"Gorde"</string>
     <string name="okay" msgid="949938843324579502">"Ados"</string>
     <string name="done" msgid="381184316122520313">"Eginda"</string>
-    <string name="alarms_and_reminders_label" msgid="6918395649731424294">"Alarmak eta abisuak"</string>
-    <string name="alarms_and_reminders_switch_title" msgid="4939393911531826222">"Eman alarmak eta abisuak ezartzeko baimena"</string>
-    <string name="alarms_and_reminders_title" msgid="8819933264635406032">"Alarmak eta abisuak"</string>
+    <string name="alarms_and_reminders_label" msgid="6918395649731424294">"Alarmak eta gogorarazpenak"</string>
+    <string name="alarms_and_reminders_switch_title" msgid="4939393911531826222">"Eman alarmak eta gogorarazpenak ezartzeko baimena"</string>
+    <string name="alarms_and_reminders_title" msgid="8819933264635406032">"Alarmak eta gogorarazpenak"</string>
     <string name="alarms_and_reminders_footer_title" msgid="6302587438389079695">"Eman alarmak ezartzeko eta denbora-muga duten ekintzak programatzeko baimena aplikazioari. Hala, aplikazioak atzeko planoan funtzionatuko du, eta litekeena da bateria gehiago kontsumitzea.\n\nBaimen hori ematen ez baduzu, ez dute funtzionatuko aplikazio honen bidez programatutako alarmek eta denbora-muga duten ekintzek."</string>
     <string name="keywords_alarms_and_reminders" msgid="6633360095891110611">"programazioa, alarma, gogorarazpena, erlojua"</string>
     <string name="zen_mode_do_not_disturb_name" msgid="6798711401734798283">"Ez molestatzeko"</string>
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Telefono hau"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tableta hau"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ordenagailu hau (barnekoa)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Telebista hau"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Oinarri bozgorailuduna"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Kanpoko gailua"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Konektatutako gailua"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC bidez konektatuta"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC bidez konektatuta"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Telebistaren audio-erreproduzigailu lehenetsia"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI irteera"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Barneko bozgorailuak"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Bozgorailu integratua"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Telebistako audioa"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Arazo bat izan da konektatzean. Itzali gailua eta pitz ezazu berriro."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Audio-gailu kableduna"</string>
     <string name="help_label" msgid="3528360748637781274">"Laguntza eta iritziak"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index c350175..85ef0f9 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"این تلفن"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"این رایانه لوحی"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"این رایانه (داخلی)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"این تلویزیون"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"بلندگوی پایه اتصال"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"دستگاه خارجی"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"دستگاه متصل"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"‏متصل ازطریق ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"‏متصل ازطریق eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"پیش‌فرض تلویزیون"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"‏خروجی HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"بلندگوهای داخلی"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"بلندگوی داخلی"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"صدای تلویزیون"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"مشکل در اتصال. دستگاه را خاموش و دوباره روشن کنید"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"دستگاه صوتی سیمی"</string>
     <string name="help_label" msgid="3528360748637781274">"راهنما و بازخورد"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 18a84b5..4b18d2f 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Tämä puhelin"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tämä tabletti"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Tämä tietokone (sisäinen)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Tämä TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Telinekaiutin"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Ulkoinen laite"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Yhdistetty laite"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Yhdistetty ARC:n kautta"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Yhdistetty eARC:n kautta"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV:n oletusasetus"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-toisto"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Sisäiset kaiuttimet"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Sisäänrakennettu kaiutin"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV:n audio"</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">"Ohjeet ja palaute"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 066244a..a1d1599 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ce téléphone"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Cette tablette"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Cet ordinateur (interne)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ce téléviseur"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Haut-parleur du socle"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Appareil externe"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Appareil connecté"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connecté par ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connecté par eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Sortie audio par défaut de la télévision"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Sortie HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Haut-parleurs internes"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Haut-parleur intégré"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Sortie audio du téléviseur"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problème de connexion. Éteignez et rallumez l\'appareil"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Appareil audio à câble"</string>
     <string name="help_label" msgid="3528360748637781274">"Aide et commentaires"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 84c2f15..f22f8bf 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ce téléphone"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Cette tablette"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Cet ordinateur (interne)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ce téléviseur"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Haut-parleur station d\'accueil"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Appareil externe"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Appareil connecté"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connecté via ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connecté via eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Sortie par défaut de la TV"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Sortie HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Haut-parleurs internes"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Haut-parleur intégré"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio TV"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problème de connexion. Éteignez l\'appareil, puis rallumez-le"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Appareil audio filaire"</string>
     <string name="help_label" msgid="3528360748637781274">"Aide et commentaires"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index d15b6e2..6a22a9b 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -239,10 +239,10 @@
     <string name="category_work" msgid="4014193632325996115">"Traballo"</string>
     <string name="category_private" msgid="4244892185452788977">"Privado"</string>
     <string name="category_clone" msgid="1554511758987195974">"Clonar"</string>
-    <string name="development_settings_title" msgid="140296922921597393">"Opcións para programadores"</string>
-    <string name="development_settings_enable" msgid="4285094651288242183">"Activar opcións para programadores"</string>
+    <string name="development_settings_title" msgid="140296922921597393">"Opcións de programación"</string>
+    <string name="development_settings_enable" msgid="4285094651288242183">"Activar opcións de programación"</string>
     <string name="development_settings_summary" msgid="8718917813868735095">"Definir as opcións de desenvolvemento de aplicacións"</string>
-    <string name="development_settings_not_available" msgid="355070198089140951">"As opcións para programadores non están dispoñibles para este usuario"</string>
+    <string name="development_settings_not_available" msgid="355070198089140951">"As opcións de programación non están dispoñibles para este usuario"</string>
     <string name="vpn_settings_not_available" msgid="2894137119965668920">"A configuración da VPN non está dispoñible para este usuario"</string>
     <string name="tethering_settings_not_available" msgid="266821736434699780">"A configuración da conexión compartida non está dispoñible para este usuario"</string>
     <string name="apn_settings_not_available" msgid="1147111671403342300">"A configuración do nome do punto de acceso non está dispoñible para este usuario"</string>
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este teléfono"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Esta tableta"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Este ordenador (interno)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Esta televisión"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Altofalante 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>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Conectado mediante ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Conectado mediante eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Opción predeterminada da televisión"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Saída HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Altofalantes internos"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Altofalante integrado"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio da televisión"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Produciuse un problema coa conexión. Apaga e acende o dispositivo."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de audio con cable"</string>
     <string name="help_label" msgid="3528360748637781274">"Axuda e comentarios"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 5fd187f..91518d3 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"આ ફોન"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"આ ટૅબ્લેટ"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"આ કમ્પ્યૂટર (આંતરિક)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"આ ટીવી"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ડૉક સ્પીકર"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"બહારનું ડિવાઇસ"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"કનેક્ટ કરેલું ડિવાઇસ"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC મારફતે કનેક્ટેડ"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC મારફતે કનેક્ટેડ"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"ડિવાઇસનું ડિફૉલ્ટ ઑડિયો આઉટપુટ, ટીવી"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI આઉટપુટ"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"આંતરિક સ્પીકર"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"બિલ્ટ-ઇન સ્પીકર"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"ટીવીનો ઑડિયો"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"કનેક્ટ કરવામાં સમસ્યા આવી રહી છે. ડિવાઇસને બંધ કરીને ફરી ચાલુ કરો"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"વાયરવાળો ઑડિયો ડિવાઇસ"</string>
     <string name="help_label" msgid="3528360748637781274">"સહાય અને પ્રતિસાદ"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index fa0716e..10a46225 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"यह फ़ोन"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"यह टैबलेट"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"यह कंप्यूटर (इंटरनल)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"यह टीवी"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"डॉक स्पीकर"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"बाहरी डिवाइस"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"कनेक्ट किया गया डिवाइस"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"एचडीएमआई eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC से कनेक्ट किए गए डिवाइस"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC से कनेक्ट किए गए डिवाइस"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"टीवी की डिफ़ॉल्ट सेटिंग"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"एचडीएमआई आउटपुट"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"इंटरनल स्पीकर"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"डिवाइस में पहले से मौजूद स्पीकर"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"टीवी ऑडियो"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"कनेक्ट करने में समस्या हो रही है. डिवाइस को बंद करके चालू करें"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"वायर वाला ऑडियो डिवाइस"</string>
     <string name="help_label" msgid="3528360748637781274">"सहायता और सुझाव"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 8ad5c9d..c3284ae 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ovaj telefon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ovaj tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ovo računalo (interno)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ovaj televizor"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Zvučnik priključne stanice"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Vanjski uređaj"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Povezani uređaj"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Povezano putem ARC-a"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Povezano putem eARC-a"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Zadano za TV"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI izlaz"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Unutarnji zvučnici"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ugrađeni zvučnik"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV zvuk"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem s povezivanjem. Isključite i ponovo uključite uređaj"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Žičani audiouređaj"</string>
     <string name="help_label" msgid="3528360748637781274">"Pomoć i povratne informacije"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 18c6ea3..dcf1610 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ez a telefon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ez a táblagép"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ez a számítógép (belső)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ez a TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dokkhangszóró"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Külső eszköz"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Csatlakoztatott eszköz"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Csatlakoztatva ARC-n keresztül"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Csatlakoztatva eARC-n keresztül"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Tévé alapértelmezett eszköze"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-kimenet"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Belső hangszórók"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Beépített hangszóró"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Tévés hangkimenet"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Sikertelen csatlakozás. Kapcsolja ki az eszközt, majd kapcsolja be újra."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Vezetékes audioeszköz"</string>
     <string name="help_label" msgid="3528360748637781274">"Súgó és visszajelzés"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 593479a..0b933a3 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Այս հեռախոսը"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Այս պլանշետը"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Այս համակարգիչը (ներքին)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Այս հեռուստացույցը"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Դոկ-կայանով բարձրախոս"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Արտաքին սարք"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Միացված սարք"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Միացված է ARC-ի միջոցով"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Միացված է eARC-ի միջոցով"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Հեռուստացույցի կանխադրված կարգավորումներ"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI ելք"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Ներքին բարձրախոսներ"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ներկառուցված բարձրախոս"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Հեռուստացույցի աուդիո"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Կապի խնդիր կա: Սարքն անջատեք և նորից միացրեք:"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Լարով աուդիո սարք"</string>
     <string name="help_label" msgid="3528360748637781274">"Օգնություն և հետադարձ կապ"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 6522e22..cd53507 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ponsel ini"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tablet ini"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Komputer ini (internal)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"TV ini"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Speaker dok"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Perangkat Eksternal"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Perangkat yang terhubung"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Terhubung melalui ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Terhubung melalui eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV sebagai default"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Output HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Speaker internal"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Speaker bawaan"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio TV"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ada masalah saat menghubungkan. Nonaktifkan perangkat &amp; aktifkan kembali"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Perangkat audio berkabel"</string>
     <string name="help_label" msgid="3528360748637781274">"Bantuan &amp; masukan"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 4a10fb3..ff4e38b 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Þessi sími"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Þessi spjaldtölva"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Þessi tölva (innbyggður)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Þetta sjónvarp"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Hátalaradokka"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Ytra tæki"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Tengt tæki"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Tengt með ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Tengt með eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Sjálfgefið í sjónvarpi"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-úttak"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Innri hátalarar"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Innbyggður hátalari"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Sjónvarpshljóð"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Vandamál í tengingu. Slökktu og kveiktu á tækinu"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Snúrutengt hljómtæki"</string>
     <string name="help_label" msgid="3528360748637781274">"Hjálp og ábendingar"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 21f78dd..183a799 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Questo smartphone"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Questo tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Questo computer (interno)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Questa TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Base con altoparlante"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositivo esterno"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositivo connesso"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"eARC HDMI"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Connessione tramite ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Connessione tramite eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV predefinita"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Uscita HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Altoparlanti interni"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Altoparlante integrato"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio della TV"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problema di connessione. Spegni e riaccendi il dispositivo"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo audio cablato"</string>
     <string name="help_label" msgid="3528360748637781274">"Guida e feedback"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 3b28bd2..c2b9cbc 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"הטלפון הזה"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"הטאבלט הזה"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"המחשב הזה (פנימי)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"הטלוויזיה הזו"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"רמקול של אביזר העגינה"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"מכשיר חיצוני"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"המכשיר המחובר"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"‏חיבור דרך ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"‏חיבור דרך eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"ברירת המחדל של הטלוויזיה"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"‏פלט HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"רמקולים פנימיים"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"רמקול מובנה"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"אודיו בטלוויזיה"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"יש בעיה בחיבור. עליך לכבות את המכשיר ולהפעיל אותו מחדש"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"התקן אודיו חוטי"</string>
     <string name="help_label" msgid="3528360748637781274">"עזרה ומשוב"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index a826810..081ab48 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"このデバイス"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"このタブレット"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"このパソコン(内蔵)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"このテレビ"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ホルダー スピーカー"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"外部デバイス"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"接続済みのデバイス"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC 経由で接続済み"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC 経由で接続済み"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"テレビのデフォルト"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI 出力"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"内蔵スピーカー"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"内蔵スピーカー"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV オーディオ"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"接続エラーです。デバイスを OFF にしてから ON に戻してください"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"有線オーディオ デバイス"</string>
     <string name="help_label" msgid="3528360748637781274">"ヘルプとフィードバック"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index f321371..0ca04a1 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"ეს ტელეფონი"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ამ ტაბლეტზე"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ეს კომპიუტერი (შიდა)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ეს ტელევიზორი"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"სამაგრის დინამიკი"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"გარე მოწყობილობა"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"დაკავშირებული მოწყობილობა"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"დაკავშირებულია ARC-ის მეშვეობით"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"დაკავშირებულია eARC-ის მეშვეობით"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"ტელევიზორის ნაგულისხმევი"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"გამომავალი HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"შიდა დინამიკები"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ჩაშენებული დინამიკი"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV Audio"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"დაკავშირებისას წარმოიქმნა პრობლემა. გამორთეთ და კვლავ ჩართეთ მოწყობილობა"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"სადენიანი აუდიო მოწყობილობა"</string>
     <string name="help_label" msgid="3528360748637781274">"დახმარება და გამოხმაურება"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 85b86d4..87917af 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Осы телефон"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Осы планшет"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Осы компьютер (ішкі)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Осы теледидар"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Динамигі бар қондыру станциясы"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Сыртқы құрылғы"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Жалғанған құрылғы"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC арқылы жалғанған."</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC арқылы жалғанған."</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Теледидардың әдепкі шығысы"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI шығыс интерфейсі"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Ішкі динамиктер"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ендірілген динамик"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Теледидардың аудио шығысы"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Байланыс орнату қатесі шығуып жатыр. Құрылғыны өшіріп, қайта қосыңыз."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Сымды аудио құрылғысы"</string>
     <string name="help_label" msgid="3528360748637781274">"Анықтама және пікір"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 12d35cf..2398ca8 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"ទូរសព្ទនេះ"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ថេប្លេតនេះ"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"កុំព្យូទ័រនេះ (ខាងក្នុង)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ទូរទស្សន៍​នេះ"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ឧបាល័រជើងទម្រ"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"ឧបករណ៍ខាងក្រៅ"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"​ឧបករណ៍ដែលបាន​ភ្ជាប់"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"បានភ្ជាប់តាមរយៈ ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"បានភ្ជាប់តាមរយៈ eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"លំនាំដើម​ទូរទស្សន៍"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"ធាតុចេញ HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ឧបករណ៍បំពងសំឡេងខាងក្នុង"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ឧបករណ៍បំពងសំឡេង​ភ្ជាប់មកជាមួយ"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"សំឡេងទូរទស្សន៍"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"មាន​បញ្ហា​ក្នុងការ​ភ្ជាប់។ បិទ រួច​បើក​ឧបករណ៍​វិញ"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ឧបករណ៍​សំឡេងប្រើខ្សែ"</string>
     <string name="help_label" msgid="3528360748637781274">"ជំនួយ និងមតិកែលម្អ"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index d95846f..690add8 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"ಈ ಫೋನ್"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ಈ ಟ್ಯಾಬ್ಲೆಟ್"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ಈ ಕಂಪ್ಯೂಟರ್ (ಆಂತರಿಕ)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ಈ ಟಿವಿ"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ಡಾಕ್ ಸ್ಪೀಕರ್"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"ಬಾಹ್ಯ ಸಾಧನ"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"ಕನೆಕ್ಟ್ ಮಾಡಿರುವ ಸಾಧನ"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC ಮೂಲಕ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC ಮೂಲಕ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"ಟಿವಿ ಡೀಫಾಲ್ಟ್"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI ಔಟ್‌‌ಪುಟ್"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ಆಂತರಿಕ ಸ್ಪೀಕರ್‌ಗಳು"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ಅಂತರ್ ನಿರ್ಮಿತ ಧ್ವನಿ ವರ್ಧಕ"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV ಆಡಿಯೊ"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ಕನೆಕ್ಟ್ ಮಾಡುವಾಗ ಸಮಸ್ಯೆ ಎದುರಾಗಿದೆ ಸಾಧನವನ್ನು ಆಫ್ ಮಾಡಿ ಹಾಗೂ ನಂತರ ಪುನಃ ಆನ್ ಮಾಡಿ"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ವೈರ್ ಹೊಂದಿರುವ ಆಡಿಯೋ ಸಾಧನ"</string>
     <string name="help_label" msgid="3528360748637781274">"ಸಹಾಯ ಮತ್ತು ಪ್ರತಿಕ್ರಿಯೆ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index f82a6a6..3f3fa54 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"이 휴대전화"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"이 태블릿"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"이 컴퓨터(내부)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"이 TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"도크 스피커"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"외부 기기"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"연결된 기기"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC를 통해 연결됨"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC를 통해 연결됨"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV 기본값"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI 출력"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"내부 스피커"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"내장 스피커"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV 오디오"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"연결 중에 문제가 발생했습니다. 기기를 껐다가 다시 켜 보세요."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"유선 오디오 기기"</string>
     <string name="help_label" msgid="3528360748637781274">"고객센터"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index ff63c19..db55252 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ушул телефон"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ушул планшет"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Бул компьютер (ички)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ушул сыналгы"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Док бекеттин динамиги"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Тышкы түзмөк"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Туташкан түзмөк"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC аркылуу туташты"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC аркылуу туташты"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Cыналгыдагы демейки түзмөк"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI аудио түзмөгү"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Ички динамиктер"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Алдын ала орнотулган динамик"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Сыналгы аудиосу"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Туташууда маселе келип чыкты. Түзмөктү өчүрүп, кайра күйгүзүп көрүңүз"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Зымдуу аудио түзмөк"</string>
     <string name="help_label" msgid="3528360748637781274">"Жардам/Пикир билдирүү"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 36d2ccf..f60894b 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"ໂທລະສັບນີ້"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ແທັບເລັດນີ້"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ຄອມພິວເຕີນີ້ (ພາຍໃນ)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ໂທລະທັດນີ້"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ແທ່ນວາງລຳໂພງ"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"ອຸປະກອນພາຍນອກ"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"ອຸປະກອນທີ່ເຊື່ອມຕໍ່"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ເຊື່ອມຕໍ່ຜ່ານ ARC ແລ້ວ"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"ເຊື່ອມຕໍ່ຜ່ານ eARC ແລ້ວ"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"ຄ່າເລີ່ມຕົ້ນສຳລັບໂທລະທັດ"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"ເອົ້າພຸດ HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ລຳໂພງຂອງເຄື່ອງ"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ລຳໂພງທີ່ມີໃນຕົວ"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"ສຽງນີ້"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ເກີດບັນຫາໃນການເຊື່ອມຕໍ່. ປິດອຸປະກອນແລ້ວເປີດກັບຄືນມາໃໝ່"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ອຸປະກອນສຽງແບບມີສາຍ"</string>
     <string name="help_label" msgid="3528360748637781274">"ຊ່ວຍເຫຼືອ ແລະ ຕິຊົມ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 5b20db3..2932d21 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Šis telefonas"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Šis planšetinis kompiuteris"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Šis kompiuteris (vidinis)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Šis televizorius"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Doko garsiakalbis"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Išorinis įrenginys"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Prijungtas įrenginys"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI „eARC“"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Prisijungta per ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Prisijungta per „eARC“"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV numatytoji išvestis"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI išvestis"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Vidiniai garsiakalbiai"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Įtaisytas garsiakalbis"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV garso įrašas"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Prisijungiant kilo problema. Išjunkite įrenginį ir vėl jį įjunkite"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Laidinis garso įrenginys"</string>
     <string name="help_label" msgid="3528360748637781274">"Pagalba ir atsiliepimai"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 620318a..2f497fb 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Šis tālrunis"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Šis planšetdators"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Šī datora iekšējais skaļrunis"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Šis televizors"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Doka skaļrunis"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Ārēja ierīce"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Pievienotā ierīce"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Savienojums izveidots, izmantojot ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Savienojums izveidots, izmantojot eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Noklusējuma iestatījums televizorā"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI izvade"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Iekšējie skaļruņi"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Iebūvēts skaļrunis"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Televizora audio"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Radās problēma ar savienojuma izveidi. Izslēdziet un atkal ieslēdziet ierīci."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Vadu audioierīce"</string>
     <string name="help_label" msgid="3528360748637781274">"Palīdzība un atsauksmes"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 0331ece..54d1ff1 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -359,7 +359,7 @@
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Овозможи апликација на терминал што овозможува локален пристап кон школка."</string>
     <string name="enable_linux_terminal_title" msgid="5076044866895670637">"Програмерска околина на Linux"</string>
     <string name="enable_linux_terminal_summary" msgid="2029479880888108902">"(Експериментално) Вклучете го Linux-терминалот на Android"</string>
-    <string name="disable_linux_terminal_disclaimer" msgid="3054320531778388231">"Ако оневозможите, податоците за Linux-терминалот ќе се избришат"</string>
+    <string name="disable_linux_terminal_disclaimer" msgid="3054320531778388231">"Ако го оневозможите, податоците за Linux-терминалот ќе се избришат"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"Проверка со HDCP"</string>
     <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"Постави однесување на проверка на HDCP"</string>
     <string name="debug_debugging_category" msgid="535341063709248842">"Отстранување грешки"</string>
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Овој телефон"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Овој таблет"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Овој компјуер (внатрешен)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Овој телевизор"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Док со звучник"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Надворешен уред"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Поврзан уред"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Поврзано преку ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Поврзано преку eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Стандарден излез на телевизор"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Излез за HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Внатрешни звучници"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Вграден звучник"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Аудио на телевизор"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Проблем со поврзување. Исклучете го уредот и повторно вклучете го"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Жичен аудиоуред"</string>
     <string name="help_label" msgid="3528360748637781274">"Помош и повратни информации"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index aaebe4c..19c380b 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"ഈ ഫോൺ"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ഈ ടാബ്‌ലെറ്റ്"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ഈ കമ്പ്യൂട്ടർ (ഇന്റേണൽ)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ഈ ടിവി"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ഡോക്ക് സ്‌പീക്കർ"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"ബാഹ്യ ഉപകരണം"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"കണക്‌റ്റ് ചെയ്‌ത ഉപകരണം"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC വഴി കണക്റ്റ് ചെയ്തിരിക്കുന്നു"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC വഴി കണക്റ്റ് ചെയ്തിരിക്കുന്നു"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"ടിവി ഡിഫോൾട്ട്"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI ഔട്ട്പുട്ട്"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ആന്തരിക സ്‌പീക്കറുകൾ"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ബിൽട്ട്-ഇൻ സ്പീക്കർ"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"ടിവി ഓഡിയോ"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"കണക്‌റ്റ് ചെയ്യുന്നതിൽ പ്രശ്‌നമുണ്ടായി. ഉപകരണം ഓഫാക്കി വീണ്ടും ഓണാക്കുക"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"വയർ മുഖേന ബന്ധിപ്പിച്ച ഓഡിയോ ഉപകരണം"</string>
     <string name="help_label" msgid="3528360748637781274">"സഹായവും ഫീഡ്‌ബാക്കും"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index fb2b729..0e3408e 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Энэ утас"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Энэ таблет"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Энэ компьютер (дотоод)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Энэ ТВ"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Суурилуулагчийн чанга яригч"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Гадаад төхөөрөмж"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Холбогдсон төхөөрөмж"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC-р холбогдсон"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC-р холбогдсон"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"ТВ-ийн өгөгдмөл"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI гаралт"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Дотоод чанга яригч"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Бүрэлдэхүүн чанга яригч"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"ТВ-ийн аудио"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Холбогдоход асуудал гарлаа. Төхөөрөмжийг унтраагаад дахин асаана уу"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Утастай аудио төхөөрөмж"</string>
     <string name="help_label" msgid="3528360748637781274">"Тусламж, санал хүсэлт"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 023a2e4..9b7c99c 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"हा फोन"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"हा टॅबलेट"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"हा काँप्युटर (अंतर्गत)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"हा टीव्ही"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"डॉक स्पीकर"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"बाह्य डिव्हाइस"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"कनेक्ट केलेले डिव्हाइस"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC द्वारे कनेक्ट केलेली"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC द्वारे कनेक्ट केलेली"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"टीव्ही डीफॉल्ट"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI आउटपुट"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"अंतर्गत स्पीकर"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"बिल्ट-इन स्पीकर"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"टीव्ही ऑडिओ"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"कनेक्‍ट करण्‍यात समस्‍या आली. डिव्हाइस बंद करा आणि नंतर सुरू करा"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"वायर असलेले ऑडिओ डिव्हाइस"</string>
     <string name="help_label" msgid="3528360748637781274">"मदत आणि फीडबॅक"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 4212eb4..7c5d3f7 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Telefon ini"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tablet ini"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Komputer ini (dalaman)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"TV ini"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Pembesar suara dok"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Peranti Luar"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Peranti yang disambungkan"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Disambungkan melalui ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Disambungkan melalui eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Tetapan lalai TV"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Output HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Pembesar suara dalaman"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Pembesar suara terbina dalam"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio TV"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Masalah penyambungan. Matikan &amp; hidupkan kembali peranti"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Peranti audio berwayar"</string>
     <string name="help_label" msgid="3528360748637781274">"Bantuan &amp; maklum balas"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index df13e6c..0bd6ca6 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"ဤဖုန်း"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ဤတက်ဘလက်"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ဤကွန်ပျူတာ (စက်တွင်း)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ဤ TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"အထိုင် စပီကာ"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"ပြင်ပစက်"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"ချိတ်ဆက်ကိရိယာ"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC မှတစ်ဆင့် ချိတ်ဆက်ထားသည်"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC မှတစ်ဆင့် ချိတ်ဆက်ထားသည်"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV မူရင်း"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI အထွက်"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"စက်ရှိ စပီကာများ"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"မူလပါရှိသည့် စပီကာ"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV အသံ"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ချိတ်ဆက်ရာတွင် ပြဿနာရှိပါသည်။ စက်ကိုပိတ်ပြီး ပြန်ဖွင့်ပါ"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ကြိုးတပ် အသံစက်ပစ္စည်း"</string>
     <string name="help_label" msgid="3528360748637781274">"အကူအညီနှင့် အကြံပြုချက်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 8126663..ab14606 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -499,7 +499,7 @@
     <string name="power_remaining_only_more_than_subtext" msgid="4873750633368888062">"Mer enn <xliff:g id="TIME_REMAINING">%1$s</xliff:g> gjenstår"</string>
     <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"Fulladet om <xliff:g id="TIME">%1$s</xliff:g>"</string>
-    <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – Fulladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
+    <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – fulladet om <xliff:g id="TIME">%2$s</xliff:g>"</string>
     <string name="power_charging_limited" msgid="4144004473976005214">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladingen er satt på vent for å beskytte batteriet"</string>
     <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> – lader"</string>
     <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATUS">%2$s</xliff:g> – Fulladet innen <xliff:g id="TIME">%3$s</xliff:g>"</string>
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Denne telefonen"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Dette nettbrettet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Denne datamaskinen (intern)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Denne TV-en"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dokkhøyttaler"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Ekstern enhet"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Tilkoblet enhet"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Tilkoblet via ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Tilkoblet via eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV-ens standardinnstilling"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-utgang"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interne høyttalere"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Innebygd høyttaler"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV-lyd"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Tilkoblingsproblemer. Slå enheten av og på igjen"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Lydenhet med kabel"</string>
     <string name="help_label" msgid="3528360748637781274">"Hjelp og tilbakemelding"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index a233622..50665f6 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"यो फोन"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"यो ट्याब्लेट"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"यो कम्प्युटर (आन्तरिक)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"यो टिभी"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"डक स्पिकर"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"बाह्य डिभाइस"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"कनेक्ट गरिएको डिभाइस"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC मार्फत कनेक्ट गरिएका"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC मार्फत कनेक्ट गरिएका"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"टिभीको डिफल्ट अडियो आउटपुट"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI आउटपुट"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"आन्तरिक स्पिकरहरू"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"अन्तर्निर्मित स्पिकर"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"टिभीको अडियो"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"जोड्ने क्रममा समस्या भयो। यन्त्रलाई निष्क्रिय पारेर फेरि अन गर्नुहोस्"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"तारयुक्त अडियो यन्त्र"</string>
     <string name="help_label" msgid="3528360748637781274">"मद्दत र प्रतिक्रिया"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 551097d..035c2d9 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Deze telefoon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Deze tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Deze computer (intern)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Deze tv"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dockspeaker"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Extern apparaat"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Verbonden apparaat"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Verbonden via ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Verbonden via eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Tv-standaard"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-uitvoer"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interne speakers"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ingebouwde speaker"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Tv-audio"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Probleem bij verbinding maken. Zet het apparaat uit en weer aan."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Bedraad audioapparaat"</string>
     <string name="help_label" msgid="3528360748637781274">"Hulp en feedback"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 790acff..0d27f00 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"ଏହି ଫୋନ"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ଏହି ଟାବଲେଟ"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ଏହି କମ୍ପ୍ୟୁଟର (ଇଣ୍ଟର୍ନଲ)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ଏହି ଟିଭି"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ଡକ ସ୍ପିକର"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"ଏକ୍ସଟର୍ନଲ ଡିଭାଇସ"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"କନେକ୍ଟ କରାଯାଇଥିବା ଡିଭାଇସ"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC ମାଧ୍ୟମରେ କନେକ୍ଟ କରାଯାଇଛି"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC ମାଧ୍ୟମରେ କନେକ୍ଟ କରାଯାଇଛି"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"ଟିଭିର ଡିଫଲ୍ଟ ଅଡିଓ ଆଉଟପୁଟ"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI ଆଉଟପୁଟ"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ଇଣ୍ଟର୍ନଲ ସ୍ପିକର"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ବିଲ୍ଟ-ଇନ ସ୍ପିକର"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"ଟିଭି ଅଡିଓ"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ସଂଯୋଗ କରିବାରେ ସମସ୍ୟା ହେଉଛି। ଡିଭାଇସ୍ ବନ୍ଦ କରି ପୁଣି ଚାଲୁ କରନ୍ତୁ"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ତାରଯୁକ୍ତ ଅଡିଓ ଡିଭାଇସ୍"</string>
     <string name="help_label" msgid="3528360748637781274">"ସାହାଯ୍ୟ ଓ ମତାମତ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index c20e0d8..14e87aa 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"ਇਹ ਫ਼ੋਨ"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ਇਹ ਟੈਬਲੈੱਟ"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ਇਸ ਕੰਪਿਊਟਰ \'ਤੇ (ਅੰਦਰੂਨੀ)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ਇਹ ਟੀਵੀ"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ਡੌਕ ਸਪੀਕਰ"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"ਬਾਹਰੀ ਡੀਵਾਈਸ"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"ਕਨੈਕਟ ਕੀਤਾ ਡੀਵਾਈਸ"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC ਰਾਹੀਂ ਕਨੈਕਟ ਕੀਤੇ ਗਏ ਡੀਵਾਈਸ"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC ਰਾਹੀਂ ਕਨੈਕਟ ਕੀਤੇ ਗਏ ਡੀਵਾਈਸ"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"ਟੀਵੀ ਦਾ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਆਊਟਪੁੱਟ"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI ਆਊਟਪੁੱਟ"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ਅੰਦਰੂਨੀ ਸਪੀਕਰ"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ਬਿਲਟ-ਇਨ ਸਪੀਕਰ"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"ਟੀਵੀ ਆਡੀਓ"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ਕਨੈਕਟ ਕਰਨ ਵਿੱਚ ਸਮੱਸਿਆ ਆਈ। ਡੀਵਾਈਸ ਨੂੰ ਬੰਦ ਕਰਕੇ ਵਾਪਸ ਚਾਲੂ ਕਰੋ"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ਤਾਰ ਵਾਲਾ ਆਡੀਓ ਡੀਵਾਈਸ"</string>
     <string name="help_label" msgid="3528360748637781274">"ਮਦਦ ਅਤੇ ਵਿਚਾਰ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index f6ab8a7..eaa12bd 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ten telefon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ten tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ten komputer (wewnętrzny)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ten telewizor"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Głośnik ze stacją dokującą"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Urządzenie zewnętrzne"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Połączone urządzenie"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Połączono przez ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Połączono przez eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Ustawienie domyślne telewizora"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Wyjście HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Głośniki wewnętrzne"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Wbudowany głośnik"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Telewizyjne urządzenie audio"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem z połączeniem. Wyłącz i ponownie włącz urządzenie"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Przewodowe urządzenie audio"</string>
     <string name="help_label" msgid="3528360748637781274">"Pomoc i opinie"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index dedc65c..afa7129 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este telefone"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Este tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Este computador (interno)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Esta TV"</string>
     <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>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Conectado via ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Conectado via eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Padrão da TV"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Saída HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Alto-falantes internos"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Alto-falante integrado"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Áudio da TV"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ocorreu um problema na conexão. Desligue o dispositivo e ligue-o novamente"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de áudio com fio"</string>
     <string name="help_label" msgid="3528360748637781274">"Ajuda e feedback"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index f1ac88b..986467a 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este telemóvel"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Este tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Este computador (interno)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Esta TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Altifalante estação carregamento"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositivo externo"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositivo associado"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Ligado através de ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Ligado através de eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Predefinição da TV"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Saída HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Altifalantes internos"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Altifalante integrado"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Áudio da TV"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problema ao ligar. Desligue e volte a ligar o dispositivo."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de áudio com fios"</string>
     <string name="help_label" msgid="3528360748637781274">"Ajuda e feedback"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index dedc65c..afa7129 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este telefone"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Este tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Este computador (interno)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Esta TV"</string>
     <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>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Conectado via ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Conectado via eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Padrão da TV"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Saída HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Alto-falantes internos"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Alto-falante integrado"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Áudio da TV"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ocorreu um problema na conexão. Desligue o dispositivo e ligue-o novamente"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de áudio com fio"</string>
     <string name="help_label" msgid="3528360748637781274">"Ajuda e feedback"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index cafb788..b64047c 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Acest telefon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Această tabletă"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Acest computer (intern)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Acest televizor"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Difuzorul dispozitivului de andocare"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispozitiv extern"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispozitiv conectat"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Conectat prin ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Conectat prin eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Prestabilit pentru televizor"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Ieșire HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Difuzoare interne"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Difuzor încorporat"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio TV"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problemă la conectare. Oprește și repornește dispozitivul."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispozitiv audio cu fir"</string>
     <string name="help_label" msgid="3528360748637781274">"Ajutor și feedback"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 951f3a0..8c9ecb1 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Этот смартфон"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Этот планшет"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Встроенное"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Этот телевизор"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Колонка с док-станцией"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Внешнее устройство"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Подключенное устройство"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Подключено через ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Подключено через eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Стандартный выход на телевизоре"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Выход HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Внутренние динамики"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Встроенный динамик"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Аудиовыход телевизора"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ошибка подключения. Выключите и снова включите устройство."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Проводное аудиоустройство"</string>
     <string name="help_label" msgid="3528360748637781274">"Справка/отзыв"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 8427064..6c9abfb 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"මෙම දුරකථනය"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"මෙම ටැබ්ලටය"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"මෙම පරිගණකය (අභ්‍යන්තර)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"මෙම රූපවාහිනිය"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ඩොක් ස්පීකරය"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"බාහිර උපාංගය"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"සම්බන්ධ කළ උපාංගය"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC හරහා සම්බන්ධ කර ඇත"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC හරහා සම්බන්ධ කර ඇත"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"රූපවාහිනී පෙරනිමිය"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI ප්‍රතිදානය"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"අභ්‍යන්තර ස්පීකර්"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"එකට තැනූ ශබ්දවාහිනීය"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"රූපවාහිනී ශ්‍රව්‍ය"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"සම්බන්ධ කිරීමේ ගැටලුවකි උපාංගය ක්‍රියාවිරහිත කර &amp; ආපසු ක්‍රියාත්මක කරන්න"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"රැහැන්ගත කළ ඕඩියෝ උපාංගය"</string>
     <string name="help_label" msgid="3528360748637781274">"උදවු &amp; ප්‍රතිපෝෂණ"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index aca07e5..05c7442 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Tento telefón"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tento tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Tento počítač (interný)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Tento televízor"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Reproduktor doku"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Externé zariadenie"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Pripojené zariadenie"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Pripojené prostredníctvom rozhrania ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Pripojené prostredníctvom rozhrania eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Predvolené nastavenie televízora"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Výstup HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interné reproduktory"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Vstavaný reproduktor"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Zvuk televízora"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Pri pripájaní sa vyskytol problém. Zariadenie vypnite a znova zapnite."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Audio zariadenie s káblom"</string>
     <string name="help_label" msgid="3528360748637781274">"Pomocník a spätná väzba"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 00d93a6..c0e4f81 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ta telefon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ta tablični računalnik"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ta računalnik (notranji)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ta televizor"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Zvočnik nosilca"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Zunanja naprava"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Povezana naprava"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Povezano prek zvočnega kanala ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Povezano prek zvočnega kanala eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Privzeti izhod televizorja"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Izhod HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Notranji zvočniki"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Vgrajen zvočnik"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Zvok televizorja"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Težava pri povezovanju. Napravo izklopite in znova vklopite."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Žična zvočna naprava"</string>
     <string name="help_label" msgid="3528360748637781274">"Pomoč in povratne informacije"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index c907e22..504d821 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -586,7 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ky telefon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ky tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ky kompjuter (i brendshëm)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
+    <!-- no translation found for media_transfer_this_device_name_tv (8508713779441163887) -->
     <skip />
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Altoparlanti i stacionit"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Pajisja e jashtme"</string>
@@ -607,9 +607,10 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Lidhur përmes ARC-së"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Lidhur përmes eARC-së"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Parazgjedhja e televizorit"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Dalja HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Altoparlantët e brendshëm"</string>
+    <!-- no translation found for tv_media_transfer_internal_speakers (4662765121700213785) -->
+    <skip />
+    <!-- no translation found for tv_media_transfer_hdmi_title (6715658310934507444) -->
+    <skip />
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem me lidhjen. Fike dhe ndize përsëri pajisjen"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Pajisja audio me tel"</string>
     <string name="help_label" msgid="3528360748637781274">"Ndihma dhe komentet"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 65000e4..8fce0a0 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Овај телефон"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Овај таблет"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Овај рачунар (интерно)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Овај ТВ"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Звучник базне станице"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Спољни уређај"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Повезани уређај"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Повезано преко ARC-а"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Повезано преко eARC-а"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Подразумевана вредност за ТВ"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI излаз"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Унутрашњи звучници"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Уграђени звучник"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Звук ТВ-а"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Проблем при повезивању. Искључите уређај, па га поново укључите"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Жичани аудио уређај"</string>
     <string name="help_label" msgid="3528360748637781274">"Помоћ и повратне информације"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 12c1729..c7ce0f8 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Den här telefonen"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Den här surfplattan"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Den här datorn (intern)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Den här tv:n"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dockningsstationens högtalare"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Extern enhet"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Ansluten enhet"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Ansluten via ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Ansluten via eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Standardutgång för tv:n"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI-utgång"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Interna högtalare"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Inbyggd högtalare"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Tv-ljud"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Det gick inte att ansluta. Stäng av enheten och slå på den igen"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Ljudenhet med kabelanslutning"</string>
     <string name="help_label" msgid="3528360748637781274">"Hjälp och feedback"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 1bf5677..3bee28a 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Simu hii"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Kishikwambi hiki"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Kompyuta hii (spika ya ndani)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Televisheni hii"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Spika ya kituo"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Kifaa cha Nje"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Kifaa kilichounganishwa"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Imeunganishwa kupitia ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Imeunganishwa kupitia eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Mipangilio Chaguomsingi ya TV"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Kutoa sauti kupitia HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Spika za ndani"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Spika iliyojumuishwa ndani"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Sauti ya Televisheni"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Kuna tatizo la kuunganisha kwenye Intaneti. Zima kisha uwashe kifaa"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Kifaa cha sauti kinachotumia waya"</string>
     <string name="help_label" msgid="3528360748637781274">"Usaidizi na maoni"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 8ee8fa2..289947e 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"இந்த மொபைல்"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"இந்த டேப்லெட்"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"இந்தக் கம்ப்யூட்டர் (அகம்)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"இந்த டிவி"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"டாக் ஸ்பீக்கர்"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"வெளிப்புறச் சாதனம்"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"இணைக்கப்பட்டுள்ள சாதனம்"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC மூலம் இணைக்கப்பட்டுள்ளவை"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC மூலம் இணைக்கப்பட்டுள்ளவை"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"டிவி இயல்புநிலை"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI அவுட்புட்"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"உட்புற ஸ்பீக்கர்கள்"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"உள்ளமைந்த ஸ்பீக்கர்"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"டிவி ஆடியோ"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"இணைப்பதில் சிக்கல். சாதனத்தை ஆஃப் செய்து மீண்டும் ஆன் செய்யவும்"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"வயருடன்கூடிய ஆடியோ சாதனம்"</string>
     <string name="help_label" msgid="3528360748637781274">"உதவியும் கருத்தும்"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 46350d5..d6683bb 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"ఈ ఫోన్"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ఈ టాబ్లెట్"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ఈ కంప్యూటర్ (ఇంటర్నల్)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ఈ టీవీ"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"డాక్ స్పీకర్"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"ఎక్స్‌టర్నల్ పరికరం"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"కనెక్ట్ చేసిన పరికరం"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC ద్వారా కనెక్ట్ చేయబడింది"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC ద్వారా కనెక్ట్ చేయబడింది"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"టీవీ ఆటోమేటిక్ సెట్టింగ్"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI అవుట్‌పుట్"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"అంతర్గత స్పీకర్‌లు"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"బిల్ట్-ఇన్ స్పీకర్"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"టీవీ ఆడియో"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"కనెక్ట్ చేయడంలో సమస్య ఉంది. పరికరాన్ని ఆఫ్ చేసి, ఆపై తిరిగి ఆన్ చేయండి"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"వైర్ గల ఆడియో పరికరం"</string>
     <string name="help_label" msgid="3528360748637781274">"సహాయం &amp; ఫీడ్‌బ్యాక్"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 460782a..6095c50 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -304,7 +304,7 @@
     <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"เลือกเวอร์ชันของบลูทูธ AVRCP"</string>
     <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"เวอร์ชันของบลูทูธ MAP"</string>
     <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"เลือกเวอร์ชันของบลูทูธ MAP"</string>
-    <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"ตัวแปลงสัญญาณเสียงบลูทูธ"</string>
+    <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"ตัวแปลงรหัสเสียงบลูทูธ"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"ทริกเกอร์การเลือกตัวแปลงรหัส\nเสียงบลูทูธ"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"อัตราตัวอย่างเสียงบลูทูธ"</string>
     <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"ทริกเกอร์การเลือกตัวแปลงรหัส\nเสียงบลูทูธ: อัตราตัวอย่าง"</string>
@@ -313,7 +313,7 @@
     <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"ทริกเกอร์การเลือกตัวแปลงรหัส\nเสียงบลูทูธ: บิตต่อตัวอย่าง"</string>
     <string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"โหมดช่องสัญญาณเสียงบลูทูธ"</string>
     <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="2076949781460359589">"ทริกเกอร์การเลือกตัวแปลงรหัส\nเสียงบลูทูธ: โหมดช่องสัญญาณ"</string>
-    <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"ตัวแปลงสัญญาณเสียงบลูทูธที่ใช้ LDAC: คุณภาพการเล่น"</string>
+    <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"ตัวแปลงรหัสเสียงบลูทูธที่ใช้ LDAC: คุณภาพการเล่น"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="7274396574659784285">"ทริกเกอร์การเลือกตัวแปลงรหัส LDAC\nเสียงบลูทูธ: คุณภาพการเล่น"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="2040810756832027227">"สตรีมมิง: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
     <string name="select_private_dns_configuration_title" msgid="7887550926056143018">"DNS ส่วนตัว"</string>
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"โทรศัพท์เครื่องนี้"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"แท็บเล็ตเครื่องนี้"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"คอมพิวเตอร์เครื่องนี้ (ภายใน)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"ทีวีเครื่องนี้"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"แท่นชาร์จที่มีลำโพง"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"อุปกรณ์ภายนอก"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"อุปกรณ์ที่เชื่อมต่อ"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"เชื่อมต่อผ่าน ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"เชื่อมต่อผ่าน eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"ค่าเริ่มต้นของทีวี"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"เอาต์พุต HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"ลำโพงของเครื่อง"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"ลำโพงในตัว"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"เสียงจากทีวี"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"เกิดปัญหาในการเชื่อมต่อ ปิดอุปกรณ์แล้วเปิดใหม่อีกครั้ง"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"อุปกรณ์เสียงแบบมีสาย"</string>
     <string name="help_label" msgid="3528360748637781274">"ความช่วยเหลือและความคิดเห็น"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 986b7771..4eb5c1e 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ang teleponong ito"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ang tablet na ito"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Sa computer na ito (internal)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Ang TV na ito"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Speaker ng dock"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"External na Device"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Nakakonektang device"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Nakakonekta sa pamamagitan ng ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Nakakonekta sa pamamagitan ng eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Default ng TV"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI output"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Mga internal speaker"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Built-in na speaker"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Audio ng TV"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Nagkaproblema sa pagkonekta. I-off at pagkatapos ay i-on ang device"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired na audio device"</string>
     <string name="help_label" msgid="3528360748637781274">"Tulong at feedback"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index df47dac..705b714 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Bu telefon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Bu tablet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Bu bilgisayar (dahili)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Bu TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Yuva hoparlörü"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Harici Cihaz"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Bağlı cihaz"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC ile bağlandı"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC ile bağlandı"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"TV varsayılanı"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI çıkışı"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Dahili hoparlörler"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Dahili hoparlör"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV Sesi"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Bağlanırken sorun oluştu. Cihazı kapatıp tekrar açın"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Kablolu ses cihazı"</string>
     <string name="help_label" msgid="3528360748637781274">"Yardım ve geri bildirim"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 3ba99a2..dda905e 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Цей телефон"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Цей планшет"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Цей комп’ютер (внутрішній)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Цей телевізор"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Динамік док-станції"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Зовнішній пристрій"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Підключений пристрій"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Підключено через ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Підключено через eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Стандартний вихід телевізора"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Вихід HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Внутрішні динаміки"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Вбудований динамік"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Аудіо з телевізора"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Не вдається підключитися. Перезавантажте пристрій."</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Дротовий аудіопристрій"</string>
     <string name="help_label" msgid="3528360748637781274">"Довідка й відгуки"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index e5e29fa..204663d 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"یہ فون"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"یہ ٹیبلیٹ"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"یہ کمپیوٹر (داخلی)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"‏یہ TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"ڈاک اسپیکر"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"بیرونی آلہ"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"منسلک آلہ"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"‏ARC کے ذریعے منسلک ہے"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"‏eARC کے ذریعے منسلک ہے"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"‏TV ڈیفالٹ"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"‏HDMI آؤٹ پٹ"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"اندرونی اسپیکرز"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"پہلے سے شامل اسپیکر"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"‏‫TV آڈیو"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"منسلک کرنے میں مسئلہ پیش آ گیا۔ آلہ کو آف اور بیک آن کریں"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"وائرڈ آڈیو آلہ"</string>
     <string name="help_label" msgid="3528360748637781274">"مدد اور تاثرات"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 2417d55..a7500a3 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Shu telefon"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Shu planshet"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Bu kompyuter (ichki)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Shu TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Dok-stansiyali karnay"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Tashqi qurilma"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Ulangan qurilma"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"ARC orqali ulangan"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"eARC orqali ulangan"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Televizorda birlamchi"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI chiqishi"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Ichki karnaylar"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Ichki karnay"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"TV Audio"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ulanishda muammo yuz berdi. Qurilmani oʻchiring va yoqing"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Simli audio qurilma"</string>
     <string name="help_label" msgid="3528360748637781274">"Yordam/fikr-mulohaza"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 0239a3f..5d620e6 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Điện thoại này"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Máy tính bảng này"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Máy tính này (nội bộ)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"TV này"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Loa có gắn đế"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Thiết bị bên ngoài"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Thiết bị đã kết nối"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Đã kết nối qua ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Đã kết nối qua eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"Chế độ mặc định của TV"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"Đầu ra HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Các loa trong"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Loa tích hợp"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Âm thanh TV"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Sự cố kết nối. Hãy tắt thiết bị rồi bật lại"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Thiết bị âm thanh có dây"</string>
     <string name="help_label" msgid="3528360748637781274">"Trợ giúp và phản hồi"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 22bfda4..7248999 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -359,7 +359,7 @@
     <string name="enable_terminal_summary" msgid="2481074834856064500">"启用终端应用,以便在本地访问 Shell"</string>
     <string name="enable_linux_terminal_title" msgid="5076044866895670637">"Linux 开发环境"</string>
     <string name="enable_linux_terminal_summary" msgid="2029479880888108902">"(实验性)在 Android 上运行 Linux 终端"</string>
-    <string name="disable_linux_terminal_disclaimer" msgid="3054320531778388231">"如果您停用它,Linux 终端数据会被清除"</string>
+    <string name="disable_linux_terminal_disclaimer" msgid="3054320531778388231">"停用后,Linux 终端数据会被清除"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP 检查"</string>
     <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"设置 HDCP 检查行为"</string>
     <string name="debug_debugging_category" msgid="535341063709248842">"调试"</string>
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"这部手机"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"这部平板电脑"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"此计算机(内部)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"此电视"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"基座音箱"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"外部设备"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"连接的设备"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"已通过 ARC 连接"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"已通过 eARC 连接"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"电视默认设置"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI 输出"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"内置扬声器"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"内置扬声器"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"电视音频"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"连接时遇到问题。请关闭并重新开启设备"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"有线音频设备"</string>
     <string name="help_label" msgid="3528360748637781274">"帮助和反馈"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 594273a..6a8d6d5 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"此手機"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"此平板電腦"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"此電腦 (內置)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"這部電視"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"插座喇叭"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"外部裝置"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"已連接的裝置"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"已透過 ARC 連接"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"已透過 eARC 連接"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"電視預設的音訊輸出"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI 輸出"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"內置喇叭"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"內置喇叭"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"電視音訊"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"無法連接,請關閉裝置然後重新開機"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"有線音響裝置"</string>
     <string name="help_label" msgid="3528360748637781274">"說明與意見反映"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 483dbf3..c5eb0bb 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"這支手機"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"這台平板電腦"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"這部電腦 (內部)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"這部電視"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"座架喇叭"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"外部裝置"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"已連結的裝置"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"透過 ARC 連線"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"透過 eARC 連線"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"電視預設的音訊輸出裝置"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"HDMI 輸出裝置"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"內建揚聲器"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"內建喇叭"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"電視音訊"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"無法連線,請關閉裝置後再重新開啟"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"有線音訊裝置"</string>
     <string name="help_label" msgid="3528360748637781274">"說明與意見回饋"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 2bb3f22..f467a3a 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -586,8 +586,7 @@
     <string name="media_transfer_this_device_name" msgid="2357329267148436433">"Le foni"</string>
     <string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Le thebhulethi"</string>
     <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Le khompyutha (ngaphakathi)"</string>
-    <!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
-    <skip />
+    <string name="media_transfer_this_device_name_tv" msgid="8508713779441163887">"Le TV"</string>
     <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Isipikha sentuba"</string>
     <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Idivayisi Yangaphandle"</string>
     <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Idivayisi exhunyiwe"</string>
@@ -607,9 +606,8 @@
     <string name="tv_media_transfer_earc_fallback_title" msgid="3098685494578519940">"I-HDMI eARC"</string>
     <string name="tv_media_transfer_arc_subtitle" msgid="1040017851325069082">"Ixhunywe nge-ARC"</string>
     <string name="tv_media_transfer_earc_subtitle" msgid="645191413103303077">"Ixhunywe nge-eARC"</string>
-    <string name="tv_media_transfer_default" msgid="5403053145185843843">"I-TV ezenzakalelayo"</string>
-    <string name="tv_media_transfer_hdmi" msgid="692569220956829921">"umphumela we-HDMI"</string>
-    <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Izipikha zangaphakathi"</string>
+    <string name="tv_media_transfer_internal_speakers" msgid="4662765121700213785">"Isipikha esakhelwe ngaphakathi"</string>
+    <string name="tv_media_transfer_hdmi_title" msgid="6715658310934507444">"Umsondo weTV"</string>
     <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Inkinga yokuxhumeka. Vala idivayisi futhi uphinde uyivule"</string>
     <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Idivayisi yomsindo enentambo"</string>
     <string name="help_label" msgid="3528360748637781274">"Usizo nempendulo"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f03014c..eaf155d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1419,7 +1419,7 @@
     <!-- Name of the internal speaker and mic. [CHAR LIMIT=30] -->
     <string name="media_transfer_this_device_name_desktop">This computer (internal)</string>
     <!-- Name of the default media output of the TV. [CHAR LIMIT=30] -->
-    <string name="media_transfer_this_device_name_tv">@string/tv_media_transfer_default</string>
+    <string name="media_transfer_this_device_name_tv">This TV</string>
     <!-- Name of the dock device. [CHAR LIMIT=30] -->
     <string name="media_transfer_dock_speaker_device_name">Dock speaker</string>
     <!-- Default name of the external device. [CHAR LIMIT=30] -->
@@ -1462,12 +1462,11 @@
     <!-- Media output switcher. Subtitle for devices connected through HDMI EARC if a device name is available. [CHAR LIMIT=NONE] -->
     <string name="tv_media_transfer_earc_subtitle">Connected via eARC</string>
 
-    <!-- TV media output switcher. Title for the default audio output of the device [CHAR LIMIT=NONE] -->
-    <string name="tv_media_transfer_default">TV default</string>
-    <!-- TV media output switcher. Subtitle for default audio output which is HDMI, e.g. TV dongle [CHAR LIMIT=NONE] -->
-    <string name="tv_media_transfer_hdmi">HDMI output</string>
-    <!-- TV media output switcher. Subtitle for default audio output which is internal speaker, i.e. panel VTs [CHAR LIMIT=NONE] -->
-    <string name="tv_media_transfer_internal_speakers">Internal speakers</string>
+    <!-- TV media output switcher. Subtitle for default audio output which is internal speaker [CHAR LIMIT=NONE] -->
+    <string name="tv_media_transfer_internal_speakers">Built-in speaker</string>
+
+    <!-- TV media output switcher. Title for default audio output which is HDMI, e.g. TV dongle [CHAR LIMIT=NONE] -->
+    <string name="tv_media_transfer_hdmi_title">TV Audio</string>
 
     <!-- Warning message to tell user is have problem during profile connect, it need to turn off device and back on. [CHAR_LIMIT=NONE] -->
     <string name="profile_connect_timeout_subtext">Problem connecting. Turn device off &amp; back on</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index fc163ce..4de6476 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -27,8 +27,10 @@
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
-import android.app.ecm.EnhancedConfirmationManager;
+import android.app.admin.EnforcingAdmin;
 import android.app.admin.PackagePolicy;
+import android.app.admin.UnknownAuthority;
+import android.app.ecm.EnhancedConfirmationManager;
 import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -43,6 +45,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManager.EnforcingUser;
+import android.security.advancedprotection.AdvancedProtectionManager;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
@@ -202,6 +205,14 @@
             return null;
         }
 
+        if (android.security.Flags.aapmApi()) {
+            EnforcingAdmin admin = dpm.getEnforcingAdmin(userId, userRestriction);
+            if (admin != null) {
+                return new EnforcedAdmin(admin.getComponentName(), userRestriction,
+                        admin.getUserHandle());
+            }
+        }
+
         final EnforcedAdmin admin =
                 getProfileOrDeviceOwner(context, userRestriction, enforcingUser.getUserHandle());
         if (admin != null) {
@@ -838,6 +849,22 @@
     }
 
     /**
+     * Checks if the identifier is enforced by advanced protection.
+     */
+    @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+    public static boolean isPolicyEnforcedByAdvancedProtection(Context context, String identifier,
+            int userId) {
+        if (!android.security.Flags.aapmApi()) return false;
+        if (identifier == null) return false;
+        EnforcingAdmin admin = context.getSystemService(DevicePolicyManager.class)
+                .getEnforcingAdmin(userId, identifier);
+        if (admin == null) return false;
+        return admin.getAuthority() instanceof UnknownAuthority authority
+                && AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY.equals(
+                        authority.getName());
+    }
+
+    /**
      * Check if there are restrictions on an application from being a Credential Manager provider.
      *
      * @return EnforcedAdmin Object containing the enforced admin component and admin user details,
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 6ca9279..25628fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -33,7 +33,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
@@ -118,10 +117,7 @@
         if (mDisabledSummary) {
             final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
             if (summaryView != null) {
-                final CharSequence disabledText =
-                        (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
-                                ? getDisabledByAdminUpdatableString()
-                                : mContext.getString(R.string.disabled_by_admin_summary_text);
+                final CharSequence disabledText = getDisabledByAdminSummaryString();
                 if (mDisabledByAdmin) {
                     summaryView.setText(disabledText);
                 } else if (mDisabledByEcm) {
@@ -134,11 +130,23 @@
         }
     }
 
-    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
-    private String getDisabledByAdminUpdatableString() {
-        return mContext.getSystemService(DevicePolicyManager.class).getResources().getString(
-                CONTROLLED_BY_ADMIN_SUMMARY,
-                () -> mContext.getString(R.string.disabled_by_admin_summary_text));
+    private String getDisabledByAdminSummaryString() {
+        if (isRestrictionEnforcedByAdvancedProtection()) {
+            return mContext.getString(com.android.settingslib.widget.restricted
+                    .R.string.disabled_by_advanced_protection);
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            return mContext.getSystemService(DevicePolicyManager.class).getResources().getString(
+                    CONTROLLED_BY_ADMIN_SUMMARY,
+                    () -> mContext.getString(R.string.disabled_by_admin_summary_text));
+        }
+        return mContext.getString(R.string.disabled_by_admin_summary_text);
+    }
+
+    public boolean isRestrictionEnforcedByAdvancedProtection() {
+        return mEnforcedAdmin != null && RestrictedLockUtilsInternal
+                .isPolicyEnforcedByAdvancedProtection(mContext, mEnforcedAdmin.enforcedRestriction,
+                        UserHandle.myUserId());
     }
 
     public void useAdminDisabledSummary(boolean useSummary) {
@@ -226,17 +234,24 @@
      */
     public boolean setDisabledByAdmin(EnforcedAdmin admin) {
         boolean disabled = false;
+        boolean changed = false;
+        EnforcedAdmin previousAdmin = mEnforcedAdmin;
         mEnforcedAdmin = null;
         if (admin != null) {
             disabled = true;
             // Copy the received instance to prevent pass be reference being overwritten.
             mEnforcedAdmin = new EnforcedAdmin(admin);
+            if (android.security.Flags.aapmApi()) {
+                changed = previousAdmin == null || !previousAdmin.equals(admin);
+            }
         }
 
-        boolean changed = false;
         if (mDisabledByAdmin != disabled) {
             mDisabledByAdmin = disabled;
             changed = true;
+        }
+
+        if (changed) {
             updateDisabledState();
         }
 
@@ -286,6 +301,10 @@
             ((PrimarySwitchPreference) mPreference).setSwitchEnabled(isEnabled);
         }
 
+        if (android.security.Flags.aapmApi() && !isEnabled && mDisabledByAdmin) {
+            mPreference.setSummary(getDisabledByAdminSummaryString());
+        }
+
         if (!isEnabled && mDisabledByEcm) {
             mPreference.setSummary(R.string.disabled_by_app_ops_text);
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 727dbe1..0aac9a11 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -126,13 +126,7 @@
 
         CharSequence switchSummary;
         if (mRestrictedSwitchSummary == null) {
-            switchSummary = isChecked()
-                    ? getUpdatableEnterpriseString(
-                            getContext(), ENABLED_BY_ADMIN_SWITCH_SUMMARY,
-                            com.android.settingslib.widget.restricted.R.string.enabled_by_admin)
-                    : getUpdatableEnterpriseString(
-                            getContext(), DISABLED_BY_ADMIN_SWITCH_SUMMARY,
-                            com.android.settingslib.widget.restricted.R.string.disabled_by_admin);
+            switchSummary = getRestrictedSwitchSummary();
         } else {
             switchSummary = mRestrictedSwitchSummary;
         }
@@ -177,6 +171,25 @@
                 () -> context.getString(resId));
     }
 
+    private String getRestrictedSwitchSummary() {
+        if (mHelper.isRestrictionEnforcedByAdvancedProtection()) {
+            final int apmResId = isChecked()
+                    ? com.android.settingslib.widget.restricted.R.string
+                            .enabled_by_advanced_protection
+                    : com.android.settingslib.widget.restricted.R.string
+                            .disabled_by_advanced_protection;
+            return getContext().getString(apmResId);
+        }
+
+        return isChecked()
+                ? getUpdatableEnterpriseString(
+                        getContext(), ENABLED_BY_ADMIN_SWITCH_SUMMARY,
+                        com.android.settingslib.widget.restricted.R.string.enabled_by_admin)
+                : getUpdatableEnterpriseString(
+                        getContext(), DISABLED_BY_ADMIN_SWITCH_SUMMARY,
+                        com.android.settingslib.widget.restricted.R.string.disabled_by_admin);
+    }
+
     @Override
     public void performClick() {
         if (!mHelper.performClick()) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index b3e48b2..fa28cf6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -94,6 +94,14 @@
     boolean setSubDeviceIfNeeded(CachedBluetoothDevice newDevice) {
         final long hiSyncId = newDevice.getHiSyncId();
         if (isValidHiSyncId(hiSyncId)) {
+            // The remote device supports CSIP, the other ear should be processed as a member
+            // device. Ignore hiSyncId grouping from ASHA here.
+            if (newDevice.getProfiles().stream().anyMatch(
+                    profile -> profile instanceof CsipSetCoordinatorProfile)) {
+                Log.w(TAG, "Skip ASHA grouping since this device supports CSIP");
+                return false;
+            }
+
             final CachedBluetoothDevice hearingAidDevice = getCachedDevice(hiSyncId);
             // Just add one of the hearing aids from a pair in the list that is shown in the UI.
             // Once there is another device with the same hiSyncId, to add new device as sub
@@ -161,6 +169,7 @@
             // device. Ignore hiSyncId grouping from ASHA here.
             if (cachedDevice.getProfiles().stream().anyMatch(
                     profile -> profile instanceof CsipSetCoordinatorProfile)) {
+                Log.w(TAG, "Skip ASHA grouping since this device supports CSIP");
                 continue;
             }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java
new file mode 100644
index 0000000..7a64965
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data.INVALID_VOLUME;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.KeyValueListParser;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * The class to manage hearing device local data from Settings.
+ *
+ * <p><b>Note:</b> Before calling any methods to get or change the local data, you must first call
+ * the {@code start()} method to load the data from Settings. Whenever the data is modified, you
+ * must call the {@code stop()} method to save the data into Settings. After calling {@code stop()},
+ * you should not call any methods to get or change the local data without again calling
+ * {@code start()}.
+ */
+public class HearingDeviceLocalDataManager {
+    private static final String TAG = "HearingDeviceDataMgr";
+    private static final boolean DEBUG = true;
+
+    /** Interface for listening hearing device local data changed */
+    public interface OnDeviceLocalDataChangeListener {
+        /**
+         * The method is called when the local data of the device with the address is changed.
+         *
+         * @param address the device anonymized address
+         * @param data    the updated data
+         */
+        void onDeviceLocalDataChange(@NonNull String address, @Nullable Data data);
+    }
+
+    static final String KEY_ADDR = "addr";
+    static final String KEY_AMBIENT = "ambient";
+    static final String KEY_GROUP_AMBIENT = "group_ambient";
+    static final String KEY_AMBIENT_CONTROL_EXPANDED = "control_expanded";
+    static final String LOCAL_AMBIENT_VOLUME_SETTINGS =
+            Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME;
+
+    private static final Object sLock = new Object();
+
+    private final Context mContext;
+    private Executor mListenerExecutor;
+    @GuardedBy("sLock")
+    private final Map<String, Data> mAddrToDataMap = new HashMap<>();
+    private OnDeviceLocalDataChangeListener mListener;
+    private SettingsObserver mSettingsObserver;
+    private boolean mIsStarted = false;
+
+    public HearingDeviceLocalDataManager(@NonNull Context context) {
+        mContext = context;
+        mSettingsObserver = new SettingsObserver(ThreadUtils.getUiThreadHandler());
+    }
+
+    /** Starts the manager. Loads the data from Settings and start observing any changes. */
+    public synchronized void start() {
+        if (mIsStarted) {
+            return;
+        }
+        mIsStarted = true;
+        getLocalDataFromSettings();
+        mSettingsObserver.register(mContext.getContentResolver());
+    }
+
+    /** Stops the manager. Flushes the data into Settings and stop observing. */
+    public synchronized void stop() {
+        if (!mIsStarted) {
+            return;
+        }
+        putAmbientVolumeSettings();
+        mSettingsObserver.unregister(mContext.getContentResolver());
+        mIsStarted = false;
+    }
+
+    /**
+     * Sets a listener which will be be notified when hearing device local data is changed.
+     *
+     * @param listener the listener to be notified
+     * @param executor the executor to run the
+     *                 {@link OnDeviceLocalDataChangeListener#onDeviceLocalDataChange(String,
+     *                 Data)} callback
+     */
+    public void setOnDeviceLocalDataChangeListener(
+            @NonNull OnDeviceLocalDataChangeListener listener, @NonNull Executor executor) {
+        mListener = listener;
+        mListenerExecutor = executor;
+    }
+
+    /**
+     * Gets the local data of the corresponding hearing device. This should be called after
+     * {@link #start()} is called().
+     *
+     * @param device the device to query the local data
+     */
+    @NonNull
+    public Data get(@NonNull BluetoothDevice device) {
+        if (!mIsStarted) {
+            Log.w(TAG, "Manager is not started. Please call start() first.");
+            return new Data();
+        }
+        synchronized (sLock) {
+            return mAddrToDataMap.getOrDefault(device.getAnonymizedAddress(), new Data());
+        }
+    }
+
+    /**
+     * Puts the local data of the corresponding hearing device.
+     *
+     * @param device the device to update the local data
+     */
+    private void put(BluetoothDevice device, Data data) {
+        if (device == null) {
+            return;
+        }
+        synchronized (sLock) {
+            final String addr = device.getAnonymizedAddress();
+            mAddrToDataMap.put(addr, data);
+            if (mListener != null && mListenerExecutor != null) {
+                mListenerExecutor.execute(() -> mListener.onDeviceLocalDataChange(addr, data));
+            }
+        }
+    }
+
+    /**
+     * Updates the ambient volume of the corresponding hearing device. This should be called after
+     * {@link #start()} is called().
+     *
+     * @param device the device to update
+     * @param value  the ambient value
+     * @return if the local data is updated
+     */
+    public boolean updateAmbient(@Nullable BluetoothDevice device, int value) {
+        if (!mIsStarted) {
+            Log.w(TAG, "Manager is not started. Please call start() first.");
+            return false;
+        }
+        if (device == null) {
+            return false;
+        }
+        synchronized (sLock) {
+            Data data = get(device);
+            if (value == data.ambient) {
+                return false;
+            }
+            put(device, new Data.Builder(data).ambient(value).build());
+            return true;
+        }
+    }
+
+    /**
+     * Updates the group ambient volume of the corresponding hearing device. This should be called
+     * after {@link #start()} is called().
+     *
+     * @param device the device to update
+     * @param value  the group ambient value
+     * @return if the local data is updated
+     */
+    public boolean updateGroupAmbient(@Nullable BluetoothDevice device, int value) {
+        if (!mIsStarted) {
+            Log.w(TAG, "Manager is not started. Please call start() first.");
+            return false;
+        }
+        if (device == null) {
+            return false;
+        }
+        synchronized (sLock) {
+            Data data = get(device);
+            if (value == data.groupAmbient) {
+                return false;
+            }
+            put(device, new Data.Builder(data).groupAmbient(value).build());
+            return true;
+        }
+    }
+
+    /**
+     * Updates the ambient control is expanded or not of the corresponding hearing device. This
+     * should be called after {@link #start()} is called().
+     *
+     * @param device   the device to update
+     * @param expanded the ambient control is expanded or not
+     * @return if the local data is updated
+     */
+    public boolean updateAmbientControlExpanded(@Nullable BluetoothDevice device,
+            boolean expanded) {
+        if (!mIsStarted) {
+            Log.w(TAG, "Manager is not started. Please call start() first.");
+            return false;
+        }
+        if (device == null) {
+            return false;
+        }
+        synchronized (sLock) {
+            Data data = get(device);
+            if (expanded == data.ambientControlExpanded) {
+                return false;
+            }
+            put(device, new Data.Builder(data).ambientControlExpanded(expanded).build());
+            return true;
+        }
+    }
+
+    void getLocalDataFromSettings() {
+        synchronized (sLock) {
+            Map<String, Data> updatedAddrToDataMap = parseFromSettings();
+            notifyIfDataChanged(mAddrToDataMap, updatedAddrToDataMap);
+            mAddrToDataMap.clear();
+            mAddrToDataMap.putAll(updatedAddrToDataMap);
+            if (DEBUG) {
+                Log.v(TAG, "getLocalDataFromSettings, " + mAddrToDataMap + ", manager: " + this);
+            }
+        }
+    }
+
+    void putAmbientVolumeSettings() {
+        synchronized (sLock) {
+            StringBuilder builder = new StringBuilder();
+            for (Map.Entry<String, Data> entry : mAddrToDataMap.entrySet()) {
+                builder.append(KEY_ADDR).append("=").append(entry.getKey());
+                builder.append(entry.getValue().toSettingsFormat()).append(";");
+            }
+            if (DEBUG) {
+                Log.v(TAG, "putAmbientVolumeSettings, " + builder + ", manager: " + this);
+            }
+            Settings.Global.putStringForUser(mContext.getContentResolver(),
+                    LOCAL_AMBIENT_VOLUME_SETTINGS, builder.toString(),
+                    UserHandle.USER_SYSTEM);
+        }
+    }
+
+    @GuardedBy("sLock")
+    private Map<String, Data> parseFromSettings() {
+        String settings = Settings.Global.getStringForUser(mContext.getContentResolver(),
+                LOCAL_AMBIENT_VOLUME_SETTINGS, UserHandle.USER_SYSTEM);
+        Map<String, Data> addrToDataMap = new ArrayMap<>();
+        if (settings != null && !settings.isEmpty()) {
+            String[] localDataArray = settings.split(";");
+            for (String localData : localDataArray) {
+                KeyValueListParser parser = new KeyValueListParser(',');
+                parser.setString(localData);
+                String address = parser.getString(KEY_ADDR, "");
+                if (!address.isEmpty()) {
+                    Data data = new Data.Builder()
+                            .ambient(parser.getInt(KEY_AMBIENT, INVALID_VOLUME))
+                            .groupAmbient(parser.getInt(KEY_GROUP_AMBIENT, INVALID_VOLUME))
+                            .ambientControlExpanded(
+                                    parser.getBoolean(KEY_AMBIENT_CONTROL_EXPANDED, false))
+                            .build();
+                    addrToDataMap.put(address, data);
+                }
+            }
+        }
+        return addrToDataMap;
+    }
+
+    @GuardedBy("sLock")
+    private void notifyIfDataChanged(Map<String, Data> oldAddrToDataMap,
+            Map<String, Data> newAddrToDataMap) {
+        newAddrToDataMap.forEach((addr, data) -> {
+            Data oldData = oldAddrToDataMap.get(addr);
+            if (oldData == null || !oldData.equals(data)) {
+                if (mListener != null) {
+                    mListenerExecutor.execute(() -> mListener.onDeviceLocalDataChange(addr, data));
+                }
+            }
+        });
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+        private final Uri mAmbientVolumeUri = Settings.Global.getUriFor(
+                LOCAL_AMBIENT_VOLUME_SETTINGS);
+
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        void register(ContentResolver contentResolver) {
+            contentResolver.registerContentObserver(mAmbientVolumeUri, false, this,
+                    UserHandle.USER_SYSTEM);
+        }
+
+        void unregister(ContentResolver contentResolver) {
+            contentResolver.unregisterContentObserver(this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, @Nullable Uri uri) {
+            if (mAmbientVolumeUri.equals(uri)) {
+                Log.v(TAG, "Local data on change, manager: " + HearingDeviceLocalDataManager.this);
+                getLocalDataFromSettings();
+            }
+        }
+    }
+
+    public record Data(int ambient, int groupAmbient, boolean ambientControlExpanded) {
+
+        public static int INVALID_VOLUME = Integer.MIN_VALUE;
+
+        private Data() {
+            this(INVALID_VOLUME, INVALID_VOLUME, false);
+        }
+
+        /**
+         * Return {@code true} if one of {@link #ambient} or {@link #groupAmbient} is assigned to
+         * a valid value.
+         */
+        public boolean hasAmbientData() {
+            return ambient != INVALID_VOLUME || groupAmbient != INVALID_VOLUME;
+        }
+
+        /**
+         * @return the composed string which is used to store the local data in
+         * {@link Settings.Global#HEARING_DEVICE_LOCAL_AMBIENT_VOLUME}
+         */
+        @NonNull
+        public String toSettingsFormat() {
+            String string = "";
+            if (ambient != INVALID_VOLUME) {
+                string += ("," + KEY_AMBIENT + "=" + ambient);
+            }
+            if (groupAmbient != INVALID_VOLUME) {
+                string += ("," + KEY_GROUP_AMBIENT + "=" + groupAmbient);
+            }
+            string += ("," + KEY_AMBIENT_CONTROL_EXPANDED + "=" + ambientControlExpanded);
+            return string;
+        }
+
+        /** Builder for a Data object */
+        public static final class Builder {
+            private int mAmbient;
+            private int mGroupAmbient;
+            private boolean mAmbientControlExpanded;
+
+            public Builder() {
+                this.mAmbient = INVALID_VOLUME;
+                this.mGroupAmbient = INVALID_VOLUME;
+                this.mAmbientControlExpanded = false;
+            }
+
+            public Builder(@NonNull Data other) {
+                this.mAmbient = other.ambient;
+                this.mGroupAmbient = other.groupAmbient;
+                this.mAmbientControlExpanded = other.ambientControlExpanded;
+            }
+
+            /** Sets the ambient volume */
+            @NonNull
+            public Builder ambient(int ambient) {
+                this.mAmbient = ambient;
+                return this;
+            }
+
+            /** Sets the group ambient volume */
+            @NonNull
+            public Builder groupAmbient(int groupAmbient) {
+                this.mGroupAmbient = groupAmbient;
+                return this;
+            }
+
+            /** Sets the ambient control expanded */
+            @NonNull
+            public Builder ambientControlExpanded(boolean ambientControlExpanded) {
+                this.mAmbientControlExpanded = ambientControlExpanded;
+                return this;
+            }
+
+            /** Build the Data object */
+            @NonNull
+            public Data build() {
+                return new Data(mAmbient, mGroupAmbient, mAmbientControlExpanded);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt
index 07abb6b..888f54f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt
@@ -69,8 +69,8 @@
             }
             .buffer(capacity = Channel.CONFLATED)
 
-/** [Flow] for [BluetoothLeBroadcast.Callback] onPlaybackStarted event */
-val LocalBluetoothLeBroadcast.onPlaybackStarted: Flow<Unit>
+/** [Flow] for [BluetoothLeBroadcast.Callback] onBroadcastMetadataChanged event */
+val LocalBluetoothLeBroadcast.onBroadcastMetadataChanged: Flow<Unit>
     get() =
         callbackFlow {
             val listener =
@@ -87,7 +87,6 @@
                     }
 
                     override fun onPlaybackStarted(reason: Int, broadcastId: Int) {
-                        launch { trySend(Unit) }
                     }
 
                     override fun onPlaybackStopped(reason: Int, broadcastId: Int) {
@@ -100,7 +99,9 @@
                     override fun onBroadcastMetadataChanged(
                         broadcastId: Int,
                         metadata: BluetoothLeBroadcastMetadata
-                    ) {}
+                    ) {
+                        trySend(Unit)
+                    }
                 }
             registerServiceCallBack(
                 ConcurrentUtils.DIRECT_EXECUTOR,
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index ab7a3db..d85b92f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
@@ -21,6 +21,7 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.IntRange;
+import android.bluetooth.AudioInputControl;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -34,6 +35,7 @@
 import androidx.annotation.RequiresApi;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -168,6 +170,7 @@
         }
         mService.setVolumeOffset(device, volumeOffset);
     }
+
     /**
      * Provides information about the possibility to set volume offset on the remote device. If the
      * remote device supports Volume Offset Control Service, it is automatically connected.
@@ -210,6 +213,22 @@
         mService.setDeviceVolume(device, volume, isGroupOp);
     }
 
+    /**
+     * Returns a list of {@link AudioInputControl} objects associated with a Bluetooth device.
+     *
+     * @param device The remote Bluetooth device.
+     * @return A list of {@link AudioInputControl} objects, or an empty list if no AICS instances
+     *     are found or if an error occurs.
+     * @hide
+     */
+    public @NonNull List<AudioInputControl> getAudioInputControlServices(
+            @NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return Collections.emptyList();
+        }
+        return mService.getAudioInputControlServices(device);
+    }
+
     @Override
     public boolean accessProfileEnabled() {
         return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
index 0209eb8..0474b50 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
@@ -121,6 +121,14 @@
                         null
                     }
                 }
+                .catch { e ->
+                    if (e is DeadObjectException) {
+                        Log.e(TAG, "DeadObjectException happens when try to get service status.", e)
+                        emit(false)
+                    } else {
+                        throw e
+                    }
+                }
                 .firstOrNull() ?: false
         }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index 717a8ee..aa2ede31 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -33,6 +33,8 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
 import android.media.MediaRoute2Info;
 import android.os.SystemProperties;
 import android.util.SparseIntArray;
@@ -116,14 +118,15 @@
 
     @SuppressLint("SwitchIntDef")
     @DrawableRes
-    private static int getIconResourceIdForTv(@MediaRoute2Info.Type int type) {
+    private int getIconResourceIdForTv(@MediaRoute2Info.Type int type) {
         return switch (type) {
             case MediaRoute2Info.TYPE_USB_DEVICE, MediaRoute2Info.TYPE_USB_HEADSET ->
                     R.drawable.ic_headphone;
             case MediaRoute2Info.TYPE_USB_ACCESSORY -> R.drawable.ic_usb;
             case MediaRoute2Info.TYPE_DOCK -> R.drawable.ic_dock_device;
-            case MediaRoute2Info.TYPE_HDMI, MediaRoute2Info.TYPE_BUILTIN_SPEAKER ->
-                    R.drawable.ic_tv;
+            case MediaRoute2Info.TYPE_BUILTIN_SPEAKER ->
+                    isPanelTv() ? R.drawable.ic_tv : R.drawable.ic_tv_box_internal_speaker;
+            case MediaRoute2Info.TYPE_HDMI -> R.drawable.ic_tv;
             case MediaRoute2Info.TYPE_HDMI_ARC, MediaRoute2Info.TYPE_HDMI_EARC ->
                     R.drawable.ic_hdmi;
             case MediaRoute2Info.TYPE_WIRED_HEADSET, MediaRoute2Info.TYPE_WIRED_HEADPHONES ->
@@ -132,6 +135,23 @@
         };
     }
 
+    private boolean isPanelTv() {
+        if (mContext == null) {
+            // This should only happen during testing.
+            return true;
+        }
+        AudioManager audioManager = mContext.getSystemService(AudioManager.class);
+        AudioDeviceInfo[] devices = audioManager.getDevices(
+                AudioManager.GET_DEVICES_OUTPUTS);
+        // If we have an HDMI output (not ARC/eARC) we can assume it's a dongle / set top box.
+        for (AudioDeviceInfo device : devices) {
+            if (device.getType() == TYPE_HDMI) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     static {
         AUDIO_DEVICE_TO_MEDIA_ROUTE_TYPE.put(TYPE_USB_DEVICE, MediaRoute2Info.TYPE_USB_DEVICE);
         AUDIO_DEVICE_TO_MEDIA_ROUTE_TYPE.put(TYPE_USB_HEADSET, MediaRoute2Info.TYPE_USB_HEADSET);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 4766a86..6ff1a99 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -29,6 +29,7 @@
 import static android.media.MediaRoute2Info.TYPE_LINE_DIGITAL;
 import static android.media.MediaRoute2Info.TYPE_LINE_ANALOG;
 import static android.media.MediaRoute2Info.TYPE_AUX_LINE;
+
 import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
 
 import android.Manifest;
@@ -40,6 +41,7 @@
 import android.hardware.hdmi.HdmiPortInfo;
 import android.media.MediaRoute2Info;
 import android.media.RouteListingPreference;
+import android.os.Build;
 import android.os.SystemProperties;
 import android.util.Log;
 
@@ -72,7 +74,7 @@
     /** Returns this device name for media transfer. */
     public static @NonNull String getMediaTransferThisDeviceName(@NonNull Context context) {
         if (isTv(context)) {
-            return context.getString(R.string.media_transfer_this_device_name_tv);
+            return Build.MODEL;
         } else if (isTablet()) {
             return context.getString(R.string.media_transfer_this_device_name_tablet);
         } else if (inputRoutingEnabledAndIsDesktop(context)) {
@@ -110,7 +112,7 @@
                 name = getMediaTransferThisDeviceName(context);
                 break;
             case TYPE_HDMI:
-                name = context.getString(isTv ? R.string.tv_media_transfer_default :
+                name = context.getString(isTv ? R.string.tv_media_transfer_hdmi_title :
                         R.string.media_transfer_external_device_name);
                 break;
             case TYPE_HDMI_ARC:
@@ -223,8 +225,6 @@
         switch (mRouteInfo.getType()) {
             case TYPE_BUILTIN_SPEAKER:
                 return mContext.getString(R.string.tv_media_transfer_internal_speakers);
-            case TYPE_HDMI:
-                return mContext.getString(R.string.tv_media_transfer_hdmi);
             case TYPE_HDMI_ARC:
                 if (getHdmiOutDeviceName(mContext) == null) {
                     // Connection type is already part of the title.
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 23be7ba..496c3e6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -74,10 +74,6 @@
         mutableModesFlow.value = mutableModesFlow.value.filter { it.id != id }
     }
 
-    fun replaceMode(modeId: String, mode: ZenMode) {
-        mutableModesFlow.value = (mutableModesFlow.value.filter { it.id != modeId }) + mode
-    }
-
     fun clearModes() {
         mutableModesFlow.value = listOf()
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index 6842d0a..abc1638 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -41,32 +41,24 @@
     private String mId;
     private AutomaticZenRule mRule;
     private ZenModeConfig.ZenRule mConfigZenRule;
+    private boolean mIsManualDnd;
 
     public static final ZenMode EXAMPLE = new TestModeBuilder().build();
 
-    public static final ZenMode MANUAL_DND_ACTIVE = manualDnd(Uri.EMPTY,
+    public static final ZenMode MANUAL_DND_ACTIVE = manualDnd(
             INTERRUPTION_FILTER_PRIORITY, true);
 
-    public static final ZenMode MANUAL_DND_INACTIVE = manualDnd(Uri.EMPTY,
+    public static final ZenMode MANUAL_DND_INACTIVE = manualDnd(
             INTERRUPTION_FILTER_PRIORITY, false);
 
     @NonNull
     public static ZenMode manualDnd(@NotificationManager.InterruptionFilter int filter,
             boolean isActive) {
-        return manualDnd(Uri.EMPTY, filter, isActive);
-    }
-
-    private static ZenMode manualDnd(Uri conditionId,
-            @NotificationManager.InterruptionFilter int filter, boolean isActive) {
-        return ZenMode.manualDndMode(
-                new AutomaticZenRule.Builder("Do Not Disturb", conditionId)
-                        .setInterruptionFilter(filter)
-                        .setType(AutomaticZenRule.TYPE_OTHER)
-                        .setManualInvocationAllowed(true)
-                        .setPackage(SystemZenRules.PACKAGE_ANDROID)
-                        .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
-                        .build(),
-                isActive);
+        return new TestModeBuilder()
+                .makeManualDnd()
+                .setInterruptionFilter(filter)
+                .setActive(isActive)
+                .build();
     }
 
     public TestModeBuilder() {
@@ -91,6 +83,10 @@
         mConfigZenRule.enabled = previous.getRule().isEnabled();
         mConfigZenRule.pkg = previous.getRule().getPackageName();
         setActive(previous.isActive());
+
+        if (previous.isManualDnd()) {
+            makeManualDnd();
+        }
     }
 
     public TestModeBuilder setId(String id) {
@@ -222,7 +218,25 @@
         return this;
     }
 
+    public TestModeBuilder makeManualDnd() {
+        mIsManualDnd = true;
+        // Set the "fixed" properties of a DND mode. Other things, such as policy/filter may be set
+        // separately or copied from a preexisting DND, so they are not overwritten here.
+        setId(ZenMode.MANUAL_DND_MODE_ID);
+        setName("Do Not Disturb");
+        setType(AutomaticZenRule.TYPE_OTHER);
+        setManualInvocationAllowed(true);
+        setPackage(SystemZenRules.PACKAGE_ANDROID);
+        setConditionId(Uri.EMPTY);
+        return this;
+    }
+
     public ZenMode build() {
-        return new ZenMode(mId, mRule, mConfigZenRule);
+        if (mIsManualDnd) {
+            return ZenMode.manualDndMode(mRule, mConfigZenRule.condition != null
+                    && mConfigZenRule.condition.state == Condition.STATE_TRUE);
+        } else {
+            return new ZenMode(mId, mRule, mConfigZenRule);
+        }
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java
index 1b0e1f1..013ff92 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java
@@ -16,11 +16,14 @@
 
 package com.android.settingslib.applications;
 
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.doReturn;
 
 import android.graphics.drawable.Drawable;
+import android.util.Log;
 
 import org.junit.After;
 import org.junit.Before;
@@ -30,9 +33,12 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 
+import java.util.concurrent.atomic.AtomicInteger;
+
 @RunWith(RobolectricTestRunner.class)
 public class AppIconCacheManagerTest {
 
+    private static final String TAG = "AppIconCacheManagerTest";
     private static final String APP_PACKAGE_NAME = "com.test.app";
     private static final String APP_PACKAGE_NAME1 = "com.test.app1";
     private static final String APP_PACKAGE_NAME2 = "com.test.app2";
@@ -176,4 +182,28 @@
         assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME2, APP_UID)).isNull();
         assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME3, APP_UID)).isNull();
     }
+
+    @Test
+    public void trimMemory_multiThread_shouldNotCrash() {
+        int numberOfTasks = 10;
+        AtomicInteger completedTasks = new AtomicInteger(0);
+
+        Runnable task =
+                () -> {
+                    String threadName = Thread.currentThread().getName();
+                    Log.i(TAG, "Starting thread: " + threadName);
+                    AppIconCacheManager.getInstance().trimMemory(TRIM_MEMORY_BACKGROUND);
+                    completedTasks.incrementAndGet();
+                    Log.i(TAG, "Ending thread: " + threadName);
+                };
+
+        for (Integer i = 0; i < numberOfTasks; i++) {
+            Thread thread = new Thread(task);
+            thread.start();
+        }
+
+        while (completedTasks.get() < numberOfTasks) {
+            // Wait until all threads are finished.
+        }
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java
new file mode 100644
index 0000000..b659c02
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.bluetooth.BluetoothDevice;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowSettings;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Tests for {@link HearingDeviceLocalDataManager}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {HearingDeviceLocalDataManagerTest.ShadowGlobal.class})
+public class HearingDeviceLocalDataManagerTest {
+
+    private static final String TEST_ADDRESS = "XX:XX:XX:XX:11:22";
+    private static final int TEST_AMBIENT = 10;
+    private static final int TEST_GROUP_AMBIENT = 20;
+    private static final boolean TEST_AMBIENT_CONTROL_EXPANDED = true;
+    private static final int TEST_UPDATED_AMBIENT = 30;
+    private static final int TEST_UPDATED_GROUP_AMBIENT = 40;
+    private static final boolean TEST_UPDATED_AMBIENT_CONTROL_EXPANDED = false;
+
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Mock
+    private BluetoothDevice mDevice;
+    @Mock
+    private HearingDeviceLocalDataManager.OnDeviceLocalDataChangeListener mListener;
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private HearingDeviceLocalDataManager mLocalDataManager;
+
+    @Before
+    public void setUp() {
+        prepareTestDataInSettings();
+        mLocalDataManager = new HearingDeviceLocalDataManager(mContext);
+        mLocalDataManager.start();
+        mLocalDataManager.setOnDeviceLocalDataChangeListener(mListener,
+                mContext.getMainExecutor());
+
+        when(mDevice.getAnonymizedAddress()).thenReturn(TEST_ADDRESS);
+    }
+
+    @Test
+    public void stop_verifyDataIsSaved() {
+        mLocalDataManager.updateAmbient(mDevice, TEST_UPDATED_AMBIENT);
+        mLocalDataManager.stop();
+
+        String settings = Settings.Global.getStringForUser(mContext.getContentResolver(),
+                Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, UserHandle.USER_SYSTEM);
+        String expectedSettings = generateSettingsString(TEST_ADDRESS, TEST_UPDATED_AMBIENT,
+                TEST_GROUP_AMBIENT, TEST_AMBIENT_CONTROL_EXPANDED);
+        assertThat(settings).isEqualTo(expectedSettings);
+    }
+
+    @Test
+    public void get_correctDataFromSettings() {
+        HearingDeviceLocalDataManager.Data data = mLocalDataManager.get(mDevice);
+
+        assertThat(data.ambient()).isEqualTo(TEST_AMBIENT);
+        assertThat(data.groupAmbient()).isEqualTo(TEST_GROUP_AMBIENT);
+        assertThat(data.ambientControlExpanded()).isEqualTo(TEST_AMBIENT_CONTROL_EXPANDED);
+    }
+
+    @Test
+    public void updateAmbient_correctValue_listenerCalled() {
+        HearingDeviceLocalDataManager.Data oldData = mLocalDataManager.get(mDevice);
+        assertThat(oldData.ambient()).isEqualTo(TEST_AMBIENT);
+
+        mLocalDataManager.updateAmbient(mDevice, TEST_UPDATED_AMBIENT);
+
+        HearingDeviceLocalDataManager.Data newData = mLocalDataManager.get(mDevice);
+        assertThat(newData.ambient()).isEqualTo(TEST_UPDATED_AMBIENT);
+        verify(mListener).onDeviceLocalDataChange(TEST_ADDRESS, newData);
+    }
+
+    @Test
+    public void updateAmbient_sameValue_listenerNotCalled() {
+        HearingDeviceLocalDataManager.Data oldData = mLocalDataManager.get(mDevice);
+        assertThat(oldData.ambient()).isEqualTo(TEST_AMBIENT);
+
+        mLocalDataManager.updateAmbient(mDevice, TEST_AMBIENT);
+
+        HearingDeviceLocalDataManager.Data newData = mLocalDataManager.get(mDevice);
+        assertThat(newData.ambient()).isEqualTo(TEST_AMBIENT);
+        verify(mListener, never()).onDeviceLocalDataChange(any(), any());
+    }
+
+    @Test
+    public void updateGroupAmbient_correctValue_listenerCalled() {
+        HearingDeviceLocalDataManager.Data oldData = mLocalDataManager.get(mDevice);
+        assertThat(oldData.groupAmbient()).isEqualTo(TEST_GROUP_AMBIENT);
+
+        mLocalDataManager.updateGroupAmbient(mDevice, TEST_UPDATED_GROUP_AMBIENT);
+
+        HearingDeviceLocalDataManager.Data newData = mLocalDataManager.get(mDevice);
+        assertThat(newData.groupAmbient()).isEqualTo(TEST_UPDATED_GROUP_AMBIENT);
+        verify(mListener).onDeviceLocalDataChange(TEST_ADDRESS, newData);
+    }
+
+    @Test
+    public void updateGroupAmbient_sameValue_listenerNotCalled() {
+        HearingDeviceLocalDataManager.Data oldData = mLocalDataManager.get(mDevice);
+        assertThat(oldData.groupAmbient()).isEqualTo(TEST_GROUP_AMBIENT);
+
+        mLocalDataManager.updateGroupAmbient(mDevice, TEST_GROUP_AMBIENT);
+
+        HearingDeviceLocalDataManager.Data newData = mLocalDataManager.get(mDevice);
+        assertThat(newData.groupAmbient()).isEqualTo(TEST_GROUP_AMBIENT);
+        verify(mListener, never()).onDeviceLocalDataChange(any(), any());
+    }
+
+    @Test
+    public void updateAmbientControlExpanded_correctValue_listenerCalled() {
+        HearingDeviceLocalDataManager.Data oldData = mLocalDataManager.get(mDevice);
+        assertThat(oldData.ambientControlExpanded()).isEqualTo(TEST_AMBIENT_CONTROL_EXPANDED);
+
+        mLocalDataManager.updateAmbientControlExpanded(mDevice,
+                TEST_UPDATED_AMBIENT_CONTROL_EXPANDED);
+
+        HearingDeviceLocalDataManager.Data newData = mLocalDataManager.get(mDevice);
+        assertThat(newData.ambientControlExpanded()).isEqualTo(
+                TEST_UPDATED_AMBIENT_CONTROL_EXPANDED);
+        verify(mListener).onDeviceLocalDataChange(TEST_ADDRESS, newData);
+    }
+
+    @Test
+    public void updateAmbientControlExpanded_sameValue_listenerNotCalled() {
+        HearingDeviceLocalDataManager.Data oldData = mLocalDataManager.get(mDevice);
+        assertThat(oldData.ambientControlExpanded()).isEqualTo(TEST_AMBIENT_CONTROL_EXPANDED);
+
+        mLocalDataManager.updateAmbientControlExpanded(mDevice, TEST_AMBIENT_CONTROL_EXPANDED);
+
+        HearingDeviceLocalDataManager.Data newData = mLocalDataManager.get(mDevice);
+        assertThat(newData.ambientControlExpanded()).isEqualTo(TEST_AMBIENT_CONTROL_EXPANDED);
+        verify(mListener, never()).onDeviceLocalDataChange(any(), any());
+    }
+
+    @Test
+    public void getLocalDataFromSettings_dataChanged_correctValue_listenerCalled() {
+        HearingDeviceLocalDataManager.Data oldData = mLocalDataManager.get(mDevice);
+        assertThat(oldData.ambient()).isEqualTo(TEST_AMBIENT);
+        assertThat(oldData.groupAmbient()).isEqualTo(TEST_GROUP_AMBIENT);
+        assertThat(oldData.ambientControlExpanded()).isEqualTo(TEST_AMBIENT_CONTROL_EXPANDED);
+
+        prepareUpdatedDataInSettings();
+        mLocalDataManager.getLocalDataFromSettings();
+
+        HearingDeviceLocalDataManager.Data newData = mLocalDataManager.get(mDevice);
+        assertThat(newData.ambient()).isEqualTo(TEST_UPDATED_AMBIENT);
+        assertThat(newData.groupAmbient()).isEqualTo(TEST_UPDATED_GROUP_AMBIENT);
+        assertThat(newData.ambientControlExpanded()).isEqualTo(
+                TEST_UPDATED_AMBIENT_CONTROL_EXPANDED);
+        verify(mListener).onDeviceLocalDataChange(TEST_ADDRESS, newData);
+    }
+
+    private void prepareTestDataInSettings() {
+        String data = generateSettingsString(TEST_ADDRESS, TEST_AMBIENT, TEST_GROUP_AMBIENT,
+                TEST_AMBIENT_CONTROL_EXPANDED);
+        Settings.Global.putStringForUser(mContext.getContentResolver(),
+                Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, data,
+                UserHandle.USER_SYSTEM);
+    }
+
+    private void prepareUpdatedDataInSettings() {
+        String data = generateSettingsString(TEST_ADDRESS, TEST_UPDATED_AMBIENT,
+                TEST_UPDATED_GROUP_AMBIENT, TEST_UPDATED_AMBIENT_CONTROL_EXPANDED);
+        Settings.Global.putStringForUser(mContext.getContentResolver(),
+                Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, data,
+                UserHandle.USER_SYSTEM);
+    }
+
+    private String generateSettingsString(String addr, int ambient, int groupAmbient,
+            boolean ambientControlExpanded) {
+        return "addr=" + addr + ",ambient=" + ambient + ",group_ambient=" + groupAmbient
+                + ",control_expanded=" + ambientControlExpanded + ";";
+    }
+
+    @Implements(value = Settings.Global.class)
+    public static class ShadowGlobal extends ShadowSettings.ShadowGlobal {
+        private static final Map<ContentResolver, Map<String, String>> sDataMap = new HashMap<>();
+
+        @Implementation
+        protected static boolean putStringForUser(
+                ContentResolver cr, String name, String value, int userHandle) {
+            get(cr).put(name, value);
+            return true;
+        }
+
+        @Implementation
+        protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
+            return get(cr).get(name);
+        }
+
+        private static Map<String, String> get(ContentResolver cr) {
+            return sDataMap.computeIfAbsent(cr, k -> new HashMap<>());
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
index 9c518de..bd67394 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.bluetooth.AudioInputControl;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
@@ -45,6 +46,7 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -248,4 +250,16 @@
         verify(mService).isVolumeOffsetAvailable(mBluetoothDevice);
         assertThat(available).isFalse();
     }
+
+    @Test
+    public void getAudioInputControlServices_verifyIsCalledAndReturnNonNullList() {
+        mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+        when(mService.getAudioInputControlServices(mBluetoothDevice)).thenReturn(new ArrayList<>());
+
+        final List<AudioInputControl> controls = mProfile.getAudioInputControlServices(
+                mBluetoothDevice);
+
+        verify(mService).getAudioInputControlServices(mBluetoothDevice);
+        assertThat(controls).isNotNull();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java
index 883640d..5ac22a7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java
@@ -173,7 +173,7 @@
     public void getIconResIdFromMediaRouteType_tv_builtinSpeaker_isTv() {
         assertThat(new DeviceIconUtil(/* isTv */ true)
                 .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER))
-                .isEqualTo(R.drawable.ic_tv);
+                .isAnyOf(R.drawable.ic_tv, R.drawable.ic_tv_box_internal_speaker);
     }
 
     @Test
@@ -331,7 +331,7 @@
     public void getIconResIdFromAudioDeviceType_tv_builtinSpeaker_isTv() {
         assertThat(new DeviceIconUtil(/* isTv */ true)
                 .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER))
-                .isEqualTo(R.drawable.ic_tv);
+                .isAnyOf(R.drawable.ic_tv, R.drawable.ic_tv_box_internal_speaker);
     }
 
     @Test
diff --git a/packages/SettingsProvider/res/xml/bookmarks.xml b/packages/SettingsProvider/res/xml/bookmarks.xml
deleted file mode 100644
index 645b275..0000000
--- a/packages/SettingsProvider/res/xml/bookmarks.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!--
-     Default system bookmarks for AOSP.
-     Bookmarks for vendor apps should be added to a bookmarks resource overlay; not here.
-
-     Typical shortcuts (not necessarily defined here):
-       'b': Browser
-       'c': Contacts
-       'e': Email
-       'g': GMail
-       'k': Calendar
-       'm': Maps
-       'p': Music
-       's': SMS
-       't': Talk
-       'u': Calculator
-       'y': YouTube
--->
-<bookmarks>
-    <!-- TODO(b/358569822): Remove this from Settings DB
-         This is legacy implementation to store bookmarks in Settings DB, which is deprecated and
-         no longer used -->
-    <bookmark
-        role="android.app.role.BROWSER"
-        shortcut="b" />
-    <bookmark
-        category="android.intent.category.APP_CONTACTS"
-        shortcut="c" />
-    <bookmark
-        category="android.intent.category.APP_EMAIL"
-        shortcut="e" />
-    <bookmark
-        category="android.intent.category.APP_CALENDAR"
-        shortcut="k" />
-    <bookmark
-        category="android.intent.category.APP_MAPS"
-        shortcut="m" />
-    <bookmark
-        category="android.intent.category.APP_MUSIC"
-        shortcut="p" />
-    <bookmark
-        role="android.app.role.SMS"
-        shortcut="s" />
-    <bookmark
-        category="android.intent.category.APP_CALCULATOR"
-        shortcut="u" />
-</bookmarks>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 8e7180c..935ea25 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -119,7 +119,8 @@
                 Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
                 Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
                 Settings.System.NOTIFICATION_COOLDOWN_ALL,
-                Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED
+                Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+                Settings.System.PREFERRED_REGION
         ));
         if (Flags.backUpSmoothDisplayAndForcePeakRefreshRate()) {
             settings.add(Settings.System.PEAK_REFRESH_RATE);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index cfc7743..63f401c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -230,6 +230,7 @@
         VALIDATORS.put(System.TOUCHPAD_TAP_TO_CLICK, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.TOUCHPAD_TAP_DRAGGING, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.TOUCHPAD_RIGHT_CLICK_ZONE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.TOUCHPAD_SYSTEM_GESTURES, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.LOCK_TO_APP_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 System.EGG_MODE,
@@ -265,5 +266,6 @@
         VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ALL, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.PREFERRED_REGION, ANY_STRING_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index e85ba45..e057682 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -16,14 +16,8 @@
 
 package com.android.providers.settings;
 
-import android.content.ComponentName;
-import android.content.ContentValues;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
 import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
@@ -46,16 +40,11 @@
 import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.RILConstants;
-import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternView;
 import com.android.internal.widget.LockscreenCredential;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 import java.io.File;
-import java.io.IOException;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -85,7 +74,7 @@
     private Context mContext;
     private int mUserHandle;
 
-    private static final HashSet<String> mValidTables = new HashSet<String>();
+    private static final HashSet<String> mValidTables = new HashSet<>();
 
     private static final String DATABASE_BACKUP_SUFFIX = "-backup";
 
@@ -100,7 +89,6 @@
 
         // These are old.
         mValidTables.add("bluetooth_devices");
-        mValidTables.add("bookmarks");
         mValidTables.add("favorites");
         mValidTables.add("old_favorites");
         mValidTables.add("android_metadata");
@@ -211,21 +199,6 @@
                     "type INTEGER" +
                     ");");
 
-        db.execSQL("CREATE TABLE bookmarks (" +
-                    "_id INTEGER PRIMARY KEY," +
-                    "title TEXT," +
-                    "folder TEXT," +
-                    "intent TEXT," +
-                    "shortcut INTEGER," +
-                    "ordering INTEGER" +
-                    ");");
-
-        db.execSQL("CREATE INDEX bookmarksIndex1 ON bookmarks (folder);");
-        db.execSQL("CREATE INDEX bookmarksIndex2 ON bookmarks (shortcut);");
-
-        // Populate bookmarks table with initial bookmarks
-        loadBookmarks(db);
-
         // Load initial volume levels into DB
         loadVolumeLevels(db);
 
@@ -392,19 +365,6 @@
         }
 
         if (upgradeVersion == 30) {
-            /*
-             * Upgrade 31 clears the title for all quick launch shortcuts so the
-             * activities' titles will be resolved at display time. Also, the
-             * folder is changed to '@quicklaunch'.
-             */
-            db.beginTransaction();
-            try {
-                db.execSQL("UPDATE bookmarks SET folder = '@quicklaunch'");
-                db.execSQL("UPDATE bookmarks SET title = ''");
-                db.setTransactionSuccessful();
-            } finally {
-                db.endTransaction();
-            }
             upgradeVersion = 31;
         }
 
@@ -1006,8 +966,6 @@
         }
 
         if (upgradeVersion == 70) {
-            // Update all built-in bookmarks.  Some of the package names have changed.
-            loadBookmarks(db);
             upgradeVersion = 71;
         }
 
@@ -2046,92 +2004,6 @@
     }
 
     /**
-     * Loads the default set of bookmarked shortcuts from an xml file.
-     *
-     * @param db The database to write the values into
-     */
-    private void loadBookmarks(SQLiteDatabase db) {
-        ContentValues values = new ContentValues();
-
-        PackageManager packageManager = mContext.getPackageManager();
-        try {
-            XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
-            XmlUtils.beginDocument(parser, "bookmarks");
-
-            final int depth = parser.getDepth();
-            int type;
-
-            while (((type = parser.next()) != XmlPullParser.END_TAG ||
-                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
-
-                if (type != XmlPullParser.START_TAG) {
-                    continue;
-                }
-
-                String name = parser.getName();
-                if (!"bookmark".equals(name)) {
-                    break;
-                }
-
-                String pkg = parser.getAttributeValue(null, "package");
-                String cls = parser.getAttributeValue(null, "class");
-                String shortcutStr = parser.getAttributeValue(null, "shortcut");
-                String category = parser.getAttributeValue(null, "category");
-
-                int shortcutValue = shortcutStr.charAt(0);
-                if (TextUtils.isEmpty(shortcutStr)) {
-                    Log.w(TAG, "Unable to get shortcut for: " + pkg + "/" + cls);
-                    continue;
-                }
-
-                final Intent intent;
-                final String title;
-                if (pkg != null && cls != null) {
-                    ActivityInfo info = null;
-                    ComponentName cn = new ComponentName(pkg, cls);
-                    try {
-                        info = packageManager.getActivityInfo(cn, 0);
-                    } catch (PackageManager.NameNotFoundException e) {
-                        String[] packages = packageManager.canonicalToCurrentPackageNames(
-                                new String[] { pkg });
-                        cn = new ComponentName(packages[0], cls);
-                        try {
-                            info = packageManager.getActivityInfo(cn, 0);
-                        } catch (PackageManager.NameNotFoundException e1) {
-                            Log.w(TAG, "Unable to add bookmark: " + pkg + "/" + cls, e);
-                            continue;
-                        }
-                    }
-
-                    intent = new Intent(Intent.ACTION_MAIN, null);
-                    intent.addCategory(Intent.CATEGORY_LAUNCHER);
-                    intent.setComponent(cn);
-                    title = info.loadLabel(packageManager).toString();
-                } else if (category != null) {
-                    intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category);
-                    title = "";
-                } else {
-                    Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutStr
-                            + ": missing package/class or category attributes");
-                    continue;
-                }
-
-                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                values.put(Settings.Bookmarks.INTENT, intent.toUri(0));
-                values.put(Settings.Bookmarks.TITLE, title);
-                values.put(Settings.Bookmarks.SHORTCUT, shortcutValue);
-                db.delete("bookmarks", "shortcut = ?",
-                        new String[] { Integer.toString(shortcutValue) });
-                db.insert("bookmarks", null, values);
-            }
-        } catch (XmlPullParserException e) {
-            Log.w(TAG, "Got execption parsing bookmarks.", e);
-        } catch (IOException e) {
-            Log.w(TAG, "Got execption parsing bookmarks.", e);
-        }
-    }
-
-    /**
      * Loads the default volume levels. It is actually inserting the index of
      * the volume array for each of the volume controls.
      *
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index ebeee85..ea8ae7b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -242,8 +242,7 @@
                 // Don't write it to setting. Let the broadcast receiver in
                 // AccessibilityManagerService handle restore/merging logic.
                 return;
-            } else if (android.view.accessibility.Flags.restoreA11yShortcutTargetService()
-                    && Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE.equals(name)) {
+            } else if (Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE.equals(name)) {
                 // Don't write it to setting. Let the broadcast receiver in
                 // AccessibilityManagerService handle restore/merging logic.
                 return;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 5ae11ba..37eda3e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -3049,6 +3049,9 @@
         dumpSetting(s, p,
                 Settings.System.TOUCHPAD_THREE_FINGER_TAP_CUSTOMIZATION,
                 SystemSettingsProto.Touchpad.THREE_FINGER_TAP_CUSTOMIZATION);
+        dumpSetting(s, p,
+                Settings.System.TOUCHPAD_SYSTEM_GESTURES,
+                SystemSettingsProto.Touchpad.SYSTEM_GESTURES);
         p.end(touchpadToken);
 
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 03fea37..6128d45 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -130,10 +130,12 @@
 
 import libcore.util.HexEncoding;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -380,6 +382,8 @@
     @GuardedBy("mLock")
     private Handler mHandler;
 
+    private static final Set<String> sDeviceConfigAllowlistedNamespaces = new ArraySet<>();
+
     // We have to call in the user manager with no lock held,
     private volatile UserManager mUserManager;
 
@@ -2442,6 +2446,10 @@
         if (!isRestrictedShell && hasWritePermission) {
             assertCallingUserDenyList(flags);
         } else if (hasAllowlistPermission) {
+            Set<String> allowlistedDeviceConfigNamespaces = null;
+            if (isRestrictedShell) {
+                allowlistedDeviceConfigNamespaces = getAllowlistedDeviceConfigNamespaces();
+            }
             for (String flag : flags) {
                 boolean namespaceAllowed = false;
                 if (isRestrictedShell) {
@@ -2452,7 +2460,7 @@
                     } else {
                         flagNamespace = flag;
                     }
-                    if (WritableNamespaces.ALLOWLIST.contains(flagNamespace)) {
+                    if (allowlistedDeviceConfigNamespaces.contains(flagNamespace)) {
                         namespaceAllowed = true;
                     }
                 } else {
@@ -2513,6 +2521,60 @@
         }
     }
 
+    /**
+     * Returns a Set of DeviceConfig allowlisted namespaces in which all flags can be modified
+     * by a caller with the {@code WRITE_ALLOWLISTED_DEVICE_CONFIG} permission.
+     * <p>
+     * This method also supports mainline modules that introduce their own allowlisted
+     * namespaces within the {@code etc/writable_namespaces} file under their directory.
+     */
+    private Set<String> getAllowlistedDeviceConfigNamespaces() {
+        synchronized (sDeviceConfigAllowlistedNamespaces) {
+            if (!sDeviceConfigAllowlistedNamespaces.isEmpty()) {
+                return sDeviceConfigAllowlistedNamespaces;
+            }
+            if (android.provider.flags.Flags.deviceConfigWritableNamespacesApi()) {
+                sDeviceConfigAllowlistedNamespaces.addAll(DeviceConfig.getAdbWritableNamespaces());
+            } else {
+                sDeviceConfigAllowlistedNamespaces.addAll(WritableNamespaces.ALLOWLIST);
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                List<String> apexDirectories;
+                try {
+                    apexDirectories = mPackageManager.getAllApexDirectories();
+                } catch (RemoteException e) {
+                    Slog.e(LOG_TAG, "Caught a RemoteException obtaining APEX directories: ", e);
+                    return sDeviceConfigAllowlistedNamespaces;
+                }
+                for (int i = 0; i < apexDirectories.size(); i++) {
+                    String apexDirectory = apexDirectories.get(i);
+                    File namespaceFile = Environment.buildPath(new File(apexDirectory), "etc",
+                            "writable_namespaces");
+                    if (namespaceFile.exists() && namespaceFile.isFile()) {
+                        try (BufferedReader reader = new BufferedReader(
+                                new FileReader(namespaceFile))) {
+                            String namespace;
+                            while ((namespace = reader.readLine()) != null) {
+                                namespace = namespace.trim();
+                                // Support comments by ignoring any lines that start with '#'.
+                                if (!namespace.isEmpty() && !namespace.startsWith("#")) {
+                                    sDeviceConfigAllowlistedNamespaces.add(namespace);
+                                }
+                            }
+                        } catch (IOException e) {
+                            Slog.e(LOG_TAG, "Caught an exception parsing file: " + namespaceFile,
+                                    e);
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+            return sDeviceConfigAllowlistedNamespaces;
+        }
+    }
+
     private static void warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
             int targetSdkVersion, String name) {
         // If the app targets Lollipop MR1 or older SDK we warn, otherwise crash.
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 011ffbc..c0e61ee 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -178,11 +178,6 @@
     private static final String APEX_DIR = "/apex";
     private static final String APEX_ACONFIG_PATH_SUFFIX = "/etc/aconfig_flags.pb";
 
-    private static final String STORAGE_MIGRATION_FLAG =
-            "core_experiments_team_internal/com.android.providers.settings.storage_test_mission_1";
-    private static final String STORAGE_MIGRATION_MARKER_FILE =
-            "/metadata/aconfig_test_missions/mission_1";
-
     /**
      * This tag is applied to all aconfig default value-loaded flags.
      */
@@ -1753,32 +1748,6 @@
                     }
                 }
 
-                if (isConfigSettingsKey(mKey) && name != null
-                        && name.equals(STORAGE_MIGRATION_FLAG)) {
-                    if (value.equals("true")) {
-                        Path path = Paths.get(STORAGE_MIGRATION_MARKER_FILE);
-                        if (!Files.exists(path)) {
-                            Files.createFile(path);
-                        }
-
-                        Set<PosixFilePermission> perms =
-                                Files.readAttributes(path, PosixFileAttributes.class).permissions();
-                        perms.add(PosixFilePermission.OWNER_WRITE);
-                        perms.add(PosixFilePermission.OWNER_READ);
-                        perms.add(PosixFilePermission.GROUP_READ);
-                        perms.add(PosixFilePermission.OTHERS_READ);
-                        try {
-                            Files.setPosixFilePermissions(path, perms);
-                        } catch (Exception e) {
-                            Slog.e(LOG_TAG, "failed to set permissions on migration marker", e);
-                        }
-                    } else {
-                        java.nio.file.Path path = Paths.get(STORAGE_MIGRATION_MARKER_FILE);
-                        if (Files.exists(path)) {
-                            Files.delete(path);
-                        }
-                    }
-                }
                 mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag,
                         fromSystem, Long.valueOf(id), isPreservedInRestore));
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
index b0409c0..5ce97eb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
@@ -33,6 +33,7 @@
 final class WritableNamespaces {
     public static final Set<String> ALLOWLIST =
             new ArraySet<String>(Arrays.asList(
+                    "adservices",
                     "captive_portal_login",
                     "connectivity",
                     "exo",
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index a62b7fd..9004488 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -947,7 +947,9 @@
                         Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE,
                         Settings.System.WEAR_TTS_PREWARM_ENABLED,
                         Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
-                        Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific
+                        Settings.System.MULTI_AUDIO_FOCUS_ENABLED, // form-factor/OEM specific
+                        // Potentially disruptive to on-boarding flow on new devices
+                        Settings.System.TOUCHPAD_SYSTEM_GESTURES
                 );
         if (!Flags.backUpSmoothDisplayAndForcePeakRefreshRate()) {
             settings.add(Settings.System.MIN_REFRESH_RATE);
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
index f64f72a..048d93b 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
@@ -26,8 +26,6 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Build;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.provider.SettingsStringUtil;
 
@@ -37,7 +35,6 @@
 import com.android.internal.util.test.BroadcastInterceptingContext;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
@@ -52,9 +49,6 @@
 @RunWith(AndroidJUnit4.class)
 public class SettingsHelperRestoreTest {
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
     private static final float FLOAT_TOLERANCE = 0.01f;
 
     private Context mContext;
@@ -211,7 +205,6 @@
     }
 
     @Test
-    @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE)
     public void restoreAccessibilityShortcutTargetService_broadcastSent()
             throws ExecutionException, InterruptedException {
         BroadcastInterceptingContext interceptingContext = new BroadcastInterceptingContext(
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 27e6bab..fa6e2db 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -65,7 +65,6 @@
     <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
     <uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
     <uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
-    <uses-permission android:name="android.permission.READ_DROPBOX_DATA" />
     <uses-permission android:name="android.permission.READ_LOGS" />
     <uses-permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
     <uses-permission android:name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
@@ -151,7 +150,8 @@
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.LOCATION_BYPASS" />
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
-    <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
+    <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"
+        android:featureFlag="!android.security.protect_device_config_flags"/>
     <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" />
     <uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS" />
diff --git a/packages/SoundPicker/res/values-in/strings.xml b/packages/SoundPicker/res/values-in/strings.xml
index 86dce64..f78fe3c 100644
--- a/packages/SoundPicker/res/values-in/strings.xml
+++ b/packages/SoundPicker/res/values-in/strings.xml
@@ -25,5 +25,5 @@
     <string name="delete_ringtone_text" msgid="201443984070732499">"Hapus"</string>
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tidak dapat menambahkan nada dering khusus"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tidak dapat menghapus nada dering khusus"</string>
-    <string name="app_label" msgid="3091611356093417332">"Sounds"</string>
+    <string name="app_label" msgid="3091611356093417332">"Suara"</string>
 </resources>
diff --git a/packages/StatementService/Android.bp b/packages/StatementService/Android.bp
index 90e1808..39b0302 100644
--- a/packages/StatementService/Android.bp
+++ b/packages/StatementService/Android.bp
@@ -38,8 +38,10 @@
         "StatementServiceParser",
         "androidx.appcompat_appcompat",
         "androidx.collection_collection-ktx",
+        "androidx.room_room-runtime",
         "androidx.work_work-runtime",
         "androidx.work_work-runtime-ktx",
         "kotlinx-coroutines-android",
     ],
+    plugins: ["androidx.room_room-compiler-plugin"],
 }
diff --git a/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt b/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt
index 021a514..6af8004 100644
--- a/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt
+++ b/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt
@@ -30,6 +30,7 @@
             // WorkManager can only schedule when the user data directories are unencrypted (after
             // the user has entered their lock password.
             DomainVerificationUtils.schedulePeriodicCheckUnlocked(WorkManager.getInstance(this))
+            DomainVerificationUtils.schedulePeriodicUpdateUnlocked(WorkManager.getInstance(this))
         }
     }
 }
diff --git a/packages/StatementService/src/com/android/statementservice/database/Converters.kt b/packages/StatementService/src/com/android/statementservice/database/Converters.kt
new file mode 100644
index 0000000..21ecc8b
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/Converters.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.statementservice.database
+
+import android.content.UriRelativeFilter
+import android.content.UriRelativeFilterGroup
+import android.util.JsonReader
+import androidx.room.TypeConverter
+import org.json.JSONArray
+import org.json.JSONObject
+import java.io.StringReader
+import java.util.ArrayList
+
+class Converters {
+    companion object {
+        private const val ACTION_NAME = "action"
+        private const val FILTERS_NAME = "filters"
+        private const val URI_PART_NAME = "uriPart"
+        private const val PATTERN_TYPE_NAME = "patternType"
+        private const val FILTER_NAME = "filter"
+    }
+
+    @TypeConverter
+    fun groupsToJson(groups: List<UriRelativeFilterGroup>): String {
+        val json = JSONArray()
+        for (group in groups) {
+            json.put(groupToJson(group))
+        }
+        return json.toString()
+    }
+
+    @TypeConverter
+    fun stringToGroups(json: String): List<UriRelativeFilterGroup> {
+        val groups = ArrayList<UriRelativeFilterGroup>()
+        StringReader(json).use { stringReader ->
+            JsonReader(stringReader).use { reader ->
+                reader.beginArray()
+                while (reader.hasNext()) {
+                    groups.add(parseGroup(reader))
+                }
+                reader.endArray()
+            }
+        }
+        return groups
+    }
+
+    private fun groupToJson(group: UriRelativeFilterGroup): JSONObject {
+        val jsonObject = JSONObject()
+        jsonObject.put(ACTION_NAME, group.action)
+        val filters = JSONArray()
+        for (filter in group.uriRelativeFilters) {
+            filters.put(filterToJson(filter))
+        }
+        jsonObject.put(FILTERS_NAME, filters)
+        return jsonObject
+    }
+
+    private fun filterToJson(filter: UriRelativeFilter): JSONObject {
+        val jsonObject = JSONObject()
+        jsonObject.put(URI_PART_NAME, filter.uriPart)
+        jsonObject.put(PATTERN_TYPE_NAME, filter.patternType)
+        jsonObject.put(FILTER_NAME, filter.filter)
+        return jsonObject
+    }
+
+    private fun parseGroup(reader: JsonReader): UriRelativeFilterGroup {
+        val jsonObject = JSONObject()
+        reader.beginObject()
+        while (reader.hasNext()) {
+            val name = reader.nextName()
+            when (name) {
+                ACTION_NAME -> jsonObject.put(ACTION_NAME, reader.nextInt())
+                FILTERS_NAME -> jsonObject.put(FILTERS_NAME, parseFilters(reader))
+                else -> reader.skipValue()
+            }
+        }
+        reader.endObject()
+
+        val group = UriRelativeFilterGroup(jsonObject.getInt(ACTION_NAME))
+        val filters = jsonObject.getJSONArray(FILTERS_NAME)
+        for (i in 0 until filters.length()) {
+            val filter = filters.getJSONObject(i)
+            group.addUriRelativeFilter(UriRelativeFilter(
+                filter.getInt(URI_PART_NAME),
+                filter.getInt(PATTERN_TYPE_NAME),
+                filter.getString(FILTER_NAME)
+            ))
+        }
+        return group
+    }
+
+    private fun parseFilters(reader: JsonReader): JSONArray {
+        val filters = JSONArray()
+        reader.beginArray()
+        while (reader.hasNext()) {
+            filters.put(parseFilter(reader))
+        }
+        reader.endArray()
+        return filters
+    }
+
+    private fun parseFilter(reader: JsonReader): JSONObject {
+        reader.beginObject()
+        val jsonObject = JSONObject()
+        while (reader.hasNext()) {
+            val name = reader.nextName()
+            when (name) {
+                URI_PART_NAME, PATTERN_TYPE_NAME -> jsonObject.put(name, reader.nextInt())
+                FILTER_NAME -> jsonObject.put(name, reader.nextString())
+                else -> reader.skipValue()
+            }
+        }
+        reader.endObject()
+        return jsonObject
+    }
+}
\ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/database/DomainGroups.kt b/packages/StatementService/src/com/android/statementservice/database/DomainGroups.kt
new file mode 100644
index 0000000..c616669
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/DomainGroups.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.statementservice.database
+
+import android.content.UriRelativeFilterGroup
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["packageName", "domain"])
+data class DomainGroups(
+    val packageName: String,
+    val domain: String,
+    val groups: List<UriRelativeFilterGroup>
+)
\ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt
new file mode 100644
index 0000000..3b4dcea
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.statementservice.database
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.Query
+
+@Dao
+interface DomainGroupsDao {
+    @Query("SELECT * FROM DomainGroups WHERE packageName = :packageName")
+    fun getDomainGroups(packageName: String): List<DomainGroups>
+
+    @Insert
+    fun insertDomainGroups(vararg domainGroups: DomainGroups)
+
+    @Query("DELETE FROM DomainGroups WHERE packageName = :packageName AND domain = :domain")
+    fun clear(packageName: String, domain: String)
+
+    @Query("DELETE FROM DomainGroups WHERE packageName = :packageName")
+    fun clear(packageName: String)
+}
\ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.kt b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.kt
new file mode 100644
index 0000000..39833f6
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.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.statementservice.database
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+
+@Database(entities = [DomainGroups::class], version = 1)
+@TypeConverters(Converters::class)
+abstract class DomainGroupsDatabase : RoomDatabase() {
+    companion object {
+        private const val DATABASE_NAME = "domain-groups"
+        @Volatile
+        private var instance: DomainGroupsDatabase? = null
+
+        fun getInstance(context: Context) = instance ?: synchronized(this) {
+            instance ?: Room.databaseBuilder(
+                context,
+                DomainGroupsDatabase::class.java, DATABASE_NAME
+            ).build().also { instance = it }
+        }
+    }
+    abstract fun domainGroupsDao(): DomainGroupsDao
+}
\ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
index acb54f6..0d7a1fd 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
@@ -22,6 +22,7 @@
 import androidx.work.ExistingWorkPolicy
 import androidx.work.WorkManager
 import com.android.statementservice.domain.worker.CollectV1Worker
+import com.android.statementservice.domain.worker.GroupUpdateV1Worker
 import com.android.statementservice.domain.worker.SingleV1RequestWorker
 
 /**
@@ -67,7 +68,7 @@
             }
         }
 
-        //clear sp before enqueue unique work since policy is REPLACE
+        // clear sp before enqueue unique work since policy is REPLACE
         val deContext = context.createDeviceProtectedStorageContext()
         val editor = deContext?.getSharedPreferences(packageName, Context.MODE_PRIVATE)?.edit()
         editor?.clear()?.apply()
@@ -78,6 +79,7 @@
                 workRequests
             )
             .then(CollectV1Worker.buildRequest(verificationId, packageName))
+            .then(GroupUpdateV1Worker.buildRequest(packageName))
             .enqueue()
     }
 }
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt
index 6944248..157a800 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt
@@ -22,6 +22,7 @@
 import androidx.work.PeriodicWorkRequestBuilder
 import androidx.work.WorkManager
 import com.android.statementservice.domain.worker.RetryRequestWorker
+import com.android.statementservice.domain.worker.UpdateVerifiedDomainsWorker
 import java.time.Duration
 
 object DomainVerificationUtils {
@@ -30,6 +31,10 @@
     private const val PERIODIC_SHORT_HOURS = 24L
     private const val PERIODIC_LONG_ID = "retry_long"
     private const val PERIODIC_LONG_HOURS = 72L
+    private const val PERIODIC_UPDATE_ID = "update"
+    private const val PERIODIC_UPDATE_HOURS = 720L
+
+    private const val UPDATE_WORKER_ENABLED = false
 
     /**
      * In a majority of cases, the initial requests will be enough to verify domains, since they
@@ -74,4 +79,38 @@
                 }
         }
     }
+
+    /**
+     * Schedule a periodic worker to check for any updates to assetlink.json files for domains that
+     * have already been verified.
+     *
+     * Due to the potential for this worker to generate enough traffic across all android devices
+     * to overwhelm websites, this method is hardcoded to be disabled by default. It is highly
+     * recommended to not enable this worker and instead implement a custom worker that pulls
+     * updates from a caching service instead of directly from websites.
+     */
+    fun schedulePeriodicUpdateUnlocked(workManager: WorkManager) {
+        if (UPDATE_WORKER_ENABLED) {
+            workManager.apply {
+                PeriodicWorkRequestBuilder<UpdateVerifiedDomainsWorker>(
+                    Duration.ofDays(
+                        PERIODIC_UPDATE_HOURS
+                    )
+                )
+                    .setConstraints(
+                        Constraints.Builder()
+                            .setRequiredNetworkType(NetworkType.CONNECTED)
+                            .setRequiresDeviceIdle(true)
+                            .build()
+                    )
+                    .build()
+                    .let {
+                        enqueueUniquePeriodicWork(
+                            PERIODIC_UPDATE_ID,
+                            ExistingPeriodicWorkPolicy.KEEP, it
+                        )
+                    }
+            }
+        }
+    }
 }
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
index 29f844f..c7f6c18 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
@@ -24,6 +24,7 @@
 import com.android.statementservice.network.retriever.StatementRetriever
 import com.android.statementservice.retriever.AbstractAsset
 import com.android.statementservice.retriever.AbstractAssetMatcher
+import com.android.statementservice.retriever.Statement
 import com.android.statementservice.utils.Result
 import com.android.statementservice.utils.StatementUtils
 import com.android.statementservice.utils.component1
@@ -63,7 +64,8 @@
 
     private val targetAssetCache = AssetLruCache()
 
-    fun collectHosts(packageNames: Iterable<String>): Iterable<Triple<UUID, String, String>> {
+    fun collectHosts(packageNames: Iterable<String>, statusFilter: (Int) -> Boolean):
+            Iterable<Triple<UUID, String, Iterable<String>>> {
         return packageNames.mapNotNull { packageName ->
             val (domainSetId, _, hostToStateMap) = try {
                 manager.getDomainVerificationInfo(packageName)
@@ -73,24 +75,23 @@
             } ?: return@mapNotNull null
 
             val hostsToRetry = hostToStateMap
-                .filterValues(VerifyStatus::shouldRetry)
+                .filterValues(statusFilter)
                 .takeIf { it.isNotEmpty() }
                 ?.map { it.key }
                 ?: return@mapNotNull null
 
-            hostsToRetry.map { Triple(domainSetId, packageName, it) }
+            Triple(domainSetId, packageName, hostsToRetry)
         }
-            .flatten()
     }
 
     suspend fun verifyHost(
         host: String,
         packageName: String,
         network: Network? = null
-    ): Pair<WorkResult, VerifyStatus> {
+    ): Triple<WorkResult, VerifyStatus, Statement?> {
         val assetMatcher = synchronized(targetAssetCache) { targetAssetCache[packageName] }
             .takeIf { it!!.isPresent }
-            ?: return WorkResult.failure() to VerifyStatus.FAILURE_PACKAGE_MANAGER
+            ?: return Triple(WorkResult.failure(), VerifyStatus.FAILURE_PACKAGE_MANAGER, null)
         return verifyHost(host, assetMatcher.get(), network)
     }
 
@@ -98,34 +99,34 @@
         host: String,
         assetMatcher: AbstractAssetMatcher,
         network: Network? = null
-    ): Pair<WorkResult, VerifyStatus> {
+    ): Triple<WorkResult, VerifyStatus, Statement?> {
         var exception: Exception? = null
         val resultAndStatus = try {
             val sourceAsset = StatementUtils.createWebAssetString(host)
                 .let(AbstractAsset::create)
             val result = retriever.retrieve(sourceAsset, network)
-                ?: return WorkResult.success() to VerifyStatus.FAILURE_UNKNOWN
+                ?: return Triple(WorkResult.success(), VerifyStatus.FAILURE_UNKNOWN, null)
             when (result.responseCode) {
                 HttpURLConnection.HTTP_MOVED_PERM,
                 HttpURLConnection.HTTP_MOVED_TEMP -> {
-                    WorkResult.failure() to VerifyStatus.FAILURE_REDIRECT
+                    Triple(WorkResult.failure(), VerifyStatus.FAILURE_REDIRECT, null)
                 }
                 else -> {
-                    val isVerified = result.statements.any { statement ->
+                    val statement = result.statements.firstOrNull { statement ->
                         (StatementUtils.RELATION.matches(statement.relation) &&
                                 assetMatcher.matches(statement.target))
                     }
 
-                    if (isVerified) {
-                        WorkResult.success() to VerifyStatus.SUCCESS
+                    if (statement != null) {
+                        Triple(WorkResult.success(), VerifyStatus.SUCCESS, statement)
                     } else {
-                        WorkResult.failure() to VerifyStatus.FAILURE_REJECTED_BY_SERVER
+                        Triple(WorkResult.failure(), VerifyStatus.FAILURE_REJECTED_BY_SERVER, statement)
                     }
                 }
             }
         } catch (e: Exception) {
             exception = e
-            WorkResult.retry() to VerifyStatus.FAILURE_UNKNOWN
+            Triple(WorkResult.retry(), VerifyStatus.FAILURE_UNKNOWN, null)
         }
 
         if (DEBUG) {
diff --git a/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt b/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt
index 2193ec5..c771da3 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt
@@ -49,7 +49,7 @@
                 return false
             }
 
-            val status = values().find { it.value == state } ?: return true
+            val status = entries.find { it.value == state } ?: return true
             return when (status) {
                 SUCCESS,
                 FAILURE_LEGACY_UNSUPPORTED_WILDCARD,
@@ -62,5 +62,20 @@
                 FAILURE_REDIRECT -> true
             }
         }
+
+        fun canUpdate(state: Int): Boolean {
+            if (state == DomainVerificationInfo.STATE_UNMODIFIABLE) {
+                return false
+            }
+
+            val status = entries.find { it.value == state }
+            return when (status) {
+                SUCCESS,
+                FAILURE_LEGACY_UNSUPPORTED_WILDCARD,
+                FAILURE_REJECTED_BY_SERVER,
+                UNKNOWN -> true
+                else -> false
+            }
+        }
     }
 }
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt
index a17f9c9..64d2d98 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt
@@ -17,9 +17,12 @@
 package com.android.statementservice.domain.worker
 
 import android.content.Context
+import android.content.UriRelativeFilterGroup
+import android.content.pm.verify.domain.DomainVerificationInfo
 import android.content.pm.verify.domain.DomainVerificationManager
 import androidx.work.CoroutineWorker
 import androidx.work.WorkerParameters
+import com.android.statementservice.database.DomainGroupsDatabase
 import com.android.statementservice.domain.DomainVerifier
 
 abstract class BaseRequestWorker(
@@ -27,8 +30,19 @@
     protected val params: WorkerParameters
 ) : CoroutineWorker(appContext, params) {
 
+    protected val database = DomainGroupsDatabase.getInstance(appContext).domainGroupsDao()
+
     protected val verificationManager =
         appContext.getSystemService(DomainVerificationManager::class.java)!!
 
     protected val verifier = DomainVerifier.getInstance(appContext)
+
+    protected fun updateUriRelativeFilterGroups(packageName: String, domainGroupUpdates: Map<String, List<UriRelativeFilterGroup>>) {
+        val verifiedDomains = verificationManager.getDomainVerificationInfo(packageName)?.hostToStateMap?.filterValues {
+            it == DomainVerificationInfo.STATE_SUCCESS || it == DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED
+        }?.keys?.toList() ?: emptyList()
+        val domainGroups = verificationManager.getUriRelativeFilterGroups(packageName, verifiedDomains)
+        domainGroupUpdates.forEach { (domain, groups) -> domainGroups[domain] = groups }
+        verificationManager.setUriRelativeFilterGroups(packageName, domainGroups)
+    }
 }
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt
new file mode 100644
index 0000000..f53dfc4
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt
@@ -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.statementservice.domain.worker
+
+import android.content.Context
+import androidx.work.Data
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkerParameters
+import kotlinx.coroutines.coroutineScope
+
+class GroupUpdateV1Worker(appContext: Context, params: WorkerParameters) :
+    BaseRequestWorker(appContext, params) {
+
+    companion object {
+
+        private const val PACKAGE_NAME_KEY = "packageName"
+
+        fun buildRequest(packageName: String) = OneTimeWorkRequestBuilder<GroupUpdateV1Worker>()
+            .setInputData(
+                Data.Builder()
+                    .putString(PACKAGE_NAME_KEY, packageName)
+                    .build()
+            )
+            .build()
+    }
+
+    override suspend fun doWork() = coroutineScope {
+        val packageName = params.inputData.getString(PACKAGE_NAME_KEY)!!
+        updateUriRelativeFilterGroups(packageName)
+        Result.success()
+    }
+
+    private fun updateUriRelativeFilterGroups(packageName: String) {
+        val groupUpdates = database.getDomainGroups(packageName)
+        updateUriRelativeFilterGroups(
+            packageName,
+            groupUpdates.associateBy({it.domain}, {it.groups})
+        )
+        database.clear(packageName)
+    }
+}
\ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/PeriodicUpdateWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/PeriodicUpdateWorker.kt
new file mode 100644
index 0000000..7ec6e6c
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/PeriodicUpdateWorker.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.statementservice.domain.worker
+
+import android.content.Context
+import android.content.UriRelativeFilterGroup
+import android.content.pm.verify.domain.DomainVerificationManager
+import androidx.work.ListenableWorker
+import androidx.work.WorkerParameters
+import com.android.statementservice.domain.VerifyStatus
+import com.android.statementservice.utils.AndroidUtils
+import com.android.statementservice.utils.StatementUtils
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.isActive
+
+abstract class PeriodicUpdateWorker(
+    appContext: Context,
+    params: WorkerParameters
+) : BaseRequestWorker(appContext, params) {
+
+    data class VerifyResult(
+        val host: String,
+        val status: VerifyStatus,
+        val groups: List<UriRelativeFilterGroup>
+    )
+
+    protected suspend fun updateDomainVerificationStatus(verifyStatusFilter: (Int) -> Boolean):
+            ListenableWorker.Result {
+        return coroutineScope {
+            if (!AndroidUtils.isReceiverV2Enabled(appContext)) {
+                return@coroutineScope Result.success()
+            }
+
+            val packageNames = verificationManager.queryValidVerificationPackageNames()
+
+            verifier.collectHosts(packageNames, verifyStatusFilter)
+                .map { (domainSetId, packageName, hosts) ->
+                    hosts.map { host ->
+                        async {
+                            if (isActive && !isStopped) {
+                                val (_, status, statement) = verifier.verifyHost(
+                                    host,
+                                    packageName,
+                                    params.network
+                                )
+                                val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
+                                    StatementUtils.createUriRelativeFilterGroup(it)
+                                }
+                                VerifyResult(host, status, groups)
+                            } else {
+                                // If the job gets cancelled, stop the remaining hosts, but continue the
+                                // job to commit the results for hosts that were already requested.
+                                null
+                            }
+                        }
+                    }.awaitAll().filterNotNull().groupBy { it.status }
+                        .forEach { (status, results) ->
+                            val error = verificationManager.setDomainVerificationStatus(
+                                domainSetId,
+                                results.map { it.host }.toSet(),
+                                status.value
+                            )
+                            if (error == DomainVerificationManager.STATUS_OK
+                                && status == VerifyStatus.SUCCESS
+                            ) {
+                                updateUriRelativeFilterGroups(
+                                    packageName,
+                                    results.associateBy({ it.host }, { it.groups })
+                                )
+                            }
+                        }
+                }
+
+            // Succeed regardless of results since this retry is best effort and not required
+            Result.success()
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
index 61ab2c2..e8b4df9 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
@@ -20,12 +20,6 @@
 import androidx.work.NetworkType
 import androidx.work.WorkerParameters
 import com.android.statementservice.domain.VerifyStatus
-import com.android.statementservice.utils.AndroidUtils
-import kotlinx.coroutines.async
-import kotlinx.coroutines.awaitAll
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.isActive
-import java.util.UUID
 
 /**
  * Scheduled every 24 hours with [NetworkType.CONNECTED] and every 72 hours without any constraints
@@ -34,46 +28,7 @@
 class RetryRequestWorker(
     appContext: Context,
     params: WorkerParameters
-) : BaseRequestWorker(appContext, params) {
+) : PeriodicUpdateWorker(appContext, params) {
 
-    data class VerifyResult(val domainSetId: UUID, val host: String, val status: VerifyStatus)
-
-    override suspend fun doWork() = coroutineScope {
-        if (!AndroidUtils.isReceiverV2Enabled(appContext)) {
-            return@coroutineScope Result.success()
-        }
-
-        val packageNames = verificationManager.queryValidVerificationPackageNames()
-
-        verifier.collectHosts(packageNames)
-            .map { (domainSetId, packageName, host) ->
-                async {
-                    if (isActive && !isStopped) {
-                        val (_, status) = verifier.verifyHost(host, packageName, params.network)
-                        VerifyResult(domainSetId, host, status)
-                    } else {
-                        // If the job gets cancelled, stop the remaining hosts, but continue the
-                        // job to commit the results for hosts that were already requested.
-                        null
-                    }
-                }
-            }
-            .awaitAll()
-            .filterNotNull() // TODO(b/159952358): Fast fail packages which can't be retrieved.
-            .groupBy { it.domainSetId }
-            .forEach { (domainSetId, resultsById) ->
-                resultsById.groupBy { it.status }
-                    .mapValues { it.value.map(VerifyResult::host).toSet() }
-                    .forEach { (status, hosts) ->
-                        verificationManager.setDomainVerificationStatus(
-                            domainSetId,
-                            hosts,
-                            status.value
-                        )
-                    }
-            }
-
-        // Succeed regardless of results since this retry is best effort and not required
-        Result.success()
-    }
+    override suspend fun doWork() = updateDomainVerificationStatus(VerifyStatus::shouldRetry)
 }
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
index 7a198cb..253a162 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
@@ -22,7 +22,9 @@
 import androidx.work.OneTimeWorkRequest
 import androidx.work.OneTimeWorkRequestBuilder
 import androidx.work.WorkerParameters
+import com.android.statementservice.database.DomainGroups
 import com.android.statementservice.utils.AndroidUtils
+import com.android.statementservice.utils.StatementUtils
 import kotlinx.coroutines.coroutineScope
 
 class SingleV1RequestWorker(appContext: Context, params: WorkerParameters) :
@@ -60,7 +62,9 @@
         val packageName = params.inputData.getString(PACKAGE_NAME_KEY)!!
         val host = params.inputData.getString(HOST_KEY)!!
 
-        val (result, status) = verifier.verifyHost(host, packageName, params.network)
+        database.clear(packageName, host)
+
+        val (result, status, statement) = verifier.verifyHost(host, packageName, params.network)
 
         if (DEBUG) {
             Log.d(
@@ -75,6 +79,10 @@
                 val deContext = appContext.createDeviceProtectedStorageContext()
                 val sp = deContext?.getSharedPreferences(packageName, Context.MODE_PRIVATE)
                 sp?.edit()?.putInt("$HOST_SUCCESS_PREFIX$host", status.value)?.apply()
+                val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
+                    StatementUtils.createUriRelativeFilterGroup(it)
+                }
+                database.insertDomainGroups(DomainGroups(packageName, host, groups))
                 Result.success()
             }
             is Result.Failure -> {
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt
index 562b132..8b1347a 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt
@@ -22,6 +22,7 @@
 import androidx.work.OneTimeWorkRequestBuilder
 import androidx.work.WorkerParameters
 import com.android.statementservice.utils.AndroidUtils
+import com.android.statementservice.utils.StatementUtils
 import kotlinx.coroutines.coroutineScope
 import java.util.UUID
 
@@ -59,9 +60,13 @@
         val packageName = params.inputData.getString(PACKAGE_NAME_KEY)!!
         val host = params.inputData.getString(HOST_KEY)!!
 
-        val (result, status) = verifier.verifyHost(host, packageName, params.network)
+        val (result, status, statement) = verifier.verifyHost(host, packageName, params.network)
 
         verificationManager.setDomainVerificationStatus(domainSetId, setOf(host), status.value)
+        val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
+            StatementUtils.createUriRelativeFilterGroup(it)
+        }
+        updateUriRelativeFilterGroups(packageName, mapOf(host to groups))
 
         result
     }
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/UpdateVerifiedDomainsWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/UpdateVerifiedDomainsWorker.kt
new file mode 100644
index 0000000..c6f40c8
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/UpdateVerifiedDomainsWorker.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.statementservice.domain.worker
+
+import android.content.Context
+import androidx.work.WorkerParameters
+import com.android.statementservice.domain.VerifyStatus
+
+class UpdateVerifiedDomainsWorker(
+    appContext: Context,
+    params: WorkerParameters
+) : PeriodicUpdateWorker(appContext, params) {
+
+    override suspend fun doWork() = updateDomainVerificationStatus(VerifyStatus::canUpdate)
+}
\ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
index ad137400..d10cb0f 100644
--- a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
+++ b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
@@ -39,6 +39,11 @@
 
     private const val FIELD_NOT_STRING_FORMAT_STRING = "Expected %s to be string."
     private const val FIELD_NOT_ARRAY_FORMAT_STRING = "Expected %s to be array."
+    private const val COMMENTS_NAME = "comments"
+    private const val EXCLUDE_NAME = "exclude"
+    private const val FRAGMENT_NAME = "#"
+    private const val QUERY_NAME = "?"
+    private const val PATH_NAME = "/"
 
     /**
      * Parses a JSON array of statements.
@@ -99,9 +104,7 @@
                 FIELD_NOT_ARRAY_FORMAT_STRING.format(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION)
             )
         val target = AssetFactory.create(targetObject)
-        val dynamicAppLinkComponents = parseDynamicAppLinkComponents(
-            statement.optJSONObject(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS)
-        )
+        val dynamicAppLinkComponents = parseDynamicAppLinkComponents(statement)
 
         val statements = (0 until relations.length())
             .map { relations.getString(it) }
@@ -129,13 +132,13 @@
     }
 
     private fun parseComponent(component: JSONObject): DynamicAppLinkComponent {
-        val query = component.optJSONObject("?")
+        val query = component.optJSONObject(QUERY_NAME)
         return DynamicAppLinkComponent.create(
-            component.optBoolean("exclude", false),
-            component.optString("#"),
-            component.optString("/"),
+            component.optBoolean(EXCLUDE_NAME, false),
+            if (component.has(FRAGMENT_NAME)) component.getString(FRAGMENT_NAME) else null,
+            if (component.has(PATH_NAME)) component.getString(PATH_NAME) else null,
             query?.keys()?.asSequence()?.associateWith { query.getString(it) },
-            component.optString("comments")
+            component.optString(COMMENTS_NAME)
         )
     }
 
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java b/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
index dc27e12..c32f194 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
@@ -130,7 +130,7 @@
     @Override
     public String toString() {
         StringBuilder statement = new StringBuilder();
-        statement.append("HandleAllUriRule: ");
+        statement.append("DynamicAppLinkComponent: ");
         statement.append(mExclude);
         statement.append(", ");
         statement.append(mFragment);
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
index 7635e82..ab1853c 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
@@ -24,8 +24,6 @@
 import org.json.JSONObject;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * A helper class that creates a {@link JSONObject} from a {@link JsonReader}.
@@ -48,7 +46,7 @@
 
             JsonToken token = reader.peek();
             if (token.equals(JsonToken.BEGIN_ARRAY)) {
-                output.put(fieldName, new JSONArray(parseArray(reader)));
+                output.put(fieldName, parseArray(reader));
             } else if (token.equals(JsonToken.STRING)) {
                 output.put(fieldName, reader.nextString());
             } else if (token.equals(JsonToken.BEGIN_OBJECT)) {
@@ -57,9 +55,11 @@
                 } catch (JSONException e) {
                     errorMsg = e.getMessage();
                 }
+            } else if (token.equals(JsonToken.BOOLEAN)) {
+                output.put(fieldName, reader.nextBoolean());
             } else {
                 reader.skipValue();
-                errorMsg = "Unsupported value type.";
+                errorMsg = "Unsupported value type: " + token;
             }
         }
         reader.endObject();
@@ -72,17 +72,36 @@
     }
 
     /**
-     * Parses one string array from the {@link JsonReader}.
+     * Parses one JSON array from the {@link JsonReader}.
      */
-    public static List<String> parseArray(JsonReader reader) throws IOException {
-        ArrayList<String> output = new ArrayList<>();
+    public static JSONArray parseArray(JsonReader reader) throws IOException, JSONException {
+        JSONArray output = new JSONArray();
+        String errorMsg = null;
 
         reader.beginArray();
         while (reader.hasNext()) {
-            output.add(reader.nextString());
+            JsonToken token = reader.peek();
+            if (token.equals(JsonToken.BEGIN_ARRAY)) {
+                output.put(parseArray(reader));
+            } else if (token.equals(JsonToken.STRING)) {
+                output.put(reader.nextString());
+            } else if (token.equals(JsonToken.BEGIN_OBJECT)) {
+                try {
+                    output.put(parse(reader));
+                } catch (JSONException e) {
+                    errorMsg = e.getMessage();
+                }
+            } else {
+                reader.skipValue();
+                errorMsg = "Unsupported value type: " + token;
+            }
         }
         reader.endArray();
 
+        if (errorMsg != null) {
+            throw new JSONException(errorMsg);
+        }
+
         return output;
     }
 }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index d1a22e8..3d250fd 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -81,33 +81,37 @@
     visibility: ["//visibility:private"],
 }
 
-// Tests where robolectric conversion caused errors in SystemUITests at runtime
-filegroup {
-    name: "SystemUI-tests-broken-robofiles-sysui-run",
-    srcs: [
-        "tests/src/**/systemui/broadcast/BroadcastDispatcherTest.kt",
-        "tests/src/**/systemui/broadcast/ActionReceiverTest.kt",
-        "tests/src/**/systemui/globalactions/GlobalActionsDialogLiteTest.java",
-        "tests/src/**/systemui/globalactions/GlobalActionsImeTest.java",
-        "tests/src/**/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt",
-        "tests/src/**/systemui/media/dialog/MediaOutputAdapterTest.java",
-        "tests/src/**/systemui/media/dialog/MediaOutputBaseDialogTest.java",
-        "tests/src/**/systemui/media/dialog/MediaOutputBroadcastDialogTest.java",
-        "tests/src/**/systemui/media/dialog/MediaOutputDialogTest.java",
-        "tests/src/**/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt",
-        "tests/src/**/systemui/settings/brightness/BrightnessDialogTest.kt",
-    ],
-}
-
 // Tests where robolectric failed at runtime. (go/central-multivalent)
 filegroup {
     name: "SystemUI-tests-broken-robofiles-run",
     srcs: [
-        "tests/src/**/systemui/ExpandHelperTest.java",
+        "tests/src/**/systemui/shade/NotificationShadeWindowViewControllerTest.kt",
+        "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt",
+        "tests/src/**/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt",
+        "tests/src/**/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt",
+        "tests/src/**/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt",
+        "tests/src/**/systemui/education/domain/ui/view/ContextualEduDialogTest.kt",
+        "tests/src/**/systemui/screenshot/ActionIntentCreatorTest.kt",
+        "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt",
+        "tests/src/**/systemui/accessibility/WindowMagnificationControllerTest.java",
+        "tests/src/**/systemui/broadcast/BroadcastDispatcherTest.kt",
+        "tests/src/**/systemui/globalactions/GlobalActionsDialogLiteTest.java",
+        "tests/src/**/systemui/globalactions/GlobalActionsImeTest.java",
+        "tests/src/**/systemui/media/dialog/MediaOutputBaseDialogTest.java",
+        "tests/src/**/systemui/media/dialog/MediaOutputBroadcastDialogTest.java",
+        "tests/src/**/systemui/media/dialog/MediaOutputDialogTest.java",
+        "tests/src/**/systemui/settings/brightness/BrightnessDialogTest.kt",
+        "tests/src/**/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt",
+        "tests/src/**/systemui/statusbar/policy/SecurityControllerTest.java",
+        "tests/src/**/systemui/lifecycle/SysUiViewModelTest.kt",
+        "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt",
+        "tests/src/**/systemui/graphics/ImageLoaderContentProviderTest.kt",
+        "tests/src/**/systemui/flags/FakeFeatureFlagsTest.kt",
+        "tests/src/**/systemui/communal/data/backup/CommunalBackupUtilsTest.kt",
         "tests/src/**/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java",
         "tests/src/**/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java",
         "tests/src/**/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java",
-        "tests/src/**/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java",
         "tests/src/**/systemui/screenshot/appclips/AppClipsActivityTest.java",
         "tests/src/**/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java",
         "tests/src/**/systemui/screenshot/appclips/AppClipsViewModelTest.java",
@@ -124,36 +128,27 @@
         "tests/src/**/systemui/classifier/FalsingDataProviderTest.java",
         "tests/src/**/systemui/screenshot/ImageExporterTest.java",
         "tests/src/**/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt",
-        "tests/src/**/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt",
         "tests/src/**/systemui/logcat/LogAccessDialogActivityTest.java",
         "tests/src/**/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt",
         "tests/src/**/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt",
         "tests/src/**/systemui/accessibility/floatingmenu/MenuNotificationFactoryTest.java",
         "tests/src/**/systemui/accessibility/floatingmenu/MenuViewLayerTest.java",
-        "tests/src/**/systemui/accessibility/floatingmenu/MenuViewTest.java",
         "tests/src/**/systemui/classifier/PointerCountClassifierTest.java",
         "tests/src/**/systemui/accessibility/floatingmenu/RadiiAnimatorTest.java",
         "tests/src/**/systemui/screenrecord/RecordingControllerTest.java",
         "tests/src/**/systemui/screenshot/RequestProcessorTest.kt",
         "tests/src/**/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt",
-        "tests/src/**/systemui/screenshot/SaveImageInBackgroundTaskTest.kt",
         "tests/src/**/systemui/screenshot/scroll/ScrollCaptureClientTest.java",
         "tests/src/**/systemui/accessibility/SecureSettingsContentObserverTest.java",
         "tests/src/**/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt",
         "tests/src/**/systemui/qs/external/TileServicesTest.java",
         "tests/src/**/systemui/ambient/touch/TouchMonitorTest.java",
-        "tests/src/**/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java",
         "tests/src/**/systemui/accessibility/WindowMagnificationSettingsTest.java",
-        "tests/src/androidx/core/animation/AnimatorTestRuleIsolationTest.kt",
         "tests/src/**/systemui/CameraProtectionLoaderImplTest.kt",
-        "tests/src/**/systemui/DependencyTest.java",
-        "tests/src/**/systemui/InitControllerTest.java",
         "tests/src/**/systemui/SliceBroadcastRelayHandlerTest.java",
         "tests/src/**/systemui/SystemUIApplicationTest.kt",
         "tests/src/**/systemui/SysUICutoutProviderTest.kt",
-        "tests/src/**/keyguard/ActiveUnlockConfigTest.kt",
         "tests/src/**/keyguard/AdminSecondaryLockScreenControllerTest.java",
-        "tests/src/**/keyguard/KeyguardClockAccessibilityDelegateTest.java",
         "tests/src/**/keyguard/KeyguardStatusViewControllerTest.java",
         "tests/src/**/systemui/accessibility/AccessibilityButtonModeObserverTest.java",
         "tests/src/**/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java",
@@ -164,16 +159,12 @@
         "tests/src/**/systemui/animation/TextAnimatorTest.kt",
         "tests/src/**/systemui/animation/TextInterpolatorTest.kt",
         "tests/src/**/systemui/animation/ActivityTransitionAnimatorTest.kt",
-        "tests/src/**/systemui/animation/AnimatorTestRuleOrderTest.kt",
         "tests/src/**/systemui/animation/DialogTransitionAnimatorTest.kt",
-        "tests/src/**/systemui/broadcast/ActionReceiverTest.kt",
         "tests/src/**/systemui/broadcast/BroadcastDispatcherTest.kt",
-        "tests/src/**/systemui/compose/ComposeInitializerTest.kt",
         "tests/src/**/systemui/controls/ui/ControlsActivityTest.kt",
         "tests/src/**/systemui/controls/management/ControlsEditingActivityTest.kt",
         "tests/src/**/systemui/controls/management/ControlsRequestDialogTest.kt",
         "tests/src/**/systemui/controls/ui/DetailDialogTest.kt",
-        "tests/src/**/systemui/fontscaling/FontScalingDialogDelegateTest.kt",
         "tests/src/**/systemui/keyguard/CustomizationProviderTest.kt",
         "tests/src/**/systemui/globalactions/GlobalActionsColumnLayoutTest.java",
         "tests/src/**/systemui/globalactions/GlobalActionsDialogLiteTest.java",
@@ -182,10 +173,6 @@
         "tests/src/**/systemui/keyguard/CustomizationProviderTest.kt",
         "tests/src/**/systemui/keyguard/KeyguardViewMediatorTest.java",
         "tests/src/**/systemui/keyguard/LifecycleTest.java",
-        "tests/src/**/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt",
-        "tests/src/**/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt",
-        "tests/src/**/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt",
-        "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt",
         "tests/src/**/systemui/lifecycle/RepeatWhenAttachedTest.kt",
         "tests/src/**/systemui/log/LogBufferTest.kt",
         "tests/src/**/systemui/media/dialog/MediaOutputBaseDialogTest.java",
@@ -193,52 +180,35 @@
         "tests/src/**/systemui/media/dialog/MediaOutputDialogTest.java",
         "tests/src/**/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt",
         "tests/src/**/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt",
-        "tests/src/**/systemui/navigationbar/views/NavigationBarButtonTest.java",
         "tests/src/**/systemui/people/PeopleProviderTest.java",
         "tests/src/**/systemui/people/PeopleSpaceUtilsTest.java",
         "tests/src/**/systemui/people/widget/PeopleSpaceWidgetManagerTest.java",
         "tests/src/**/systemui/people/PeopleTileViewHelperTest.java",
         "tests/src/**/systemui/power/data/repository/PowerRepositoryImplTest.kt",
-        "tests/src/**/systemui/privacy/PrivacyConfigFlagsTest.kt",
-        "tests/src/**/systemui/privacy/PrivacyDialogV2Test.kt",
-        "tests/src/**/systemui/qs/external/TileRequestDialogEventLoggerTest.kt",
-        "tests/src/**/systemui/qs/AutoAddTrackerTest.kt",
-        "tests/src/**/systemui/qs/external/TileRequestDialogEventLoggerTest.kt",
         "tests/src/**/systemui/qs/tiles/DndTileTest.kt",
         "tests/src/**/systemui/qs/tiles/DreamTileTest.java",
-        "tests/src/**/systemui/qs/FgsManagerControllerTest.java",
         "tests/src/**/systemui/qs/QSPanelTest.kt",
         "tests/src/**/systemui/reardisplay/RearDisplayCoreStartableTest.kt",
         "tests/src/**/systemui/reardisplay/RearDisplayDialogControllerTest.java",
         "tests/src/**/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt",
         "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/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/wrapper/NotificationTemplateViewWrapperTest.kt",
         "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",
@@ -252,13 +222,9 @@
         "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/policy/ui/dialog/ModesDialogDelegateTest.kt",
-        "tests/src/**/systemui/statusbar/StatusBarStateControllerImplTest.kt",
         "tests/src/**/systemui/theme/ThemeOverlayApplierTest.java",
         "tests/src/**/systemui/touch/TouchInsetManagerTest.java",
         "tests/src/**/systemui/util/LifecycleFragmentTest.java",
-        "tests/src/**/systemui/util/kotlin/PairwiseFlowTest",
-        "tests/src/**/systemui/util/sensors/AsyncManagerTest.java",
         "tests/src/**/systemui/util/sensors/ThresholdSensorImplTest.java",
         "tests/src/**/systemui/volume/VolumeDialogImplTest.java",
         "tests/src/**/systemui/wallet/controller/QuickAccessWalletControllerTest.java",
@@ -271,26 +237,17 @@
         "tests/src/**/systemui/clipboardoverlay/ClipboardListenerTest.java",
         "tests/src/**/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt",
         "tests/src/**/systemui/communal/data/db/CommunalWidgetDaoTest.kt",
-        "tests/src/**/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt",
         "tests/src/**/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt",
         "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt",
         "tests/src/**/systemui/lifecycle/ActivatableTest.kt",
-        "tests/src/**/systemui/lifecycle/HydratorTest.kt",
         "tests/src/**/systemui/media/dialog/MediaSwitchingControllerTest.java",
         "tests/src/**/systemui/qs/QSImplTest.java",
         "tests/src/**/systemui/qs/panels/ui/compose/DragAndDropTest.kt",
         "tests/src/**/systemui/qs/panels/ui/compose/ResizingTest.kt",
         "tests/src/**/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java",
-        "tests/src/**/systemui/accessibility/floatingmenu/PositionTest.java",
         "tests/src/**/systemui/animation/TransitionAnimatorTest.kt",
-        "tests/src/**/systemui/screenshot/scroll/ScrollCaptureControllerTest.java",
-        "tests/src/**/systemui/lifecycle/SysuiViewModelTest.kt",
-        "tests/src/**/systemui/flags/FakeFeatureFlags.kt",
         "tests/src/**/systemui/animation/TransitionAnimatorTest.kt",
-        "tests/src/**/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java",
         "tests/src/**/systemui/statusbar/connectivity/NetworkControllerSignalTest.java",
-        "tests/src/**/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt",
-        "tests/src/**/systemui/statusbar/policy/RotationLockControllerImplTest.java",
         "tests/src/**/systemui/statusbar/phone/ScrimControllerTest.java",
         "tests/src/**/systemui/toast/ToastUITest.java",
         "tests/src/**/systemui/statusbar/policy/FlashlightControllerImplTest.kt",
@@ -329,47 +286,48 @@
         "tests/src/**/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java",
         "tests/src/**/systemui/qs/external/TileLifecycleManagerTest.java",
         "tests/src/**/systemui/ScreenDecorationsTest.java",
+        "tests/src/**/systemui/statusbar/policy/BatteryControllerStartableTest.java",
         "tests/src/**/keyguard/CarrierTextManagerTest.java",
         "tests/src/**/keyguard/KeyguardUpdateMonitorTest.java",
     ],
 }
 
-// Tests where robolectric failed at compile time. (go/multivalent-tests)
+// Tests where compilation failed due to kotlin internal references.
 filegroup {
-    name: "SystemUI-tests-broken-robofiles-compile",
+    name: "SystemUI-tests-broken-robofiles-internal",
     srcs: [
-        "tests/src/**/systemui/statusbar/notification/icon/IconManagerTest.kt",
-        "tests/src/**/systemui/statusbar/KeyguardIndicationControllerTest.java",
-        "tests/src/**/systemui/doze/DozeScreenStateTest.java",
-        "tests/src/**/systemui/notetask/NoteTaskInitializerTest.kt",
-        "tests/src/**/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt",
-        "tests/src/**/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt",
-        "tests/src/**/systemui/controls/management/ControlsFavoritingActivityTest.kt",
-        "tests/src/**/systemui/controls/management/ControlsProviderSelectorActivityTest.kt",
-        "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt",
-        "tests/src/**/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt",
-        "tests/src/**/systemui/qs/tileimpl/QSTileViewImplTest.kt",
+        "tests/src/**/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt",
+        "tests/src/**/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt",
+        "tests/src/**/android/systemui/statusbar/notification/icon/IconManagerTest.kt",
+        "tests/src/**/android/systemui/notetask/NoteTaskInitializerTest.kt",
+        "tests/src/**/systemui/statusbar/policy/VariableDateViewControllerTest.kt",
+        "tests/src/**/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt",
+        "tests/src/**/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt",
         "tests/src/**/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt",
+        "tests/src/**/systemui/statusbar/policy/WalletControllerImplTest.kt",
         "tests/src/**/keyguard/ClockEventControllerTest.kt",
-        "tests/src/**/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt",
+        "tests/src/**/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt",
+        "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt",
         "tests/src/**/systemui/broadcast/UserBroadcastDispatcherTest.kt",
         "tests/src/**/systemui/charging/WiredChargingRippleControllerTest.kt",
         "tests/src/**/systemui/clipboardoverlay/ClipboardModelTest.kt",
         "tests/src/**/systemui/controls/controller/AuxiliaryPersistenceWrapperTest.kt",
-        "tests/src/**/systemui/controls/controller/ControlsBindingControllerImplTest.kt",
         "tests/src/**/systemui/controls/controller/ControlsControllerImplTest.kt",
         "tests/src/**/systemui/controls/controller/DeletionJobServiceTest.kt",
-        "tests/src/**/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt",
+        "tests/src/**/systemui/controls/management/ControlsFavoritingActivityTest.kt",
         "tests/src/**/systemui/controls/ui/ControlsUiControllerImplTest.kt",
-        "tests/src/**/systemui/controls/ui/ControlViewHolderTest.kt",
         "tests/src/**/systemui/controls/ui/SelectionItemTest.kt",
         "tests/src/**/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt",
         "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt",
-        "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt",
+        "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt",
+        "tests/src/**/systemui/media/controls/ui/MediaPlayerDataTest.kt",
         "tests/src/**/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt",
         "tests/src/**/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt",
         "tests/src/**/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt",
@@ -377,7 +335,6 @@
         "tests/src/**/systemui/media/controls/ui/controller/MediaControlPanelTest.kt",
         "tests/src/**/systemui/media/controls/ui/controller/MediaViewControllerTest.kt",
         "tests/src/**/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt",
-        "tests/src/**/systemui/media/controls/ui/MediaPlayerDataTest.kt",
         "tests/src/**/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt",
         "tests/src/**/systemui/navigationbar/gestural/BackPanelControllerTest.kt",
         "tests/src/**/systemui/notetask/NoteTaskControllerTest.kt",
@@ -386,61 +343,61 @@
         "tests/src/**/systemui/qs/external/CustomTileStatePersisterTest.kt",
         "tests/src/**/systemui/qs/external/TileRequestDialogTest.kt",
         "tests/src/**/systemui/qs/external/TileServiceRequestControllerTest.kt",
-        "tests/src/**/systemui/qs/tileimpl/TilesStatesTextTest.kt",
+        "tests/src/**/systemui/qs/tileimpl/QSTileViewImplTest.kt",
         "tests/src/**/systemui/qs/tiles/AlarmTileTest.kt",
         "tests/src/**/systemui/qs/tiles/BluetoothTileTest.kt",
-        "tests/src/**/systemui/screenshot/ScreenshotPolicyImplTest.kt",
-        "tests/src/**/systemui/settings/DisplayTrackerImplTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt",
+        "tests/src/**/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt",
+        "tests/src/**/systemui/statusbar/phone/FoldStateListenerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/TextPrecomputerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt",
+        "tests/src/**/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt",
+        "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt",
+        "tests/src/**/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt",
+        "tests/src/**/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt",
+        "tests/src/**/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt",
+        "tests/src/**/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt",
+        "tests/src/**/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt",
+        "tests/src/**/systemui/statusbar/notification/RoundableTest.kt",
+        "tests/src/**/systemui/stylus/StylusUsiPowerUiTest.kt",
+        "tests/src/**/systemui/statusbar/gesture/GenericGestureDetectorTest.kt",
+        "tests/src/**/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt",
+        "tests/src/**/systemui/statusbar/connectivity/MobileStateTest.kt",
+        "tests/src/**/systemui/statusbar/commandline/CommandParserTest.kt",
+        "tests/src/**/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt",
+        "tests/src/**/systemui/statusbar/LightRevealScrimTest.kt",
+        "tests/src/**/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt",
+        "tests/src/**/systemui/shade/ShadeExpansionStateManagerTest.kt",
+        "tests/src/**/systemui/shade/ShadeHeaderControllerTest.kt",
+        "tests/src/**/systemui/shade/NotificationsQSContainerControllerTest.kt",
         "tests/src/**/systemui/settings/UserFileManagerImplTest.kt",
         "tests/src/**/systemui/settings/UserTrackerImplReceiveTest.kt",
         "tests/src/**/systemui/settings/UserTrackerImplTest.kt",
         "tests/src/**/systemui/shade/GlanceableHubContainerControllerTest.kt",
         "tests/src/**/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt",
-        "tests/src/**/systemui/shade/NotificationsQSContainerControllerTest.kt",
-        "tests/src/**/systemui/shade/ShadeExpansionStateManagerTest.kt",
-        "tests/src/**/systemui/shade/ShadeHeaderControllerTest.kt",
-        "tests/src/**/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt",
-        "tests/src/**/systemui/statusbar/commandline/CommandParserTest.kt",
-        "tests/src/**/systemui/statusbar/connectivity/MobileStateTest.kt",
-        "tests/src/**/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt",
-        "tests/src/**/systemui/statusbar/gesture/GenericGestureDetectorTest.kt",
-        "tests/src/**/systemui/statusbar/LightRevealScrimTest.kt",
-        "tests/src/**/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt",
-        "tests/src/**/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt",
-        "tests/src/**/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt",
-        "tests/src/**/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt",
-        "tests/src/**/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt",
-        "tests/src/**/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt",
-        "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt",
-        "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt",
-        "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt",
-        "tests/src/**/systemui/statusbar/notification/RoundableTest.kt",
-        "tests/src/**/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt",
-        "tests/src/**/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt",
-        "tests/src/**/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt",
-        "tests/src/**/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt",
-        "tests/src/**/systemui/statusbar/notification/row/TextPrecomputerTest.kt",
-        "tests/src/**/systemui/statusbar/phone/FoldStateListenerTest.kt",
-        "tests/src/**/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt",
-        "tests/src/**/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt",
-        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt",
-        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt",
-        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt",
-        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt",
-        "tests/src/**/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt",
-        "tests/src/**/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt",
-        "tests/src/**/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt",
-        "tests/src/**/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt",
-        "tests/src/**/systemui/statusbar/policy/VariableDateViewControllerTest.kt",
-        "tests/src/**/systemui/statusbar/policy/WalletControllerImplTest.kt",
-        "tests/src/**/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt",
-        "tests/src/**/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt",
-        "tests/src/**/systemui/statusbar/policy/BatteryControllerStartableTest.java",
-        "tests/src/**/systemui/shared/plugins/PluginActionManagerTest.java",
-        "tests/src/**/systemui/statusbar/policy/SecurityControllerTest.java",
-        "tests/src/**/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt",
+        "tests/src/**/systemui/screenshot/ScreenshotPolicyImplTest.kt",
+        "tests/src/**/systemui/qs/tileimpl/TilesStatesTextTest.kt",
+        "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt",
+        "tests/src/**/systemui/controls/ui/ControlViewHolderTest.kt",
+        "tests/src/**/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt",
+        "tests/src/**/systemui/controls/controller/ControlsBindingControllerImplTest.kt",
+        "tests/src/**/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt",
+        "tests/src/**/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt",
+        "tests/src/**/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt",
+        "tests/src/**/systemui/controls/management/ControlsProviderSelectorActivityTest.kt",
+        "tests/src/**/systemui/settings/DisplayTrackerImplTest.kt",
+        "tests/src/**/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt",
+        "tests/src/**/systemui/wmshell/BubblesTest.java",
     ],
-    visibility: ["//visibility:private"],
 }
 
 //Create a library to expose SystemUI's resources to other modules.
@@ -903,9 +860,8 @@
     ],
     exclude_srcs: [
         ":SystemUI-tests-broken-robofiles-mockito-extended",
-        ":SystemUI-tests-broken-robofiles-compile",
+        ":SystemUI-tests-broken-robofiles-internal",
         ":SystemUI-tests-broken-robofiles-run",
-        ":SystemUI-tests-broken-robofiles-sysui-run",
     ],
     static_libs: [
         "RoboTestLibraries",
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index e47704eb..cc01071 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -55,14 +55,6 @@
           "exclude-filter": "android.platform.tests.HomeTest#testAssistantWidget"
         }
       ]
-    },
-    {
-      "name": "AndroidAutomotiveNotificationsTests",
-      "options" : [
-        {
-          "include-filter": "android.platform.tests.NotificationTest"
-        }
-      ]
     }
   ],
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index a7b91c2..0f210e7 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -41,7 +41,7 @@
             android:exported="true"
             android:label="@string/accessibility_menu_settings_name"
             android:launchMode="singleTop"
-            android:theme="@style/SettingsTheme">
+            android:theme="@style/Theme.SettingsBase">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
index a138fa9..4169155 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml
@@ -21,11 +21,6 @@
     <item name="android:colorControlNormal">@color/colorControlNormal</item>
   </style>
 
-  <style name="SettingsTheme" parent="Theme.SettingsBase">
-    <!-- Quick fix so that the preference page doesn't render under its parent header. -->
-    <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
-  </style>
-
   <!--The basic theme for service and test case only-->
   <style name="A11yMenuBaseTheme" parent="android:Theme.DeviceDefault.Light">
     <item name="android:windowActionBar">false</item>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index c71ef83..3f7ce2c 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -21,22 +21,24 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
+import android.graphics.Insets;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Browser;
 import android.provider.Settings;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
 import android.widget.TextView;
 import android.window.OnBackInvokedCallback;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.fragment.app.FragmentActivity;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceManager;
 
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
 import com.android.systemui.accessibility.accessibilitymenu.R;
 
 /**
@@ -62,10 +64,8 @@
         ((TextView) findViewById(R.id.action_bar_title)).setText(
                 getResources().getString(R.string.accessibility_menu_settings_name)
         );
-        if (Flags.actionBarWrapContent()) {
-            setHeightWrapContent(findViewById(com.android.internal.R.id.action_bar));
-            setHeightWrapContent(findViewById(com.android.internal.R.id.action_bar_container));
-        }
+        setHeightWrapContent(findViewById(com.android.internal.R.id.action_bar));
+        setHeightWrapContent(findViewById(com.android.internal.R.id.action_bar_container));
     }
 
     private void setHeightWrapContent(View view) {
@@ -97,6 +97,18 @@
             super.onViewCreated(view, savedInstanceState);
             view.setLayoutDirection(
                     view.getResources().getConfiguration().getLayoutDirection());
+            view.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
+                @NonNull
+                @Override
+                public WindowInsets onApplyWindowInsets(@NonNull View v,
+                        @NonNull WindowInsets windowInsets) {
+                    Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars()
+                            | WindowInsets.Type.navigationBars()
+                            | WindowInsets.Type.displayCutout());
+                    v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+                    return WindowInsets.CONSUMED;
+                }
+            });
         }
 
         /**
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
index 3db61a5..a607786 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -57,7 +57,6 @@
 
 import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
 import com.android.systemui.accessibility.accessibilitymenu.R;
 import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment;
 import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
@@ -220,9 +219,6 @@
 
     @SuppressLint("MissingPermission")
     private boolean isShortcutRestricted(int shortcutId) {
-        if (!Flags.hideRestrictedActions()) {
-            return false;
-        }
         final UserManager userManager = mService.getSystemService(UserManager.class);
         if (userManager == null) {
             return false;
@@ -366,12 +362,11 @@
         if (mLayout.getVisibility() == View.VISIBLE) {
             mLayout.setVisibility(View.GONE);
         } else {
-            if (Flags.hideRestrictedActions()) {
-                // Reconfigure the shortcut list in case the set of restricted actions has changed.
-                mA11yMenuViewPager.configureViewPagerAndFooter(
-                        mLayout, createShortcutList(), getPageIndex());
-                updateViewLayout();
-            }
+            // Reconfigure the shortcut list in case the set of restricted actions has changed.
+            mA11yMenuViewPager.configureViewPagerAndFooter(
+                    mLayout, createShortcutList(), getPageIndex());
+            updateViewLayout();
+
             mLayout.setVisibility(View.VISIBLE);
         }
     }
@@ -387,9 +382,7 @@
             return;
         }
         snackbar.setText(text);
-        if (Flags.a11yMenuSnackbarLiveRegion()) {
-            snackbar.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
-        }
+        snackbar.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
 
         // Remove any existing fade-out animation before starting any new animations.
         mHandler.removeCallbacksAndMessages(null);
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 4ab771b..7172619 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -46,9 +46,6 @@
 import android.media.AudioManager;
 import android.os.PowerManager;
 import android.os.UserManager;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.uiautomator_helpers.WaitUtils;
 import android.provider.Settings;
 import android.util.Log;
@@ -63,7 +60,6 @@
 import androidx.test.uiautomator.UiDevice;
 
 import com.android.compatibility.common.util.TestUtils;
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
 import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
 
 import org.junit.After;
@@ -71,7 +67,6 @@
 import org.junit.Assume;
 import org.junit.Before;
 import org.junit.BeforeClass;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -82,9 +77,6 @@
 
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityMenuServiceTest {
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
     private static final String TAG = "A11yMenuServiceTest";
     private static final int CLICK_ID = AccessibilityNodeInfo.ACTION_CLICK;
 
@@ -499,7 +491,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HIDE_RESTRICTED_ACTIONS)
     public void testRestrictedActions_BrightnessNotAvailable() throws Throwable {
         try {
             setUserRestriction(UserManager.DISALLOW_CONFIG_BRIGHTNESS, true);
@@ -519,7 +510,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_HIDE_RESTRICTED_ACTIONS)
     public void testRestrictedActions_VolumeNotAvailable() throws Throwable {
         try {
             setUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME, true);
diff --git a/packages/SystemUI/aconfig/biometrics_framework.aconfig b/packages/SystemUI/aconfig/biometrics_framework.aconfig
index 10d7352..e3f5378 100644
--- a/packages/SystemUI/aconfig/biometrics_framework.aconfig
+++ b/packages/SystemUI/aconfig/biometrics_framework.aconfig
@@ -12,3 +12,10 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+    name: "cont_auth_plugin"
+    namespace: "biometrics_framework"
+    description: "Plugin and related API hooks for contextual auth plugins"
+    bug: "373600589"
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3541742..c1f7868 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -156,13 +156,6 @@
 }
 
 flag {
-    name: "notifications_improved_hun_animation"
-    namespace: "systemui"
-    description: "Adds a translateY animation, and other improvements to match the motion specs of the HUN Intro + Outro animations."
-    bug: "243302608"
-}
-
-flag {
     name: "notification_content_alpha_optimization"
     namespace: "systemui"
     description: "Only reset alpha values of needed content views"
@@ -434,6 +427,18 @@
     }
 }
 
+
+flag {
+    name: "status_bar_chips_modernization"
+    namespace: "systemui"
+    description: "Deprecate OngoingCallController and implement OngoingActivityChips"
+    "in compose"
+    bug: "372657935"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
 flag {
     name: "status_bar_use_repos_for_call_chip"
     namespace: "systemui"
@@ -1201,6 +1206,23 @@
 }
 
 flag {
+  name: "communal_hub_use_thread_pool_for_widgets"
+  namespace: "systemui"
+  description: "Use a dedicated thread pool executor for loading widgets on glanceable hub"
+  bug: "369412569"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "communal_responsive_grid"
+  namespace: "systemui"
+  description: "Enables responsive grid on glanceable hub"
+  bug: "378171351"
+}
+
+flag {
   name: "communal_standalone_support"
   namespace: "systemui"
   description: "Support communal features without a dock"
@@ -1215,6 +1237,13 @@
 }
 
 flag {
+  name: "glanceable_hub_v2"
+  namespace: "systemui"
+  description: "Gates the refreshed glanceable hub experience that also brings the glanceable hub to mobile phones"
+  bug: "375689917"
+}
+
+flag {
     name: "dream_overlay_updated_font"
     namespace: "systemui"
     description: "Flag to enable updated font settings for dream overlay"
@@ -1300,6 +1329,13 @@
 }
 
 flag {
+  name: "media_controls_ui_update"
+  namespace: "systemui"
+  description: "Enables media visuals update"
+  bug: "380053768"
+}
+
+flag {
   namespace: "systemui"
   name: "enable_view_capture_tracing"
   description: "Enables view capture tracing in System UI."
@@ -1502,6 +1538,16 @@
 }
 
 flag {
+   name: "sim_pin_use_slot_id"
+   namespace: "systemui"
+   description: "Reorient SIM data processing around slotId instead of subId"
+   bug: "376173142"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
    name: "use_transitions_for_keyguard_occluded"
    namespace: "systemui"
    description: "Use Keyguard Transitions to set Notification Shade occlusion state"
@@ -1755,16 +1801,6 @@
 }
 
 flag {
-  name: "ensure_enr_views_visibility"
-  namespace: "systemui"
-  description: "Ensures public and private visibilities"
-  bug: "361552380"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
   name: "shade_expands_on_status_bar_long_press"
   namespace: "systemui"
   description: "Expands the shade on long press of any status bar"
@@ -1836,3 +1872,17 @@
     description: "Implement the depth push scaling effect on Launcher when users pull down shade."
     bug: "370562309"
 }
+
+flag {
+    name: "spatial_model_app_pushback"
+    namespace: "systemui"
+    description: "Implement the depth push scaling effect on the current app when users pull down shade."
+    bug: "370560660"
+}
+
+flag {
+    name: "expanded_privacy_indicators_on_large_screen"
+    namespace: "systemui"
+    description: "Larger privacy indicators on large screen"
+    bug: "381864715"
+}
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
index 0f5e367..ca2b957 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.animation;
 
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
 import android.animation.ValueAnimator;
@@ -39,6 +41,7 @@
 import com.android.wm.shell.shared.TransitionUtil;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -90,7 +93,7 @@
                 () -> {
                     mStartTransaction = t;
                     mFinishCallback = finishCallback;
-                    startAnimationInternal(info);
+                    startAnimationInternal(info, /* states= */ null);
                 });
     }
 
@@ -112,7 +115,13 @@
             SurfaceControl.Transaction t,
             IRemoteTransitionFinishedCallback finishCallback,
             WindowAnimationState[] states) {
-        logD("takeOverAnimation - " + info);
+        logD("takeOverAnimation - info=" + info + ", states=" + Arrays.toString(states));
+        mHandler.post(
+                () -> {
+                    mStartTransaction = t;
+                    mFinishCallback = finishCallback;
+                    startAnimationInternal(info, states);
+                });
     }
 
     @Override
@@ -121,14 +130,19 @@
         mHandler.post(this::cancel);
     }
 
-    private void startAnimationInternal(TransitionInfo info) {
+    private void startAnimationInternal(
+            TransitionInfo info, @Nullable WindowAnimationState[] states) {
         if (!prepareUIs(info)) {
             logE("Unable to prepare UI!");
             finishAnimation(/* finished= */ false);
             return;
         }
         // Notify player that we are starting.
-        mPlayer.onStart(info, mStartTransaction, mOrigin, mOriginTransaction);
+        mPlayer.onStart(info, states, mStartTransaction, mOrigin, mOriginTransaction);
+
+        // Apply the initial transactions in case the player forgot to apply them.
+        mOriginTransaction.commit();
+        mStartTransaction.apply();
 
         // Start the animator.
         mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
@@ -205,7 +219,8 @@
                             .setCornerRadius(leash, windowRadius)
                             .setWindowCrop(leash, bounds.width(), bounds.height());
                 }
-            } else if (TransitionUtil.isClosingMode(mode)) {
+            } else if (TransitionUtil.isClosingMode(mode) || mode == TRANSIT_CHANGE) {
+                // TRANSIT_CHANGE refers to the closing window in predictive back animation.
                 closingSurfaces.add(change.getLeash());
                 // For closing surfaces, starting bounds are base bounds. Apply corner radius if
                 // it's full screen.
@@ -236,13 +251,8 @@
 
         // Attach origin UIComponent to origin leash.
         mOriginTransaction = mOrigin.newTransaction();
-        mOriginTransaction
-                .attachToTransitionLeash(
-                        mOrigin, mOriginLeash, displayBounds.width(), displayBounds.height())
-                .commit();
-
-        // Apply all surface changes.
-        mStartTransaction.apply();
+        mOriginTransaction.attachToTransitionLeash(
+                mOrigin, mOriginLeash, displayBounds.width(), displayBounds.height());
         return true;
     }
 
@@ -328,6 +338,58 @@
                 /* baseBounds= */ maxBounds);
     }
 
+    private static void applyWindowAnimationStates(
+            TransitionInfo info,
+            @Nullable WindowAnimationState[] states,
+            UIComponent closingApp,
+            UIComponent openingApp) {
+        if (states == null) {
+            // Nothing to apply.
+            return;
+        }
+        // Calculate bounds.
+        Rect maxClosingBounds = new Rect();
+        Rect maxOpeningBounds = new Rect();
+        for (int i = 0; i < info.getChanges().size(); i++) {
+            Rect bound = getBounds(states[i]);
+            if (bound == null) {
+                continue;
+            }
+            int mode = info.getChanges().get(i).getMode();
+            if (TransitionUtil.isOpeningMode(mode)) {
+                maxOpeningBounds.union(bound);
+            } else if (TransitionUtil.isClosingMode(mode) || mode == TRANSIT_CHANGE) {
+                // TRANSIT_CHANGE refers to the closing window in predictive back animation.
+                maxClosingBounds.union(bound);
+            }
+        }
+
+        // Intentionally use a new transaction instead of reusing the existing transaction since we
+        // want to apply window animation states first without committing any other pending changes
+        // in the existing transaction. The existing transaction is expected to be committed by the
+        // onStart() client callback together with client's custom transformation.
+        UIComponent.Transaction transaction = closingApp.newTransaction();
+        if (!maxClosingBounds.isEmpty()) {
+            logD("Applying closing window bounds: " + maxClosingBounds);
+            transaction.setBounds(closingApp, maxClosingBounds);
+        }
+        if (!maxOpeningBounds.isEmpty()) {
+            logD("Applying opening window bounds: " + maxOpeningBounds);
+            transaction.setBounds(openingApp, maxOpeningBounds);
+        }
+        transaction.commit();
+    }
+
+    @Nullable
+    private static Rect getBounds(@Nullable WindowAnimationState state) {
+        if (state == null || state.bounds == null) {
+            return null;
+        }
+        Rect out = new Rect();
+        state.bounds.roundOut(out);
+        return out;
+    }
+
     /**
      * An interface that represents an origin transitions.
      *
@@ -338,9 +400,14 @@
         /**
          * Called when an origin transition starts. This method exposes the raw {@link
          * TransitionInfo} so that clients can extract more information from it.
+         *
+         * <p>Note: if this transition is taking over a predictive back animation, the {@link
+         * WindowAnimationState} will be passed to this method. The concrete implementation is
+         * expected to apply the {@link WindowAnimationState} before continuing the transition.
          */
         default void onStart(
                 TransitionInfo transitionInfo,
+                @Nullable WindowAnimationState[] states,
                 SurfaceControl.Transaction sfTransaction,
                 UIComponent origin,
                 UIComponent.Transaction uiTransaction) {
@@ -351,12 +418,15 @@
                             .registerTransactionForClass(
                                     SurfaceUIComponent.class,
                                     new SurfaceUIComponent.Transaction(sfTransaction));
-            // Wrap surfaces and start.
-            onStart(
-                    transactions,
-                    origin,
-                    wrapSurfaces(transitionInfo, /* isOpening= */ false),
-                    wrapSurfaces(transitionInfo, /* isOpening= */ true));
+            // Wrap surfaces.
+            UIComponent closingApp = wrapSurfaces(transitionInfo, /* isOpening= */ false);
+            UIComponent openingApp = wrapSurfaces(transitionInfo, /* isOpening= */ true);
+
+            // Restore the pending animation states coming from predictive back transition.
+            applyWindowAnimationStates(transitionInfo, states, closingApp, openingApp);
+
+            // Start.
+            onStart(transactions, origin, closingApp, openingApp);
         }
 
         /**
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
index 9cef43c..cec740a 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
@@ -89,7 +89,6 @@
         mSurfaceControl =
                 new SurfaceControl.Builder().setName("ViewUIComponent").setBufferSize(w, h).build();
         mSurface = new Surface(mSurfaceControl);
-        forceDraw();
 
         // Attach surface to transition leash
         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
@@ -99,7 +98,13 @@
         mView.getViewTreeObserver().addOnDrawListener(mOnDrawListener);
 
         // Make the view invisible AFTER the surface is shown.
-        t.addTransactionCommittedListener(mView::post, () -> mView.setVisibility(View.INVISIBLE))
+        t.addTransactionCommittedListener(
+                        mView::post,
+                        () -> {
+                            logD("Surface attached!");
+                            forceDraw();
+                            mView.setVisibility(View.INVISIBLE);
+                        })
                 .apply();
     }
 
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java
index 3cbb688..3b66460 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/server/IOriginTransitionsImpl.java
@@ -16,8 +16,10 @@
 
 package com.android.systemui.animation.server;
 
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
@@ -26,6 +28,7 @@
 import android.app.TaskInfo;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.ArrayMap;
@@ -51,8 +54,8 @@
 
 /** An implementation of the {@link IOriginTransitions}. */
 public class IOriginTransitionsImpl extends IOriginTransitions.Stub {
-    private static final boolean DEBUG = true;
     private static final String TAG = "OriginTransitions";
+    private static final boolean DEBUG = Build.IS_USERDEBUG || Log.isLoggable(TAG, Log.DEBUG);
 
     private final Object mLock = new Object();
     private final ShellTransitions mShellTransitions;
@@ -149,18 +152,7 @@
             if (DEBUG) {
                 Log.d(TAG, "startAnimation: " + info);
             }
-            if (!mOnStarting.test(info)) {
-                Log.w(TAG, "Skipping cancelled transition " + mTransition);
-                t.addTransactionCommittedListener(
-                                mExecutor,
-                                () -> {
-                                    try {
-                                        finishCallback.onTransitionFinished(null, null);
-                                    } catch (RemoteException e) {
-                                        Log.e(TAG, "Unable to report finish.", e);
-                                    }
-                                })
-                        .apply();
+            if (maybeInterceptTransition(info, t, finishCallback)) {
                 return;
             }
             mTransition.startAnimation(token, info, t, finishCallback);
@@ -191,6 +183,9 @@
             if (DEBUG) {
                 Log.d(TAG, "takeOverAnimation: " + info);
             }
+            if (maybeInterceptTransition(info, t, finishCallback)) {
+                return;
+            }
             mTransition.takeOverAnimation(transition, info, t, finishCallback, states);
         }
 
@@ -207,6 +202,27 @@
         public String toString() {
             return "RemoteTransitionDelegate{transition=" + mTransition + "}";
         }
+
+        private boolean maybeInterceptTransition(
+                TransitionInfo info,
+                SurfaceControl.Transaction t,
+                IRemoteTransitionFinishedCallback finishCallback) {
+            if (!mOnStarting.test(info)) {
+                Log.w(TAG, "Intercepting cancelled transition " + mTransition);
+                t.addTransactionCommittedListener(
+                                mExecutor,
+                                () -> {
+                                    try {
+                                        finishCallback.onTransitionFinished(null, null);
+                                    } catch (RemoteException e) {
+                                        Log.e(TAG, "Unable to report finish.", e);
+                                    }
+                                })
+                        .apply();
+                return true;
+            }
+            return false;
+        }
     }
 
     /** A data record containing the origin transition pieces. */
@@ -229,13 +245,25 @@
                 if (mDestroyed) {
                     return false;
                 }
-                TransitionFilter filter = createFilterForReverseTransition(info);
+                TransitionFilter filter =
+                        createFilterForReverseTransition(
+                                info, /* forPredictiveBackTakeover= */ false);
                 if (filter != null) {
                     if (DEBUG) {
                         Log.d(TAG, "Registering filter " + filter);
                     }
                     mShellTransitions.registerRemote(filter, mWrappedReturnTransition);
                 }
+                TransitionFilter takeoverFilter =
+                        createFilterForReverseTransition(
+                                info, /* forPredictiveBackTakeover= */ true);
+                if (takeoverFilter != null) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Registering filter for takeover " + takeoverFilter);
+                    }
+                    mShellTransitions.registerRemoteForTakeover(
+                            takeoverFilter, mWrappedReturnTransition);
+                }
                 return true;
             }
         }
@@ -331,7 +359,8 @@
         }
 
         @Nullable
-        private static TransitionFilter createFilterForReverseTransition(TransitionInfo info) {
+        private static TransitionFilter createFilterForReverseTransition(
+                TransitionInfo info, boolean forPredictiveBackTakeover) {
             TaskInfo launchingTaskInfo = null;
             TaskInfo launchedTaskInfo = null;
             ComponentName launchingActivity = null;
@@ -365,7 +394,9 @@
             if (DEBUG) {
                 Log.d(
                         TAG,
-                        "createFilterForReverseTransition: launchingTaskInfo="
+                        "createFilterForReverseTransition: forPredictiveBackTakeover="
+                                + forPredictiveBackTakeover
+                                + ", launchingTaskInfo="
                                 + launchingTaskInfo
                                 + ", launchedTaskInfo="
                                 + launchedTaskInfo
@@ -395,8 +426,21 @@
                                 + " cookie!");
                 return null;
             }
+            if (forPredictiveBackTakeover && launchedTaskInfo == null) {
+                // Predictive back take over currently only support cross-task transition.
+                Log.d(
+                        TAG,
+                        "createFilterForReverseTransition: skipped - unable to find launched task"
+                                + " for predictive back takeover");
+                return null;
+            }
             TransitionFilter filter = new TransitionFilter();
-            filter.mTypeSet = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK};
+            if (forPredictiveBackTakeover) {
+                filter.mTypeSet = new int[] {TRANSIT_PREPARE_BACK_NAVIGATION};
+            } else {
+                filter.mTypeSet =
+                        new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK, TRANSIT_OPEN, TRANSIT_TO_FRONT};
+            }
 
             // The opening activity of the return transition must match the activity we just closed.
             TransitionFilter.Requirement req1 = new TransitionFilter.Requirement();
@@ -405,15 +449,18 @@
                     launchingActivity == null ? launchingTaskInfo.topActivity : launchingActivity;
 
             TransitionFilter.Requirement req2 = new TransitionFilter.Requirement();
-            req2.mModes = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK};
+            if (forPredictiveBackTakeover) {
+                req2.mModes = new int[] {TRANSIT_CHANGE};
+            } else {
+                req2.mModes = new int[] {TRANSIT_CLOSE, TRANSIT_TO_BACK};
+            }
             if (launchedTaskInfo != null) {
                 // For task transitions, the closing task's cookie must match the task we just
                 // launched.
                 req2.mLaunchCookie = launchedTaskInfo.launchCookies.get(0);
             } else {
                 // For activity transitions, the closing activity of the return transition must
-                // match
-                // the activity we just launched.
+                // match the activity we just launched.
                 req2.mTopActivity = launchedActivity;
             }
 
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 eee0caf..3eeaf41 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.animation
 
 import android.app.ActivityManager
+import android.app.ActivityOptions
 import android.app.ActivityTaskManager
 import android.app.PendingIntent
 import android.app.TaskInfo
@@ -423,19 +424,19 @@
                         newKeyguardOccludedState: Boolean?
                     ) {
                         super.onTransitionAnimationCancelled(newKeyguardOccludedState)
-                        cleanUp()
+                        onDispose()
                     }
 
                     override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                         super.onTransitionAnimationEnd(isExpandingFullyAbove)
-                        cleanUp()
+                        onDispose()
                     }
 
-                    private fun cleanUp() {
+                    override fun onDispose() {
+                        super.onDispose()
                         cleanUpRunnable?.run()
                     }
-                },
-                initializeLazily = longLivedReturnAnimationsEnabled(),
+                }
             )
 
         // mTypeSet and mModes match back signals only, and not home. This is on purpose, because
@@ -478,8 +479,8 @@
     /** Create a new animation [Runner] controlled by [controller]. */
     @VisibleForTesting
     @JvmOverloads
-    fun createRunner(controller: Controller, initializeLazily: Boolean = false): Runner {
-        if (initializeLazily) assertLongLivedReturnAnimations()
+    fun createRunner(controller: Controller, longLived: Boolean = false): Runner {
+        if (longLived) assertLongLivedReturnAnimations()
 
         // Make sure we use the modified timings when animating a dialog into an app.
         val transitionAnimator =
@@ -489,13 +490,7 @@
                 transitionAnimator
             }
 
-        return Runner(
-            controller,
-            callback!!,
-            transitionAnimator,
-            lifecycleListener,
-            initializeLazily,
-        )
+        return Runner(controller, callback!!, transitionAnimator, lifecycleListener, longLived)
     }
 
     interface PendingIntentStarter {
@@ -566,6 +561,7 @@
                 cookie: TransitionCookie? = null,
                 component: ComponentName? = null,
                 returnCujType: Int? = null,
+                isEphemeral: Boolean = true,
             ): Controller? {
                 // Make sure the View we launch from implements LaunchableView to avoid visibility
                 // issues.
@@ -593,6 +589,7 @@
                     cookie,
                     component,
                     returnCujType,
+                    isEphemeral,
                 )
             }
         }
@@ -653,6 +650,9 @@
          * appropriately.
          */
         fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
+
+        /** The controller will not be used again. Clean up the relevant internal state. */
+        fun onDispose() {}
     }
 
     /**
@@ -699,7 +699,7 @@
             }
         val launchRemoteTransition =
             RemoteTransition(
-                OriginTransition(createRunner(controller, initializeLazily = true)),
+                OriginTransition(createRunner(controller, longLived = true)),
                 "${cookie}_launchTransition",
             )
         transitionRegister.register(launchFilter, launchRemoteTransition, includeTakeover = true)
@@ -708,6 +708,8 @@
             object : Controller by controller {
                 override val isLaunching: Boolean = false
             }
+        // Cross-task close transitions should not use this animation, so we only register it for
+        // when the opening window is Launcher.
         val returnFilter =
             TransitionFilter().apply {
                 mRequirements =
@@ -716,12 +718,16 @@
                             mActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD
                             mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK)
                             mTopActivity = component
-                        }
+                        },
+                        TransitionFilter.Requirement().apply {
+                            mActivityType = WindowConfiguration.ACTIVITY_TYPE_HOME
+                            mModes = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT)
+                        },
                     )
             }
         val returnRemoteTransition =
             RemoteTransition(
-                OriginTransition(createRunner(returnController, initializeLazily = true)),
+                OriginTransition(createRunner(returnController, longLived = true)),
                 "${cookie}_returnTransition",
             )
         transitionRegister.register(returnFilter, returnRemoteTransition, includeTakeover = true)
@@ -910,14 +916,22 @@
 
     @VisibleForTesting
     inner class Runner(
-        private val controller: Controller,
+        /**
+         * This can hold a reference to a view, so it needs to be cleaned up and can't be held on to
+         * forever when ![longLived].
+         */
+        private var controller: Controller?,
         private val callback: Callback,
         /** The animator to use to animate the window transition. */
         private val transitionAnimator: TransitionAnimator,
         /** Listener for animation lifecycle events. */
         private val listener: Listener? = null,
-        /** Whether the internal [delegate] should be initialized lazily. */
-        private val initializeLazily: Boolean = false,
+        /**
+         * Whether the internal should be kept around after execution for later usage. IMPORTANT:
+         * should always be false if this [Runner] is to be used directly with [ActivityOptions]
+         * (i.e. for ephemeral launches), or the controller will leak its view.
+         */
+        private val longLived: Boolean = false,
     ) : IRemoteAnimationRunner.Stub() {
         // 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
@@ -926,7 +940,7 @@
 
         init {
             delegate = null
-            if (!initializeLazily) {
+            if (!longLived) {
                 // Ephemeral launches bundle the runner with the launch request (instead of being
                 // registered ahead of time for later use). This means that there could be a timeout
                 // between creation and invocation, so the delegate needs to exist from the
@@ -1004,16 +1018,17 @@
 
         @AnyThread
         private fun maybeSetUp() {
-            if (!initializeLazily || delegate != null) return
+            if (!longLived || delegate != null) return
             createDelegate()
         }
 
         @AnyThread
         private fun createDelegate() {
+            if (controller == null) return
             delegate =
                 AnimationDelegate(
                     mainExecutor,
-                    controller,
+                    controller!!,
                     callback,
                     DelegatingAnimationCompletionListener(listener, this::dispose),
                     transitionAnimator,
@@ -1025,7 +1040,12 @@
         fun dispose() {
             // Drop references to animation controller once we're done with the animation
             // to avoid leaking.
-            mainExecutor.execute { delegate = null }
+            mainExecutor.execute {
+                delegate = null
+                // When long lived, the same Runner can be used more than once. In this case we need
+                // to keep the controller around so we can rebuild the delegate on demand.
+                if (!longLived) controller = null
+            }
         }
     }
 
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 3ba9a29..b56a68cb 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
@@ -39,7 +39,8 @@
         launchCujType: Int? = null,
         cookie: ActivityTransitionAnimator.TransitionCookie? = null,
         component: ComponentName? = null,
-        returnCujType: Int? = null
+        returnCujType: Int? = null,
+        isEphemeral: Boolean = true,
     ): ActivityTransitionAnimator.Controller?
 
     /**
@@ -55,7 +56,8 @@
             launchCujType,
             cookie = null,
             component = null,
-            returnCujType = null
+            returnCujType = null,
+            isEphemeral = true,
         )
     }
 
@@ -80,14 +82,16 @@
                     launchCujType: Int?,
                     cookie: ActivityTransitionAnimator.TransitionCookie?,
                     component: ComponentName?,
-                    returnCujType: Int?
+                    returnCujType: Int?,
+                    isEphemeral: Boolean,
                 ): ActivityTransitionAnimator.Controller? {
                     return ActivityTransitionAnimator.Controller.fromView(
                         view,
                         launchCujType,
                         cookie,
                         component,
-                        returnCujType
+                        returnCujType,
+                        isEphemeral,
                     )
                 }
 
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 e626c04..558c1eba 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -67,6 +67,12 @@
 
     /** The [CujType] associated to this return animation. */
     private val returnCujType: Int? = null,
+
+    /**
+     * Whether this controller should be invalidated after its first use, and whenever [ghostedView]
+     * is detached.
+     */
+    private val isEphemeral: Boolean = false,
     private var interactionJankMonitor: InteractionJankMonitor =
         InteractionJankMonitor.getInstance(),
 ) : ActivityTransitionAnimator.Controller {
@@ -119,6 +125,19 @@
                 returnCujType
             }
 
+    /**
+     * Used to automatically clean up the internal state once [ghostedView] is detached from the
+     * hierarchy.
+     */
+    private val detachListener =
+        object : View.OnAttachStateChangeListener {
+            override fun onViewAttachedToWindow(v: View) {}
+
+            override fun onViewDetachedFromWindow(v: View) {
+                onDispose()
+            }
+        }
+
     init {
         // Make sure the View we launch from implements LaunchableView to avoid visibility issues.
         if (ghostedView !is LaunchableView) {
@@ -155,6 +174,16 @@
         }
 
         background = findBackground(ghostedView)
+
+        if (TransitionAnimator.returnAnimationsEnabled() && isEphemeral) {
+            ghostedView.addOnAttachStateChangeListener(detachListener)
+        }
+    }
+
+    override fun onDispose() {
+        if (TransitionAnimator.returnAnimationsEnabled()) {
+            ghostedView.removeOnAttachStateChangeListener(detachListener)
+        }
     }
 
     /**
@@ -164,7 +193,7 @@
     protected open fun setBackgroundCornerRadius(
         background: Drawable,
         topCornerRadius: Float,
-        bottomCornerRadius: Float
+        bottomCornerRadius: Float,
     ) {
         // By default, we rely on WrappedDrawable to set/restore the background radii before/after
         // each draw.
@@ -195,7 +224,7 @@
         val state =
             TransitionAnimator.State(
                 topCornerRadius = getCurrentTopCornerRadius(),
-                bottomCornerRadius = getCurrentBottomCornerRadius()
+                bottomCornerRadius = getCurrentBottomCornerRadius(),
             )
         fillGhostedViewState(state)
         return state
@@ -269,7 +298,7 @@
     override fun onTransitionAnimationProgress(
         state: TransitionAnimator.State,
         progress: Float,
-        linearProgress: Float
+        linearProgress: Float,
     ) {
         val ghostView = this.ghostView ?: return
         val backgroundView = this.backgroundView!!
@@ -317,11 +346,11 @@
             scale,
             scale,
             ghostedViewState.centerX - transitionContainerLocation[0],
-            ghostedViewState.centerY - transitionContainerLocation[1]
+            ghostedViewState.centerY - transitionContainerLocation[1],
         )
         ghostViewMatrix.postTranslate(
             (leftChange + rightChange) / 2f,
-            (topChange + bottomChange) / 2f
+            (topChange + bottomChange) / 2f,
         )
         ghostView.animationMatrix = ghostViewMatrix
 
@@ -462,7 +491,7 @@
         private fun updateRadii(
             radii: FloatArray,
             topCornerRadius: Float,
-            bottomCornerRadius: Float
+            bottomCornerRadius: Float,
         ) {
             radii[0] = topCornerRadius
             radii[1] = topCornerRadius
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
index cbe11a3..8a57e8c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
@@ -23,6 +23,7 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 
 import static com.android.internal.util.Preconditions.checkArgument;
@@ -163,7 +164,8 @@
                         t.show(wallpapers[i].leash);
                         t.setAlpha(wallpapers[i].leash, 1.f);
                     }
-                    if (ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue()) {
+                    if (ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue()
+                            || ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue()) {
                         resetLauncherAlphaOnDesktopExit(info, launcherTask, leashMap, t);
                     }
                 } else {
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt
new file mode 100644
index 0000000..2f83d82
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.UClass
+import org.jetbrains.uast.getContainingUFile
+
+class ShadeDisplayAwareDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableUastTypes() = listOf(UClass::class.java)
+
+    override fun createUastHandler(context: JavaContext) =
+        object : UElementHandler() {
+            override fun visitClass(node: UClass) {
+                for (constructor in node.constructors) {
+                    // Visit all injected constructors in shade-relevant packages
+                    if (!constructor.hasAnnotation(INJECT_ANNOTATION)) continue
+                    if (!isInRelevantShadePackage(node)) continue
+                    if (IGNORED_PACKAGES.contains(node.qualifiedName)) continue
+
+                    // Check the any context-dependent parameter to see if it has @ShadeDisplayAware
+                    // annotation
+                    for (parameter in constructor.parameterList.parameters) {
+                        val shouldReport =
+                            CONTEXT_DEPENDENT_SHADE_CLASSES.contains(
+                                parameter.type.canonicalText
+                            ) && !parameter.hasAnnotation(SHADE_DISPLAY_AWARE_ANNOTATION)
+                        if (shouldReport) {
+                            context.report(
+                                issue = ISSUE,
+                                scope = parameter.declarationScope,
+                                location = context.getNameLocation(parameter),
+                                message = reportMsg(className = parameter.type.presentableText),
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+    companion object {
+        private const val INJECT_ANNOTATION = "javax.inject.Inject"
+        private const val SHADE_DISPLAY_AWARE_ANNOTATION =
+            "com.android.systemui.shade.ShadeDisplayAware"
+
+        private val CONTEXT_DEPENDENT_SHADE_CLASSES =
+            setOf(
+                "android.content.Context",
+                "android.view.WindowManager",
+                "android.view.LayoutInflater",
+                "android.content.res.Resources",
+                "com.android.systemui.common.ui.ConfigurationState",
+                "com.android.systemui.statusbar.policy.ConfigurationController",
+                "com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor",
+            )
+
+        private val SHADE_WINDOW_PACKAGES =
+            listOf(
+                "com.android.systemui.biometrics",
+                "com.android.systemui.bouncer",
+                "com.android.systemui.keyboard.docking.ui.viewmodel",
+                "com.android.systemui.qs",
+                "com.android.systemui.shade",
+                "com.android.systemui.statusbar.notification",
+                "com.android.systemui.unfold.domain.interactor",
+            )
+
+        private val IGNORED_PACKAGES =
+            setOf(
+                "com.android.systemui.biometrics.UdfpsController",
+                "com.android.systemui.qs.customize.TileAdapter",
+            )
+
+        private fun isInRelevantShadePackage(node: UClass): Boolean {
+            val packageName = node.getContainingUFile()?.packageName
+            if (packageName.isNullOrBlank()) return false
+            return SHADE_WINDOW_PACKAGES.any { relevantPackage ->
+                packageName.startsWith(relevantPackage)
+            }
+        }
+
+        private fun reportMsg(className: String) =
+            """UI elements of the shade window
+              |should use ShadeDisplayAware-annotated $className, as the shade might move between windows, and only
+              |@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
+              |might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
+              |If the usage of $className is not related to display specific configuration or UI, then there is
+              |technically no need to use the annotation, and you can annotate the class with
+              |@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")
+              |"""
+                .trimMargin()
+
+        @JvmField
+        val ISSUE: Issue =
+            Issue.create(
+                id = "ShadeDisplayAwareContextChecker",
+                briefDescription = "Using non-ShadeDisplayAware component within shade",
+                explanation =
+                    """
+                Any context-dependent components (Resources, LayoutInflater, ConfigurationState,
+                etc.) being injected into Shade-relevant classes must have the @ShadeDisplayAware
+                annotation to ensure they work with when the shade is moved to a different display.
+                When the shade is moved, the configuration might change, and only @ShadeDisplayAware
+                components will update accordingly to reflect the new display.
+            """
+                        .trimIndent(),
+                category = Category.CORRECTNESS,
+                priority = 8,
+                severity = Severity.ERROR,
+                implementation =
+                    Implementation(ShadeDisplayAwareDetector::class.java, Scope.JAVA_FILE_SCOPE),
+            )
+    }
+}
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 a1f4f55..6d18f93 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
@@ -46,8 +46,9 @@
                 DemotingTestWithoutBugDetector.ISSUE,
                 TestFunctionNameViolationDetector.ISSUE,
                 MissingApacheLicenseDetector.ISSUE,
+                ShadeDisplayAwareDetector.ISSUE,
                 RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING,
-                RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR
+                RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR,
             )
 
     override val api: Int
@@ -60,6 +61,6 @@
         Vendor(
             vendorName = "Android",
             feedbackUrl = "http://b/issues/new?component=78010",
-            contact = "jernej@google.com"
+            contact = "jernej@google.com",
         )
 }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt
new file mode 100644
index 0000000..58ad363
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt
@@ -0,0 +1,375 @@
+/*
+ * 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.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class ShadeDisplayAwareDetectorTest : SystemUILintDetectorTest() {
+    override fun getDetector(): Detector = ShadeDisplayAwareDetector()
+
+    override fun getIssues(): List<Issue> = listOf(ShadeDisplayAwareDetector.ISSUE)
+
+    private val qsContext: TestFile =
+        java(
+                """
+            package com.android.systemui.qs.dagger;
+
+            import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+            import java.lang.annotation.Retention;
+
+            @Retention(RUNTIME) public @interface QSThemedContext {}
+            """
+            )
+            .indented()
+
+    private val injectStub: TestFile =
+        kotlin(
+                """
+                package javax.inject
+
+                @Retention(AnnotationRetention.RUNTIME) annotation class Inject
+                """
+            )
+            .indented()
+
+    private val shadeDisplayAwareStub: TestFile =
+        kotlin(
+                """
+                package com.android.systemui.shade
+
+                @Retention(AnnotationRetention.RUNTIME) annotation class ShadeDisplayAware
+                """
+            )
+            .indented()
+
+    private val configStateStub: TestFile =
+        kotlin(
+                """
+                package com.android.systemui.common.ui
+
+                class ConfigurationState
+                """
+            )
+            .indented()
+
+    private val configControllerStub: TestFile =
+        kotlin(
+                """
+                package com.android.systemui.statusbar.policy
+
+                class ConfigurationController
+                """
+            )
+            .indented()
+
+    private val configInteractorStub: TestFile =
+        kotlin(
+                """
+                package com.android.systemui.common.ui.domain.interactor
+
+                class ConfigurationInteractor
+                """
+            )
+            .indented()
+
+    private val otherStubs =
+        arrayOf(
+            injectStub,
+            qsContext,
+            shadeDisplayAwareStub,
+            configStateStub,
+            configControllerStub,
+            configInteractorStub,
+        )
+
+    @Test
+    fun injectedConstructor_inRelevantPackage_withRelevantParameter_withoutAnnotation() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                        package com.android.systemui.shade.example
+
+                        import javax.inject.Inject
+                        import android.content.Context
+
+                        class ExampleClass
+                            @Inject
+                            constructor(private val context: Context)
+                    """
+                        .trimIndent()
+                ),
+                *androidStubs,
+                *otherStubs,
+            )
+            .issues(ShadeDisplayAwareDetector.ISSUE)
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectErrorCount(1)
+            .expectContains(errorMsgString(8, "Context"))
+            .expectContains("[ShadeDisplayAwareContextChecker]")
+            .expectContains(
+                "constructor(private val context: Context)\n" +
+                    "                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+            )
+            .expectContains("1 errors, 0 warnings")
+    }
+
+    @Test
+    fun injectedConstructor_inRelevantPackage_withMultipleRelevantParameters_withoutAnnotation() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                        package com.android.systemui.shade.example
+
+                        import javax.inject.Inject
+                        import android.content.Context
+                        import android.content.res.Resources
+                        import android.view.LayoutInflater
+                        import android.view.WindowManager
+                        import com.android.systemui.common.ui.ConfigurationState
+                        import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+                        import com.android.systemui.statusbar.policy.ConfigurationController
+
+                        class ExampleClass
+                            @Inject
+                            constructor(
+                                private val context: Context,
+                                private val inflater: LayoutInflater,
+                                private val windowManager: WindowManager,
+                                private val configState: ConfigurationState,
+                                private val configController: ConfigurationController,
+                                private val configInteractor: ConfigurationInteractor,
+                            )
+                    """
+                        .trimIndent()
+                ),
+                *androidStubs,
+                *otherStubs,
+            )
+            .issues(ShadeDisplayAwareDetector.ISSUE)
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectErrorCount(6)
+            .expectContains(errorMsgString(lineNumber = 15, className = "Context"))
+            .expectContains(
+                "private val context: Context,\n" + "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+            )
+            .expectContains(errorMsgString(lineNumber = 16, className = "LayoutInflater"))
+            .expectContains(
+                "private val inflater: LayoutInflater,\n" +
+                    "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+            )
+            .expectContains(errorMsgString(lineNumber = 17, className = "WindowManager"))
+            .expectContains(
+                "private val windowManager: WindowManager,\n" +
+                    "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+            )
+            .expectContains(errorMsgString(lineNumber = 18, className = "ConfigurationState"))
+            .expectContains(
+                "private val configState: ConfigurationState,\n" +
+                    "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+            )
+            .expectContains(errorMsgString(lineNumber = 19, className = "ConfigurationController"))
+            .expectContains(
+                "private val configController: ConfigurationController,\n" +
+                    "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+            )
+            .expectContains(errorMsgString(lineNumber = 20, className = "ConfigurationInteractor"))
+            .expectContains(
+                "private val configInteractor: ConfigurationInteractor,\n" +
+                    "        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+            )
+            .expectContains(" [ShadeDisplayAwareContextChecker]")
+    }
+
+    @Test
+    fun injectedConstructor_inRelevantPackage_withRelevantParameter_withAnnotation() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                        package com.android.systemui.shade.example
+
+                        import javax.inject.Inject
+                        import android.content.Context
+                        import com.android.systemui.shade.ShadeDisplayAware
+
+                        class ExampleClass
+                            @Inject
+                            constructor(@ShadeDisplayAware private val context: Context)
+                    """
+                        .trimIndent()
+                ),
+                *androidStubs,
+                *otherStubs,
+            )
+            .issues(ShadeDisplayAwareDetector.ISSUE)
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun injectedConstructor_inRelevantPackage_withoutRelevantParameter_withoutAnnotation() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                        package com.android.systemui.shade.example
+
+                        import javax.inject.Inject
+                        import android.content.ContextWrapper
+
+                        class ExampleClass
+                            @Inject
+                            constructor(private val contextWrapper: ContextWrapper)
+                    """
+                        .trimIndent()
+                ),
+                *androidStubs,
+                *otherStubs,
+            )
+            .issues(ShadeDisplayAwareDetector.ISSUE)
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun injectedConstructor_notInRelevantPackage_withRelevantParameter_withoutAnnotation() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                        package com.android.systemui.keyboard
+
+                        import javax.inject.Inject
+                        import android.content.Context
+
+                        class ExampleClass @Inject constructor(private val context: Context)
+                    """
+                        .trimIndent()
+                ),
+                *androidStubs,
+                *otherStubs,
+            )
+            .issues(ShadeDisplayAwareDetector.ISSUE)
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun nonInjectedConstructor_inRelevantPackage_withRelevantParameter_withoutAnnotation() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                        package com.android.systemui.shade.example
+
+                        import android.content.Context
+
+                        class ExampleClass(private val context: Context)
+                    """
+                        .trimIndent()
+                ),
+                *androidStubs,
+                *otherStubs,
+            )
+            .issues(ShadeDisplayAwareDetector.ISSUE)
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun injectedConstructor_inRelevantPackage_withRelevantParameter_withoutAnnotation_suppressed() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                        package com.android.systemui.shade.example
+
+                        import javax.inject.Inject
+                        import android.content.Context
+
+                        @Suppress("ShadeDisplayAwareContextChecker")
+                        class ExampleClass
+                            @Inject
+                            constructor(
+                                private val context: Context
+                            )
+                    """
+                        .trimIndent()
+                ),
+                *androidStubs,
+                *otherStubs,
+            )
+            .issues(ShadeDisplayAwareDetector.ISSUE)
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun injectedConstructor_inExemptPackage_withRelevantParameter_withoutAnnotation() {
+        lint()
+            .files(
+                TestFiles.java(
+                    """
+                        package com.android.systemui.qs.customize;
+
+                        import javax.inject.Inject;
+                        import com.android.systemui.qs.dagger.QSThemedContext;
+                        import android.content.Context;
+
+                        public class TileAdapter {
+                            @Inject
+                            public TileAdapter(@QSThemedContext Context context) {}
+                        }
+                    """
+                        .trimIndent()
+                ),
+                *androidStubs,
+                *otherStubs,
+            )
+            .issues(ShadeDisplayAwareDetector.ISSUE)
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectClean()
+    }
+
+    private fun errorMsgString(lineNumber: Int, className: String) =
+        """
+        src/com/android/systemui/shade/example/ExampleClass.kt:$lineNumber: Error: UI elements of the shade window
+        should use ShadeDisplayAware-annotated $className, as the shade might move between windows, and only
+        @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
+        might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
+        If the usage of $className is not related to display specific configuration or UI, then there is
+        technically no need to use the annotation, and you can annotate the class with
+        @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")
+    """
+}
diff --git a/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt b/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
index efbdf4d..0abeeb7 100644
--- a/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
+++ b/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
@@ -20,7 +20,7 @@
 import kotlin.coroutines.CoroutineContext
 
 fun newTracingContext(name: String): CoroutineContext {
-    return createCoroutineTracingContext(name, walkStackForDefaultNames = true) { className ->
+    return createCoroutineTracingContext(name, walkStackForDefaultNames = false) { className ->
         className.startsWith("com.android.systemui.util.kotlin.JavaAdapter") ||
             className.startsWith("com.android.systemui.lifecycle.RepeatWhenAttached")
     }
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 a55df2b..103a9b5 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
@@ -52,6 +52,9 @@
 interface ExpandableController {
     /** The [Expandable] controlled by this controller. */
     val expandable: Expandable
+
+    /** Called when the [Expandable] stop being included in the composition. */
+    fun onDispose()
 }
 
 /**
@@ -88,33 +91,44 @@
     // Whether this composable is still composed. We only do the dialog exit animation if this is
     // true.
     val isComposed = remember { mutableStateOf(true) }
-    DisposableEffect(Unit) { onDispose { isComposed.value = false } }
 
-    return remember(
-        color,
-        contentColor,
-        shape,
-        borderStroke,
-        composeViewRoot,
-        density,
-        layoutDirection,
-    ) {
-        ExpandableControllerImpl(
+    val controller =
+        remember(
             color,
             contentColor,
             shape,
             borderStroke,
             composeViewRoot,
             density,
-            animatorState,
-            isDialogShowing,
-            overlay,
-            currentComposeViewInOverlay,
-            boundsInComposeViewRoot,
             layoutDirection,
-            isComposed,
-        )
+        ) {
+            ExpandableControllerImpl(
+                color,
+                contentColor,
+                shape,
+                borderStroke,
+                composeViewRoot,
+                density,
+                animatorState,
+                isDialogShowing,
+                overlay,
+                currentComposeViewInOverlay,
+                boundsInComposeViewRoot,
+                layoutDirection,
+                isComposed,
+            )
+        }
+
+    DisposableEffect(Unit) {
+        onDispose {
+            isComposed.value = false
+            if (TransitionAnimator.returnAnimationsEnabled()) {
+                controller.onDispose()
+            }
+        }
     }
+
+    return controller
 }
 
 internal class ExpandableControllerImpl(
@@ -132,19 +146,29 @@
     private val layoutDirection: LayoutDirection,
     private val isComposed: State<Boolean>,
 ) : ExpandableController {
+    /** The [ActivityTransitionAnimator.Controller] to be cleaned up [onDispose]. */
+    private var activityControllerForDisposal: ActivityTransitionAnimator.Controller? = null
+
     override val expandable: Expandable =
         object : Expandable {
             override fun activityTransitionController(
                 launchCujType: Int?,
                 cookie: ActivityTransitionAnimator.TransitionCookie?,
                 component: ComponentName?,
-                returnCujType: Int?
+                returnCujType: Int?,
+                isEphemeral: Boolean,
             ): ActivityTransitionAnimator.Controller? {
                 if (!isComposed.value) {
                     return null
                 }
 
-                return activityController(launchCujType, cookie, component, returnCujType)
+                val controller = activityController(launchCujType, cookie, component, returnCujType)
+                if (TransitionAnimator.returnAnimationsEnabled() && isEphemeral) {
+                    activityControllerForDisposal?.onDispose()
+                    activityControllerForDisposal = controller
+                }
+
+                return controller
             }
 
             override fun dialogTransitionController(
@@ -158,6 +182,11 @@
             }
         }
 
+    override fun onDispose() {
+        activityControllerForDisposal?.onDispose()
+        activityControllerForDisposal = null
+    }
+
     /**
      * Create a [TransitionAnimator.Controller] that is going to be used to drive an activity or
      * dialog animation. This controller will:
@@ -181,7 +210,7 @@
             override fun onTransitionAnimationProgress(
                 state: TransitionAnimator.State,
                 progress: Float,
-                linearProgress: Float
+                linearProgress: Float,
             ) {
                 // We copy state given that it's always the same object that is mutated by
                 // ActivityTransitionAnimator.
@@ -269,7 +298,7 @@
         launchCujType: Int?,
         cookie: ActivityTransitionAnimator.TransitionCookie?,
         component: ComponentName?,
-        returnCujType: Int?
+        returnCujType: Int?,
     ): ActivityTransitionAnimator.Controller {
         val delegate = transitionController()
         return object :
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index ae92d259..85f549d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -21,6 +21,7 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
@@ -71,9 +72,7 @@
     }
 
     @Composable
-    override fun SceneScope.Content(
-        modifier: Modifier,
-    ) =
+    override fun SceneScope.Content(modifier: Modifier) =
         BouncerScene(
             viewModel = rememberViewModel("BouncerScene") { contentViewModelFactory.create() },
             dialogFactory = dialogFactory,
@@ -89,6 +88,8 @@
 ) {
     val backgroundColor = MaterialTheme.colorScheme.surface
 
+    DisposableEffect(Unit) { onDispose { viewModel.onUiDestroyed() } }
+
     Box(modifier) {
         Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) {
             drawRect(color = backgroundColor)
@@ -101,7 +102,7 @@
             dialogFactory,
             Modifier.element(Bouncer.Elements.Content)
                 .sysuiResTag(Bouncer.TestTags.Root)
-                .fillMaxSize()
+                .fillMaxSize(),
         )
     }
 }
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 87e9c42..4705d8d 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
@@ -44,6 +44,7 @@
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.compose.animation.scene.transitions
+import com.android.systemui.Flags.communalHubOnMobile
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
@@ -186,15 +187,19 @@
     ) {
         scene(
             CommunalScenes.Blank,
-            userActions = mapOf(Swipe.Start(fromSource = Edge.End) to CommunalScenes.Communal),
+            userActions =
+                if (communalHubOnMobile()) emptyMap()
+                else mapOf(Swipe.Start(fromSource = Edge.End) to CommunalScenes.Communal),
         ) {
             // This scene shows nothing only allowing for transitions to the communal scene.
             Box(modifier = Modifier.fillMaxSize())
         }
 
-        val userActions = mapOf(Swipe.End to CommunalScenes.Blank)
-
-        scene(CommunalScenes.Communal, userActions = userActions) {
+        scene(
+            CommunalScenes.Communal,
+            userActions =
+                if (communalHubOnMobile()) emptyMap() else mapOf(Swipe.End to CommunalScenes.Blank),
+        ) {
             CommunalScene(
                 backgroundType = backgroundType,
                 colors = colors,
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 5b1203f..573e5ca 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
@@ -66,6 +66,7 @@
 import androidx.compose.foundation.layout.wrapContentHeight
 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.LazyGridState
 import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
 import androidx.compose.foundation.lazy.grid.itemsIndexed
@@ -141,6 +142,7 @@
 import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.customActions
+import androidx.compose.ui.semantics.heading
 import androidx.compose.ui.semantics.onClick
 import androidx.compose.ui.semantics.paneTitle
 import androidx.compose.ui.semantics.semantics
@@ -168,6 +170,7 @@
 import com.android.compose.ui.graphics.painter.rememberDrawablePainter
 import com.android.internal.R.dimen.system_app_widget_background_radius
 import com.android.systemui.Flags
+import com.android.systemui.Flags.communalResponsiveGrid
 import com.android.systemui.Flags.communalTimerFlickerFix
 import com.android.systemui.Flags.communalWidgetResizing
 import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -193,7 +196,6 @@
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
 import kotlin.math.max
 import kotlin.math.min
-import kotlin.math.roundToInt
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 
@@ -692,7 +694,12 @@
             onResize = onResize,
             minHeightPx = minHeightPx,
             maxHeightPx = maxHeightPx,
-            resizeMultiple = CommunalContentSize.HALF.span,
+            resizeMultiple =
+                if (communalResponsiveGrid()) {
+                    1
+                } else {
+                    CommunalContentSize.FixedSize.HALF.span
+                },
         ) {
             content(Modifier)
         }
@@ -700,14 +707,22 @@
 }
 
 @Composable
-fun calculateWidgetSize(item: CommunalContentModel, isResizable: Boolean): WidgetSizeInfo {
+fun calculateWidgetSize(
+    cellHeight: Dp?,
+    availableHeight: Dp?,
+    item: CommunalContentModel,
+    isResizable: Boolean,
+): WidgetSizeInfo {
     val density = LocalDensity.current
 
+    val minHeight = cellHeight ?: CommunalContentSize.FixedSize.HALF.dp()
+    val maxHeight = availableHeight ?: CommunalContentSize.FixedSize.FULL.dp()
+
     return if (isResizable && item is CommunalContentModel.WidgetContent.Widget) {
         with(density) {
             val minHeightPx =
                 (min(item.providerInfo.minResizeHeight, item.providerInfo.minHeight)
-                    .coerceAtLeast(CommunalContentSize.HALF.dp().toPx().roundToInt()))
+                    .coerceAtLeast(minHeight.roundToPx()))
 
             val maxHeightPx =
                 (if (item.providerInfo.maxResizeHeight > 0) {
@@ -715,7 +730,7 @@
                     } else {
                         Int.MAX_VALUE
                     })
-                    .coerceIn(minHeightPx, CommunalContentSize.FULL.dp().toPx().roundToInt())
+                    .coerceIn(minHeightPx, maxHeight.roundToPx())
 
             WidgetSizeInfo(minHeightPx, maxHeightPx)
         }
@@ -724,6 +739,37 @@
     }
 }
 
+@Composable
+private fun HorizontalGridWrapper(
+    contentPadding: PaddingValues,
+    gridState: LazyGridState,
+    modifier: Modifier = Modifier,
+    content: LazyGridScope.(sizeInfo: SizeInfo?) -> Unit,
+) {
+    if (communalResponsiveGrid()) {
+        ResponsiveLazyHorizontalGrid(
+            cellAspectRatio = 1.5f,
+            modifier = modifier,
+            state = gridState,
+            minContentPadding = contentPadding,
+            minHorizontalArrangement = Dimensions.ItemSpacing,
+            minVerticalArrangement = Dimensions.ItemSpacing,
+            content = content,
+        )
+    } else {
+        LazyHorizontalGrid(
+            modifier = modifier,
+            state = gridState,
+            rows = GridCells.Fixed(CommunalContentSize.FixedSize.FULL.span),
+            contentPadding = contentPadding,
+            horizontalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
+            verticalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
+        ) {
+            content(null)
+        }
+    }
+}
+
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun BoxScope.CommunalHubLazyGrid(
@@ -777,28 +823,32 @@
         // Since the grid has its own listener for in-grid drag events, we use a separate element
         // for android drag events.
         Box(Modifier.fillMaxSize().dragAndDropTarget(dragAndDropTargetState)) {}
+    } else if (communalResponsiveGrid()) {
+        gridModifier = gridModifier.fillMaxSize()
     } else {
         gridModifier = gridModifier.height(hubDimensions.GridHeight)
     }
 
-    val itemArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing)
-    LazyHorizontalGrid(
+    HorizontalGridWrapper(
         modifier = gridModifier,
-        state = gridState,
-        rows = GridCells.Fixed(CommunalContentSize.FULL.span),
+        gridState = gridState,
         contentPadding = contentPadding,
-        horizontalArrangement = itemArrangement,
-        verticalArrangement = itemArrangement,
-    ) {
+    ) { sizeInfo ->
         itemsIndexed(
             items = list,
             key = { _, item -> item.key },
             contentType = { _, item -> item.key },
-            span = { _, item -> GridItemSpan(item.size.span) },
+            span = { _, item -> GridItemSpan(item.getSpanOrMax(sizeInfo?.gridSize?.height)) },
         ) { index, item ->
-            val size = SizeF(Dimensions.CardWidth.value, item.size.dp().value)
+            val currentItemSpan = item.getSpanOrMax(sizeInfo?.gridSize?.height)
+            val dpSize =
+                if (sizeInfo != null) {
+                    DpSize(sizeInfo.cellSize.width, sizeInfo.calculateHeight(currentItemSpan))
+                } else {
+                    DpSize(Dimensions.CardWidth, (item.size as CommunalContentSize.FixedSize).dp())
+                }
+            val size = SizeF(dpSize.width.value, dpSize.height.value)
             val selected = item.key == selectedKey.value
-            val dpSize = DpSize(size.width.dp, size.height.dp)
             val isResizable =
                 if (item is CommunalContentModel.WidgetContent.Widget) {
                     item.providerInfo.resizeMode and AppWidgetProviderInfo.RESIZE_VERTICAL != 0
@@ -808,7 +858,7 @@
 
             val resizeableItemFrameViewModel =
                 rememberViewModel(
-                    key = item.size.span,
+                    key = currentItemSpan,
                     traceName = "ResizeableItemFrame.viewModel.$index",
                 ) {
                     ResizeableItemFrameViewModel()
@@ -821,13 +871,23 @@
                         animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
                         label = "Widget resizing outline alpha",
                     )
-                val widgetSizeInfo = calculateWidgetSize(item, isResizable)
+
+                val widgetSizeInfo =
+                    calculateWidgetSize(
+                        cellHeight = sizeInfo?.cellSize?.height,
+                        availableHeight = sizeInfo?.availableHeight,
+                        item = item,
+                        isResizable = isResizable,
+                    )
                 ResizableItemFrameWrapper(
                     key = item.key,
-                    currentSpan = GridItemSpan(item.size.span),
+                    currentSpan = GridItemSpan(currentItemSpan),
                     gridState = gridState,
                     gridContentPadding = contentPadding,
-                    verticalArrangement = itemArrangement,
+                    verticalArrangement =
+                        Arrangement.spacedBy(
+                            sizeInfo?.verticalArrangement ?: Dimensions.ItemSpacing
+                        ),
                     enabled = selected,
                     alpha = { outlineAlpha },
                     modifier =
@@ -902,11 +962,17 @@
                 Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
+            val titleForEmptyStateCTA = stringResource(R.string.title_for_empty_state_cta)
             Text(
-                text = stringResource(R.string.title_for_empty_state_cta),
+                text = titleForEmptyStateCTA,
                 style = MaterialTheme.typography.displaySmall,
                 textAlign = TextAlign.Center,
-                color = colors.secondary,
+                color = colors.primary,
+                modifier =
+                    Modifier.focusable().semantics(mergeDescendants = true) {
+                        contentDescription = titleForEmptyStateCTA
+                        heading()
+                    },
             )
             Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
                 Button(
@@ -1679,11 +1745,11 @@
     }
 }
 
-private fun CommunalContentSize.dp(): Dp {
+private fun CommunalContentSize.FixedSize.dp(): Dp {
     return when (this) {
-        CommunalContentSize.FULL -> Dimensions.CardHeightFull
-        CommunalContentSize.HALF -> Dimensions.CardHeightHalf
-        CommunalContentSize.THIRD -> Dimensions.CardHeightThird
+        CommunalContentSize.FixedSize.FULL -> Dimensions.CardHeightFull
+        CommunalContentSize.FixedSize.HALF -> Dimensions.CardHeightHalf
+        CommunalContentSize.FixedSize.THIRD -> Dimensions.CardHeightThird
     }
 }
 
@@ -1702,7 +1768,10 @@
     val GridTopSpacing: Dp
         get() {
             val result =
-                if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+                if (
+                    communalResponsiveGrid() ||
+                        config.orientation == Configuration.ORIENTATION_LANDSCAPE
+                ) {
                     114.dp
                 } else {
                     val windowMetrics =
@@ -1722,7 +1791,7 @@
             get() = 530.adjustedDp
 
         val ItemSpacing
-            get() = 50.adjustedDp
+            get() = if (communalResponsiveGrid()) 32.adjustedDp else 50.adjustedDp
 
         val CardHeightHalf
             get() = (CardHeightFull - ItemSpacing) / 2
@@ -1764,6 +1833,13 @@
 
 data class WidgetSizeInfo(val minHeightPx: Int, val maxHeightPx: Int)
 
+private fun CommunalContentModel.getSpanOrMax(maxSpan: Int?) =
+    if (maxSpan != null) {
+        size.span.coerceAtMost(maxSpan)
+    } else {
+        size.span
+    }
+
 private object Colors {
     val DisabledColorFilter by lazy { disabledColorMatrix() }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
index 3707a87..f2edec6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
@@ -27,6 +27,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.key.onPreviewKeyEvent
 import androidx.compose.ui.input.pointer.motionEventSpy
+import androidx.compose.ui.semantics.hideFromAccessibility
+import androidx.compose.ui.semantics.semantics
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 
 @OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
@@ -42,6 +44,9 @@
     Box(
         modifier =
             modifier
+                // The touchable surface is hidden for accessibility because these actions are
+                // already provided through custom accessibility actions.
+                .semantics { hideFromAccessibility() }
                 .combinedClickable(
                     onLongClick = viewModel::onLongClick,
                     onClick = viewModel::onClick,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index ef5e90b..7a50080 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -94,7 +94,7 @@
     private val scope: CoroutineScope,
     private val updateDragPositionForRemove: (draggingBoundingBox: IntRect) -> Boolean,
 ) {
-    var draggingItemKey by mutableStateOf<Any?>(null)
+    var draggingItemKey by mutableStateOf<String?>(null)
         private set
 
     var isDraggingToRemove by mutableStateOf(false)
@@ -138,7 +138,7 @@
             // before content padding from the initial pointer position
             .firstItemAtOffset(normalizedOffset - contentOffset)
             ?.apply {
-                draggingItemKey = key
+                draggingItemKey = key as String
                 draggingItemInitialOffset = this.offset.toOffset()
                 return true
             }
@@ -284,7 +284,9 @@
                             contentOffset,
                         )
                     ) {
-                        viewModel.onReorderWidgetStart()
+                        // draggingItemKey is guaranteed to be non-null here because it is set in
+                        // onDragStart()
+                        viewModel.onReorderWidgetStart(dragDropState.draggingItemKey!!)
                     }
                 },
                 onDragEnd = {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
new file mode 100644
index 0000000..3642127
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt
@@ -0,0 +1,238 @@
+/*
+ * 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 android.content.res.Configuration
+import androidx.compose.foundation.OverscrollEffect
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.calculateEndPadding
+import androidx.compose.foundation.layout.calculateStartPadding
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
+import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.rememberOverscrollEffect
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.coerceAtMost
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.times
+
+/**
+ * Renders a responsive [LazyHorizontalGrid] with dynamic columns and rows. Each cell will maintain
+ * the specified aspect ratio, but is otherwise resizeable in order to best fill the available
+ * space.
+ */
+@Composable
+fun ResponsiveLazyHorizontalGrid(
+    cellAspectRatio: Float,
+    modifier: Modifier = Modifier,
+    state: LazyGridState = rememberLazyGridState(),
+    minContentPadding: PaddingValues = PaddingValues(0.dp),
+    minHorizontalArrangement: Dp = 0.dp,
+    minVerticalArrangement: Dp = 0.dp,
+    flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+    userScrollEnabled: Boolean = true,
+    overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
+    content: LazyGridScope.(sizeInfo: SizeInfo) -> Unit,
+) {
+    check(cellAspectRatio > 0f) { "Aspect ratio must be greater than 0, but was $cellAspectRatio" }
+    check(minHorizontalArrangement.value >= 0f && minVerticalArrangement.value >= 0f) {
+        "Horizontal and vertical arrangements must be non-negative, but were " +
+            "$minHorizontalArrangement and $minVerticalArrangement, respectively."
+    }
+    BoxWithConstraints(modifier) {
+        val gridSize = rememberGridSize(maxWidth = maxWidth, maxHeight = maxHeight)
+        val layoutDirection = LocalLayoutDirection.current
+
+        val minStartPadding = minContentPadding.calculateStartPadding(layoutDirection)
+        val minEndPadding = minContentPadding.calculateEndPadding(layoutDirection)
+        val minTopPadding = minContentPadding.calculateTopPadding()
+        val minBottomPadding = minContentPadding.calculateBottomPadding()
+        val minHorizontalPadding = minStartPadding + minEndPadding
+        val minVerticalPadding = minTopPadding + minBottomPadding
+
+        // Determine the maximum allowed cell width and height based on the available width and
+        // height, and the desired number of columns and rows.
+        val maxCellWidth =
+            calculateCellSize(
+                availableSpace = maxWidth,
+                padding = minHorizontalPadding,
+                numCells = gridSize.width,
+                cellSpacing = minHorizontalArrangement,
+            )
+        val maxCellHeight =
+            calculateCellSize(
+                availableSpace = maxHeight,
+                padding = minVerticalPadding,
+                numCells = gridSize.height,
+                cellSpacing = minVerticalArrangement,
+            )
+
+        // Constrain the max size to the desired aspect ratio.
+        val finalSize =
+            calculateClosestSize(
+                maxWidth = maxCellWidth,
+                maxHeight = maxCellHeight,
+                aspectRatio = cellAspectRatio,
+            )
+
+        // Determine how much space in each dimension we've used up, and how much we have left as
+        // extra space. Distribute the extra space evenly along the content padding.
+        val usedWidth =
+            calculateUsedSpace(
+                    cellSize = finalSize.width,
+                    numCells = gridSize.width,
+                    padding = minHorizontalPadding,
+                    cellSpacing = minHorizontalArrangement,
+                )
+                .coerceAtMost(maxWidth)
+        val usedHeight =
+            calculateUsedSpace(
+                    cellSize = finalSize.height,
+                    numCells = gridSize.height,
+                    padding = minVerticalPadding,
+                    cellSpacing = minVerticalArrangement,
+                )
+                .coerceAtMost(maxHeight)
+        val extraWidth = maxWidth - usedWidth
+        val extraHeight = maxHeight - usedHeight
+
+        val finalContentPadding =
+            PaddingValues(
+                start = minStartPadding + extraWidth / 2,
+                end = minEndPadding + extraWidth / 2,
+                top = minTopPadding + extraHeight / 2,
+                bottom = minBottomPadding + extraHeight / 2,
+            )
+
+        LazyHorizontalGrid(
+            rows = GridCells.Fixed(gridSize.height),
+            modifier = Modifier.fillMaxSize(),
+            state = state,
+            contentPadding = finalContentPadding,
+            horizontalArrangement = Arrangement.spacedBy(minHorizontalArrangement),
+            verticalArrangement = Arrangement.spacedBy(minVerticalArrangement),
+            flingBehavior = flingBehavior,
+            userScrollEnabled = userScrollEnabled,
+            overscrollEffect = overscrollEffect,
+        ) {
+            content(
+                SizeInfo(
+                    cellSize = finalSize,
+                    contentPadding = finalContentPadding,
+                    verticalArrangement = minVerticalArrangement,
+                    maxHeight = maxHeight,
+                    gridSize = gridSize,
+                )
+            )
+        }
+    }
+}
+
+private fun calculateCellSize(availableSpace: Dp, padding: Dp, numCells: Int, cellSpacing: Dp): Dp =
+    (availableSpace - padding - cellSpacing * (numCells - 1)) / numCells
+
+private fun calculateUsedSpace(cellSize: Dp, numCells: Int, padding: Dp, cellSpacing: Dp): Dp =
+    cellSize * numCells + padding + (numCells - 1) * cellSpacing
+
+private fun calculateClosestSize(maxWidth: Dp, maxHeight: Dp, aspectRatio: Float): DpSize {
+    return if (maxWidth / maxHeight > aspectRatio) {
+        // Target is too wide, shrink width
+        DpSize(maxHeight * aspectRatio, maxHeight)
+    } else {
+        // Target is too tall, shrink height
+        DpSize(maxWidth, maxWidth / aspectRatio)
+    }
+}
+
+/**
+ * Provides size info of the responsive grid, since the size is dynamic.
+ *
+ * @property cellSize The size of each cell in the grid.
+ * @property verticalArrangement The space between rows in the grid.
+ * @property gridSize The size of the grid, in cell units.
+ * @property availableHeight The maximum height an item in the grid may occupy.
+ */
+data class SizeInfo(
+    val cellSize: DpSize,
+    val verticalArrangement: Dp,
+    val gridSize: IntSize,
+    private val contentPadding: PaddingValues,
+    private val maxHeight: Dp,
+) {
+    val availableHeight: Dp
+        get() =
+            maxHeight -
+                contentPadding.calculateBottomPadding() -
+                contentPadding.calculateTopPadding()
+
+    /** Calculates the height in dp of a certain number of rows. */
+    fun calculateHeight(numRows: Int): Dp {
+        return numRows * cellSize.height + (numRows - 1) * verticalArrangement
+    }
+}
+
+@Composable
+private fun rememberGridSize(maxWidth: Dp, maxHeight: Dp): IntSize {
+    val configuration = LocalConfiguration.current
+    val orientation = configuration.orientation
+
+    return remember(orientation, maxWidth, maxHeight) {
+        if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+            IntSize(
+                width = calculateNumCellsWidth(maxWidth),
+                height = calculateNumCellsHeight(maxHeight),
+            )
+        } else {
+            // In landscape we invert the rows/columns to ensure we match the same area as portrait.
+            // This keeps the number of elements in the grid consistent when changing orientation.
+            IntSize(
+                width = calculateNumCellsHeight(maxWidth),
+                height = calculateNumCellsWidth(maxHeight),
+            )
+        }
+    }
+}
+
+private fun calculateNumCellsWidth(width: Dp) =
+    // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes
+    when {
+        width >= 840.dp -> 3
+        width >= 600.dp -> 2
+        else -> 1
+    }
+
+private fun calculateNumCellsHeight(height: Dp) =
+    // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes
+    when {
+        height >= 900.dp -> 3
+        height >= 480.dp -> 2
+        else -> 1
+    }
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 2a2c2fc..105e8da 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
@@ -21,7 +21,6 @@
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -70,10 +69,7 @@
     override val id: String = "default"
 
     @Composable
-    override fun SceneScope.Content(
-        viewModel: LockscreenContentViewModel,
-        modifier: Modifier,
-    ) {
+    override fun SceneScope.Content(viewModel: LockscreenContentViewModel, modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
         val isShadeLayoutWide by viewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
         val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
@@ -85,22 +81,18 @@
             with(notificationSection) { HeadsUpNotifications() }
         }
 
-        LockscreenLongPress(
-            viewModel = viewModel.touchHandling,
-            modifier = modifier,
-        ) { onSettingsMenuPlaced ->
+        LockscreenLongPress(viewModel = viewModel.touchHandling, modifier = modifier) {
+            onSettingsMenuPlaced ->
             Layout(
                 content = {
                     // Constrained to above the lock icon.
-                    Column(
-                        modifier = Modifier.fillMaxSize(),
-                    ) {
+                    Column(modifier = Modifier.fillMaxSize()) {
                         with(statusBarSection) {
                             StatusBar(
                                 modifier =
                                     Modifier.fillMaxWidth()
                                         .padding(
-                                            horizontal = { unfoldTranslations.start.roundToInt() },
+                                            horizontal = { unfoldTranslations.start.roundToInt() }
                                         )
                             )
                         }
@@ -109,13 +101,14 @@
                             with(topAreaSection) {
                                 DefaultClockLayout(
                                     smartSpacePaddingTop = viewModel::getSmartSpacePaddingTop,
+                                    isShadeLayoutWide = isShadeLayoutWide,
                                     modifier =
                                         Modifier.thenIf(isShadeLayoutWide) {
                                                 Modifier.fillMaxWidth(0.5f)
                                             }
                                             .graphicsLayer {
                                                 translationX = unfoldTranslations.start
-                                            }
+                                            },
                                 )
                             }
                             if (isShadeLayoutWide && !isBypassEnabled) {
@@ -127,7 +120,7 @@
                                         modifier =
                                             Modifier.fillMaxWidth(0.5f)
                                                 .fillMaxHeight()
-                                                .align(alignment = Alignment.TopEnd)
+                                                .align(alignment = Alignment.TopEnd),
                                     )
                                 }
                             }
@@ -142,7 +135,7 @@
                                     AodNotificationIcons(
                                         modifier =
                                             Modifier.align(alignment = Alignment.TopStart)
-                                                .padding(start = aodIconPadding),
+                                                .padding(start = aodIconPadding)
                                     )
                                     Notifications(
                                         areNotificationsVisible = areNotificationsVisible,
@@ -152,7 +145,7 @@
                                 }
                             } else {
                                 AodNotificationIcons(
-                                    modifier = Modifier.padding(start = aodIconPadding),
+                                    modifier = Modifier.padding(start = aodIconPadding)
                                 )
                             }
                         }
@@ -205,11 +198,7 @@
                 val endShortcutMeasurable = measurables[4]
                 val settingsMenuMeasurable = measurables[5]
 
-                val noMinConstraints =
-                    constraints.copy(
-                        minWidth = 0,
-                        minHeight = 0,
-                    )
+                val noMinConstraints = constraints.copy(minWidth = 0, minHeight = 0)
                 val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
                 val lockIconBounds =
                     IntRect(
@@ -235,14 +224,8 @@
                 val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
 
                 layout(constraints.maxWidth, constraints.maxHeight) {
-                    aboveLockIconPlaceable.place(
-                        x = 0,
-                        y = 0,
-                    )
-                    lockIconPlaceable.place(
-                        x = lockIconBounds.left,
-                        y = lockIconBounds.top,
-                    )
+                    aboveLockIconPlaceable.place(x = 0, y = 0)
+                    lockIconPlaceable.place(x = lockIconBounds.left, y = lockIconBounds.top)
                     belowLockIconPlaceable.place(
                         x = 0,
                         y = constraints.maxHeight - belowLockIconPlaceable.height,
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 3ca2b9c..4a9f44b 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
@@ -17,9 +17,11 @@
 package com.android.systemui.keyguard.ui.composable.section
 
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardMediaViewModel
@@ -27,6 +29,7 @@
 import com.android.systemui.media.controls.ui.controller.MediaCarouselController
 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
 
@@ -39,13 +42,22 @@
 ) {
 
     @Composable
-    fun SceneScope.KeyguardMediaCarousel() {
+    fun SceneScope.KeyguardMediaCarousel(
+        isShadeLayoutWide: Boolean,
+        modifier: Modifier = Modifier,
+    ) {
         val isMediaVisible by keyguardMediaViewModel.isMediaVisible.collectAsStateWithLifecycle()
-
+        val horizontalPadding =
+            if (isShadeLayoutWide) {
+                dimensionResource(id = R.dimen.notification_side_paddings)
+            } else {
+                dimensionResource(id = R.dimen.notification_side_paddings) +
+                    dimensionResource(id = R.dimen.notification_panel_margin_horizontal)
+            }
         MediaCarousel(
             isVisible = isMediaVisible,
             mediaHost = mediaHost,
-            modifier = Modifier.fillMaxWidth(),
+            modifier = modifier.fillMaxWidth().padding(horizontal = horizontalPadding),
             carouselController = mediaCarouselController,
         )
     }
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 afa92f2..db33e7c 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
@@ -63,6 +63,7 @@
     @Composable
     fun SceneScope.DefaultClockLayout(
         smartSpacePaddingTop: (Resources) -> Int,
+        isShadeLayoutWide: Boolean,
         modifier: Modifier = Modifier,
     ) {
         val currentClockLayout by clockViewModel.currentClockLayout.collectAsStateWithLifecycle()
@@ -128,7 +129,7 @@
                     )
                 }
             }
-            with(mediaCarouselSection) { KeyguardMediaCarousel() }
+            with(mediaCarouselSection) { KeyguardMediaCarousel(isShadeLayoutWide) }
         }
     }
 
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 e725ce5..58336c2 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
@@ -79,8 +79,8 @@
         val MediaLandscapeTopOffset = ValueKey("MediaLandscapeTopOffset")
 
         object MediaOffset {
-            // Brightness + padding
-            val InQS = 92.dp
+            // Brightness
+            val InQS = 60.dp
             val Default = 0.dp
 
             @Composable
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 0e7165c..52adaf2 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
@@ -74,6 +74,7 @@
 import com.android.compose.animation.scene.animateSceneDpAsState
 import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.modifiers.padding
 import com.android.compose.modifiers.thenIf
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.battery.BatteryMeterViewController
@@ -379,7 +380,11 @@
                             mediaHost = mediaHost,
                             modifier =
                                 Modifier.fillMaxWidth()
-                                    .layoutId(QSMediaMeasurePolicy.LayoutId.Media),
+                                    .layoutId(QSMediaMeasurePolicy.LayoutId.Media)
+                                    .padding(
+                                        horizontal =
+                                            dimensionResource(id = R.dimen.qs_horizontal_margin)
+                                    ),
                             carouselController = mediaCarouselController,
                         )
                     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index 26c827a..3fce890 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -44,8 +44,11 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
+import com.android.systemui.plugins.qs.TileDetailsViewModel
 import com.android.systemui.qs.composefragment.ui.GridAnchor
+import com.android.systemui.qs.flags.QsDetailedView
 import com.android.systemui.qs.panels.ui.compose.EditMode
+import com.android.systemui.qs.panels.ui.compose.TileDetails
 import com.android.systemui.qs.panels.ui.compose.TileGrid
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsViewModel
@@ -116,24 +119,50 @@
     }
 }
 
+// A sealed interface to represent the possible states of the `ShadeBody`
+sealed interface ShadeBodyState {
+    data object Editing : ShadeBodyState
+    data object TileDetails : ShadeBodyState
+    data object Default : ShadeBodyState
+}
+
+// Function to map the current state of the `ShadeBody`
+fun checkQsState(isEditing: Boolean, tileDetails: TileDetailsViewModel?): ShadeBodyState {
+    if (isEditing) {
+        return ShadeBodyState.Editing
+    } else if (tileDetails != null && QsDetailedView.isEnabled) {
+        return ShadeBodyState.TileDetails
+    }
+    return ShadeBodyState.Default
+}
+
 @Composable
 fun SceneScope.ShadeBody(viewModel: QuickSettingsContainerViewModel) {
     val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
+    val tileDetails = viewModel.detailsViewModel.activeTileDetails
 
     AnimatedContent(
-        targetState = isEditing,
+        targetState = checkQsState(isEditing, tileDetails),
         transitionSpec = { fadeIn(tween(500)) togetherWith fadeOut(tween(500)) },
-    ) { editing ->
-        if (editing) {
-            EditMode(
-                viewModel = viewModel.editModeViewModel,
-                modifier = Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding),
-            )
-        } else {
-            QuickSettingsLayout(
-                viewModel = viewModel,
-                modifier = Modifier.sysuiResTag("quick_settings_panel"),
-            )
+    ) { state ->
+        when (state) {
+            ShadeBodyState.Editing -> {
+                EditMode(
+                    viewModel = viewModel.editModeViewModel,
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(QuickSettingsShade.Dimensions.Padding),
+                )
+            }
+            ShadeBodyState.TileDetails -> {
+                TileDetails(viewModel.detailsViewModel)
+            }
+            else -> {
+                QuickSettingsLayout(
+                    viewModel = viewModel,
+                    modifier = Modifier.sysuiResTag("quick_settings_panel"),
+                )
+            }
         }
     }
 }
@@ -168,7 +197,6 @@
                 modifier =
                     Modifier.fillMaxWidth()
                         .heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight),
-                viewModel.editModeViewModel::startEditing,
             )
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index ae5dd8a..5fb9416 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -19,12 +19,17 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.animateContentDpAsState
 import com.android.compose.animation.scene.animateContentFloatAsState
+import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.rememberViewModel
@@ -63,9 +68,25 @@
     }
 
     @Composable
-    override fun SceneScope.Content(
-        modifier: Modifier,
-    ) {
+    override fun SceneScope.Content(modifier: Modifier) {
+
+        val isIdle by remember {
+            derivedStateOf { layoutState.transitionState is TransitionState.Idle }
+        }
+
+        LaunchedEffect(isIdle) {
+            // Wait for being Idle on this Scene, otherwise LaunchedEffect would fire too soon,
+            // and another transition could override the NSSL stack bounds.
+            if (isIdle) {
+                // Reset the stack bounds to avoid caching these values from the previous Scenes,
+                // and not to confuse the StackScrollAlgorithm when it displays a HUN over GONE.
+                notificationStackScrolLView.get().apply {
+                    setStackTop(0f)
+                    setStackCutoff(0f)
+                }
+            }
+        }
+
         animateContentFloatAsState(
             value = QuickSettings.SharedValues.SquishinessValues.GoneSceneStarting,
             key = QuickSettings.SharedValues.TilesSquishiness,
@@ -75,9 +96,7 @@
         SnoozeableHeadsUpNotificationSpace(
             stackScrollView = notificationStackScrolLView.get(),
             viewModel =
-                rememberViewModel("GoneScene") {
-                    notificationsPlaceholderViewModelFactory.create()
-                },
+                rememberViewModel("GoneScene") { notificationsPlaceholderViewModelFactory.create() },
         )
     }
 }
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 a266e7e..c3dc84d 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
@@ -39,6 +39,7 @@
 import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.SceneTransitions
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.observableTransitionState
@@ -78,6 +79,7 @@
     sceneByKey: Map<SceneKey, Scene>,
     overlayByKey: Map<OverlayKey, Overlay>,
     initialSceneKey: SceneKey,
+    sceneTransitions: SceneTransitions,
     dataSourceDelegator: SceneDataSourceDelegator,
     qsSceneAdapter: Provider<QSSceneAdapter>,
     modifier: Modifier = Modifier,
@@ -87,7 +89,7 @@
         MutableSceneTransitionLayoutState(
             initialScene = initialSceneKey,
             canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
-            transitions = SceneContainerTransitions,
+            transitions = sceneTransitions,
         )
     }
 
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 bba3d69..22b6dbc 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
@@ -270,6 +270,7 @@
         )
     val isEmptySpaceClickable by viewModel.isEmptySpaceClickable.collectAsStateWithLifecycle()
     val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
+    val isQsEnabled by viewModel.isQsEnabled.collectAsStateWithLifecycle()
 
     val shouldPunchHoleBehindScrim =
         layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
@@ -353,14 +354,26 @@
                     )
                 }
 
+                val qqsLayoutPaddingBottom =
+                    dimensionResource(id = R.dimen.qqs_layout_padding_bottom)
                 ShadeMediaCarousel(
                     isVisible = isMediaVisible,
                     isInRow = mediaInRow,
                     mediaHost = mediaHost,
                     mediaOffsetProvider = mediaOffsetProvider,
                     carouselController = mediaCarouselController,
-                    modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.Media),
+                    modifier =
+                        Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.Media)
+                            .padding(
+                                horizontal =
+                                    shadeHorizontalPadding +
+                                        dimensionResource(id = R.dimen.qs_horizontal_margin)
+                            )
+                            .thenIf(!mediaInRow) {
+                                Modifier.padding(bottom = qqsLayoutPaddingBottom)
+                            },
                     usingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia,
+                    isQsEnabled = isQsEnabled,
                     isInSplitShade = false,
                 )
 
@@ -416,6 +429,7 @@
     val screenCornerRadius = LocalScreenCornerRadius.current
 
     val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle()
+    val isQsEnabled by viewModel.isQsEnabled.collectAsStateWithLifecycle()
     val isCustomizerShowing by
         viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle()
     val customizingAnimationDuration by
@@ -562,11 +576,16 @@
                                 mediaOffsetProvider = mediaOffsetProvider,
                                 modifier =
                                     Modifier.thenIf(
-                                        MediaContentPicker.shouldElevateMedia(layoutState)
-                                    ) {
-                                        Modifier.zIndex(1f)
-                                    },
+                                            MediaContentPicker.shouldElevateMedia(layoutState)
+                                        ) {
+                                            Modifier.zIndex(1f)
+                                        }
+                                        .padding(
+                                            horizontal =
+                                                dimensionResource(id = R.dimen.qs_horizontal_margin)
+                                        ),
                                 carouselController = mediaCarouselController,
+                                isQsEnabled = isQsEnabled,
                                 isInSplitShade = true,
                             )
                         }
@@ -620,10 +639,14 @@
     mediaHost: MediaHost,
     carouselController: MediaCarouselController,
     mediaOffsetProvider: ShadeMediaOffsetProvider,
+    isInSplitShade: Boolean,
+    isQsEnabled: Boolean,
     modifier: Modifier = Modifier,
     usingCollapsedLandscapeMedia: Boolean = false,
-    isInSplitShade: Boolean,
 ) {
+    if (!isQsEnabled) {
+        return
+    }
     MediaCarousel(
         modifier = modifier.fillMaxWidth(),
         isVisible = isVisible,
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 cf0ba51..caf5e41 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
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:Suppress("NOTHING_TO_INLINE")
-
 package com.android.compose.animation.scene
 
 import androidx.compose.foundation.gestures.Orientation
@@ -97,68 +95,11 @@
     internal val positionalThreshold
         get() = with(layoutImpl.density) { 56.dp.toPx() }
 
-    /**
-     * Whether we should immediately intercept a gesture.
-     *
-     * Note: if this returns true, then [onDragStarted] will be called with overSlop equal to 0f,
-     * indicating that the transition should be intercepted.
-     */
-    internal fun shouldImmediatelyIntercept(pointersDown: PointersInfo.PointersDown?): Boolean {
-        // We don't intercept the touch if we are not currently driving the transition.
-        val dragController = dragController
-        if (dragController?.isDrivingTransition != true) {
-            return false
-        }
-
-        val swipeAnimation = dragController.swipeAnimation
-
-        // Only intercept the current transition if one of the 2 swipes results is also a transition
-        // between the same pair of contents.
-        val swipes = computeSwipes(pointersDown)
-        val fromContent = layoutImpl.content(swipeAnimation.currentContent)
-        val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent)
-        val currentScene = layoutImpl.state.currentScene
-        val contentTransition = swipeAnimation.contentTransition
-        return (upOrLeft != null &&
-            contentTransition.isTransitioningBetween(
-                fromContent.key,
-                upOrLeft.toContent(currentScene),
-            )) ||
-            (downOrRight != null &&
-                contentTransition.isTransitioningBetween(
-                    fromContent.key,
-                    downOrRight.toContent(currentScene),
-                ))
-    }
-
     override fun onDragStarted(
         pointersDown: PointersInfo.PointersDown?,
         overSlop: Float,
     ): DragController {
-        if (overSlop == 0f) {
-            val oldDragController = dragController
-            check(oldDragController != null && oldDragController.isDrivingTransition) {
-                val isActive = oldDragController?.isDrivingTransition
-                "onDragStarted(overSlop=0f) requires an active dragController, but was $isActive"
-            }
-
-            // This [transition] was already driving the animation: simply take over it.
-            // Stop animating and start from the current offset.
-            val oldSwipeAnimation = oldDragController.swipeAnimation
-
-            // We need to recompute the swipe results since this is a new gesture, and the
-            // fromScene.userActions may have changed.
-            val swipes = oldDragController.swipes
-            swipes.updateSwipesResults(
-                fromContent = layoutImpl.content(oldSwipeAnimation.fromContent)
-            )
-
-            // A new gesture should always create a new SwipeAnimation. This way there cannot be
-            // different gestures controlling the same transition.
-            val swipeAnimation = createSwipeAnimation(oldSwipeAnimation)
-            return updateDragController(swipes, swipeAnimation)
-        }
-
+        check(overSlop != 0f)
         val swipes = computeSwipes(pointersDown)
         val fromContent = layoutImpl.contentForUserActions()
 
@@ -183,7 +124,7 @@
         return newDragController
     }
 
-    internal fun createSwipeAnimation(swipes: Swipes, result: UserActionResult): SwipeAnimation<*> {
+    private fun createSwipeAnimation(swipes: Swipes, result: UserActionResult): SwipeAnimation<*> {
         val upOrLeftResult = swipes.upOrLeftResult
         val downOrRightResult = swipes.downOrRightResult
         val isUpOrLeft =
@@ -196,7 +137,7 @@
         return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation)
     }
 
-    internal fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? {
+    private fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? {
         return layoutImpl.swipeSourceDetector.source(
             layoutSize = layoutImpl.lastSize,
             position = startedPosition.round(),
@@ -291,101 +232,24 @@
             return 0f
         }
 
-        val toContent = swipeAnimation.toContent
         val distance = swipeAnimation.distance()
         val previousOffset = swipeAnimation.dragOffset
         val desiredOffset = previousOffset + delta
+        val desiredProgress = swipeAnimation.computeProgress(desiredOffset)
 
-        fun hasReachedToSceneUpOrLeft() =
-            distance < 0 &&
-                desiredOffset <= distance &&
-                swipes.upOrLeftResult?.toContent(layoutState.currentScene) == toContent
-
-        fun hasReachedToSceneDownOrRight() =
-            distance > 0 &&
-                desiredOffset >= distance &&
-                swipes.downOrRightResult?.toContent(layoutState.currentScene) == toContent
-
-        // Considering accelerated swipe: Change fromContent in the case where the user quickly
-        // swiped multiple times in the same direction to accelerate the transition from A => B then
-        // B => C.
-        //
-        // TODO(b/290184746): the second drag needs to pass B to work. Add support for flinging
-        //  twice before B has been reached
-        val hasReachedToContent =
-            swipeAnimation.currentContent == toContent &&
-                (hasReachedToSceneUpOrLeft() || hasReachedToSceneDownOrRight())
-
-        val fromContent: ContentKey
-        val currentTransitionOffset: Float
-        val newOffset: Float
-        val consumedDelta: Float
-        if (hasReachedToContent) {
-            // The new transition will start from the current toContent.
-            fromContent = toContent
-
-            // The current transition is completed (we have reached the full swipe distance).
-            currentTransitionOffset = distance
-
-            // The next transition will start with the remaining offset.
-            newOffset = desiredOffset - distance
-            consumedDelta = delta
-        } else {
-            fromContent = swipeAnimation.fromContent
-            val desiredProgress = swipeAnimation.computeProgress(desiredOffset)
-
-            // Note: the distance could be negative if fromContent is above or to the left of
-            // toContent.
-            currentTransitionOffset =
-                when {
-                    distance == DistanceUnspecified ||
-                        swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) ->
-                        desiredOffset
-
-                    distance > 0f -> desiredOffset.fastCoerceIn(0f, distance)
-                    else -> desiredOffset.fastCoerceIn(distance, 0f)
-                }
-
-            // If there is a new transition, we will use the same offset
-            newOffset = currentTransitionOffset
-            consumedDelta = newOffset - previousOffset
-        }
-
-        swipeAnimation.dragOffset = currentTransitionOffset
-
-        if (hasReachedToContent) {
-            swipes.updateSwipesResults(draggableHandler.layoutImpl.content(fromContent))
-        }
-        val result = swipes.findUserActionResult(directionOffset = newOffset)
-
-        if (result == null) {
-            onCancel(canChangeContent = true)
-            return 0f
-        }
-
-        val currentTransitionIrreversible =
-            if (swipeAnimation.isUpOrLeft) {
-                swipes.upOrLeftResult?.isIrreversible ?: false
-            } else {
-                swipes.downOrRightResult?.isIrreversible ?: false
+        // Note: the distance could be negative if fromContent is above or to the left of
+        // toContent.
+        val newOffset =
+            when {
+                distance == DistanceUnspecified ||
+                    swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) ->
+                    desiredOffset
+                distance > 0f -> desiredOffset.fastCoerceIn(0f, distance)
+                else -> desiredOffset.fastCoerceIn(distance, 0f)
             }
 
-        val needNewTransition =
-            !currentTransitionIrreversible &&
-                (hasReachedToContent ||
-                    result.toContent(layoutState.currentScene) != swipeAnimation.toContent ||
-                    result.transitionKey != swipeAnimation.contentTransition.key)
-
-        if (needNewTransition) {
-            // Make sure the current transition will finish to the right current scene.
-            swipeAnimation.currentContent = fromContent
-
-            val newSwipeAnimation = draggableHandler.createSwipeAnimation(swipes, result)
-            newSwipeAnimation.dragOffset = newOffset
-            updateTransition(newSwipeAnimation)
-        }
-
-        return consumedDelta
+        swipeAnimation.dragOffset = newOffset
+        return newOffset - previousOffset
     }
 
     override suspend fun onStop(velocity: Float, canChangeContent: Boolean): Float {
@@ -499,7 +363,9 @@
     var upOrLeftResult: UserActionResult? = null
     var downOrRightResult: UserActionResult? = null
 
-    fun computeSwipesResults(fromContent: Content): Pair<UserActionResult?, UserActionResult?> {
+    private fun computeSwipesResults(
+        fromContent: Content
+    ): Pair<UserActionResult?, UserActionResult?> {
         val upOrLeftResult = fromContent.findActionResultBestMatch(swipe = upOrLeft)
         val downOrRightResult = fromContent.findActionResultBestMatch(swipe = downOrRight)
         return upOrLeftResult to downOrRightResult
@@ -564,48 +430,9 @@
                 .shouldEnableSwipes(draggableHandler.orientation)
         }
 
-        var isIntercepting = false
-
         return PriorityNestedScrollConnection(
             orientation = draggableHandler.orientation,
-            canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
-                val pointersDown: PointersInfo.PointersDown? =
-                    when (val info = pointersInfoOwner.pointersInfo()) {
-                        PointersInfo.MouseWheel -> {
-                            // Do not support mouse wheel interactions
-                            return@PriorityNestedScrollConnection false
-                        }
-
-                        is PointersInfo.PointersDown -> info
-                        null -> null
-                    }
-
-                canChangeScene =
-                    if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
-
-                val canInterceptSwipeTransition =
-                    canChangeScene &&
-                        offsetAvailable != 0f &&
-                        draggableHandler.shouldImmediatelyIntercept(pointersDown)
-                if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
-
-                val layoutImpl = draggableHandler.layoutImpl
-                val threshold = layoutImpl.transitionInterceptionThreshold
-                val hasSnappedToIdle = layoutImpl.state.snapToIdleIfClose(threshold)
-                if (hasSnappedToIdle) {
-                    // If the current swipe transition is closed to 0f or 1f, then we want to
-                    // interrupt the transition (snapping it to Idle) and scroll the list.
-                    return@PriorityNestedScrollConnection false
-                }
-
-                lastPointersDown = pointersDown
-
-                // If the current swipe transition is *not* closed to 0f or 1f, then we want the
-                // scroll events to intercept the current transition to continue the scene
-                // transition.
-                isIntercepting = true
-                true
-            },
+            canStartPreScroll = { _, _, _ -> false },
             canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ ->
                 val behavior: NestedScrollBehavior =
                     when {
@@ -629,29 +456,22 @@
                     }
                 lastPointersDown = pointersDown
 
-                val canStart =
-                    when (behavior) {
-                        NestedScrollBehavior.EdgeNoPreview -> {
-                            canChangeScene = isZeroOffset
-                            isZeroOffset && shouldEnableSwipes()
-                        }
-
-                        NestedScrollBehavior.EdgeWithPreview -> {
-                            canChangeScene = isZeroOffset
-                            shouldEnableSwipes()
-                        }
-
-                        NestedScrollBehavior.EdgeAlways -> {
-                            canChangeScene = true
-                            shouldEnableSwipes()
-                        }
+                when (behavior) {
+                    NestedScrollBehavior.EdgeNoPreview -> {
+                        canChangeScene = isZeroOffset
+                        isZeroOffset && shouldEnableSwipes()
                     }
 
-                if (canStart) {
-                    isIntercepting = false
-                }
+                    NestedScrollBehavior.EdgeWithPreview -> {
+                        canChangeScene = isZeroOffset
+                        shouldEnableSwipes()
+                    }
 
-                canStart
+                    NestedScrollBehavior.EdgeAlways -> {
+                        canChangeScene = true
+                        shouldEnableSwipes()
+                    }
+                }
             },
             canStartPostFling = { velocityAvailable ->
                 val behavior: NestedScrollBehavior =
@@ -676,19 +496,14 @@
                     }
                 lastPointersDown = pointersDown
 
-                val canStart = behavior.canStartOnPostFling && shouldEnableSwipes()
-                if (canStart) {
-                    isIntercepting = false
-                }
-
-                canStart
+                behavior.canStartOnPostFling && shouldEnableSwipes()
             },
             onStart = { firstScroll ->
                 scrollController(
                     dragController =
                         draggableHandler.onDragStarted(
                             pointersDown = lastPointersDown,
-                            overSlop = if (isIntercepting) 0f else firstScroll,
+                            overSlop = firstScroll,
                         ),
                     canChangeScene = canChangeScene,
                     pointersInfoOwner = pointersInfoOwner,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index eb2a016..e819bfd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -53,10 +53,10 @@
 import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
 import com.android.compose.animation.scene.transformation.InterpolatedPropertyTransformation
 import com.android.compose.animation.scene.transformation.PropertyTransformation
-import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.animation.scene.transformation.TransformationWithRange
 import com.android.compose.modifiers.thenIf
 import com.android.compose.ui.graphics.drawInContainer
+import com.android.compose.ui.util.IntIndexedMap
 import com.android.compose.ui.util.lerp
 import kotlin.math.roundToInt
 import kotlinx.coroutines.launch
@@ -70,6 +70,14 @@
     val stateByContent = SnapshotStateMap<ContentKey, State>()
 
     /**
+     * A sorted map of nesting depth (key) to content key (value). For shared elements it is used to
+     * determine which content this element should be rendered by. The nesting depth refers to the
+     * number of STLs nested within each other, starting at 0 for the parent STL and increasing by
+     * one for each nested [NestedSceneTransitionLayout].
+     */
+    val renderAuthority = IntIndexedMap<ContentKey>()
+
+    /**
      * The last transition that was used when computing the state (size, position and alpha) of this
      * element in any content, or `null` if it was last laid out when idle.
      */
@@ -232,9 +240,8 @@
     private val element: Element
         get() = _element!!
 
-    private var _stateInContent: Element.State? = null
     private val stateInContent: Element.State
-        get() = _stateInContent!!
+        get() = element.stateByContent.getValue(content.key)
 
     override val traverseKey: Any = ElementTraverseKey
 
@@ -248,9 +255,13 @@
         val element =
             layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it }
         _element = element
-        _stateInContent =
-            element.stateByContent[content.key]
-                ?: Element.State(content.key).also { element.stateByContent[content.key] = it }
+        addToRenderAuthority(element)
+        if (!element.stateByContent.contains(content.key)) {
+            val elementState = Element.State(content.key)
+            element.stateByContent[content.key] = elementState
+
+            layoutImpl.ancestorContentKeys.forEach { element.stateByContent[it] = elementState }
+        }
     }
 
     private fun addNodeToContentState() {
@@ -272,8 +283,20 @@
         removeNodeFromContentState()
         maybePruneMaps(layoutImpl, element, stateInContent)
 
+        removeFromRenderAuthority()
         _element = null
-        _stateInContent = null
+    }
+
+    private fun addToRenderAuthority(element: Element) {
+        val nestingDepth = layoutImpl.ancestorContentKeys.size
+        element.renderAuthority[nestingDepth] = content.key
+    }
+
+    private fun removeFromRenderAuthority() {
+        val nestingDepth = layoutImpl.ancestorContentKeys.size
+        if (element.renderAuthority[nestingDepth] == content.key) {
+            element.renderAuthority.remove(nestingDepth)
+        }
     }
 
     private fun removeNodeFromContentState() {
@@ -346,15 +369,17 @@
         val elementState = elementState(layoutImpl, element, currentTransitionStates)
         if (elementState == null) {
             // If the element is not part of any transition, place it normally in its idle scene.
+            // This is the case if for example a transition between two overlays is ongoing where
+            // sharedElement isn't part of either but the element is still rendered as part of
+            // the underlying scene that is currently not being transitioned.
             val currentState = currentTransitionStates.last()
-            val placeInThisContent =
+            val shouldPlaceInThisContent =
                 elementContentWhenIdle(
                     layoutImpl,
                     currentState,
                     isInContent = { it in element.stateByContent },
                 ) == content.key
-
-            return if (placeInThisContent) {
+            return if (shouldPlaceInThisContent) {
                 placeNormally(measurable, constraints)
             } else {
                 doNotPlace(measurable, constraints)
@@ -536,7 +561,9 @@
 
         stateInContent.clearLastPlacementValues()
         traverseDescendants(ElementTraverseKey) { node ->
-            (node as ElementNode)._stateInContent?.clearLastPlacementValues()
+            if ((node as ElementNode)._element != null) {
+                node.stateInContent.clearLastPlacementValues()
+            }
             TraversableNode.Companion.TraverseDescendantsAction.ContinueTraversal
         }
     }
@@ -569,22 +596,30 @@
             element: Element,
             stateInContent: Element.State,
         ) {
-            // If element is not composed in this content anymore, remove the content values. This
-            // works because [onAttach] is called before [onDetach], so if an element is moved from
-            // the UI tree we will first add the new code location then remove the old one.
-            if (
-                stateInContent.nodes.isEmpty() &&
-                    element.stateByContent[stateInContent.content] == stateInContent
-            ) {
-                element.stateByContent.remove(stateInContent.content)
-
-                // If the element is not composed in any content, remove it from the elements map.
+            fun pruneForContent(contentKey: ContentKey) {
+                // If element is not composed in this content anymore, remove the content values.
+                // This works because [onAttach] is called before [onDetach], so if an element is
+                // moved from the UI tree we will first add the new code location then remove the
+                // old one.
                 if (
-                    element.stateByContent.isEmpty() && layoutImpl.elements[element.key] == element
+                    stateInContent.nodes.isEmpty() &&
+                        element.stateByContent[contentKey] == stateInContent
                 ) {
-                    layoutImpl.elements.remove(element.key)
+                    element.stateByContent.remove(contentKey)
+
+                    // If the element is not composed in any content, remove it from the elements
+                    // map.
+                    if (
+                        element.stateByContent.isEmpty() &&
+                            layoutImpl.elements[element.key] == element
+                    ) {
+                        layoutImpl.elements.remove(element.key)
+                    }
                 }
             }
+
+            pruneForContent(stateInContent.content)
+            layoutImpl.ancestorContentKeys.forEach { content -> pruneForContent(content) }
         }
     }
 }
@@ -890,12 +925,13 @@
     val transition =
         when (elementState) {
             is TransitionState.Idle -> {
-                return content ==
-                    elementContentWhenIdle(
-                        layoutImpl,
-                        elementState,
-                        isInContent = { it in element.stateByContent },
-                    )
+                return element.shouldBeRenderedBy(content) &&
+                    content ==
+                        elementContentWhenIdle(
+                            layoutImpl,
+                            elementState,
+                            isInContent = { it in element.stateByContent },
+                        )
             }
             is TransitionState.Transition -> elementState
         }
@@ -925,76 +961,7 @@
         return true
     }
 
-    return shouldPlaceOrComposeSharedElement(
-        layoutImpl,
-        content,
-        element.key,
-        transition,
-        isInContent = { it in element.stateByContent },
-    )
-}
-
-internal inline fun shouldPlaceOrComposeSharedElement(
-    layoutImpl: SceneTransitionLayoutImpl,
-    content: ContentKey,
-    element: ElementKey,
-    transition: TransitionState.Transition,
-    isInContent: (ContentKey) -> Boolean,
-): Boolean {
-    val overscrollContent = transition.currentOverscrollSpec?.content
-    if (overscrollContent != null) {
-        return when (transition) {
-            // If we are overscrolling between scenes, only place/compose the element in the
-            // overscrolling scene.
-            is TransitionState.Transition.ChangeScene -> content == overscrollContent
-
-            // If we are overscrolling an overlay, place/compose the element if [content] is the
-            // overscrolling content or if [content] is the current scene and the overscrolling
-            // overlay does not contain the element.
-            is TransitionState.Transition.ReplaceOverlay,
-            is TransitionState.Transition.ShowOrHideOverlay ->
-                content == overscrollContent ||
-                    (content == transition.currentScene && !isInContent(overscrollContent))
-        }
-    }
-
-    val scenePicker = element.contentPicker
-    val pickedScene =
-        scenePicker.contentDuringTransition(
-            element = element,
-            transition = transition,
-            fromContentZIndex = layoutImpl.content(transition.fromContent).zIndex,
-            toContentZIndex = layoutImpl.content(transition.toContent).zIndex,
-        )
-
-    return pickedScene == content
-}
-
-private fun isSharedElementEnabled(
-    element: ElementKey,
-    transition: TransitionState.Transition,
-): Boolean {
-    return sharedElementTransformation(element, transition)?.transformation?.enabled ?: true
-}
-
-internal fun sharedElementTransformation(
-    element: ElementKey,
-    transition: TransitionState.Transition,
-): TransformationWithRange<SharedElementTransformation>? {
-    val transformationSpec = transition.transformationSpec
-    val sharedInFromContent =
-        transformationSpec.transformations(element, transition.fromContent).shared
-    val sharedInToContent = transformationSpec.transformations(element, transition.toContent).shared
-
-    // The sharedElement() transformation must either be null or be the same in both contents.
-    if (sharedInFromContent != sharedInToContent) {
-        error(
-            "Different sharedElement() transformations matched $element " +
-                "(from=$sharedInFromContent to=$sharedInToContent)"
-        )
-    }
-
-    return sharedInFromContent
+    return shouldPlaceSharedElement(layoutImpl, content, element.key, transition)
 }
 
 /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 509a16c..17510c7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -196,18 +196,54 @@
         is TransitionState.Transition -> {
             // During transitions, always compose movable elements in the scene picked by their
             // content picker.
-            val contents = element.contentPicker.contents
-            shouldPlaceOrComposeSharedElement(
+            shouldComposeMoveableElement(
                 layoutImpl,
                 content,
                 element,
                 elementState,
-                isInContent = { contents.contains(it) },
+                element.contentPicker.contents,
             )
         }
     }
 }
 
+private fun shouldComposeMoveableElement(
+    layoutImpl: SceneTransitionLayoutImpl,
+    content: ContentKey,
+    elementKey: ElementKey,
+    transition: TransitionState.Transition,
+    containingContents: Set<ContentKey>,
+): Boolean {
+    val overscrollContent = transition.currentOverscrollSpec?.content
+    if (overscrollContent != null) {
+        return when (transition) {
+            // If we are overscrolling between scenes, only place/compose the element in the
+            // overscrolling scene.
+            is TransitionState.Transition.ChangeScene -> content == overscrollContent
+
+            // If we are overscrolling an overlay, place/compose the element if [content] is the
+            // overscrolling content or if [content] is the current scene and the overscrolling
+            // overlay does not contain the element.
+            is TransitionState.Transition.ReplaceOverlay,
+            is TransitionState.Transition.ShowOrHideOverlay ->
+                content == overscrollContent ||
+                    (content == transition.currentScene &&
+                        !containingContents.contains(overscrollContent))
+        }
+    }
+
+    val scenePicker = elementKey.contentPicker
+    val pickedScene =
+        scenePicker.contentDuringTransition(
+            element = elementKey,
+            transition = transition,
+            fromContentZIndex = layoutImpl.content(transition.fromContent).zIndex,
+            toContentZIndex = layoutImpl.content(transition.toContent).zIndex,
+        )
+
+    return pickedScene == content
+}
+
 private fun movableElementState(
     element: MovableElementKey,
     transitionStates: List<TransitionState>,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 59ac68b..f5f01d4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -80,7 +80,6 @@
 @Stable
 internal fun Modifier.multiPointerDraggable(
     orientation: Orientation,
-    startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
     onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
     onFirstPointerDown: () -> Unit = {},
     swipeDetector: SwipeDetector = DefaultSwipeDetector,
@@ -89,7 +88,6 @@
     this.then(
         MultiPointerDraggableElement(
             orientation,
-            startDragImmediately,
             onDragStarted,
             onFirstPointerDown,
             swipeDetector,
@@ -99,7 +97,6 @@
 
 private data class MultiPointerDraggableElement(
     private val orientation: Orientation,
-    private val startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
     private val onDragStarted:
         (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
     private val onFirstPointerDown: () -> Unit,
@@ -109,7 +106,6 @@
     override fun create(): MultiPointerDraggableNode =
         MultiPointerDraggableNode(
             orientation = orientation,
-            startDragImmediately = startDragImmediately,
             onDragStarted = onDragStarted,
             onFirstPointerDown = onFirstPointerDown,
             swipeDetector = swipeDetector,
@@ -118,7 +114,6 @@
 
     override fun update(node: MultiPointerDraggableNode) {
         node.orientation = orientation
-        node.startDragImmediately = startDragImmediately
         node.onDragStarted = onDragStarted
         node.onFirstPointerDown = onFirstPointerDown
         node.swipeDetector = swipeDetector
@@ -127,16 +122,11 @@
 
 internal class MultiPointerDraggableNode(
     orientation: Orientation,
-    var startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
     var onDragStarted: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
     var onFirstPointerDown: () -> Unit,
     swipeDetector: SwipeDetector = DefaultSwipeDetector,
     private val dispatcher: NestedScrollDispatcher,
-) :
-    DelegatingNode(),
-    PointerInputModifierNode,
-    CompositionLocalConsumerModifierNode,
-    SpaceVectorConverter {
+) : DelegatingNode(), PointerInputModifierNode, CompositionLocalConsumerModifierNode {
     private val pointerTracker = delegate(SuspendingPointerInputModifierNode { pointerTracker() })
     private val pointerInput = delegate(SuspendingPointerInputModifierNode { pointerInput() })
     private val velocityTracker = VelocityTracker()
@@ -151,13 +141,13 @@
 
     private var converter = SpaceVectorConverter(orientation)
 
-    override fun Offset.toFloat(): Float = with(converter) { this@toFloat.toFloat() }
+    fun Offset.toFloat(): Float = with(converter) { this@toFloat.toFloat() }
 
-    override fun Velocity.toFloat(): Float = with(converter) { this@toFloat.toFloat() }
+    fun Velocity.toFloat(): Float = with(converter) { this@toFloat.toFloat() }
 
-    override fun Float.toOffset(): Offset = with(converter) { this@toOffset.toOffset() }
+    fun Float.toOffset(): Offset = with(converter) { this@toOffset.toOffset() }
 
-    override fun Float.toVelocity(): Velocity = with(converter) { this@toVelocity.toVelocity() }
+    fun Float.toVelocity(): Velocity = with(converter) { this@toVelocity.toVelocity() }
 
     var orientation: Orientation = orientation
         set(value) {
@@ -297,7 +287,6 @@
                 try {
                     detectDragGestures(
                         orientation = orientation,
-                        startDragImmediately = startDragImmediately,
                         onDragStart = { pointersDown, overSlop ->
                             onDragStarted(pointersDown, overSlop)
                         },
@@ -438,13 +427,11 @@
      * Detect drag gestures in the given [orientation].
      *
      * This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and
-     * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for:
-     * 1) starting the gesture immediately without requiring a drag >= touch slope;
-     * 2) passing the number of pointers down to [onDragStart].
+     * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for passing
+     * the number of pointers down to [onDragStart].
      */
     private suspend fun AwaitPointerEventScope.detectDragGestures(
         orientation: Orientation,
-        startDragImmediately: (pointersDown: PointersInfo.PointersDown) -> Boolean,
         onDragStart: (pointersDown: PointersInfo.PointersDown, overSlop: Float) -> DragController,
         onDrag: (controller: DragController, dragAmount: Float) -> Unit,
         onDragEnd: (controller: DragController) -> Unit,
@@ -470,71 +457,49 @@
                 .first()
 
         var overSlop = 0f
-        var lastPointersDown: PointersInfo.PointersDown =
+        val onSlopReached = { change: PointerInputChange, over: Float ->
+            if (swipeDetector.detectSwipe(change)) {
+                change.consume()
+                overSlop = over
+            }
+        }
+
+        // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once it
+        // is public.
+        val drag =
+            when (orientation) {
+                Orientation.Horizontal ->
+                    awaitHorizontalTouchSlopOrCancellation(consumablePointer.id, onSlopReached)
+                Orientation.Vertical ->
+                    awaitVerticalTouchSlopOrCancellation(consumablePointer.id, onSlopReached)
+            } ?: return
+
+        val lastPointersDown =
             checkNotNull(pointersInfo()) {
                 "We should have pointers down, last event: $currentEvent"
             }
                 as PointersInfo.PointersDown
-
-        val drag =
-            if (startDragImmediately(lastPointersDown)) {
-                consumablePointer.consume()
-                consumablePointer
-            } else {
-                val onSlopReached = { change: PointerInputChange, over: Float ->
-                    if (swipeDetector.detectSwipe(change)) {
-                        change.consume()
-                        overSlop = over
-                    }
+        // Make sure that overSlop is not 0f. This can happen when the user drags by exactly
+        // the touch slop. However, the overSlop we pass to onDragStarted() is used to
+        // compute the direction we are dragging in, so overSlop should never be 0f.
+        if (overSlop == 0f) {
+            // If the user drags in the opposite direction, the delta becomes zero because
+            // we return to the original point. Therefore, we should use the previous event
+            // to calculate the direction.
+            val delta = (drag.position - drag.previousPosition).toFloat()
+            check(delta != 0f) {
+                buildString {
+                    append("delta is equal to 0 ")
+                    append("touchSlop ${currentValueOf(LocalViewConfiguration).touchSlop} ")
+                    append("consumablePointer.position ${consumablePointer.position} ")
+                    append("drag.position ${drag.position} ")
+                    append("drag.previousPosition ${drag.previousPosition}")
                 }
-
-                // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once it
-                // is public.
-                val drag =
-                    when (orientation) {
-                        Orientation.Horizontal ->
-                            awaitHorizontalTouchSlopOrCancellation(
-                                consumablePointer.id,
-                                onSlopReached,
-                            )
-                        Orientation.Vertical ->
-                            awaitVerticalTouchSlopOrCancellation(
-                                consumablePointer.id,
-                                onSlopReached,
-                            )
-                    } ?: return
-
-                lastPointersDown =
-                    checkNotNull(pointersInfo()) {
-                        "We should have pointers down, last event: $currentEvent"
-                    }
-                        as PointersInfo.PointersDown
-                // Make sure that overSlop is not 0f. This can happen when the user drags by exactly
-                // the touch slop. However, the overSlop we pass to onDragStarted() is used to
-                // compute the direction we are dragging in, so overSlop should never be 0f unless
-                // we intercept an ongoing swipe transition (i.e. startDragImmediately() returned
-                // true).
-                if (overSlop == 0f) {
-                    // If the user drags in the opposite direction, the delta becomes zero because
-                    // we return to the original point. Therefore, we should use the previous event
-                    // to calculate the direction.
-                    val delta = (drag.position - drag.previousPosition).toFloat()
-                    check(delta != 0f) {
-                        buildString {
-                            append("delta is equal to 0 ")
-                            append("touchSlop ${currentValueOf(LocalViewConfiguration).touchSlop} ")
-                            append("consumablePointer.position ${consumablePointer.position} ")
-                            append("drag.position ${drag.position} ")
-                            append("drag.previousPosition ${drag.previousPosition}")
-                        }
-                    }
-                    overSlop = delta.sign
-                }
-                drag
             }
+            overSlop = delta.sign
+        }
 
         val controller = onDragStart(lastPointersDown, overSlop)
-
         val successful: Boolean
         try {
             onDrag(controller, overSlop)
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 d3ddb50..a14b2b3 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
@@ -28,6 +28,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.pointer.PointerType
+import androidx.compose.ui.layout.LookaheadScope
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.Density
@@ -68,7 +69,7 @@
         swipeDetector,
         transitionInterceptionThreshold,
         onLayoutImpl = null,
-        builder,
+        builder = builder,
     )
 }
 
@@ -261,8 +262,21 @@
      * lists keep a constant size during transitions even if its elements are growing/shrinking.
      */
     fun Modifier.noResizeDuringTransitions(): Modifier
+
+    /**
+     * A [NestedSceneTransitionLayout] will share its elements with its ancestor STLs therefore
+     * enabling sharedElement transitions between them.
+     */
+    // TODO(b/380070506): Add more parameters when default params are supported in Kotlin 2.0.21
+    @Composable
+    fun NestedSceneTransitionLayout(
+        state: SceneTransitionLayoutState,
+        modifier: Modifier,
+        builder: SceneTransitionLayoutScope.() -> Unit,
+    )
 }
 
+@Deprecated("Use ContentScope instead", ReplaceWith("ContentScope"))
 typealias SceneScope = ContentScope
 
 @Stable
@@ -540,12 +554,6 @@
      * bigger than 100% when the user released their finger. `
      */
     open val requiresFullDistanceSwipe: Boolean,
-
-    /**
-     * Whether swiping back in the opposite direction past the origin point of the swipe can replace
-     * the action with the action for the opposite direction.
-     */
-    open val isIrreversible: Boolean = false,
 ) {
     internal abstract fun toContent(currentScene: SceneKey): ContentKey
 
@@ -555,7 +563,6 @@
         val toScene: SceneKey,
         override val transitionKey: TransitionKey? = null,
         override val requiresFullDistanceSwipe: Boolean = false,
-        override val isIrreversible: Boolean = false,
     ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
         override fun toContent(currentScene: SceneKey): ContentKey = toScene
     }
@@ -565,7 +572,6 @@
         val overlay: OverlayKey,
         override val transitionKey: TransitionKey? = null,
         override val requiresFullDistanceSwipe: Boolean = false,
-        override val isIrreversible: Boolean = false,
     ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
         override fun toContent(currentScene: SceneKey): ContentKey = overlay
     }
@@ -608,14 +614,7 @@
              * the user released their finger.
              */
             requiresFullDistanceSwipe: Boolean = false,
-
-            /**
-             * Whether swiping back in the opposite direction past the origin point of the swipe can
-             * replace the action with the action for the opposite direction.
-             */
-            isIrreversible: Boolean = false,
-        ): UserActionResult =
-            ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe, isIrreversible)
+        ): UserActionResult = ChangeScene(toScene, transitionKey, requiresFullDistanceSwipe)
 
         /** A [UserActionResult] that shows [toOverlay]. */
         operator fun invoke(
@@ -677,6 +676,9 @@
     swipeDetector: SwipeDetector = DefaultSwipeDetector,
     transitionInterceptionThreshold: Float = 0f,
     onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
+    sharedElementMap: MutableMap<ElementKey, Element> = remember { mutableMapOf() },
+    ancestorContentKeys: List<ContentKey> = emptyList(),
+    lookaheadScope: LookaheadScope? = null,
     builder: SceneTransitionLayoutScope.() -> Unit,
 ) {
     val density = LocalDensity.current
@@ -691,6 +693,9 @@
                 transitionInterceptionThreshold = transitionInterceptionThreshold,
                 builder = builder,
                 animationScope = animationScope,
+                elements = sharedElementMap,
+                ancestorContentKeys = ancestorContentKeys,
+                lookaheadScope = lookaheadScope,
             )
             .also { onLayoutImpl?.invoke(it) }
     }
@@ -706,6 +711,24 @@
                     " that was used when creating it, which is not supported"
             )
         }
+        if (layoutImpl.elements != sharedElementMap) {
+            error(
+                "This SceneTransitionLayout was bound to a different elements map that was used " +
+                    "when creating it, which is not supported"
+            )
+        }
+        if (layoutImpl.ancestorContentKeys != ancestorContentKeys) {
+            error(
+                "This SceneTransitionLayout was bound to a different ancestorContents that was " +
+                    "used when creating it, which is not supported"
+            )
+        }
+        if (lookaheadScope != null && layoutImpl.lookaheadScope != lookaheadScope) {
+            error(
+                "This SceneTransitionLayout was bound to a different lookaheadScope that was " +
+                    "used when creating it, which is not supported"
+            )
+        }
 
         layoutImpl.density = density
         layoutImpl.layoutDirection = layoutDirection
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index b916b0b..bdc1461 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -70,7 +70,39 @@
      * animations.
      */
     internal val animationScope: CoroutineScope,
+
+    /**
+     * The map of [Element]s.
+     *
+     * Important: [Element]s from this map should never be accessed during composition because the
+     * Elements are added when the associated Modifier.element() node is attached to the Modifier
+     * tree, i.e. after composition.
+     */
+    internal val elements: MutableMap<ElementKey, Element> = mutableMapOf(),
+
+    /**
+     * When this STL is a [NestedSceneTransitionLayout], this is a list of [ContentKey]s of where
+     * this STL is composed in within its ancestors.
+     *
+     * The root STL holds an emptyList. With each nesting level the parent is supposed to add
+     * exactly one scene to the list, therefore the size of this list is equal to the nesting depth
+     * of this STL.
+     *
+     * This is used to know in which content of the ancestors a sharedElement appears in.
+     */
+    internal val ancestorContentKeys: List<ContentKey> = emptyList(),
+    lookaheadScope: LookaheadScope? = null,
 ) {
+
+    /**
+     * The [LookaheadScope] of this layout, that can be used to compute offsets relative to the
+     * layout. For [NestedSceneTransitionLayout]s this scope is the scope of the root STL, such that
+     * offset computations can be shared among all children.
+     */
+    private var _lookaheadScope: LookaheadScope? = lookaheadScope
+    internal val lookaheadScope: LookaheadScope
+        get() = _lookaheadScope!!
+
     /**
      * The map of [Scene]s.
      *
@@ -89,15 +121,6 @@
         get() = _overlays ?: SnapshotStateMap<OverlayKey, Overlay>().also { _overlays = it }
 
     /**
-     * The map of [Element]s.
-     *
-     * Important: [Element]s from this map should never be accessed during composition because the
-     * Elements are added when the associated Modifier.element() node is attached to the Modifier
-     * tree, i.e. after composition.
-     */
-    internal val elements = mutableMapOf<ElementKey, Element>()
-
-    /**
      * The map of contents of movable elements.
      *
      * Note that given that this map is mutated directly during a composition, it has to be a
@@ -138,13 +161,6 @@
                     _userActionDistanceScope = it
                 }
 
-    /**
-     * The [LookaheadScope] of this layout, that can be used to compute offsets relative to the
-     * layout.
-     */
-    internal lateinit var lookaheadScope: LookaheadScope
-        private set
-
     internal var lastSize: IntSize = IntSize.Zero
 
     init {
@@ -347,7 +363,12 @@
                 .then(LayoutElement(layoutImpl = this))
         ) {
             LookaheadScope {
-                lookaheadScope = this
+                if (_lookaheadScope == null) {
+                    // We can't init this in a SideEffect as other NestedSTLs are already calling
+                    // this during composition. However, when composition is canceled
+                    // SceneTransitionLayoutImpl is discarded as well. So it's fine to do this here.
+                    _lookaheadScope = this
+                }
 
                 BackHandler()
                 Scenes()
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 4bccac1..86c5fd8 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
@@ -28,7 +28,6 @@
 import androidx.compose.ui.util.fastForEach
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
-import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Job
@@ -528,39 +527,6 @@
         transitionStates = listOf(TransitionState.Idle(scene, currentOverlays))
     }
 
-    /**
-     * Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap
-     * to the closest scene.
-     *
-     * Important: Snapping to the closest scene will instantly finish *all* ongoing transitions,
-     * only the progress of the last transition will be checked.
-     *
-     * @return true if snapped to the closest scene.
-     */
-    internal fun snapToIdleIfClose(threshold: Float): Boolean {
-        val transition = currentTransition ?: return false
-        val progress = transition.progress
-
-        fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
-
-        fun finishAllTransitions() {
-            // Force finish all transitions.
-            while (currentTransitions.isNotEmpty()) {
-                finishTransition(transitionStates[0] as TransitionState.Transition)
-            }
-        }
-
-        val shouldSnap =
-            (isProgressCloseTo(0f) && transition.isFromCurrentContent()) ||
-                (isProgressCloseTo(1f) && transition.isToCurrentContent())
-        return if (shouldSnap) {
-            finishAllTransitions()
-            true
-        } else {
-            false
-        }
-    }
-
     override fun showOverlay(
         overlay: OverlayKey,
         animationScope: CoroutineScope,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt
new file mode 100644
index 0000000..599a152a
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.transformation.SharedElementTransformation
+import com.android.compose.animation.scene.transformation.TransformationWithRange
+
+/**
+ * Whether this element should be rendered by the given [content]. This method returns true only for
+ * exactly one content at any given time.
+ */
+internal fun Element.shouldBeRenderedBy(content: ContentKey): Boolean {
+    // The current strategy is that always the content with the lowest nestingDepth has authority.
+    // This content is supposed to render the shared element because this is also the level at which
+    // the transition is running. If the [renderAuthority.size] is 1 it means that that this element
+    // is currently composed only in one nesting level, which means that the render authority
+    // is determined by "classic" shared element code.
+    return renderAuthority.size == 1 || renderAuthority.first() == content
+}
+
+/**
+ * Whether this element is currently composed in multiple [SceneTransitionLayout]s.
+ *
+ * Note: Shared elements across [NestedSceneTransitionLayout]s side-by-side are not supported.
+ */
+internal fun Element.isPresentInMultipleStls(): Boolean {
+    return renderAuthority.size > 1
+}
+
+internal fun shouldPlaceSharedElement(
+    layoutImpl: SceneTransitionLayoutImpl,
+    content: ContentKey,
+    elementKey: ElementKey,
+    transition: TransitionState.Transition,
+): Boolean {
+    val element = layoutImpl.elements.getValue(elementKey)
+    if (element.isPresentInMultipleStls()) {
+        // If the element is present in multiple STLs we require the highest STL to render it and
+        // we don't want contentPicker to potentially return false for the highest STL.
+        return element.shouldBeRenderedBy(content)
+    }
+
+    val overscrollContent = transition.currentOverscrollSpec?.content
+    if (overscrollContent != null) {
+        return when (transition) {
+            // If we are overscrolling between scenes, only place/compose the element in the
+            // overscrolling scene.
+            is TransitionState.Transition.ChangeScene -> content == overscrollContent
+
+            // If we are overscrolling an overlay, place/compose the element if [content] is the
+            // overscrolling content or if [content] is the current scene and the overscrolling
+            // overlay does not contain the element.
+            is TransitionState.Transition.ReplaceOverlay,
+            is TransitionState.Transition.ShowOrHideOverlay ->
+                content == overscrollContent ||
+                    (content == transition.currentScene &&
+                        overscrollContent !in element.stateByContent)
+        }
+    }
+
+    val scenePicker = elementKey.contentPicker
+    val pickedScene =
+        scenePicker.contentDuringTransition(
+            element = elementKey,
+            transition = transition,
+            fromContentZIndex = layoutImpl.content(transition.fromContent).zIndex,
+            toContentZIndex = layoutImpl.content(transition.toContent).zIndex,
+        )
+
+    return pickedScene == content
+}
+
+internal fun isSharedElementEnabled(
+    element: ElementKey,
+    transition: TransitionState.Transition,
+): Boolean {
+    return sharedElementTransformation(element, transition)?.transformation?.enabled ?: true
+}
+
+internal fun sharedElementTransformation(
+    element: ElementKey,
+    transition: TransitionState.Transition,
+): TransformationWithRange<SharedElementTransformation>? {
+    val transformationSpec = transition.transformationSpec
+    val sharedInFromContent =
+        transformationSpec.transformations(element, transition.fromContent).shared
+    val sharedInToContent = transformationSpec.transformations(element, transition.toContent).shared
+
+    // The sharedElement() transformation must either be null or be the same in both contents.
+    if (sharedInFromContent != sharedInToContent) {
+        error(
+            "Different sharedElement() transformations matched $element " +
+                "(from=$sharedInFromContent to=$sharedInToContent)"
+        )
+    }
+
+    return sharedInFromContent
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 5ab306a..6ef8b86 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -149,7 +149,6 @@
         delegate(
             MultiPointerDraggableNode(
                 orientation = draggableHandler.orientation,
-                startDragImmediately = ::startDragImmediately,
                 onDragStarted = draggableHandler::onDragStarted,
                 onFirstPointerDown = ::onFirstPointerDown,
                 swipeDetector = swipeDetector,
@@ -198,21 +197,6 @@
     ) = multiPointerDraggableNode.onPointerEvent(pointerEvent, pass, bounds)
 
     override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()
-
-    private fun startDragImmediately(pointersDown: PointersInfo.PointersDown): Boolean {
-        // Immediately start the drag if the user can't swipe in the other direction and the gesture
-        // handler can intercept it.
-        return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersDown)
-    }
-
-    private fun canOppositeSwipe(): Boolean {
-        val oppositeOrientation =
-            when (draggableHandler.orientation) {
-                Orientation.Vertical -> Orientation.Horizontal
-                Orientation.Horizontal -> Orientation.Vertical
-            }
-        return draggableHandler.contentForSwipes().shouldEnableSwipes(oppositeOrientation)
-    }
 }
 
 /** Find the [ScrollBehaviorOwner] for the current orientation. */
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 255a16c..8c4cd8c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -23,6 +23,7 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.approachLayout
@@ -41,7 +42,9 @@
 import com.android.compose.animation.scene.MovableElementContentScope
 import com.android.compose.animation.scene.MovableElementKey
 import com.android.compose.animation.scene.NestedScrollBehavior
+import com.android.compose.animation.scene.SceneTransitionLayoutForTesting
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.SceneTransitionLayoutScope
 import com.android.compose.animation.scene.SceneTransitionLayoutState
 import com.android.compose.animation.scene.SharedValueType
 import com.android.compose.animation.scene.UserAction
@@ -175,4 +178,24 @@
     override fun Modifier.noResizeDuringTransitions(): Modifier {
         return noResizeDuringTransitions(layoutState = layoutImpl.state)
     }
+
+    @Composable
+    override fun NestedSceneTransitionLayout(
+        state: SceneTransitionLayoutState,
+        modifier: Modifier,
+        builder: SceneTransitionLayoutScope.() -> Unit,
+    ) {
+        SceneTransitionLayoutForTesting(
+            state,
+            modifier,
+            onLayoutImpl = null,
+            builder = builder,
+            sharedElementMap = layoutImpl.elements,
+            ancestorContentKeys =
+                remember(layoutImpl.ancestorContentKeys, contentKey) {
+                    layoutImpl.ancestorContentKeys + contentKey
+                },
+            lookaheadScope = layoutImpl.lookaheadScope,
+        )
+    }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/IntIndexedMap.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/IntIndexedMap.kt
new file mode 100644
index 0000000..1b5341b
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/IntIndexedMap.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.ui.util
+
+/**
+ * This is a custom implementation that resembles a SortedMap<Int, T> but is based on a simple
+ * ArrayList to avoid the allocation overhead and boxing.
+ *
+ * It can only hold positive keys and 0 and it is only efficient for small keys (0 - ~100), but
+ * therefore provides fast operations for small keys.
+ */
+internal class IntIndexedMap<T> {
+    private val arrayList = ArrayList<T?>()
+    private var _size = 0
+    val size
+        get() = _size
+
+    /** Returns the value at [key] or null if the key is not present. */
+    operator fun get(key: Int): T? {
+        if (key < 0 || key >= arrayList.size) return null
+        return arrayList[key]
+    }
+
+    /**
+     * Sets the value at [key] to [value]. If [key] is larger than the current size of the map, this
+     * operation may take up to O(key) time and space. Therefore this data structure is only
+     * efficient for small [key] sizes.
+     */
+    operator fun set(key: Int, value: T?) {
+        if (key < 0)
+            throw UnsupportedOperationException("This map can only hold positive keys and 0.")
+        if (key < arrayList.size) {
+            if (arrayList[key] != null && value == null) _size--
+            if (arrayList[key] == null && value != null) _size++
+            arrayList[key] = value
+        } else {
+            if (value == null) return
+            while (key > arrayList.size) {
+                arrayList.add(null)
+            }
+            _size++
+            arrayList.add(value)
+        }
+    }
+
+    /** Remove value at [key] */
+    fun remove(key: Int) {
+        if (key >= arrayList.size) return
+        this[key] = null
+    }
+
+    /** Get the [value] with the smallest [key] of the map. */
+    fun first(): T {
+        for (i in 0 until arrayList.size) {
+            return arrayList[i] ?: continue
+        }
+        throw NoSuchElementException("The map is empty.")
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt
index f08a180..ca50e77 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/SpaceVectorConverter.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.Velocity
 
 interface SpaceVectorConverter {
@@ -25,9 +26,13 @@
 
     fun Velocity.toFloat(): Float
 
+    fun IntOffset.toInt(): Int
+
     fun Float.toOffset(): Offset
 
     fun Float.toVelocity(): Velocity
+
+    fun Int.toIntOffset(): IntOffset
 }
 
 fun SpaceVectorConverter(orientation: Orientation) =
@@ -36,24 +41,30 @@
         Orientation.Vertical -> VerticalConverter
     }
 
-private val HorizontalConverter =
-    object : SpaceVectorConverter {
-        override fun Offset.toFloat() = x
+private data object HorizontalConverter : SpaceVectorConverter {
+    override fun Offset.toFloat() = x
 
-        override fun Velocity.toFloat() = x
+    override fun Velocity.toFloat() = x
 
-        override fun Float.toOffset() = Offset(this, 0f)
+    override fun IntOffset.toInt() = x
 
-        override fun Float.toVelocity() = Velocity(this, 0f)
-    }
+    override fun Float.toOffset() = Offset(this, 0f)
 
-private val VerticalConverter =
-    object : SpaceVectorConverter {
-        override fun Offset.toFloat() = y
+    override fun Float.toVelocity() = Velocity(this, 0f)
 
-        override fun Velocity.toFloat() = y
+    override fun Int.toIntOffset() = IntOffset(this, 0)
+}
 
-        override fun Float.toOffset() = Offset(0f, this)
+private data object VerticalConverter : SpaceVectorConverter {
+    override fun Offset.toFloat() = y
 
-        override fun Float.toVelocity() = Velocity(0f, this)
-    }
+    override fun Velocity.toFloat() = y
+
+    override fun IntOffset.toInt() = y
+
+    override fun Float.toOffset() = Offset(0f, this)
+
+    override fun Float.toVelocity() = Velocity(0f, this)
+
+    override fun Int.toIntOffset() = IntOffset(0, this)
+}
diff --git a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
index 174ad30..2b76d7b 100644
--- a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
+++ b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
@@ -18,7 +18,8 @@
     package="com.android.compose.animation.scene.tests" >
 
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <application>
+    <application
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
         <uses-library android:name="android.test.runner" />
     </application>
 
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 a1077cf..2c8dc32 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
@@ -234,12 +234,6 @@
             )
         }
 
-        fun onDragStartedImmediately(
-            pointersInfo: PointersInfo.PointersDown = pointersDown()
-        ): DragController {
-            return onDragStarted(draggableHandler, pointersInfo, overSlop = 0f)
-        }
-
         fun onDragStarted(
             draggableHandler: DraggableHandler,
             pointersInfo: PointersInfo.PointersDown = pointersDown(),
@@ -424,37 +418,6 @@
     }
 
     @Test
-    fun onDragReversedDirection_changeToScene() = runGestureTest {
-        // Drag A -> B with progress 0.6
-        val dragController = onDragStarted(overSlop = -60f)
-        assertTransition(
-            currentScene = SceneA,
-            fromScene = SceneA,
-            toScene = SceneB,
-            progress = 0.6f,
-        )
-
-        // Reverse direction such that A -> C now with 0.4
-        dragController.onDragDelta(pixels = 100f)
-        assertTransition(
-            currentScene = SceneA,
-            fromScene = SceneA,
-            toScene = SceneC,
-            progress = 0.4f,
-        )
-
-        // After the drag stopped scene C should be committed
-        dragController.onDragStoppedAnimateNow(
-            velocity = velocityThreshold,
-            onAnimationStart = {
-                assertTransition(currentScene = SceneC, fromScene = SceneA, toScene = SceneC)
-            },
-            expectedConsumedVelocity = velocityThreshold,
-        )
-        assertIdle(currentScene = SceneC)
-    }
-
-    @Test
     fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
         onDragStarted(
             horizontalDraggableHandler,
@@ -504,31 +467,9 @@
     }
 
     @Test
-    fun onDragWithActionsInBothDirections_dragToOppositeDirectionReplacesAction() = runGestureTest {
-        // We are on SceneA. UP -> B, DOWN-> C.
-        val dragController = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
-        assertTransition(
-            currentScene = SceneA,
-            fromScene = SceneA,
-            toScene = SceneB,
-            progress = 0.2f,
-        )
-
-        // Reverse drag direction, it will replace the previous transition
-        dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f))
-        assertTransition(
-            currentScene = SceneA,
-            fromScene = SceneA,
-            toScene = SceneC,
-            progress = 0.3f,
-        )
-    }
-
-    @Test
     fun onDragWithActionsInBothDirections_dragToOppositeDirectionNotReplaceable() = runGestureTest {
         // We are on SceneA. UP -> B, DOWN-> C. The up swipe is not replaceable though.
-        mutableUserActionsA =
-            mapOf(Swipe.Up to UserActionResult(SceneB, isIrreversible = true), Swipe.Down to SceneC)
+        mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB), Swipe.Down to SceneC)
         val dragController =
             onDragStarted(
                 pointersInfo =
@@ -542,7 +483,7 @@
             progress = 0.2f,
         )
 
-        // Reverse drag direction, it cannot replace the previous transition
+        // Reverse drag direction, it does not replace the previous transition.
         dragController.onDragDelta(pixels = down(fractionOfScreen = 0.5f))
         assertTransition(
             currentScene = SceneA,
@@ -602,82 +543,6 @@
     }
 
     @Test
-    fun onAcceleratedScroll_scrollToThirdScene() = runGestureTest {
-        // Drag A -> B with progress 0.2
-        val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
-        assertTransition(
-            currentScene = SceneA,
-            fromScene = SceneA,
-            toScene = SceneB,
-            progress = 0.2f,
-        )
-
-        // Start animation A -> B with progress 0.2 -> 1.0
-        dragController1.onDragStoppedAnimateLater(velocity = -velocityThreshold)
-        assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
-
-        // While at A -> B do a 100% screen drag (progress 1.2). This should go past B and change
-        // the transition to B -> C with progress 0.2
-        val dragController2 = onDragStartedImmediately()
-        dragController2.onDragDelta(pixels = up(fractionOfScreen = 1f))
-        assertTransition(
-            currentScene = SceneB,
-            fromScene = SceneB,
-            toScene = SceneC,
-            progress = 0.2f,
-        )
-
-        // After the drag stopped scene C should be committed
-        dragController2.onDragStoppedAnimateNow(
-            velocity = -velocityThreshold,
-            onAnimationStart = {
-                assertTransition(currentScene = SceneC, fromScene = SceneB, toScene = SceneC)
-            },
-            expectedConsumedVelocity = -velocityThreshold,
-        )
-        assertIdle(currentScene = SceneC)
-    }
-
-    @Test
-    fun onAcceleratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
-        val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
-        dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.2f))
-        dragController1.onDragStoppedAnimateLater(velocity = -velocityThreshold)
-        assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
-
-        mutableUserActionsA = emptyMap()
-        mutableUserActionsB = emptyMap()
-
-        // start acceleratedScroll and scroll over to B -> null
-        val dragController2 = onDragStartedImmediately()
-        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
-        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
-
-        // here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
-        // still be called. Make sure that they don't crash or change the scene
-        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
-        dragController2.onDragStoppedAnimateNow(
-            velocity = 0f,
-            onAnimationStart = {
-                assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
-            },
-            expectedConsumedVelocity = 0f,
-        )
-
-        advanceUntilIdle()
-        assertIdle(SceneB)
-
-        // These events can still come in after the animation has settled
-        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = 0f)
-        dragController2.onDragStoppedAnimateNow(
-            velocity = 0f,
-            onAnimationStart = { assertIdle(SceneB) },
-            expectedConsumedVelocity = 0f,
-        )
-        assertIdle(SceneB)
-    }
-
-    @Test
     fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
         val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
@@ -711,9 +576,8 @@
         dragController1.onDragStoppedAnimateLater(velocity = down(fractionOfScreen = 0.1f))
 
         // now target changed to C for new drag that started before previous drag settled to Idle
-        val dragController2 = onDragStartedImmediately()
-        dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
-        assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
+        onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
+        assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.1f)
     }
 
     @Test
@@ -728,7 +592,7 @@
         assertThat(isUserInputOngoing).isFalse()
 
         // Start a new gesture while the offset is animating
-        onDragStartedImmediately()
+        onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertThat(isUserInputOngoing).isTrue()
     }
 
@@ -812,36 +676,6 @@
     }
 
     @Test
-    fun scrollAndFling_scrollLessThanInterceptable_goToIdleOnCurrentScene() = runGestureTest {
-        val firstScroll = (transitionInterceptionThreshold - 0.0001f) * SCREEN_SIZE
-        val secondScroll = 1f
-
-        preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
-
-        assertIdle(SceneA)
-    }
-
-    @Test
-    fun scrollAndFling_scrollMinInterceptable_interceptPreScrollEvents() = runGestureTest {
-        val firstScroll = (transitionInterceptionThreshold + 0.0001f) * SCREEN_SIZE
-        val secondScroll = 1f
-
-        preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
-
-        assertTransition(progress = (firstScroll + secondScroll) / SCREEN_SIZE)
-    }
-
-    @Test
-    fun scrollAndFling_scrollMaxInterceptable_interceptPreScrollEvents() = runGestureTest {
-        val firstScroll = -(1f - transitionInterceptionThreshold - 0.0001f) * SCREEN_SIZE
-        val secondScroll = -1f
-
-        preScrollAfterSceneTransition(firstScroll = firstScroll, secondScroll = secondScroll)
-
-        assertTransition(progress = -(firstScroll + secondScroll) / SCREEN_SIZE)
-    }
-
-    @Test
     fun scrollAndFling_scrollMoreThanInterceptable_goToIdleOnNextScene() = runGestureTest {
         val firstScroll = -(1f - transitionInterceptionThreshold + 0.0001f) * SCREEN_SIZE
         val secondScroll = -0.01f
@@ -1025,7 +859,7 @@
 
         // now we can intercept the scroll events
         nestedScroll.scroll(available = -offsetY10)
-        assertThat(progress).isEqualTo(0.2f)
+        assertThat(progress).isEqualTo(0.1f)
 
         // this should be ignored, we are scrolling now!
         dragController.onDragStoppedAnimateNow(
@@ -1036,10 +870,10 @@
         assertTransition(currentScene = SceneA)
 
         nestedScroll.scroll(available = -offsetY10)
-        assertThat(progress).isEqualTo(0.3f)
+        assertThat(progress).isEqualTo(0.2f)
 
         nestedScroll.scroll(available = -offsetY10)
-        assertThat(progress).isEqualTo(0.4f)
+        assertThat(progress).isEqualTo(0.3f)
 
         nestedScroll.preFling(available = Velocity(0f, -velocityThreshold))
         assertTransition(currentScene = SceneB)
@@ -1050,57 +884,6 @@
     }
 
     @Test
-    fun interceptTransition() = runGestureTest {
-        // Start at scene C.
-        navigateToSceneC()
-
-        // Swipe up from the middle to transition to scene B.
-        val middle = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
-        onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
-        assertTransition(
-            currentScene = SceneC,
-            fromScene = SceneC,
-            toScene = SceneB,
-            progress = 0.1f,
-            isUserInputOngoing = true,
-        )
-
-        val firstTransition = transitionState
-
-        // During the current gesture, start a new gesture, still in the middle of the screen. We
-        // should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
-        // should be 0f.
-        assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
-        onDragStartedImmediately(pointersInfo = middle)
-
-        // We should have intercepted the transition, so the transition should be the same object.
-        assertTransition(
-            currentScene = SceneC,
-            fromScene = SceneC,
-            toScene = SceneB,
-            progress = 0.1f,
-            isUserInputOngoing = true,
-        )
-        // We should have a new transition
-        assertThat(transitionState).isNotSameInstanceAs(firstTransition)
-
-        // Start a new gesture from the bottom of the screen. Because swiping up from the bottom of
-        // C leads to scene A (and not B), the previous transitions is *not* intercepted and we
-        // instead animate from C to A.
-        val bottom = pointersDown(startedPosition = Offset(SCREEN_SIZE / 2, SCREEN_SIZE))
-        assertThat(draggableHandler.shouldImmediatelyIntercept(bottom)).isFalse()
-        onDragStarted(pointersInfo = bottom, overSlop = up(0.1f))
-
-        assertTransition(
-            currentScene = SceneC,
-            fromScene = SceneC,
-            toScene = SceneA,
-            isUserInputOngoing = true,
-        )
-        assertThat(transitionState).isNotSameInstanceAs(firstTransition)
-    }
-
-    @Test
     fun freezeAndAnimateToCurrentState() = runGestureTest {
         // Start at scene C.
         navigateToSceneC()
@@ -1110,9 +893,6 @@
         onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
         assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true)
 
-        // The current transition can be intercepted.
-        assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
-
         // Freeze the transition.
         val transition = transitionState as Transition
         transition.freezeAndAnimateToCurrentState()
@@ -1123,19 +903,6 @@
     }
 
     @Test
-    fun interruptedTransitionCanNotBeImmediatelyIntercepted() = runGestureTest {
-        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isFalse()
-        onDragStarted(overSlop = up(0.1f))
-        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isTrue()
-
-        layoutState.startTransitionImmediately(
-            animationScope = testScope.backgroundScope,
-            transition(SceneA, SceneB),
-        )
-        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isFalse()
-    }
-
-    @Test
     fun blockTransition() = runGestureTest {
         assertIdle(SceneA)
 
@@ -1154,30 +921,6 @@
     }
 
     @Test
-    fun blockInterceptedTransition() = runGestureTest {
-        assertIdle(SceneA)
-
-        // Swipe up to B.
-        val dragController1 = onDragStarted(overSlop = up(0.1f))
-        assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB)
-        dragController1.onDragStoppedAnimateLater(velocity = -velocityThreshold)
-        assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
-
-        // Intercept the transition and swipe down back to scene A.
-        assertThat(draggableHandler.shouldImmediatelyIntercept(pointersDown = null)).isTrue()
-        val dragController2 = onDragStartedImmediately()
-
-        // Block the transition when the user release their finger.
-        canChangeScene = { false }
-        dragController2.onDragStoppedAnimateNow(
-            velocity = velocityThreshold,
-            onAnimationStart = { assertTransition(fromScene = SceneA, toScene = SceneB) },
-            expectedConsumedVelocity = velocityThreshold,
-        )
-        assertIdle(SceneB)
-    }
-
-    @Test
     fun scrollFromIdleWithNoTargetScene_shouldUseOverscrollSpecIfAvailable() = runGestureTest {
         layoutState.transitions = transitions {
             overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
@@ -1531,25 +1274,6 @@
     }
 
     @Test
-    fun interceptingTransitionKeepsDistance() = runGestureTest {
-        var swipeDistance = 75f
-        layoutState.transitions = transitions {
-            from(SceneA, to = SceneB) { distance = UserActionDistance { _, _, _ -> swipeDistance } }
-        }
-
-        // Start transition.
-        val controller = onDragStarted(overSlop = -50f)
-        assertTransition(fromScene = SceneA, toScene = SceneB, progress = 50f / 75f)
-
-        // Intercept the transition and change the swipe distance. The original distance and
-        // progress should be the same.
-        swipeDistance = 50f
-        controller.onDragStoppedAnimateLater(0f)
-        onDragStartedImmediately()
-        assertTransition(fromScene = SceneA, toScene = SceneB, progress = 50f / 75f)
-    }
-
-    @Test
     fun requireFullDistanceSwipe() = runGestureTest {
         mutableUserActionsA +=
             Swipe.Up to UserActionResult(SceneB, requiresFullDistanceSwipe = true)
@@ -1579,19 +1303,6 @@
     }
 
     @Test
-    fun interceptingTransitionReplacesCurrentTransition() = runGestureTest {
-        val controller = onDragStarted(overSlop = up(fractionOfScreen = 0.5f))
-        val transition = assertThat(layoutState.transitionState).isSceneTransition()
-        controller.onDragStoppedAnimateLater(velocity = 0f)
-
-        // Intercept the transition.
-        onDragStartedImmediately()
-        val newTransition = assertThat(layoutState.transitionState).isSceneTransition()
-        assertThat(newTransition).isNotSameInstanceAs(transition)
-        assertThat(newTransition.replacedTransition).isSameInstanceAs(transition)
-    }
-
-    @Test
     fun showOverlay() = runGestureTest {
         mutableUserActionsA = mapOf(Swipe.Down to UserActionResult.ShowOverlay(OverlayA))
 
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index cb3e433..4153350 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -101,7 +101,6 @@
                     .thenIf(enabled) {
                         Modifier.multiPointerDraggable(
                             orientation = Orientation.Vertical,
-                            startDragImmediately = { false },
                             onDragStarted = { _, _ ->
                                 started = true
                                 SimpleDragController(
@@ -169,8 +168,6 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
-                        // We want to start a drag gesture immediately
-                        startDragImmediately = { true },
                         onDragStarted = { _, _ ->
                             started = true
                             SimpleDragController(
@@ -242,7 +239,6 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
-                        startDragImmediately = { false },
                         onDragStarted = { _, _ ->
                             started = true
                             SimpleDragController(
@@ -361,7 +357,6 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
-                        startDragImmediately = { false },
                         onDragStarted = { _, _ ->
                             started = true
                             SimpleDragController(
@@ -466,7 +461,6 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
-                        startDragImmediately = { false },
                         onDragStarted = { _, _ ->
                             verticalStarted = true
                             SimpleDragController(
@@ -478,7 +472,6 @@
                     )
                     .multiPointerDraggable(
                         orientation = Orientation.Horizontal,
-                        startDragImmediately = { false },
                         onDragStarted = { _, _ ->
                             horizontalStarted = true
                             SimpleDragController(
@@ -570,7 +563,6 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
-                        startDragImmediately = { false },
                         swipeDetector =
                             object : SwipeDetector {
                                 override fun detectSwipe(change: PointerInputChange): Boolean {
@@ -636,7 +628,6 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
-                        startDragImmediately = { false },
                         swipeDetector =
                             object : SwipeDetector {
                                 override fun detectSwipe(change: PointerInputChange): Boolean {
@@ -738,7 +729,6 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
-                        startDragImmediately = { false },
                         onDragStarted = { _, _ ->
                             SimpleDragController(
                                 onDrag = { consumedOnDrag = it },
@@ -809,7 +799,6 @@
                     .nestedScrollDispatcher()
                     .multiPointerDraggable(
                         orientation = Orientation.Vertical,
-                        startDragImmediately = { false },
                         onDragStarted = { _, _ ->
                             SimpleDragController(
                                 onDrag = { /* do nothing */ },
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 79ca891..3b7d661 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
@@ -18,12 +18,9 @@
 
 import android.util.Log
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.animation.scene.TestOverlays.OverlayA
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
@@ -169,130 +166,6 @@
         assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(2)
     }
 
-    @Test
-    fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
-        state.startTransitionImmediately(
-            animationScope = backgroundScope,
-            transition(from = SceneA, to = SceneB, current = { SceneA }, progress = { 0.2f }),
-        )
-        assertThat(state.isTransitioning()).isTrue()
-
-        // Ignore the request if the progress is not close to 0 or 1, using the threshold.
-        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
-        assertThat(state.isTransitioning()).isTrue()
-
-        // Go to the initial scene if it is close to 0.
-        assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
-        assertThat(state.isTransitioning()).isFalse()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
-    }
-
-    @Test
-    fun snapToIdleIfClose_snapToStart_overlays() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
-        state.startTransitionImmediately(
-            animationScope = backgroundScope,
-            transition(SceneA, OverlayA, isEffectivelyShown = { false }, progress = { 0.2f }),
-        )
-        assertThat(state.isTransitioning()).isTrue()
-
-        // Ignore the request if the progress is not close to 0 or 1, using the threshold.
-        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
-        assertThat(state.isTransitioning()).isTrue()
-
-        // Go to the initial scene if it is close to 0.
-        assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
-        assertThat(state.isTransitioning()).isFalse()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
-    }
-
-    @Test
-    fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
-        state.startTransitionImmediately(
-            animationScope = backgroundScope,
-            transition(from = SceneA, to = SceneB, progress = { 0.8f }),
-        )
-        assertThat(state.isTransitioning()).isTrue()
-
-        // Ignore the request if the progress is not close to 0 or 1, using the threshold.
-        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
-        assertThat(state.isTransitioning()).isTrue()
-
-        // Go to the final scene if it is close to 1.
-        assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
-        assertThat(state.isTransitioning()).isFalse()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
-    }
-
-    @Test
-    fun snapToIdleIfClose_snapToEnd_overlays() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
-        state.startTransitionImmediately(
-            animationScope = backgroundScope,
-            transition(SceneA, OverlayA, isEffectivelyShown = { true }, progress = { 0.8f }),
-        )
-        assertThat(state.isTransitioning()).isTrue()
-
-        // Ignore the request if the progress is not close to 0 or 1, using the threshold.
-        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
-        assertThat(state.isTransitioning()).isTrue()
-
-        // Go to the final scene if it is close to 1.
-        assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
-        assertThat(state.isTransitioning()).isFalse()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA, setOf(OverlayA)))
-    }
-
-    @Test
-    fun snapToIdleIfClose_multipleTransitions() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
-
-        val aToB = transition(from = SceneA, to = SceneB, progress = { 0.5f })
-        state.startTransitionImmediately(animationScope = backgroundScope, aToB)
-        assertThat(state.currentTransitions).containsExactly(aToB).inOrder()
-
-        val bToC = transition(from = SceneB, to = SceneC, progress = { 0.8f })
-        state.startTransitionImmediately(animationScope = backgroundScope, bToC)
-        assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder()
-
-        // Ignore the request if the progress is not close to 0 or 1, using the threshold.
-        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
-        assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder()
-
-        // Go to the final scene if it is close to 1.
-        assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
-        assertThat(state.currentTransitions).isEmpty()
-    }
-
-    @Test
-    fun snapToIdleIfClose_closeButNotCurrentScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
-        var progress by mutableStateOf(0f)
-        var currentScene by mutableStateOf(SceneB)
-        state.startTransitionImmediately(
-            animationScope = backgroundScope,
-            transition(
-                from = SceneA,
-                to = SceneB,
-                current = { currentScene },
-                progress = { progress },
-            ),
-        )
-        assertThat(state.isTransitioning()).isTrue()
-
-        // Ignore the request if we are close to a scene that is not the current scene
-        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
-        assertThat(state.isTransitioning()).isTrue()
-
-        progress = 1f
-        currentScene = SceneA
-        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
-        assertThat(state.isTransitioning()).isTrue()
-    }
-
     private fun MonotonicClockTestScope.startOverscrollableTransistionFromAtoB(
         progress: () -> Float,
         sceneTransitions: SceneTransitions,
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 b3a3261..fe7b5b6 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
@@ -664,17 +664,11 @@
             }
         }
 
-        // Swipe down for the default transition from A to B.
-        rule.onRoot().performTouchInput {
-            down(middle)
-            moveBy(Offset(0f, touchSlop), delayMillis = 1_000)
-        }
-
-        assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
-        assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).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) }
+        rule.onRoot().performTouchInput {
+            down(center)
+            moveBy(Offset(0f, -touchSlop - 1.dp.toPx()), delayMillis = 1_000)
+        }
         assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
         assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(2)
     }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
new file mode 100644
index 0000000..c6ef8cf
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
@@ -0,0 +1,283 @@
+/*
+ * 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.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.AutoTransitionTestAssertionScope
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.Default4FrameLinearTransition
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.TestScenes
+import com.android.compose.animation.scene.inScene
+import com.android.compose.animation.scene.testTransition
+import com.android.compose.animation.scene.transitions
+import com.android.compose.test.assertSizeIsEqualTo
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class NestedSharedElementTest {
+    @get:Rule val rule = createComposeRule()
+
+    private object Scenes {
+        val NestedSceneA = SceneKey("NestedSceneA")
+        val NestedSceneB = SceneKey("NestedSceneB")
+        val NestedNestedSceneA = SceneKey("NestedNestedSceneA")
+        val NestedNestedSceneB = SceneKey("NestedNestedSceneB")
+    }
+
+    private val elementVariant1 = SharedElement(0.dp, 0.dp, 100.dp, 100.dp, Color.Red)
+    private val elementVariant2 = SharedElement(40.dp, 80.dp, 60.dp, 20.dp, Color.Blue)
+    private val elementVariant3 = SharedElement(80.dp, 40.dp, 140.dp, 180.dp, Color.Yellow)
+    private val elementVariant4 = SharedElement(120.dp, 240.dp, 20.dp, 140.dp, Color.Green)
+
+    private class SharedElement(
+        val x: Dp,
+        val y: Dp,
+        val width: Dp,
+        val height: Dp,
+        val color: Color = Color.Black,
+        val alpha: Float = 0.8f,
+    )
+
+    @Composable
+    private fun ContentScope.SharedElement(element: SharedElement) {
+        Box(Modifier.fillMaxSize()) {
+            Box(
+                Modifier.offset(element.x, element.y)
+                    .element(TestElements.Foo)
+                    .size(element.width, element.height)
+                    .background(element.color)
+                    .alpha(element.alpha)
+            )
+        }
+    }
+
+    private val contentWithSharedElement: @Composable ContentScope.() -> Unit = {
+        SharedElement(elementVariant1)
+    }
+
+    private val nestedState: MutableSceneTransitionLayoutState =
+        rule.runOnUiThread {
+            MutableSceneTransitionLayoutState(
+                Scenes.NestedSceneA,
+                transitions {
+                    from(
+                        from = Scenes.NestedSceneA,
+                        to = Scenes.NestedSceneB,
+                        builder = Default4FrameLinearTransition,
+                    )
+                },
+            )
+        }
+
+    private val nestedNestedState: MutableSceneTransitionLayoutState =
+        rule.runOnUiThread {
+            MutableSceneTransitionLayoutState(
+                Scenes.NestedNestedSceneA,
+                transitions {
+                    from(
+                        from = Scenes.NestedNestedSceneA,
+                        to = Scenes.NestedNestedSceneB,
+                        builder = Default4FrameLinearTransition,
+                    )
+                },
+            )
+        }
+
+    private val nestedStlWithSharedElement: @Composable ContentScope.() -> Unit = {
+        NestedSceneTransitionLayout(nestedState, modifier = Modifier) {
+            scene(Scenes.NestedSceneA) { SharedElement(elementVariant2) }
+            scene(Scenes.NestedSceneB) { SharedElement(elementVariant3) }
+        }
+    }
+
+    private val nestedNestedStlWithSharedElement: @Composable ContentScope.() -> Unit = {
+        NestedSceneTransitionLayout(nestedState, modifier = Modifier) {
+            scene(Scenes.NestedSceneA) {
+                NestedSceneTransitionLayout(state = nestedNestedState, modifier = Modifier) {
+                    scene(Scenes.NestedNestedSceneA) { SharedElement(elementVariant4) }
+                    scene(Scenes.NestedNestedSceneB) { SharedElement(elementVariant3) }
+                }
+            }
+            scene(Scenes.NestedSceneB) { SharedElement(elementVariant2) }
+        }
+    }
+
+    @Test
+    fun nestedSharedElementTransition_fromNestedSTLtoParentSTL() {
+        rule.testTransition(
+            fromSceneContent = nestedStlWithSharedElement,
+            toSceneContent = contentWithSharedElement,
+        ) {
+            before { onElement(TestElements.Foo).assertElementVariant(elementVariant2) }
+            atAllFrames(4) {
+                onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
+
+                onElement(TestElements.Foo, TestScenes.SceneB)
+                    .assertBetweenElementVariants(elementVariant2, elementVariant1, this)
+            }
+            after { onElement(TestElements.Foo).assertElementVariant(elementVariant1) }
+        }
+    }
+
+    @Test
+    fun nestedSharedElementTransition_fromParentSTLtoNestedSTL() {
+        rule.testTransition(
+            fromSceneContent = contentWithSharedElement,
+            toSceneContent = nestedStlWithSharedElement,
+        ) {
+            before { onElement(TestElements.Foo).assertElementVariant(elementVariant1) }
+            atAllFrames(4) {
+                onElement(TestElements.Foo, TestScenes.SceneB).assertIsNotDisplayed()
+
+                onElement(TestElements.Foo, TestScenes.SceneA)
+                    .assertBetweenElementVariants(elementVariant1, elementVariant2, this)
+            }
+            after { onElement(TestElements.Foo).assertElementVariant(elementVariant2) }
+        }
+    }
+
+    @Test
+    fun nestedSharedElementTransition_fromParentSTLtoNestedNestedSTL() {
+        rule.testTransition(
+            fromSceneContent = contentWithSharedElement,
+            toSceneContent = nestedNestedStlWithSharedElement,
+        ) {
+            before { onElement(TestElements.Foo).assertElementVariant(elementVariant1) }
+            atAllFrames(4) {
+                onElement(TestElements.Foo, TestScenes.SceneB).assertIsNotDisplayed()
+
+                onElement(TestElements.Foo, TestScenes.SceneA)
+                    .assertBetweenElementVariants(elementVariant1, elementVariant4, this)
+            }
+            after { onElement(TestElements.Foo).assertElementVariant(elementVariant4) }
+        }
+    }
+
+    @Test
+    fun nestedSharedElementTransition_fromNestedNestedSTLtoNestedSTL() {
+        rule.testTransition(
+            fromSceneContent = nestedNestedStlWithSharedElement,
+            toSceneContent = { Box(modifier = Modifier.fillMaxSize()) },
+            changeState = { nestedState.setTargetScene(Scenes.NestedSceneB, this) },
+        ) {
+            before { onElement(TestElements.Foo).assertElementVariant(elementVariant4) }
+            atAllFrames(4) {
+                onElement(TestElements.Foo, Scenes.NestedSceneA).assertIsNotDisplayed()
+                onElement(TestElements.Foo, Scenes.NestedNestedSceneA).assertIsNotDisplayed()
+
+                onElement(TestElements.Foo, Scenes.NestedSceneB)
+                    .assertBetweenElementVariants(elementVariant4, elementVariant2, this)
+            }
+            after { onElement(TestElements.Foo).assertElementVariant(elementVariant2) }
+        }
+    }
+
+    @Test
+    fun nestedSharedElement_sharedElementTransitionIsDisabled() {
+        rule.testTransition(
+            fromSceneContent = contentWithSharedElement,
+            toSceneContent = nestedStlWithSharedElement,
+            transition = {
+                spec = tween(16 * 4, easing = LinearEasing)
+
+                // Disable the shared element animation.
+                sharedElement(TestElements.Foo, enabled = false)
+
+                // In SceneA, Foo leaves to the left edge.
+                translate(TestElements.Foo.inScene(TestScenes.SceneA), Edge.Left, false)
+
+                // We can't reference the element inside the NestedSTL as of today
+            },
+        ) {
+            before { onElement(TestElements.Foo).assertElementVariant(elementVariant1) }
+            atAllFrames(4) {
+                onElement(TestElements.Foo, scene = TestScenes.SceneA)
+                    .assertPositionInRootIsEqualTo(
+                        interpolate(elementVariant1.x, 0.dp),
+                        elementVariant1.y,
+                    )
+                    .assertSizeIsEqualTo(elementVariant1.width, elementVariant1.height)
+            }
+            after { onElement(TestElements.Foo).assertElementVariant(elementVariant2) }
+        }
+    }
+
+    @Test
+    fun nestedSharedElementTransition_transitionInsideNestedStl() {
+        rule.testTransition(
+            layoutModifier = Modifier.fillMaxSize(),
+            fromSceneContent = nestedStlWithSharedElement,
+            toSceneContent = contentWithSharedElement,
+            changeState = { nestedState.setTargetScene(Scenes.NestedSceneB, animationScope = this) },
+        ) {
+            before { onElement(TestElements.Foo).assertElementVariant(elementVariant2) }
+            atAllFrames(4) {
+                onElement(TestElements.Foo, Scenes.NestedSceneA).assertIsNotDisplayed()
+
+                onElement(TestElements.Foo, scene = Scenes.NestedSceneB)
+                    .assertBetweenElementVariants(elementVariant2, elementVariant3, this)
+            }
+            after {
+                onElement(TestElements.Foo, Scenes.NestedSceneA).assertIsNotDisplayed()
+                onElement(TestElements.Foo).assertElementVariant(elementVariant3)
+            }
+        }
+    }
+
+    private fun SemanticsNodeInteraction.assertElementVariant(variant: SharedElement) {
+        assertPositionInRootIsEqualTo(variant.x, variant.y)
+        assertSizeIsEqualTo(variant.width, variant.height)
+    }
+
+    private fun SemanticsNodeInteraction.assertBetweenElementVariants(
+        from: SharedElement,
+        to: SharedElement,
+        assertScope: AutoTransitionTestAssertionScope,
+    ) {
+        assertPositionInRootIsEqualTo(
+            assertScope.interpolate(from.x, to.x),
+            assertScope.interpolate(from.y, to.y),
+        )
+        assertSizeIsEqualTo(
+            assertScope.interpolate(from.width, to.width),
+            assertScope.interpolate(from.height, to.height),
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
index 2e3a934..47c10f5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
@@ -62,35 +62,14 @@
                 onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp)
                 onElement(TestElements.Foo).assertSizeIsEqualTo(20.dp, 80.dp)
             }
-            at(0) {
-                // Shared elements are by default placed and drawn only in the scene with highest
-                // zIndex.
+            atAllFrames(4) {
                 onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
-
                 onElement(TestElements.Foo, TestScenes.SceneB)
-                    .assertPositionInRootIsEqualTo(10.dp, 50.dp)
-                    .assertSizeIsEqualTo(20.dp, 80.dp)
-            }
-            at(16) {
-                onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
-
-                onElement(TestElements.Foo, TestScenes.SceneB)
-                    .assertPositionInRootIsEqualTo(20.dp, 55.dp)
-                    .assertSizeIsEqualTo(17.5.dp, 70.dp)
-            }
-            at(32) {
-                onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
-
-                onElement(TestElements.Foo, TestScenes.SceneB)
-                    .assertPositionInRootIsEqualTo(30.dp, 60.dp)
-                    .assertSizeIsEqualTo(15.dp, 60.dp)
-            }
-            at(48) {
-                onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
-
-                onElement(TestElements.Foo, TestScenes.SceneB)
-                    .assertPositionInRootIsEqualTo(40.dp, 65.dp)
-                    .assertSizeIsEqualTo(12.5.dp, 50.dp)
+                    .assertPositionInRootIsEqualTo(
+                        interpolate(10.dp, 50.dp),
+                        interpolate(50.dp, 70.dp),
+                    )
+                    .assertSizeIsEqualTo(interpolate(20.dp, 10.dp), interpolate(80.dp, 40.dp))
             }
             after {
                 onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 70.dp)
@@ -132,29 +111,11 @@
             },
         ) {
             before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
-            at(0) {
+            atAllFrames(4) {
                 onElement(TestElements.Foo, scene = TestScenes.SceneA)
-                    .assertPositionInRootIsEqualTo(10.dp, 50.dp)
+                    .assertPositionInRootIsEqualTo(interpolate(10.dp, 0.dp), 50.dp)
                 onElement(TestElements.Foo, scene = TestScenes.SceneB)
-                    .assertPositionInRootIsEqualTo(50.dp, 100.dp)
-            }
-            at(16) {
-                onElement(TestElements.Foo, scene = TestScenes.SceneA)
-                    .assertPositionInRootIsEqualTo(7.5.dp, 50.dp)
-                onElement(TestElements.Foo, scene = TestScenes.SceneB)
-                    .assertPositionInRootIsEqualTo(50.dp, 90.dp)
-            }
-            at(32) {
-                onElement(TestElements.Foo, scene = TestScenes.SceneA)
-                    .assertPositionInRootIsEqualTo(5.dp, 50.dp)
-                onElement(TestElements.Foo, scene = TestScenes.SceneB)
-                    .assertPositionInRootIsEqualTo(50.dp, 80.dp)
-            }
-            at(48) {
-                onElement(TestElements.Foo, scene = TestScenes.SceneA)
-                    .assertPositionInRootIsEqualTo(2.5.dp, 50.dp)
-                onElement(TestElements.Foo, scene = TestScenes.SceneB)
-                    .assertPositionInRootIsEqualTo(50.dp, 70.dp)
+                    .assertPositionInRootIsEqualTo(50.dp, interpolate(100.dp, 60.dp))
             }
             after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 60.dp) }
         }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/IntIndexMapTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/IntIndexMapTest.kt
new file mode 100644
index 0000000..d7a9b90
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/IntIndexMapTest.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.ui.util
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class IntIndexMapTest {
+
+    @Test
+    fun testSetGetFirstAndSize() {
+        val map = IntIndexedMap<String>()
+
+        // Write first element at 10
+        map[10] = "1"
+        assertThat(map[10]).isEqualTo("1")
+        assertThat(map.size).isEqualTo(1)
+        assertThat(map.first()).isEqualTo("1")
+
+        // Write same element to same index
+        map[10] = "1"
+        assertThat(map[10]).isEqualTo("1")
+        assertThat(map.size).isEqualTo(1)
+
+        // Writing into larger index
+        map[12] = "2"
+        assertThat(map[12]).isEqualTo("2")
+        assertThat(map.size).isEqualTo(2)
+        assertThat(map.first()).isEqualTo("1")
+
+        // Overwriting existing index
+        map[10] = "3"
+        assertThat(map[10]).isEqualTo("3")
+        assertThat(map.size).isEqualTo(2)
+        assertThat(map.first()).isEqualTo("3")
+
+        // Writing into smaller index
+        map[0] = "4"
+        assertThat(map[0]).isEqualTo("4")
+        assert(map.size == 3)
+        assertThat(map.first()).isEqualTo("4")
+
+        // Writing null into non-null index
+        map[0] = null
+        assertThat(map[0]).isEqualTo(null)
+        assertThat(map.size).isEqualTo(2)
+        assertThat(map.first()).isEqualTo("3")
+
+        // Writing null into smaller null index
+        map[1] = null
+        assertThat(map[1]).isEqualTo(null)
+        assertThat(map.size).isEqualTo(2)
+
+        // Writing null into larger null index
+        map[15] = null
+        assertThat(map[15]).isEqualTo(null)
+        assertThat(map.size).isEqualTo(2)
+
+        // Remove existing element
+        map.remove(12)
+        assertThat(map[12]).isEqualTo(null)
+        assertThat(map.size).isEqualTo(1)
+
+        // Remove non-existing element
+        map.remove(17)
+        assertThat(map[17]).isEqualTo(null)
+        assertThat(map.size).isEqualTo(1)
+
+        // Remove all elements
+        assertThat(map.first()).isEqualTo("3")
+        map.remove(10)
+        map.remove(10)
+        map.remove(0)
+        assertThat(map.size).isEqualTo(0)
+        assertThat(map[10]).isEqualTo(null)
+        assertThat(map.size).isEqualTo(0)
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 0d2fcfc..124b61e 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -16,6 +16,8 @@
 
 package com.android.compose.animation.scene
 
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
@@ -27,6 +29,9 @@
 import androidx.compose.ui.test.SemanticsNodeInteraction
 import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.lerp
+import androidx.compose.ui.util.lerp
 import kotlinx.coroutines.CoroutineScope
 import platform.test.motion.MotionTestRule
 import platform.test.motion.RecordedMotion
@@ -62,6 +67,16 @@
     fun at(timestamp: Long, builder: TransitionTestAssertionScope.() -> Unit)
 
     /**
+     * Run the same assertion for all frames of a transition.
+     *
+     * @param totalFrames needs to be the exact number of frames of the transition that is run,
+     *   otherwise the passed progress will be incorrect. That is the duration in ms divided by 16.
+     * @param builder is passed a progress Float which can be used to calculate values for the
+     *   specific frame. Or use [AutoTransitionTestAssertionScope.interpolate].
+     */
+    fun atAllFrames(totalFrames: Int, builder: AutoTransitionTestAssertionScope.(Float) -> Unit)
+
+    /**
      * Assert on the state of the layout after the transition finished.
      *
      * This should be called maximum once, after [before] or [at] is called.
@@ -82,6 +97,16 @@
     fun onElement(element: ElementKey, scene: SceneKey? = null): SemanticsNodeInteraction
 }
 
+interface AutoTransitionTestAssertionScope : TransitionTestAssertionScope {
+
+    /** Linear interpolate [from] and [to] with the current progress of the transition. */
+    fun <T> interpolate(from: T, to: T): T
+}
+
+val Default4FrameLinearTransition: TransitionBuilder.() -> Unit = {
+    spec = tween(16 * 4, easing = LinearEasing)
+}
+
 /**
  * Test the transition between [fromSceneContent] and [toSceneContent] at different points in time.
  *
@@ -90,10 +115,13 @@
 fun ComposeContentTestRule.testTransition(
     fromSceneContent: @Composable ContentScope.() -> Unit,
     toSceneContent: @Composable ContentScope.() -> Unit,
-    transition: TransitionBuilder.() -> Unit,
+    transition: TransitionBuilder.() -> Unit = Default4FrameLinearTransition,
     layoutModifier: Modifier = Modifier,
     fromScene: SceneKey = TestScenes.SceneA,
     toScene: SceneKey = TestScenes.SceneB,
+    changeState: CoroutineScope.(MutableSceneTransitionLayoutState) -> Unit = { state ->
+        state.setTargetScene(toScene, animationScope = this)
+    },
     builder: TransitionTestBuilder.() -> Unit,
 ) {
     testTransition(
@@ -104,7 +132,7 @@
                     transitions { from(fromScene, to = toScene, builder = transition) },
                 )
             },
-        to = toScene,
+        changeState = changeState,
         transitionLayout = { state ->
             SceneTransitionLayout(state, layoutModifier) {
                 scene(fromScene, content = fromSceneContent)
@@ -293,13 +321,30 @@
 ) {
     val test = transitionTest(builder)
     val assertionScope =
-        object : TransitionTestAssertionScope {
+        object : AutoTransitionTestAssertionScope {
+            var progress = 0f
+
             override fun onElement(
                 element: ElementKey,
                 scene: SceneKey?,
             ): SemanticsNodeInteraction {
                 return onNode(isElement(element, scene))
             }
+
+            override fun <T> interpolate(from: T, to: T): T {
+                @Suppress("UNCHECKED_CAST")
+                return when {
+                    from is Float && to is Float -> lerp(from, to, progress)
+                    from is Int && to is Int -> lerp(from, to, progress)
+                    from is Long && to is Long -> lerp(from, to, progress)
+                    from is Dp && to is Dp -> lerp(from, to, progress)
+                    else ->
+                        throw UnsupportedOperationException(
+                            "Interpolation not supported for this type"
+                        )
+                }
+                    as T
+            }
         }
 
     lateinit var coroutineScope: CoroutineScope
@@ -321,14 +366,28 @@
     mainClock.advanceTimeByFrame()
     waitForIdle()
 
+    var currentTime = 0L
     // Test the assertions at specific points in time.
     test.timestamps.forEach { tsAssertion ->
         if (tsAssertion.timestampDelta > 0L) {
             mainClock.advanceTimeBy(tsAssertion.timestampDelta)
             waitForIdle()
+            currentTime += tsAssertion.timestampDelta.toInt()
         }
 
-        tsAssertion.assertion(assertionScope)
+        assertionScope.progress = tsAssertion.progress
+        try {
+            tsAssertion.assertion(assertionScope, tsAssertion.progress)
+        } catch (assertionError: AssertionError) {
+            if (assertionScope.progress > 0) {
+                throw AssertionError(
+                    "Transition assertion failed at ${currentTime}ms " +
+                        "at progress: ${assertionScope.progress}f",
+                    assertionError,
+                )
+            }
+            throw assertionError
+        }
     }
 
     // Go to the end state and test it.
@@ -371,7 +430,25 @@
                     val delta = timestamp - currentTimestamp
                     currentTimestamp = timestamp
 
-                    timestamps.add(TimestampAssertion(delta, builder))
+                    timestamps.add(TimestampAssertion(delta, { builder() }, 0f))
+                }
+
+                override fun atAllFrames(
+                    totalFrames: Int,
+                    builder: AutoTransitionTestAssertionScope.(Float) -> Unit,
+                ) {
+                    check(after == null) { "atFrames(...) {} must be called before after {}" }
+                    check(currentTimestamp == 0L) {
+                        "atFrames(...) can't be called multiple times or after at(...)"
+                    }
+
+                    for (frame in 0 until totalFrames) {
+                        val timestamp = frame * 16L
+                        val delta = timestamp - currentTimestamp
+                        val progress = frame.toFloat() / totalFrames
+                        currentTimestamp = timestamp
+                        timestamps.add(TimestampAssertion(delta, builder, progress))
+                    }
                 }
 
                 override fun after(builder: TransitionTestAssertionScope.() -> Unit) {
@@ -396,5 +473,6 @@
 
 private class TimestampAssertion(
     val timestampDelta: Long,
-    val assertion: TransitionTestAssertionScope.() -> Unit,
+    val assertion: AutoTransitionTestAssertionScope.(Float) -> Unit,
+    val progress: Float,
 )
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
index 21b4c71..2bb5541 100644
--- a/packages/SystemUI/customization/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -33,6 +33,7 @@
     <dimen name="small_clock_height">114dp</dimen>
     <dimen name="small_clock_padding_top">28dp</dimen>
     <dimen name="clock_padding_start">28dp</dimen>
+    <dimen name="weather_date_icon_padding">28dp</dimen>
 
     <!-- When large clock is showing, offset the smartspace by this amount -->
     <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
index bcf055b..15373d3 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
@@ -33,7 +33,7 @@
     val thumbnail: String? = null,
     val large: ClockFace? = null,
     val small: ClockFace? = null,
-    val colorPalette: MonetStyle? = null,
+    @MonetStyle.Type val colorPalette: Int? = null,
 )
 
 /** Describes a clock using layers */
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
index 34c4dfb..48af2d9 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
@@ -25,6 +25,7 @@
 import android.graphics.Color
 import android.graphics.drawable.Drawable
 import android.net.Uri
+import android.os.Bundle
 import android.util.Log
 import androidx.annotation.DrawableRes
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
@@ -51,10 +52,7 @@
      * selected affordances on the slot will move the selected affordance to the newest location in
      * the slot.
      */
-    suspend fun insertSelection(
-        slotId: String,
-        affordanceId: String,
-    )
+    suspend fun insertSelection(slotId: String, affordanceId: String)
 
     /** Returns all available slots supported by the device. */
     suspend fun querySlots(): List<Slot>
@@ -63,6 +61,11 @@
     suspend fun queryFlags(): List<Flag>
 
     /**
+     * Returns [Bundle] where the keys are from [CustomizationProviderContract.RuntimeValuesTable]
+     */
+    suspend fun queryRuntimeValues(): Bundle
+
+    /**
      * Returns [Flow] for observing the collection of slots.
      *
      * @see [querySlots]
@@ -77,6 +80,13 @@
     fun observeFlags(): Flow<List<Flag>>
 
     /**
+     * Returns [Flow] for observing the variables from the System UI.
+     *
+     * @see [queryRuntimeValues]
+     */
+    fun observeRuntimeValues(): Flow<Bundle>
+
+    /**
      * Returns all available affordances supported by the device, regardless of current slot
      * placement.
      */
@@ -100,15 +110,10 @@
     fun observeSelections(): Flow<List<Selection>>
 
     /** Unselects an affordance with the given ID from the slot with the given ID. */
-    suspend fun deleteSelection(
-        slotId: String,
-        affordanceId: String,
-    )
+    suspend fun deleteSelection(slotId: String, affordanceId: String)
 
     /** Unselects all affordances from the slot with the given ID. */
-    suspend fun deleteAllSelections(
-        slotId: String,
-    )
+    suspend fun deleteAllSelections(slotId: String)
 
     /** Returns a [Drawable] with the given ID, loaded from the system UI package. */
     suspend fun getAffordanceIcon(
@@ -200,10 +205,7 @@
     private val backgroundDispatcher: CoroutineDispatcher,
 ) : CustomizationProviderClient {
 
-    override suspend fun insertSelection(
-        slotId: String,
-        affordanceId: String,
-    ) {
+    override suspend fun insertSelection(slotId: String, affordanceId: String) {
         withContext(backgroundDispatcher) {
             context.contentResolver.insert(
                 Contract.LockScreenQuickAffordances.SelectionTable.URI,
@@ -211,9 +213,9 @@
                     put(Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID, slotId)
                     put(
                         Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID,
-                        affordanceId
+                        affordanceId,
                     )
-                }
+                },
             )
         }
     }
@@ -221,13 +223,7 @@
     override suspend fun querySlots(): List<CustomizationProviderClient.Slot> {
         return withContext(backgroundDispatcher) {
             context.contentResolver
-                .query(
-                    Contract.LockScreenQuickAffordances.SlotTable.URI,
-                    null,
-                    null,
-                    null,
-                    null,
-                )
+                .query(Contract.LockScreenQuickAffordances.SlotTable.URI, null, null, null, null)
                 ?.use { cursor ->
                     buildList {
                         val idColumnIndex =
@@ -252,42 +248,55 @@
                         }
                     }
                 }
-        }
-            ?: emptyList()
+        } ?: emptyList()
     }
 
     override suspend fun queryFlags(): List<CustomizationProviderClient.Flag> {
         return withContext(backgroundDispatcher) {
-            context.contentResolver
-                .query(
-                    Contract.FlagsTable.URI,
-                    null,
-                    null,
-                    null,
-                    null,
-                )
-                ?.use { cursor ->
-                    buildList {
+            context.contentResolver.query(Contract.FlagsTable.URI, null, null, null, null)?.use {
+                cursor ->
+                buildList {
+                    val nameColumnIndex = cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME)
+                    val valueColumnIndex = cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE)
+                    if (nameColumnIndex == -1 || valueColumnIndex == -1) {
+                        return@buildList
+                    }
+
+                    while (cursor.moveToNext()) {
+                        add(
+                            CustomizationProviderClient.Flag(
+                                name = cursor.getString(nameColumnIndex),
+                                value = cursor.getInt(valueColumnIndex) == 1,
+                            )
+                        )
+                    }
+                }
+            }
+        } ?: emptyList()
+    }
+
+    override suspend fun queryRuntimeValues(): Bundle {
+        return withContext(backgroundDispatcher) {
+            Bundle().apply {
+                context.contentResolver
+                    .query(Contract.RuntimeValuesTable.URI, null, null, null, null)
+                    ?.use { cursor ->
                         val nameColumnIndex =
                             cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME)
                         val valueColumnIndex =
                             cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE)
-                        if (nameColumnIndex == -1 || valueColumnIndex == -1) {
-                            return@buildList
-                        }
-
-                        while (cursor.moveToNext()) {
-                            add(
-                                CustomizationProviderClient.Flag(
-                                    name = cursor.getString(nameColumnIndex),
-                                    value = cursor.getInt(valueColumnIndex) == 1,
-                                )
-                            )
+                        if (nameColumnIndex >= 0 && valueColumnIndex >= 0) {
+                            while (cursor.moveToNext()) {
+                                when (val name = cursor.getString(nameColumnIndex)) {
+                                    Contract.RuntimeValuesTable.KEY_IS_SHADE_LAYOUT_WIDE -> {
+                                        putBoolean(name, cursor.getInt(valueColumnIndex) == 1)
+                                    }
+                                }
+                            }
                         }
                     }
-                }
+            }
         }
-            ?: emptyList()
     }
 
     override fun observeSlots(): Flow<List<CustomizationProviderClient.Slot>> {
@@ -298,6 +307,10 @@
         return observeUri(Contract.FlagsTable.URI).map { queryFlags() }
     }
 
+    override fun observeRuntimeValues(): Flow<Bundle> {
+        return observeUri(Contract.RuntimeValuesTable.URI).map { queryRuntimeValues() }
+    }
+
     override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> {
         return withContext(backgroundDispatcher) {
             context.contentResolver
@@ -375,22 +388,17 @@
                                     enablementActionIntent =
                                         cursor
                                             .getString(enablementActionIntentColumnIndex)
-                                            ?.toIntent(
-                                                affordanceId = affordanceId,
-                                            ),
+                                            ?.toIntent(affordanceId = affordanceId),
                                     configureIntent =
                                         cursor
                                             .getString(configureIntentColumnIndex)
-                                            ?.toIntent(
-                                                affordanceId = affordanceId,
-                                            ),
+                                            ?.toIntent(affordanceId = affordanceId),
                                 )
                             )
                         }
                     }
                 }
-        }
-            ?: emptyList()
+        } ?: emptyList()
     }
 
     override fun observeAffordances(): Flow<List<CustomizationProviderClient.Affordance>> {
@@ -444,8 +452,7 @@
                         }
                     }
                 }
-        }
-            ?: emptyList()
+        } ?: emptyList()
     }
 
     override fun observeSelections(): Flow<List<CustomizationProviderClient.Selection>> {
@@ -454,34 +461,24 @@
         }
     }
 
-    override suspend fun deleteSelection(
-        slotId: String,
-        affordanceId: String,
-    ) {
+    override suspend fun deleteSelection(slotId: String, affordanceId: String) {
         withContext(backgroundDispatcher) {
             context.contentResolver.delete(
                 Contract.LockScreenQuickAffordances.SelectionTable.URI,
                 "${Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID} = ? AND" +
                     " ${Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID}" +
                     " = ?",
-                arrayOf(
-                    slotId,
-                    affordanceId,
-                ),
+                arrayOf(slotId, affordanceId),
             )
         }
     }
 
-    override suspend fun deleteAllSelections(
-        slotId: String,
-    ) {
+    override suspend fun deleteAllSelections(slotId: String) {
         withContext(backgroundDispatcher) {
             context.contentResolver.delete(
                 Contract.LockScreenQuickAffordances.SelectionTable.URI,
                 Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID,
-                arrayOf(
-                    slotId,
-                ),
+                arrayOf(slotId),
             )
         }
     }
@@ -499,9 +496,7 @@
         }
     }
 
-    private fun observeUri(
-        uri: Uri,
-    ): Flow<Unit> {
+    private fun observeUri(uri: Uri): Flow<Unit> {
         return callbackFlow {
                 val observer =
                     object : ContentObserver(null) {
@@ -522,9 +517,7 @@
             .flowOn(backgroundDispatcher)
     }
 
-    private fun String.toIntent(
-        affordanceId: String,
-    ): Intent? {
+    private fun String.toIntent(affordanceId: String): Intent? {
         return try {
             Intent.parseUri(this, Intent.URI_INTENT_SCHEME)
         } catch (e: URISyntaxException) {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index 8721c78..cb167ed 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -198,4 +198,27 @@
             const val VALUE = "value"
         }
     }
+
+    object RuntimeValuesTable {
+        const val TABLE_NAME = "runtime_values"
+        val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+
+        /**
+         * This key corresponds to an Int value, where `1` means `true` and `0` means `false`.
+         *
+         * Whether the shade layout should be wide (true) or narrow (false).
+         *
+         * In a wide layout, notifications and quick settings each take up only half the screen
+         * width (whether they are shown at the same time or not). In a narrow layout, they can each
+         * be as wide as the entire screen.
+         */
+        const val KEY_IS_SHADE_LAYOUT_WIDE = "is_shade_layout_wide"
+
+        object Columns {
+            /** String. Unique ID for the value. */
+            const val NAME = "name"
+            /** Type depends on the key name. */
+            const val VALUE = "value"
+        }
+    }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt
index f5a955d..47c5bda 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt
@@ -19,6 +19,7 @@
 
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Drawable
+import android.os.Bundle
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -64,11 +65,13 @@
                 value = true,
             )
         ),
+    runtimeValues: Bundle = Bundle(),
 ) : CustomizationProviderClient {
 
     private val slots = MutableStateFlow(slots)
     private val affordances = MutableStateFlow(affordances)
     private val flags = MutableStateFlow(flags)
+    private val runtimeValues = MutableStateFlow(runtimeValues)
 
     private val selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
 
@@ -93,6 +96,10 @@
         return flags.value
     }
 
+    override suspend fun queryRuntimeValues(): Bundle {
+        return runtimeValues.value
+    }
+
     override fun observeSlots(): Flow<List<CustomizationProviderClient.Slot>> {
         return slots.asStateFlow()
     }
@@ -101,6 +108,10 @@
         return flags.asStateFlow()
     }
 
+    override fun observeRuntimeValues(): Flow<Bundle> {
+        return runtimeValues.asStateFlow()
+    }
+
     override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> {
         return affordances.value
     }
@@ -139,10 +150,7 @@
         }
     }
 
-    fun setFlag(
-        name: String,
-        value: Boolean,
-    ) {
+    fun setFlag(name: String, value: Boolean) {
         flags.value =
             flags.value.toMutableList().apply {
                 removeIf { it.name == name }
@@ -150,6 +158,10 @@
             }
     }
 
+    fun setRuntimeValues(runtimeValues: Bundle) {
+        this.runtimeValues.value = runtimeValues
+    }
+
     fun setSlotCapacity(slotId: String, capacity: Int) {
         slots.value =
             slots.value.toMutableList().apply {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
index a3f40d4..a487b28 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
@@ -23,12 +23,16 @@
     const val MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED = 1988
     const val MESSAGE_ID_SLOT_SELECTED = 1337
     const val MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES = 214
+    const val MESSAGE_ID_PREVIEW_CLOCK_SIZE = 1119
 
     const val KEY_HIDE_SMART_SPACE = "hide_smart_space"
     const val KEY_HIGHLIGHT_QUICK_AFFORDANCES = "highlight_quick_affordances"
     const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
     const val KEY_QUICK_AFFORDANCE_ID = "quick_affordance_id"
     const val KEY_SLOT_ID = "slot_id"
+    const val KEY_CLOCK_SIZE = "clock_size"
 
     const val KEYGUARD_QUICK_AFFORDANCE_ID_NONE = "none"
+    const val CLOCK_SIZE_DYNAMIC = "clock_size_dynamic"
+    const val CLOCK_SIZE_SMALL = "clock_size_small"
 }
diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml
index 7577147..00b0c44 100644
--- a/packages/SystemUI/lint-baseline.xml
+++ b/packages/SystemUI/lint-baseline.xml
@@ -32784,4 +32784,984 @@
             column="23"/>
     </issue>
 
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, val theme: Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt"
+            line="33"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt"
+            line="39"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="            @NonNull Context context,"
+        errorLine2="                             ~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java"
+            line="300"
+            column="30"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="            Context context, DeviceConfigProxy proxy) {"
+        errorLine2="                    ~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java"
+            line="75"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    public AuthController(Context context,"
+        errorLine2="                                  ~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java"
+            line="716"
+            column="35"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of WindowManager is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="            @NonNull WindowManager windowManager,"
+        errorLine2="                                   ~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java"
+            line="721"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val sysuiContext: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt"
+            line="72"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of ConfigurationController is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val configurationController: ConfigurationController,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt"
+            line="74"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main protected val resources: Resources, private val theme: Resources.Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt"
+            line="32"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="            Context context,"
+        errorLine2="                    ~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java"
+            line="46"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="            @Main Resources resources,"
+        errorLine2="                            ~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java"
+            line="52"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    public BiometricNotificationService(@NonNull Context context,"
+        errorLine2="                                                         ~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java"
+            line="148"
+            column="58"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Application private val applicationContext: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt"
+            line="62"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Application private val applicationContext: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt"
+            line="37"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    c: Context,"
+        errorLine2="    ~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt"
+            line="61"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt"
+            line="32"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt"
+            line="33"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Application private val applicationContext: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt"
+            line="52"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Application private val applicationContext: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt"
+            line="30"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="class CustomTileStatePersisterImpl @Inject constructor(context: Context) :"
+        errorLine2="                                                       ~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt"
+            line="74"
+            column="56"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt"
+            line="32"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt"
+            line="18"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Application context: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt"
+            line="77"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Application val context: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt"
+            line="68"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt"
+            line="38"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt"
+            line="37"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt"
+            line="42"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Application val applicationContext: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt"
+            line="95"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt"
+            line="142"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Application private val context: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt"
+            line="44"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt"
+            line="33"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt"
+            line="32"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="            @NonNull final Context context,"
+        errorLine2="                                   ~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java"
+            line="186"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of ConfigurationController is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="            ConfigurationController configurationController,"
+        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java"
+            line="192"
+            column="37"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt"
+            line="32"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="class IconBuilder @Inject constructor(private val context: Context) {"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt"
+            line="27"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt"
+            line="31"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of WindowManager is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val windowManager: WindowManager,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt"
+            line="39"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val context: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt"
+            line="40"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt"
+            line="41"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt"
+            line="33"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated LayoutInflater, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of LayoutInflater is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val layoutInflater: LayoutInflater"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt"
+            line="29"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="class MinimumTilesResourceRepository @Inject constructor(@Main resources: Resources) :"
+        errorLine2="                                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt"
+            line="38"
+            column="58"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, val theme: Resources.Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt"
+            line="33"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Application private val context: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt"
+            line="38"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt"
+            line="42"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt"
+            line="32"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    public NotificationGutsManager(Context context,"
+        errorLine2="                                           ~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java"
+            line="137"
+            column="44"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt"
+            line="47"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt"
+            line="53"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(val context: Context) {"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt"
+            line="27"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of ConfigurationController is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="            ConfigurationController configurationController,"
+        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java"
+            line="737"
+            column="37"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt"
+            line="63"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="        Builder(@Main Resources resources, ViewConfiguration viewConfiguration,"
+        errorLine2="                                ~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java"
+            line="563"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt"
+            line="32"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    public PackageManagerAdapter(Context context) {"
+        errorLine2="                                         ~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java"
+            line="45"
+            column="42"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt"
+            line="36"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val context: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt"
+            line="74"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val context: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt"
+            line="41"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Application private val context: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt"
+            line="82"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt"
+            line="32"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt"
+            line="36"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="        Factory(Context context, QSCustomizerController qsCustomizerController) {"
+        errorLine2="                        ~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java"
+            line="99"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt"
+            line="32"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt"
+            line="33"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt"
+            line="36"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt"
+            line="47"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt"
+            line="36"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt"
+            line="51"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt"
+            line="33"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated LayoutInflater, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of LayoutInflater is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val layoutInflater: LayoutInflater,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt"
+            line="43"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    context: Context"
+        errorLine2="    ~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt"
+            line="35"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Application private val applicationContext: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt"
+            line="63"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Application private val applicationContext: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt"
+            line="53"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val context: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt"
+            line="51"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of WindowManager is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    windowManager: WindowManager,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt"
+            line="53"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Application private val applicationContext: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt"
+            line="60"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt"
+            line="65"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt"
+            line="91"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(context: Context, val shadeViewController: ShadeViewController) {"
+        errorLine2="            ~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt"
+            line="30"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt"
+            line="31"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val context: Context"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt"
+            line="33"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt"
+            line="95"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt"
+            line="33"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Application private val context: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt"
+            line="49"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of ConfigurationController is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val configurationController: ConfigurationController,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt"
+            line="43"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="constructor(@Main private val resources: Resources, private val theme: Theme) :"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt"
+            line="37"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt"
+            line="36"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated ConfigurationInteractor, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of ConfigurationInteractor is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @GlobalConfig configurationInteractor: ConfigurationInteractor,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt"
+            line="43"
+            column="5"/>
+    </issue>
+
+
 </issues>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
deleted file mode 100644
index f41d5c8..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ /dev/null
@@ -1,1586 +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.accessibility;
-
-import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_UP;
-import static android.view.WindowInsets.Type.systemGestures;
-import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.AdditionalAnswers.returnsSecondArg;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import static java.util.Arrays.asList;
-
-import android.animation.ValueAnimator;
-import android.annotation.IdRes;
-import android.annotation.Nullable;
-import android.app.Instrumentation;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Insets;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.provider.Settings;
-import android.testing.TestableLooper;
-import android.testing.TestableResources;
-import android.util.Size;
-import android.view.AttachedSurfaceControl;
-import android.view.Display;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewRootImpl;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.widget.FrameLayout;
-import android.window.InputTransferToken;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-
-import com.android.systemui.Flags;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.AnimatorTestRule;
-import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.res.R;
-import com.android.systemui.settings.FakeDisplayTracker;
-import com.android.systemui.util.FakeSharedPreferences;
-import com.android.systemui.util.leak.ReferenceTestUtils;
-import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.utils.os.FakeHandler;
-
-import com.google.common.util.concurrent.AtomicDouble;
-
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Supplier;
-
-@LargeTest
-@TestableLooper.RunWithLooper
-@RunWith(AndroidJUnit4.class)
-public class WindowMagnificationControllerTest extends SysuiTestCase {
-
-    @Rule
-    // NOTE: pass 'null' to allow this test advances time on the main thread.
-    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null);
-
-    private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
-    @Mock
-    private MirrorWindowControl mMirrorWindowControl;
-    @Mock
-    private WindowMagnifierCallback mWindowMagnifierCallback;
-    @Mock
-    IRemoteMagnificationAnimationCallback mAnimationCallback;
-    @Mock
-    IRemoteMagnificationAnimationCallback mAnimationCallback2;
-
-    private SurfaceControl.Transaction mTransaction;
-    @Mock
-    private SecureSettings mSecureSettings;
-
-    private long mWaitAnimationDuration;
-    private long mWaitBounceEffectDuration;
-
-    private Handler mHandler;
-    private TestableWindowManager mWindowManager;
-    private SysUiState mSysUiState;
-    private Resources mResources;
-    private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
-    private WindowMagnificationController mWindowMagnificationController;
-    private Instrumentation mInstrumentation;
-    private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0);
-    private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
-
-    private View mSpyView;
-    private View.OnTouchListener mTouchListener;
-
-    private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
-
-    // This list contains all SurfaceControlViewHosts created during a given test. If the
-    // magnification window is recreated during a test, the list will contain more than a single
-    // element.
-    private List<SurfaceControlViewHost> mSurfaceControlViewHosts = new ArrayList<>();
-    // The most recently created SurfaceControlViewHost.
-    private SurfaceControlViewHost mSurfaceControlViewHost;
-    private KosmosJavaAdapter mKosmos;
-    private FakeSharedPreferences mSharedPreferences;
-
-    /**
-     *  return whether window magnification is supported for current test context.
-     */
-    private boolean isWindowModeSupported() {
-        return getContext().getPackageManager().hasSystemFeature(FEATURE_WINDOW_MAGNIFICATION);
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mContext = spy(mContext);
-        mKosmos = new KosmosJavaAdapter(this);
-        mContext = Mockito.spy(getContext());
-        mHandler = new FakeHandler(TestableLooper.get(this).getLooper());
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        final WindowManager wm = mContext.getSystemService(WindowManager.class);
-        mWindowManager = spy(new TestableWindowManager(wm));
-
-        mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
-        mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin());
-        mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
-        when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then(
-                returnsSecondArg());
-        when(mSecureSettings.getFloatForUser(anyString(), anyFloat(), anyInt())).then(
-                returnsSecondArg());
-
-        mResources = getContext().getOrCreateTestableResources().getResources();
-        // prevent the config orientation from undefined, which may cause config.diff method
-        // neglecting the orientation update.
-        if (mResources.getConfiguration().orientation == ORIENTATION_UNDEFINED) {
-            mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT;
-        }
-
-        // Using the animation duration in WindowMagnificationAnimationController for testing.
-        mWaitAnimationDuration = mResources.getInteger(
-                com.android.internal.R.integer.config_longAnimTime);
-        // Using the bounce effect duration in WindowMagnificationController for testing.
-        mWaitBounceEffectDuration = mResources.getInteger(
-                com.android.internal.R.integer.config_shortAnimTime);
-
-        mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
-                mContext, mValueAnimator);
-        Supplier<SurfaceControlViewHost> scvhSupplier = () -> {
-            mSurfaceControlViewHost = spy(new SurfaceControlViewHost(
-                    mContext, mContext.getDisplay(), new InputTransferToken(),
-                    "WindowMagnification"));
-            ViewRootImpl viewRoot = mock(ViewRootImpl.class);
-            when(mSurfaceControlViewHost.getRootSurfaceControl()).thenReturn(viewRoot);
-            mSurfaceControlViewHosts.add(mSurfaceControlViewHost);
-            return mSurfaceControlViewHost;
-        };
-        mTransaction = spy(new SurfaceControl.Transaction());
-        mSharedPreferences = new FakeSharedPreferences();
-        when(mContext.getSharedPreferences(
-                eq("window_magnification_preferences"), anyInt()))
-                .thenReturn(mSharedPreferences);
-        mWindowMagnificationController =
-                new WindowMagnificationController(
-                        mContext,
-                        mHandler,
-                        mWindowMagnificationAnimationController,
-                        mMirrorWindowControl,
-                        mTransaction,
-                        mWindowMagnifierCallback,
-                        mSysUiState,
-                        mSecureSettings,
-                        scvhSupplier);
-
-        verify(mMirrorWindowControl).setWindowDelegate(
-                any(MirrorWindowControl.MirrorWindowDelegate.class));
-        mSpyView = Mockito.spy(new View(mContext));
-        doAnswer((invocation) -> {
-            mTouchListener = invocation.getArgument(0);
-            return null;
-        }).when(mSpyView).setOnTouchListener(
-                any(View.OnTouchListener.class));
-
-        // skip test if window magnification is not supported to prevent fail results. (b/279820875)
-        Assume.assumeTrue(isWindowModeSupported());
-    }
-
-    @After
-    public void tearDown() {
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.deleteWindowMagnification();
-                });
-        mValueAnimator.cancel();
-    }
-
-    @Test
-    public void initWindowMagnificationController_checkAllowDiagonalScrollingWithSecureSettings() {
-        verify(mSecureSettings).getIntForUser(
-                eq(Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING),
-                /* def */ eq(1), /* userHandle= */ anyInt());
-        assertThat(mWindowMagnificationController.isDiagonalScrollingEnabled()).isTrue();
-    }
-
-    @Test
-    public void enableWindowMagnification_showControlAndNotifyBoundsChanged() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        verify(mMirrorWindowControl).showControl();
-        verify(mWindowMagnifierCallback,
-                timeout(LAYOUT_CHANGE_TIMEOUT_MS).atLeastOnce()).onWindowMagnifierBoundsChanged(
-                eq(mContext.getDisplayId()), any(Rect.class));
-    }
-
-    @Test
-    public void enableWindowMagnification_notifySourceBoundsChanged() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
-                        Float.NaN, /* magnificationFrameOffsetRatioX= */ 0,
-                        /* magnificationFrameOffsetRatioY= */ 0, null));
-
-        // Waits for the surface created
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)).onSourceBoundsChanged(
-                (eq(mContext.getDisplayId())), any());
-    }
-
-    @Test
-    public void enableWindowMagnification_disabled_notifySourceBoundsChanged() {
-        enableWindowMagnification_notifySourceBoundsChanged();
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.deleteWindowMagnification(null));
-        Mockito.reset(mWindowMagnifierCallback);
-
-        enableWindowMagnification_notifySourceBoundsChanged();
-    }
-
-    @Test
-    public void enableWindowMagnification_withAnimation_schedulesFrame() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnification(2.0f, 10,
-                    10, /* magnificationFrameOffsetRatioX= */ 0,
-                    /* magnificationFrameOffsetRatioY= */ 0,
-                    Mockito.mock(IRemoteMagnificationAnimationCallback.class));
-        });
-        advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS);
-
-        verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(),
-                eq(Surface.ROTATION_0));
-    }
-
-    @Test
-    public void moveWindowMagnifier_enabled_notifySourceBoundsChanged() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
-                    Float.NaN, 0, 0, null);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.moveWindowMagnifier(10, 10);
-        });
-
-        final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
-        verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged(
-                (eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertThat(mWindowMagnificationController.getCenterX())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
-        assertThat(mWindowMagnificationController.getCenterY())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
-    }
-
-    @Test
-    public void enableWindowMagnification_systemGestureExclusionRectsIsSet() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-        // Wait for Rects updated.
-        waitForIdleSync();
-
-        List<Rect> rects = mSurfaceControlViewHost.getView().getSystemGestureExclusionRects();
-        assertThat(rects).isNotEmpty();
-    }
-
-    @Ignore("The default window size should be constrained after fixing b/288056772")
-    @Test
-    public void enableWindowMagnification_LargeScreen_windowSizeIsConstrained() {
-        final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
-        mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        final int halfScreenSize = screenSize / 2;
-        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
-        // The frame size should be the half of smaller value of window height/width unless it
-        //exceed the max frame size.
-        assertThat(params.width).isLessThan(halfScreenSize);
-        assertThat(params.height).isLessThan(halfScreenSize);
-    }
-
-    @Test
-    public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN,
-                        Float.NaN));
-
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.deleteWindowMagnification());
-
-        verify(mMirrorWindowControl).destroyControl();
-        verify(mContext).unregisterComponentCallbacks(mWindowMagnificationController);
-    }
-
-    @Test
-    public void deleteWindowMagnification_enableAtTheBottom_overlapFlagIsFalse() {
-        final WindowManager wm = mContext.getSystemService(WindowManager.class);
-        final Rect bounds = wm.getCurrentWindowMetrics().getBounds();
-        setSystemGestureInsets();
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    bounds.bottom);
-        });
-        ReferenceTestUtils.waitForCondition(this::hasMagnificationOverlapFlag);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.deleteWindowMagnification();
-        });
-
-        verify(mMirrorWindowControl).destroyControl();
-        assertThat(hasMagnificationOverlapFlag()).isFalse();
-    }
-
-    @Test
-    public void deleteWindowMagnification_notifySourceBoundsChanged() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN,
-                        Float.NaN));
-
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.deleteWindowMagnification());
-
-        // The first time is for notifying magnification enabled and the second time is for
-        // notifying magnification disabled.
-        verify(mWindowMagnifierCallback, times(2)).onSourceBoundsChanged(
-                (eq(mContext.getDisplayId())), any());
-    }
-
-    @Test
-    public void moveMagnifier_schedulesFrame() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        waitForIdleSync();
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.moveWindowMagnifier(100f, 100f));
-
-        verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(),
-                eq(Surface.ROTATION_0));
-    }
-
-    @Test
-    public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback()
-            throws RemoteException {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
-                    Float.NaN, 0, 0, null);
-        });
-
-        final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
-                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
-        final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
-
-        reset(mWindowMagnifierCallback);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    targetCenterX, targetCenterY, mAnimationCallback);
-        });
-        advanceTimeBy(mWaitAnimationDuration);
-
-        verify(mAnimationCallback, times(1)).onResult(eq(true));
-        verify(mAnimationCallback, never()).onResult(eq(false));
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
-                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertThat(mWindowMagnificationController.getCenterX())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
-        assertThat(mWindowMagnificationController.getCenterY())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
-        assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(targetCenterX);
-        assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(targetCenterY);
-    }
-
-    @Test
-    public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback()
-            throws RemoteException {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
-                    Float.NaN, 0, 0, null);
-        });
-
-        final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
-                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
-        final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
-
-        reset(mWindowMagnifierCallback);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 10, centerY + 10, mAnimationCallback);
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 20, centerY + 20, mAnimationCallback);
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 30, centerY + 30, mAnimationCallback);
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 40, centerY + 40, mAnimationCallback2);
-        });
-        advanceTimeBy(mWaitAnimationDuration);
-
-        // only the last one callback will return true
-        verify(mAnimationCallback2).onResult(eq(true));
-        // the others will return false
-        verify(mAnimationCallback, times(3)).onResult(eq(false));
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
-                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertThat(mWindowMagnificationController.getCenterX())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
-        assertThat(mWindowMagnificationController.getCenterY())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
-        assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(centerX + 40);
-        assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(centerY + 40);
-    }
-
-    @Test
-    public void setScale_enabled_expectedValueAndUpdateStateDescription() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(2.0f,
-                        Float.NaN, Float.NaN));
-
-        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
-
-        assertThat(mWindowMagnificationController.getScale()).isEqualTo(3.0f);
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        assertThat(mirrorView).isNotNull();
-        assertThat(mirrorView.getStateDescription().toString()).contains("300");
-    }
-
-    @Test
-    public void onConfigurationChanged_disabled_withoutException() {
-        Display display = Mockito.spy(mContext.getDisplay());
-        when(display.getRotation()).thenReturn(Surface.ROTATION_90);
-        when(mContext.getDisplay()).thenReturn(display);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-    }
-
-    @Test
-    public void onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition() {
-        final int newRotation = simulateRotateTheDevice();
-        final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
-        final float center = Math.min(windowBounds.exactCenterX(), windowBounds.exactCenterY());
-        final float displayWidth = windowBounds.width();
-        final PointF magnifiedCenter = new PointF(center, center + 5f);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                    magnifiedCenter.x, magnifiedCenter.y);
-            // Get the center again in case the center we set is out of screen.
-            magnifiedCenter.set(mWindowMagnificationController.getCenterX(),
-                    mWindowMagnificationController.getCenterY());
-        });
-        // Rotate the window clockwise 90 degree.
-        windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
-                windowBounds.right);
-        mWindowManager.setWindowBounds(windowBounds);
-
-        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
-                ActivityInfo.CONFIG_ORIENTATION));
-
-        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
-        final PointF expectedCenter = new PointF(magnifiedCenter.y,
-                displayWidth - magnifiedCenter.x);
-        final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(),
-                mWindowMagnificationController.getCenterY());
-        assertThat(actualCenter).isEqualTo(expectedCenter);
-    }
-
-    @Test
-    public void onOrientationChanged_disabled_updateDisplayRotation() {
-        final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
-        // Rotate the window clockwise 90 degree.
-        windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
-                windowBounds.right);
-        mWindowManager.setWindowBounds(windowBounds);
-        final int newRotation = simulateRotateTheDevice();
-
-        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
-                ActivityInfo.CONFIG_ORIENTATION));
-
-        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
-    }
-
-    @Test
-    public void onScreenSizeAndDensityChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() {
-        // The default position is at the center of the screen.
-        final float expectedRatio = 0.5f;
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        // Screen size and density change
-        mContext.getResources().getConfiguration().smallestScreenWidthDp =
-                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
-        final Rect testWindowBounds = new Rect(
-                mWindowManager.getCurrentWindowMetrics().getBounds());
-        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
-                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
-        mWindowManager.setWindowBounds(testWindowBounds);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-
-        // The ratio of center to window size should be the same.
-        assertThat(mWindowMagnificationController.getCenterX() / testWindowBounds.width())
-                .isEqualTo(expectedRatio);
-        assertThat(mWindowMagnificationController.getCenterY() / testWindowBounds.height())
-                .isEqualTo(expectedRatio);
-    }
-
-    @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
-    @Test
-    public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierWindow() {
-        int newSmallestScreenWidthDp =
-                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
-        int windowFrameSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize);
-        mSharedPreferences
-                .edit()
-                .putString(String.valueOf(newSmallestScreenWidthDp),
-                        preferredWindowSize.toString())
-                .commit();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        // Screen density and size change
-        mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
-        final Rect testWindowBounds = new Rect(
-                mWindowManager.getCurrentWindowMetrics().getBounds());
-        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
-                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
-        mWindowManager.setWindowBounds(testWindowBounds);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-
-        // wait for rect update
-        waitForIdleSync();
-        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // The width and height of the view include the magnification frame and the margins.
-        assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
-        assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
-    }
-
-    @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
-    @Test
-    public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() {
-        int newSmallestScreenWidthDp =
-                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
-        int windowFrameSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize);
-        mSharedPreferences
-                .edit()
-                .putString(String.valueOf(newSmallestScreenWidthDp),
-                        WindowMagnificationFrameSpec.serialize(
-                                WindowMagnificationSettings.MagnificationSize.CUSTOM,
-                                preferredWindowSize))
-                .commit();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        // Screen density and size change
-        mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
-        final Rect testWindowBounds = new Rect(
-                mWindowManager.getCurrentWindowMetrics().getBounds());
-        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
-                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
-        mWindowManager.setWindowBounds(testWindowBounds);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-
-        // wait for rect update
-        waitForIdleSync();
-        verify(mWindowMagnifierCallback).onWindowMagnifierBoundsRestored(
-                eq(mContext.getDisplayId()),
-                eq(WindowMagnificationSettings.MagnificationSize.CUSTOM));
-        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // The width and height of the view include the magnification frame and the margins.
-        assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
-        assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
-    }
-
-    @Test
-    public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-        final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
-        // Screen size and density change
-        mContext.getResources().getConfiguration().smallestScreenWidthDp =
-                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
-        mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-
-        final int defaultWindowSize =
-                mWindowMagnificationController.getMagnificationWindowSizeFromIndex(
-                        WindowMagnificationSettings.MagnificationSize.MEDIUM);
-        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
-
-        assertThat(params.width).isEqualTo(defaultWindowSize);
-        assertThat(params.height).isEqualTo(defaultWindowSize);
-    }
-
-    @Test
-    public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            Mockito.reset(mWindowManager);
-            Mockito.reset(mMirrorWindowControl);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
-        });
-
-        verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
-        verify(mSurfaceControlViewHosts.get(0)).release();
-        verify(mMirrorWindowControl).destroyControl();
-        verify(mSurfaceControlViewHosts.get(1)).setView(any(), any());
-        verify(mMirrorWindowControl).showControl();
-    }
-
-    @Test
-    public void onDensityChanged_disabled_updateDimensions() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
-        });
-
-        verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
-    }
-
-    @Test
-    public void initializeA11yNode_enabled_expectedValues() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
-                    Float.NaN);
-        });
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        assertThat(mirrorView).isNotNull();
-        final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
-
-        mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo);
-
-        assertThat(nodeInfo.getContentDescription()).isNotNull();
-        assertThat(nodeInfo.getStateDescription().toString()).contains("250");
-        assertThat(nodeInfo.getActionList()).containsExactlyElementsIn(asList(
-                new AccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
-                        mContext.getResources().getString(
-                                R.string.magnification_open_settings_click_label)),
-                new AccessibilityAction(R.id.accessibility_action_zoom_in,
-                        mContext.getString(R.string.accessibility_control_zoom_in)),
-                new AccessibilityAction(R.id.accessibility_action_zoom_out,
-                        mContext.getString(R.string.accessibility_control_zoom_out)),
-                new AccessibilityAction(R.id.accessibility_action_move_right,
-                        mContext.getString(R.string.accessibility_control_move_right)),
-                new AccessibilityAction(R.id.accessibility_action_move_left,
-                        mContext.getString(R.string.accessibility_control_move_left)),
-                new AccessibilityAction(R.id.accessibility_action_move_down,
-                        mContext.getString(R.string.accessibility_control_move_down)),
-                new AccessibilityAction(R.id.accessibility_action_move_up,
-                        mContext.getString(R.string.accessibility_control_move_up))));
-    }
-
-    @Test
-    public void performA11yActions_visible_expectedResults() {
-        final int displayId = mContext.getDisplayId();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(1.5f, Float.NaN,
-                    Float.NaN);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null))
-                .isTrue();
-        // Minimum scale is 1.0.
-        verify(mWindowMagnifierCallback).onPerformScaleAction(
-                eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true));
-
-        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null))
-                .isTrue();
-        verify(mWindowMagnifierCallback).onPerformScaleAction(
-                eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true));
-
-        // TODO: Verify the final state when the mirror surface is visible.
-        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null))
-                .isTrue();
-        assertThat(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null))
-                .isTrue();
-        assertThat(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null))
-                .isTrue();
-        assertThat(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null))
-                .isTrue();
-        verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId));
-
-        assertThat(mirrorView.performAccessibilityAction(
-                AccessibilityAction.ACTION_CLICK.getId(), null)).isTrue();
-        verify(mWindowMagnifierCallback).onClickSettingsButton(eq(displayId));
-    }
-
-    @Test
-    public void performA11yActions_visible_notifyAccessibilityActionPerformed() {
-        final int displayId = mContext.getDisplayId();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
-                    Float.NaN);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null);
-
-        verify(mWindowMagnifierCallback).onAccessibilityActionPerformed(eq(displayId));
-    }
-
-    @Test
-    public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        View closeButton = getInternalView(R.id.close_button);
-        View bottomRightCorner = getInternalView(R.id.bottom_right_corner);
-        View bottomLeftCorner = getInternalView(R.id.bottom_left_corner);
-        View topRightCorner = getInternalView(R.id.top_right_corner);
-        View topLeftCorner = getInternalView(R.id.top_left_corner);
-
-        assertThat(closeButton.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(topRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(topLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        mInstrumentation.runOnMainSync(() ->
-                mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
-                        null));
-
-        assertThat(closeButton.getVisibility()).isEqualTo(View.GONE);
-        assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.GONE);
-        assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.GONE);
-        assertThat(topRightCorner.getVisibility()).isEqualTo(View.GONE);
-        assertThat(topLeftCorner.getVisibility()).isEqualTo(View.GONE);
-    }
-
-    @Test
-    public void windowWidthIsNotMax_performA11yActionIncreaseWidth_windowWidthIncreased() {
-        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        final int startingWidth = (int) (windowBounds.width() * 0.8);
-        final int startingHeight = (int) (windowBounds.height() * 0.8);
-        final float changeWindowSizeAmount = mContext.getResources().getFraction(
-                R.fraction.magnification_resize_window_size_amount,
-                /* base= */ 1,
-                /* pbase= */ 1);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mirrorView.performAccessibilityAction(
-                            R.id.accessibility_action_increase_window_width, null);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // Window width includes the magnifier frame and the margin. Increasing the window size
-        // will be increasing the amount of the frame size only.
-        int newWindowWidth =
-                (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
-                        + 2 * mirrorSurfaceMargin;
-        assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
-        assertThat(actualWindowHeight.get()).isEqualTo(startingHeight);
-    }
-
-    @Test
-    public void windowHeightIsNotMax_performA11yActionIncreaseHeight_windowHeightIncreased() {
-        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        final int startingWidth = (int) (windowBounds.width() * 0.8);
-        final int startingHeight = (int) (windowBounds.height() * 0.8);
-        final float changeWindowSizeAmount = mContext.getResources().getFraction(
-                R.fraction.magnification_resize_window_size_amount,
-                /* base= */ 1,
-                /* pbase= */ 1);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mirrorView.performAccessibilityAction(
-                            R.id.accessibility_action_increase_window_height, null);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // Window height includes the magnifier frame and the margin. Increasing the window size
-        // will be increasing the amount of the frame size only.
-        int newWindowHeight =
-                (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
-                        + 2 * mirrorSurfaceMargin;
-        assertThat(actualWindowWidth.get()).isEqualTo(startingWidth);
-        assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
-    }
-
-    @Test
-    public void windowWidthIsMax_noIncreaseWindowWidthA11yAction() {
-        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        final int startingWidth = windowBounds.width();
-        final int startingHeight = windowBounds.height();
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AccessibilityNodeInfo accessibilityNodeInfo =
-                mirrorView.createAccessibilityNodeInfo();
-        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
-                new AccessibilityAction(
-                        R.id.accessibility_action_increase_window_width,
-                        mContext.getString(
-                                R.string.accessibility_control_increase_window_width)));
-    }
-
-    @Test
-    public void windowHeightIsMax_noIncreaseWindowHeightA11yAction() {
-        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        final int startingWidth = windowBounds.width();
-        final int startingHeight = windowBounds.height();
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AccessibilityNodeInfo accessibilityNodeInfo =
-                mirrorView.createAccessibilityNodeInfo();
-        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
-                new AccessibilityAction(
-                        R.id.accessibility_action_increase_window_height, null));
-    }
-
-    @Test
-    public void windowWidthIsNotMin_performA11yActionDecreaseWidth_windowWidthDecreased() {
-        int mMinWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int startingSize = (int) (mMinWindowSize * 1.1);
-        final float changeWindowSizeAmount = mContext.getResources().getFraction(
-                R.fraction.magnification_resize_window_size_amount,
-                /* base= */ 1,
-                /* pbase= */ 1);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mirrorView.performAccessibilityAction(
-                            R.id.accessibility_action_decrease_window_width, null);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // Window width includes the magnifier frame and the margin. Decreasing the window size
-        // will be decreasing the amount of the frame size only.
-        int newWindowWidth =
-                (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
-                        + 2 * mirrorSurfaceMargin;
-        assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
-        assertThat(actualWindowHeight.get()).isEqualTo(startingSize);
-    }
-
-    @Test
-    public void windowHeightIsNotMin_performA11yActionDecreaseHeight_windowHeightDecreased() {
-        int mMinWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int startingSize = (int) (mMinWindowSize * 1.1);
-        final float changeWindowSizeAmount = mContext.getResources().getFraction(
-                R.fraction.magnification_resize_window_size_amount,
-                /* base= */ 1,
-                /* pbase= */ 1);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mirrorView.performAccessibilityAction(
-                            R.id.accessibility_action_decrease_window_height, null);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // Window height includes the magnifier frame and the margin. Decreasing the window size
-        // will be decreasing the amount of the frame size only.
-        int newWindowHeight =
-                (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
-                        + 2 * mirrorSurfaceMargin;
-        assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
-        assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
-    }
-
-    @Test
-    public void windowWidthIsMin_noDecreaseWindowWidthA11yAction() {
-        int mMinWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int startingSize = mMinWindowSize;
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AccessibilityNodeInfo accessibilityNodeInfo =
-                mirrorView.createAccessibilityNodeInfo();
-        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
-                new AccessibilityAction(
-                        R.id.accessibility_action_decrease_window_width, null));
-    }
-
-    @Test
-    public void windowHeightIsMin_noDecreaseWindowHeightA11yAction() {
-        int mMinWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int startingSize = mMinWindowSize;
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AccessibilityNodeInfo accessibilityNodeInfo =
-                mirrorView.createAccessibilityNodeInfo();
-        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
-                new AccessibilityAction(
-                        R.id.accessibility_action_decrease_window_height, null));
-    }
-
-    @Test
-    public void enableWindowMagnification_hasA11yWindowTitle() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        assertThat(getAccessibilityWindowTitle()).isEqualTo(getContext().getResources().getString(
-                com.android.internal.R.string.android_system_label));
-    }
-
-    @Test
-    public void enableWindowMagnificationWithScaleLessThanOne_enabled_disabled() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(0.9f, Float.NaN,
-                    Float.NaN);
-        });
-
-        assertThat(mWindowMagnificationController.getScale()).isEqualTo(Float.NaN);
-    }
-
-    @Test
-    public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
-        // the config orientation should not be undefined, since it would cause config.diff
-        // returning 0 and thus the orientation changed would not be detected
-        assertThat(mResources.getConfiguration().orientation).isNotEqualTo(ORIENTATION_UNDEFINED);
-
-        final Configuration config = mResources.getConfiguration();
-        config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
-                : ORIENTATION_LANDSCAPE;
-        final int newRotation = simulateRotateTheDevice();
-
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
-    }
-
-    @Test
-    public void enableWindowMagnification_registerComponentCallback() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN,
-                        Float.NaN));
-
-        verify(mContext).registerComponentCallbacks(mWindowMagnificationController);
-    }
-
-    @Test
-    public void onLocaleChanged_enabled_updateA11yWindowTitle() {
-        final String newA11yWindowTitle = "new a11y window title";
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-        final TestableResources testableResources = getContext().getOrCreateTestableResources();
-        testableResources.addOverride(com.android.internal.R.string.android_system_label,
-                newA11yWindowTitle);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
-        });
-
-        assertThat(getAccessibilityWindowTitle()).isEqualTo(newA11yWindowTitle);
-    }
-
-    @Ignore("it's flaky in presubmit but works in abtd, filter for now. b/305654925")
-    @Test
-    public void onSingleTap_enabled_scaleAnimates() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onSingleTap(mSpyView);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-
-        final AtomicDouble maxScaleX = new AtomicDouble();
-        advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> {
-            // For some reason the fancy way doesn't compile...
-            // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
-            final double oldMax = maxScaleX.get();
-            final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
-            assertThat(maxScaleX.compareAndSet(oldMax, newMax)).isTrue();
-        });
-
-        assertThat(maxScaleX.get()).isGreaterThan(1.0);
-    }
-
-    @Test
-    public void moveWindowMagnificationToTheBottom_enabledWithGestureInset_overlapFlagIsTrue() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        setSystemGestureInsets();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.moveWindowMagnifier(0, bounds.height());
-        });
-
-        ReferenceTestUtils.waitForCondition(() -> hasMagnificationOverlapFlag());
-    }
-
-    @Test
-    public void moveWindowMagnificationToRightEdge_dragHandleMovesToLeftAndUpdatesTapExcludeRegion()
-            throws RemoteException {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        setSystemGestureInsets();
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.updateWindowMagnificationInternal(
-                            Float.NaN, Float.NaN, Float.NaN);
-                });
-        // Wait for Region updated.
-        waitForIdleSync();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.moveWindowMagnifier(bounds.width(), 0);
-                });
-        // Wait for Region updated.
-        waitForIdleSync();
-
-        AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl();
-        // Verifying two times in: (1) enable window magnification (2) reposition drag handle
-        verify(viewRoot, times(2)).setTouchableRegion(any());
-
-        View dragButton = getInternalView(R.id.drag_handle);
-        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
-        assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.LEFT);
-    }
-
-    @Test
-    public void moveWindowMagnificationToLeftEdge_dragHandleMovesToRightAndUpdatesTapExcludeRegion()
-            throws RemoteException {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        setSystemGestureInsets();
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.updateWindowMagnificationInternal(
-                            Float.NaN, Float.NaN, Float.NaN);
-                });
-        // Wait for Region updated.
-        waitForIdleSync();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.moveWindowMagnifier(-bounds.width(), 0);
-                });
-        // Wait for Region updated.
-        waitForIdleSync();
-
-        AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl();
-        // Verifying one times in: (1) enable window magnification
-        verify(viewRoot).setTouchableRegion(any());
-
-        View dragButton = getInternalView(R.id.drag_handle);
-        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
-        assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.RIGHT);
-    }
-
-    @Test
-    public void setMinimumWindowSize_enabled_expectedWindowSize() {
-        final int minimumWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int  expectedWindowHeight = minimumWindowSize;
-        final int  expectedWindowWidth = minimumWindowSize;
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
-            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
-            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
-
-        });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
-        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
-    }
-
-    @Test
-    public void setMinimumWindowSizeThenEnable_expectedWindowSize() {
-        final int minimumWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int  expectedWindowHeight = minimumWindowSize;
-        final int  expectedWindowWidth = minimumWindowSize;
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                    Float.NaN, Float.NaN);
-            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
-            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
-        });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
-        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
-    }
-
-    @Test
-    public void setWindowSizeLessThanMin_enabled_minimumWindowSize() {
-        final int minimumWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSize(minimumWindowSize - 10,
-                    minimumWindowSize - 10);
-            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
-            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
-        });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(minimumWindowSize);
-        assertThat(actualWindowWidth.get()).isEqualTo(minimumWindowSize);
-    }
-
-    @Test
-    public void setWindowSizeLargerThanScreenSize_enabled_windowSizeIsScreenSize() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSize(bounds.width() + 10, bounds.height() + 10);
-            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
-            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
-        });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(bounds.height());
-        assertThat(actualWindowWidth.get()).isEqualTo(bounds.width());
-    }
-
-    @Test
-    public void changeMagnificationSize_expectedWindowSize() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-
-        final float magnificationScaleLarge = 2.5f;
-        final int initSize = Math.min(bounds.width(), bounds.height()) / 3;
-        final int magnificationSize = (int) (initSize * magnificationScaleLarge)
-                - (int) (initSize * magnificationScaleLarge) % 2;
-
-        final int expectedWindowHeight = magnificationSize;
-        final int expectedWindowWidth = magnificationSize;
-
-        mInstrumentation.runOnMainSync(
-                () ->
-                        mWindowMagnificationController.updateWindowMagnificationInternal(
-                                Float.NaN, Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.changeMagnificationSize(
-                            WindowMagnificationSettings.MagnificationSize.LARGE);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
-        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
-    }
-
-    @Test
-    public void editModeOnDragCorner_resizesWindow() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-
-        final int startingSize = (int) (bounds.width() / 2);
-
-        mInstrumentation.runOnMainSync(
-                () ->
-                        mWindowMagnificationController.updateWindowMagnificationInternal(
-                                Float.NaN, Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-                    mWindowMagnificationController.setEditMagnifierSizeMode(true);
-                });
-
-        waitForIdleSync();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController
-                            .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
-        assertThat(actualWindowWidth.get()).isEqualTo(startingSize + 2);
-    }
-
-    @Test
-    public void editModeOnDragEdge_resizesWindowInOnlyOneDirection() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-
-        final int startingSize = (int) (bounds.width() / 2f);
-
-        mInstrumentation.runOnMainSync(
-                () ->
-                        mWindowMagnificationController.updateWindowMagnificationInternal(
-                                Float.NaN, Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-                    mWindowMagnificationController.setEditMagnifierSizeMode(true);
-                    mWindowMagnificationController
-                            .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-        assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
-        assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
-    }
-
-    @Test
-    public void setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen() {
-
-        final int minimumWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        final AtomicInteger magnificationCenterX = new AtomicInteger();
-        final AtomicInteger magnificationCenterY = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSizeAndCenter(minimumWindowSize,
-                    minimumWindowSize, bounds.right, bounds.bottom);
-            magnificationCenterX.set((int) mWindowMagnificationController.getCenterX());
-            magnificationCenterY.set((int) mWindowMagnificationController.getCenterY());
-        });
-
-        assertThat(magnificationCenterX.get()).isLessThan(bounds.right);
-        assertThat(magnificationCenterY.get()).isLessThan(bounds.bottom);
-    }
-
-    @Test
-    public void performSingleTap_DragHandle() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.updateWindowMagnificationInternal(
-                            1.5f, bounds.centerX(), bounds.centerY());
-                });
-        View dragButton = getInternalView(R.id.drag_handle);
-
-        // Perform a single-tap
-        final long downTime = SystemClock.uptimeMillis();
-        dragButton.dispatchTouchEvent(
-                obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100));
-        dragButton.dispatchTouchEvent(
-                obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100));
-
-        verify(mSurfaceControlViewHost).setView(any(View.class), any());
-    }
-
-    private <T extends View> T getInternalView(@IdRes int idRes) {
-        View mirrorView = mSurfaceControlViewHost.getView();
-        T view = mirrorView.findViewById(idRes);
-        assertThat(view).isNotNull();
-        return view;
-    }
-
-    private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
-            float y) {
-        return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
-    }
-
-    private String getAccessibilityWindowTitle() {
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        if (mirrorView == null) {
-            return null;
-        }
-        WindowManager.LayoutParams layoutParams =
-                (WindowManager.LayoutParams) mirrorView.getLayoutParams();
-        return layoutParams.accessibilityTitle.toString();
-    }
-
-    private boolean hasMagnificationOverlapFlag() {
-        return (mSysUiState.getFlags() & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0;
-    }
-
-    private void setSystemGestureInsets() {
-        final WindowInsets testInsets = new WindowInsets.Builder()
-                .setInsets(systemGestures(), Insets.of(0, 0, 0, 10))
-                .build();
-        mWindowManager.setWindowInsets(testInsets);
-    }
-
-    private int updateMirrorSurfaceMarginDimension() {
-        return mContext.getResources().getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-    }
-
-    @Surface.Rotation
-    private int simulateRotateTheDevice() {
-        final Display display = Mockito.spy(mContext.getDisplay());
-        final int currentRotation = display.getRotation();
-        final int newRotation = (currentRotation + 1) % 4;
-        when(display.getRotation()).thenReturn(newRotation);
-        when(mContext.getDisplay()).thenReturn(display);
-        return newRotation;
-    }
-
-    // advance time based on the device frame refresh rate
-    private void advanceTimeBy(long timeDelta) {
-        advanceTimeBy(timeDelta, /* runnableOnEachRefresh= */ null);
-    }
-
-    // advance time based on the device frame refresh rate, and trigger runnable on each refresh
-    private void advanceTimeBy(long timeDelta, @Nullable Runnable runnableOnEachRefresh) {
-        final float frameRate = mContext.getDisplay().getRefreshRate();
-        final int timeSlot = (int) (1000 / frameRate);
-        int round = (int) Math.ceil((double) timeDelta / timeSlot);
-        for (; round >= 0; round--) {
-            mInstrumentation.runOnMainSync(() -> {
-                mAnimatorTestRule.advanceTimeBy(timeSlot);
-                if (runnableOnEachRefresh != null) {
-                    runnableOnEachRefresh.run();
-                }
-            });
-        }
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
index 944066fa..d47ec8c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
@@ -25,15 +25,12 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper;
 import android.util.Size;
 
 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.util.FakeSharedPreferences;
 
@@ -59,18 +56,6 @@
         mWindowMagnificationFrameSizePrefs = new WindowMagnificationFrameSizePrefs(mContext);
     }
 
-    @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
-    @Test
-    public void saveSizeForCurrentDensity_getExpectedSize() {
-        Size testSize = new Size(500, 500);
-        mWindowMagnificationFrameSizePrefs
-                .saveIndexAndSizeForCurrentDensity(MagnificationSize.CUSTOM, testSize);
-
-        assertThat(mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity())
-                .isEqualTo(testSize);
-    }
-
-    @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
     @Test
     public void saveSizeForCurrentDensity_validPreference_getExpectedSize() {
         int testIndex = MagnificationSize.MEDIUM;
@@ -81,7 +66,6 @@
                 .isEqualTo(testSize);
     }
 
-    @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
     @Test
     public void saveSizeForCurrentDensity_validPreference_getExpectedIndex() {
         int testIndex = MagnificationSize.MEDIUM;
@@ -92,7 +76,6 @@
                 .isEqualTo(testIndex);
     }
 
-    @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
     @Test
     public void saveSizeForCurrentDensity_invalidPreference_getDefaultIndex() {
         mSharedPreferences
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index 80de087..fa8cdcc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -24,7 +24,6 @@
 import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper;
 import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -40,7 +39,6 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -56,15 +54,11 @@
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
-    @Mock
-    private AccessibilityManager mAccessibilityManager;
-
     @Before
     public void setUp() throws Exception {
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final SecureSettings mockSecureSettings = TestUtils.mockSecureSettings();
-        final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
-                mockSecureSettings);
+        final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mockSecureSettings);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 stubWindowManager);
         final MenuView stubMenuView = spy(new MenuView(mContext, stubMenuViewModel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
index 24f3a29..7e4b6f9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
@@ -16,16 +16,11 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
-import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
-
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 
-import android.content.Context;
 import android.content.res.Configuration;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -33,7 +28,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.settings.SecureSettings;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -42,8 +36,6 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Locale;
 
 /** Tests for {@link MenuInfoRepository}. */
@@ -54,30 +46,16 @@
     public MockitoRule mockito = MockitoJUnit.rule();
 
     @Mock
-    private AccessibilityManager mAccessibilityManager;
-
-    @Mock
     private MenuInfoRepository.OnSettingsContentsChanged mMockSettingsContentsChanged;
     @Mock
     private SecureSettings mSecureSettings;
 
     private MenuInfoRepository mMenuInfoRepository;
-    private final List<String> mShortcutTargets = new ArrayList<>();
 
     @Before
     public void setUp() {
-        mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
-        mShortcutTargets.add(MAGNIFICATION_CONTROLLER_NAME);
-        doReturn(mShortcutTargets).when(mAccessibilityManager).getAccessibilityShortcutTargets(
-                anyInt());
-
-        mMenuInfoRepository = new MenuInfoRepository(mContext, mAccessibilityManager,
-                mMockSettingsContentsChanged, mSecureSettings);
-    }
-
-    @After
-    public void tearDown() {
-        mShortcutTargets.clear();
+        mMenuInfoRepository = new MenuInfoRepository(mContext, mMockSettingsContentsChanged,
+                mSecureSettings);
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 157cccc..1f48bec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -83,8 +83,7 @@
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 stubWindowManager);
-        final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
-                mSecureSettings);
+        final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mSecureSettings);
 
         final int halfScreenHeight =
                 stubWindowManager.getCurrentWindowMetrics().getBounds().height() / 2;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 46f076a..f7b81cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -33,7 +33,6 @@
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
 import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.recyclerview.widget.RecyclerView;
@@ -53,7 +52,6 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -80,15 +78,11 @@
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
-    @Mock
-    private AccessibilityManager mAccessibilityManager;
-
     @Before
     public void setUp() throws Exception {
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         final SecureSettings secureSettings = TestUtils.mockSecureSettings();
-        final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
-                secureSettings);
+        final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, secureSettings);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 windowManager);
         mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index ee8ce17..c1708d1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -32,7 +32,6 @@
 import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper;
 import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -49,7 +48,6 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -68,9 +66,6 @@
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
-    @Mock
-    private AccessibilityManager mAccessibilityManager;
-
     private SysuiTestableContext mSpyContext;
 
     @Before
@@ -89,8 +84,7 @@
         doNothing().when(mSpyContext).startActivity(any());
 
         final SecureSettings secureSettings = TestUtils.mockSecureSettings();
-        final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
-                secureSettings);
+        final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, secureSettings);
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         mStubMenuViewAppearance = new MenuViewAppearance(mSpyContext, stubWindowManager);
         mMenuView = spy(new MenuView(mSpyContext, stubMenuViewModel, mStubMenuViewAppearance,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling
deleted file mode 100644
index 0bd00fb..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling
+++ /dev/null
@@ -1,330 +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.accessibility.fontscaling
-
-import android.content.res.Configuration
-import android.os.Handler
-import android.provider.Settings
-import android.testing.TestableLooper
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.SeekBar
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
-import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
-import com.android.systemui.model.SysUiState
-import com.android.systemui.res.R
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.settings.FakeSettings
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SystemSettings
-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.any
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyLong
-import org.mockito.Mock
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
-
-private const val ON: Int = 1
-private const val OFF: Int = 0
-
-/** Tests for [FontScalingDialogDelegate]. */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class FontScalingDialogDelegateTest : SysuiTestCase() {
-    private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate
-    private lateinit var dialog: SystemUIDialog
-    private lateinit var systemSettings: SystemSettings
-    private lateinit var secureSettings: SecureSettings
-    private lateinit var systemClock: FakeSystemClock
-    private lateinit var backgroundDelayableExecutor: FakeExecutor
-    private lateinit var testableLooper: TestableLooper
-    private val fontSizeValueArray: Array<String> =
-        mContext
-            .getResources()
-            .getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
-
-    @Mock private lateinit var dialogManager: SystemUIDialogManager
-    @Mock private lateinit var dialogFactory: SystemUIDialog.Factory
-    @Mock private lateinit var userTracker: UserTracker
-    @Mock private lateinit var sysuiState: SysUiState
-    @Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        testableLooper = TestableLooper.get(this)
-        val mainHandler = Handler(testableLooper.looper)
-        systemSettings = FakeSettings()
-        // Guarantee that the systemSettings always starts with the default font scale.
-        systemSettings.putFloatForUser(Settings.System.FONT_SCALE, 1.0f, userTracker.userId)
-        secureSettings = FakeSettings()
-        systemClock = FakeSystemClock()
-        backgroundDelayableExecutor = FakeExecutor(systemClock)
-        whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
-
-        fontScalingDialogDelegate =
-            spy(
-                FontScalingDialogDelegate(
-                    mContext,
-                    dialogFactory,
-                    LayoutInflater.from(mContext),
-                    systemSettings,
-                    secureSettings,
-                    systemClock,
-                    userTracker,
-                    mainHandler,
-                    backgroundDelayableExecutor
-                )
-            )
-
-        dialog =
-            SystemUIDialog(
-                mContext,
-                0,
-                DEFAULT_DISMISS_ON_DEVICE_LOCK,
-                dialogManager,
-                sysuiState,
-                fakeBroadcastDispatcher,
-                mDialogTransitionAnimator,
-                fontScalingDialogDelegate
-            )
-
-        whenever(dialogFactory.create(any(), any())).thenReturn(dialog)
-    }
-
-    @Test
-    fun showTheDialog_seekbarIsShowingCorrectProgress() {
-        dialog.show()
-
-        val seekBar: SeekBar = dialog.findViewById<SeekBar>(R.id.seekbar)!!
-        val progress: Int = seekBar.getProgress()
-        val currentScale =
-            systemSettings.getFloatForUser(
-                Settings.System.FONT_SCALE,
-                /* def= */ 1.0f,
-                userTracker.userId
-            )
-
-        assertThat(currentScale).isEqualTo(fontSizeValueArray[progress].toFloat())
-
-        dialog.dismiss()
-    }
-
-    @Test
-    fun progressIsZero_clickIconEnd_seekBarProgressIncreaseOne_fontSizeScaled() {
-        dialog.show()
-
-        val iconEndFrame: ViewGroup = dialog.findViewById(R.id.icon_end_frame)!!
-        val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
-            dialog.findViewById(R.id.font_scaling_slider)!!
-        val seekBar: SeekBar = dialog.findViewById(R.id.seekbar)!!
-
-        seekBarWithIconButtonsView.setProgress(0)
-        backgroundDelayableExecutor.runAllReady()
-        backgroundDelayableExecutor.advanceClockToNext()
-        backgroundDelayableExecutor.runAllReady()
-
-        iconEndFrame.performClick()
-        backgroundDelayableExecutor.runAllReady()
-        backgroundDelayableExecutor.advanceClockToNext()
-        backgroundDelayableExecutor.runAllReady()
-
-        val currentScale =
-            systemSettings.getFloatForUser(
-                Settings.System.FONT_SCALE,
-                /* def= */ 1.0f,
-                userTracker.userId
-            )
-        assertThat(seekBar.getProgress()).isEqualTo(1)
-        assertThat(currentScale).isEqualTo(fontSizeValueArray[1].toFloat())
-
-        dialog.dismiss()
-    }
-
-    @Test
-    fun progressIsMax_clickIconStart_seekBarProgressDecreaseOne_fontSizeScaled() {
-        dialog.show()
-
-        val iconStartFrame: ViewGroup = dialog.findViewById(R.id.icon_start_frame)!!
-        val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
-            dialog.findViewById(R.id.font_scaling_slider)!!
-        val seekBar: SeekBar = dialog.findViewById(R.id.seekbar)!!
-
-        seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1)
-        backgroundDelayableExecutor.runAllReady()
-        backgroundDelayableExecutor.advanceClockToNext()
-        backgroundDelayableExecutor.runAllReady()
-
-        iconStartFrame.performClick()
-        backgroundDelayableExecutor.runAllReady()
-        backgroundDelayableExecutor.advanceClockToNext()
-        backgroundDelayableExecutor.runAllReady()
-
-        val currentScale =
-            systemSettings.getFloatForUser(
-                Settings.System.FONT_SCALE,
-                /* def= */ 1.0f,
-                userTracker.userId
-            )
-        assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
-        assertThat(currentScale)
-            .isEqualTo(fontSizeValueArray[fontSizeValueArray.size - 2].toFloat())
-
-        dialog.dismiss()
-    }
-
-    @Test
-    fun progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOn() {
-        dialog.show()
-
-        val iconStartFrame: ViewGroup = dialog.findViewById(R.id.icon_start_frame)!!
-        secureSettings.putIntForUser(
-            Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
-            OFF,
-            userTracker.userId
-        )
-
-        // Default seekbar progress for font size is 1, click start icon to decrease the progress
-        iconStartFrame.performClick()
-        backgroundDelayableExecutor.runAllReady()
-        backgroundDelayableExecutor.advanceClockToNext()
-        backgroundDelayableExecutor.runAllReady()
-
-        val currentSettings =
-            secureSettings.getIntForUser(
-                Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
-                /* def = */ OFF,
-                userTracker.userId
-            )
-        assertThat(currentSettings).isEqualTo(ON)
-
-        dialog.dismiss()
-    }
-
-    @Test
-    fun dragSeekbar_systemFontSizeSettingsDoesNotChange() {
-        dialog.show()
-
-        val slider = dialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)!!
-        val changeListener = slider.onSeekBarWithIconButtonsChangeListener
-
-        val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
-
-        // Default seekbar progress for font size is 1, simulate dragging to 0 without
-        // releasing the finger.
-        changeListener.onStartTrackingTouch(seekBar)
-        // Update seekbar progress. This will trigger onProgressChanged in the
-        // OnSeekBarChangeListener and the seekbar could get updated progress value
-        // in onStopTrackingTouch.
-        seekBar.progress = 0
-        backgroundDelayableExecutor.runAllReady()
-        backgroundDelayableExecutor.advanceClockToNext()
-        backgroundDelayableExecutor.runAllReady()
-
-        // Verify that the scale of font size remains the default value 1.0f.
-        var systemScale =
-            systemSettings.getFloatForUser(
-                Settings.System.FONT_SCALE,
-                /* def= */ 1.0f,
-                userTracker.userId
-            )
-        assertThat(systemScale).isEqualTo(1.0f)
-
-        // Simulate releasing the finger from the seekbar.
-        changeListener.onStopTrackingTouch(seekBar)
-        backgroundDelayableExecutor.runAllReady()
-        backgroundDelayableExecutor.advanceClockToNext()
-        backgroundDelayableExecutor.runAllReady()
-
-        // SeekBar interaction is finalized.
-        changeListener.onUserInteractionFinalized(
-            seekBar,
-            OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER
-        )
-        backgroundDelayableExecutor.runAllReady()
-        backgroundDelayableExecutor.advanceClockToNext()
-        backgroundDelayableExecutor.runAllReady()
-
-        // Verify that the scale of font size has been updated.
-        systemScale =
-            systemSettings.getFloatForUser(
-                Settings.System.FONT_SCALE,
-                /* def= */ 1.0f,
-                userTracker.userId
-            )
-        assertThat(systemScale).isEqualTo(fontSizeValueArray[0].toFloat())
-
-        dialog.dismiss()
-    }
-
-    @Test
-    fun dragSeekBar_createTextPreview() {
-        dialog.show()
-        val slider = dialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)!!
-        val changeListener = slider.onSeekBarWithIconButtonsChangeListener
-
-        val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
-
-        // Default seekbar progress for font size is 1, simulate dragging to 0 without
-        // releasing the finger
-        changeListener.onStartTrackingTouch(seekBar)
-        changeListener.onProgressChanged(seekBar, /* progress= */ 0, /* fromUser= */ false)
-        backgroundDelayableExecutor.advanceClockToNext()
-        backgroundDelayableExecutor.runAllReady()
-
-        verify(fontScalingDialogDelegate).createTextPreview(/* index= */ 0)
-        dialog.dismiss()
-    }
-
-    @Test
-    fun changeFontSize_buttonIsDisabledBeforeFontSizeChangeFinishes() {
-        dialog.show()
-
-        val iconEndFrame: ViewGroup = dialog.findViewById(R.id.icon_end_frame)!!
-        val doneButton: Button = dialog.findViewById(com.android.internal.R.id.button1)!!
-
-        iconEndFrame.performClick()
-        backgroundDelayableExecutor.runAllReady()
-        backgroundDelayableExecutor.advanceClockToNext()
-        backgroundDelayableExecutor.runAllReady()
-
-        // Verify that the button is disabled before receiving onConfigurationChanged
-        assertThat(doneButton.isEnabled).isFalse()
-
-        val config = Configuration()
-        config.fontScale = 1.15f
-        dialog.onConfigurationChanged(config)
-        testableLooper.processAllMessages()
-        assertThat(doneButton.isEnabled).isTrue()
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
new file mode 100644
index 0000000..73efea7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
@@ -0,0 +1,320 @@
+/*
+ * 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.accessibility.fontscaling
+
+import android.content.res.Configuration
+import android.os.Handler
+import android.provider.Settings
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.SeekBar
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.model.SysUiState
+import com.android.systemui.res.R
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SystemSettings
+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.any
+import org.mockito.ArgumentMatchers.anyBoolean
+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
+
+private const val ON: Int = 1
+private const val OFF: Int = 0
+
+/** Tests for [FontScalingDialogDelegate]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class FontScalingDialogDelegateTest : SysuiTestCase() {
+    private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate
+    private lateinit var dialog: SystemUIDialog
+    private lateinit var systemSettings: SystemSettings
+    private lateinit var secureSettings: SecureSettings
+    private lateinit var systemClock: FakeSystemClock
+    private lateinit var backgroundDelayableExecutor: FakeExecutor
+    private lateinit var testableLooper: TestableLooper
+    private val fontSizeValueArray: Array<String> =
+        mContext
+            .getResources()
+            .getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
+
+    @Mock private lateinit var dialogManager: SystemUIDialogManager
+    @Mock private lateinit var dialogFactory: SystemUIDialog.Factory
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var sysuiState: SysUiState
+    @Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+        val mainHandler = Handler(testableLooper.looper)
+        systemSettings = FakeSettings()
+        // Guarantee that the systemSettings always starts with the default font scale.
+        systemSettings.putFloatForUser(Settings.System.FONT_SCALE, 1.0f, userTracker.userId)
+        secureSettings = FakeSettings()
+        systemClock = FakeSystemClock()
+        backgroundDelayableExecutor = FakeExecutor(systemClock)
+        whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
+
+        fontScalingDialogDelegate =
+            spy(
+                FontScalingDialogDelegate(
+                    mContext,
+                    dialogFactory,
+                    LayoutInflater.from(mContext),
+                    systemSettings,
+                    secureSettings,
+                    systemClock,
+                    userTracker,
+                    mainHandler,
+                    backgroundDelayableExecutor,
+                )
+            )
+
+        dialog =
+            SystemUIDialog(
+                mContext,
+                0,
+                DEFAULT_DISMISS_ON_DEVICE_LOCK,
+                dialogManager,
+                sysuiState,
+                fakeBroadcastDispatcher,
+                mDialogTransitionAnimator,
+                fontScalingDialogDelegate,
+            )
+
+        whenever(dialogFactory.create(any(), any())).thenReturn(dialog)
+    }
+
+    @Test
+    fun showTheDialog_seekbarIsShowingCorrectProgress() {
+        dialog.show()
+
+        val seekBar: SeekBar = dialog.findViewById<SeekBar>(R.id.seekbar)!!
+        val progress: Int = seekBar.getProgress()
+        val currentScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId,
+            )
+
+        assertThat(currentScale).isEqualTo(fontSizeValueArray[progress].toFloat())
+
+        dialog.dismiss()
+    }
+
+    @Test
+    fun progressIsZero_clickIconEnd_seekBarProgressIncreaseOne_fontSizeScaled() {
+        dialog.show()
+
+        val iconEndFrame: ViewGroup = dialog.findViewById(R.id.icon_end_frame)!!
+        val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
+            dialog.findViewById(R.id.font_scaling_slider)!!
+        val seekBar: SeekBar = dialog.findViewById(R.id.seekbar)!!
+
+        seekBarWithIconButtonsView.setProgress(0)
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
+        backgroundDelayableExecutor.runAllReady()
+
+        iconEndFrame.performClick()
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
+        backgroundDelayableExecutor.runAllReady()
+
+        val currentScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId,
+            )
+        assertThat(seekBar.getProgress()).isEqualTo(1)
+        assertThat(currentScale).isEqualTo(fontSizeValueArray[1].toFloat())
+
+        dialog.dismiss()
+    }
+
+    @Test
+    fun progressIsMax_clickIconStart_seekBarProgressDecreaseOne_fontSizeScaled() {
+        dialog.show()
+
+        val iconStartFrame: ViewGroup = dialog.findViewById(R.id.icon_start_frame)!!
+        val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
+            dialog.findViewById(R.id.font_scaling_slider)!!
+        val seekBar: SeekBar = dialog.findViewById(R.id.seekbar)!!
+
+        seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1)
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
+        backgroundDelayableExecutor.runAllReady()
+
+        iconStartFrame.performClick()
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
+        backgroundDelayableExecutor.runAllReady()
+
+        val currentScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId,
+            )
+        assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
+        assertThat(currentScale)
+            .isEqualTo(fontSizeValueArray[fontSizeValueArray.size - 2].toFloat())
+
+        dialog.dismiss()
+    }
+
+    @Test
+    fun progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOn() {
+        dialog.show()
+
+        val iconStartFrame: ViewGroup = dialog.findViewById(R.id.icon_start_frame)!!
+        secureSettings.putIntForUser(
+            Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
+            OFF,
+            userTracker.userId,
+        )
+
+        // Default seekbar progress for font size is 1, click start icon to decrease the progress
+        iconStartFrame.performClick()
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
+        backgroundDelayableExecutor.runAllReady()
+
+        val currentSettings =
+            secureSettings.getIntForUser(
+                Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
+                /* def = */ OFF,
+                userTracker.userId,
+            )
+        assertThat(currentSettings).isEqualTo(ON)
+
+        dialog.dismiss()
+    }
+
+    @Test
+    fun dragSeekbar_systemFontSizeSettingsDoesNotChange() {
+        dialog.show()
+
+        val slider = dialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)!!
+        val seekBarListener = slider.getSeekBarChangeListener()
+
+        val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
+
+        // Default seekbar progress for font size is 1, simulate dragging to 0 without
+        // releasing the finger.
+        seekBarListener.onStartTrackingTouch(seekBar)
+        // Update seekbar progress. This will trigger onProgressChanged in the
+        // OnSeekBarChangeListener and the seekbar could get updated progress value
+        // in onStopTrackingTouch.
+        seekBar.progress = 0
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
+        backgroundDelayableExecutor.runAllReady()
+
+        // Verify that the scale of font size remains the default value 1.0f.
+        var systemScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId,
+            )
+        assertThat(systemScale).isEqualTo(1.0f)
+
+        // Simulate releasing the finger from the seekbar.
+        seekBarListener.onStopTrackingTouch(seekBar)
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
+        backgroundDelayableExecutor.runAllReady()
+
+        // Verify that the scale of font size has been updated.
+        systemScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId,
+            )
+        assertThat(systemScale).isEqualTo(fontSizeValueArray[0].toFloat())
+
+        dialog.dismiss()
+    }
+
+    @Test
+    fun dragSeekBar_createTextPreview() {
+        dialog.show()
+        val slider = dialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)!!
+        val changeListener = slider.onSeekBarWithIconButtonsChangeListener
+
+        val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
+
+        // Default seekbar progress for font size is 1, simulate dragging to 0 without
+        // releasing the finger
+        changeListener.onStartTrackingTouch(seekBar)
+        changeListener.onProgressChanged(seekBar, /* progress= */ 0, /* fromUser= */ false)
+        backgroundDelayableExecutor.advanceClockToNext()
+        backgroundDelayableExecutor.runAllReady()
+
+        verify(fontScalingDialogDelegate).createTextPreview(/* index= */ 0)
+        dialog.dismiss()
+    }
+
+    @Test
+    fun changeFontSize_buttonIsDisabledBeforeFontSizeChangeFinishes() {
+        dialog.show()
+
+        val iconEndFrame: ViewGroup = dialog.findViewById(R.id.icon_end_frame)!!
+        val doneButton: Button = dialog.findViewById(com.android.internal.R.id.button1)!!
+
+        iconEndFrame.performClick()
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
+        backgroundDelayableExecutor.runAllReady()
+
+        // Verify that the button is disabled before receiving onConfigurationChanged
+        assertThat(doneButton.isEnabled).isFalse()
+
+        val config = Configuration()
+        config.fontScale = 1.15f
+        dialog.onConfigurationChanged(config)
+        testableLooper.processAllMessages()
+        assertThat(doneButton.isEnabled).isTrue()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt
new file mode 100644
index 0000000..d6ba98d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt
@@ -0,0 +1,143 @@
+/*
+ * 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.activity.data.repository
+
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING
+import android.app.activityManager
+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.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ActivityManagerRepositoryTest : SysuiTestCase() {
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val logger = Logger(logcatLogBuffer("ActivityManagerRepositoryTest"), "tag")
+
+    private val Kosmos.underTest by Kosmos.Fixture { realActivityManagerRepository }
+
+    @Test
+    fun createIsAppVisibleFlow_fetchesInitialValue_true() =
+        kosmos.runTest {
+            whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_FOREGROUND)
+
+            val latest by
+                collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun createIsAppVisibleFlow_fetchesInitialValue_false() =
+        kosmos.runTest {
+            whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_GONE)
+
+            val latest by
+                collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun createIsAppVisibleFlow_getsImportanceUpdates() =
+        kosmos.runTest {
+            val latest by
+                collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+            val listenerCaptor = argumentCaptor<ActivityManager.OnUidImportanceListener>()
+            verify(activityManager).addOnUidImportanceListener(listenerCaptor.capture(), any())
+            val listener = listenerCaptor.firstValue
+
+            listener.onUidImportance(THIS_UID, IMPORTANCE_GONE)
+            assertThat(latest).isFalse()
+
+            listener.onUidImportance(THIS_UID, IMPORTANCE_FOREGROUND)
+            assertThat(latest).isTrue()
+
+            listener.onUidImportance(THIS_UID, IMPORTANCE_TOP_SLEEPING)
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun createIsAppVisibleFlow_ignoresUpdatesForOtherUids() =
+        kosmos.runTest {
+            val latest by
+                collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+            val listenerCaptor = argumentCaptor<ActivityManager.OnUidImportanceListener>()
+            verify(activityManager).addOnUidImportanceListener(listenerCaptor.capture(), any())
+            val listener = listenerCaptor.firstValue
+
+            listener.onUidImportance(THIS_UID, IMPORTANCE_GONE)
+            assertThat(latest).isFalse()
+
+            // WHEN another UID becomes foreground
+            listener.onUidImportance(THIS_UID + 2, IMPORTANCE_FOREGROUND)
+
+            // THEN this UID still stays not visible
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun createIsAppVisibleFlow_securityExceptionOnUidRegistration_ok() =
+        kosmos.runTest {
+            whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_GONE)
+            whenever(activityManager.addOnUidImportanceListener(any(), any()))
+                .thenThrow(SecurityException())
+
+            val latest by
+                collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+            // Verify no crash, and we get a value emitted
+            assertThat(latest).isFalse()
+        }
+
+    /** Regression test for b/216248574. */
+    @Test
+    fun createIsAppVisibleFlow_getUidImportanceThrowsException_ok() =
+        kosmos.runTest {
+            whenever(activityManager.getUidImportance(any())).thenThrow(SecurityException())
+
+            val latest by
+                collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+            // Verify no crash, and we get a value emitted
+            assertThat(latest).isFalse()
+        }
+
+    companion object {
+        private const val THIS_UID = 558
+        private const val LOG_TAG = "LogTag"
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index cdda9cc..41cc6ee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -48,8 +48,8 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
new file mode 100644
index 0000000..b8d4bb4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -0,0 +1,737 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.biometrics
+
+import android.app.ActivityTaskManager
+import android.app.admin.DevicePolicyManager
+import android.content.pm.PackageManager
+import android.content.res.Configuration
+import android.hardware.biometrics.BiometricAuthenticator
+import android.hardware.biometrics.BiometricConstants
+import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
+import android.hardware.biometrics.PromptInfo
+import android.hardware.biometrics.PromptVerticalListContentView
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.os.IBinder
+import android.os.UserManager
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.View
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.widget.ScrollView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCapture
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.widget.LockPatternUtils
+import com.android.launcher3.icons.IconProvider
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
+import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.haptics.msdl.msdlPlayer
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.testKosmos
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+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
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.eq
+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
+
+private const val OP_PACKAGE_NAME = "biometric.testapp"
+
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+open class AuthContainerViewTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    @Mock lateinit var callback: AuthDialogCallback
+    @Mock lateinit var userManager: UserManager
+    @Mock lateinit var fingerprintManager: FingerprintManager
+    @Mock lateinit var lockPatternUtils: LockPatternUtils
+    @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock lateinit var windowToken: IBinder
+    @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
+    @Mock lateinit var vibrator: VibratorHelper
+    @Mock lateinit var udfpsUtils: UdfpsUtils
+    @Mock lateinit var authController: AuthController
+    @Mock lateinit var selectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var packageManager: PackageManager
+    @Mock private lateinit var activityTaskManager: ActivityTaskManager
+    @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture>
+
+    private lateinit var displayRepository: FakeDisplayRepository
+    private lateinit var displayStateInteractor: DisplayStateInteractor
+    private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
+    private lateinit var biometricStatusInteractor: BiometricStatusInteractor
+    private lateinit var iconProvider: IconProvider
+
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
+    private val biometricPromptRepository = FakePromptRepository()
+    private val biometricStatusRepository = FakeBiometricStatusRepository()
+    private val fingerprintRepository = FakeFingerprintPropertyRepository()
+    private val displayStateRepository = FakeDisplayStateRepository()
+    private val credentialInteractor = FakeCredentialInteractor()
+    private val bpCredentialInteractor =
+        PromptCredentialInteractor(
+            Dispatchers.Main.immediate,
+            biometricPromptRepository,
+            credentialInteractor,
+        )
+    private val promptSelectorInteractor by lazy {
+        PromptSelectorInteractorImpl(
+            fingerprintRepository,
+            displayStateInteractor,
+            credentialInteractor,
+            biometricPromptRepository,
+            lockPatternUtils,
+        )
+    }
+
+    private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
+    private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
+
+    private val kosmos = testKosmos()
+    private val msdlPlayer = kosmos.msdlPlayer
+
+    private var authContainer: TestAuthContainerView? = null
+
+    @Before
+    fun setup() {
+        displayRepository = FakeDisplayRepository()
+
+        displayStateInteractor =
+            DisplayStateInteractorImpl(
+                testScope.backgroundScope,
+                mContext,
+                fakeExecutor,
+                displayStateRepository,
+                displayRepository,
+            )
+        udfpsOverlayInteractor =
+            UdfpsOverlayInteractor(
+                context,
+                authController,
+                selectedUserInteractor,
+                fingerprintManager,
+                testScope.backgroundScope,
+            )
+        biometricStatusInteractor =
+            BiometricStatusInteractorImpl(
+                activityTaskManager,
+                biometricStatusRepository,
+                fingerprintRepository,
+            )
+        iconProvider = IconProvider(context)
+        // Set up default logo icon
+        whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
+        context.setMockPackageManager(packageManager)
+    }
+
+    @After
+    fun tearDown() {
+        if (authContainer?.isAttachedToWindow == true) {
+            ViewUtils.detachView(authContainer)
+        }
+    }
+
+    @Test
+    fun testNotifiesAnimatedIn() {
+        initializeFingerprintContainer()
+        verify(callback)
+            .onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+    }
+
+    @Test
+    fun testDismissesOnBack() {
+        val container = initializeFingerprintContainer(addToView = true)
+        assertThat(container.parent).isNotNull()
+        val root = container.rootView
+
+        // Simulate back invocation
+        container.onBackInvoked()
+        waitForIdleSync()
+
+        assertThat(container.parent).isNull()
+        assertThat(root.isAttachedToWindow).isFalse()
+    }
+
+    @Test
+    fun testDimissOnLock() {
+        val container = initializeFingerprintContainer(addToView = true)
+        assertThat(container.parent).isNotNull()
+        val root = container.rootView
+
+        // Simulate sleep/lock invocation
+        container.onStartedGoingToSleep()
+        waitForIdleSync()
+
+        assertThat(container.parent).isNull()
+        assertThat(root.isAttachedToWindow).isFalse()
+    }
+
+    @Test
+    fun testCredentialPasswordDismissesOnBack() {
+        val container = initializeCredentialPasswordContainer(addToView = true)
+        assertThat(container.parent).isNotNull()
+        val root = container.rootView
+
+        // Simulate back invocation
+        container.onBackInvoked()
+        waitForIdleSync()
+
+        assertThat(container.parent).isNull()
+        assertThat(root.isAttachedToWindow).isFalse()
+    }
+
+    @Test
+    fun testIgnoresAnimatedInWhenDismissed() {
+        val container = initializeFingerprintContainer(addToView = false)
+        container.dismissFromSystemServer()
+        waitForIdleSync()
+
+        verify(callback, never()).onDialogAnimatedIn(anyLong(), anyBoolean())
+
+        container.addToView()
+        waitForIdleSync()
+
+        // attaching the view resets the state and allows this to happen again
+        verify(callback)
+            .onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+    }
+
+    @Test
+    fun testIgnoresAnimatedInWhenDialogAnimatingOut() {
+        val container = initializeFingerprintContainer(addToView = false)
+        container.mContainerState = 4 // STATE_ANIMATING_OUT
+        container.addToView()
+        waitForIdleSync()
+
+        verify(callback, never()).onDialogAnimatedIn(anyLong(), anyBoolean())
+    }
+
+    @Test
+    fun testDismissBeforeIntroEnd() {
+        val container = initializeFingerprintContainer()
+        waitForIdleSync()
+
+        // STATE_ANIMATING_IN = 1
+        container?.mContainerState = 1
+
+        container.dismissWithoutCallback(false)
+
+        // the first time is triggered by initializeFingerprintContainer()
+        // the second time was triggered by dismissWithoutCallback()
+        verify(callback, times(2))
+            .onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+    }
+
+    @Test
+    fun testActionAuthenticated_sendsDismissedAuthenticated() {
+        val container = initializeFingerprintContainer()
+        container.mBiometricCallback.onAuthenticated()
+        waitForIdleSync()
+
+        verify(callback)
+            .onDismissed(
+                eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED),
+                eq<ByteArray?>(null), /* credentialAttestation */
+                eq(authContainer?.requestId ?: 0L),
+            )
+        assertThat(container.parent).isNull()
+    }
+
+    @Test
+    fun testActionUserCanceled_sendsDismissedUserCanceled() {
+        val container = initializeFingerprintContainer()
+        container.mBiometricCallback.onUserCanceled()
+        waitForIdleSync()
+
+        verify(callback)
+            .onSystemEvent(
+                eq(BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL),
+                eq(authContainer?.requestId ?: 0L),
+            )
+        verify(callback)
+            .onDismissed(
+                eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+                eq<ByteArray?>(null), /* credentialAttestation */
+                eq(authContainer?.requestId ?: 0L),
+            )
+        assertThat(container.parent).isNull()
+    }
+
+    @Test
+    fun testActionButtonNegative_sendsDismissedButtonNegative() {
+        val container = initializeFingerprintContainer()
+        container.mBiometricCallback.onButtonNegative()
+        waitForIdleSync()
+
+        verify(callback)
+            .onDismissed(
+                eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE),
+                eq<ByteArray?>(null), /* credentialAttestation */
+                eq(authContainer?.requestId ?: 0L),
+            )
+        assertThat(container.parent).isNull()
+    }
+
+    @Test
+    fun testActionTryAgain_sendsTryAgain() {
+        val container =
+            initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
+            )
+        container.mBiometricCallback.onButtonTryAgain()
+        waitForIdleSync()
+
+        verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L)
+    }
+
+    @Test
+    fun testActionError_sendsDismissedError() {
+        val container = initializeFingerprintContainer()
+        container.mBiometricCallback.onError()
+        waitForIdleSync()
+
+        verify(callback)
+            .onDismissed(
+                eq(AuthDialogCallback.DISMISSED_ERROR),
+                eq<ByteArray?>(null), /* credentialAttestation */
+                eq(authContainer?.requestId ?: 0L),
+            )
+        assertThat(authContainer!!.parent).isNull()
+    }
+
+    @Ignore("b/279650412")
+    @Test
+    fun testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
+        val container =
+            initializeFingerprintContainer(
+                authenticators =
+                    BiometricManager.Authenticators.BIOMETRIC_WEAK or
+                        BiometricManager.Authenticators.DEVICE_CREDENTIAL
+            )
+        container.mBiometricCallback.onUseDeviceCredential()
+        waitForIdleSync()
+
+        verify(callback).onDeviceCredentialPressed(authContainer?.requestId ?: 0L)
+        assertThat(container.hasCredentialView()).isTrue()
+    }
+
+    @Test
+    fun testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
+        val container =
+            initializeFingerprintContainer(
+                authenticators =
+                    BiometricManager.Authenticators.BIOMETRIC_WEAK or
+                        BiometricManager.Authenticators.DEVICE_CREDENTIAL
+            )
+        container.animateToCredentialUI(false)
+        waitForIdleSync()
+
+        assertThat(container.hasCredentialView()).isTrue()
+        assertThat(container.hasBiometricPrompt()).isFalse()
+
+        // Check credential view persists after new attachment
+        container.onAttachedToWindow()
+
+        assertThat(container.hasCredentialView()).isTrue()
+        assertThat(container.hasBiometricPrompt()).isFalse()
+    }
+
+    @Test
+    fun testAnimateToCredentialUI_rotateCredentialUI() {
+        val container =
+            initializeFingerprintContainer(
+                authenticators =
+                    BiometricManager.Authenticators.BIOMETRIC_WEAK or
+                        BiometricManager.Authenticators.DEVICE_CREDENTIAL
+            )
+        container.animateToCredentialUI(false)
+        waitForIdleSync()
+
+        assertThat(container.hasCredentialView()).isTrue()
+        assertThat(container.hasBiometricPrompt()).isFalse()
+
+        // Check credential view persists after new attachment
+        container.onAttachedToWindow()
+
+        assertThat(container.hasCredentialView()).isTrue()
+        assertThat(container.hasBiometricPrompt()).isFalse()
+
+        val configuration = Configuration(context.resources.configuration)
+        configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+        container.dispatchConfigurationChanged(configuration)
+        waitForIdleSync()
+
+        assertThat(container.hasCredentialView()).isTrue()
+        assertThat(container.hasBiometricPrompt()).isFalse()
+    }
+
+    @Test
+    fun testShowBiometricUI_ContentViewWithMoreOptionsButton() {
+        var isButtonClicked = false
+        val contentView =
+            PromptContentViewWithMoreOptionsButton.Builder()
+                .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> isButtonClicked = true }
+                .build()
+
+        val container =
+            initializeFingerprintContainer(contentViewWithMoreOptionsButton = contentView)
+
+        waitForIdleSync()
+
+        assertThat(container.hasCredentialView()).isFalse()
+        assertThat(container.hasConstraintBiometricPrompt()).isTrue()
+
+        // TODO(b/328843028): Use button.performClick() instead of calling
+        //  onContentViewMoreOptionsButtonPressed() directly, and check |isButtonClicked| is true.
+        container.mBiometricCallback.onContentViewMoreOptionsButtonPressed()
+        waitForIdleSync()
+        // container is gone
+        assertThat(container.mContainerState).isEqualTo(5)
+    }
+
+    @Test
+    fun testShowCredentialUI_withDescription() {
+        val container =
+            initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
+            )
+        waitForIdleSync()
+
+        assertThat(container.hasCredentialView()).isTrue()
+        assertThat(container.hasBiometricPrompt()).isFalse()
+    }
+
+    @Test
+    fun testShowCredentialUI_withVerticalListContentView() {
+        val container =
+            initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
+                verticalListContentView = PromptVerticalListContentView.Builder().build(),
+            )
+        // Two-step credential view should show -
+        // 1. biometric prompt without sensor 2. credential view ui
+        waitForIdleSync()
+        assertThat(container.hasConstraintBiometricPrompt()).isTrue()
+        assertThat(container.hasCredentialView()).isFalse()
+
+        container.animateToCredentialUI(false)
+        waitForIdleSync()
+        // TODO(b/302735104): Check the reason why hasConstraintBiometricPrompt() is still true
+        // assertThat(container.hasConstraintBiometricPrompt()).isFalse()
+        assertThat(container.hasCredentialView()).isTrue()
+    }
+
+    @Test
+    fun testShowCredentialUI_withContentViewWithMoreOptionsButton() {
+        val contentView =
+            PromptContentViewWithMoreOptionsButton.Builder()
+                .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> }
+                .build()
+        val container =
+            initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
+                contentViewWithMoreOptionsButton = contentView,
+            )
+        waitForIdleSync()
+
+        assertThat(container.hasCredentialView()).isTrue()
+        assertThat(container.hasBiometricPrompt()).isFalse()
+    }
+
+    @Test
+    fun testCredentialViewUsesEffectiveUserId() {
+        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(200)
+        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(200)))
+            .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING)
+
+        val container =
+            initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
+            )
+        waitForIdleSync()
+
+        assertThat(container.hasCredentialPatternView()).isTrue()
+        assertThat(container.hasBiometricPrompt()).isFalse()
+    }
+
+    @Test
+    fun testCredentialUI_disablesClickingOnBackground() {
+        val container = initializeCredentialPasswordContainer()
+        assertThat(container.hasBiometricPrompt()).isFalse()
+        assertThat(container.findViewById<View>(R.id.background)?.isImportantForAccessibility)
+            .isFalse()
+
+        container.findViewById<View>(R.id.background)?.performClick()
+        waitForIdleSync()
+
+        assertThat(container.hasCredentialPasswordView()).isTrue()
+        assertThat(container.hasBiometricPrompt()).isFalse()
+    }
+
+    @Test
+    fun testLayoutParams_hasSecureWindowFlag() {
+        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+        assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SECURE) != 0).isTrue()
+    }
+
+    @Test
+    fun testLayoutParams_hasShowWhenLockedFlag() {
+        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+        assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) != 0)
+            .isTrue()
+    }
+
+    @Test
+    fun testLayoutParams_hasDimbehindWindowFlag() {
+        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+        val lpFlags = layoutParams.flags
+        val lpDimAmount = layoutParams.dimAmount
+
+        assertThat((lpFlags and WindowManager.LayoutParams.FLAG_DIM_BEHIND) != 0).isTrue()
+        assertThat(lpDimAmount).isGreaterThan(0f)
+    }
+
+    @Test
+    fun testLayoutParams_excludesImeInsets() {
+        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+        assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.ime()) == 0).isTrue()
+    }
+
+    @Test
+    fun coexFaceRestartsOnTouch() {
+        val container = initializeCoexContainer()
+
+        container.onPointerDown()
+        waitForIdleSync()
+
+        container.onAuthenticationFailed(BiometricAuthenticator.TYPE_FACE, "failed")
+        waitForIdleSync()
+
+        verify(callback, never()).onTryAgainPressed(anyLong())
+
+        container.onPointerDown()
+        waitForIdleSync()
+
+        verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L)
+    }
+
+    private fun initializeCredentialPasswordContainer(
+        addToView: Boolean = true
+    ): TestAuthContainerView {
+        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
+        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20)))
+            .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC)
+
+        // In the credential view, clicking on the background (to cancel authentication) is not
+        // valid. Thus, the listener should be null, and it should not be in the accessibility
+        // hierarchy.
+        val container =
+            initializeFingerprintContainer(
+                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
+                addToView = addToView,
+            )
+        waitForIdleSync()
+
+        assertThat(container.hasCredentialPasswordView()).isTrue()
+        return container
+    }
+
+    private fun initializeFingerprintContainer(
+        authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
+        addToView: Boolean = true,
+        verticalListContentView: PromptVerticalListContentView? = null,
+        contentViewWithMoreOptionsButton: PromptContentViewWithMoreOptionsButton? = null,
+    ) =
+        initializeContainer(
+            TestAuthContainerView(
+                authenticators = authenticators,
+                fingerprintProps = fingerprintSensorPropertiesInternal(),
+                verticalListContentView = verticalListContentView,
+            ),
+            addToView,
+        )
+
+    private fun initializeCoexContainer(
+        authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
+        addToView: Boolean = true,
+    ) =
+        initializeContainer(
+            TestAuthContainerView(
+                authenticators = authenticators,
+                fingerprintProps = fingerprintSensorPropertiesInternal(),
+                faceProps = faceSensorPropertiesInternal(),
+            ),
+            addToView,
+        )
+
+    private fun initializeContainer(
+        view: TestAuthContainerView,
+        addToView: Boolean,
+    ): TestAuthContainerView {
+        authContainer = view
+
+        if (addToView) {
+            authContainer!!.addToView()
+        }
+
+        return authContainer!!
+    }
+
+    private inner class TestAuthContainerView(
+        authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
+        fingerprintProps: List<FingerprintSensorPropertiesInternal> = listOf(),
+        faceProps: List<FaceSensorPropertiesInternal> = listOf(),
+        verticalListContentView: PromptVerticalListContentView? = null,
+        contentViewWithMoreOptionsButton: PromptContentViewWithMoreOptionsButton? = null,
+    ) :
+        AuthContainerView(
+            Config().apply {
+                mContext = this@AuthContainerViewTest.context
+                mCallback = callback
+                mSensorIds =
+                    (fingerprintProps.map { it.sensorId } + faceProps.map { it.sensorId })
+                        .toIntArray()
+                mSkipAnimation = true
+                mPromptInfo =
+                    PromptInfo().apply {
+                        this.authenticators = authenticators
+                        if (verticalListContentView != null) {
+                            this.contentView = verticalListContentView
+                        } else if (contentViewWithMoreOptionsButton != null) {
+                            this.contentView = contentViewWithMoreOptionsButton
+                        }
+                    }
+                mOpPackageName = OP_PACKAGE_NAME
+            },
+            testScope.backgroundScope,
+            fingerprintProps,
+            faceProps,
+            wakefulnessLifecycle,
+            userManager,
+            null /* authContextPlugins */,
+            lockPatternUtils,
+            interactionJankMonitor,
+            { promptSelectorInteractor },
+            PromptViewModel(
+                displayStateInteractor,
+                promptSelectorInteractor,
+                context,
+                udfpsOverlayInteractor,
+                biometricStatusInteractor,
+                udfpsUtils,
+                iconProvider,
+                activityTaskManager,
+            ),
+            { credentialViewModel },
+            fakeExecutor,
+            vibrator,
+            lazyViewCapture,
+            msdlPlayer,
+        ) {
+        override fun postOnAnimation(runnable: Runnable) {
+            runnable.run()
+        }
+    }
+
+    override fun waitForIdleSync() {
+        testScope.runCurrent()
+        TestableLooper.get(this).processAllMessages()
+    }
+
+    private fun AuthContainerView.addToView() {
+        ViewUtils.attachView(this)
+        waitForIdleSync()
+        assertThat(isAttachedToWindow()).isTrue()
+    }
+
+    @Test
+    fun testLayoutParams_hasCutoutModeAlwaysFlag() {
+        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+        val lpFlags = layoutParams.flags
+
+        assertThat(
+                (lpFlags and WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) != 0
+            )
+            .isTrue()
+    }
+
+    @Test
+    fun testLayoutParams_excludesSystemBarInsets() {
+        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+        assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.systemBars()) == 0).isTrue()
+    }
+}
+
+private fun AuthContainerView.hasConstraintBiometricPrompt() =
+    (findViewById<ConstraintLayout>(R.id.biometric_prompt_constraint_layout)?.childCount ?: 0) > 0
+
+private fun AuthContainerView.hasBiometricPrompt() =
+    (findViewById<ScrollView>(R.id.biometric_scrollview)?.childCount ?: 0) > 0
+
+private fun AuthContainerView.hasCredentialView() =
+    hasCredentialPatternView() || hasCredentialPasswordView()
+
+private fun AuthContainerView.hasCredentialPatternView() =
+    findViewById<View>(R.id.lockPattern) != null
+
+private fun AuthContainerView.hasCredentialPasswordView() =
+    findViewById<View>(R.id.lockPassword) != null
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 2dcbdc8..2817f55 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -117,6 +117,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import java.util.Random;
 
 @RunWith(AndroidJUnit4.class)
@@ -1187,7 +1188,8 @@
         TestableAuthController(Context context) {
             super(context, null /* applicationCoroutineScope */,
                     mExecution, mCommandQueue, mActivityTaskManager, mWindowManager,
-                    mFingerprintManager, mFaceManager, () -> mUdfpsController, mDisplayManager,
+                    mFingerprintManager, mFaceManager, Optional.empty(),
+                    () -> mUdfpsController, mDisplayManager,
                     mWakefulnessLifecycle, mUserManager, mLockPatternUtils, () -> mUdfpsLogger,
                     () -> mLogContextInteractor, () -> mPromptSelectionInteractor,
                     () -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 21c6583..aeea99b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -365,6 +365,25 @@
     }
 
     @Test
+    public void showUdfpsOverlay_invokedTwice_doesNotNotifyListenerSecondTime() throws RemoteException {
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        verify(mFingerprintManager).onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN,
+                TEST_REQUEST_ID, mOpticalProps.sensorId);
+
+        reset(mFingerprintManager);
+
+        // Second attempt should do nothing
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+        verify(mFingerprintManager, never()).onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN,
+                TEST_REQUEST_ID, mOpticalProps.sensorId);
+    }
+
+    @Test
     public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
deleted file mode 100644
index a4653e7..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
+++ /dev/null
@@ -1,741 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.udfps
-
-import android.graphics.Rect
-import android.view.MotionEvent
-import android.view.MotionEvent.INVALID_POINTER_ID
-import android.view.MotionEvent.PointerProperties
-import android.view.Surface
-import android.view.Surface.Rotation
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
-import platform.test.runner.parameterized.Parameter
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(ParameterizedAndroidJunit4::class)
-class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() {
-    private val overlapDetector = FakeOverlapDetector()
-    private val underTest = SinglePointerTouchProcessor(overlapDetector)
-
-    @Test
-    fun processTouch() {
-        overlapDetector.shouldReturn =
-            testCase.currentPointers.associate { pointer -> pointer.id to pointer.onSensor }
-
-        val actual =
-            underTest.processTouch(
-                testCase.event,
-                testCase.previousPointerOnSensorId,
-                testCase.overlayParams,
-            )
-
-        assertThat(actual).isInstanceOf(testCase.expected.javaClass)
-        if (actual is TouchProcessorResult.ProcessedTouch) {
-            assertThat(actual).isEqualTo(testCase.expected)
-        }
-    }
-
-    data class TestCase(
-        val event: MotionEvent,
-        val currentPointers: List<TestPointer>,
-        val previousPointerOnSensorId: Int,
-        val overlayParams: UdfpsOverlayParams,
-        val expected: TouchProcessorResult,
-    ) {
-        override fun toString(): String {
-            val expectedOutput =
-                if (expected is TouchProcessorResult.ProcessedTouch) {
-                    expected.event.toString() +
-                        ", (x: ${expected.touchData.x}, y: ${expected.touchData.y})" +
-                        ", pointerOnSensorId: ${expected.pointerOnSensorId}" +
-                        ", ..."
-                } else {
-                    TouchProcessorResult.Failure().toString()
-                }
-            return "{" +
-                MotionEvent.actionToString(event.action) +
-                ", (x: ${event.x}, y: ${event.y})" +
-                ", scale: ${overlayParams.scaleFactor}" +
-                ", rotation: " +
-                Surface.rotationToString(overlayParams.rotation) +
-                ", previousPointerOnSensorId: $previousPointerOnSensorId" +
-                ", ...} expected: {$expectedOutput}"
-        }
-    }
-
-    companion object {
-        @Parameters(name = "{0}")
-        @JvmStatic
-        fun data(): List<TestCase> =
-            listOf(
-                    // MotionEvent.ACTION_DOWN
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_DOWN,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
-                        expectedInteractionEvent = InteractionEvent.DOWN,
-                        expectedPointerOnSensorId = POINTER_ID_1,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_DOWN,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
-                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_DOWN,
-                        previousPointerOnSensorId = POINTER_ID_1,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
-                        expectedInteractionEvent = InteractionEvent.UP,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    // MotionEvent.ACTION_HOVER_ENTER
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
-                        expectedInteractionEvent = InteractionEvent.DOWN,
-                        expectedPointerOnSensorId = POINTER_ID_1,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
-                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
-                        previousPointerOnSensorId = POINTER_ID_1,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
-                        expectedInteractionEvent = InteractionEvent.UP,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    // MotionEvent.ACTION_MOVE
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_MOVE,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
-                        expectedInteractionEvent = InteractionEvent.DOWN,
-                        expectedPointerOnSensorId = POINTER_ID_1,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_MOVE,
-                        previousPointerOnSensorId = POINTER_ID_1,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
-                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
-                        expectedPointerOnSensorId = POINTER_ID_1,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_MOVE,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
-                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_MOVE,
-                        previousPointerOnSensorId = POINTER_ID_1,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
-                        expectedInteractionEvent = InteractionEvent.UP,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_MOVE,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers =
-                            listOf(
-                                TestPointer(id = POINTER_ID_1, onSensor = false),
-                                TestPointer(id = POINTER_ID_2, onSensor = true)
-                            ),
-                        expectedInteractionEvent = InteractionEvent.DOWN,
-                        expectedPointerOnSensorId = POINTER_ID_2,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_MOVE,
-                        previousPointerOnSensorId = POINTER_ID_1,
-                        currentPointers =
-                            listOf(
-                                TestPointer(id = POINTER_ID_1, onSensor = false),
-                                TestPointer(id = POINTER_ID_2, onSensor = true)
-                            ),
-                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
-                        expectedPointerOnSensorId = POINTER_ID_2,
-                    ),
-                    // MotionEvent.ACTION_HOVER_MOVE
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
-                        expectedInteractionEvent = InteractionEvent.DOWN,
-                        expectedPointerOnSensorId = POINTER_ID_1,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
-                        previousPointerOnSensorId = POINTER_ID_1,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
-                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
-                        expectedPointerOnSensorId = POINTER_ID_1,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
-                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
-                        previousPointerOnSensorId = POINTER_ID_1,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
-                        expectedInteractionEvent = InteractionEvent.UP,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    // MotionEvent.ACTION_UP
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_UP,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
-                        expectedInteractionEvent = InteractionEvent.UP,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_UP,
-                        previousPointerOnSensorId = POINTER_ID_1,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
-                        expectedInteractionEvent = InteractionEvent.UP,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_UP,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
-                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    // MotionEvent.ACTION_HOVER_EXIT
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
-                        expectedInteractionEvent = InteractionEvent.UP,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
-                        previousPointerOnSensorId = POINTER_ID_1,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
-                        expectedInteractionEvent = InteractionEvent.UP,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
-                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    // MotionEvent.ACTION_CANCEL
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_CANCEL,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
-                        expectedInteractionEvent = InteractionEvent.CANCEL,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_CANCEL,
-                        previousPointerOnSensorId = POINTER_ID_1,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
-                        expectedInteractionEvent = InteractionEvent.CANCEL,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_CANCEL,
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
-                        expectedInteractionEvent = InteractionEvent.CANCEL,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_CANCEL,
-                        previousPointerOnSensorId = POINTER_ID_1,
-                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
-                        expectedInteractionEvent = InteractionEvent.CANCEL,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID,
-                    ),
-                    // MotionEvent.ACTION_POINTER_DOWN
-                    genPositiveTestCases(
-                        motionEventAction =
-                            MotionEvent.ACTION_POINTER_DOWN +
-                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers =
-                            listOf(
-                                TestPointer(id = POINTER_ID_1, onSensor = true),
-                                TestPointer(id = POINTER_ID_2, onSensor = false)
-                            ),
-                        expectedInteractionEvent = InteractionEvent.DOWN,
-                        expectedPointerOnSensorId = POINTER_ID_1,
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction =
-                            MotionEvent.ACTION_POINTER_DOWN +
-                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers =
-                            listOf(
-                                TestPointer(id = POINTER_ID_1, onSensor = false),
-                                TestPointer(id = POINTER_ID_2, onSensor = true)
-                            ),
-                        expectedInteractionEvent = InteractionEvent.DOWN,
-                        expectedPointerOnSensorId = POINTER_ID_2
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction =
-                            MotionEvent.ACTION_POINTER_DOWN +
-                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
-                        previousPointerOnSensorId = POINTER_ID_1,
-                        currentPointers =
-                            listOf(
-                                TestPointer(id = POINTER_ID_1, onSensor = true),
-                                TestPointer(id = POINTER_ID_2, onSensor = false)
-                            ),
-                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
-                        expectedPointerOnSensorId = POINTER_ID_1,
-                    ),
-                    // MotionEvent.ACTION_POINTER_UP
-                    genPositiveTestCases(
-                        motionEventAction =
-                            MotionEvent.ACTION_POINTER_UP +
-                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
-                        previousPointerOnSensorId = INVALID_POINTER_ID,
-                        currentPointers =
-                            listOf(
-                                TestPointer(id = POINTER_ID_1, onSensor = false),
-                                TestPointer(id = POINTER_ID_2, onSensor = false)
-                            ),
-                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction =
-                            MotionEvent.ACTION_POINTER_UP +
-                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
-                        previousPointerOnSensorId = POINTER_ID_2,
-                        currentPointers =
-                            listOf(
-                                TestPointer(id = POINTER_ID_1, onSensor = false),
-                                TestPointer(id = POINTER_ID_2, onSensor = true)
-                            ),
-                        expectedInteractionEvent = InteractionEvent.UP,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_POINTER_UP,
-                        previousPointerOnSensorId = POINTER_ID_1,
-                        currentPointers =
-                            listOf(
-                                TestPointer(id = POINTER_ID_1, onSensor = true),
-                                TestPointer(id = POINTER_ID_2, onSensor = false)
-                            ),
-                        expectedInteractionEvent = InteractionEvent.UP,
-                        expectedPointerOnSensorId = INVALID_POINTER_ID
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction =
-                            MotionEvent.ACTION_POINTER_UP +
-                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
-                        previousPointerOnSensorId = POINTER_ID_1,
-                        currentPointers =
-                            listOf(
-                                TestPointer(id = POINTER_ID_1, onSensor = true),
-                                TestPointer(id = POINTER_ID_2, onSensor = false)
-                            ),
-                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
-                        expectedPointerOnSensorId = POINTER_ID_1
-                    ),
-                    genPositiveTestCases(
-                        motionEventAction = MotionEvent.ACTION_POINTER_UP,
-                        previousPointerOnSensorId = POINTER_ID_2,
-                        currentPointers =
-                            listOf(
-                                TestPointer(id = POINTER_ID_1, onSensor = false),
-                                TestPointer(id = POINTER_ID_2, onSensor = true)
-                            ),
-                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
-                        expectedPointerOnSensorId = POINTER_ID_2
-                    )
-                )
-                .flatten()
-    }
-}
-
-data class TestPointer(val id: Int, val onSensor: Boolean)
-
-/* Display dimensions in native resolution and natural orientation. */
-private const val ROTATION_0_NATIVE_DISPLAY_WIDTH = 400
-private const val ROTATION_0_NATIVE_DISPLAY_HEIGHT = 600
-
-/* Placeholder touch parameters. */
-private const val POINTER_ID_1 = 42
-private const val POINTER_ID_2 = 43
-private const val NATIVE_MINOR = 2.71828f
-private const val NATIVE_MAJOR = 3.14f
-private const val ORIENTATION = 1.2345f
-private const val TIME = 12345699L
-private const val GESTURE_START = 12345600L
-
-/*
- * ROTATION_0 map:
- * _ _ _ _
- * _ _ O _
- * _ _ _ _
- * _ S _ _
- * _ S _ _
- * _ _ _ _
- *
- * (_) empty space
- * (S) sensor
- * (O) touch outside of the sensor
- */
-private val ROTATION_0_NATIVE_SENSOR_BOUNDS =
-    Rect(
-        100, /* left */
-        300, /* top */
-        200, /* right */
-        500, /* bottom */
-    )
-private val ROTATION_0_INPUTS =
-    OrientationBasedInputs(
-        rotation = Surface.ROTATION_0,
-        nativeOrientation = ORIENTATION,
-        nativeXWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterX(),
-        nativeYWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterY(),
-        nativeXOutsideSensor = 250f,
-        nativeYOutsideSensor = 150f,
-    )
-
-/*
- * ROTATION_90 map:
- * _ _ _ _ _ _
- * _ O _ _ _ _
- * _ _ _ S S _
- * _ _ _ _ _ _
- *
- * (_) empty space
- * (S) sensor
- * (O) touch outside of the sensor
- */
-private val ROTATION_90_NATIVE_SENSOR_BOUNDS =
-    Rect(
-        300, /* left */
-        200, /* top */
-        500, /* right */
-        300, /* bottom */
-    )
-private val ROTATION_90_INPUTS =
-    OrientationBasedInputs(
-        rotation = Surface.ROTATION_90,
-        nativeOrientation = (ORIENTATION - Math.PI.toFloat() / 2),
-        nativeXWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterX(),
-        nativeYWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterY(),
-        nativeXOutsideSensor = 150f,
-        nativeYOutsideSensor = 150f,
-    )
-
-/*
- * ROTATION_180 map:
- * _ _ _ _
- * _ _ s _
- * _ _ s _
- * _ _ _ _
- * _ O _ _
- * _ _ _ _
- *
- * (_) empty space
- * (S) sensor
- * (O) touch outside of the sensor
- */
-private val ROTATION_180_NATIVE_SENSOR_BOUNDS =
-    Rect(
-        200, /* left */
-        100, /* top */
-        300, /* right */
-        300, /* bottom */
-    )
-private val ROTATION_180_INPUTS =
-    OrientationBasedInputs(
-        rotation = Surface.ROTATION_180,
-        nativeOrientation = (ORIENTATION - Math.PI.toFloat() / 2),
-        nativeXWithinSensor = ROTATION_180_NATIVE_SENSOR_BOUNDS.exactCenterX(),
-        nativeYWithinSensor = ROTATION_180_NATIVE_SENSOR_BOUNDS.exactCenterY(),
-        nativeXOutsideSensor = 150f,
-        nativeYOutsideSensor = 450f,
-    )
-
-/*
- * ROTATION_270 map:
- * _ _ _ _ _ _
- * _ S S _ _ _
- * _ _ _ _ O _
- * _ _ _ _ _ _
- *
- * (_) empty space
- * (S) sensor
- * (O) touch outside of the sensor
- */
-private val ROTATION_270_NATIVE_SENSOR_BOUNDS =
-    Rect(
-        100, /* left */
-        100, /* top */
-        300, /* right */
-        200, /* bottom */
-    )
-private val ROTATION_270_INPUTS =
-    OrientationBasedInputs(
-        rotation = Surface.ROTATION_270,
-        nativeOrientation = (ORIENTATION + Math.PI.toFloat() / 2),
-        nativeXWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterX(),
-        nativeYWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterY(),
-        nativeXOutsideSensor = 450f,
-        nativeYOutsideSensor = 250f,
-    )
-
-/* Template [MotionEvent]. */
-private val MOTION_EVENT =
-    obtainMotionEvent(
-        action = 0,
-        pointerId = POINTER_ID_1,
-        x = 0f,
-        y = 0f,
-        minor = 0f,
-        major = 0f,
-        orientation = ORIENTATION,
-        time = TIME,
-        gestureStart = GESTURE_START,
-    )
-
-/* Template [NormalizedTouchData]. */
-private val NORMALIZED_TOUCH_DATA =
-    NormalizedTouchData(
-        POINTER_ID_1,
-        x = 0f,
-        y = 0f,
-        NATIVE_MINOR,
-        NATIVE_MAJOR,
-        ORIENTATION,
-        TIME,
-        GESTURE_START
-    )
-
-/*
- * Contains test inputs that are tied to a particular device orientation.
- *
- * "native" means in native resolution (not scaled).
- */
-private data class OrientationBasedInputs(
-    @Rotation val rotation: Int,
-    val nativeOrientation: Float,
-    val nativeXWithinSensor: Float,
-    val nativeYWithinSensor: Float,
-    val nativeXOutsideSensor: Float,
-    val nativeYOutsideSensor: Float,
-) {
-
-    fun toOverlayParams(scaleFactor: Float): UdfpsOverlayParams =
-        UdfpsOverlayParams(
-            sensorBounds = ROTATION_0_NATIVE_SENSOR_BOUNDS.scaled(scaleFactor),
-            overlayBounds = ROTATION_0_NATIVE_SENSOR_BOUNDS.scaled(scaleFactor),
-            naturalDisplayHeight = (ROTATION_0_NATIVE_DISPLAY_HEIGHT * scaleFactor).toInt(),
-            naturalDisplayWidth = (ROTATION_0_NATIVE_DISPLAY_WIDTH * scaleFactor).toInt(),
-            scaleFactor = scaleFactor,
-            rotation = rotation
-        )
-
-    fun getNativeX(isWithinSensor: Boolean): Float {
-        return if (isWithinSensor) nativeXWithinSensor else nativeXOutsideSensor
-    }
-
-    fun getNativeY(isWithinSensor: Boolean): Float {
-        return if (isWithinSensor) nativeYWithinSensor else nativeYOutsideSensor
-    }
-}
-
-private fun genPositiveTestCases(
-    motionEventAction: Int,
-    previousPointerOnSensorId: Int,
-    currentPointers: List<TestPointer>,
-    expectedInteractionEvent: InteractionEvent,
-    expectedPointerOnSensorId: Int
-): List<SinglePointerTouchProcessorTest.TestCase> {
-    val scaleFactors = listOf(0.75f, 1f, 1.5f)
-    val orientations =
-        listOf(
-            ROTATION_0_INPUTS,
-            ROTATION_90_INPUTS,
-            ROTATION_180_INPUTS,
-            ROTATION_270_INPUTS,
-        )
-    return scaleFactors.flatMap { scaleFactor ->
-        orientations.map { orientation ->
-            val overlayParams = orientation.toOverlayParams(scaleFactor)
-
-            val pointerProperties =
-                currentPointers
-                    .map { pointer ->
-                        val pp = MotionEvent.PointerProperties()
-                        pp.id = pointer.id
-                        pp
-                    }
-                    .toList()
-
-            val pointerCoords =
-                currentPointers
-                    .map { pointer ->
-                        val pc = MotionEvent.PointerCoords()
-                        pc.x = orientation.getNativeX(pointer.onSensor) * scaleFactor
-                        pc.y = orientation.getNativeY(pointer.onSensor) * scaleFactor
-                        pc.touchMinor = NATIVE_MINOR * scaleFactor
-                        pc.touchMajor = NATIVE_MAJOR * scaleFactor
-                        pc.orientation = orientation.nativeOrientation
-                        pc
-                    }
-                    .toList()
-
-            val event =
-                MOTION_EVENT.copy(
-                    action = motionEventAction,
-                    pointerProperties = pointerProperties,
-                    pointerCoords = pointerCoords
-                )
-
-            val expectedTouchDataPointer =
-                currentPointers.find { it.id == expectedPointerOnSensorId }
-                    ?: currentPointers.find { it.id == previousPointerOnSensorId }
-                        ?: currentPointers[0]
-            val expectedTouchData =
-                if (motionEventAction != MotionEvent.ACTION_CANCEL) {
-                    NORMALIZED_TOUCH_DATA.copy(
-                        pointerId = expectedTouchDataPointer.id,
-                        x = ROTATION_0_INPUTS.getNativeX(expectedTouchDataPointer.onSensor),
-                        y = ROTATION_0_INPUTS.getNativeY(expectedTouchDataPointer.onSensor)
-                    )
-                } else {
-                    NormalizedTouchData()
-                }
-
-            val expected =
-                TouchProcessorResult.ProcessedTouch(
-                    event = expectedInteractionEvent,
-                    pointerOnSensorId = expectedPointerOnSensorId,
-                    touchData = expectedTouchData,
-                )
-            SinglePointerTouchProcessorTest.TestCase(
-                event = event,
-                currentPointers = currentPointers,
-                previousPointerOnSensorId = previousPointerOnSensorId,
-                overlayParams = overlayParams,
-                expected = expected,
-            )
-        }
-    }
-}
-
-private fun obtainMotionEvent(
-    action: Int,
-    pointerId: Int,
-    x: Float,
-    y: Float,
-    minor: Float,
-    major: Float,
-    orientation: Float,
-    time: Long,
-    gestureStart: Long,
-): MotionEvent {
-    val pp = PointerProperties()
-    pp.id = pointerId
-    val pc = MotionEvent.PointerCoords()
-    pc.x = x
-    pc.y = y
-    pc.touchMinor = minor
-    pc.touchMajor = major
-    pc.orientation = orientation
-    return obtainMotionEvent(action, arrayOf(pp), arrayOf(pc), time, gestureStart)
-}
-
-private fun obtainMotionEvent(
-    action: Int,
-    pointerProperties: Array<MotionEvent.PointerProperties>,
-    pointerCoords: Array<MotionEvent.PointerCoords>,
-    time: Long,
-    gestureStart: Long,
-): MotionEvent {
-    return MotionEvent.obtain(
-        gestureStart /* downTime */,
-        time /* eventTime */,
-        action /* action */,
-        pointerCoords.size /* pointerCount */,
-        pointerProperties /* pointerProperties */,
-        pointerCoords /* pointerCoords */,
-        0 /* metaState */,
-        0 /* buttonState */,
-        1f /* xPrecision */,
-        1f /* yPrecision */,
-        0 /* deviceId */,
-        0 /* edgeFlags */,
-        0 /* source */,
-        0 /* flags */
-    )
-}
-
-private fun MotionEvent.copy(
-    action: Int = this.action,
-    pointerId: Int = this.getPointerId(0),
-    x: Float = this.rawX,
-    y: Float = this.rawY,
-    minor: Float = this.touchMinor,
-    major: Float = this.touchMajor,
-    orientation: Float = this.orientation,
-    time: Long = this.eventTime,
-    gestureStart: Long = this.downTime,
-) = obtainMotionEvent(action, pointerId, x, y, minor, major, orientation, time, gestureStart)
-
-private fun MotionEvent.copy(
-    action: Int = this.action,
-    pointerProperties: List<MotionEvent.PointerProperties>,
-    pointerCoords: List<MotionEvent.PointerCoords>,
-    time: Long = this.eventTime,
-    gestureStart: Long = this.downTime
-) =
-    obtainMotionEvent(
-        action,
-        pointerProperties.toTypedArray(),
-        pointerCoords.toTypedArray(),
-        time,
-        gestureStart
-    )
-
-private fun Rect.scaled(scaleFactor: Float) = Rect(this).apply { scale(scaleFactor) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
index 25f9565..a11dace 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothLeBroadcast
+import android.bluetooth.BluetoothLeBroadcastMetadata
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -50,6 +51,7 @@
     @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
     private val kosmos = testKosmos()
     @Mock private lateinit var localBluetoothLeBroadcast: LocalBluetoothLeBroadcast
+    @Mock private lateinit var bluetoothLeBroadcastMetadata: BluetoothLeBroadcastMetadata
     @Captor private lateinit var callbackCaptor: ArgumentCaptor<BluetoothLeBroadcast.Callback>
     private lateinit var underTest: AudioSharingInteractor
 
@@ -202,7 +204,7 @@
                 verify(localBluetoothLeBroadcast)
                     .registerServiceCallBack(any(), callbackCaptor.capture())
                 runCurrent()
-                callbackCaptor.value.onPlaybackStarted(0, 0)
+                callbackCaptor.value.onBroadcastMetadataChanged(0, bluetoothLeBroadcastMetadata)
                 runCurrent()
 
                 assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable
deleted file mode 100644
index 97f2e56..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable
+++ /dev/null
@@ -1,337 +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.bouncer.ui.composable
-
-import android.app.AlertDialog
-import android.platform.test.annotations.MotionTest
-import android.testing.TestableLooper.RunWithLooper
-import android.view.View
-import androidx.activity.BackEventCompat
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.isFinite
-import androidx.compose.ui.geometry.isUnspecified
-import androidx.compose.ui.semantics.SemanticsNode
-import androidx.compose.ui.test.junit4.AndroidComposeTestRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.compose.animation.scene.Scale
-import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.UserAction
-import com.android.compose.animation.scene.UserActionResult
-import com.android.compose.animation.scene.isElement
-import com.android.compose.animation.scene.testing.lastAlphaForTesting
-import com.android.compose.animation.scene.testing.lastScaleForTesting
-import com.android.compose.theme.PlatformTheme
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
-import com.android.systemui.bouncer.ui.viewmodel.BouncerUserActionsViewModel
-import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModel
-import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.rememberViewModel
-import com.android.systemui.motion.createSysUiComposeMotionTestRule
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.domain.startable.sceneContainerStartable
-import com.android.systemui.scene.sceneContainerViewModelFactory
-import com.android.systemui.scene.shared.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
-import com.android.systemui.scene.ui.composable.Scene
-import com.android.systemui.scene.ui.composable.SceneContainer
-import com.android.systemui.testKosmos
-import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
-import org.json.JSONObject
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
-import org.mockito.kotlin.mock
-import platform.test.motion.compose.ComposeFeatureCaptures.positionInRoot
-import platform.test.motion.compose.ComposeRecordingSpec
-import platform.test.motion.compose.MotionControl
-import platform.test.motion.compose.feature
-import platform.test.motion.compose.recordMotion
-import platform.test.motion.compose.runTest
-import platform.test.motion.golden.DataPoint
-import platform.test.motion.golden.DataPointType
-import platform.test.motion.golden.DataPointTypes
-import platform.test.motion.golden.FeatureCapture
-import platform.test.motion.golden.UnknownTypeException
-import platform.test.screenshot.DeviceEmulationSpec
-import platform.test.screenshot.Displays.Phone
-
-/** MotionTest for the Bouncer Predictive Back animation */
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-@RunWithLooper
-@EnableSceneContainer
-@MotionTest
-class BouncerPredictiveBackTest : SysuiTestCase() {
-
-    private val deviceSpec = DeviceEmulationSpec(Phone)
-    private val kosmos = testKosmos()
-
-    @get:Rule val motionTestRule = createSysUiComposeMotionTestRule(kosmos, deviceSpec)
-    private val androidComposeTestRule =
-        motionTestRule.toolkit.composeContentTestRule as AndroidComposeTestRule<*, *>
-
-    private val sceneInteractor by lazy { kosmos.sceneInteractor }
-    private val Kosmos.sceneKeys by Fixture { listOf(Scenes.Lockscreen, Scenes.Bouncer) }
-    private val Kosmos.initialSceneKey by Fixture { Scenes.Bouncer }
-    private val Kosmos.sceneContainerConfig by Fixture {
-        val navigationDistances = mapOf(Scenes.Lockscreen to 1, Scenes.Bouncer to 0)
-        SceneContainerConfig(sceneKeys, initialSceneKey, emptyList(), navigationDistances)
-    }
-    private val view = mock<View>()
-
-    private val transitionState by lazy {
-        MutableStateFlow<ObservableTransitionState>(
-            ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
-        )
-    }
-
-    private val sceneContainerViewModel by lazy {
-        kosmos.sceneContainerViewModelFactory
-            .create(view) {}
-            .apply { setTransitionState(transitionState) }
-    }
-
-    private val bouncerDialogFactory =
-        object : BouncerDialogFactory {
-            override fun invoke(): AlertDialog {
-                throw AssertionError()
-            }
-        }
-    private val bouncerSceneActionsViewModelFactory =
-        object : BouncerUserActionsViewModel.Factory {
-            override fun create() = BouncerUserActionsViewModel(kosmos.bouncerInteractor)
-        }
-    private lateinit var bouncerSceneContentViewModel: BouncerSceneContentViewModel
-    private val bouncerSceneContentViewModelFactory =
-        object : BouncerSceneContentViewModel.Factory {
-            override fun create() = bouncerSceneContentViewModel
-        }
-    private val bouncerScene =
-        BouncerScene(
-            bouncerSceneActionsViewModelFactory,
-            bouncerSceneContentViewModelFactory,
-            bouncerDialogFactory,
-        )
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        bouncerSceneContentViewModel = kosmos.bouncerSceneContentViewModel
-
-        val startable = kosmos.sceneContainerStartable
-        startable.start()
-    }
-
-    @Test
-    fun bouncerPredictiveBackMotion() =
-        motionTestRule.runTest(timeout = 30.seconds) {
-            val motion =
-                recordMotion(
-                    content = { play ->
-                        PlatformTheme {
-                            BackGestureAnimation(play)
-                            SceneContainer(
-                                viewModel =
-                                    rememberViewModel("BouncerPredictiveBackTest") {
-                                        sceneContainerViewModel
-                                    },
-                                sceneByKey =
-                                    mapOf(
-                                        Scenes.Lockscreen to FakeLockscreen(),
-                                        Scenes.Bouncer to bouncerScene,
-                                    ),
-                                initialSceneKey = Scenes.Bouncer,
-                                overlayByKey = emptyMap(),
-                                dataSourceDelegator = kosmos.sceneDataSourceDelegator,
-                            )
-                        }
-                    },
-                    ComposeRecordingSpec(
-                        MotionControl(
-                            delayRecording = {
-                                awaitCondition {
-                                    sceneInteractor.transitionState.value.isTransitioning()
-                                }
-                            }
-                        ) {
-                            awaitCondition {
-                                sceneInteractor.transitionState.value.isIdle(Scenes.Lockscreen)
-                            }
-                        }
-                    ) {
-                        feature(isElement(Bouncer.Elements.Content), elementAlpha, "content_alpha")
-                        feature(isElement(Bouncer.Elements.Content), elementScale, "content_scale")
-                        feature(
-                            isElement(Bouncer.Elements.Content),
-                            positionInRoot,
-                            "content_offset",
-                        )
-                        feature(
-                            isElement(Bouncer.Elements.Background),
-                            elementAlpha,
-                            "background_alpha",
-                        )
-                    },
-                )
-
-            assertThat(motion).timeSeriesMatchesGolden()
-        }
-
-    @Composable
-    private fun BackGestureAnimation(play: Boolean) {
-        val backProgress = remember { Animatable(0f) }
-
-        LaunchedEffect(play) {
-            if (play) {
-                val dispatcher = androidComposeTestRule.activity.onBackPressedDispatcher
-                androidComposeTestRule.runOnUiThread {
-                    dispatcher.dispatchOnBackStarted(backEvent())
-                }
-                backProgress.animateTo(
-                    targetValue = 1f,
-                    animationSpec = tween(durationMillis = 500),
-                ) {
-                    androidComposeTestRule.runOnUiThread {
-                        dispatcher.dispatchOnBackProgressed(
-                            backEvent(progress = backProgress.value)
-                        )
-                        if (backProgress.value == 1f) {
-                            dispatcher.onBackPressed()
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private fun backEvent(progress: Float = 0f): BackEventCompat {
-        return BackEventCompat(
-            touchX = 0f,
-            touchY = 0f,
-            progress = progress,
-            swipeEdge = BackEventCompat.EDGE_LEFT,
-        )
-    }
-
-    private class FakeLockscreen : ExclusiveActivatable(), Scene {
-        override val key: SceneKey = Scenes.Lockscreen
-        override val userActions: Flow<Map<UserAction, UserActionResult>> = flowOf()
-
-        @Composable
-        override fun SceneScope.Content(modifier: Modifier) {
-            Box(modifier = modifier, contentAlignment = Alignment.Center) {
-                Text(text = "Fake Lockscreen")
-            }
-        }
-
-        override suspend fun onActivated() = awaitCancellation()
-    }
-
-    companion object {
-        private val elementAlpha =
-            FeatureCapture<SemanticsNode, Float>("alpha") {
-                DataPoint.of(it.lastAlphaForTesting, DataPointTypes.float)
-            }
-
-        private val elementScale =
-            FeatureCapture<SemanticsNode, Scale>("scale") {
-                DataPoint.of(it.lastScaleForTesting, scale)
-            }
-
-        private val scale: DataPointType<Scale> =
-            DataPointType(
-                "scale",
-                jsonToValue = {
-                    when (it) {
-                        "unspecified" -> Scale.Unspecified
-                        "default" -> Scale.Default
-                        "zero" -> Scale.Zero
-                        is JSONObject -> {
-                            val pivot = it.get("pivot")
-                            Scale(
-                                scaleX = it.getDouble("x").toFloat(),
-                                scaleY = it.getDouble("y").toFloat(),
-                                pivot =
-                                    when (pivot) {
-                                        "unspecified" -> Offset.Unspecified
-                                        "infinite" -> Offset.Infinite
-                                        is JSONObject ->
-                                            Offset(
-                                                pivot.getDouble("x").toFloat(),
-                                                pivot.getDouble("y").toFloat(),
-                                            )
-                                        else -> throw UnknownTypeException()
-                                    },
-                            )
-                        }
-                        else -> throw UnknownTypeException()
-                    }
-                },
-                valueToJson = {
-                    when (it) {
-                        Scale.Unspecified -> "unspecified"
-                        Scale.Default -> "default"
-                        Scale.Zero -> "zero"
-                        else -> {
-                            JSONObject().apply {
-                                put("x", it.scaleX)
-                                put("y", it.scaleY)
-                                put(
-                                    "pivot",
-                                    when {
-                                        it.pivot.isUnspecified -> "unspecified"
-                                        !it.pivot.isFinite -> "infinite"
-                                        else ->
-                                            JSONObject().apply {
-                                                put("x", it.pivot.x)
-                                                put("y", it.pivot.y)
-                                            }
-                                    },
-                                )
-                            }
-                        }
-                    }
-                },
-            )
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
new file mode 100644
index 0000000..b33a83c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
@@ -0,0 +1,347 @@
+/*
+ * 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.bouncer.ui.composable
+
+import android.app.AlertDialog
+import android.platform.test.annotations.MotionTest
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import androidx.activity.BackEventCompat
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.isFinite
+import androidx.compose.ui.geometry.isUnspecified
+import androidx.compose.ui.semantics.SemanticsNode
+import androidx.compose.ui.test.junit4.AndroidComposeTestRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.Scale
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.compose.animation.scene.isElement
+import com.android.compose.animation.scene.testing.lastAlphaForTesting
+import com.android.compose.animation.scene.testing.lastScaleForTesting
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerUserActionsViewModel
+import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModel
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.motion.createSysUiComposeMotionTestRule
+import com.android.systemui.qs.ui.viewmodel.fakeQsSceneAdapter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.scene.sceneContainerViewModelFactory
+import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
+import com.android.systemui.scene.ui.composable.Scene
+import com.android.systemui.scene.ui.composable.SceneContainer
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
+import com.android.systemui.testKosmos
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import org.json.JSONObject
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import platform.test.motion.compose.ComposeFeatureCaptures.positionInRoot
+import platform.test.motion.compose.ComposeRecordingSpec
+import platform.test.motion.compose.MotionControl
+import platform.test.motion.compose.feature
+import platform.test.motion.compose.recordMotion
+import platform.test.motion.compose.runTest
+import platform.test.motion.golden.DataPoint
+import platform.test.motion.golden.DataPointType
+import platform.test.motion.golden.DataPointTypes
+import platform.test.motion.golden.FeatureCapture
+import platform.test.motion.golden.UnknownTypeException
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.Displays.Phone
+
+/** MotionTest for the Bouncer Predictive Back animation */
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+@EnableSceneContainer
+@MotionTest
+class BouncerPredictiveBackTest : SysuiTestCase() {
+
+    private val deviceSpec = DeviceEmulationSpec(Phone)
+    private val kosmos = testKosmos()
+
+    @get:Rule val motionTestRule = createSysUiComposeMotionTestRule(kosmos, deviceSpec)
+    private val androidComposeTestRule =
+        motionTestRule.toolkit.composeContentTestRule as AndroidComposeTestRule<*, *>
+
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val Kosmos.sceneKeys by Fixture { listOf(Scenes.Lockscreen, Scenes.Bouncer) }
+    private val Kosmos.initialSceneKey by Fixture { Scenes.Bouncer }
+    private val Kosmos.sceneContainerConfig by Fixture {
+        val navigationDistances = mapOf(Scenes.Lockscreen to 1, Scenes.Bouncer to 0)
+        SceneContainerConfig(
+            sceneKeys,
+            initialSceneKey,
+            SceneContainerTransitions,
+            emptyList(),
+            navigationDistances,
+        )
+    }
+    private val view = mock<View>()
+
+    private val transitionState by lazy {
+        MutableStateFlow<ObservableTransitionState>(
+            ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
+        )
+    }
+
+    private val sceneContainerViewModel by lazy {
+        kosmos.sceneContainerViewModelFactory
+            .create(view) {}
+            .apply { setTransitionState(transitionState) }
+    }
+
+    private val bouncerDialogFactory =
+        object : BouncerDialogFactory {
+            override fun invoke(): AlertDialog {
+                throw AssertionError()
+            }
+        }
+    private val bouncerSceneActionsViewModelFactory =
+        object : BouncerUserActionsViewModel.Factory {
+            override fun create() = BouncerUserActionsViewModel(kosmos.bouncerInteractor)
+        }
+    private lateinit var bouncerSceneContentViewModel: BouncerSceneContentViewModel
+    private val bouncerSceneContentViewModelFactory =
+        object : BouncerSceneContentViewModel.Factory {
+            override fun create() = bouncerSceneContentViewModel
+        }
+    private val bouncerScene =
+        BouncerScene(
+            bouncerSceneActionsViewModelFactory,
+            bouncerSceneContentViewModelFactory,
+            bouncerDialogFactory,
+        )
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        bouncerSceneContentViewModel = kosmos.bouncerSceneContentViewModel
+
+        val startable = kosmos.sceneContainerStartable
+        startable.start()
+    }
+
+    @Test
+    fun bouncerPredictiveBackMotion() =
+        motionTestRule.runTest(timeout = 30.seconds) {
+            val motion =
+                recordMotion(
+                    content = { play ->
+                        PlatformTheme {
+                            BackGestureAnimation(play)
+                            SceneContainer(
+                                viewModel =
+                                    rememberViewModel("BouncerPredictiveBackTest") {
+                                        sceneContainerViewModel
+                                    },
+                                sceneByKey =
+                                    mapOf(
+                                        Scenes.Lockscreen to FakeLockscreen(),
+                                        Scenes.Bouncer to bouncerScene,
+                                    ),
+                                initialSceneKey = Scenes.Bouncer,
+                                sceneTransitions = SceneContainerTransitions,
+                                overlayByKey = emptyMap(),
+                                dataSourceDelegator = kosmos.sceneDataSourceDelegator,
+                                qsSceneAdapter = { kosmos.fakeQsSceneAdapter },
+                            )
+                        }
+                    },
+                    ComposeRecordingSpec(
+                        MotionControl(
+                            delayRecording = {
+                                awaitCondition {
+                                    sceneInteractor.transitionState.value.isTransitioning()
+                                }
+                            }
+                        ) {
+                            awaitCondition {
+                                sceneInteractor.transitionState.value.isIdle(Scenes.Lockscreen)
+                            }
+                        }
+                    ) {
+                        feature(isElement(Bouncer.Elements.Content), elementAlpha, "content_alpha")
+                        feature(isElement(Bouncer.Elements.Content), elementScale, "content_scale")
+                        feature(
+                            isElement(Bouncer.Elements.Content),
+                            positionInRoot,
+                            "content_offset",
+                        )
+                        feature(
+                            isElement(Bouncer.Elements.Background),
+                            elementAlpha,
+                            "background_alpha",
+                        )
+                    },
+                )
+
+            assertThat(motion).timeSeriesMatchesGolden()
+        }
+
+    @Composable
+    private fun BackGestureAnimation(play: Boolean) {
+        val backProgress = remember { Animatable(0f) }
+
+        LaunchedEffect(play) {
+            if (play) {
+                val dispatcher = androidComposeTestRule.activity.onBackPressedDispatcher
+                androidComposeTestRule.runOnUiThread {
+                    dispatcher.dispatchOnBackStarted(backEvent())
+                }
+                backProgress.animateTo(
+                    targetValue = 1f,
+                    animationSpec = tween(durationMillis = 500),
+                ) {
+                    androidComposeTestRule.runOnUiThread {
+                        dispatcher.dispatchOnBackProgressed(
+                            backEvent(progress = backProgress.value)
+                        )
+                        if (backProgress.value == 1f) {
+                            dispatcher.onBackPressed()
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private fun backEvent(progress: Float = 0f): BackEventCompat {
+        return BackEventCompat(
+            touchX = 0f,
+            touchY = 0f,
+            progress = progress,
+            swipeEdge = BackEventCompat.EDGE_LEFT,
+        )
+    }
+
+    private class FakeLockscreen : ExclusiveActivatable(), Scene {
+        override val key: SceneKey = Scenes.Lockscreen
+        override val userActions: Flow<Map<UserAction, UserActionResult>> = flowOf()
+
+        @Composable
+        override fun SceneScope.Content(modifier: Modifier) {
+            Box(modifier = modifier, contentAlignment = Alignment.Center) {
+                Text(text = "Fake Lockscreen")
+            }
+        }
+
+        override suspend fun onActivated() = awaitCancellation()
+    }
+
+    companion object {
+        private val elementAlpha =
+            FeatureCapture<SemanticsNode, Float>("alpha") {
+                DataPoint.of(it.lastAlphaForTesting, DataPointTypes.float)
+            }
+
+        private val elementScale =
+            FeatureCapture<SemanticsNode, Scale>("scale") {
+                DataPoint.of(it.lastScaleForTesting, scale)
+            }
+
+        private val scale: DataPointType<Scale> =
+            DataPointType(
+                "scale",
+                jsonToValue = {
+                    when (it) {
+                        "unspecified" -> Scale.Unspecified
+                        "default" -> Scale.Default
+                        "zero" -> Scale.Zero
+                        is JSONObject -> {
+                            val pivot = it.get("pivot")
+                            Scale(
+                                scaleX = it.getDouble("x").toFloat(),
+                                scaleY = it.getDouble("y").toFloat(),
+                                pivot =
+                                    when (pivot) {
+                                        "unspecified" -> Offset.Unspecified
+                                        "infinite" -> Offset.Infinite
+                                        is JSONObject ->
+                                            Offset(
+                                                pivot.getDouble("x").toFloat(),
+                                                pivot.getDouble("y").toFloat(),
+                                            )
+                                        else -> throw UnknownTypeException()
+                                    },
+                            )
+                        }
+                        else -> throw UnknownTypeException()
+                    }
+                },
+                valueToJson = {
+                    when (it) {
+                        Scale.Unspecified -> "unspecified"
+                        Scale.Default -> "default"
+                        Scale.Zero -> "zero"
+                        else -> {
+                            JSONObject().apply {
+                                put("x", it.scaleX)
+                                put("y", it.scaleY)
+                                put(
+                                    "pivot",
+                                    when {
+                                        it.pivot.isUnspecified -> "unspecified"
+                                        !it.pivot.isFinite -> "infinite"
+                                        else ->
+                                            JSONObject().apply {
+                                                put("x", it.pivot.x)
+                                                put("y", it.pivot.y)
+                                            }
+                                    },
+                                )
+                            }
+                        }
+                    }
+                },
+            )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
index 3bf4460..94f6769 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
@@ -34,6 +34,11 @@
 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
+import com.android.systemui.keyguard.shared.model.DismissAction
+import com.android.systemui.keyguard.shared.model.KeyguardDone
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.res.R
@@ -212,6 +217,25 @@
             assertThat(isFoldSplitRequired).isTrue()
         }
 
+    @Test
+    fun onUiDestroyed_clearsPendingDismissAction() =
+        kosmos.runTest {
+            val dismissAction by collectLastValue(fakeKeyguardRepository.dismissAction)
+            fakeKeyguardRepository.setDismissAction(
+                DismissAction.RunImmediately(
+                    onDismissAction = { KeyguardDone.IMMEDIATE },
+                    onCancelAction = {},
+                    message = "",
+                    willAnimateOnLockscreen = true,
+                )
+            )
+            assertThat(dismissAction).isNotEqualTo(DismissAction.None)
+
+            underTest.onUiDestroyed()
+
+            assertThat(dismissAction).isEqualTo(DismissAction.None)
+        }
+
     private fun authMethodsToTest(): List<AuthenticationMethodModel> {
         return listOf(None, Pin, Password, Pattern, Sim)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
index 0f148f8..2d093bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.brightness.domain.interactor.screenBrightnessInteractor
 import com.android.systemui.brightness.shared.model.GammaBrightness
 import com.android.systemui.brightness.shared.model.LinearBrightness
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
@@ -61,6 +62,7 @@
                 brightnessPolicyEnforcementInteractor,
                 sliderHapticsViewModelFactory,
                 brightnessMirrorShowingInteractor,
+                falsingInteractor,
                 supportsMirroring = true,
                 brightnessWarningToast,
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
index cecb525..01baadd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -129,16 +128,43 @@
     }
 
     @Test
-    public void setProgress_onlyOnProgressChangedTriggeredWithFromUserFalse() {
+    public void setProgress_onProgressChangedAndOnUserInteractionFinalized() {
         reset(mOnSeekBarChangeListener);
         mIconDiscreteSliderLinearLayout.setProgress(1);
 
+        // If users are changing seekbar progress without touching the seekbar or clicking the
+        // buttons, trigger onUserInteractionFinalized.
         verify(mOnSeekBarChangeListener).onProgressChanged(
                 eq(mSeekbar), /* progress= */ eq(1), /* fromUser= */ eq(false));
         verify(mOnSeekBarChangeListener, never()).onStartTrackingTouch(/* seekBar= */ any());
         verify(mOnSeekBarChangeListener, never()).onStopTrackingTouch(/* seekBar= */ any());
+        verify(mOnSeekBarChangeListener).onUserInteractionFinalized(
+                /* seekBar= */ any(),
+                eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER));
+    }
+
+    @Test
+    public void setProgressToSeekBarByTouch_onUserInteractionFinalizedAfterTouchEnds() {
+        reset(mOnSeekBarChangeListener);
+        final SeekBarWithIconButtonsView.SeekBarChangeListener seekBarChangeListener =
+                mIconDiscreteSliderLinearLayout.getSeekBarChangeListener();
+        final SeekBar seekBar = mIconDiscreteSliderLinearLayout.findViewById(R.id.seekbar);
+
+        // Simulate changing seekbar progress by touch
+        seekBarChangeListener.onStartTrackingTouch(seekBar);
+        mIconDiscreteSliderLinearLayout.setProgress(1);
+
+        verify(mOnSeekBarChangeListener).onProgressChanged(
+                eq(mSeekbar), /* progress= */ eq(1), /* fromUser= */ eq(false));
         verify(mOnSeekBarChangeListener, never()).onUserInteractionFinalized(
-                /* seekBar= */any(), /* control= */ anyInt());
+                /* seekBar= */ any(),
+                eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER));
+
+        // Notify onUserInteractionFinalized after touch ends
+        seekBarChangeListener.onStopTrackingTouch(seekBar);
+        verify(mOnSeekBarChangeListener).onUserInteractionFinalized(
+                /* seekBar= */ any(),
+                eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER));
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
index 596db07..f1c58a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
@@ -24,6 +24,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.db.DefaultWidgetPopulation.SkipReason.RESTORED_FROM_BACKUP
+import com.android.systemui.communal.shared.model.SpanValue
 import com.android.systemui.communal.widgets.CommunalWidgetHost
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
@@ -117,7 +118,7 @@
                     componentName = defaultWidgets[0],
                     rank = 0,
                     userSerialNumber = 0,
-                    spanY = 3,
+                    spanY = SpanValue.Fixed(3),
                 )
             verify(communalWidgetDao)
                 .addWidget(
@@ -125,7 +126,7 @@
                     componentName = defaultWidgets[1],
                     rank = 1,
                     userSerialNumber = 0,
-                    spanY = 3,
+                    spanY = SpanValue.Fixed(3),
                 )
             verify(communalWidgetDao)
                 .addWidget(
@@ -133,7 +134,7 @@
                     componentName = defaultWidgets[2],
                     rank = 2,
                     userSerialNumber = 0,
-                    spanY = 3,
+                    spanY = SpanValue.Fixed(3),
                 )
         }
 
@@ -155,7 +156,7 @@
                     componentName = any(),
                     rank = anyInt(),
                     userSerialNumber = anyInt(),
-                    spanY = anyInt(),
+                    spanY = any(),
                 )
         }
 }
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 06b710e..b66727e 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
@@ -22,6 +22,7 @@
 import android.app.admin.devicePolicyManager
 import android.content.Intent
 import android.content.pm.UserInfo
+import android.content.res.mainResources
 import android.os.UserManager.USER_TYPE_PROFILE_MANAGED
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
@@ -29,6 +30,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.communal.data.model.DisabledReason
@@ -53,7 +55,8 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
+    private val kosmos =
+        testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources }
     private val testScope = kosmos.testScope
     private lateinit var underTest: CommunalSettingsRepository
 
@@ -67,6 +70,7 @@
     }
 
     @EnableFlags(FLAG_COMMUNAL_HUB)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun getFlagEnabled_bothEnabled() {
         kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -74,7 +78,7 @@
         assertThat(underTest.getFlagEnabled()).isTrue()
     }
 
-    @DisableFlags(FLAG_COMMUNAL_HUB)
+    @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun getFlagEnabled_bothDisabled() {
         kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
@@ -82,7 +86,7 @@
         assertThat(underTest.getFlagEnabled()).isFalse()
     }
 
-    @DisableFlags(FLAG_COMMUNAL_HUB)
+    @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun getFlagEnabled_onlyClassicFlagEnabled() {
         kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -91,6 +95,7 @@
     }
 
     @EnableFlags(FLAG_COMMUNAL_HUB)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun getFlagEnabled_onlyTrunkFlagEnabled() {
         kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
@@ -98,6 +103,57 @@
         assertThat(underTest.getFlagEnabled()).isFalse()
     }
 
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+    @DisableFlags(FLAG_COMMUNAL_HUB)
+    @Test
+    fun getFlagEnabled_mobileConfigEnabled() {
+        mContext.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_glanceableHubEnabled,
+            true,
+        )
+
+        assertThat(underTest.getFlagEnabled()).isTrue()
+    }
+
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2, FLAG_COMMUNAL_HUB)
+    @Test
+    fun getFlagEnabled_onlyMobileConfigEnabled() {
+        mContext.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_glanceableHubEnabled,
+            true,
+        )
+
+        assertThat(underTest.getFlagEnabled()).isFalse()
+    }
+
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+    @DisableFlags(FLAG_COMMUNAL_HUB)
+    @Test
+    fun getFlagEnabled_onlyMobileFlagEnabled() {
+        mContext.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_glanceableHubEnabled,
+            false,
+        )
+
+        assertThat(underTest.getFlagEnabled()).isFalse()
+    }
+
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+    @DisableFlags(FLAG_COMMUNAL_HUB)
+    @Test
+    fun getFlagEnabled_oldFlagIgnored() {
+        // New config flag enabled.
+        mContext.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_glanceableHubEnabled,
+            true,
+        )
+
+        // Old config flag disabled.
+        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+
+        assertThat(underTest.getFlagEnabled()).isTrue()
+    }
+
     @EnableFlags(FLAG_COMMUNAL_HUB)
     @Test
     fun secondaryUserIsInvalid() =
@@ -134,7 +190,7 @@
             kosmos.fakeSettings.putIntForUser(
                 Settings.Secure.GLANCEABLE_HUB_ENABLED,
                 0,
-                PRIMARY_USER.id
+                PRIMARY_USER.id,
             )
             val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
             assertThat(enabledState?.enabled).isFalse()
@@ -143,14 +199,14 @@
             kosmos.fakeSettings.putIntForUser(
                 Settings.Secure.GLANCEABLE_HUB_ENABLED,
                 1,
-                SECONDARY_USER.id
+                SECONDARY_USER.id,
             )
             assertThat(enabledState?.enabled).isFalse()
 
             kosmos.fakeSettings.putIntForUser(
                 Settings.Secure.GLANCEABLE_HUB_ENABLED,
                 1,
-                PRIMARY_USER.id
+                PRIMARY_USER.id,
             )
             assertThat(enabledState?.enabled).isTrue()
         }
@@ -201,7 +257,7 @@
             kosmos.fakeSettings.putIntForUser(
                 Settings.Secure.GLANCEABLE_HUB_ENABLED,
                 0,
-                PRIMARY_USER.id
+                PRIMARY_USER.id,
             )
             setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
 
@@ -228,7 +284,7 @@
                 kosmos.fakeSettings.putIntForUser(
                     GLANCEABLE_HUB_BACKGROUND_SETTING,
                     type.value,
-                    PRIMARY_USER.id
+                    PRIMARY_USER.id,
                 )
                 assertWithMessage(
                         "Expected $type when $GLANCEABLE_HUB_BACKGROUND_SETTING is set to" +
@@ -253,12 +309,6 @@
             UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
         val SECONDARY_USER = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
         val WORK_PROFILE =
-            UserInfo(
-                10,
-                "work",
-                /* iconPath= */ "",
-                /* flags= */ 0,
-                USER_TYPE_PROFILE_MANAGED,
-            )
+            UserInfo(10, "work", /* iconPath= */ "", /* flags= */ 0, USER_TYPE_PROFILE_MANAGED)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt
index 55d7d08..335e399 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt
@@ -24,11 +24,15 @@
 import android.content.applicationContext
 import android.graphics.Bitmap
 import android.os.UserHandle
+import android.os.UserManager
 import android.os.userManager
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID
 import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING
+import com.android.systemui.Flags.communalResponsiveGrid
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.data.repository.fakePackageChangeRepository
 import com.android.systemui.common.shared.model.PackageInstallSession
@@ -40,11 +44,15 @@
 import com.android.systemui.communal.nano.CommunalHubState
 import com.android.systemui.communal.proto.toByteArray
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.SpanValue
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.CommunalWidgetHost
 import com.android.systemui.communal.widgets.widgetConfiguratorFail
 import com.android.systemui.communal.widgets.widgetConfiguratorSuccess
-import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.LogBuffer
@@ -52,48 +60,55 @@
 import com.android.systemui.res.R
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 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.ArgumentMatchers.anyInt
-import org.mockito.Mock
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
-    @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
-    @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
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalWidgetRepositoryLocalImplTest(flags: FlagsParameterization) : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    private val appWidgetHost = mock<CommunalAppWidgetHost>()
+    private val providerInfoA = mock<AppWidgetProviderInfo>()
+    private val providerInfoB = mock<AppWidgetProviderInfo>()
+    private val providerInfoC = mock<AppWidgetProviderInfo>()
 
     private val communalHubStateCaptor = argumentCaptor<CommunalHubState>()
     private val componentNameCaptor = argumentCaptor<ComponentName>()
 
-    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.communalWidgetHost by
+        Kosmos.Fixture {
+            mock<CommunalWidgetHost> { on { appWidgetProviders } doReturn fakeProviders }
+        }
+    private val Kosmos.communalWidgetDao by
+        Kosmos.Fixture { mock<CommunalWidgetDao> { on { getWidgets() } doReturn fakeWidgets } }
 
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-    private val packageChangeRepository = kosmos.fakePackageChangeRepository
-    private val userManager = kosmos.userManager
+    private val Kosmos.backupManager by Kosmos.Fixture { mock<BackupManager>() }
+
+    private val Kosmos.backupUtils: CommunalBackupUtils by
+        Kosmos.Fixture { CommunalBackupUtils(applicationContext) }
+
+    private val Kosmos.logBuffer: LogBuffer by
+        Kosmos.Fixture { logcatLogBuffer(name = "CommunalWidgetRepoLocalImplTest") }
+
+    private val Kosmos.fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>> by
+        Kosmos.Fixture { MutableStateFlow(emptyMap()) }
+
+    private val Kosmos.fakeProviders: MutableStateFlow<Map<Int, AppWidgetProviderInfo?>> by
+        Kosmos.Fixture { MutableStateFlow(emptyMap()) }
 
     private val mainUser = UserHandle(0)
     private val workProfile = UserHandle(10)
@@ -105,48 +120,49 @@
             "com.android.fake/WidgetProviderC",
         )
 
-    private lateinit var underTest: CommunalWidgetRepositoryLocalImpl
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        fakeWidgets = MutableStateFlow(emptyMap())
-        fakeProviders = MutableStateFlow(emptyMap())
-        logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoLocalImplTest")
-        backupUtils = CommunalBackupUtils(kosmos.applicationContext)
-
-        setAppWidgetIds(emptyList())
-
-        overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
-
-        whenever(communalWidgetDao.getWidgets()).thenReturn(fakeWidgets)
-        whenever(communalWidgetHost.appWidgetProviders).thenReturn(fakeProviders)
-        whenever(userManager.mainUser).thenReturn(mainUser)
-
-        restoreUser(mainUser)
-
-        underTest =
+    private val Kosmos.underTest by
+        Kosmos.Fixture {
             CommunalWidgetRepositoryLocalImpl(
                 appWidgetHost,
                 testScope.backgroundScope,
-                kosmos.testDispatcher,
+                testDispatcher,
                 communalWidgetHost,
                 communalWidgetDao,
                 logBuffer,
                 backupManager,
                 backupUtils,
-                packageChangeRepository,
+                fakePackageChangeRepository,
                 userManager,
-                kosmos.defaultWidgetPopulation,
+                defaultWidgetPopulation,
             )
+        }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    @Before
+    fun setUp() {
+        kosmos.userManager = mock<UserManager> { on { mainUser } doReturn mainUser }
+        setAppWidgetIds(emptyList())
+        overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
+        restoreUser(mainUser)
     }
 
     @Test
     fun communalWidgets_queryWidgetsFromDb() =
-        testScope.runTest {
+        kosmos.runTest {
             val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
             val communalWidgetItemEntry =
-                CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L, 0, 3)
+                CommunalWidgetItem(
+                    uid = 1L,
+                    widgetId = 1,
+                    componentName = "pk_name/cls_name",
+                    itemId = 1L,
+                    userSerialNumber = 0,
+                    spanY = 3,
+                    spanYNew = 1,
+                )
             fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry)
             fakeProviders.value = mapOf(1 to providerInfoA)
 
@@ -158,7 +174,12 @@
                         appWidgetId = communalWidgetItemEntry.widgetId,
                         providerInfo = providerInfoA,
                         rank = communalItemRankEntry.rank,
-                        spanY = communalWidgetItemEntry.spanY,
+                        spanY =
+                            if (communalResponsiveGrid()) {
+                                communalWidgetItemEntry.spanYNew
+                            } else {
+                                communalWidgetItemEntry.spanY
+                            },
                     )
                 )
 
@@ -168,18 +189,50 @@
 
     @Test
     fun communalWidgets_widgetsWithoutMatchingProvidersAreSkipped() =
-        testScope.runTest {
+        kosmos.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, 0, 3),
+                        CommunalWidgetItem(
+                            uid = 1L,
+                            widgetId = 1,
+                            componentName = "pk_1/cls_1",
+                            itemId = 1L,
+                            userSerialNumber = 0,
+                            spanY = 3,
+                            spanYNew = 1,
+                        ),
                     CommunalItemRank(uid = 2L, rank = 2) to
-                        CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3),
+                        CommunalWidgetItem(
+                            uid = 2L,
+                            widgetId = 2,
+                            componentName = "pk_2/cls_2",
+                            itemId = 2L,
+                            userSerialNumber = 0,
+                            spanY = 3,
+                            spanYNew = 1,
+                        ),
                     CommunalItemRank(uid = 3L, rank = 3) to
-                        CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L, 0, 3),
+                        CommunalWidgetItem(
+                            uid = 3L,
+                            widgetId = 3,
+                            componentName = "pk_3/cls_3",
+                            itemId = 3L,
+                            userSerialNumber = 0,
+                            spanY = 3,
+                            spanYNew = 1,
+                        ),
                     CommunalItemRank(uid = 4L, rank = 4) to
-                        CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L, 0, 3),
+                        CommunalWidgetItem(
+                            uid = 4L,
+                            widgetId = 4,
+                            componentName = "pk_4/cls_4",
+                            itemId = 4L,
+                            userSerialNumber = 0,
+                            spanY = 3,
+                            spanYNew = 1,
+                        ),
                 )
             fakeProviders.value = mapOf(1 to providerInfoA, 2 to providerInfoB)
 
@@ -191,27 +244,43 @@
                         appWidgetId = 1,
                         providerInfo = providerInfoA,
                         rank = 1,
-                        spanY = 3,
+                        spanY = if (communalResponsiveGrid()) 1 else 3,
                     ),
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 2,
                         providerInfo = providerInfoB,
                         rank = 2,
-                        spanY = 3,
+                        spanY = if (communalResponsiveGrid()) 1 else 3,
                     ),
                 )
         }
 
     @Test
     fun communalWidgets_updatedWhenProvidersUpdate() =
-        testScope.runTest {
+        kosmos.runTest {
             // Set up widgets and providers
             fakeWidgets.value =
                 mapOf(
                     CommunalItemRank(uid = 1L, rank = 1) to
-                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3),
+                        CommunalWidgetItem(
+                            uid = 1L,
+                            widgetId = 1,
+                            componentName = "pk_1/cls_1",
+                            itemId = 1L,
+                            userSerialNumber = 0,
+                            spanY = 3,
+                            spanYNew = 1,
+                        ),
                     CommunalItemRank(uid = 2L, rank = 2) to
-                        CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3),
+                        CommunalWidgetItem(
+                            uid = 2L,
+                            widgetId = 2,
+                            componentName = "pk_2/cls_2",
+                            itemId = 2L,
+                            userSerialNumber = 0,
+                            spanY = 6,
+                            spanYNew = 2,
+                        ),
                 )
             fakeProviders.value = mapOf(1 to providerInfoA, 2 to providerInfoB)
 
@@ -224,13 +293,13 @@
                         appWidgetId = 1,
                         providerInfo = providerInfoA,
                         rank = 1,
-                        spanY = 3,
+                        spanY = if (communalResponsiveGrid()) 1 else 3,
                     ),
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 2,
                         providerInfo = providerInfoB,
                         rank = 2,
-                        spanY = 3,
+                        spanY = if (communalResponsiveGrid()) 2 else 6,
                     ),
                 )
 
@@ -245,20 +314,20 @@
                         // Verify that provider info updated
                         providerInfo = providerInfoC,
                         rank = 1,
-                        spanY = 3,
+                        spanY = if (communalResponsiveGrid()) 1 else 3,
                     ),
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 2,
                         providerInfo = providerInfoB,
                         rank = 2,
-                        spanY = 3,
+                        spanY = if (communalResponsiveGrid()) 2 else 6,
                     ),
                 )
         }
 
     @Test
     fun addWidget_allocateId_bindWidget_andAddToDb() =
-        testScope.runTest {
+        kosmos.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val rank = 1
@@ -275,7 +344,8 @@
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
-            verify(communalWidgetDao).addWidget(id, provider, rank, testUserSerialNumber(mainUser))
+            verify(communalWidgetDao)
+                .addWidget(id, provider, rank, testUserSerialNumber(mainUser), SpanValue.Fixed(3))
 
             // Verify backup requested
             verify(backupManager).dataChanged()
@@ -283,7 +353,7 @@
 
     @Test
     fun addWidget_configurationFails_doNotAddWidgetToDb() =
-        testScope.runTest {
+        kosmos.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val rank = 1
@@ -301,7 +371,7 @@
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
             verify(communalWidgetDao, never())
-                .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), anyInt())
+                .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), any())
             verify(appWidgetHost).deleteAppWidgetId(id)
 
             // Verify backup not requested
@@ -310,7 +380,7 @@
 
     @Test
     fun addWidget_configurationThrowsError_doNotAddWidgetToDb() =
-        testScope.runTest {
+        kosmos.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val rank = 1
@@ -330,7 +400,7 @@
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
             verify(communalWidgetDao, never())
-                .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), anyInt())
+                .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt(), any())
             verify(appWidgetHost).deleteAppWidgetId(id)
 
             // Verify backup not requested
@@ -339,7 +409,7 @@
 
     @Test
     fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() =
-        testScope.runTest {
+        kosmos.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
             val rank = 1
@@ -356,7 +426,8 @@
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
-            verify(communalWidgetDao).addWidget(id, provider, rank, testUserSerialNumber(mainUser))
+            verify(communalWidgetDao)
+                .addWidget(id, provider, rank, testUserSerialNumber(mainUser), SpanValue.Fixed(3))
 
             // Verify backup requested
             verify(backupManager).dataChanged()
@@ -364,7 +435,7 @@
 
     @Test
     fun deleteWidget_deleteFromDbTrue_alsoDeleteFromHost() =
-        testScope.runTest {
+        kosmos.runTest {
             val id = 1
             whenever(communalWidgetDao.deleteWidgetById(eq(id))).thenReturn(true)
             underTest.deleteWidget(id)
@@ -379,7 +450,7 @@
 
     @Test
     fun deleteWidget_deleteFromDbFalse_doesNotDeleteFromHost() =
-        testScope.runTest {
+        kosmos.runTest {
             val id = 1
             whenever(communalWidgetDao.deleteWidgetById(eq(id))).thenReturn(false)
             underTest.deleteWidget(id)
@@ -394,7 +465,7 @@
 
     @Test
     fun reorderWidgets_queryDb() =
-        testScope.runTest {
+        kosmos.runTest {
             val widgetIdToRankMap = mapOf(104 to 1, 103 to 2, 101 to 3)
             underTest.updateWidgetOrder(widgetIdToRankMap)
             runCurrent()
@@ -407,7 +478,7 @@
 
     @Test
     fun restoreWidgets_deleteStateFileIfRestoreFails() =
-        testScope.runTest {
+        kosmos.runTest {
             // Write a state file that is invalid, and verify it is written
             backupUtils.writeBytesToDisk(byteArrayOf(1, 2, 3, 4, 5, 6))
             assertThat(backupUtils.fileExists()).isTrue()
@@ -422,7 +493,7 @@
 
     @Test
     fun restoreWidgets_deleteStateFileAfterWidgetsRestored() =
-        testScope.runTest {
+        kosmos.runTest {
             // Write a state file, and verify it is written
             backupUtils.writeBytesToDisk(fakeState.toByteArray())
             assertThat(backupUtils.fileExists()).isTrue()
@@ -443,7 +514,7 @@
 
     @Test
     fun restoreWidgets_restoredWidgetsNotRegisteredWithHostAreSkipped() =
-        testScope.runTest {
+        kosmos.runTest {
             // Write fake state to file
             backupUtils.writeBytesToDisk(fakeState.toByteArray())
 
@@ -470,7 +541,7 @@
 
     @Test
     fun restoreWidgets_registeredWidgetsNotRestoredAreRemoved() =
-        testScope.runTest {
+        kosmos.runTest {
             // Write fake state to file
             backupUtils.writeBytesToDisk(fakeState.toByteArray())
 
@@ -504,7 +575,7 @@
 
     @Test
     fun restoreWidgets_onlySomeWidgetsGotNewIds() =
-        testScope.runTest {
+        kosmos.runTest {
             // Write fake state to file
             backupUtils.writeBytesToDisk(fakeState.toByteArray())
 
@@ -536,7 +607,7 @@
 
     @Test
     fun restoreWidgets_undefinedUser_restoredAsMain() =
-        testScope.runTest {
+        kosmos.runTest {
             // Write two widgets to file, both of which have user serial number undefined.
             val fakeState =
                 CommunalHubState().apply {
@@ -584,7 +655,7 @@
 
     @Test
     fun restoreWidgets_workProfileNotRestored_widgetSkipped() =
-        testScope.runTest {
+        kosmos.runTest {
             // Write fake state to file
             backupUtils.writeBytesToDisk(fakeStateWithWorkProfile.toByteArray())
 
@@ -610,7 +681,7 @@
 
     @Test
     fun restoreWidgets_workProfileRestored_manuallyBindWidget() =
-        testScope.runTest {
+        kosmos.runTest {
             // Write fake state to file
             backupUtils.writeBytesToDisk(fakeStateWithWorkProfile.toByteArray())
 
@@ -649,7 +720,7 @@
                     componentNameCaptor.capture(),
                     eq(2),
                     eq(testUserSerialNumber(workProfile)),
-                    anyInt(),
+                    any(),
                 )
 
             assertThat(componentNameCaptor.firstValue)
@@ -658,13 +729,29 @@
 
     @Test
     fun pendingWidgets() =
-        testScope.runTest {
+        kosmos.runTest {
             fakeWidgets.value =
                 mapOf(
                     CommunalItemRank(uid = 1L, rank = 1) to
-                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3),
+                        CommunalWidgetItem(
+                            uid = 1L,
+                            widgetId = 1,
+                            componentName = "pk_1/cls_1",
+                            itemId = 1L,
+                            userSerialNumber = 0,
+                            spanY = 3,
+                            spanYNew = 1,
+                        ),
                     CommunalItemRank(uid = 2L, rank = 2) to
-                        CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0, 3),
+                        CommunalWidgetItem(
+                            uid = 2L,
+                            widgetId = 2,
+                            componentName = "pk_2/cls_2",
+                            itemId = 2L,
+                            userSerialNumber = 0,
+                            spanY = 3,
+                            spanYNew = 1,
+                        ),
                 )
 
             // Widget 1 is installed
@@ -672,7 +759,7 @@
 
             // Widget 2 is pending install
             val fakeIcon = mock<Bitmap>()
-            packageChangeRepository.setInstallSessions(
+            fakePackageChangeRepository.setInstallSessions(
                 listOf(
                     PackageInstallSession(
                         sessionId = 1,
@@ -690,7 +777,7 @@
                         appWidgetId = 1,
                         providerInfo = providerInfoA,
                         rank = 1,
-                        spanY = 3,
+                        spanY = if (communalResponsiveGrid()) 1 else 3,
                     ),
                     CommunalWidgetContentModel.Pending(
                         appWidgetId = 2,
@@ -698,23 +785,31 @@
                         componentName = ComponentName("pk_2", "cls_2"),
                         icon = fakeIcon,
                         user = mainUser,
-                        spanY = 3,
+                        spanY = if (communalResponsiveGrid()) 1 else 3,
                     ),
                 )
         }
 
     @Test
     fun pendingWidgets_pendingWidgetBecomesAvailableAfterInstall() =
-        testScope.runTest {
+        kosmos.runTest {
             fakeWidgets.value =
                 mapOf(
                     CommunalItemRank(uid = 1L, rank = 1) to
-                        CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0, 3)
+                        CommunalWidgetItem(
+                            uid = 1L,
+                            widgetId = 1,
+                            componentName = "pk_1/cls_1",
+                            itemId = 1L,
+                            userSerialNumber = 0,
+                            spanY = 3,
+                            spanYNew = 1,
+                        )
                 )
 
             // Widget 1 is pending install
             val fakeIcon = mock<Bitmap>()
-            packageChangeRepository.setInstallSessions(
+            fakePackageChangeRepository.setInstallSessions(
                 listOf(
                     PackageInstallSession(
                         sessionId = 1,
@@ -734,12 +829,12 @@
                         componentName = ComponentName("pk_1", "cls_1"),
                         icon = fakeIcon,
                         user = mainUser,
-                        spanY = 3,
+                        spanY = if (communalResponsiveGrid()) 1 else 3,
                     )
                 )
 
             // Package for widget 1 finished installing
-            packageChangeRepository.setInstallSessions(emptyList())
+            fakePackageChangeRepository.setInstallSessions(emptyList())
 
             // Provider info for widget 1 becomes available
             fakeProviders.value = mapOf(1 to providerInfoA)
@@ -752,15 +847,16 @@
                         appWidgetId = 1,
                         providerInfo = providerInfoA,
                         rank = 1,
-                        spanY = 3,
+                        spanY = if (communalResponsiveGrid()) 1 else 3,
                     )
                 )
         }
 
     @Test
     @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING)
-    fun updateWidgetSpanY_updatesWidgetInDaoAndRequestsBackup() =
-        testScope.runTest {
+    @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
+    fun updateWidgetSpanY_updatesWidgetInDaoAndRequestsBackup_fixed() =
+        kosmos.runTest {
             val widgetId = 1
             val newSpanY = 6
             val widgetIdToRankMap = emptyMap<Int, Int>()
@@ -768,7 +864,24 @@
             underTest.resizeWidget(widgetId, newSpanY, widgetIdToRankMap)
             runCurrent()
 
-            verify(communalWidgetDao).resizeWidget(widgetId, newSpanY, widgetIdToRankMap)
+            verify(communalWidgetDao)
+                .resizeWidget(widgetId, SpanValue.Fixed(newSpanY), widgetIdToRankMap)
+            verify(backupManager).dataChanged()
+        }
+
+    @Test
+    @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID)
+    fun updateWidgetSpanY_updatesWidgetInDaoAndRequestsBackup_responsive() =
+        kosmos.runTest {
+            val widgetId = 1
+            val newSpanY = 6
+            val widgetIdToRankMap = emptyMap<Int, Int>()
+
+            underTest.resizeWidget(widgetId, newSpanY, widgetIdToRankMap)
+            runCurrent()
+
+            verify(communalWidgetDao)
+                .resizeWidget(widgetId, SpanValue.Responsive(newSpanY), widgetIdToRankMap)
             verify(backupManager).dataChanged()
         }
 
@@ -784,13 +897,19 @@
     }
 
     private fun restoreUser(user: UserHandle) {
-        whenever(backupManager.getUserForAncestralSerialNumber(user.identifier.toLong()))
+        whenever(kosmos.backupManager.getUserForAncestralSerialNumber(user.identifier.toLong()))
             .thenReturn(user)
-        whenever(userManager.getUserSerialNumber(user.identifier))
+        whenever(kosmos.userManager.getUserSerialNumber(user.identifier))
             .thenReturn(testUserSerialNumber(user))
     }
 
-    private companion object {
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_RESPONSIVE_GRID)
+        }
+
         val PROVIDER_INFO_REQUIRES_CONFIGURATION =
             AppWidgetProviderInfo().apply { configure = ComponentName("test.pkg", "test.cmp") }
         val PROVIDER_INFO_CONFIGURATION_OPTIONAL =
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 611a61a6..b9e646f 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
@@ -24,14 +24,16 @@
 import android.os.UserHandle
 import android.os.UserManager
 import android.os.userManager
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
 import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
 import android.widget.RemoteViews
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID
 import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.broadcastDispatcher
@@ -96,6 +98,8 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 /**
  * This class of test cases assume that communal is enabled. For disabled cases, see
@@ -103,8 +107,8 @@
  */
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(AndroidJUnit4::class)
-class CommunalInteractorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
     @Mock private lateinit var mainUser: UserInfo
     @Mock private lateinit var secondaryUser: UserInfo
 
@@ -129,6 +133,10 @@
 
     private lateinit var underTest: CommunalInteractor
 
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -262,71 +270,84 @@
             assertThat(widgetContent!![2].appWidgetId).isEqualTo(3)
         }
 
+    /** TODO(b/378171351): Handle ongoing content in responsive grid. */
     @Test
+    @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun smartspaceDynamicSizing_oneCard_fullSize() =
         testSmartspaceDynamicSizing(
             totalTargets = 1,
-            expectedSizes = listOf(CommunalContentSize.FULL),
+            expectedSizes = listOf(CommunalContentSize.FixedSize.FULL),
         )
 
+    /** TODO(b/378171351): Handle ongoing content in responsive grid. */
     @Test
+    @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun smartspace_dynamicSizing_twoCards_halfSize() =
         testSmartspaceDynamicSizing(
             totalTargets = 2,
-            expectedSizes = listOf(CommunalContentSize.HALF, CommunalContentSize.HALF),
+            expectedSizes =
+                listOf(CommunalContentSize.FixedSize.HALF, CommunalContentSize.FixedSize.HALF),
         )
 
+    /** TODO(b/378171351): Handle ongoing content in responsive grid. */
     @Test
+    @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun smartspace_dynamicSizing_threeCards_thirdSize() =
         testSmartspaceDynamicSizing(
             totalTargets = 3,
             expectedSizes =
                 listOf(
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
+                    CommunalContentSize.FixedSize.THIRD,
+                    CommunalContentSize.FixedSize.THIRD,
+                    CommunalContentSize.FixedSize.THIRD,
                 ),
         )
 
+    /** TODO(b/378171351): Handle ongoing content in responsive grid. */
     @Test
+    @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun smartspace_dynamicSizing_fourCards_threeThirdSizeAndOneFullSize() =
         testSmartspaceDynamicSizing(
             totalTargets = 4,
             expectedSizes =
                 listOf(
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.FULL,
+                    CommunalContentSize.FixedSize.THIRD,
+                    CommunalContentSize.FixedSize.THIRD,
+                    CommunalContentSize.FixedSize.THIRD,
+                    CommunalContentSize.FixedSize.FULL,
                 ),
         )
 
+    /** TODO(b/378171351): Handle ongoing content in responsive grid. */
     @Test
+    @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun smartspace_dynamicSizing_fiveCards_threeThirdAndTwoHalfSize() =
         testSmartspaceDynamicSizing(
             totalTargets = 5,
             expectedSizes =
                 listOf(
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.HALF,
-                    CommunalContentSize.HALF,
+                    CommunalContentSize.FixedSize.THIRD,
+                    CommunalContentSize.FixedSize.THIRD,
+                    CommunalContentSize.FixedSize.THIRD,
+                    CommunalContentSize.FixedSize.HALF,
+                    CommunalContentSize.FixedSize.HALF,
                 ),
         )
 
+    /** TODO(b/378171351): Handle ongoing content in responsive grid. */
     @Test
+    @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun smartspace_dynamicSizing_sixCards_allThirdSize() =
         testSmartspaceDynamicSizing(
             totalTargets = 6,
             expectedSizes =
                 listOf(
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
-                    CommunalContentSize.THIRD,
+                    CommunalContentSize.FixedSize.THIRD,
+                    CommunalContentSize.FixedSize.THIRD,
+                    CommunalContentSize.FixedSize.THIRD,
+                    CommunalContentSize.FixedSize.THIRD,
+                    CommunalContentSize.FixedSize.THIRD,
+                    CommunalContentSize.FixedSize.THIRD,
                 ),
         )
 
@@ -383,7 +404,9 @@
             assertThat(umoContent?.size).isEqualTo(0)
         }
 
+    /** TODO(b/378171351): Handle ongoing content in responsive grid. */
     @Test
+    @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun ongoing_shouldOrderAndSizeByTimestamp() =
         testScope.runTest {
             // Keyguard showing, and tutorial completed.
@@ -410,15 +433,15 @@
             assertThat(ongoingContent?.size).isEqualTo(4)
             assertThat(ongoingContent?.get(0)?.key)
                 .isEqualTo(CommunalContentModel.KEY.smartspace("timer3"))
-            assertThat(ongoingContent?.get(0)?.size).isEqualTo(CommunalContentSize.HALF)
+            assertThat(ongoingContent?.get(0)?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
             assertThat(ongoingContent?.get(1)?.key)
                 .isEqualTo(CommunalContentModel.KEY.smartspace("timer2"))
-            assertThat(ongoingContent?.get(1)?.size).isEqualTo(CommunalContentSize.HALF)
+            assertThat(ongoingContent?.get(1)?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
             assertThat(ongoingContent?.get(2)?.key).isEqualTo(CommunalContentModel.KEY.umo())
-            assertThat(ongoingContent?.get(2)?.size).isEqualTo(CommunalContentSize.HALF)
+            assertThat(ongoingContent?.get(2)?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
             assertThat(ongoingContent?.get(3)?.key)
                 .isEqualTo(CommunalContentModel.KEY.smartspace("timer1"))
-            assertThat(ongoingContent?.get(3)?.size).isEqualTo(CommunalContentSize.HALF)
+            assertThat(ongoingContent?.get(3)?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
         }
 
     @Test
@@ -1082,6 +1105,7 @@
 
     @Test
     @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING)
+    @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun resizeWidget_withoutUpdatingOrder() =
         testScope.runTest {
             val userInfos = listOf(MAIN_USER_INFO)
@@ -1094,45 +1118,97 @@
                 appWidgetId = 1,
                 userId = MAIN_USER_INFO.id,
                 rank = 0,
-                spanY = CommunalContentSize.HALF.span,
+                spanY = CommunalContentSize.FixedSize.HALF.span,
             )
             widgetRepository.addWidget(
                 appWidgetId = 2,
                 userId = MAIN_USER_INFO.id,
                 rank = 1,
-                spanY = CommunalContentSize.HALF.span,
+                spanY = CommunalContentSize.FixedSize.HALF.span,
             )
             widgetRepository.addWidget(
                 appWidgetId = 3,
                 userId = MAIN_USER_INFO.id,
                 rank = 2,
-                spanY = CommunalContentSize.HALF.span,
+                spanY = CommunalContentSize.FixedSize.HALF.span,
             )
 
             val widgetContent by collectLastValue(underTest.widgetContent)
 
             assertThat(widgetContent?.map { it.appWidgetId to it.size })
                 .containsExactly(
-                    1 to CommunalContentSize.HALF,
-                    2 to CommunalContentSize.HALF,
-                    3 to CommunalContentSize.HALF,
+                    1 to CommunalContentSize.FixedSize.HALF,
+                    2 to CommunalContentSize.FixedSize.HALF,
+                    3 to CommunalContentSize.FixedSize.HALF,
                 )
                 .inOrder()
 
-            underTest.resizeWidget(2, CommunalContentSize.FULL.span, emptyMap())
+            underTest.resizeWidget(2, CommunalContentSize.FixedSize.FULL.span, emptyMap())
 
             // Widget 2 should have been resized to FULL
             assertThat(widgetContent?.map { it.appWidgetId to it.size })
                 .containsExactly(
-                    1 to CommunalContentSize.HALF,
-                    2 to CommunalContentSize.FULL,
-                    3 to CommunalContentSize.HALF,
+                    1 to CommunalContentSize.FixedSize.HALF,
+                    2 to CommunalContentSize.FixedSize.FULL,
+                    3 to CommunalContentSize.FixedSize.HALF,
+                )
+                .inOrder()
+        }
+
+    @Test
+    @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID)
+    fun resizeWidget_withoutUpdatingOrder_responsive() =
+        testScope.runTest {
+            val userInfos = listOf(MAIN_USER_INFO)
+            userRepository.setUserInfos(userInfos)
+            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
+            runCurrent()
+
+            // Widgets available.
+            widgetRepository.addWidget(
+                appWidgetId = 1,
+                userId = MAIN_USER_INFO.id,
+                rank = 0,
+                spanY = 1,
+            )
+            widgetRepository.addWidget(
+                appWidgetId = 2,
+                userId = MAIN_USER_INFO.id,
+                rank = 1,
+                spanY = 1,
+            )
+            widgetRepository.addWidget(
+                appWidgetId = 3,
+                userId = MAIN_USER_INFO.id,
+                rank = 2,
+                spanY = 1,
+            )
+
+            val widgetContent by collectLastValue(underTest.widgetContent)
+
+            assertThat(widgetContent?.map { it.appWidgetId to it.size })
+                .containsExactly(
+                    1 to CommunalContentSize.Responsive(1),
+                    2 to CommunalContentSize.Responsive(1),
+                    3 to CommunalContentSize.Responsive(1),
+                )
+                .inOrder()
+
+            underTest.resizeWidget(appWidgetId = 2, spanY = 5, widgetIdToRankMap = emptyMap())
+
+            // Widget 2 should have been resized to FULL
+            assertThat(widgetContent?.map { it.appWidgetId to it.size })
+                .containsExactly(
+                    1 to CommunalContentSize.Responsive(1),
+                    2 to CommunalContentSize.Responsive(5),
+                    3 to CommunalContentSize.Responsive(1),
                 )
                 .inOrder()
         }
 
     @Test
     @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING)
+    @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun resizeWidget_andUpdateOrder() =
         testScope.runTest {
             val userInfos = listOf(MAIN_USER_INFO)
@@ -1145,39 +1221,98 @@
                 appWidgetId = 1,
                 userId = MAIN_USER_INFO.id,
                 rank = 0,
-                spanY = CommunalContentSize.HALF.span,
+                spanY = CommunalContentSize.FixedSize.HALF.span,
             )
             widgetRepository.addWidget(
                 appWidgetId = 2,
                 userId = MAIN_USER_INFO.id,
                 rank = 1,
-                spanY = CommunalContentSize.HALF.span,
+                spanY = CommunalContentSize.FixedSize.HALF.span,
             )
             widgetRepository.addWidget(
                 appWidgetId = 3,
                 userId = MAIN_USER_INFO.id,
                 rank = 2,
-                spanY = CommunalContentSize.HALF.span,
+                spanY = CommunalContentSize.FixedSize.HALF.span,
             )
 
             val widgetContent by collectLastValue(underTest.widgetContent)
 
             assertThat(widgetContent?.map { it.appWidgetId to it.size })
                 .containsExactly(
-                    1 to CommunalContentSize.HALF,
-                    2 to CommunalContentSize.HALF,
-                    3 to CommunalContentSize.HALF,
+                    1 to CommunalContentSize.FixedSize.HALF,
+                    2 to CommunalContentSize.FixedSize.HALF,
+                    3 to CommunalContentSize.FixedSize.HALF,
                 )
                 .inOrder()
 
-            underTest.resizeWidget(2, CommunalContentSize.FULL.span, mapOf(2 to 0, 1 to 1))
+            underTest.resizeWidget(
+                2,
+                CommunalContentSize.FixedSize.FULL.span,
+                mapOf(2 to 0, 1 to 1),
+            )
 
             // Widget 2 should have been resized to FULL and moved to the front of the list
             assertThat(widgetContent?.map { it.appWidgetId to it.size })
                 .containsExactly(
-                    2 to CommunalContentSize.FULL,
-                    1 to CommunalContentSize.HALF,
-                    3 to CommunalContentSize.HALF,
+                    2 to CommunalContentSize.FixedSize.FULL,
+                    1 to CommunalContentSize.FixedSize.HALF,
+                    3 to CommunalContentSize.FixedSize.HALF,
+                )
+                .inOrder()
+        }
+
+    @Test
+    @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID)
+    fun resizeWidget_andUpdateOrder_responsive() =
+        testScope.runTest {
+            val userInfos = listOf(MAIN_USER_INFO)
+            userRepository.setUserInfos(userInfos)
+            userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
+            runCurrent()
+
+            // Widgets available.
+            widgetRepository.addWidget(
+                appWidgetId = 1,
+                userId = MAIN_USER_INFO.id,
+                rank = 0,
+                spanY = 1,
+            )
+            widgetRepository.addWidget(
+                appWidgetId = 2,
+                userId = MAIN_USER_INFO.id,
+                rank = 1,
+                spanY = 1,
+            )
+            widgetRepository.addWidget(
+                appWidgetId = 3,
+                userId = MAIN_USER_INFO.id,
+                rank = 2,
+                spanY = 1,
+            )
+
+            val widgetContent by collectLastValue(underTest.widgetContent)
+
+            assertThat(widgetContent?.map { it.appWidgetId to it.size })
+                .containsExactly(
+                    1 to CommunalContentSize.Responsive(1),
+                    2 to CommunalContentSize.Responsive(1),
+                    3 to CommunalContentSize.Responsive(1),
+                )
+                .inOrder()
+
+            underTest.resizeWidget(
+                appWidgetId = 2,
+                spanY = 5,
+                widgetIdToRankMap = mapOf(2 to 0, 1 to 1),
+            )
+
+            // Widget 2 should have been resized to FULL and moved to the front of the list
+            assertThat(widgetContent?.map { it.appWidgetId to it.size })
+                .containsExactly(
+                    2 to CommunalContentSize.Responsive(5),
+                    1 to CommunalContentSize.Responsive(1),
+                    3 to CommunalContentSize.Responsive(1),
                 )
                 .inOrder()
         }
@@ -1191,9 +1326,15 @@
         )
     }
 
-    private companion object {
-        val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
-        val USER_INFO_WORK =
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_RESPONSIVE_GRID)
+        }
+
+        private val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+        private val USER_INFO_WORK =
             UserInfo(
                 10,
                 "work",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
index 755c4eb..ca7e203 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
@@ -85,8 +85,7 @@
             assertThat(actions).isNotEmpty()
             assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
-            assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+            assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
 
             setUpState(
                 isShadeTouchable = false,
@@ -103,8 +102,7 @@
             assertThat(actions).isNotEmpty()
             assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
-            assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+            assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
         }
 
     @Test
@@ -122,7 +120,7 @@
             assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
             assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
 
             setUpState(
                 isShadeTouchable = false,
@@ -140,7 +138,7 @@
             assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
             assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
         }
 
     @Test
@@ -158,9 +156,7 @@
             assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
             assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(
-                    UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
-                )
+                .isEqualTo(UserActionResult.ShowOverlay(Overlays.NotificationsShade))
 
             setUpState(
                 isShadeTouchable = false,
@@ -174,9 +170,7 @@
             assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
             assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(
-                    UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
-                )
+                .isEqualTo(UserActionResult.ShowOverlay(Overlays.NotificationsShade))
         }
 
     private fun TestScope.setUpState(
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 5bbd3ff..18cc8bf 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
@@ -172,16 +172,16 @@
         }
 
     @Test
-    fun selectedKey_onReorderWidgets_isCleared() =
+    fun selectedKey_onReorderWidgets_isSet() =
         testScope.runTest {
             val selectedKey by collectLastValue(underTest.selectedKey)
 
-            val key = CommunalContentModel.KEY.widget(123)
-            underTest.setSelectedKey(key)
-            assertThat(selectedKey).isEqualTo(key)
-
-            underTest.onReorderWidgetStart()
+            underTest.setSelectedKey(null)
             assertThat(selectedKey).isNull()
+
+            val key = CommunalContentModel.KEY.widget(123)
+            underTest.onReorderWidgetStart(key)
+            assertThat(selectedKey).isEqualTo(key)
         }
 
     @Test
@@ -234,7 +234,7 @@
 
     @Test
     fun reorderWidget_uiEventLogging_start() {
-        underTest.onReorderWidgetStart()
+        underTest.onReorderWidgetStart(CommunalContentModel.KEY.widget(123))
         verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
     }
 
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 3eba8ff..763ea39 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
@@ -18,12 +18,14 @@
 
 import android.content.ComponentName
 import android.content.pm.UserInfo
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
 import android.widget.RemoteViews
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
 import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
@@ -248,7 +250,9 @@
                 .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
         }
 
+    /** TODO(b/378171351): Handle ongoing content in responsive grid. */
     @Test
+    @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun ongoingContent_umoAndOneTimer_sizedAppropriately() =
         testScope.runTest {
             // Widgets available.
@@ -280,11 +284,13 @@
             assertThat(timer).isInstanceOf(CommunalContentModel.Smartspace::class.java)
             assertThat(umo).isInstanceOf(CommunalContentModel.Umo::class.java)
 
-            assertThat(timer?.size).isEqualTo(CommunalContentSize.HALF)
-            assertThat(umo?.size).isEqualTo(CommunalContentSize.HALF)
+            assertThat(timer?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
+            assertThat(umo?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
         }
 
+    /** TODO(b/378171351): Handle ongoing content in responsive grid. */
     @Test
+    @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
     fun ongoingContent_umoAndTwoTimers_sizedAppropriately() =
         testScope.runTest {
             // Widgets available.
@@ -324,9 +330,9 @@
             assertThat(umo).isInstanceOf(CommunalContentModel.Umo::class.java)
 
             // One full-sized timer and a half-sized timer and half-sized UMO.
-            assertThat(timer1?.size).isEqualTo(CommunalContentSize.HALF)
-            assertThat(timer2?.size).isEqualTo(CommunalContentSize.HALF)
-            assertThat(umo?.size).isEqualTo(CommunalContentSize.FULL)
+            assertThat(timer1?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
+            assertThat(timer2?.size).isEqualTo(CommunalContentSize.FixedSize.HALF)
+            assertThat(umo?.size).isEqualTo(CommunalContentSize.FixedSize.FULL)
         }
 
     @Test
@@ -891,7 +897,8 @@
         @JvmStatic
         @Parameters(name = "{0}")
         fun getParams(): List<FlagsParameterization> {
-            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+            return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_RESPONSIVE_GRID)
+                .andSceneContainer()
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index d90d58b..1bb5c9a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -43,7 +43,6 @@
 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.scene.domain.interactor.sceneBackInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
 import com.android.systemui.scene.shared.model.Scenes
@@ -72,7 +71,6 @@
     private val trustRepository by lazy { kosmos.fakeTrustRepository }
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
-    private val sceneBackInteractor by lazy { kosmos.sceneBackInteractor }
     private val sceneContainerStartable by lazy { kosmos.sceneContainerStartable }
     private val sysuiStatusBarStateController by lazy { kosmos.sysuiStatusBarStateController }
     private lateinit var underTest: DeviceEntryInteractor
@@ -437,7 +435,9 @@
     fun isDeviceEntered_unlockedWhileOnShade_emitsTrue() =
         testScope.runTest {
             val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
+            val isDeviceEnteredDirectly by collectLastValue(underTest.isDeviceEnteredDirectly)
             assertThat(isDeviceEntered).isFalse()
+            assertThat(isDeviceEnteredDirectly).isFalse()
             val currentScene by collectLastValue(sceneInteractor.currentScene)
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
@@ -445,19 +445,20 @@
             switchToScene(Scenes.Shade)
             assertThat(currentScene).isEqualTo(Scenes.Shade)
             // Simulating a "leave it open when the keyguard is hidden" which means the bouncer will
-            // be
-            // shown and successful authentication should take the user back to where they are, the
-            // shade scene.
+            // be shown and successful authentication should take the user back to where they are,
+            // the shade scene.
             sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
             switchToScene(Scenes.Bouncer)
             assertThat(currentScene).isEqualTo(Scenes.Bouncer)
 
             assertThat(isDeviceEntered).isFalse()
+            assertThat(isDeviceEnteredDirectly).isFalse()
             // Authenticate with PIN to unlock and dismiss the lockscreen:
             authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
             runCurrent()
 
             assertThat(isDeviceEntered).isTrue()
+            assertThat(isDeviceEnteredDirectly).isFalse()
         }
 
     private fun TestScope.switchToScene(sceneKey: SceneKey) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractorImplTest.kt
new file mode 100644
index 0000000..e1344ca
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractorImplTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.domain.interactor
+
+import android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR
+import android.view.layoutInflater
+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.display.data.repository.fakeDisplayWindowPropertiesRepository
+import com.android.systemui.display.shared.model.DisplayWindowProperties
+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 DisplayWindowPropertiesInteractorImplTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val repo = kosmos.fakeDisplayWindowPropertiesRepository
+
+    private val underTest = kosmos.displayWindowPropertiesInteractor
+
+    @Test
+    fun getForStatusBar_returnsPropertiesWithCorrectWindowType() {
+        val displayId = 123
+        val statusBarWindowProperties = createDisplayWindowProperties(displayId, TYPE_STATUS_BAR)
+        val navBarWindowProperties = createDisplayWindowProperties(displayId, TYPE_NAVIGATION_BAR)
+        repo.insert(statusBarWindowProperties)
+        repo.insert(navBarWindowProperties)
+
+        assertThat(underTest.getForStatusBar(displayId)).isEqualTo(statusBarWindowProperties)
+    }
+
+    private fun createDisplayWindowProperties(displayId: Int, windowType: Int) =
+        DisplayWindowProperties(
+            displayId,
+            windowType,
+            context,
+            kosmos.windowManager,
+            kosmos.layoutInflater,
+        )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeScreenStateTest.java
new file mode 100644
index 0000000..bbd78b3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.doze;
+
+import static com.android.systemui.doze.DozeMachine.State.DOZE;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE;
+import static com.android.systemui.doze.DozeMachine.State.FINISH;
+import static com.android.systemui.doze.DozeMachine.State.INITIALIZED;
+import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
+import static com.android.systemui.utils.os.FakeHandler.Mode.QUEUEING;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Looper;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.UdfpsController;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.wakelock.WakeLockFake;
+import com.android.systemui.utils.os.FakeHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import javax.inject.Provider;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DozeScreenStateTest extends SysuiTestCase {
+
+    private DozeServiceFake mServiceFake;
+    private FakeHandler mHandlerFake;
+    @Mock
+    private DozeHost mDozeHost;
+    @Mock
+    private DozeParameters mDozeParameters;
+    private WakeLockFake mWakeLock;
+    private DozeScreenState mScreen;
+    @Mock
+    private Provider<UdfpsController> mUdfpsControllerProvider;
+    @Mock
+    private AuthController mAuthController;
+    @Mock
+    private UdfpsController mUdfpsController;
+    @Mock
+    private DozeLog mDozeLog;
+    @Mock
+    private DozeScreenBrightness mDozeScreenBrightness;
+    @Mock
+    private SelectedUserInteractor mSelectedUserInteractor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
+        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+        when(mUdfpsControllerProvider.get()).thenReturn(mUdfpsController);
+        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
+        when(mUdfpsController.isFingerDown()).thenReturn(false);
+
+        mServiceFake = new DozeServiceFake();
+        mHandlerFake = new FakeHandler(Looper.getMainLooper());
+        mWakeLock = new WakeLockFake();
+        mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters,
+                mWakeLock, mAuthController, mUdfpsControllerProvider, mDozeLog,
+                mDozeScreenBrightness, mSelectedUserInteractor);
+    }
+
+    @Test
+    public void testScreen_offInDoze() {
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE);
+
+        assertEquals(Display.STATE_OFF, mServiceFake.screenState);
+    }
+
+    @Test
+    public void testScreen_onInAod() {
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+        assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
+    }
+
+    @Test
+    public void testScreen_onInPulse() {
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE);
+
+        mScreen.transitionTo(DOZE, DOZE_REQUEST_PULSE);
+        mScreen.transitionTo(DOZE_REQUEST_PULSE, DOZE_PULSING);
+
+        assertEquals(Display.STATE_ON, mServiceFake.screenState);
+    }
+
+    @Test
+    public void testScreen_offInRequestPulseWithoutAoD() {
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE);
+
+        mScreen.transitionTo(DOZE, DOZE_REQUEST_PULSE);
+
+        assertEquals(Display.STATE_OFF, mServiceFake.screenState);
+    }
+
+    @Test
+    public void testScreen_offInRequestPulseWithAoD() {
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+        mScreen.transitionTo(DOZE, DOZE_REQUEST_PULSE);
+
+        assertEquals(Display.STATE_OFF, mServiceFake.screenState);
+    }
+
+    @Test
+    public void testScreen_onInDockedAod() {
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD_DOCKED);
+
+        assertEquals(Display.STATE_ON, mServiceFake.screenState);
+    }
+
+    @Test
+    public void test_initialScreenStatePostedToHandler() {
+        mHandlerFake.setMode(QUEUEING);
+
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mServiceFake.screenStateSet = false;
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+        assertFalse(mServiceFake.screenStateSet);
+
+        mHandlerFake.dispatchQueuedMessages();
+
+        assertTrue(mServiceFake.screenStateSet);
+        assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
+    }
+
+    @Test
+    public void test_noScreenStateSetAfterFinish() {
+        mHandlerFake.setMode(QUEUEING);
+
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        mScreen.transitionTo(DOZE_AOD, FINISH);
+
+        mServiceFake.screenStateSet = false;
+
+        mHandlerFake.dispatchQueuedMessages();
+
+        assertFalse(mServiceFake.screenStateSet);
+    }
+
+    @Test
+    public void test_holdsWakeLockWhenGoingToLowPowerDelayed() {
+        // Transition to low power mode will be delayed to let
+        // animations play at 60 fps.
+        when(mDozeParameters.shouldDelayDisplayDozeTransition()).thenReturn(true);
+        mHandlerFake.setMode(QUEUEING);
+
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mHandlerFake.dispatchQueuedMessages();
+
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        assertThat(mWakeLock.isHeld(), is(true));
+
+        mHandlerFake.dispatchQueuedMessages();
+        assertThat(mWakeLock.isHeld(), is(false));
+    }
+
+    @Test
+    public void test_releasesWakeLock_abortingLowPowerDelayed() {
+        // Transition to low power mode will be delayed to let
+        // animations play at 60 fps.
+        when(mDozeParameters.shouldDelayDisplayDozeTransition()).thenReturn(true);
+        mHandlerFake.setMode(QUEUEING);
+
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mHandlerFake.dispatchQueuedMessages();
+
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        assertThat(mWakeLock.isHeld(), is(true));
+        mScreen.transitionTo(DOZE_AOD, FINISH);
+
+        assertThat(mWakeLock.isHeld(), is(false));
+    }
+
+    @Test
+    public void test_animatesPausing() {
+        ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+        doAnswer(invocation -> null).when(mDozeHost).prepareForGentleSleep(captor.capture());
+        mHandlerFake.setMode(QUEUEING);
+
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD_PAUSING);
+        mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
+
+        mHandlerFake.dispatchQueuedMessages();
+        verify(mDozeHost).prepareForGentleSleep(eq(captor.getValue()));
+        captor.getValue().run();
+        assertEquals(Display.STATE_OFF, mServiceFake.screenState);
+    }
+
+    @Test
+    public void test_animatesOff() {
+        ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+        doAnswer(invocation -> null).when(mDozeHost).prepareForGentleSleep(captor.capture());
+        mHandlerFake.setMode(QUEUEING);
+
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        mScreen.transitionTo(DOZE_AOD, DOZE);
+
+        mHandlerFake.dispatchQueuedMessages();
+        verify(mDozeHost).prepareForGentleSleep(eq(captor.getValue()));
+        captor.getValue().run();
+        assertEquals(Display.STATE_OFF, mServiceFake.screenState);
+    }
+
+    @Test
+    public void testDelayEnterDozeScreenState_whenUdfpsFingerDown() {
+        // GIVEN AOD is initialized
+        when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
+        mHandlerFake.setMode(QUEUEING);
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mHandlerFake.dispatchQueuedMessages();
+
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+        // WHEN udfps is activated (fingerDown)
+        when(mUdfpsController.isFingerDown()).thenReturn(true);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // THEN the display screen state doesn't immediately change
+        assertEquals(Display.STATE_ON, mServiceFake.screenState);
+
+        // WHEN udfpsController finger is no longer down and the queued messages are run
+        when(mUdfpsController.isFingerDown()).thenReturn(false);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // THEN the display screen state will change
+        assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
+    }
+
+    @Test
+    public void testDelayExitPulsingScreenState_whenUdfpsFingerDown() {
+        // GIVEN we're pulsing
+        when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
+        mHandlerFake.setMode(QUEUEING);
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        mScreen.transitionTo(DOZE_AOD, DOZE_REQUEST_PULSE);
+        mScreen.transitionTo(DOZE_REQUEST_PULSE, DOZE_PULSING);
+        mScreen.transitionTo(DOZE_PULSING, DOZE_PULSE_DONE);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // WHEN udfps is activated while are transitioning back to DOZE_AOD
+        mScreen.transitionTo(DOZE_PULSE_DONE, DOZE_AOD);
+        when(mUdfpsController.isFingerDown()).thenReturn(true);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // THEN the display screen state doesn't immediately change
+        assertEquals(Display.STATE_ON, mServiceFake.screenState);
+
+        // WHEN udfpsController finger is no longer down and the queued messages are run
+        when(mUdfpsController.isFingerDown()).thenReturn(false);
+        mHandlerFake.dispatchQueuedMessages();
+
+        // THEN the display screen state will change
+        assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
+    }
+
+    @Test
+    public void authCallbackRemovedOnDestroy() {
+        mScreen.destroy();
+
+        verify(mAuthController).removeCallback(any());
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index bbfc960..f924ccb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -36,7 +36,6 @@
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
 import androidx.test.filters.SmallTest
-import com.android.app.viewcapture.ViewCapture
 import com.android.app.viewcapture.ViewCaptureAwareWindowManager
 import com.android.app.viewcapture.ViewCaptureFactory
 import com.android.compose.animation.scene.ObservableTransitionState
@@ -44,15 +43,15 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB_ON_MOBILE
 import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.ambient.touch.TouchHandler
 import com.android.systemui.ambient.touch.TouchMonitor
 import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
 import com.android.systemui.ambient.touch.scrim.ScrimController
 import com.android.systemui.ambient.touch.scrim.ScrimManager
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
-import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.communalInteractor
@@ -63,7 +62,9 @@
 import com.android.systemui.complication.ComplicationLayoutEngine
 import com.android.systemui.complication.dagger.ComplicationComponent
 import com.android.systemui.dreams.complication.HideComplicationTouchHandler
+import com.android.systemui.dreams.complication.dagger.DreamComplicationComponent
 import com.android.systemui.dreams.dagger.DreamOverlayComponent
+import com.android.systemui.dreams.touch.CommunalTouchHandler
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.gesture.domain.gestureInteractor
@@ -87,21 +88,17 @@
 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.Mockito.clearInvocations
 import org.mockito.Mockito.isNull
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.firstValue
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
-import org.mockito.kotlin.times
 import org.mockito.kotlin.verifyNoMoreInteractions
 import org.mockito.kotlin.whenever
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
@@ -117,73 +114,53 @@
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
-    @Mock lateinit var mLifecycleOwner: DreamOverlayLifecycleOwner
+    private val mLifecycleOwner = mock<DreamOverlayLifecycleOwner>()
+    private val mDreamOverlayCallback = mock<IDreamOverlayCallback>()
+    private val mWindowManager = mock<WindowManagerImpl>()
+    private val mComplicationComponentFactory = mock<ComplicationComponent.Factory>()
+    private val mComplicationHostViewController = mock<ComplicationHostViewController>()
+    private val mComplicationVisibilityController = mock<ComplicationLayoutEngine>()
+    private val mDreamComplicationComponentFactory = mock<DreamComplicationComponent.Factory>()
+    private val mHideComplicationTouchHandler = mock<HideComplicationTouchHandler>()
+    private val mDreamOverlayComponentFactory = mock<DreamOverlayComponent.Factory>()
+    private val mCommunalTouchHandler = mock<CommunalTouchHandler>()
+    private val mAmbientTouchComponentFactory = mock<AmbientTouchComponent.Factory>()
+    private val mDreamOverlayContainerView = mock<DreamOverlayContainerView>()
+    private val mDreamOverlayContainerViewController =
+        mock<DreamOverlayContainerViewController> {
+            on { containerView }.thenReturn(mDreamOverlayContainerView)
+        }
+    private val mKeyguardUpdateMonitor = mock<KeyguardUpdateMonitor>()
+    private val mTouchMonitor = mock<TouchMonitor>()
+    private val mStateController = mock<DreamOverlayStateController>()
+    private val mDreamOverlayContainerViewParent = mock<ViewGroup>()
+    private val mTouchInsetManager = mock<TouchInsetManager>()
+    private val mUiEventLogger = mock<UiEventLogger>()
+    private val mScrimController = mock<ScrimController>()
+    private val mScrimManager =
+        mock<ScrimManager> { on { currentController }.thenReturn(mScrimController) }
+    private val mSystemDialogsCloser = mock<SystemDialogsCloser>()
+    private val mDreamOverlayCallbackController = mock<DreamOverlayCallbackController>()
+    private val mLazyViewCapture = lazy { viewCaptureSpy }
 
-    private lateinit var lifecycleRegistry: FakeLifecycleRegistry
+    private val mViewCaptor = argumentCaptor<View>()
+    private val mTouchHandlersCaptor = argumentCaptor<Set<TouchHandler>>()
 
-    lateinit var mCommunalInteractor: CommunalInteractor
-
-    private lateinit var mWindowParams: WindowManager.LayoutParams
-
-    @Mock lateinit var mDreamOverlayCallback: IDreamOverlayCallback
-
-    @Mock lateinit var mWindowManager: WindowManagerImpl
-
-    @Mock lateinit var mComplicationComponentFactory: ComplicationComponent.Factory
-
-    @Mock lateinit var mComplicationHostViewController: ComplicationHostViewController
-
-    @Mock lateinit var mComplicationVisibilityController: ComplicationLayoutEngine
-
-    @Mock
-    lateinit var mDreamComplicationComponentFactory:
-        com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
-
-    @Mock lateinit var mHideComplicationTouchHandler: HideComplicationTouchHandler
-
-    @Mock lateinit var mDreamOverlayComponentFactory: DreamOverlayComponent.Factory
-
-    @Mock lateinit var mAmbientTouchComponentFactory: AmbientTouchComponent.Factory
-
-    @Mock lateinit var mDreamOverlayContainerView: DreamOverlayContainerView
-
-    @Mock lateinit var mDreamOverlayContainerViewController: DreamOverlayContainerViewController
-
-    @Mock lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
-
-    @Mock lateinit var mTouchMonitor: TouchMonitor
-
-    @Mock lateinit var mStateController: DreamOverlayStateController
-
-    @Mock lateinit var mDreamOverlayContainerViewParent: ViewGroup
-
-    @Mock lateinit var mTouchInsetManager: TouchInsetManager
-
-    @Mock lateinit var mUiEventLogger: UiEventLogger
-
-    @Mock lateinit var mScrimManager: ScrimManager
-
-    @Mock lateinit var mScrimController: ScrimController
-
-    @Mock lateinit var mSystemDialogsCloser: SystemDialogsCloser
-
-    @Mock lateinit var mDreamOverlayCallbackController: DreamOverlayCallbackController
-
-    @Mock lateinit var mLazyViewCapture: Lazy<ViewCapture>
-
-    private lateinit var mViewCaptureAwareWindowManager: ViewCaptureAwareWindowManager
-    private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
-    private lateinit var communalRepository: FakeCommunalSceneRepository
+    private val mWindowParams = WindowManager.LayoutParams()
+    private val lifecycleRegistry = FakeLifecycleRegistry(mLifecycleOwner)
+    private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+    private val communalRepository = kosmos.fakeCommunalSceneRepository
     private var viewCaptureSpy = spy(ViewCaptureFactory.getInstance(context))
-    private lateinit var gestureInteractor: GestureInteractor
+    private val gestureInteractor = spy(kosmos.gestureInteractor)
+
+    private lateinit var mCommunalInteractor: CommunalInteractor
+    private lateinit var mViewCaptureAwareWindowManager: ViewCaptureAwareWindowManager
     private lateinit var environmentComponents: EnvironmentComponents
 
-    @Captor var mViewCaptor: ArgumentCaptor<View>? = null
     private lateinit var mService: DreamOverlayService
 
     private class EnvironmentComponents(
-        val dreamsComplicationComponent:
-            com.android.systemui.dreams.complication.dagger.ComplicationComponent,
+        val dreamsComplicationComponent: DreamComplicationComponent,
         val dreamOverlayComponent: DreamOverlayComponent,
         val complicationComponent: ComplicationComponent,
         val ambientTouchComponent: AmbientTouchComponent,
@@ -208,8 +185,7 @@
     }
 
     private fun setupComponentFactories(
-        dreamComplicationComponentFactory:
-            com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory,
+        dreamComplicationComponentFactory: DreamComplicationComponent.Factory,
         dreamOverlayComponentFactory: DreamOverlayComponent.Factory,
         complicationComponentFactory: ComplicationComponent.Factory,
         ambientTouchComponentFactory: AmbientTouchComponent.Factory,
@@ -230,10 +206,10 @@
         whenever(complicationComponent.getVisibilityController())
             .thenReturn(mComplicationVisibilityController)
 
-        val dreamComplicationComponent =
-            mock<com.android.systemui.dreams.complication.dagger.ComplicationComponent>()
+        val dreamComplicationComponent = mock<DreamComplicationComponent>()
         whenever(dreamComplicationComponent.getHideComplicationTouchHandler())
             .thenReturn(mHideComplicationTouchHandler)
+        whenever(dreamOverlayComponent.communalTouchHandler).thenReturn(mCommunalTouchHandler)
         whenever(dreamComplicationComponentFactory.create(any(), any()))
             .thenReturn(dreamComplicationComponent)
 
@@ -259,13 +235,6 @@
 
     @Before
     fun setup() {
-        MockitoAnnotations.initMocks(this)
-
-        lifecycleRegistry = FakeLifecycleRegistry(mLifecycleOwner)
-        bouncerRepository = kosmos.fakeKeyguardBouncerRepository
-        communalRepository = kosmos.fakeCommunalSceneRepository
-        gestureInteractor = spy(kosmos.gestureInteractor)
-
         environmentComponents =
             setupComponentFactories(
                 mDreamComplicationComponentFactory,
@@ -273,12 +242,6 @@
                 mComplicationComponentFactory,
                 mAmbientTouchComponentFactory,
             )
-
-        whenever(mDreamOverlayContainerViewController.containerView)
-            .thenReturn(mDreamOverlayContainerView)
-        whenever(mScrimManager.getCurrentController()).thenReturn(mScrimController)
-        whenever(mLazyViewCapture.value).thenReturn(viewCaptureSpy)
-        mWindowParams = WindowManager.LayoutParams()
         mViewCaptureAwareWindowManager =
             ViewCaptureAwareWindowManager(
                 mWindowManager,
@@ -381,10 +344,10 @@
         verify(mStateController).setOverlayActive(false)
         verify(mStateController).setLowLightActive(false)
         verify(mStateController).setEntryAnimationsFinished(false)
-        verify(mStateController, Mockito.never()).setOverlayActive(true)
-        verify(mUiEventLogger, Mockito.never())
+        verify(mStateController, never()).setOverlayActive(true)
+        verify(mUiEventLogger, never())
             .log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START)
-        verify(mDreamOverlayCallbackController, Mockito.never()).onStartDream()
+        verify(mDreamOverlayCallbackController, never()).onStartDream()
     }
 
     @Test
@@ -528,14 +491,14 @@
         mMainExecutor.runAllReady()
 
         // Verify view added.
-        verify(mWindowManager).addView(mViewCaptor!!.capture(), any())
+        verify(mWindowManager).addView(mViewCaptor.capture(), any())
 
         // Service destroyed.
         mService.onEndDream()
         mMainExecutor.runAllReady()
 
         // Verify view removed.
-        verify(mWindowManager).removeView(mViewCaptor!!.value)
+        verify(mWindowManager).removeView(mViewCaptor.firstValue)
 
         // Verify state correctly set.
         verify(mStateController).setOverlayActive(false)
@@ -567,8 +530,8 @@
 
         // The overlay starts then finishes.
         val inOrder = Mockito.inOrder(mWindowManager)
-        inOrder.verify(mWindowManager).addView(mViewCaptor!!.capture(), any())
-        inOrder.verify(mWindowManager).removeView(mViewCaptor!!.value)
+        inOrder.verify(mWindowManager).addView(mViewCaptor.capture(), any())
+        inOrder.verify(mWindowManager).removeView(mViewCaptor.firstValue)
     }
 
     @Test
@@ -596,8 +559,8 @@
 
         // The overlay starts then finishes.
         val inOrder = Mockito.inOrder(mWindowManager)
-        inOrder.verify(mWindowManager).addView(mViewCaptor!!.capture(), any())
-        inOrder.verify(mWindowManager).removeView(mViewCaptor!!.value)
+        inOrder.verify(mWindowManager).addView(mViewCaptor.capture(), any())
+        inOrder.verify(mWindowManager).removeView(mViewCaptor.firstValue)
     }
 
     @Test
@@ -615,14 +578,14 @@
         mMainExecutor.runAllReady()
 
         // Verify view added.
-        verify(mWindowManager).addView(mViewCaptor!!.capture(), any())
+        verify(mWindowManager).addView(mViewCaptor.capture(), any())
 
         // Service destroyed.
         mService.onDestroy()
         mMainExecutor.runAllReady()
 
         // Verify view removed.
-        verify(mWindowManager).removeView(mViewCaptor!!.value)
+        verify(mWindowManager).removeView(mViewCaptor.firstValue)
 
         // Verify state correctly set.
         verify(mKeyguardUpdateMonitor).removeCallback(any())
@@ -639,7 +602,7 @@
         mMainExecutor.runAllReady()
 
         // Verify no view is removed.
-        verify(mWindowManager, Mockito.never()).removeView(any())
+        verify(mWindowManager, never()).removeView(any())
 
         // Verify state still correctly set.
         verify(mKeyguardUpdateMonitor).removeCallback(any())
@@ -665,7 +628,7 @@
             false, /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
-        verify(mWindowManager, Mockito.never()).addView(any(), any())
+        verify(mWindowManager, never()).addView(any(), any())
     }
 
     @Test
@@ -673,7 +636,7 @@
         // Service destroyed before dream started.
         mService.onDestroy()
         mMainExecutor.runAllReady()
-        verify(mWindowManager, Mockito.never()).removeView(any())
+        verify(mWindowManager, never()).removeView(any())
     }
 
     @Test
@@ -691,8 +654,8 @@
         mMainExecutor.runAllReady()
 
         // Verify that a new window is added.
-        verify(mWindowManager).addView(mViewCaptor!!.capture(), any())
-        val windowDecorView = mViewCaptor!!.value
+        verify(mWindowManager).addView(mViewCaptor.capture(), any())
+        val windowDecorView = mViewCaptor.firstValue
 
         // Assert that the overlay is not showing complications.
         assertThat(mService.shouldShowComplications()).isFalse()
@@ -751,7 +714,7 @@
     @Test
     fun testWakeUpBeforeStartDoesNothing() {
         mService.onWakeUp()
-        verify(mDreamOverlayContainerViewController, Mockito.never()).onWakeUp()
+        verify(mDreamOverlayContainerViewController, never()).onWakeUp()
     }
 
     @Test
@@ -879,8 +842,8 @@
         )
         mMainExecutor.runAllReady()
 
-        whenever(mDreamOverlayContainerViewController.isBouncerShowing()).thenReturn(true)
-        mService!!.onComeToFront()
+        whenever(mDreamOverlayContainerViewController.isBouncerShowing).thenReturn(true)
+        mService.onComeToFront()
         verify(mScrimController).expand(any())
     }
 
@@ -900,7 +863,7 @@
         )
         mMainExecutor.runAllReady()
 
-        mService!!.onComeToFront()
+        mService.onComeToFront()
         assertThat(communalRepository.currentScene.value).isEqualTo(CommunalScenes.Blank)
     }
 
@@ -920,7 +883,7 @@
         )
         mMainExecutor.runAllReady()
 
-        mService!!.onComeToFront()
+        mService.onComeToFront()
         verify(mSystemDialogsCloser).closeSystemDialogs()
     }
 
@@ -1320,6 +1283,45 @@
         environmentComponents.verifyNoMoreInteractions()
     }
 
+    @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+    @Test
+    fun testAmbientTouchHandlersRegistration_registerHideComplicationAndCommunal() {
+        val client = client
+
+        // Inform the overlay service of dream starting.
+        client.startDream(
+            mWindowParams,
+            mDreamOverlayCallback,
+            DREAM_COMPONENT,
+            false /*isPreview*/,
+            false, /*shouldShowComplication*/
+        )
+        mMainExecutor.runAllReady()
+
+        verify(mAmbientTouchComponentFactory).create(any(), mTouchHandlersCaptor.capture(), any())
+        assertThat(mTouchHandlersCaptor.firstValue)
+            .containsExactly(mHideComplicationTouchHandler, mCommunalTouchHandler)
+    }
+
+    @EnableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+    @Test
+    fun testAmbientTouchHandlersRegistration_v2_registerOnlyHideComplication() {
+        val client = client
+
+        // Inform the overlay service of dream starting.
+        client.startDream(
+            mWindowParams,
+            mDreamOverlayCallback,
+            DREAM_COMPONENT,
+            false /*isPreview*/,
+            false, /*shouldShowComplication*/
+        )
+        mMainExecutor.runAllReady()
+
+        verify(mAmbientTouchComponentFactory).create(any(), mTouchHandlersCaptor.capture(), any())
+        assertThat(mTouchHandlersCaptor.firstValue).containsExactly(mHideComplicationTouchHandler)
+    }
+
     internal class FakeLifecycleRegistry(provider: LifecycleOwner) : LifecycleRegistry(provider) {
         val mLifecycles: MutableList<State> = ArrayList()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
index 82bcece..d6daa79 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.flags.EnableSceneContainer
@@ -72,8 +73,10 @@
 
     @Test
     @DisableFlags(DualShade.FLAG_NAME)
-    fun actions_singleShade() =
+    fun actions_communalNotAvailable_singleShade() =
         testScope.runTest {
+            kosmos.setCommunalAvailable(false)
+
             val actions by collectLastValue(underTest.actions)
 
             setUpState(
@@ -83,8 +86,9 @@
             )
             assertThat(actions).isNotEmpty()
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
-            assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+            assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
+            assertThat(actions?.get(Swipe.Start)).isNull()
+            assertThat(actions?.get(Swipe.End)).isNull()
 
             setUpState(
                 isShadeTouchable = false,
@@ -100,14 +104,17 @@
             )
             assertThat(actions).isNotEmpty()
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
-            assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+            assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
+            assertThat(actions?.get(Swipe.Start)).isNull()
+            assertThat(actions?.get(Swipe.End)).isNull()
         }
 
     @Test
     @DisableFlags(DualShade.FLAG_NAME)
-    fun actions_splitShade() =
+    fun actions_communalNotAvailable_splitShade() =
         testScope.runTest {
+            kosmos.setCommunalAvailable(false)
+
             val actions by collectLastValue(underTest.actions)
 
             setUpState(
@@ -118,7 +125,9 @@
             assertThat(actions).isNotEmpty()
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
             assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+            assertThat(actions?.get(Swipe.Start)).isNull()
+            assertThat(actions?.get(Swipe.End)).isNull()
 
             setUpState(
                 isShadeTouchable = false,
@@ -135,13 +144,17 @@
             assertThat(actions).isNotEmpty()
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
             assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+            assertThat(actions?.get(Swipe.Start)).isNull()
+            assertThat(actions?.get(Swipe.End)).isNull()
         }
 
     @Test
     @EnableFlags(DualShade.FLAG_NAME)
-    fun actions_dualShade() =
+    fun actions_communalNotAvailable_dualShade() =
         testScope.runTest {
+            kosmos.setCommunalAvailable(false)
+
             val actions by collectLastValue(underTest.actions)
 
             setUpState(
@@ -152,9 +165,9 @@
             assertThat(actions).isNotEmpty()
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
             assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(
-                    UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
-                )
+                .isEqualTo(UserActionResult.ShowOverlay(Overlays.NotificationsShade))
+            assertThat(actions?.get(Swipe.Start)).isNull()
+            assertThat(actions?.get(Swipe.End)).isNull()
 
             setUpState(
                 isShadeTouchable = false,
@@ -167,9 +180,123 @@
             assertThat(actions).isNotEmpty()
             assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
             assertThat(actions?.get(Swipe.Down))
-                .isEqualTo(
-                    UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
-                )
+                .isEqualTo(UserActionResult.ShowOverlay(Overlays.NotificationsShade))
+            assertThat(actions?.get(Swipe.Start)).isNull()
+            assertThat(actions?.get(Swipe.End)).isNull()
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun actions_communalAvailable_singleShade() =
+        testScope.runTest {
+            kosmos.setCommunalAvailable(true)
+
+            val actions by collectLastValue(underTest.actions)
+
+            setUpState(
+                isShadeTouchable = true,
+                isDeviceUnlocked = false,
+                shadeMode = ShadeMode.Single,
+            )
+            assertThat(actions).isNotEmpty()
+            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+            assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
+            assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+            assertThat(actions?.get(Swipe.End)).isNull()
+
+            setUpState(
+                isShadeTouchable = false,
+                isDeviceUnlocked = false,
+                shadeMode = ShadeMode.Single,
+            )
+            assertThat(actions).isEmpty()
+
+            setUpState(
+                isShadeTouchable = true,
+                isDeviceUnlocked = true,
+                shadeMode = ShadeMode.Single,
+            )
+            assertThat(actions).isNotEmpty()
+            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+            assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
+            assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+            assertThat(actions?.get(Swipe.End)).isNull()
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun actions_communalAvailable_splitShade() =
+        testScope.runTest {
+            kosmos.setCommunalAvailable(true)
+
+            val actions by collectLastValue(underTest.actions)
+
+            setUpState(
+                isShadeTouchable = true,
+                isDeviceUnlocked = false,
+                shadeMode = ShadeMode.Split,
+            )
+            assertThat(actions).isNotEmpty()
+            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+            assertThat(actions?.get(Swipe.Down))
+                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+            assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+            assertThat(actions?.get(Swipe.End)).isNull()
+
+            setUpState(
+                isShadeTouchable = false,
+                isDeviceUnlocked = false,
+                shadeMode = ShadeMode.Split,
+            )
+            assertThat(actions).isEmpty()
+
+            setUpState(
+                isShadeTouchable = true,
+                isDeviceUnlocked = true,
+                shadeMode = ShadeMode.Split,
+            )
+            assertThat(actions).isNotEmpty()
+            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+            assertThat(actions?.get(Swipe.Down))
+                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+            assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+            assertThat(actions?.get(Swipe.End)).isNull()
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun actions_communalAvailable_dualShade() =
+        testScope.runTest {
+            kosmos.setCommunalAvailable(true)
+
+            val actions by collectLastValue(underTest.actions)
+
+            setUpState(
+                isShadeTouchable = true,
+                isDeviceUnlocked = false,
+                shadeMode = ShadeMode.Dual,
+            )
+            assertThat(actions).isNotEmpty()
+            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+            assertThat(actions?.get(Swipe.Down))
+                .isEqualTo(UserActionResult.ShowOverlay(Overlays.NotificationsShade))
+            assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+            assertThat(actions?.get(Swipe.End)).isNull()
+
+            setUpState(
+                isShadeTouchable = false,
+                isDeviceUnlocked = false,
+                shadeMode = ShadeMode.Dual,
+            )
+            assertThat(actions).isEmpty()
+
+            setUpState(isShadeTouchable = true, isDeviceUnlocked = true, shadeMode = ShadeMode.Dual)
+            assertThat(actions).isNotEmpty()
+            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+            assertThat(actions?.get(Swipe.Down))
+                .isEqualTo(UserActionResult.ShowOverlay(Overlays.NotificationsShade))
+            assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+            assertThat(actions?.get(Swipe.End)).isNull()
         }
 
     private fun TestScope.setUpState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
index dd83702..248b922 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
@@ -20,17 +20,20 @@
 import android.app.Notification
 import android.app.NotificationManager
 import android.content.applicationContext
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
 import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
+import com.android.systemui.contextualeducation.GestureType.OVERVIEW
 import com.android.systemui.education.data.repository.fakeEduClock
 import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor
 import com.android.systemui.education.domain.interactor.contextualEducationInteractor
 import com.android.systemui.education.domain.interactor.keyboardTouchpadEduInteractor
 import com.android.systemui.education.ui.view.ContextualEduUiCoordinator
 import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
@@ -52,13 +55,16 @@
 import org.mockito.Mock
 import org.mockito.junit.MockitoJUnit
 import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
-class ContextualEduUiCoordinatorTest : SysuiTestCase() {
+class ContextualEduUiCoordinatorTest(private val gestureType: GestureType) : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val interactor = kosmos.contextualEducationInteractor
@@ -66,6 +72,7 @@
     private val minDurationForNextEdu =
         KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds
     private lateinit var underTest: ContextualEduUiCoordinator
+    private lateinit var previousDialog: Dialog
     @Mock private lateinit var dialog: Dialog
     @Mock private lateinit var notificationManager: NotificationManager
     @Mock private lateinit var accessibilityManagerWrapper: AccessibilityManagerWrapper
@@ -95,9 +102,11 @@
                 kosmos.applicationCoroutineScope,
                 viewModel,
                 kosmos.applicationContext,
-                notificationManager
+                notificationManager,
             ) { model ->
                 toastContent = model.message
+                previousDialog = dialog
+                dialog = mock<Dialog>()
                 dialog
             }
         underTest.start()
@@ -107,56 +116,98 @@
     @Test
     fun showDialogOnNewEdu() =
         testScope.runTest {
-            triggerEducation(BACK)
+            triggerEducation(gestureType)
             verify(dialog).show()
         }
 
     @Test
     fun showNotificationOn2ndEdu() =
         testScope.runTest {
-            triggerEducation(BACK)
+            triggerEducation(gestureType)
             eduClock.offset(minDurationForNextEdu)
-            triggerEducation(BACK)
+            triggerEducation(gestureType)
             verify(notificationManager).notifyAsUser(any(), anyInt(), any(), any())
         }
 
     @Test
     fun dismissDialogAfterTimeout() =
         testScope.runTest {
-            triggerEducation(BACK)
+            triggerEducation(gestureType)
             advanceTimeBy(timeoutMillis + 1)
             verify(dialog).dismiss()
         }
 
     @Test
-    fun verifyBackEduToastContent() =
+    fun dismissPreviousDialogOnNewDialog() =
         testScope.runTest {
             triggerEducation(BACK)
-            assertThat(toastContent).isEqualTo(context.getString(R.string.back_edu_toast_content))
+            triggerEducation(HOME)
+            verify(previousDialog).dismiss()
         }
 
     @Test
-    fun verifyBackEduNotificationContent() =
+    fun verifyEduToastContent() =
+        testScope.runTest {
+            triggerEducation(gestureType)
+
+            val expectedContent =
+                when (gestureType) {
+                    BACK -> R.string.back_edu_toast_content
+                    HOME -> R.string.home_edu_toast_content
+                    OVERVIEW -> R.string.overview_edu_toast_content
+                    ALL_APPS -> R.string.all_apps_edu_toast_content
+                }
+
+            assertThat(toastContent).isEqualTo(context.getString(expectedContent))
+        }
+
+    @Test
+    fun verifyEduNotificationContent() =
         testScope.runTest {
             val notificationCaptor = ArgumentCaptor.forClass(Notification::class.java)
-            triggerEducation(BACK)
+            triggerEducation(gestureType)
 
             eduClock.offset(minDurationForNextEdu)
-            triggerEducation(BACK)
+            triggerEducation(gestureType)
 
             verify(notificationManager)
                 .notifyAsUser(any(), anyInt(), notificationCaptor.capture(), any())
+
+            val expectedTitle =
+                when (gestureType) {
+                    BACK -> R.string.back_edu_notification_title
+                    HOME -> R.string.home_edu_notification_title
+                    OVERVIEW -> R.string.overview_edu_notification_title
+                    ALL_APPS -> R.string.all_apps_edu_notification_title
+                }
+
+            val expectedContent =
+                when (gestureType) {
+                    BACK -> R.string.back_edu_notification_content
+                    HOME -> R.string.home_edu_notification_content
+                    OVERVIEW -> R.string.overview_edu_notification_content
+                    ALL_APPS -> R.string.all_apps_edu_notification_content
+                }
+
+            val expectedTutorialClassName =
+                when (gestureType) {
+                    OVERVIEW -> TUTORIAL_ACTION
+                    else -> KeyboardTouchpadTutorialActivity::class.qualifiedName
+                }
+
             verifyNotificationContent(
-                R.string.back_edu_notification_title,
-                R.string.back_edu_notification_content,
-                notificationCaptor.value
+                expectedTitle,
+                expectedContent,
+                expectedTutorialClassName,
+                notificationCaptor.value,
             )
         }
 
     private fun verifyNotificationContent(
         titleResId: Int,
         contentResId: Int,
-        notification: Notification
+        expectedTutorialClassName: String?,
+        notification: Notification,
     ) {
         val expectedContent = context.getString(contentResId)
         val expectedTitle = context.getString(titleResId)
@@ -164,6 +215,10 @@
         val actualTitle = notification.getString(Notification.EXTRA_TITLE)
         assertThat(actualContent).isEqualTo(expectedContent)
         assertThat(actualTitle).isEqualTo(expectedTitle)
+        val actualTutorialClassName =
+            notification.contentIntent.intent.component?.className
+                ?: notification.contentIntent.intent.action
+        assertThat(actualTutorialClassName).isEqualTo(expectedTutorialClassName)
     }
 
     private fun Notification.getString(key: String): String =
@@ -175,4 +230,14 @@
         }
         runCurrent()
     }
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getGestureTypes(): List<GestureType> {
+            return listOf(BACK, HOME, OVERVIEW, ALL_APPS)
+        }
+
+        private const val TUTORIAL_ACTION: String = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
index 639737b..76434ee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
@@ -27,9 +27,11 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
 import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor
-import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
-import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
-import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_TOUCHPAD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_KEY
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_TOUCHPAD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_TOUCHPAD_BACK
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_TOUCHPAD_HOME
 import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.ACTION_KEY
 import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.BACK_GESTURE
 import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.HOME_GESTURE
@@ -66,8 +68,8 @@
     private val sysUiState = kosmos.sysUiState
     private val touchpadRepo = PrettyFakeTouchpadRepository()
     private val keyboardRepo = kosmos.keyboardRepository
-    private var startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
-    private val viewModel by lazy { createViewModel(startingPeripheral) }
+    private var tutorialScope = INTENT_TUTORIAL_SCOPE_TOUCHPAD
+    private val viewModel by lazy { createViewModel(tutorialScope) }
 
     // createUnsafe so its methods don't have to be called on Main thread
     private val lifecycle = LifecycleRegistry.createUnsafe(mock(LifecycleOwner::class.java))
@@ -75,7 +77,7 @@
     @get:Rule val mainDispatcherRule = MainDispatcherRule(kosmos.testDispatcher)
 
     private fun createViewModel(
-        startingPeripheral: String = INTENT_TUTORIAL_TYPE_TOUCHPAD,
+        scope: String = INTENT_TUTORIAL_SCOPE_TOUCHPAD,
         hasTouchpadTutorialScreens: Boolean = true,
     ): KeyboardTouchpadTutorialViewModel {
         val viewModel =
@@ -84,7 +86,7 @@
                 KeyboardTouchpadConnectionInteractor(keyboardRepo, touchpadRepo),
                 hasTouchpadTutorialScreens,
                 mock<InputDeviceTutorialLogger>(),
-                SavedStateHandle(mapOf(INTENT_TUTORIAL_TYPE_KEY to startingPeripheral))
+                SavedStateHandle(mapOf(INTENT_TUTORIAL_SCOPE_KEY to scope)),
             )
         lifecycle.addObserver(viewModel)
         return viewModel
@@ -169,7 +171,7 @@
     @Test
     fun screensOrder_whenGoingBackAndOnlyKeyboardConnected() =
         testScope.runTest {
-            startingPeripheral = INTENT_TUTORIAL_TYPE_KEYBOARD
+            tutorialScope = INTENT_TUTORIAL_SCOPE_KEYBOARD
             val screens by collectValues(viewModel.screen)
             val closeActivity by collectLastValue(viewModel.closeActivity)
             peripheralsState(keyboardConnected = true, touchpadConnected = false)
@@ -185,7 +187,7 @@
     @Test
     fun screensOrder_whenTouchpadConnected() =
         testScope.runTest {
-            startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
+            tutorialScope = INTENT_TUTORIAL_SCOPE_TOUCHPAD
             val screens by collectValues(viewModel.screen)
             val closeActivity by collectLastValue(viewModel.closeActivity)
 
@@ -193,22 +195,47 @@
 
             goToNextScreen()
             goToNextScreen()
-            goToNextScreen()
 
             assertThat(screens).containsExactly(BACK_GESTURE, HOME_GESTURE).inOrder()
             assertThat(closeActivity).isTrue()
         }
 
     @Test
-    fun screensOrder_whenKeyboardConnected() =
+    fun screensOrder_withBackGestureScope() =
         testScope.runTest {
-            startingPeripheral = INTENT_TUTORIAL_TYPE_KEYBOARD
+            tutorialScope = INTENT_TUTORIAL_SCOPE_TOUCHPAD_BACK
             val screens by collectValues(viewModel.screen)
             val closeActivity by collectLastValue(viewModel.closeActivity)
-
-            peripheralsState(keyboardConnected = true)
+            peripheralsState(touchpadConnected = true)
 
             goToNextScreen()
+
+            assertThat(screens).containsExactly(BACK_GESTURE).inOrder()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun screensOrder_withHomeGestureScope() =
+        testScope.runTest {
+            tutorialScope = INTENT_TUTORIAL_SCOPE_TOUCHPAD_HOME
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+            peripheralsState(touchpadConnected = true)
+
+            goToNextScreen()
+
+            assertThat(screens).containsExactly(HOME_GESTURE).inOrder()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun screensOrder_withKeyboardScope() =
+        testScope.runTest {
+            tutorialScope = INTENT_TUTORIAL_SCOPE_KEYBOARD
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+            peripheralsState(keyboardConnected = true)
+
             goToNextScreen()
 
             assertThat(screens).containsExactly(ACTION_KEY).inOrder()
@@ -218,7 +245,7 @@
     @Test
     fun touchpadGesturesDisabled_onlyDuringTouchpadTutorial() =
         testScope.runTest {
-            startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
+            tutorialScope = INTENT_TUTORIAL_SCOPE_TOUCHPAD
             collectValues(viewModel.screen) // just to initialize viewModel
             peripheralsState(keyboardConnected = true, touchpadConnected = true)
 
@@ -234,8 +261,8 @@
         testScope.runTest {
             val viewModel =
                 createViewModel(
-                    startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD,
-                    hasTouchpadTutorialScreens = false
+                    scope = INTENT_TUTORIAL_SCOPE_TOUCHPAD,
+                    hasTouchpadTutorialScreens = false,
                 )
             val screens by collectValues(viewModel.screen)
             val closeActivity by collectLastValue(viewModel.closeActivity)
@@ -248,7 +275,7 @@
     @Test
     fun touchpadGesturesDisabled_whenTutorialGoesToForeground() =
         testScope.runTest {
-            startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
+            tutorialScope = INTENT_TUTORIAL_SCOPE_TOUCHPAD
             collectValues(viewModel.screen) // just to initialize viewModel
             peripheralsState(touchpadConnected = true)
 
@@ -260,7 +287,7 @@
     @Test
     fun touchpadGesturesNotDisabled_whenTutorialGoesToBackground() =
         testScope.runTest {
-            startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
+            tutorialScope = INTENT_TUTORIAL_SCOPE_TOUCHPAD
             collectValues(viewModel.screen)
             peripheralsState(touchpadConnected = true)
 
@@ -288,7 +315,7 @@
 
     private fun TestScope.peripheralsState(
         keyboardConnected: Boolean = false,
-        touchpadConnected: Boolean = false
+        touchpadConnected: Boolean = false,
     ) {
         keyboardRepo.setIsAnyKeyboardConnected(keyboardConnected)
         touchpadRepo.setIsAnyTouchpadConnected(touchpadConnected)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt
new file mode 100644
index 0000000..e659ef2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.Context.INPUT_SERVICE
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+import android.hardware.input.fakeInputManager
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.hardware.input.Flags.FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES
+import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.shortcut.customInputGesturesRepository
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.FakeUserTracker
+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
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+class CustomInputGesturesRepositoryTest : SysuiTestCase() {
+
+    private val mockUserContext: Context = mock()
+    private val kosmos = testKosmos().also {
+        it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
+    }
+
+    private val inputManager = kosmos.fakeInputManager.inputManager
+    private val testScope = kosmos.testScope
+    private val customInputGesturesRepository = kosmos.customInputGesturesRepository
+
+    @Before
+    fun setup(){
+        whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager)
+    }
+
+    @Test
+    fun customInputGestures_initialValueReturnsDataFromAPI() {
+        testScope.runTest {
+            val customInputGestures = listOf(allAppsInputGestureData)
+            whenever(
+                inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+            ).then { return@then customInputGestures }
+
+            val inputGestures by collectLastValue(customInputGesturesRepository.customInputGestures)
+
+            assertThat(inputGestures).containsExactly(allAppsInputGestureData)
+        }
+    }
+
+    @Test
+    fun customInputGestures_isUpdatedToMostRecentDataAfterNewGestureIsAdded() {
+        testScope.runTest {
+            var customInputGestures = listOf<InputGestureData>()
+            whenever(
+                inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+            ).then { return@then customInputGestures }
+            whenever(inputManager.addCustomInputGesture(any())).then { invocation ->
+                val inputGesture = invocation.getArgument<InputGestureData>(0)
+                customInputGestures = customInputGestures + inputGesture
+                return@then CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+            }
+
+            val inputGestures by collectLastValue(customInputGesturesRepository.customInputGestures)
+            assertThat(inputGestures).isEmpty()
+
+            customInputGesturesRepository.addCustomInputGesture(allAppsInputGestureData)
+            assertThat(inputGestures).containsExactly(allAppsInputGestureData)
+        }
+    }
+
+    @Test
+    fun retrieveCustomInputGestures_retrievesMostRecentData() {
+        testScope.runTest {
+            var customInputGestures = listOf<InputGestureData>()
+            whenever(
+                inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+            ).then { return@then customInputGestures }
+
+
+            assertThat(customInputGesturesRepository.retrieveCustomInputGestures()).isEmpty()
+
+            customInputGestures = listOf(allAppsInputGestureData)
+
+            assertThat(customInputGesturesRepository.retrieveCustomInputGestures())
+                .containsExactly(allAppsInputGestureData)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
index f045404..d12c045 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
@@ -19,35 +19,36 @@
 import android.content.Context
 import android.content.Context.INPUT_SERVICE
 import android.hardware.input.InputGestureData
-import android.hardware.input.InputGestureData.createKeyTrigger
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
 import android.hardware.input.fakeInputManager
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.view.KeyEvent.KEYCODE_A
 import android.view.KeyEvent.KEYCODE_SLASH
-import android.view.KeyEvent.META_ALT_ON
 import android.view.KeyEvent.META_CAPS_LOCK_ON
-import android.view.KeyEvent.META_CTRL_ON
-import android.view.KeyEvent.META_FUNCTION_ON
-import android.view.KeyEvent.META_META_LEFT_ON
 import android.view.KeyEvent.META_META_ON
-import android.view.KeyEvent.META_SHIFT_ON
-import android.view.KeyEvent.META_SHIFT_RIGHT_ON
-import android.view.KeyEvent.META_SYM_ON
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.hardware.input.Flags.FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES
 import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult
 import com.android.systemui.keyboard.shortcut.customShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.ALL_SUPPORTED_MODIFIERS
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutCategory
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest
 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCustomizableInputGesturesWithSimpleShortcutCombinations
 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.customizableInputGestureWithUnknownKeyGestureType
 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedShortcutCategoriesWithSimpleShortcutCombination
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardKeyCombination
 import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.Add
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.Delete
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
 import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
 import com.android.systemui.kosmos.testScope
@@ -60,6 +61,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
 import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
@@ -74,24 +76,21 @@
             it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
         }
 
-    private val fakeInputManager = kosmos.fakeInputManager
+    private val inputManager = kosmos.fakeInputManager.inputManager
     private val testScope = kosmos.testScope
     private val helper = kosmos.shortcutHelperTestHelper
     private val repo = kosmos.customShortcutCategoriesRepository
 
     @Before
     fun setup() {
-        whenever(mockUserContext.getSystemService(INPUT_SERVICE))
-            .thenReturn(fakeInputManager.inputManager)
+        whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager)
     }
 
     @Test
     @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
-    fun categories_emitsCorrectlyConvertedShortcutCategories() {
+    fun categories_correctlyConvertsAPIModelsToShortcutHelperModels() {
         testScope.runTest {
-            whenever(
-                    fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())
-                )
+            whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull()))
                 .thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations)
 
             helper.toggle(deviceId = 123)
@@ -106,9 +105,7 @@
     @DisableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
     fun categories_emitsEmptyListWhenFlagIsDisabled() {
         testScope.runTest {
-            whenever(
-                    fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())
-                )
+            whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull()))
                 .thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations)
 
             helper.toggle(deviceId = 123)
@@ -122,9 +119,7 @@
     @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
     fun categories_ignoresUnknownKeyGestureTypes() {
         testScope.runTest {
-            whenever(
-                    fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())
-                )
+            whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull()))
                 .thenReturn(customizableInputGestureWithUnknownKeyGestureType)
 
             helper.toggle(deviceId = 123)
@@ -151,7 +146,7 @@
             helper.toggle(deviceId = 123)
             val pressedKeys by collectLastValue(repo.pressedKeys)
             repo.updateUserKeyCombination(
-                KeyCombination(modifiers = allSupportedModifiers, keyCode = null)
+                KeyCombination(modifiers = ALL_SUPPORTED_MODIFIERS, keyCode = null)
             )
 
             assertThat(pressedKeys)
@@ -199,11 +194,11 @@
     @Test
     fun shortcutBeingCustomized_updatedOnCustomizationRequested() {
         testScope.runTest {
-            repo.onCustomizationRequested(standardCustomizationRequestInfo)
+            repo.onCustomizationRequested(allAppsShortcutAddRequest)
 
             val shortcutBeingCustomized = repo.getShortcutBeingCustomized()
 
-            assertThat(shortcutBeingCustomized).isEqualTo(standardCustomizationRequestInfo)
+            assertThat(shortcutBeingCustomized).isEqualTo(allAppsShortcutAddRequest)
         }
     }
 
@@ -223,7 +218,7 @@
     fun buildInputGestureDataForShortcutBeingCustomized_noKeyCombinationSelected_returnsNull() {
         testScope.runTest {
             helper.toggle(deviceId = 123)
-            repo.onCustomizationRequested(standardCustomizationRequestInfo)
+            repo.onCustomizationRequested(allAppsShortcutAddRequest)
 
             val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized()
 
@@ -235,46 +230,126 @@
     fun buildInputGestureDataForShortcutBeingCustomized_successfullyBuildInputGestureData() {
         testScope.runTest {
             helper.toggle(deviceId = 123)
-            repo.onCustomizationRequested(standardCustomizationRequestInfo)
+            repo.onCustomizationRequested(allAppsShortcutAddRequest)
             repo.updateUserKeyCombination(standardKeyCombination)
             val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized()
 
             // using toString as we're testing for only structural equality not referential.
             // inputGestureData is a java class and isEqual Tests for referential equality
             // as well which would cause this assert to fail
-            assertThat(inputGestureData.toString()).isEqualTo(standardInputGestureData.toString())
+            assertThat(inputGestureData.toString()).isEqualTo(allAppsInputGestureData.toString())
         }
     }
 
-    private val standardCustomizationRequestInfo =
-        ShortcutCustomizationRequestInfo.Add(
-            label = "Open apps list",
-            categoryType = ShortcutCategoryType.System,
-            subCategoryLabel = "System controls",
-        )
+    @Test
+    @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+    fun deleteShortcut_successfullyRetrievesGestureDataAndDeletesShortcut() {
+        testScope.runTest {
+            whenever(inputManager.getCustomInputGestures(anyOrNull()))
+                .thenReturn(listOf(allAppsInputGestureData, goHomeInputGestureData))
+            whenever(inputManager.removeCustomInputGesture(allAppsInputGestureData))
+                .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS)
+            helper.toggle(deviceId = 123)
 
-    private val standardKeyCombination =
-        KeyCombination(
-            modifiers = META_META_ON or META_SHIFT_ON or META_META_LEFT_ON or META_SHIFT_RIGHT_ON,
-            keyCode = KEYCODE_A,
-        )
+            val result = customizeShortcut(allAppsShortcutDeleteRequest)
+            assertThat(result).isEqualTo(ShortcutCustomizationRequestResult.SUCCESS)
+        }
+    }
 
-    private val allSupportedModifiers =
-        META_META_ON or
-            META_CTRL_ON or
-            META_FUNCTION_ON or
-            META_SHIFT_ON or
-            META_ALT_ON or
-            META_SYM_ON
+    @Test
+    @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+    fun categories_isUpdatedAfterCustomShortcutIsDeleted() {
+        testScope.runTest {
+            // TODO(b/380445594) refactor tests and move these stubbing to ShortcutHelperTestHelper
+            var customInputGestures = listOf(allAppsInputGestureData)
+            whenever(inputManager.getCustomInputGestures(anyOrNull())).then {
+                return@then customInputGestures
+            }
+            whenever(inputManager.removeCustomInputGesture(any())).then {
+                val inputGestureToRemove = it.getArgument<InputGestureData>(0)
+                val containsGesture = customInputGestures.contains(inputGestureToRemove)
+                customInputGestures = customInputGestures - inputGestureToRemove
+                return@then if (containsGesture) CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+                else CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST
+            }
+            val categories by collectLastValue(repo.categories)
+            helper.toggle(deviceId = 123)
 
-    private val standardInputGestureData =
-        InputGestureData.Builder()
-            .setKeyGestureType(KEY_GESTURE_TYPE_ALL_APPS)
-            .setTrigger(
-                createKeyTrigger(
-                    /* keycode = */ standardKeyCombination.keyCode!!,
-                    /* modifierState = */ standardKeyCombination.modifiers and allSupportedModifiers,
+            customizeShortcut(customizationRequest = allAppsShortcutDeleteRequest)
+            assertThat(categories).isEmpty()
+        }
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+    fun categories_isUpdatedAfterCustomShortcutIsAdded() {
+        testScope.runTest {
+            // TODO(b/380445594) refactor tests and move these stubbings to ShortcutHelperTestHelper
+            var customInputGestures = listOf<InputGestureData>()
+            whenever(inputManager.getCustomInputGestures(anyOrNull())).then {
+                return@then customInputGestures
+            }
+            whenever(inputManager.addCustomInputGesture(any())).then {
+                val inputGestureToAdd = it.getArgument<InputGestureData>(0)
+                customInputGestures = customInputGestures + inputGestureToAdd
+                return@then CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+            }
+            val categories by collectLastValue(repo.categories)
+            helper.toggle(deviceId = 123)
+
+            customizeShortcut(allAppsShortcutAddRequest, standardKeyCombination)
+            assertThat(categories).containsExactly(allAppsShortcutCategory)
+        }
+    }
+
+    private suspend fun customizeShortcut(
+        customizationRequest: ShortcutCustomizationRequestInfo,
+        keyCombination: KeyCombination? = null
+    ): ShortcutCustomizationRequestResult{
+        repo.onCustomizationRequested(customizationRequest)
+        repo.updateUserKeyCombination(keyCombination)
+
+        return when (customizationRequest) {
+            is Add -> {
+                repo.confirmAndSetShortcutCurrentlyBeingCustomized()
+            }
+
+            is Delete -> {
+                repo.deleteShortcutCurrentlyBeingCustomized()
+            }
+
+            else -> {
+                ShortcutCustomizationRequestResult.ERROR_OTHER
+            }
+        }
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+    fun categories_isUpdatedAfterCustomShortcutsAreReset() {
+        testScope.runTest {
+            // TODO(b/380445594) refactor tests and move these stubbings to ShortcutHelperTestHelper
+            var customInputGestures = listOf(allAppsInputGestureData)
+            whenever(inputManager.getCustomInputGestures(anyOrNull())).then {
+                return@then customInputGestures
+            }
+            whenever(
+                    inputManager.removeAllCustomInputGestures(
+                        /* filter = */ InputGestureData.Filter.KEY
+                    )
                 )
-            )
-            .build()
+                .then {
+                    customInputGestures = emptyList()
+                    return@then CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+                }
+
+            val categories by collectLastValue(repo.categories)
+            helper.toggle(deviceId = 123)
+            repo.onCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+
+            assertThat(categories).containsExactly(allAppsShortcutCategory)
+            repo.resetAllCustomShortcuts()
+            assertThat(categories).isEmpty()
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt
index 715d907..c9f7035 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt
@@ -17,21 +17,28 @@
 package com.android.systemui.keyboard.shortcut.data.source
 
 import android.content.res.mainResources
+import android.hardware.input.KeyGlyphMap
+import android.hardware.input.fakeInputManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.KeyEvent.KEYCODE_EMOJI_PICKER
 import android.view.KeyboardShortcutGroup
 import android.view.WindowManager.KeyboardShortcutsReceiver
 import android.view.mockWindowManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
-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
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -41,7 +48,8 @@
     private val testScope = kosmos.testScope
 
     private val mockWindowManager = kosmos.mockWindowManager
-    private val source = InputShortcutsSource(kosmos.mainResources, mockWindowManager)
+    private val inputManager = kosmos.fakeInputManager.inputManager
+    private val source = InputShortcutsSource(kosmos.mainResources, mockWindowManager, inputManager)
 
     private var wmImeShortcutGroups: List<KeyboardShortcutGroup>? = null
 
@@ -88,6 +96,36 @@
             assertThat(groups).hasSize(4)
         }
 
+    @Test
+    @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+    fun shortcutGroups_flagEnabled_inputManagerReturnsKeyGlyph_returnsEmojiShortcut() =
+        testScope.runTest {
+            val mockKeyGlyph = mock(KeyGlyphMap::class.java)
+            whenever(mockKeyGlyph.functionRowKeys).thenReturn(intArrayOf(KEYCODE_EMOJI_PICKER))
+            whenever(inputManager.getKeyGlyphMap(TEST_DEVICE_ID)).thenReturn(mockKeyGlyph)
+            wmImeShortcutGroups = null
+
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            val keyCodes = groups[0].items.map { it.keycode }
+            assertThat(keyCodes).contains(KEYCODE_EMOJI_PICKER)
+        }
+
+    @Test
+    @DisableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+    fun shortcutGroups_flagDisabled_inputManagerReturnsKeyGlyph_returnsNoEmojiShortcut() =
+        testScope.runTest {
+            val mockKeyGlyph = mock(KeyGlyphMap::class.java)
+            whenever(mockKeyGlyph.functionRowKeys).thenReturn(intArrayOf(KEYCODE_EMOJI_PICKER))
+            whenever(inputManager.getKeyGlyphMap(TEST_DEVICE_ID)).thenReturn(mockKeyGlyph)
+            wmImeShortcutGroups = null
+
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            val keyCodes = groups[0].items.map { it.keycode }
+            assertThat(keyCodes).doesNotContain(KEYCODE_EMOJI_PICKER)
+        }
+
     companion object {
         private const val TEST_DEVICE_ID = 1234
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSourceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSourceTest.kt
new file mode 100644
index 0000000..60d7089
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSourceTest.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.keyboard.shortcut.data.source
+
+import android.content.res.mainResources
+import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_SHIFT_ON
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+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 MultitaskingShortcutsSourceTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val source = MultitaskingShortcutsSource(kosmos.mainResources, context)
+
+    @Test
+    fun shortcutGroups_doesNotContainCycleThroughRecentAppsShortcuts() {
+        testScope.runTest {
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            val shortcuts =
+                groups.flatMap { it.items }.map { c -> Triple(c.label, c.modifiers, c.keycode) }
+
+            val cycleThroughRecentAppsShortcuts =
+                listOf(
+                    Triple(
+                        context.getString(R.string.group_system_cycle_forward),
+                        META_ALT_ON,
+                        KEYCODE_TAB,
+                    ),
+                    Triple(
+                        context.getString(R.string.group_system_cycle_back),
+                        META_SHIFT_ON or META_ALT_ON,
+                        KEYCODE_TAB,
+                    ),
+                )
+
+            assertThat(shortcuts).containsNoneIn(cycleThroughRecentAppsShortcuts)
+        }
+    }
+
+    private companion object {
+        private const val TEST_DEVICE_ID = 1234
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt
new file mode 100644
index 0000000..b9fb3e6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright 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.source
+
+import android.content.res.mainResources
+import android.hardware.input.KeyGlyphMap
+import android.hardware.input.KeyGlyphMap.KeyCombination
+import android.hardware.input.fakeInputManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.KeyEvent
+import android.view.KeyEvent.KEYCODE_BACK
+import android.view.KeyEvent.KEYCODE_HOME
+import android.view.KeyEvent.KEYCODE_RECENT_APPS
+import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_SHIFT_ON
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+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
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SystemShortcutsSourceTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val inputManager = kosmos.fakeInputManager.inputManager
+    private val source = SystemShortcutsSource(kosmos.mainResources, inputManager)
+    private val mockKeyGlyphMap = mock(KeyGlyphMap::class.java)
+    private val functionRowKeyCodes = listOf(KEYCODE_HOME, KEYCODE_BACK, KEYCODE_RECENT_APPS)
+
+    @Before
+    fun setUp() {
+        whenever(mockKeyGlyphMap.functionRowKeys).thenReturn(intArrayOf())
+        whenever(inputManager.getKeyGlyphMap(TEST_DEVICE_ID)).thenReturn(mockKeyGlyphMap)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+    fun shortcutGroups_flagEnabled_inputManagerReturnsNoFunctionRowKeys_returnsNoFunctionRowShortcuts() =
+        testScope.runTest {
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            val keyCodes = groups[0].items.map { it.keycode }
+            assertThat(keyCodes).containsNoneIn(functionRowKeyCodes)
+        }
+
+    @Test
+    @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+    fun shortcutGroups_flagEnabled_inputManagerReturnsFunctionRowKeys_returnsFunctionRowShortcuts() =
+        testScope.runTest {
+            whenever(mockKeyGlyphMap.functionRowKeys)
+                .thenReturn(intArrayOf(KEYCODE_HOME, KEYCODE_BACK, KEYCODE_RECENT_APPS))
+
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            val keyCodes = groups[0].items.map { it.keycode }
+            assertThat(keyCodes).containsAtLeastElementsIn(functionRowKeyCodes)
+        }
+
+    @Test
+    @DisableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+    fun shortcutGroups_flagDisabled_inputManagerReturnsNoFunctionRowKeys_returnsDefaultFunctionRowShortcuts() =
+        testScope.runTest {
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            val keyCodes = groups[0].items.map { it.keycode }
+            assertThat(keyCodes).containsAtLeastElementsIn(functionRowKeyCodes)
+        }
+
+    @Test
+    @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+    fun shortcutGroups_flagEnabled_inputManagerReturnsHardwareShortcuts_returnsHardwareShortcuts() =
+        testScope.runTest {
+            whenever(mockKeyGlyphMap.functionRowKeys).thenReturn(intArrayOf())
+            val hardwareShortcutMap =
+                mapOf(Pair(KeyCombination(KeyEvent.META_META_ON, KeyEvent.KEYCODE_1), KEYCODE_BACK))
+            whenever(mockKeyGlyphMap.hardwareShortcuts).thenReturn(hardwareShortcutMap)
+
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            val shortcuts = groups[0].items.map { c -> Triple(c.label, c.modifiers, c.keycode) }
+            val hardwareShortcut =
+                Triple(
+                    context.getString(R.string.group_system_go_back),
+                    KeyEvent.META_META_ON,
+                    KeyEvent.KEYCODE_1,
+                )
+            assertThat(shortcuts).contains(hardwareShortcut)
+        }
+
+    @Test
+    @DisableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+    fun shortcutGroups_flagDisabled_inputManagerReturnsHardwareShortcuts_returnsNoHardwareShortcuts() =
+        testScope.runTest {
+            val hardwareShortcutMap =
+                mapOf(Pair(KeyCombination(KeyEvent.META_META_ON, KeyEvent.KEYCODE_1), KEYCODE_BACK))
+            whenever(mockKeyGlyphMap.hardwareShortcuts).thenReturn(hardwareShortcutMap)
+
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            val shortcuts = groups[0].items.map { c -> Triple(c.label, c.modifiers, c.keycode) }
+            val hardwareShortcut =
+                Triple(
+                    context.getString(R.string.group_system_go_back),
+                    KeyEvent.META_META_ON,
+                    KeyEvent.KEYCODE_1,
+                )
+            assertThat(shortcuts).doesNotContain(hardwareShortcut)
+        }
+
+    @Test
+    fun shortcutGroups_containsCycleThroughRecentAppsShortcuts() {
+        testScope.runTest {
+            val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+            val shortcuts =
+                groups.flatMap { it.items }.map { c -> Triple(c.label, c.modifiers, c.keycode) }
+
+            val cycleThroughRecentAppsShortcuts =
+                listOf(
+                    Triple(
+                        context.getString(R.string.group_system_cycle_forward),
+                        META_ALT_ON,
+                        KEYCODE_TAB,
+                    ),
+                    Triple(
+                        context.getString(R.string.group_system_cycle_back),
+                        META_SHIFT_ON or META_ALT_ON,
+                        KEYCODE_TAB,
+                    ),
+                )
+
+            assertThat(shortcuts).containsAtLeastElementsIn(cycleThroughRecentAppsShortcuts)
+        }
+    }
+
+    private companion object {
+        private const val TEST_DEVICE_ID = 1234
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index a1e7ef4..cc718c7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -19,9 +19,24 @@
 import android.hardware.input.InputGestureData
 import android.hardware.input.InputGestureData.createKeyTrigger
 import android.hardware.input.KeyGestureEvent
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+import android.os.SystemClock
 import android.view.KeyEvent
+import android.view.KeyEvent.ACTION_DOWN
+import android.view.KeyEvent.KEYCODE_A
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_CTRL_ON
+import android.view.KeyEvent.META_FUNCTION_ON
+import android.view.KeyEvent.META_META_LEFT_ON
+import android.view.KeyEvent.META_META_ON
+import android.view.KeyEvent.META_SHIFT_ON
+import android.view.KeyEvent.META_SHIFT_RIGHT_ON
+import android.view.KeyEvent.META_SYM_ON
 import android.view.KeyboardShortcutGroup
 import android.view.KeyboardShortcutInfo
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys
+import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
 import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
@@ -29,9 +44,11 @@
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
 import com.android.systemui.keyboard.shortcut.shared.model.shortcut
+import com.android.systemui.keyboard.shortcut.shared.model.shortcutCategory
 import com.android.systemui.res.R
 
 object TestShortcuts {
@@ -57,6 +74,27 @@
             /* modifiers = */ KeyEvent.META_SHIFT_ON,
         )
 
+    private val ShortcutsWithDiffSizeOfKeys =
+        KeyboardShortcutInfo(
+            /* label = */ "Shortcuts with diff size of keys",
+            /* keycode = */ KeyEvent.KEYCODE_HOME,
+            /* modifiers = */ 0,
+        )
+
+    private val ShortcutsWithDiffSizeOfKeys2 =
+        KeyboardShortcutInfo(
+            /* label = */ ShortcutsWithDiffSizeOfKeys.label,
+            /* keycode = */ KeyEvent.KEYCODE_1,
+            /* modifiers = */ META_META_ON,
+        )
+
+    private val ShortcutsWithDiffSizeOfKeys3 =
+        KeyboardShortcutInfo(
+            /* label = */ ShortcutsWithDiffSizeOfKeys.label,
+            /* keycode = */ KeyEvent.KEYCODE_2,
+            /* modifiers = */ META_META_ON or META_FUNCTION_ON,
+        )
+
     private val shortcutWithGroupedRepeatedLabel =
         shortcut(shortcutInfoWithRepeatedLabel.label!!.toString()) {
             command {
@@ -71,6 +109,10 @@
                 key("Shift")
                 key("M")
             }
+            contentDescription {
+                "${shortcutInfoWithRepeatedLabel.label}, " +
+                    "Press key Meta plus H, or Meta plus L, or Shift plus M"
+            }
         }
 
     private val goHomeShortcutInfo =
@@ -93,6 +135,7 @@
                 key(R.drawable.ic_ksh_key_meta)
                 key("N")
             }
+            contentDescription { "${standardShortcutInfo1.label}, Press key Meta plus N" }
         }
 
     private val customGoHomeShortcut =
@@ -103,6 +146,7 @@
                 key("A")
                 isCustom(true)
             }
+            contentDescription { "Go to home screen, Press key Ctrl plus Alt plus A" }
         }
 
     private val standardShortcutInfo2 =
@@ -119,6 +163,7 @@
                 key("Shift")
                 key("Z")
             }
+            contentDescription { "${standardShortcutInfo2.label}, Press key Alt plus Shift plus Z" }
         }
 
     private val standardShortcutInfo3 =
@@ -134,6 +179,7 @@
                 key("Ctrl")
                 key("J")
             }
+            contentDescription { "${standardShortcutInfo3.label}, Press key Ctrl plus J" }
         }
 
     private val shortcutInfoWithUnsupportedModifiers =
@@ -189,6 +235,7 @@
                 key("Ctrl")
                 key("Space")
             }
+            contentDescription { "Switch to next language, Press key Ctrl plus Space" }
         }
 
     private val switchToPreviousLanguageShortcut =
@@ -198,6 +245,9 @@
                 key("Shift")
                 key("Space")
             }
+            contentDescription {
+                "Switch to previous language, Press key Ctrl plus Shift plus Space"
+            }
         }
 
     private val subCategoryForInputLanguageSwitchShortcuts =
@@ -276,6 +326,10 @@
                             key("A")
                             isCustom(true)
                         }
+                        contentDescription {
+                            "Go to home screen, Press key Ctrl plus Alt plus B, " +
+                                "or Ctrl plus Alt plus A"
+                        }
                     }
                 ),
         )
@@ -348,6 +402,16 @@
             groupWithSupportedAndUnsupportedModifierShortcut,
         )
 
+    val groupWithDifferentSizeOfShortcutKeys =
+        KeyboardShortcutGroup(
+            "Group with different size of shortcut keys",
+            listOf(
+                ShortcutsWithDiffSizeOfKeys3,
+                ShortcutsWithDiffSizeOfKeys,
+                ShortcutsWithDiffSizeOfKeys2,
+            ),
+        )
+
     val subCategoriesWithUnsupportedModifiersRemoved =
         listOf(subCategoryWithStandardShortcut, subCategoryWithUnsupportedShortcutsRemoved)
 
@@ -458,17 +522,15 @@
             simpleShortcutCategory(AppCategories, "Applications", "Email"),
             simpleShortcutCategory(AppCategories, "Applications", "Maps"),
             simpleShortcutCategory(AppCategories, "Applications", "SMS"),
-            simpleShortcutCategory(MultiTasking, "Recent apps", "Cycle forward through recent apps"),
         )
-    val customInputGestureTypeHome =
-        simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+    val customInputGestureTypeHome = simpleInputGestureData(keyGestureType = KEY_GESTURE_TYPE_HOME)
 
     val allCustomizableInputGesturesWithSimpleShortcutCombinations =
         listOf(
             simpleInputGestureData(
                 keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
             ),
-            simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+            simpleInputGestureData(keyGestureType = KEY_GESTURE_TYPE_HOME),
             simpleInputGestureData(
                 keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
             ),
@@ -525,4 +587,113 @@
                 keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER
             ),
         )
+
+    val allAppsShortcutAddRequest =
+        ShortcutCustomizationRequestInfo.Add(
+            label = "Open apps list",
+            categoryType = System,
+            subCategoryLabel = "System controls",
+        )
+
+    val allAppsShortcutDeleteRequest =
+        ShortcutCustomizationRequestInfo.Delete(
+            label = "Open apps list",
+            categoryType = System,
+            subCategoryLabel = "System controls",
+        )
+
+    val standardKeyCombination =
+        KeyCombination(
+            modifiers = META_META_ON or META_SHIFT_ON or META_META_LEFT_ON or META_SHIFT_RIGHT_ON,
+            keyCode = KEYCODE_A,
+        )
+
+    const val ALL_SUPPORTED_MODIFIERS =
+        META_META_ON or
+            META_CTRL_ON or
+            META_FUNCTION_ON or
+            META_SHIFT_ON or
+            META_ALT_ON or
+            META_SYM_ON
+
+    val allAppsInputGestureData: InputGestureData =
+        InputGestureData.Builder()
+            .setKeyGestureType(KEY_GESTURE_TYPE_ALL_APPS)
+            .setTrigger(
+                createKeyTrigger(
+                    /* keycode = */ standardKeyCombination.keyCode!!,
+                    /* modifierState = */ standardKeyCombination.modifiers and
+                        ALL_SUPPORTED_MODIFIERS,
+                )
+            )
+            .build()
+
+    val goHomeInputGestureData: InputGestureData =
+        InputGestureData.Builder()
+            .setKeyGestureType(KEY_GESTURE_TYPE_HOME)
+            .setTrigger(
+                createKeyTrigger(
+                    /* keycode = */ standardKeyCombination.keyCode!!,
+                    /* modifierState = */ standardKeyCombination.modifiers and
+                        ALL_SUPPORTED_MODIFIERS,
+                )
+            )
+            .build()
+
+    val allAppsShortcutCategory =
+        shortcutCategory(System) {
+            subCategory("System controls") {
+                shortcut("Open apps list") {
+                    command {
+                        isCustom(true)
+                        key(ShortcutHelperKeys.metaModifierIconResId)
+                        key("Shift")
+                        key("A")
+                    }
+                }
+            }
+        }
+
+    val keyDownEventWithoutActionKeyPressed =
+        androidx.compose.ui.input.key.KeyEvent(
+            android.view.KeyEvent(
+                /* downTime = */ SystemClock.uptimeMillis(),
+                /* eventTime = */ SystemClock.uptimeMillis(),
+                /* action = */ ACTION_DOWN,
+                /* code = */ KEYCODE_A,
+                /* repeat = */ 0,
+                /* metaState = */ META_CTRL_ON,
+            )
+        )
+
+    val keyDownEventWithActionKeyPressed =
+        androidx.compose.ui.input.key.KeyEvent(
+            android.view.KeyEvent(
+                /* downTime = */ SystemClock.uptimeMillis(),
+                /* eventTime = */ SystemClock.uptimeMillis(),
+                /* action = */ ACTION_DOWN,
+                /* code = */ KEYCODE_A,
+                /* repeat = */ 0,
+                /* metaState = */ META_CTRL_ON or META_META_ON,
+            )
+        )
+
+    val keyUpEventWithActionKeyPressed =
+        androidx.compose.ui.input.key.KeyEvent(
+            android.view.KeyEvent(
+                /* downTime = */ SystemClock.uptimeMillis(),
+                /* eventTime = */ SystemClock.uptimeMillis(),
+                /* action = */ ACTION_DOWN,
+                /* code = */ KEYCODE_A,
+                /* repeat = */ 0,
+                /* metaState = */ 0,
+            )
+        )
+
+    val standardAddShortcutRequest =
+        ShortcutCustomizationRequestInfo.Add(
+            label = "Standard shortcut",
+            categoryType = System,
+            subCategoryLabel = "Standard subcategory",
+        )
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
index f7c7701..8f0bc64 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
@@ -333,6 +333,56 @@
         }
     }
 
+    @Test
+    fun categories_addsSameContentDescriptionForShortcutsOfSameType() {
+        testScope.runTest {
+            setCustomInputGestures(listOf(customInputGestureTypeHome))
+            systemShortcutsSource.setGroups(groupWithGoHomeShortcutInfo)
+            helper.showFromActivity()
+
+            val categories by collectLastValue(interactor.shortcutCategories)
+            val contentDescriptions =
+                categories?.flatMap {
+                    it.subCategories.flatMap { it.shortcuts.map { it.contentDescription } }
+                }
+
+            assertThat(contentDescriptions)
+                .containsExactly(
+                    "Go to home screen, Press key Ctrl plus Alt plus B, or Ctrl plus Alt plus A",
+                    "Standard shortcut 3, Press key Ctrl plus J",
+                    "Standard shortcut 2, Press key Alt plus Shift plus Z",
+                    "Standard shortcut 1, Press key Meta plus N",
+                    "Standard shortcut 1, Press key Meta plus N",
+                    "Standard shortcut 2, Press key Alt plus Shift plus Z",
+                    "Standard shortcut 3, Press key Ctrl plus J",
+                    "Switch to next language, Press key Ctrl plus Space",
+                    "Switch to previous language, Press key Ctrl plus Shift plus Space",
+                    "Standard shortcut 1, Press key Meta plus N",
+                    "Standard shortcut 2, Press key Alt plus Shift plus Z",
+                    "Standard shortcut 3, Press key Ctrl plus J",
+                    "Standard shortcut 3, Press key Ctrl plus J",
+                    "Standard shortcut 2, Press key Alt plus Shift plus Z",
+                    "Standard shortcut 1, Press key Meta plus N",
+                    "Standard shortcut 2, Press key Alt plus Shift plus Z",
+                    "Standard shortcut 1, Press key Meta plus N",
+                )
+        }
+    }
+
+    @Test
+    fun categories_showShortcutsInAscendingOrderOfKeySize() =
+        testScope.runTest {
+            systemShortcutsSource.setGroups(TestShortcuts.groupWithDifferentSizeOfShortcutKeys)
+            val categories by collectLastValue(interactor.shortcutCategories)
+
+            helper.showFromActivity()
+
+            val systemCategoryShortcuts = categories?.get(0)?.subCategories?.get(0)?.shortcuts
+            val shortcutKeyCount =
+                systemCategoryShortcuts?.flatMap { it.commands }?.map { it.keys.size }
+            assertThat(shortcutKeyCount).containsExactly(1, 2, 3).inOrder()
+        }
+
     private fun setCustomInputGestures(customInputGestures: List<InputGestureData>) {
         whenever(fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()))
             .thenReturn(customInputGestures)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
new file mode 100644
index 0000000..755c218
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
@@ -0,0 +1,320 @@
+/*
+ * 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 android.content.Context
+import android.content.Context.INPUT_SERVICE
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+import android.hardware.input.fakeInputManager
+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.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithActionKeyPressed
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithoutActionKeyPressed
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyUpEventWithActionKeyPressed
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddShortcutRequest
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+import com.android.systemui.keyboard.shortcut.shortcutCustomizationViewModelFactory
+import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.settings.FakeUserTracker
+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
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShortcutCustomizationViewModelTest : SysuiTestCase() {
+
+    private val mockUserContext: Context = mock()
+    private val kosmos =
+        testKosmos().also {
+            it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
+        }
+    private val testScope = kosmos.testScope
+    private val inputManager = kosmos.fakeInputManager.inputManager
+    private val helper = kosmos.shortcutHelperTestHelper
+    private val viewModel = kosmos.shortcutCustomizationViewModelFactory.create()
+
+    @Before
+    fun setup() {
+        helper.showFromActivity()
+        whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager)
+    }
+
+    @Test
+    fun uiState_inactiveByDefault() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+
+            assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
+        }
+    }
+
+    @Test
+    fun uiState_correctlyUpdatedWhenAddShortcutCustomizationIsRequested() {
+        testScope.runTest {
+            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+
+            assertThat(uiState).isEqualTo(
+                AddShortcutDialog(
+                    shortcutLabel = "Standard shortcut",
+                    defaultCustomShortcutModifierKey =
+                    ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
+                )
+            )
+        }
+    }
+
+    @Test
+    fun uiState_correctlyUpdatedWhenResetShortcutCustomizationIsRequested() {
+        testScope.runTest {
+            viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+
+            assertThat(uiState).isEqualTo(ResetShortcutDialog)
+        }
+    }
+
+    @Test
+    fun uiState_correctlyUpdatedWhenDeleteShortcutCustomizationIsRequested() {
+        testScope.runTest {
+            viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest)
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+
+            assertThat(uiState).isEqualTo(DeleteShortcutDialog)
+        }
+    }
+
+    @Test
+    fun uiState_inactiveAfterDialogIsDismissed() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+            viewModel.onDialogDismissed()
+            assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
+        }
+    }
+
+    @Test
+    fun uiState_pressedKeys_emptyByDefault() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+            assertThat((uiState as AddShortcutDialog).pressedKeys)
+                .isEmpty()
+        }
+    }
+
+    @Test
+    fun uiState_becomeInactiveAfterSuccessfullySettingShortcut() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            whenever(inputManager.addCustomInputGesture(any()))
+                .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS)
+
+            openAddShortcutDialogAndSetShortcut()
+
+            assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
+        }
+    }
+
+    @Test
+    fun uiState_errorMessage_isEmptyByDefault() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest)
+
+            assertThat((uiState as AddShortcutDialog).errorMessage)
+                .isEmpty()
+        }
+    }
+
+    @Test
+    fun uiState_errorMessage_isKeyCombinationInUse_whenKeyCombinationAlreadyExists() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            whenever(inputManager.addCustomInputGesture(any()))
+                .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS)
+
+            openAddShortcutDialogAndSetShortcut()
+
+            assertThat((uiState as AddShortcutDialog).errorMessage)
+                .isEqualTo(
+                    context.getString(
+                        R.string.shortcut_customizer_key_combination_in_use_error_message
+                    )
+                )
+        }
+    }
+
+    @Test
+    fun uiState_errorMessage_isKeyCombinationInUse_whenKeyCombinationIsReserved() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            whenever(inputManager.addCustomInputGesture(any()))
+                .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE)
+
+            openAddShortcutDialogAndSetShortcut()
+
+            assertThat((uiState as AddShortcutDialog).errorMessage)
+                .isEqualTo(
+                    context.getString(
+                        R.string.shortcut_customizer_key_combination_in_use_error_message
+                    )
+                )
+        }
+    }
+
+    @Test
+    fun uiState_errorMessage_isGenericError_whenErrorIsUnknown() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            whenever(inputManager.addCustomInputGesture(any()))
+                .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER)
+
+            openAddShortcutDialogAndSetShortcut()
+
+            assertThat((uiState as AddShortcutDialog).errorMessage)
+                .isEqualTo(context.getString(R.string.shortcut_customizer_generic_error_message))
+        }
+    }
+
+    @Test
+    fun uiState_becomesInactiveAfterSuccessfullyDeletingShortcut() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            whenever(inputManager.getCustomInputGestures(any()))
+                .thenReturn(listOf(goHomeInputGestureData, allAppsInputGestureData))
+            whenever(inputManager.removeCustomInputGesture(any()))
+                .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS)
+
+            openDeleteShortcutDialogAndDeleteShortcut()
+
+            assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
+        }
+    }
+
+    @Test
+    fun uiState_becomesInactiveAfterSuccessfullyResettingShortcuts() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            whenever(inputManager.getCustomInputGestures(any())).thenReturn(emptyList())
+
+            openResetShortcutDialogAndResetAllCustomShortcuts()
+
+            assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
+        }
+    }
+
+    @Test
+    fun onKeyPressed_handlesKeyEvents_whereActionKeyIsAlsoPressed() {
+        testScope.runTest {
+            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+            val isHandled = viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
+
+            assertThat(isHandled).isTrue()
+        }
+    }
+
+    @Test
+    fun onKeyPressed_doesNotHandleKeyEvents_whenActionKeyIsNotAlsoPressed() {
+        testScope.runTest {
+            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+            val isHandled = viewModel.onKeyPressed(keyDownEventWithoutActionKeyPressed)
+
+            assertThat(isHandled).isFalse()
+        }
+    }
+
+    @Test
+    fun onKeyPressed_convertsKeyEventsAndUpdatesUiStatesPressedKey() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+            viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
+            viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
+
+            // Note that Action Key is excluded as it's already displayed on the UI
+            assertThat((uiState as AddShortcutDialog).pressedKeys)
+                .containsExactly(ShortcutKey.Text("Ctrl"), ShortcutKey.Text("A"))
+        }
+    }
+
+    @Test
+    fun uiState_pressedKeys_resetsToEmptyListAfterDialogIsDismissedAndReopened() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+            viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
+            viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
+
+            // Note that Action Key is excluded as it's already displayed on the UI
+            assertThat((uiState as AddShortcutDialog).pressedKeys)
+                .containsExactly(ShortcutKey.Text("Ctrl"), ShortcutKey.Text("A"))
+
+            // Close the dialog and show it again
+            viewModel.onDialogDismissed()
+            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+            assertThat((uiState as AddShortcutDialog).pressedKeys)
+                .isEmpty()
+        }
+    }
+
+    private suspend fun openAddShortcutDialogAndSetShortcut() {
+        viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest)
+
+        viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
+        viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
+
+        viewModel.onSetShortcut()
+    }
+
+    private suspend fun openDeleteShortcutDialogAndDeleteShortcut() {
+        viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest)
+
+        viewModel.deleteShortcutCurrentlyBeingCustomized()
+    }
+
+    private suspend fun openResetShortcutDialogAndResetAllCustomShortcuts() {
+        viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+
+        viewModel.resetAllCustomShortcuts()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
new file mode 100644
index 0000000..78fce27
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -0,0 +1,469 @@
+/*
+ * 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 android.app.role.RoleManager
+import android.app.role.mockRoleManager
+import android.content.Context
+import android.content.Context.INPUT_SERVICE
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.hardware.input.InputGestureData
+import android.hardware.input.fakeInputManager
+import android.view.KeyEvent
+import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Tv
+import androidx.compose.material.icons.filled.VerticalSplit
+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.coroutines.collectValues
+import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.keyboard.shortcut.shared.model.shortcut
+import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
+import com.android.systemui.keyboard.shortcut.shortcutHelperViewModel
+import com.android.systemui.keyboard.shortcut.ui.model.IconSource
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
+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.settings.FakeUserTracker
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.settings.userTracker
+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.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShortcutHelperViewModelTest : SysuiTestCase() {
+
+    private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
+    private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
+    private val fakeCurrentAppsSource = FakeKeyboardShortcutGroupsSource()
+    private val mockPackageManager: PackageManager = mock()
+    private val mockUserContext: Context = mock()
+    private val mockApplicationInfo: ApplicationInfo = mock()
+
+    private val kosmos =
+        Kosmos().also {
+            it.testCase = this
+            it.testDispatcher = UnconfinedTestDispatcher()
+            it.shortcutHelperSystemShortcutsSource = fakeSystemSource
+            it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
+            it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
+            it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
+            it.shortcutHelperCurrentAppShortcutsSource = fakeCurrentAppsSource
+            it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
+        }
+
+    private val testScope = kosmos.testScope
+    private val testHelper = kosmos.shortcutHelperTestHelper
+    private val sysUiState = kosmos.sysUiState
+    private val fakeUserTracker = kosmos.fakeUserTracker
+    private val mockRoleManager = kosmos.mockRoleManager
+    private val inputManager = kosmos.fakeInputManager.inputManager
+    private val viewModel = kosmos.shortcutHelperViewModel
+
+
+    @Before
+    fun setUp() {
+        fakeSystemSource.setGroups(TestShortcuts.systemGroups)
+        fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
+        fakeCurrentAppsSource.setGroups(TestShortcuts.currentAppGroups)
+        whenever(mockPackageManager.getApplicationInfo(anyString(), eq(0))).thenReturn(mockApplicationInfo)
+        whenever(mockPackageManager.getApplicationLabel(mockApplicationInfo)).thenReturn("Current App")
+        whenever(mockPackageManager.getApplicationIcon(anyString())).thenThrow(NameNotFoundException())
+        whenever(mockUserContext.packageManager).thenReturn(mockPackageManager)
+        whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager)
+    }
+
+    @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_falseAfterCloseSystemDialogs() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(viewModel.shouldShow)
+
+            testHelper.showFromActivity()
+            testHelper.hideThroughCloseSystemDialogs()
+
+            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()
+        }
+
+    @Test
+    fun shortcutsUiState_inactiveByDefault() =
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            assertThat(uiState).isEqualTo(ShortcutsUiState.Inactive)
+        }
+
+    @Test
+    fun shortcutsUiState_featureActive_emitsActive() =
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+
+            assertThat(uiState).isInstanceOf(ShortcutsUiState.Active::class.java)
+        }
+
+    @Test
+    fun shortcutsUiState_noCurrentAppCategory_defaultSelectedCategoryIsSystem() =
+        testScope.runTest {
+            fakeCurrentAppsSource.setGroups(emptyList())
+
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.defaultSelectedCategory).isEqualTo(System)
+        }
+
+    @Test
+    fun shortcutsUiState_currentAppCategoryPresent_currentAppIsDefaultSelected() =
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.defaultSelectedCategory)
+                .isEqualTo(CurrentApp(TestShortcuts.currentAppPackageName))
+        }
+
+    @Test
+    fun shortcutsUiState_currentAppIsLauncher_defaultSelectedCategoryIsSystem() =
+        testScope.runTest {
+            whenever(
+                mockRoleManager.getRoleHoldersAsUser(
+                    RoleManager.ROLE_HOME,
+                    fakeUserTracker.userHandle,
+                )
+            )
+                .thenReturn(listOf(TestShortcuts.currentAppPackageName))
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.defaultSelectedCategory).isEqualTo(System)
+        }
+
+    @Test
+    fun shortcutsUiState_userTypedQuery_filtersMatchingShortcutLabels() =
+        testScope.runTest {
+            fakeSystemSource.setGroups(
+                groupWithShortcutLabels("first Foo shortcut1", "first bar shortcut1"),
+                groupWithShortcutLabels(
+                    "second foO shortcut2",
+                    "second bar shortcut2",
+                    groupLabel = SECOND_SIMPLE_GROUP_LABEL,
+                ),
+            )
+            fakeMultiTaskingSource.setGroups(
+                groupWithShortcutLabels("third FoO shortcut1", "third bar shortcut1")
+            )
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+            viewModel.onSearchQueryChanged("foo")
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.shortcutCategories)
+                .containsExactly(
+                    ShortcutCategoryUi(
+                        label = "System",
+                        iconSource = IconSource(imageVector = Icons.Default.Tv),
+                        shortcutCategory =
+                        ShortcutCategory(
+                            System,
+                            subCategoryWithShortcutLabels("first Foo shortcut1"),
+                            subCategoryWithShortcutLabels(
+                                "second foO shortcut2",
+                                subCategoryLabel = SECOND_SIMPLE_GROUP_LABEL,
+                            ),
+                        ),
+                    ),
+                    ShortcutCategoryUi(
+                        label = "Multitasking",
+                        iconSource = IconSource(imageVector = Icons.Default.VerticalSplit),
+                        shortcutCategory =
+                        ShortcutCategory(
+                            MultiTasking,
+                            subCategoryWithShortcutLabels("third FoO shortcut1"),
+                        ),
+                    ),
+                )
+        }
+
+    @Test
+    fun shortcutsUiState_userTypedQuery_noMatch_returnsEmptyList() =
+        testScope.runTest {
+            fakeSystemSource.setGroups(
+                groupWithShortcutLabels("first Foo shortcut1", "first bar shortcut1"),
+                groupWithShortcutLabels("second foO shortcut2", "second bar shortcut2"),
+            )
+            fakeMultiTaskingSource.setGroups(
+                groupWithShortcutLabels("third FoO shortcut1", "third bar shortcut1")
+            )
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+            viewModel.onSearchQueryChanged("unmatched query")
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.shortcutCategories).isEmpty()
+        }
+
+    @Test
+    fun shortcutsUiState_userTypedQuery_noMatch_returnsNullDefaultSelectedCategory() =
+        testScope.runTest {
+            fakeSystemSource.setGroups(
+                groupWithShortcutLabels("first Foo shortcut1", "first bar shortcut1"),
+                groupWithShortcutLabels("second foO shortcut2", "second bar shortcut2"),
+            )
+            fakeMultiTaskingSource.setGroups(
+                groupWithShortcutLabels("third FoO shortcut1", "third bar shortcut1")
+            )
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+            viewModel.onSearchQueryChanged("unmatched query")
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.defaultSelectedCategory).isNull()
+        }
+
+    @Test
+    fun shortcutsUiState_userTypedQuery_changesDefaultSelectedCategoryToFirstMatchingCategory() =
+        testScope.runTest {
+            fakeSystemSource.setGroups(groupWithShortcutLabels("first shortcut"))
+            fakeMultiTaskingSource.setGroups(groupWithShortcutLabels("second shortcut"))
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+            viewModel.onSearchQueryChanged("second")
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.defaultSelectedCategory).isEqualTo(MultiTasking)
+        }
+
+    @Test
+    fun shortcutsUiState_userTypedQuery_multipleCategoriesMatch_currentAppIsDefaultSelected() =
+        testScope.runTest {
+            fakeSystemSource.setGroups(groupWithShortcutLabels("first shortcut"))
+            fakeMultiTaskingSource.setGroups(groupWithShortcutLabels("second shortcut"))
+            fakeCurrentAppsSource.setGroups(groupWithShortcutLabels("third shortcut"))
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+            viewModel.onSearchQueryChanged("shortcut")
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.defaultSelectedCategory).isInstanceOf(CurrentApp::class.java)
+        }
+
+    @Test
+    fun shortcutsUiState_shouldShowResetButton_isFalseWhenThereAreNoCustomShortcuts() =
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.shouldShowResetButton).isFalse()
+        }
+
+    @Test
+    fun shortcutsUiState_shouldShowResetButton_isTrueWhenThereAreCustomShortcuts() =
+        testScope.runTest {
+            whenever(
+                inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+            ).thenReturn(listOf(allAppsInputGestureData))
+            val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+            testHelper.showFromActivity()
+
+            val activeUiState = uiState as ShortcutsUiState.Active
+            assertThat(activeUiState.shouldShowResetButton).isTrue()
+        }
+
+    private fun groupWithShortcutLabels(
+        vararg shortcutLabels: String,
+        groupLabel: String = FIRST_SIMPLE_GROUP_LABEL,
+    ) =
+        KeyboardShortcutGroup(groupLabel, shortcutLabels.map { simpleShortcutInfo(it) }).apply {
+            packageName = "test.package.name"
+        }
+
+    private fun simpleShortcutInfo(label: String) =
+        KeyboardShortcutInfo(label, KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON)
+
+    private fun subCategoryWithShortcutLabels(
+        vararg shortcutLabels: String,
+        subCategoryLabel: String = FIRST_SIMPLE_GROUP_LABEL,
+    ) =
+        ShortcutSubCategory(
+            label = subCategoryLabel,
+            shortcuts = shortcutLabels.map { simpleShortcut(it) },
+        )
+
+    private fun simpleShortcut(label: String) =
+        shortcut(label) {
+            command {
+                key("Ctrl")
+                key("A")
+            }
+            contentDescription { "$label, Press key Ctrl plus A" }
+        }
+
+    companion object {
+        private const val FIRST_SIMPLE_GROUP_LABEL = "simple group 1"
+        private const val SECOND_SIMPLE_GROUP_LABEL = "simple group 2"
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index fcf4662..50ac2619 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -405,7 +405,8 @@
         testScope.runTest {
             val lockScreenState by collectLastValue(underTest.lockScreenState)
 
-            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+            val manualDnd = TestModeBuilder.MANUAL_DND_INACTIVE
+            zenModeRepository.addMode(manualDnd)
             runCurrent()
 
             assertThat(lockScreenState)
@@ -419,8 +420,7 @@
                     )
                 )
 
-            zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
-            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+            zenModeRepository.activateMode(manualDnd)
             runCurrent()
 
             assertThat(lockScreenState)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index 0329794..0a4198a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -32,7 +32,8 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -125,14 +126,11 @@
             )
 
         underTest =
-            HomeControlsKeyguardQuickAffordanceConfig(
-                context = context,
-                component = component,
-            )
+            HomeControlsKeyguardQuickAffordanceConfig(context = context, component = component)
     }
 
     @Test
-    fun state() = runBlockingTest {
+    fun state() = runTest(UnconfinedTestDispatcher()) {
         whenever(component.isEnabled()).thenReturn(isFeatureEnabled)
         whenever(controlsController.getFavorites())
             .thenReturn(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index 7d68cc0..0003d07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -19,19 +19,20 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Expandable
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
+import com.android.systemui.res.R
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -54,14 +55,11 @@
         whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
 
         underTest =
-            HomeControlsKeyguardQuickAffordanceConfig(
-                context = context,
-                component = component,
-            )
+            HomeControlsKeyguardQuickAffordanceConfig(context = context, component = component)
     }
 
     @Test
-    fun state_whenCannotShowWhileLocked_returnsHidden() = runBlockingTest {
+    fun state_whenCannotShowWhileLocked_returnsHidden() = runTest(UnconfinedTestDispatcher()) {
         whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
         whenever(component.isEnabled()).thenReturn(true)
         whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
@@ -81,7 +79,7 @@
     }
 
     @Test
-    fun state_whenListingControllerIsMissing_returnsHidden() = runBlockingTest {
+    fun state_whenListingControllerIsMissing_returnsHidden() = runTest(UnconfinedTestDispatcher()) {
         whenever(component.isEnabled()).thenReturn(true)
         whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
         whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
@@ -100,23 +98,26 @@
     }
 
     @Test
-    fun onQuickAffordanceTriggered_canShowWhileLockedSettingIsTrue() = runBlockingTest {
-        whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
+    fun onQuickAffordanceTriggered_canShowWhileLockedSettingIsTrue() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
 
-        val onClickedResult = underTest.onTriggered(expandable)
+            val onClickedResult = underTest.onTriggered(expandable)
 
-        assertThat(onClickedResult).isInstanceOf(OnTriggeredResult.StartActivity::class.java)
-        assertThat((onClickedResult as OnTriggeredResult.StartActivity).canShowWhileLocked).isTrue()
-    }
+            assertThat(onClickedResult).isInstanceOf(OnTriggeredResult.StartActivity::class.java)
+            assertThat((onClickedResult as OnTriggeredResult.StartActivity).canShowWhileLocked)
+                .isTrue()
+        }
 
     @Test
-    fun onQuickAffordanceTriggered_canShowWhileLockedSettingIsFalse() = runBlockingTest {
-        whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
+    fun onQuickAffordanceTriggered_canShowWhileLockedSettingIsFalse() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
 
-        val onClickedResult = underTest.onTriggered(expandable)
+            val onClickedResult = underTest.onTriggered(expandable)
 
-        assertThat(onClickedResult).isInstanceOf(OnTriggeredResult.StartActivity::class.java)
-        assertThat((onClickedResult as OnTriggeredResult.StartActivity).canShowWhileLocked)
-            .isFalse()
-    }
+            assertThat(onClickedResult).isInstanceOf(OnTriggeredResult.StartActivity::class.java)
+            assertThat((onClickedResult as OnTriggeredResult.StartActivity).canShowWhileLocked)
+                .isFalse()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index ca64cec..05a74c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -29,7 +29,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -56,60 +56,63 @@
     }
 
     @Test
-    fun affordance_setsUpRegistrationAndDeliversInitialModel() = runBlockingTest {
-        whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
-        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+    fun affordance_setsUpRegistrationAndDeliversInitialModel() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+            var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
 
-        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
+            val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
 
-        val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
-        verify(controller).addCallback(callbackCaptor.capture())
-        verify(controller)
-            .registerQRCodeScannerChangeObservers(
-                QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
-                QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
-            )
-        assertVisibleState(latest)
+            val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+            verify(controller).addCallback(callbackCaptor.capture())
+            verify(controller)
+                .registerQRCodeScannerChangeObservers(
+                    QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+                    QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE,
+                )
+            assertVisibleState(latest)
 
-        job.cancel()
-        verify(controller).removeCallback(callbackCaptor.value)
-    }
+            job.cancel()
+            verify(controller).removeCallback(callbackCaptor.value)
+        }
 
     @Test
-    fun affordance_scannerActivityChanged_deliversModelWithUpdatedIntent() = runBlockingTest {
-        whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
-        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
-        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
-        val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
-        verify(controller).addCallback(callbackCaptor.capture())
+    fun affordance_scannerActivityChanged_deliversModelWithUpdatedIntent() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+            var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+            val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
+            val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+            verify(controller).addCallback(callbackCaptor.capture())
 
-        whenever(controller.intent).thenReturn(INTENT_2)
-        callbackCaptor.value.onQRCodeScannerActivityChanged()
+            whenever(controller.intent).thenReturn(INTENT_2)
+            callbackCaptor.value.onQRCodeScannerActivityChanged()
 
-        assertVisibleState(latest)
+            assertVisibleState(latest)
 
-        job.cancel()
-        verify(controller).removeCallback(callbackCaptor.value)
-    }
+            job.cancel()
+            verify(controller).removeCallback(callbackCaptor.value)
+        }
 
     @Test
-    fun affordance_scannerPreferenceChanged_deliversVisibleModel() = runBlockingTest {
-        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
-        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
-        val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
-        verify(controller).addCallback(callbackCaptor.capture())
+    fun affordance_scannerPreferenceChanged_deliversVisibleModel() =
+        runTest(UnconfinedTestDispatcher()) {
+            var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+            val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
+            val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+            verify(controller).addCallback(callbackCaptor.capture())
 
-        whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
-        callbackCaptor.value.onQRCodeScannerPreferenceChanged()
+            whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+            callbackCaptor.value.onQRCodeScannerPreferenceChanged()
 
-        assertVisibleState(latest)
+            assertVisibleState(latest)
 
-        job.cancel()
-        verify(controller).removeCallback(callbackCaptor.value)
-    }
+            job.cancel()
+            verify(controller).removeCallback(callbackCaptor.value)
+        }
 
     @Test
-    fun affordance_scannerPreferenceChanged_deliversNone() = runBlockingTest {
+    fun affordance_scannerPreferenceChanged_deliversNone() = runTest(UnconfinedTestDispatcher()) {
         var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
         val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
         val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
@@ -128,30 +131,29 @@
     fun onQuickAffordanceTriggered() {
         assertThat(underTest.onTriggered(mock()))
             .isEqualTo(
-                OnTriggeredResult.StartActivity(
-                    intent = INTENT_1,
-                    canShowWhileLocked = true,
-                )
+                OnTriggeredResult.StartActivity(intent = INTENT_1, canShowWhileLocked = true)
             )
     }
 
     @Test
-    fun getPickerScreenState_enabledIfConfiguredOnDevice_isEnabledForPickerState() = runTest {
-        whenever(controller.isAllowedOnLockScreen).thenReturn(true)
-        whenever(controller.isAbleToLaunchScannerActivity).thenReturn(true)
+    fun getPickerScreenState_enabledIfConfiguredOnDevice_isEnabledForPickerState() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(controller.isAllowedOnLockScreen).thenReturn(true)
+            whenever(controller.isAbleToLaunchScannerActivity).thenReturn(true)
 
-        assertThat(underTest.getPickerScreenState())
-            .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
-    }
+            assertThat(underTest.getPickerScreenState())
+                .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
+        }
 
     @Test
-    fun getPickerScreenState_disabledIfConfiguredOnDevice_isDisabledForPickerState() = runTest {
-        whenever(controller.isAllowedOnLockScreen).thenReturn(true)
-        whenever(controller.isAbleToLaunchScannerActivity).thenReturn(false)
+    fun getPickerScreenState_disabledIfConfiguredOnDevice_isDisabledForPickerState() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(controller.isAllowedOnLockScreen).thenReturn(true)
+            whenever(controller.isAbleToLaunchScannerActivity).thenReturn(false)
 
-        assertThat(underTest.getPickerScreenState())
-            .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
-    }
+            assertThat(underTest.getPickerScreenState())
+                .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+        }
 
     private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.LockScreenState?) {
         assertThat(latest)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index e149687..dadcf71 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -35,10 +35,9 @@
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
-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.scene.data.repository.Idle
 import com.android.systemui.scene.data.repository.Transition
 import com.android.systemui.scene.data.repository.setSceneTransition
@@ -222,28 +221,6 @@
         }
 
     @Test
-    fun resetDismissAction() =
-        testScope.runTest {
-            kosmos.setSceneTransition(Idle(Scenes.Bouncer))
-            var wasOnCancelInvoked = false
-            startInteractor()
-            keyguardRepository.setDismissAction(
-                DismissAction.RunAfterKeyguardGone(
-                    dismissAction = {},
-                    onCancelAction = { wasOnCancelInvoked = true },
-                    message = "message",
-                    willAnimateOnLockscreen = true,
-                )
-            )
-            assertThat(wasOnCancelInvoked).isFalse()
-            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
-            runCurrent()
-
-            assertThat(wasOnCancelInvoked).isTrue()
-            assertThat(keyguardRepository.dismissAction.value).isEqualTo(DismissAction.None)
-        }
-
-    @Test
     fun doNotResetDismissActionOnUnlockedShade() =
         testScope.runTest {
             kosmos.setSceneTransition(Idle(Scenes.Bouncer))
@@ -272,37 +249,6 @@
         }
 
     @Test
-    fun resetDismissAction_onBouncer_OnAsleep() =
-        testScope.runTest {
-            kosmos.setSceneTransition(Idle(Scenes.Bouncer))
-            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.None
-            )
-            var wasOnCancelInvoked = false
-            startInteractor()
-
-            keyguardRepository.setDismissAction(
-                DismissAction.RunAfterKeyguardGone(
-                    dismissAction = {},
-                    onCancelAction = { wasOnCancelInvoked = true },
-                    message = "message",
-                    willAnimateOnLockscreen = true,
-                )
-            )
-            assertThat(wasOnCancelInvoked).isFalse()
-            kosmos.fakePowerRepository.updateWakefulness(
-                rawState = WakefulnessState.ASLEEP,
-                lastWakeReason = WakeSleepReason.POWER_BUTTON,
-                lastSleepReason = WakeSleepReason.TIMEOUT,
-                powerButtonLaunchGestureTriggered = false,
-            )
-            runCurrent()
-
-            assertThat(wasOnCancelInvoked).isTrue()
-            assertThat(keyguardRepository.dismissAction.value).isEqualTo(DismissAction.None)
-        }
-
-    @Test
     fun setDismissAction_callsCancelRunnableOnPreviousDismissAction() =
         testScope.runTest {
             val dismissAction by collectLastValue(keyguardRepository.dismissAction)
@@ -410,6 +356,25 @@
             assertThat(wasCancelActionInvoked).isFalse()
         }
 
+    @Test
+    fun clearDismissAction() =
+        kosmos.runTest {
+            val dismissAction by collectLastValue(fakeKeyguardRepository.dismissAction)
+            fakeKeyguardRepository.setDismissAction(
+                DismissAction.RunImmediately(
+                    onDismissAction = { KeyguardDone.IMMEDIATE },
+                    onCancelAction = {},
+                    message = "",
+                    willAnimateOnLockscreen = true,
+                )
+            )
+            assertThat(dismissAction).isNotEqualTo(DismissAction.None)
+
+            underTest.clearDismissAction()
+
+            assertThat(dismissAction).isEqualTo(DismissAction.None)
+        }
+
     private fun TestScope.startInteractor() {
         testScope.backgroundScope.launchTraced(
             "KeyguardDismissActionInteractorTest#startInteractor"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt
index bd26e42..bef995f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.widget.lockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
@@ -33,6 +34,8 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -52,14 +55,21 @@
         testScope.runTest {
             val values by collectValues(underTest.lockWhileAwakeEvents)
 
-            underTest.onKeyguardServiceDoKeyguardTimeout(options = null)
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+            runCurrent()
+
+            kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(
+                options = null
+            )
             runCurrent()
 
             assertThat(values)
                 .containsExactly(LockWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON)
 
             advanceTimeBy(1000)
-            underTest.onKeyguardServiceDoKeyguardTimeout(options = null)
+            kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(
+                options = null
+            )
             runCurrent()
 
             assertThat(values)
@@ -69,8 +79,15 @@
                 )
         }
 
+    /**
+     * We re-show keyguard when it's re-enabled, but only if it was originally showing when we
+     * disabled it.
+     *
+     * If it wasn't showing when originally disabled it, re-enabling it should do nothing (the
+     * keyguard will re-show next time we're locked).
+     */
     @Test
-    fun emitsWhenKeyguardEnabled_onlyIfShowingWhenDisabled() =
+    fun emitsWhenKeyguardReenabled_onlyIfShowingWhenDisabled() =
         testScope.runTest {
             val values by collectValues(underTest.lockWhileAwakeEvents)
 
@@ -98,4 +115,49 @@
 
             assertThat(values).containsExactly(LockWhileAwakeReason.KEYGUARD_REENABLED)
         }
+
+    /**
+     * Un-suppressing keyguard should never cause us to re-show. We'll re-show when we're next
+     * locked, even if we were showing when originally suppressed.
+     */
+    @Test
+    fun doesNotEmit_keyguardNoLongerSuppressed() =
+        testScope.runTest {
+            val values by collectValues(underTest.lockWhileAwakeEvents)
+
+            // Enable keyguard and then suppress it.
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+            whenever(kosmos.lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+            runCurrent()
+
+            assertEquals(0, values.size)
+
+            // Un-suppress keyguard.
+            whenever(kosmos.lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+            runCurrent()
+
+            assertEquals(0, values.size)
+        }
+
+    /**
+     * Lockdown and lockNow() should not cause us to lock while awake if we are suppressed via adb.
+     */
+    @Test
+    fun doesNotEmit_fromLockdown_orFromLockNow_ifEnabledButSuppressed() =
+        testScope.runTest {
+            val values by collectValues(underTest.lockWhileAwakeEvents)
+
+            // Set keyguard enabled, but then disable lockscreen (suppress it).
+            kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+            whenever(kosmos.lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+            runCurrent()
+
+            kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(null)
+            runCurrent()
+
+            kosmos.biometricSettingsRepository.setIsUserInLockdown(true)
+            runCurrent()
+
+            assertEquals(0, values.size)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
index 7e249e8..ead151e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
@@ -87,9 +87,9 @@
 
             assertEquals(
                 listOf(
-                    false, // Defaults to false.
+                    false // Defaults to false.
                 ),
-                canWake
+                canWake,
             )
 
             repository.setKeyguardEnabled(false)
@@ -100,33 +100,26 @@
                     false, // Default to false.
                     true, // True now that keyguard service is disabled
                 ),
-                canWake
+                canWake,
             )
 
             repository.setKeyguardEnabled(true)
             runCurrent()
 
-            assertEquals(
-                listOf(
-                    false,
-                    true,
-                    false,
-                ),
-                canWake
-            )
+            assertEquals(listOf(false, true, false), canWake)
         }
 
     @Test
     @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
-    fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled() =
+    fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled_onlyAfterWakefulnessChange() =
         testScope.runTest {
             val canWake by collectValues(underTest.canWakeDirectlyToGone)
 
             assertEquals(
                 listOf(
-                    false, // Defaults to false.
+                    false // Defaults to false.
                 ),
-                canWake
+                canWake,
             )
 
             whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
@@ -136,9 +129,9 @@
                 listOf(
                     // Still false - isLockScreenDisabled only causes canWakeDirectlyToGone to
                     // update on the next wake/sleep event.
-                    false,
+                    false
                 ),
-                canWake
+                canWake,
             )
 
             kosmos.powerInteractor.setAsleepForTest()
@@ -150,7 +143,7 @@
                     // True since we slept after setting isLockScreenDisabled=true
                     true,
                 ),
-                canWake
+                canWake,
             )
 
             kosmos.powerInteractor.setAwakeForTest()
@@ -159,25 +152,75 @@
             kosmos.powerInteractor.setAsleepForTest()
             runCurrent()
 
-            assertEquals(
-                listOf(
-                    false,
-                    true,
-                ),
-                canWake
-            )
+            assertEquals(listOf(false, true), canWake)
 
             whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
             kosmos.powerInteractor.setAwakeForTest()
             runCurrent()
 
+            assertEquals(listOf(false, true, false), canWake)
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled_lockNowEvent() =
+        testScope.runTest {
+            val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+            assertEquals(
+                listOf(
+                    false // Defaults to false.
+                ),
+                canWake,
+            )
+
+            whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    // Still false - isLockScreenDisabled only causes canWakeDirectlyToGone to
+                    // update on the next wakefulness or lockNow event.
+                    false
+                ),
+                canWake,
+            )
+
+            kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(null)
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    // True when lockNow() called after setting isLockScreenDisabled=true
+                    true,
+                ),
+                canWake,
+            )
+
+            whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    // Still true since no lockNow() calls made.
+                    true,
+                ),
+                canWake,
+            )
+
+            kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(null)
+            runCurrent()
+
             assertEquals(
                 listOf(
                     false,
                     true,
+                    // False again after the lockNow() call.
                     false,
                 ),
-                canWake
+                canWake,
             )
         }
 
@@ -189,9 +232,9 @@
 
             assertEquals(
                 listOf(
-                    false, // Defaults to false.
+                    false // Defaults to false.
                 ),
-                canWake
+                canWake,
             )
 
             repository.setBiometricUnlockState(BiometricUnlockMode.WAKE_AND_UNLOCK)
@@ -213,9 +256,9 @@
 
             assertEquals(
                 listOf(
-                    false, // Defaults to false.
+                    false // Defaults to false.
                 ),
-                canWake
+                canWake,
             )
 
             repository.setCanIgnoreAuthAndReturnToGone(true)
@@ -237,9 +280,9 @@
 
             assertEquals(
                 listOf(
-                    false, // Defaults to false.
+                    false // Defaults to false.
                 ),
-                canWake
+                canWake,
             )
 
             whenever(kosmos.devicePolicyManager.getMaximumTimeToLock(eq(null), anyInt()))
@@ -257,13 +300,7 @@
             )
             runCurrent()
 
-            assertEquals(
-                listOf(
-                    false,
-                    true,
-                ),
-                canWake
-            )
+            assertEquals(listOf(false, true), canWake)
 
             verify(kosmos.alarmManager)
                 .setExactAndAllowWhileIdle(
@@ -281,9 +318,9 @@
 
             assertEquals(
                 listOf(
-                    false, // Defaults to false.
+                    false // Defaults to false.
                 ),
-                canWake
+                canWake,
             )
 
             whenever(kosmos.devicePolicyManager.getMaximumTimeToLock(eq(null), anyInt()))
@@ -312,7 +349,7 @@
                     // Timed out, so we can ignore auth/return to GONE.
                     true,
                 ),
-                canWake
+                canWake,
             )
 
             verify(kosmos.alarmManager)
@@ -338,7 +375,7 @@
                     // alarm in flight that should be canceled.
                     false,
                 ),
-                canWake
+                canWake,
             )
 
             kosmos.powerInteractor.setAsleepForTest(
@@ -354,25 +391,17 @@
                     // Back to sleep.
                     true,
                 ),
-                canWake
+                canWake,
             )
 
             // Simulate the first sleep's alarm coming in.
             lastRegisteredBroadcastReceiver?.onReceive(
                 kosmos.mockedContext,
-                Intent("com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD")
+                Intent("com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD"),
             )
             runCurrent()
 
             // It should not have any effect.
-            assertEquals(
-                listOf(
-                    false,
-                    true,
-                    false,
-                    true,
-                ),
-                canWake
-            )
+            assertEquals(listOf(false, true, false, true), canWake)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index 2c12f87..2101987 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.flags.EnableSceneContainer
@@ -34,10 +35,12 @@
 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.model.asIterable
+import com.android.systemui.scene.data.model.sceneStackOf
 import com.android.systemui.scene.data.repository.Idle
 import com.android.systemui.scene.data.repository.Transition
-import com.android.systemui.scene.data.repository.sceneContainerRepository
 import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.interactor.sceneBackInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
@@ -52,6 +55,7 @@
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -85,7 +89,7 @@
     fun setUp() {
         // lazy value needs to be called here otherwise flow collection misbehaves
         underTest.value
-        kosmos.sceneContainerRepository.setTransitionState(sceneTransitions)
+        kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Lockscreen))
     }
 
     @Test
@@ -883,6 +887,7 @@
 
     @Test
     @EnableSceneContainer
+    @Ignore("b/378766637")
     fun lockscreenVisibilityWithScenes() =
         testScope.runTest {
             val isDeviceUnlocked by
@@ -967,15 +972,56 @@
 
     @Test
     @EnableSceneContainer
+    fun lockscreenVisibilityWithScenes_staysTrue_despiteEnteringIndirectly() =
+        testScope.runTest {
+            val isDeviceUnlocked by
+                collectLastValue(
+                    kosmos.deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked }
+                )
+            assertThat(isDeviceUnlocked).isFalse()
+
+            val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+
+            val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility)
+            assertThat(lockscreenVisibility).isTrue()
+
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
+            kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
+            kosmos.sceneBackInteractor.onSceneChange(from = Scenes.Lockscreen, to = Scenes.Shade)
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+            assertThat(lockscreenVisibility).isTrue()
+            val sceneBackStack by collectLastValue(kosmos.sceneBackInteractor.backStack)
+            assertThat(sceneBackStack?.asIterable()?.toList()).isEqualTo(listOf(Scenes.Lockscreen))
+
+            val isDeviceEntered by collectLastValue(kosmos.deviceEntryInteractor.isDeviceEntered)
+            val isDeviceEnteredDirectly by
+                collectLastValue(kosmos.deviceEntryInteractor.isDeviceEnteredDirectly)
+            runCurrent()
+            assertThat(isDeviceEntered).isFalse()
+            assertThat(isDeviceEnteredDirectly).isFalse()
+
+            kosmos.authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
+            kosmos.sceneBackInteractor.updateBackStack { sceneStackOf(Scenes.Gone) }
+            assertThat(sceneBackStack?.asIterable()?.toList()).isEqualTo(listOf(Scenes.Gone))
+
+            assertThat(isDeviceEntered).isTrue()
+            assertThat(isDeviceEnteredDirectly).isFalse()
+            assertThat(isDeviceUnlocked).isTrue()
+            assertThat(lockscreenVisibility).isTrue()
+        }
+
+    @Test
+    @EnableSceneContainer
     fun sceneContainer_usingGoingAwayAnimation_duringTransitionToGone() =
         testScope.runTest {
             val usingKeyguardGoingAwayAnimation by
                 collectLastValue(underTest.value.usingKeyguardGoingAwayAnimation)
 
-            sceneTransitions.value = lsToGone
+            kosmos.setSceneTransition(lsToGone)
             assertThat(usingKeyguardGoingAwayAnimation).isTrue()
 
-            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
+            kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone))
             assertThat(usingKeyguardGoingAwayAnimation).isFalse()
         }
 
@@ -986,14 +1032,14 @@
             val usingKeyguardGoingAwayAnimation by
                 collectLastValue(underTest.value.usingKeyguardGoingAwayAnimation)
 
-            sceneTransitions.value = lsToGone
+            kosmos.setSceneTransition(lsToGone)
             surfaceBehindIsAnimatingFlow.emit(true)
             assertThat(usingKeyguardGoingAwayAnimation).isTrue()
 
-            sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
+            kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone))
             assertThat(usingKeyguardGoingAwayAnimation).isTrue()
 
-            sceneTransitions.value = goneToLs
+            kosmos.setSceneTransition(goneToLs)
             assertThat(usingKeyguardGoingAwayAnimation).isTrue()
 
             surfaceBehindIsAnimatingFlow.emit(false)
@@ -1003,11 +1049,6 @@
     companion object {
         private val progress = MutableStateFlow(0f)
 
-        private val sceneTransitions =
-            MutableStateFlow<ObservableTransitionState>(
-                ObservableTransitionState.Idle(Scenes.Lockscreen)
-            )
-
         private val lsToGone =
             ObservableTransitionState.Transition(
                 Scenes.Lockscreen,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index 74a0bafda..df4d5ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -195,6 +195,7 @@
     @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
     fun setSurfaceBehindVisibility_goesAwayFirst_andIgnoresSecondCall_with_keyguard_shell_transitions() {
         underTest.setLockscreenShown(true)
+        verify(keyguardTransitions).startKeyguardTransition(true, false)
         underTest.setSurfaceBehindVisibility(true)
         verify(keyguardTransitions).startKeyguardTransition(false, false)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
index 5c4b743..97e6763 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
@@ -32,12 +32,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.power.data.repository.fakePowerRepository
@@ -48,7 +47,6 @@
 import com.android.systemui.scene.shared.model.TransitionKeys
 import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
 import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.pow
@@ -69,12 +67,13 @@
 class LockscreenUserActionsViewModelTest : SysuiTestCase() {
 
     companion object {
-        private const val parameterCount = 6
+        private const val parameterCount = 7
 
         @Parameters(
             name =
                 "canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," +
-                    " isSingleShade={3}, isCommunalAvailable={4}, isShadeTouchable={5}"
+                    " isSingleShade={3}, isCommunalAvailable={4}, isShadeTouchable={5}," +
+                    " isOccluded={6}"
         )
         @JvmStatic
         fun combinations() = buildList {
@@ -87,6 +86,7 @@
                             /* isSingleShade= */ combination and 8 != 0,
                             /* isCommunalAvailable= */ combination and 16 != 0,
                             /* isShadeTouchable= */ combination and 32 != 0,
+                            /* isOccluded= */ combination and 64 != 0,
                         )
                         .also { check(it.size == parameterCount) }
                 )
@@ -116,10 +116,12 @@
             downFromEdge: Boolean,
             isNarrowScreen: Boolean,
             isShadeTouchable: Boolean,
+            isOccluded: Boolean,
         ): SceneKey? {
             return when {
                 !isShadeTouchable -> null
-                downFromEdge && isNarrowScreen -> Scenes.QuickSettings
+                downFromEdge && isNarrowScreen && !isOccluded -> Scenes.QuickSettings
+                downFromEdge && isNarrowScreen && isOccluded -> null
                 else -> Scenes.Shade
             }
         }
@@ -168,8 +170,9 @@
     @JvmField @Parameter(3) var isNarrowScreen: Boolean = true
     @JvmField @Parameter(4) var isCommunalAvailable: Boolean = false
     @JvmField @Parameter(5) var isShadeTouchable: Boolean = false
+    @JvmField @Parameter(6) var isOccluded: Boolean = false
 
-    private val underTest by lazy { createLockscreenSceneViewModel() }
+    private val underTest by lazy { kosmos.lockscreenUserActionsViewModel }
 
     @Test
     @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
@@ -196,6 +199,7 @@
                         WakefulnessState.ASLEEP
                     }
             )
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = isOccluded)
 
             val userActions by collectLastValue(underTest.actions)
             val downDestination =
@@ -217,6 +221,7 @@
                         downFromEdge = downFromEdge,
                         isNarrowScreen = isNarrowScreen,
                         isShadeTouchable = isShadeTouchable,
+                        isOccluded = isOccluded,
                     )
                 )
 
@@ -285,6 +290,7 @@
                         WakefulnessState.ASLEEP
                     }
             )
+            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = isOccluded)
 
             val userActions by collectLastValue(underTest.actions)
 
@@ -300,8 +306,7 @@
                 // Top edge is not applicable in dual shade, as well as two-finger swipe.
                 assertThat(downDestination).isNull()
             } else {
-                assertThat(downDestination)
-                    .isEqualTo(ShowOverlay(Overlays.NotificationsShade, isIrreversible = true))
+                assertThat(downDestination).isEqualTo(ShowOverlay(Overlays.NotificationsShade))
                 assertThat(downDestination?.transitionKey).isNull()
             }
 
@@ -317,7 +322,7 @@
                 downWithTwoPointers -> assertThat(downFromTopRightDestination).isNull()
                 else -> {
                     assertThat(downFromTopRightDestination)
-                        .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true))
+                        .isEqualTo(ShowOverlay(Overlays.QuickSettingsShade))
                     assertThat(downFromTopRightDestination?.transitionKey).isNull()
                 }
             }
@@ -354,12 +359,4 @@
                     )
                 )
         }
-
-    private fun createLockscreenSceneViewModel(): LockscreenUserActionsViewModel {
-        return LockscreenUserActionsViewModel(
-            deviceEntryInteractor = kosmos.deviceEntryInteractor,
-            communalInteractor = kosmos.communalInteractor,
-            shadeInteractor = kosmos.shadeInteractor,
-        )
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
index 02825a5..ff00bfb5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -18,6 +18,7 @@
 
 import android.hardware.display.displayManager
 import android.media.projection.MediaProjectionInfo
+import android.media.projection.StopReason
 import android.os.Binder
 import android.os.Handler
 import android.os.UserHandle
@@ -339,8 +340,9 @@
     @Test
     fun stopProjecting_invokesManager() =
         testScope.runTest {
-            repo.stopProjecting()
+            repo.stopProjecting(StopReason.STOP_QS_TILE)
 
-            verify(fakeMediaProjectionManager.mediaProjectionManager).stopActiveProjection()
+            verify(fakeMediaProjectionManager.mediaProjectionManager)
+                .stopActiveProjection(StopReason.STOP_QS_TILE)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
index 77be8c7..6ec38ba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryTest.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.mediarouter.data.repository
 
-import androidx.test.filters.SmallTest
+import android.media.projection.StopReason
 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.Kosmos
@@ -101,7 +102,7 @@
                 origin = CastDevice.CastOrigin.MediaRouter,
             )
 
-        underTest.stopCasting(device)
+        underTest.stopCasting(device, StopReason.STOP_UNKNOWN)
 
         assertThat(castController.lastStoppedDevice).isEqualTo(device)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 646722b..555ba56 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -90,6 +90,7 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavBarHelper;
 import com.android.systemui.navigationbar.NavigationBarController;
@@ -116,8 +117,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
-import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.AutoHideControllerStore;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
@@ -148,6 +148,7 @@
 @SmallTest
 public class NavigationBarTest extends SysuiTestCase {
     private static final int EXTERNAL_DISPLAY_ID = 2;
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
 
     private NavigationBar mNavigationBar;
     private NavigationBar mExternalDisplayNavigationBar;
@@ -209,11 +210,7 @@
     @Mock
     private LightBarController mLightBarController;
     @Mock
-    private LightBarControllerStore mLightBarControllerStore;
-    @Mock
-    private AutoHideController mAutoHideController;
-    @Mock
-    private AutoHideController.Factory mAutoHideControllerFactory;
+    private LightBarController.Factory mLightBarcontrollerFactory;
     @Mock
     private WindowManager mWindowManager;
     @Mock
@@ -248,6 +245,8 @@
     private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
     private TaskStackChangeListeners mTaskStackChangeListeners =
             TaskStackChangeListeners.getTestInstance();
+    private final AutoHideControllerStore mAutoHideControllerStore =
+            mKosmos.getAutoHideControllerStore();
 
     @Rule
     public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
@@ -258,8 +257,7 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        when(mLightBarControllerStore.forDisplay(anyInt())).thenReturn(mLightBarController);
-        when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
+        when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
         when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton);
         when(mNavigationBarView.getRecentsButton()).thenReturn(mRecentsButton);
         when(mNavigationBarView.getAccessibilityButton()).thenReturn(mAccessibilityButton);
@@ -650,9 +648,9 @@
                 mFakeExecutor,
                 mUiEventLogger,
                 mNavBarHelper,
-                mLightBarControllerStore,
-                mAutoHideController,
-                mAutoHideControllerFactory,
+                mLightBarController,
+                mLightBarcontrollerFactory,
+                mAutoHideControllerStore,
                 Optional.of(mTelecomManager),
                 mInputMethodManager,
                 mDeadZone,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
index b5fc52f..87e2fef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.qs.composefragment.viewmodel
 
+import androidx.compose.runtime.snapshots.Snapshot
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.runCurrent
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
@@ -36,7 +38,6 @@
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.test.setMain
-import org.junit.After
 import org.junit.Before
 import org.junit.runner.RunWith
 
@@ -58,11 +59,14 @@
     @Before
     fun setUp() {
         Dispatchers.setMain(kosmos.testDispatcher)
-    }
+        onTeardown { Dispatchers.resetMain() }
 
-    @After
-    fun teardown() {
-        Dispatchers.resetMain()
+        val globalWriteObserverHandle =
+            Snapshot.registerGlobalWriteObserver {
+                Snapshot.sendApplyNotifications()
+                kosmos.runCurrent()
+            }
+        onTeardown { globalWriteObserverHandle.dispose() }
     }
 
     protected inline fun TestScope.testWithinLifecycle(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 921a8a6..111b3b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -20,7 +20,6 @@
 import android.content.testableContext
 import android.graphics.Rect
 import android.testing.TestableLooper.RunWithLooper
-import androidx.compose.runtime.snapshots.Snapshot
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
@@ -186,15 +185,12 @@
                 val squishiness by collectLastValue(tileSquishinessInteractor.squishiness)
 
                 underTest.squishinessFraction = 0.3f
-                Snapshot.sendApplyNotifications()
                 assertThat(squishiness).isWithin(epsilon).of(0.3f.constrainSquishiness())
 
                 underTest.squishinessFraction = 0f
-                Snapshot.sendApplyNotifications()
                 assertThat(squishiness).isWithin(epsilon).of(0f.constrainSquishiness())
 
                 underTest.squishinessFraction = 1f
-                Snapshot.sendApplyNotifications()
                 assertThat(squishiness).isWithin(epsilon).of(1f.constrainSquishiness())
             }
         }
@@ -328,13 +324,9 @@
                 setMediaState(ACTIVE_MEDIA)
 
                 setConfigurationForMediaInRow(mediaInRow = false)
-                Snapshot.sendApplyNotifications()
-                runCurrent()
                 assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
 
                 setConfigurationForMediaInRow(mediaInRow = true)
-                Snapshot.sendApplyNotifications()
-                runCurrent()
                 assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.COLLAPSED)
             }
         }
@@ -347,13 +339,9 @@
                 setMediaState(ACTIVE_MEDIA)
 
                 setConfigurationForMediaInRow(mediaInRow = false)
-                Snapshot.sendApplyNotifications()
-                runCurrent()
                 assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
 
                 setConfigurationForMediaInRow(mediaInRow = true)
-                Snapshot.sendApplyNotifications()
-                runCurrent()
                 assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
             }
         }
@@ -366,13 +354,9 @@
                 setMediaState(ACTIVE_MEDIA)
 
                 setCollapsedMediaInLandscape(false)
-                Snapshot.sendApplyNotifications()
-                runCurrent()
                 assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
 
                 setCollapsedMediaInLandscape(true)
-                Snapshot.sendApplyNotifications()
-                runCurrent()
                 assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.COLLAPSED)
             }
         }
@@ -401,13 +385,11 @@
                 underTest.squishinessFraction = 0.3f
 
                 underTest.shouldUpdateSquishinessOnMedia = true
-                Snapshot.sendApplyNotifications()
                 runCurrent()
 
                 assertThat(underTest.qsMediaHost.squishFraction).isWithin(0.01f).of(0.3f)
 
                 underTest.shouldUpdateSquishinessOnMedia = false
-                Snapshot.sendApplyNotifications()
                 runCurrent()
                 assertThat(underTest.qsMediaHost.squishFraction).isWithin(0.01f).of(1f)
             }
@@ -421,20 +403,15 @@
                 underTest.squishinessFraction = 0.3f
 
                 sysuiStatusBarStateController.setState(StatusBarState.SHADE)
-                Snapshot.sendApplyNotifications()
                 runCurrent()
                 assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(0.3f)
 
                 sysuiStatusBarStateController.setState(StatusBarState.KEYGUARD)
                 runCurrent()
-                Snapshot.sendApplyNotifications()
-                runCurrent()
                 assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(1f)
 
                 sysuiStatusBarStateController.setState(StatusBarState.SHADE_LOCKED)
                 runCurrent()
-                Snapshot.sendApplyNotifications()
-                runCurrent()
                 assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(1f)
             }
         }
@@ -446,8 +423,6 @@
                 setMediaState(ACTIVE_MEDIA)
 
                 setConfigurationForMediaInRow(false)
-                Snapshot.sendApplyNotifications()
-                runCurrent()
 
                 assertThat(underTest.qqsMediaHost.disappearParameters)
                     .isEqualTo(disappearParamsColumn)
@@ -455,8 +430,6 @@
                     .isEqualTo(disappearParamsColumn)
 
                 setConfigurationForMediaInRow(true)
-                Snapshot.sendApplyNotifications()
-                runCurrent()
 
                 assertThat(underTest.qqsMediaHost.disappearParameters).isEqualTo(disappearParamsRow)
                 assertThat(underTest.qsMediaHost.disappearParameters).isEqualTo(disappearParamsRow)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOn.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOn.kt
new file mode 100644
index 0000000..f02856c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOn.kt
@@ -0,0 +1,276 @@
+/*
+ * 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.Dialog
+import android.app.StatusBarManager
+import android.content.ComponentName
+import android.content.DialogInterface
+import android.graphics.drawable.Icon
+import android.platform.test.annotations.EnableFlags
+import android.view.WindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IAddTileResultCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.qs.external.ui.dialog.FakeTileRequestDialogComposeDelegateFactory
+import com.android.systemui.qs.external.ui.dialog.fake
+import com.android.systemui.qs.external.ui.dialog.tileRequestDialogComposeDelegateFactory
+import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.runOnMainThreadAndWaitForIdleSync
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import kotlin.test.Test
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(QSComposeFragment.FLAG_NAME)
+class TileServiceRequestControllerTestComposeOn : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    private val userId: Int
+        get() = kosmos.currentTilesInteractor.userId.value
+
+    private val mockIcon: Icon
+        get() = mock()
+
+    private val Kosmos.underTest by Kosmos.Fixture { tileServiceRequestController }
+
+    @Before
+    fun setup() {
+        kosmos.fakeInstalledTilesRepository.setInstalledPackagesForUser(
+            userId,
+            setOf(TEST_COMPONENT),
+        )
+        // Start with some tiles, so adding tiles is possible (adding tiles waits until there's
+        // at least one tile, to wait for setup).
+        kosmos.currentTilesInteractor.setTiles(listOf(TileSpec.create("a")))
+        kosmos.runCurrent()
+    }
+
+    @Test
+    fun tileAlreadyAdded_correctResult() =
+        kosmos.runTest {
+            // An existing tile
+            currentTilesInteractor.setTiles(listOf(TILE_SPEC))
+            runCurrent()
+
+            val callback = Callback()
+            runOnMainThreadAndWaitForIdleSync {
+                val dialog =
+                    underTest.requestTileAdd(
+                        TEST_UID,
+                        TEST_COMPONENT,
+                        TEST_APP_NAME,
+                        TEST_LABEL,
+                        mockIcon,
+                        callback,
+                    )
+                assertThat(dialog).isNull()
+            }
+
+            assertThat(callback.lastAccepted).isEqualTo(TILE_ALREADY_ADDED)
+            assertThat(currentTilesInteractor.currentTilesSpecs.count { it == TILE_SPEC })
+                .isEqualTo(1)
+        }
+
+    @Test
+    fun cancelDialog_dismissResult_tileNotAdded() =
+        kosmos.runTest {
+            val callback = Callback()
+            val dialog = runOnMainThreadAndWaitForIdleSync {
+                underTest.requestTileAdd(
+                    TEST_UID,
+                    TEST_COMPONENT,
+                    TEST_APP_NAME,
+                    TEST_LABEL,
+                    mockIcon,
+                    callback,
+                )!!
+            }
+
+            runOnMainThreadAndWaitForIdleSync { dialog.cancel() }
+
+            assertThat(callback.lastAccepted).isEqualTo(DISMISSED)
+            assertThat(currentTilesInteractor.currentTilesSpecs).doesNotContain(TILE_SPEC)
+        }
+
+    @Test
+    fun cancelAndThenDismissSendsOnlyOnce() =
+        kosmos.runTest {
+            // After cancelling, the dialog is dismissed. This tests that only one response
+            // is sent.
+            val callback = Callback()
+            val dialog = runOnMainThreadAndWaitForIdleSync {
+                underTest.requestTileAdd(
+                    TEST_UID,
+                    TEST_COMPONENT,
+                    TEST_APP_NAME,
+                    TEST_LABEL,
+                    mockIcon,
+                    callback,
+                )!!
+            }
+
+            runOnMainThreadAndWaitForIdleSync {
+                dialog.cancel()
+                dialog.dismiss()
+            }
+
+            assertThat(callback.lastAccepted).isEqualTo(DISMISSED)
+            assertThat(callback.timesCalled).isEqualTo(1)
+        }
+
+    @Test
+    fun showAllUsers_set() =
+        kosmos.runTest {
+            val dialog = runOnMainThreadAndWaitForIdleSync {
+                underTest.requestTileAdd(
+                    TEST_UID,
+                    TEST_COMPONENT,
+                    TEST_APP_NAME,
+                    TEST_LABEL,
+                    mockIcon,
+                    Callback(),
+                )!!
+            }
+            onTeardown { dialog.cancel() }
+
+            assertThat(dialog.isShowForAllUsers).isTrue()
+        }
+
+    @Test
+    fun cancelOnTouchOutside_set() =
+        kosmos.runTest {
+            val dialog = runOnMainThreadAndWaitForIdleSync {
+                underTest.requestTileAdd(
+                    TEST_UID,
+                    TEST_COMPONENT,
+                    TEST_APP_NAME,
+                    TEST_LABEL,
+                    mockIcon,
+                    Callback(),
+                )!!
+            }
+            onTeardown { dialog.cancel() }
+
+            assertThat(dialog.isCancelOnTouchOutside).isTrue()
+        }
+
+    @Test
+    fun positiveAction_tileAdded() =
+        kosmos.runTest {
+            // Not using a real dialog
+            tileRequestDialogComposeDelegateFactory = FakeTileRequestDialogComposeDelegateFactory()
+
+            val callback = Callback()
+            val dialog =
+                underTest.requestTileAdd(
+                    TEST_UID,
+                    TEST_COMPONENT,
+                    TEST_APP_NAME,
+                    TEST_LABEL,
+                    mockIcon,
+                    callback,
+                )
+
+            tileRequestDialogComposeDelegateFactory.fake.clickListener.onClick(
+                dialog,
+                DialogInterface.BUTTON_POSITIVE,
+            )
+            runCurrent()
+
+            assertThat(callback.lastAccepted).isEqualTo(ADD_TILE)
+            assertThat(currentTilesInteractor.currentTilesSpecs).hasSize(2)
+            assertThat(currentTilesInteractor.currentTilesSpecs.last()).isEqualTo(TILE_SPEC)
+        }
+
+    @Test
+    fun negativeAction_tileNotAdded() =
+        kosmos.runTest {
+            // Not using a real dialog
+            tileRequestDialogComposeDelegateFactory = FakeTileRequestDialogComposeDelegateFactory()
+
+            val callback = Callback()
+            val dialog =
+                underTest.requestTileAdd(
+                    TEST_UID,
+                    TEST_COMPONENT,
+                    TEST_APP_NAME,
+                    TEST_LABEL,
+                    mockIcon,
+                    callback,
+                )
+
+            tileRequestDialogComposeDelegateFactory.fake.clickListener.onClick(
+                dialog,
+                DialogInterface.BUTTON_NEGATIVE,
+            )
+            runCurrent()
+
+            assertThat(callback.lastAccepted).isEqualTo(DONT_ADD_TILE)
+            assertThat(currentTilesInteractor.currentTilesSpecs).doesNotContain(TILE_SPEC)
+        }
+
+    companion object {
+        private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls")
+        private val TILE_SPEC = TileSpec.create(TEST_COMPONENT)
+        private const val TEST_APP_NAME = "App"
+        private const val TEST_LABEL = "Label"
+        private const val TEST_UID = 12345
+
+        const val ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED
+        const val DONT_ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED
+        const val TILE_ALREADY_ADDED = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED
+        const val DISMISSED = StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED
+    }
+
+    private class Callback : IAddTileResultCallback.Stub(), Consumer<Int> {
+        var lastAccepted: Int? = null
+            private set
+
+        var timesCalled = 0
+            private set
+
+        override fun accept(t: Int) {
+            lastAccepted = t
+            timesCalled++
+        }
+
+        override fun onTileRequest(r: Int) {
+            accept(r)
+        }
+    }
+}
+
+private val Dialog.isShowForAllUsers: Boolean
+    get() =
+        window!!.attributes.privateFlags and
+            WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS != 0
+
+private val Dialog.isCancelOnTouchOutside: Boolean
+    get() = window!!.shouldCloseOnTouchOutside()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt
new file mode 100644
index 0000000..369975a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.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.systemui.qs.external.ui.viewmodel
+
+import android.content.applicationContext
+import android.content.res.mainResources
+import android.graphics.drawable.Icon
+import android.graphics.drawable.TestStubDrawable
+import android.service.quicksettings.Tile
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.app.iUriGrantsManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.external.TileData
+import com.android.systemui.qs.panels.ui.viewmodel.toUiState
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Expect
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TileRequestDialogViewModelTest : SysuiTestCase() {
+
+    @get:Rule val expect: Expect = Expect.create()
+
+    private val kosmos = testKosmos()
+
+    private val icon: Icon = mock {
+        on {
+            loadDrawableCheckingUriGrant(
+                kosmos.applicationContext,
+                kosmos.iUriGrantsManager,
+                TEST_UID,
+                TEST_PACKAGE,
+            )
+        } doReturn (loadedDrawable)
+    }
+
+    private val tileData = TileData(TEST_UID, TEST_APP_NAME, TEST_LABEL, icon, TEST_PACKAGE)
+
+    private val Kosmos.underTest by
+        Kosmos.Fixture { tileRequestDialogViewModelFactory.create(applicationContext, tileData) }
+
+    private val baseResultLegacyState =
+        QSTile.State().apply {
+            label = TEST_LABEL
+            state = Tile.STATE_ACTIVE
+            handlesLongClick = false
+        }
+
+    @Test
+    fun uiState_beforeActivation_hasDefaultIcon_andCorrectData() =
+        kosmos.runTest {
+            val expectedState =
+                baseResultLegacyState.apply { icon = defaultIcon }.toUiState(mainResources)
+
+            with(underTest.uiState) {
+                expect.that(label).isEqualTo(TEST_LABEL)
+                expect.that(secondaryLabel).isEmpty()
+                expect.that(state).isEqualTo(expectedState.state)
+                expect.that(handlesLongClick).isFalse()
+                expect.that(handlesSecondaryClick).isFalse()
+                expect.that(icon.get()).isEqualTo(defaultIcon)
+                expect.that(sideDrawable).isNull()
+                expect.that(accessibilityUiState).isEqualTo(expectedState.accessibilityUiState)
+            }
+        }
+
+    @Test
+    fun uiState_afterActivation_hasCorrectIcon_andCorrectData() =
+        kosmos.runTest {
+            val expectedState =
+                baseResultLegacyState
+                    .apply { icon = QSTileImpl.DrawableIcon(loadedDrawable) }
+                    .toUiState(mainResources)
+
+            underTest.activateIn(testScope)
+            runCurrent()
+
+            with(underTest.uiState) {
+                expect.that(label).isEqualTo(TEST_LABEL)
+                expect.that(secondaryLabel).isEmpty()
+                expect.that(state).isEqualTo(expectedState.state)
+                expect.that(handlesLongClick).isFalse()
+                expect.that(handlesSecondaryClick).isFalse()
+                expect.that(icon.get()).isEqualTo(QSTileImpl.DrawableIcon(loadedDrawable))
+                expect.that(sideDrawable).isNull()
+                expect.that(accessibilityUiState).isEqualTo(expectedState.accessibilityUiState)
+            }
+        }
+
+    @Test
+    fun uiState_afterActivation_iconNotLoaded_usesDefault() =
+        kosmos.runTest {
+            icon.stub {
+                on {
+                    loadDrawableCheckingUriGrant(
+                        kosmos.applicationContext,
+                        kosmos.iUriGrantsManager,
+                        TEST_UID,
+                        TEST_PACKAGE,
+                    )
+                } doReturn (null)
+            }
+
+            underTest.activateIn(testScope)
+            runCurrent()
+
+            assertThat(underTest.uiState.icon.get()).isEqualTo(defaultIcon)
+        }
+
+    companion object {
+        private val defaultIcon: QSTile.Icon = ResourceIcon.get(R.drawable.android)
+        private val loadedDrawable = TestStubDrawable("loaded")
+
+        private const val TEST_PACKAGE = "test_pkg"
+        private const val TEST_APP_NAME = "App"
+        private const val TEST_LABEL = "Label"
+        private const val TEST_UID = 12345
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt
index 4acf3ee..645efae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt
@@ -27,7 +27,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class MutableSelectionStateTest : SysuiTestCase() {
-    private val underTest = MutableSelectionState({}, {})
+    private val underTest = MutableSelectionState()
 
     @Test
     fun selectTile_isCorrectlySelected() {
@@ -48,120 +48,6 @@
         assertThat(underTest.selection?.manual).isFalse()
     }
 
-    @Test
-    fun startResize_createsResizingState() {
-        assertThat(underTest.resizingState).isNull()
-
-        // Resizing starts but no tile is selected
-        underTest.onResizingDragStart(TileWidths(0, 0, 1))
-        assertThat(underTest.resizingState).isNull()
-
-        // Resizing starts with a selected tile
-        underTest.select(TEST_SPEC, manual = true)
-        underTest.onResizingDragStart(TileWidths(0, 0, 1))
-
-        assertThat(underTest.resizingState).isNotNull()
-    }
-
-    @Test
-    fun endResize_clearsResizingState() {
-        val spec = TileSpec.create("testSpec")
-
-        // Resizing starts with a selected tile
-        underTest.select(spec, manual = true)
-        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
-        assertThat(underTest.resizingState).isNotNull()
-
-        underTest.onResizingDragEnd()
-        assertThat(underTest.resizingState).isNull()
-    }
-
-    @Test
-    fun unselect_clearsResizingState() {
-        // Resizing starts with a selected tile
-        underTest.select(TEST_SPEC, manual = true)
-        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
-        assertThat(underTest.resizingState).isNotNull()
-
-        underTest.unSelect()
-        assertThat(underTest.resizingState).isNull()
-    }
-
-    @Test
-    fun onResizingDrag_updatesResizingState() {
-        // Resizing starts with a selected tile
-        underTest.select(TEST_SPEC, manual = true)
-        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
-        assertThat(underTest.resizingState).isNotNull()
-
-        underTest.onResizingDrag(5f)
-        assertThat(underTest.resizingState?.width).isEqualTo(5)
-
-        underTest.onResizingDrag(2f)
-        assertThat(underTest.resizingState?.width).isEqualTo(7)
-
-        underTest.onResizingDrag(-6f)
-        assertThat(underTest.resizingState?.width).isEqualTo(1)
-    }
-
-    @Test
-    fun onResizingDrag_receivesResizeCallback() {
-        var resized = false
-        val onResize: (TileSpec) -> Unit = {
-            assertThat(it).isEqualTo(TEST_SPEC)
-            resized = !resized
-        }
-        val underTest = MutableSelectionState(onResize = onResize, {})
-
-        // Resizing starts with a selected tile
-        underTest.select(TEST_SPEC, true)
-        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
-        assertThat(underTest.resizingState).isNotNull()
-
-        // Drag under the threshold
-        underTest.onResizingDrag(1f)
-        assertThat(resized).isFalse()
-
-        // Drag over the threshold
-        underTest.onResizingDrag(5f)
-        assertThat(resized).isTrue()
-
-        // Drag back under the threshold
-        underTest.onResizingDrag(-5f)
-        assertThat(resized).isFalse()
-    }
-
-    @Test
-    fun onResizingEnded_receivesResizeEndCallback() {
-        var resizeEnded = false
-        val onResizeEnd: (TileSpec) -> Unit = { resizeEnded = true }
-        val underTest = MutableSelectionState({}, onResizeEnd = onResizeEnd)
-
-        // Resizing starts with a selected tile
-        underTest.select(TEST_SPEC, true)
-        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
-
-        underTest.onResizingDragEnd()
-        assertThat(resizeEnded).isTrue()
-    }
-
-    @Test
-    fun onResizingEnded_setsSelectionAutomatically() {
-        val underTest = MutableSelectionState({}, {})
-
-        // Resizing starts with a selected tile
-        underTest.select(TEST_SPEC, manual = true)
-        underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
-
-        // Assert the selection was manual
-        assertThat(underTest.selection?.manual).isTrue()
-
-        underTest.onResizingDragEnd()
-
-        // Assert the selection is no longer manual due to the resizing
-        assertThat(underTest.selection?.manual).isFalse()
-    }
-
     companion object {
         private val TEST_SPEC = TileSpec.create("testSpec")
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingStateTest.kt
index 6e66783..2206f4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingStateTest.kt
@@ -19,7 +19,9 @@
 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.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -27,36 +29,32 @@
 @RunWith(AndroidJUnit4::class)
 class ResizingStateTest : SysuiTestCase() {
 
+    private val underTest =
+        ResizingState(TileSpec.create("a"), startsAsIcon = true).apply { updateAnchors(10f, 20f) }
+
     @Test
-    fun drag_updatesStateCorrectly() {
-        var resized = false
-        val underTest =
-            ResizingState(TileWidths(base = 0, min = 0, max = 10)) { resized = !resized }
-
-        assertThat(underTest.width).isEqualTo(0)
-
-        underTest.onDrag(2f)
-        assertThat(underTest.width).isEqualTo(2)
-
-        underTest.onDrag(1f)
-        assertThat(underTest.width).isEqualTo(3)
-        assertThat(resized).isTrue()
-
-        underTest.onDrag(-1f)
-        assertThat(underTest.width).isEqualTo(2)
-        assertThat(resized).isFalse()
+    fun newResizingState_setInitialValueCorrectly() {
+        assertThat(underTest.anchoredDraggableState.currentValue).isEqualTo(QSDragAnchor.Icon)
     }
 
     @Test
-    fun dragOutOfBounds_isClampedCorrectly() {
-        val underTest = ResizingState(TileWidths(base = 0, min = 0, max = 10)) {}
+    fun updateAnchors_setBoundsCorrectly() {
+        assertThat(underTest.bounds).isEqualTo(10f to 20f)
+    }
 
-        assertThat(underTest.width).isEqualTo(0)
+    @Test
+    fun dragOverThreshold_resizesToLarge() = runTest {
+        underTest.anchoredDraggableState.anchoredDrag { dragTo(16f) }
 
-        underTest.onDrag(100f)
-        assertThat(underTest.width).isEqualTo(10)
+        assertThat(underTest.temporaryResizeOperation.spec).isEqualTo(TileSpec.create("a"))
+        assertThat(underTest.temporaryResizeOperation.toIcon).isFalse()
+    }
 
-        underTest.onDrag(-200f)
-        assertThat(underTest.width).isEqualTo(0)
+    @Test
+    fun dragUnderThreshold_staysIcon() = runTest {
+        underTest.anchoredDraggableState.anchoredDrag { dragTo(12f) }
+
+        assertThat(underTest.temporaryResizeOperation.spec).isEqualTo(TileSpec.create("a"))
+        assertThat(underTest.temporaryResizeOperation.toIcon).isTrue()
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt
new file mode 100644
index 0000000..98770c7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.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.qs.panels.ui.viewmodel
+
+
+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.qs.FakeQSTile
+import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.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
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class DetailsViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private lateinit var underTest: DetailsViewModel
+    private val spec = TileSpec.create("internet")
+    private val specNoDetails = TileSpec.create("NoDetailsTile")
+
+    @Before
+    fun setUp() {
+        underTest = kosmos.detailsViewModel
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun changeTileDetailsViewModel() = with(kosmos) {
+        testScope.runTest {
+            val specs = listOf(
+                spec,
+                specNoDetails,
+            )
+            tileSpecRepository.setTiles(0, specs)
+            runCurrent()
+
+            val tiles = currentTilesInteractor.currentTiles.value
+
+            assertThat(currentTilesInteractor.currentTilesSpecs.size).isEqualTo(2)
+            assertThat(tiles!![1].spec).isEqualTo(specNoDetails)
+            (tiles!![1].tile as FakeQSTile).hasDetailsViewModel = false
+
+            assertThat(underTest.activeTileDetails).isNull()
+
+            // Click on the tile who has the `spec`.
+            assertThat(underTest.onTileClicked(spec)).isTrue()
+            assertThat(underTest.activeTileDetails).isNotNull()
+            assertThat(underTest.activeTileDetails?.getTitle()).isEqualTo("internet")
+
+            // Click on a tile who dose not have a valid spec.
+            assertThat(underTest.onTileClicked(null)).isFalse()
+            assertThat(underTest.activeTileDetails).isNull()
+
+            // Click again on the tile who has the `spec`.
+            assertThat(underTest.onTileClicked(spec)).isTrue()
+            assertThat(underTest.activeTileDetails).isNotNull()
+            assertThat(underTest.activeTileDetails?.getTitle()).isEqualTo("internet")
+
+            // Click on a tile who dose not have a detailed view.
+            assertThat(underTest.onTileClicked(specNoDetails)).isFalse()
+            assertThat(underTest.activeTileDetails).isNull()
+
+            underTest.closeDetailedView()
+            assertThat(underTest.activeTileDetails).isNull()
+
+            assertThat(underTest.onTileClicked(null)).isFalse()
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt
new file mode 100644
index 0000000..f2bfd72
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.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.qs.panels.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.fakeFalsingManager
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+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 EditModeButtonViewModelTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+
+    val underTest = kosmos.editModeButtonViewModelFactory.create()
+
+    @Test
+    fun falsingFalseTap_editModeDoesntStart() =
+        kosmos.runTest {
+            val isEditing by collectLastValue(editModeViewModel.isEditing)
+
+            fakeFalsingManager.setFalseTap(true)
+
+            underTest.onButtonClick()
+            runCurrent()
+
+            assertThat(isEditing).isFalse()
+        }
+
+    @Test
+    fun falsingNotFalseTap_editModeStarted() =
+        kosmos.runTest {
+            val isEditing by collectLastValue(editModeViewModel.isEditing)
+
+            fakeFalsingManager.setFalseTap(false)
+
+            underTest.onButtonClick()
+            runCurrent()
+
+            assertThat(isEditing).isTrue()
+        }
+}
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 8995f46..165ff7b 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
@@ -691,6 +691,32 @@
             verify(logger, never()).logTileUserChanged(TileSpec.create("a"), 0)
         }
 
+
+    @Test
+    fun getTileDetails() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(underTest.currentTiles)
+            val tileA = TileSpec.create("a")
+            val tileB = TileSpec.create("b")
+            val tileNoDetails = TileSpec.create("NoDetails")
+
+            val specs = listOf(tileA, tileB, tileNoDetails)
+
+            assertThat(tiles!!.isEmpty()).isTrue()
+
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+            assertThat(tiles!!.size).isEqualTo(3)
+
+            // The third tile doesn't have a details view.
+            assertThat(tiles!![2].spec).isEqualTo(tileNoDetails)
+            (tiles!![2].tile as FakeQSTile).hasDetailsViewModel = false
+
+            assertThat(tiles!![0].tile.detailsViewModel.getTitle()).isEqualTo("a")
+            assertThat(tiles!![1].tile.detailsViewModel.getTitle()).isEqualTo("b")
+            assertThat(tiles!![2].tile.detailsViewModel).isNull()
+        }
+
+
     private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
         this.state = state
         this.label = label
@@ -770,7 +796,7 @@
         private val USER_INFO_0 = UserInfo().apply { id = 0 }
         private val USER_INFO_1 = UserInfo().apply { id = 1 }
 
-        private val VALID_TILES = setOf("a", "b", "c", "d", "e")
+        private val VALID_TILES = setOf("a", "b", "c", "d", "e", "NoDetails")
         private val TEST_COMPONENT = ComponentName("pkg", "cls")
         private val CUSTOM_TILE_SPEC = TileSpec.Companion.create(TEST_COMPONENT)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
index ee2a1d5..411960f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs.tileimpl
 
-import android.animation.AnimatorTestRule
 import android.content.Context
 import android.service.quicksettings.Tile
 import android.view.ContextThemeWrapper
@@ -26,6 +25,7 @@
 import androidx.test.annotation.UiThreadTest
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.connectivity.WifiIcons
@@ -77,7 +77,7 @@
         // Set the second state to animate (it shouldn't, because `State.state` is the same) and
         // advance time to 2 animations length
         iconView.setIcon(secondState, /* allowAnimations= */ true)
-        animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH * 2)
+        animatorRule.advanceAnimationDuration(QSIconViewImpl.QS_ANIM_LENGTH * 2)
 
         assertThat(iconView.mLastIcon).isEqualTo(secondState.icon)
     }
@@ -126,7 +126,7 @@
         // Set the third state to animate and advance time by two times the animation length
         // to guarantee that all animations are done
         iconView.setIcon(thirdState, /* allowAnimations= */ true)
-        animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH * 2)
+        animatorRule.advanceAnimationDuration(QSIconViewImpl.QS_ANIM_LENGTH * 2)
 
         assertThat(iconView.mLastIcon).isEqualTo(thirdState.icon)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 9f12b18..31a627f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -20,6 +20,7 @@
 import static junit.framework.TestCase.assertEquals;
 
 import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
@@ -30,6 +31,7 @@
 import android.media.MediaRouter;
 import android.media.MediaRouter.RouteInfo;
 import android.media.projection.MediaProjectionInfo;
+import android.media.projection.StopReason;
 import android.os.Handler;
 import android.service.quicksettings.Tile;
 import android.testing.TestableLooper;
@@ -336,7 +338,8 @@
         mCastTile.handleClick(null /* view */);
         mTestableLooper.processAllMessages();
 
-        verify(mController, times(1)).stopCasting(same(device));
+        verify(mController, times(1))
+                .stopCasting(same(device), eq(StopReason.STOP_QS_TILE));
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index 940da99..33748b9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -27,7 +27,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.classifier.FalsingManagerFake
@@ -45,13 +44,14 @@
 import com.android.systemui.qs.QSHost
 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.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.SecureSettings
 import com.google.common.truth.Truth.assertThat
+import java.util.Optional
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -67,40 +67,27 @@
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import java.util.Optional
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class DeviceControlsTileTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var qsHost: QSHost
-    @Mock
-    private lateinit var metricsLogger: MetricsLogger
-    @Mock
-    private lateinit var statusBarStateController: StatusBarStateController
-    @Mock
-    private lateinit var activityStarter: ActivityStarter
-    @Mock
-    private lateinit var qsLogger: QSLogger
-    @Mock
-    private lateinit var controlsComponent: ControlsComponent
-    @Mock
-    private lateinit var controlsUiController: ControlsUiController
-    @Mock
-    private lateinit var controlsListingController: ControlsListingController
-    @Mock
-    private lateinit var controlsController: ControlsController
-    @Mock
-    private lateinit var serviceInfo: ControlsServiceInfo
-    @Mock
-    private lateinit var uiEventLogger: QsEventLogger
+    @Mock private lateinit var qsHost: QSHost
+    @Mock private lateinit var metricsLogger: MetricsLogger
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var qsLogger: QSLogger
+    @Mock private lateinit var controlsComponent: ControlsComponent
+    @Mock private lateinit var controlsUiController: ControlsUiController
+    @Mock private lateinit var controlsListingController: ControlsListingController
+    @Mock private lateinit var controlsController: ControlsController
+    @Mock private lateinit var serviceInfo: ControlsServiceInfo
+    @Mock private lateinit var uiEventLogger: QsEventLogger
     @Captor
     private lateinit var listingCallbackCaptor:
-            ArgumentCaptor<ControlsListingController.ControlsListingCallback>
-    @Captor
-    private lateinit var intentCaptor: ArgumentCaptor<Intent>
+        ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+    @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent>
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: DeviceControlsTile
@@ -120,8 +107,11 @@
         `when`(qsHost.context).thenReturn(spiedContext)
         `when`(controlsComponent.isEnabled()).thenReturn(true)
         `when`(controlsController.getPreferredSelection())
-                .thenReturn(SelectedItem.StructureItem(
-                        StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())))
+            .thenReturn(
+                SelectedItem.StructureItem(
+                    StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())
+                )
+            )
         secureSettings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 1)
 
         setupControlsComponent()
@@ -182,10 +172,11 @@
 
     @Test
     fun testObservingCallback() {
-        verify(controlsListingController).observe(
+        verify(controlsListingController)
+            .observe(
                 any(LifecycleOwner::class.java),
-                any(ControlsListingController.ControlsListingCallback::class.java)
-        )
+                any(ControlsListingController.ControlsListingCallback::class.java),
+            )
     }
 
     @Test
@@ -205,10 +196,8 @@
 
     @Test
     fun testStateUnavailableIfNoListings() {
-        verify(controlsListingController).observe(
-                any(LifecycleOwner::class.java),
-                capture(listingCallbackCaptor)
-        )
+        verify(controlsListingController)
+            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
 
         listingCallbackCaptor.value.onServicesUpdated(emptyList())
         testableLooper.processAllMessages()
@@ -218,10 +207,8 @@
 
     @Test
     fun testStateUnavailableIfNotEnabled() {
-        verify(controlsListingController).observe(
-            any(LifecycleOwner::class.java),
-            capture(listingCallbackCaptor)
-        )
+        verify(controlsListingController)
+            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
         `when`(controlsComponent.isEnabled()).thenReturn(false)
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
@@ -232,18 +219,19 @@
 
     @Test
     fun testStateActiveIfListingsHasControlsFavorited() {
-        verify(controlsListingController).observe(
-                any(LifecycleOwner::class.java),
-                capture(listingCallbackCaptor)
-        )
+        verify(controlsListingController)
+            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
         `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
-        `when`(controlsController.getPreferredSelection()).thenReturn(
-            SelectedItem.StructureItem(StructureInfo(
-                ComponentName("pkg", "cls"),
-                "structure",
-                listOf(ControlInfo("id", "title", "subtitle", 1))
-            ))
-        )
+        `when`(controlsController.getPreferredSelection())
+            .thenReturn(
+                SelectedItem.StructureItem(
+                    StructureInfo(
+                        ComponentName("pkg", "cls"),
+                        "structure",
+                        listOf(ControlInfo("id", "title", "subtitle", 1)),
+                    )
+                )
+            )
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
         testableLooper.processAllMessages()
@@ -253,14 +241,15 @@
 
     @Test
     fun testStateInactiveIfListingsHasNoControlsFavorited() {
-        verify(controlsListingController).observe(
-                any(LifecycleOwner::class.java),
-                capture(listingCallbackCaptor)
-        )
+        verify(controlsListingController)
+            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
         `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
         `when`(controlsController.getPreferredSelection())
-                .thenReturn(SelectedItem.StructureItem(
-                        StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())))
+            .thenReturn(
+                SelectedItem.StructureItem(
+                    StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())
+                )
+            )
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
         testableLooper.processAllMessages()
@@ -270,13 +259,11 @@
 
     @Test
     fun testStateActiveIfPreferredIsPanel() {
-        verify(controlsListingController).observe(
-                any(LifecycleOwner::class.java),
-                capture(listingCallbackCaptor)
-        )
+        verify(controlsListingController)
+            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
         `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
         `when`(controlsController.getPreferredSelection())
-                .thenReturn(SelectedItem.PanelItem("appName", ComponentName("pkg", "cls")))
+            .thenReturn(SelectedItem.PanelItem("appName", ComponentName("pkg", "cls")))
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
         testableLooper.processAllMessages()
@@ -286,10 +273,8 @@
 
     @Test
     fun testStateInactiveIfLocked() {
-        verify(controlsListingController).observe(
-            any(LifecycleOwner::class.java),
-            capture(listingCallbackCaptor)
-        )
+        verify(controlsListingController)
+            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
         `when`(controlsComponent.getVisibility())
             .thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
 
@@ -301,10 +286,8 @@
 
     @Test
     fun testMoveBetweenStates() {
-        verify(controlsListingController).observe(
-                any(LifecycleOwner::class.java),
-                capture(listingCallbackCaptor)
-        )
+        verify(controlsListingController)
+            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
         testableLooper.processAllMessages()
@@ -325,19 +308,20 @@
 
     @Test
     fun handleClick_available_shownOverLockscreenWhenLocked() {
-        verify(controlsListingController).observe(
-                any(LifecycleOwner::class.java),
-                capture(listingCallbackCaptor)
-        )
+        verify(controlsListingController)
+            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
         `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
         `when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
-        `when`(controlsController.getPreferredSelection()).thenReturn(
-            SelectedItem.StructureItem(StructureInfo(
-                    ComponentName("pkg", "cls"),
-                    "structure",
-                    listOf(ControlInfo("id", "title", "subtitle", 1))
-            ))
-        )
+        `when`(controlsController.getPreferredSelection())
+            .thenReturn(
+                SelectedItem.StructureItem(
+                    StructureInfo(
+                        ComponentName("pkg", "cls"),
+                        "structure",
+                        listOf(ControlInfo("id", "title", "subtitle", 1)),
+                    )
+                )
+            )
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
         testableLooper.processAllMessages()
@@ -345,30 +329,33 @@
         tile.click(null /* view */)
         testableLooper.processAllMessages()
 
-        verify(activityStarter).startActivity(
+        verify(activityStarter)
+            .startActivity(
                 intentCaptor.capture(),
                 eq(true) /* dismissShade */,
                 nullable(ActivityTransitionAnimator.Controller::class.java),
-                eq(true) /* showOverLockscreenWhenLocked */)
+                eq(true), /* showOverLockscreenWhenLocked */
+            )
         assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
     }
 
     @Test
     fun handleClick_availableAfterUnlock_notShownOverLockscreenWhenLocked() {
-        verify(controlsListingController).observe(
-            any(LifecycleOwner::class.java),
-            capture(listingCallbackCaptor)
-        )
+        verify(controlsListingController)
+            .observe(any(LifecycleOwner::class.java), capture(listingCallbackCaptor))
         `when`(controlsComponent.getVisibility())
             .thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
         `when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
-        `when`(controlsController.getPreferredSelection()).thenReturn(
-            SelectedItem.StructureItem(StructureInfo(
-                ComponentName("pkg", "cls"),
-                "structure",
-                listOf(ControlInfo("id", "title", "subtitle", 1))
-            ))
-        )
+        `when`(controlsController.getPreferredSelection())
+            .thenReturn(
+                SelectedItem.StructureItem(
+                    StructureInfo(
+                        ComponentName("pkg", "cls"),
+                        "structure",
+                        listOf(ControlInfo("id", "title", "subtitle", 1)),
+                    )
+                )
+            )
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
         testableLooper.processAllMessages()
@@ -376,26 +363,19 @@
         tile.click(null /* view */)
         testableLooper.processAllMessages()
 
-        verify(activityStarter).startActivity(
+        verify(activityStarter)
+            .startActivity(
                 intentCaptor.capture(),
                 anyBoolean() /* dismissShade */,
                 nullable(ActivityTransitionAnimator.Controller::class.java),
-                eq(false) /* showOverLockscreenWhenLocked */)
+                eq(false), /* showOverLockscreenWhenLocked */
+            )
         assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
     }
 
     @Test
     fun verifyTileEqualsResourceFromComponent() {
-        assertThat(tile.tileLabel)
-            .isEqualTo(
-                context.getText(
-                    controlsComponent.getTileTitleId()))
-    }
-
-    @Test
-    fun verifyTileImageEqualsResourceFromComponent() {
-        assertThat(tile.icon)
-            .isEqualTo(QSTileImpl.ResourceIcon.get(controlsComponent.getTileImageId()))
+        assertThat(tile.tileLabel).isEqualTo(context.getText(controlsComponent.getTileTitleId()))
     }
 
     private fun createTile(): DeviceControlsTile {
@@ -409,11 +389,12 @@
                 statusBarStateController,
                 activityStarter,
                 qsLogger,
-                controlsComponent
-        ).also {
-            it.initialize()
-            testableLooper.processAllMessages()
-        }
+                controlsComponent,
+            )
+            .also {
+                it.initialize()
+                testableLooper.processAllMessages()
+            }
     }
 }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
index 9f84e34..f33de4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
@@ -128,7 +128,7 @@
                 viewModel,
                 dialogManager,
                 wifiStateWorker,
-                accessPointController
+                accessPointController,
             )
 
         underTest.initialize()
@@ -156,10 +156,7 @@
         testScope.runTest {
             connectivityRepository.defaultConnections.value = DefaultConnectionModel()
             wifiRepository.wifiScanResults.value =
-                listOf(
-                    WifiScanEntry(ssid = "ssid 1"),
-                    WifiScanEntry(ssid = "ssid 2"),
-                )
+                listOf(WifiScanEntry(ssid = "ssid 1"), WifiScanEntry(ssid = "ssid 2"))
 
             runCurrent()
             looper.processAllMessages()
@@ -204,10 +201,7 @@
         testScope.runTest {
             airplaneModeRepository.setIsAirplaneMode(true)
             connectivityRepository.defaultConnections.value =
-                DefaultConnectionModel(
-                    wifi = Wifi(true),
-                    isValidated = true,
-                )
+                DefaultConnectionModel(wifi = Wifi(true), isValidated = true)
             wifiRepository.setIsWifiEnabled(true)
             wifiRepository.setWifiNetwork(ACTIVE_WIFI)
 
@@ -222,10 +216,7 @@
     fun wifiConnected() =
         testScope.runTest {
             connectivityRepository.defaultConnections.value =
-                DefaultConnectionModel(
-                    wifi = Wifi(true),
-                    isValidated = true,
-                )
+                DefaultConnectionModel(wifi = Wifi(true), isValidated = true)
 
             wifiRepository.setIsWifiEnabled(true)
             wifiRepository.setWifiNetwork(ACTIVE_WIFI)
@@ -242,6 +233,7 @@
         whenever(wifiStateWorker.isWifiEnabled).thenReturn(true)
 
         underTest.secondaryClick(null)
+        looper.processAllMessages()
 
         verify(wifiStateWorker, times(1)).isWifiEnabled = eq(false)
     }
@@ -251,6 +243,7 @@
         whenever(wifiStateWorker.isWifiEnabled).thenReturn(false)
 
         underTest.secondaryClick(null)
+        looper.processAllMessages()
 
         verify(wifiStateWorker, times(1)).isWifiEnabled = eq(true)
     }
@@ -258,10 +251,6 @@
     companion object {
         const val WIFI_SSID = "test ssid"
         val ACTIVE_WIFI =
-            WifiNetworkModel.Active.of(
-                isValidated = true,
-                level = 4,
-                ssid = WIFI_SSID,
-            )
+            WifiNetworkModel.Active.of(isValidated = true, level = 4, ssid = WIFI_SSID)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index 0cf9604..b5ec0a0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -180,6 +180,7 @@
         when(mWifiStateWorker.isWifiEnabled()).thenReturn(true);
 
         mTile.secondaryClick(null);
+        mTestableLooper.processAllMessages();
 
         verify(mWifiStateWorker, times(1)).setWifiEnabled(eq(false));
     }
@@ -189,6 +190,7 @@
         when(mWifiStateWorker.isWifiEnabled()).thenReturn(false);
 
         mTile.secondaryClick(null);
+        mTestableLooper.processAllMessages();
 
         verify(mWifiStateWorker, times(1)).setWifiEnabled(eq(true));
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 03c1f92..4068d9f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -46,6 +46,9 @@
 import android.graphics.drawable.Icon;
 import android.os.Handler;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.service.quickaccesswallet.Flags;
 import android.service.quickaccesswallet.GetWalletCardsError;
 import android.service.quickaccesswallet.GetWalletCardsResponse;
 import android.service.quickaccesswallet.QuickAccessWalletClient;
@@ -221,6 +224,7 @@
     }
 
     @Test
+    @DisableFlags({Flags.FLAG_LAUNCH_SELECTED_CARD_FROM_QS_TILE})
     public void testHandleClick_startQuickAccessUiIntent_noCard() {
         setUpWalletCard(/* hasCard= */ false);
 
@@ -234,6 +238,7 @@
     }
 
     @Test
+    @DisableFlags({Flags.FLAG_LAUNCH_SELECTED_CARD_FROM_QS_TILE})
     public void testHandleClick_startQuickAccessUiIntent_hasCard() {
         setUpWalletCard(/* hasCard= */ true);
 
@@ -247,6 +252,34 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_LAUNCH_SELECTED_CARD_FROM_QS_TILE})
+    public void testHandleClick_startCardIntent_noCard() {
+        setUpWalletCard(/* hasCard= */ false);
+
+        mTile.handleClick(/* view= */ null);
+        mTestableLooper.processAllMessages();
+
+        verify(mController).startQuickAccessUiIntent(
+                eq(mActivityStarter),
+                eq(null),
+                /* hasCard= */ eq(false));
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_LAUNCH_SELECTED_CARD_FROM_QS_TILE})
+    public void testHandleClick_startCardIntent_hasCard() {
+        setUpWalletCard(/* hasCard= */ true);
+
+        mTile.handleClick(null /* view */);
+        mTestableLooper.processAllMessages();
+
+        verify(mController).startWalletCardPendingIntent(
+                any(),
+                eq(mActivityStarter),
+                eq(null));
+    }
+
+    @Test
     public void testHandleUpdateState_updateLabelAndIcon() {
         QSTile.State state = new QSTile.State();
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 53708fd..6ebe830 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -23,11 +23,13 @@
 import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Dialog;
+import android.media.projection.StopReason;
 import android.os.Handler;
 import android.service.quicksettings.Tile;
 import android.testing.TestableLooper;
@@ -214,7 +216,7 @@
 
         mTile.handleClick(null /* view */);
 
-        verify(mController, times(1)).stopRecording();
+        verify(mController, times(1)).stopRecording(eq(StopReason.STOP_QS_TILE));
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
index 0b56d7b..778c73f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor
 
 import android.app.Dialog
+import android.media.projection.StopReason
 import android.os.UserHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -92,7 +93,7 @@
 
         underTest.handleInput(QSTileInputTestKtx.click(recordingModel))
 
-        verify(recordingController).stopRecording()
+        verify(recordingController).stopRecording(eq(StopReason.STOP_QS_TILE))
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index db2297c..3d014b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -35,9 +35,9 @@
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.init.NotificationsController
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
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 5d49c11..79bb0c4 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
@@ -2861,9 +2861,7 @@
         )
 
     private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean) =
-        FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
-            this.isPinned.value = isPinned
-        }
+        FakeHeadsUpRowRepository(key = key, elementKey = Any(), isPinned = isPinned)
 
     private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) {
         kosmos.fingerprintPropertyRepository.setProperties(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
index bb2e941..fc915ca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
@@ -104,7 +104,7 @@
             runCurrent()
 
             assertThat(userActions?.get(swipeDownFromTopWithTwoFingers()))
-                .isEqualTo(UserActionResult(Scenes.QuickSettings, isIrreversible = true))
+                .isEqualTo(UserActionResult(Scenes.QuickSettings))
         }
 
     @Test
@@ -116,7 +116,7 @@
             runCurrent()
 
             assertThat(userActions?.get(swipeDownFromTopWithTwoFingers()))
-                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index a6a1d4a..50fa9d2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -41,6 +41,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Intent;
+import android.media.projection.StopReason;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -199,16 +200,16 @@
     public void testOnSystemRequestedStop_recordingInProgress_endsRecording() throws IOException {
         doReturn(true).when(mController).isRecording();
 
-        mRecordingService.onStopped();
+        mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
 
-        verify(mScreenMediaRecorder).end();
+        verify(mScreenMediaRecorder).end(eq(StopReason.STOP_UNKNOWN));
     }
 
     @Test
     public void testOnSystemRequestedStop_recordingInProgress_updatesState() {
         doReturn(true).when(mController).isRecording();
 
-        mRecordingService.onStopped();
+        mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
 
         assertUpdateState(false);
     }
@@ -218,18 +219,18 @@
             throws IOException {
         doReturn(false).when(mController).isRecording();
 
-        mRecordingService.onStopped();
+        mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
 
-        verify(mScreenMediaRecorder, never()).end();
+        verify(mScreenMediaRecorder, never()).end(StopReason.STOP_UNKNOWN);
     }
 
     @Test
     public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_releasesRecording()
             throws IOException {
         doReturn(true).when(mController).isRecording();
-        doThrow(new RuntimeException()).when(mScreenMediaRecorder).end();
+        doThrow(new RuntimeException()).when(mScreenMediaRecorder).end(StopReason.STOP_UNKNOWN);
 
-        mRecordingService.onStopped();
+        mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
 
         verify(mScreenMediaRecorder).release();
     }
@@ -238,7 +239,7 @@
     public void testOnSystemRequestedStop_whenRecordingInProgress_showsNotifications() {
         doReturn(true).when(mController).isRecording();
 
-        mRecordingService.onStopped();
+        mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
 
         // Processing notification
         ArgumentCaptor<Notification> notifCaptor = ArgumentCaptor.forClass(Notification.class);
@@ -271,9 +272,9 @@
     public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_showsErrorNotification()
             throws IOException {
         doReturn(true).when(mController).isRecording();
-        doThrow(new RuntimeException()).when(mScreenMediaRecorder).end();
+        doThrow(new RuntimeException()).when(mScreenMediaRecorder).end(anyInt());
 
-        mRecordingService.onStopped();
+        mRecordingService.onStopped(StopReason.STOP_UNKNOWN);
 
         verify(mRecordingService).createErrorSavingNotification(any());
         ArgumentCaptor<Notification> notifCaptor = ArgumentCaptor.forClass(Notification.class);
@@ -289,9 +290,9 @@
     public void testOnSystemRequestedStop_recorderEndThrowsOOMError_releasesRecording()
             throws IOException {
         doReturn(true).when(mController).isRecording();
-        doThrow(new OutOfMemoryError()).when(mScreenMediaRecorder).end();
+        doThrow(new OutOfMemoryError()).when(mScreenMediaRecorder).end(anyInt());
 
-        assertThrows(Throwable.class, () -> mRecordingService.onStopped());
+        assertThrows(Throwable.class, () -> mRecordingService.onStopped(StopReason.STOP_UNKNOWN));
 
         verify(mScreenMediaRecorder).release();
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 534c12c..3a4c993 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -18,7 +18,10 @@
 
 import android.content.Intent
 import android.hardware.display.DisplayManager
+import android.hardware.display.VirtualDisplay
+import android.hardware.display.VirtualDisplayConfig
 import android.os.UserHandle
+import android.platform.test.annotations.RequiresFlagsEnabled
 import android.testing.TestableLooper
 import android.view.View
 import android.widget.Spinner
@@ -42,6 +45,7 @@
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import junit.framework.Assert.assertEquals
 import org.junit.After
 import org.junit.Before
@@ -224,6 +228,34 @@
             .notifyProjectionRequestCancelled(TEST_HOST_UID)
     }
 
+    @Test
+    @RequiresFlagsEnabled(
+        com.android.media.projection.flags.Flags
+            .FLAG_MEDIA_PROJECTION_CONNECTED_DISPLAY_NO_VIRTUAL_DEVICE
+    )
+    fun doNotShowVirtualDisplayInDialog() {
+        val displayManager = context.getSystemService(DisplayManager::class.java)!!
+        var virtualDisplay: VirtualDisplay? = null
+        try {
+            virtualDisplay =
+                displayManager.createVirtualDisplay(
+                    VirtualDisplayConfig.Builder("virtual display", 1, 1, 160).build()
+                )
+            showDialog()
+            val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
+            val adapter = spinner.adapter
+            val virtualDisplayAvailable =
+                (0 until adapter.count)
+                    .mapNotNull { adapter.getItem(it) as? String }
+                    .any { it.contains("virtual display", ignoreCase = true) }
+            assertWithMessage("A Virtual Display was shown in the list of display to record")
+                .that(virtualDisplayAvailable)
+                .isFalse()
+        } finally {
+            virtualDisplay?.release()
+        }
+    }
+
     private fun showDialog() {
         dialog.show()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt
index aceea90..ade5941 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepositoryTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.screenrecord.data.repository
 
+import android.media.projection.StopReason
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -31,6 +32,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -126,8 +128,8 @@
     @Test
     fun stopRecording_invokesController() =
         testScope.runTest {
-            underTest.stopRecording()
+            underTest.stopRecording(StopReason.STOP_PRIVACY_CHIP)
 
-            verify(recordingController).stopRecording()
+            verify(recordingController).stopRecording(eq(StopReason.STOP_PRIVACY_CHIP))
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index ed31f36..0d8d57e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -150,7 +150,7 @@
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.ConversationNotificationManager;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
 import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository;
@@ -178,12 +178,12 @@
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.phone.ShadeTouchableRegionManager;
 import com.android.systemui.statusbar.phone.TapAgainViewController;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
@@ -245,7 +245,7 @@
     @Mock protected NotificationPanelView mView;
     @Mock protected LayoutInflater mLayoutInflater;
     @Mock protected DynamicPrivacyController mDynamicPrivacyController;
-    @Mock protected StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+    @Mock protected ShadeTouchableRegionManager mShadeTouchableRegionManager;
     @Mock protected KeyguardStateController mKeyguardStateController;
     @Mock protected DozeLog mDozeLog;
     private final ShadeLogger mShadeLog = new ShadeLogger(logcatLogBuffer());
@@ -703,7 +703,7 @@
                 mMetricsLogger,
                 mShadeLog,
                 mConfigurationController,
-                () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
+                () -> flingAnimationUtilsBuilder, mShadeTouchableRegionManager,
                 mConversationNotificationManager, mMediaHierarchyManager,
                 mStatusBarKeyguardViewManager,
                 mGutsManager,
@@ -819,7 +819,7 @@
                 mLockscreenShadeTransitionController,
                 mNotificationShadeDepthController,
                 mShadeHeaderController,
-                mStatusBarTouchableRegionManager,
+                mShadeTouchableRegionManager,
                 () -> mStatusBarLongPressGestureDetector,
                 mKeyguardStateController,
                 mKeyguardBypassController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 614d51e..764068e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -41,6 +41,7 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.platform.test.flag.junit.FlagsParameterization;
+import android.provider.Settings;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
 import android.view.WindowManager;
@@ -70,6 +71,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.settings.FakeSettings;
 
 import com.google.common.util.concurrent.MoreExecutors;
 
@@ -111,6 +113,7 @@
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
 
+    private FakeSettings mSecureSettings;
     private final Executor mMainExecutor = MoreExecutors.directExecutor();
     private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
     private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
@@ -131,6 +134,10 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+
+        mSecureSettings = new FakeSettings();
+        mSecureSettings.putInt(Settings.Secure.DISABLE_SECURE_WINDOWS, 0);
+
         // Preferred refresh rate is equal to the first displayMode's refresh rate
         mPreferredRefreshRate = mContext.getDisplay().getSystemSupportedModes()[0].getRefreshRate();
         overrideResource(
@@ -164,12 +171,9 @@
                 () -> mSelectedUserInteractor,
                 mUserTracker,
                 mKosmos.getNotificationShadeWindowModel(),
-                mKosmos::getCommunalInteractor) {
-                    @Override
-                    protected boolean isDebuggable() {
-                        return false;
-                    }
-            };
+                mSecureSettings,
+                mKosmos::getCommunalInteractor,
+                mKosmos.getShadeLayoutParams());
         mNotificationShadeWindowController.setScrimsVisibilityListener((visibility) -> {});
         mNotificationShadeWindowController.fetchWindowRootView();
 
@@ -351,6 +355,19 @@
     }
 
     @Test
+    public void setKeyguardShowingWithSecureWindowsDisabled_disablesSecureFlag() {
+        mSecureSettings.putInt(Settings.Secure.DISABLE_SECURE_WINDOWS, 1);
+        mNotificationShadeWindowController.setBouncerShowing(true);
+
+        verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+        assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) == 0).isTrue();
+        assertThat(
+                (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_PRIVACY)
+                        != 0)
+                .isTrue();
+    }
+
+    @Test
     public void setKeyguardNotShowing_disablesSecureFlag() {
         mNotificationShadeWindowController.setBouncerShowing(false);
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 61d4c99..b58c13c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -71,8 +71,8 @@
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ShadeTouchableRegionManager;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
@@ -125,7 +125,7 @@
     @Mock protected LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     @Mock protected NotificationShadeDepthController mNotificationShadeDepthController;
     @Mock protected ShadeHeaderController mShadeHeaderController;
-    @Mock protected StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+    @Mock protected ShadeTouchableRegionManager mShadeTouchableRegionManager;
     @Mock protected StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
     @Mock protected DozeParameters mDozeParameters;
     @Mock protected KeyguardStateController mKeyguardStateController;
@@ -250,7 +250,7 @@
                 mLockscreenShadeTransitionController,
                 mNotificationShadeDepthController,
                 mShadeHeaderController,
-                mStatusBarTouchableRegionManager,
+                mShadeTouchableRegionManager,
                 () -> mStatusBarLongPressGestureDetector,
                 mKeyguardStateController,
                 mKeyguardBypassController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 0f476d0..c6ce581 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -36,10 +36,10 @@
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
new file mode 100644
index 0000000..0966759
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.shade.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.display.ShadeDisplayPolicy
+import com.android.systemui.shade.display.SpecificDisplayIdPolicy
+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.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeDisplaysRepositoryTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val defaultPolicy = SpecificDisplayIdPolicy(0)
+
+    private val shadeDisplaysRepository =
+        ShadeDisplaysRepositoryImpl(defaultPolicy, testScope.backgroundScope)
+
+    @Test
+    fun policy_changing_propagatedFromTheLatestPolicy() =
+        testScope.runTest {
+            val displayIds by collectValues(shadeDisplaysRepository.displayId)
+            val policy1 = MutablePolicy()
+            val policy2 = MutablePolicy()
+
+            assertThat(displayIds).containsExactly(0)
+
+            shadeDisplaysRepository.policy.value = policy1
+
+            policy1.sendDisplayId(1)
+
+            assertThat(displayIds).containsExactly(0, 1)
+
+            policy1.sendDisplayId(2)
+
+            assertThat(displayIds).containsExactly(0, 1, 2)
+
+            shadeDisplaysRepository.policy.value = policy2
+
+            assertThat(displayIds).containsExactly(0, 1, 2, 0)
+
+            policy1.sendDisplayId(4)
+
+            // Changes to the first policy don't affect the output now
+            assertThat(displayIds).containsExactly(0, 1, 2, 0)
+
+            policy2.sendDisplayId(5)
+
+            assertThat(displayIds).containsExactly(0, 1, 2, 0, 5)
+        }
+
+    private class MutablePolicy : ShadeDisplayPolicy {
+        fun sendDisplayId(id: Int) {
+            _displayId.value = id
+        }
+
+        private val _displayId = MutableStateFlow(0)
+        override val name: String
+            get() = "mutable_policy"
+
+        override val displayId: StateFlow<Int>
+            get() = _displayId
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
index af01547..d584dc9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
@@ -23,12 +23,17 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.shade.ShadePrimaryDisplayCommand
+import com.android.systemui.shade.display.ShadeDisplayPolicy
 import com.android.systemui.statusbar.commandline.commandRegistry
 import com.android.systemui.testKosmos
+import com.google.common.truth.StringSubject
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -37,15 +42,26 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ShadePrimaryDisplayCommandTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val testScope = kosmos.testScope
     private val commandRegistry = kosmos.commandRegistry
     private val displayRepository = kosmos.displayRepository
-    private val shadeDisplaysRepository = ShadeDisplaysRepositoryImpl()
+    private val defaultPolicy = kosmos.defaultShadeDisplayPolicy
+    private val policy1 = makePolicy("policy_1")
+    private val shadeDisplaysRepository = kosmos.shadeDisplaysRepository
     private val pw = PrintWriter(StringWriter())
 
+    private val policies =
+        setOf(defaultPolicy, policy1, makePolicy("policy_2"), makePolicy("policy_3"))
+
     private val underTest =
-        ShadePrimaryDisplayCommand(commandRegistry, displayRepository, shadeDisplaysRepository)
+        ShadePrimaryDisplayCommand(
+            commandRegistry,
+            displayRepository,
+            shadeDisplaysRepository,
+            policies,
+            defaultPolicy,
+        )
 
     @Before
     fun setUp() {
@@ -96,4 +112,41 @@
 
             assertThat(displayId).isEqualTo(newDisplayId)
         }
+
+    @Test
+    fun policies_listsAllPolicies() =
+        testScope.runTest {
+            val stringWriter = StringWriter()
+            commandRegistry.onShellCommand(
+                PrintWriter(stringWriter),
+                arrayOf("shade_display_override", "policies"),
+            )
+            val result = stringWriter.toString()
+
+            assertThat(result).containsAllIn(policies.map { it.name })
+        }
+
+    @Test
+    fun policies_setsSpecificPolicy() =
+        testScope.runTest {
+            val policy by collectLastValue(shadeDisplaysRepository.policy)
+
+            commandRegistry.onShellCommand(pw, arrayOf("shade_display_override", policy1.name))
+
+            assertThat(policy!!.name).isEqualTo(policy1.name)
+        }
+
+    private fun makePolicy(policyName: String): ShadeDisplayPolicy {
+        return object : ShadeDisplayPolicy {
+            override val name: String
+                get() = policyName
+
+            override val displayId: StateFlow<Int>
+                get() = MutableStateFlow(0)
+        }
+    }
+}
+
+private fun StringSubject.containsAllIn(strings: List<String>) {
+    strings.forEach { contains(it) }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicyTest.kt
new file mode 100644
index 0000000..4d4efd1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicyTest.kt
@@ -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.systemui.shade.display
+
+import android.view.Display
+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.display.data.repository.display
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AnyExternalShadeDisplayPolicyTest : SysuiTestCase() {
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val testScope = kosmos.testScope
+    private val displayRepository = kosmos.displayRepository
+    val underTest = AnyExternalShadeDisplayPolicy(displayRepository, testScope.backgroundScope)
+
+    @Test
+    fun displayId_ignoresUnwantedTypes() =
+        testScope.runTest {
+            val displayId by collectLastValue(underTest.displayId)
+
+            displayRepository.addDisplays(
+                display(id = 0, type = Display.TYPE_INTERNAL),
+                display(id = 1, type = Display.TYPE_UNKNOWN),
+                display(id = 2, type = Display.TYPE_VIRTUAL),
+                display(id = 3, type = Display.TYPE_EXTERNAL),
+            )
+
+            assertThat(displayId).isEqualTo(3)
+        }
+
+    @Test
+    fun displayId_onceRemoved_goesToNextDisplay() =
+        testScope.runTest {
+            val displayId by collectLastValue(underTest.displayId)
+
+            displayRepository.addDisplays(
+                display(id = 0, type = Display.TYPE_INTERNAL),
+                display(id = 2, type = Display.TYPE_EXTERNAL),
+                display(id = 3, type = Display.TYPE_EXTERNAL),
+            )
+
+            assertThat(displayId).isEqualTo(2)
+
+            displayRepository.removeDisplay(2)
+
+            assertThat(displayId).isEqualTo(3)
+        }
+
+    @Test
+    fun displayId_onlyDefaultDisplay_defaultDisplay() =
+        testScope.runTest {
+            val displayId by collectLastValue(underTest.displayId)
+
+            displayRepository.addDisplays(display(id = 0, type = Display.TYPE_INTERNAL))
+
+            assertThat(displayId).isEqualTo(0)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
new file mode 100644
index 0000000..ef1ae09
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.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.shade.display
+
+import android.view.Display
+import android.view.Display.TYPE_EXTERNAL
+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.coroutines.collectValues
+import com.android.systemui.display.data.repository.display
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val testScope = kosmos.testScope
+    private val displayRepository = kosmos.displayRepository
+    val underTest = StatusBarTouchShadeDisplayPolicy(displayRepository, testScope.backgroundScope)
+
+    @Test
+    fun displayId_defaultToDefaultDisplay() {
+        assertThat(underTest.displayId.value).isEqualTo(Display.DEFAULT_DISPLAY)
+    }
+
+    @Test
+    fun onStatusBarTouched_called_updatesDisplayId() =
+        testScope.runTest {
+            val displayId by collectLastValue(underTest.displayId)
+
+            displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
+            underTest.onStatusBarTouched(2)
+
+            assertThat(displayId).isEqualTo(2)
+        }
+
+    @Test
+    fun onStatusBarTouched_notExistentDisplay_displayIdNotUpdated() =
+        testScope.runTest {
+            val displayIds by collectValues(underTest.displayId)
+            assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY))
+
+            underTest.onStatusBarTouched(2)
+
+            // Never set, as 2 was not a display according to the repository.
+            assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY))
+        }
+
+    @Test
+    fun onStatusBarTouched_afterDisplayRemoved_goesBackToDefaultDisplay() =
+        testScope.runTest {
+            val displayId by collectLastValue(underTest.displayId)
+
+            displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
+            underTest.onStatusBarTouched(2)
+
+            assertThat(displayId).isEqualTo(2)
+
+            displayRepository.removeDisplay(2)
+
+            assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
index 08a22a8..a8d5c31 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
@@ -16,29 +16,25 @@
 
 package com.android.systemui.shade.domain.interactor
 
-import android.content.Context
-import android.content.MutableContextWrapper
+import android.content.mockedContext
 import android.content.res.Configuration
-import android.content.res.Resources
+import android.content.res.mockResources
 import android.view.Display
-import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE
+import android.view.mockWindowManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.display.data.repository.FakeDisplayWindowPropertiesRepository
-import com.android.systemui.display.shared.model.DisplayWindowProperties
-import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.data.repository.FakeShadeDisplayRepository
-import com.android.systemui.statusbar.phone.ConfigurationForwarder
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.scene.ui.view.mockShadeRootView
+import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
+import com.android.systemui.testKosmos
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.advanceUntilIdle
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.verify
+import org.mockito.Mockito.inOrder
 import org.mockito.kotlin.any
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
@@ -49,31 +45,18 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class ShadeDisplaysInteractorTest : SysuiTestCase() {
+    val kosmos = testKosmos().useUnconfinedTestDispatcher()
 
-    private val shadeRootview = mock<WindowRootView>()
-    private val positionRepository = FakeShadeDisplayRepository()
-    private val defaultContext = mock<Context>()
-    private val secondaryContext = mock<Context>()
-    private val contextStore = FakeDisplayWindowPropertiesRepository()
-    private val testScope = TestScope(UnconfinedTestDispatcher())
-    private val configurationForwarder = mock<ConfigurationForwarder>()
-    private val defaultWm = mock<WindowManager>()
-    private val secondaryWm = mock<WindowManager>()
-    private val resources = mock<Resources>()
+    private val shadeRootview = kosmos.mockShadeRootView
+    private val positionRepository = kosmos.fakeShadeDisplaysRepository
+    private val shadeContext = kosmos.mockedContext
+    private val testScope = kosmos.testScope
+    private val shadeWm = kosmos.mockWindowManager
+    private val resources = kosmos.mockResources
     private val configuration = mock<Configuration>()
     private val display = mock<Display>()
 
-    private val interactor =
-        ShadeDisplaysInteractor(
-            shadeRootview,
-            positionRepository,
-            MutableContextWrapper(defaultContext),
-            resources,
-            contextStore,
-            testScope.backgroundScope,
-            configurationForwarder,
-            testScope.backgroundScope.coroutineContext,
-        )
+    private val underTest = kosmos.shadeDisplaysInteractor
 
     @Before
     fun setup() {
@@ -82,74 +65,46 @@
 
         whenever(resources.configuration).thenReturn(configuration)
 
-        whenever(defaultContext.displayId).thenReturn(0)
-        whenever(defaultContext.getSystemService(any())).thenReturn(defaultWm)
-        whenever(defaultContext.resources).thenReturn(resources)
-        contextStore.insert(
-            DisplayWindowProperties(
-                displayId = 0,
-                windowType = TYPE_NOTIFICATION_SHADE,
-                context = defaultContext,
-                windowManager = defaultWm,
-                layoutInflater = mock(),
-            )
-        )
-
-        whenever(secondaryContext.displayId).thenReturn(1)
-        whenever(secondaryContext.getSystemService(any())).thenReturn(secondaryWm)
-        whenever(secondaryContext.resources).thenReturn(resources)
-        contextStore.insert(
-            DisplayWindowProperties(
-                displayId = 1,
-                windowType = TYPE_NOTIFICATION_SHADE,
-                context = secondaryContext,
-                windowManager = secondaryWm,
-                layoutInflater = mock(),
-            )
-        )
+        whenever(shadeContext.displayId).thenReturn(0)
+        whenever(shadeContext.getSystemService(any())).thenReturn(shadeWm)
+        whenever(shadeContext.resources).thenReturn(resources)
     }
 
     @Test
     fun start_shadeInCorrectPosition_notAddedOrRemoved() {
         whenever(display.displayId).thenReturn(0)
         positionRepository.setDisplayId(0)
-        interactor.start()
-        testScope.advanceUntilIdle()
 
-        verifyNoMoreInteractions(defaultWm)
-        verifyNoMoreInteractions(secondaryWm)
+        underTest.start()
+
+        verifyNoMoreInteractions(shadeWm)
     }
 
     @Test
     fun start_shadeInWrongPosition_changes() {
         whenever(display.displayId).thenReturn(0)
         positionRepository.setDisplayId(1)
-        interactor.start()
 
-        verify(defaultWm).removeView(eq(shadeRootview))
-        verify(secondaryWm).addView(eq(shadeRootview), any())
+        underTest.start()
+
+        inOrder(shadeWm).apply {
+            verify(shadeWm).removeView(eq(shadeRootview))
+            verify(shadeWm).addView(eq(shadeRootview), any())
+        }
     }
 
     @Test
     fun start_shadePositionChanges_removedThenAdded() {
         whenever(display.displayId).thenReturn(0)
         positionRepository.setDisplayId(0)
-        interactor.start()
+        underTest.start()
 
         positionRepository.setDisplayId(1)
+        testScope.advanceUntilIdle()
 
-        verify(defaultWm).removeView(eq(shadeRootview))
-        verify(secondaryWm).addView(eq(shadeRootview), any())
-    }
-
-    @Test
-    fun start_shadePositionChanges_newConfigPropagated() {
-        whenever(display.displayId).thenReturn(0)
-        positionRepository.setDisplayId(0)
-        interactor.start()
-
-        positionRepository.setDisplayId(1)
-
-        verify(configurationForwarder).onConfigurationChanged(eq(configuration))
+        inOrder(shadeWm).apply {
+            verify(shadeWm).removeView(eq(shadeRootview))
+            verify(shadeWm).addView(eq(shadeRootview), any())
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt
index 558606f..a9d5790 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade.ui.viewmodel
 
+import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
 import android.platform.test.annotations.DisableFlags
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -42,6 +43,7 @@
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
 import com.google.common.truth.Truth.assertThat
@@ -49,6 +51,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -158,10 +161,7 @@
                         underTest.unfoldTranslationX(isOnStartSide = true),
                         underTest.unfoldTranslationX(isOnStartSide = false),
                     ) { start, end ->
-                        Translations(
-                            start = start,
-                            end = end,
-                        )
+                        Translations(start = start, end = end)
                     }
                 )
 
@@ -186,6 +186,20 @@
             assertThat(translations?.end).isEqualTo(-0f)
         }
 
+    @Test
+    fun disable2QuickSettings_isQsEnabledIsFalse() =
+        testScope.runTest {
+            val isQsEnabled by collectLastValue(underTest.isQsEnabled)
+            assertThat(isQsEnabled).isTrue()
+
+            kosmos.fakeDisableFlagsRepository.disableFlags.update {
+                it.copy(disable2 = DISABLE2_QUICK_SETTINGS)
+            }
+            runCurrent()
+
+            assertThat(isQsEnabled).isFalse()
+        }
+
     private fun prepareConfiguration(): Int {
         val configuration = context.resources.configuration
         configuration.setLayoutDirection(Locale.US)
@@ -193,7 +207,7 @@
         val maxTranslation = 10
         kosmos.fakeConfigurationRepository.setDimensionPixelSize(
             R.dimen.notification_side_paddings,
-            maxTranslation
+            maxTranslation,
         )
         return maxTranslation
     }
@@ -224,8 +238,5 @@
         runCurrent()
     }
 
-    private data class Translations(
-        val start: Float,
-        val end: Float,
-    )
+    private data class Translations(val start: Float, val end: Float)
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
new file mode 100644
index 0000000..32a9f54
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -0,0 +1,1667 @@
+/*
+ * 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.statusbar;
+
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
+import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ADAPTIVE_AUTH;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
+import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_OFF;
+import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_TURNING_ON;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+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.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.graphics.Color;
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricSourceType;
+import android.os.BatteryManager;
+import android.os.RemoteException;
+import android.testing.TestableLooper;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.TrustGrantFlags;
+import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.keyguard.KeyguardIndication;
+import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
+import com.android.systemui.res.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.text.NumberFormat;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class KeyguardIndicationControllerTest extends KeyguardIndicationControllerBaseTest {
+    @Test
+    public void afterFaceLockout_skipShowingFaceNotRecognized() {
+        createController();
+        onFaceLockoutError("lockout");
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "lockout");
+        clearInvocations(mRotateTextViewController);
+
+        // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED (face fail)
+        mKeyguardUpdateMonitorCallback.onBiometricHelp(
+                BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
+                "Face not recognized",
+                BiometricSourceType.FACE);
+        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); // no updated message
+    }
+
+    @Test
+    public void createController_setIndicationAreaAgain_destroysPreviousRotateTextViewController() {
+        // GIVEN a controller with a mocked rotate text view controlller
+        final KeyguardIndicationRotateTextViewController mockedRotateTextViewController =
+                mock(KeyguardIndicationRotateTextViewController.class);
+        createController();
+        mController.mRotateTextViewController = mockedRotateTextViewController;
+
+        // WHEN a new indication area is set
+        mController.setIndicationArea(mIndicationArea);
+
+        // THEN the previous rotateTextViewController is destroyed
+        verify(mockedRotateTextViewController).destroy();
+    }
+
+    @Test
+    public void createController_addsAlignmentListener() {
+        createController();
+
+        verify(mDockManager).addAlignmentStateListener(
+                any(DockManager.AlignmentStateListener.class));
+    }
+
+    @Test
+    public void onAlignmentStateChanged_showsSlowChargingIndication() {
+        createController();
+        verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+        mController.setVisible(true);
+
+        mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_POOR);
+        mTestableLooper.processAllMessages();
+
+        verifyIndicationMessage(INDICATION_TYPE_ALIGNMENT,
+                mContext.getResources().getString(R.string.dock_alignment_slow_charging));
+        assertThat(mKeyguardIndicationCaptor.getValue().getTextColor().getDefaultColor())
+                .isEqualTo(mContext.getColor(R.color.misalignment_text_color));
+    }
+
+    @Test
+    public void onAlignmentStateChanged_showsNotChargingIndication() {
+        createController();
+        verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+        mController.setVisible(true);
+
+        mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
+        mTestableLooper.processAllMessages();
+
+        verifyIndicationMessage(INDICATION_TYPE_ALIGNMENT,
+                mContext.getResources().getString(R.string.dock_alignment_not_charging));
+        assertThat(mKeyguardIndicationCaptor.getValue().getTextColor().getDefaultColor())
+                .isEqualTo(mContext.getColor(R.color.misalignment_text_color));
+    }
+
+    @FlakyTest(bugId = 279944472)
+    @Test
+    public void onAlignmentStateChanged_whileDozing_showsSlowChargingIndication() {
+        createController();
+        verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+        mController.setVisible(true);
+        mStatusBarStateListener.onDozingChanged(true);
+
+        mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_POOR);
+        mTestableLooper.processAllMessages();
+
+        assertThat(mTextView.getText()).isEqualTo(
+                mContext.getResources().getString(R.string.dock_alignment_slow_charging));
+        assertThat(mTextView.getCurrentTextColor()).isEqualTo(
+                mContext.getColor(R.color.misalignment_text_color));
+    }
+
+    @Test
+    public void onAlignmentStateChanged_whileDozing_showsNotChargingIndication() {
+        createController();
+        verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+        mController.setVisible(true);
+        mStatusBarStateListener.onDozingChanged(true);
+
+        mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
+        mTestableLooper.processAllMessages();
+
+        assertThat(mTextView.getText()).isEqualTo(
+                mContext.getResources().getString(R.string.dock_alignment_not_charging));
+        assertThat(mTextView.getCurrentTextColor()).isEqualTo(
+                mContext.getColor(R.color.misalignment_text_color));
+    }
+
+    @Test
+    public void disclosure_unmanaged() {
+        createController();
+        mController.setVisible(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
+        when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(false);
+        reset(mRotateTextViewController);
+
+        sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
+
+        verifyHideIndication(INDICATION_TYPE_DISCLOSURE);
+    }
+
+    @Test
+    public void disclosure_deviceOwner_noOrganizationName() {
+        createController();
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
+        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
+        sendUpdateDisclosureBroadcast();
+        mController.setVisible(true);
+        mExecutor.runAllReady();
+
+        verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric);
+    }
+
+    @Test
+    public void disclosure_orgOwnedDeviceWithManagedProfile_noOrganizationName() {
+        createController();
+        mController.setVisible(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList(
+                new UserInfo(10, /* name */ null, /* flags */ FLAG_MANAGED_PROFILE)));
+        when(mDevicePolicyManager.getOrganizationNameForUser(eq(10))).thenReturn(null);
+        sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
+
+        verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric);
+    }
+
+    @Test
+    public void disclosure_deviceOwner_withOrganizationName() {
+        createController();
+        mController.setVisible(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
+        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
+        sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
+
+        verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureWithOrganization);
+    }
+
+    @Test
+    public void disclosure_orgOwnedDeviceWithManagedProfile_withOrganizationName() {
+        createController();
+        mController.setVisible(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList(
+                new UserInfo(10, /* name */ null, FLAG_MANAGED_PROFILE)));
+        when(mDevicePolicyManager.getOrganizationNameForUser(eq(10))).thenReturn(ORGANIZATION_NAME);
+        sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
+
+        verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureWithOrganization);
+    }
+
+    @Test
+    public void disclosure_updateOnTheFly() {
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
+        createController();
+        mController.setVisible(true);
+
+        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
+        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
+        sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
+
+        verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric);
+        reset(mRotateTextViewController);
+
+        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
+        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
+        sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
+
+        verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureWithOrganization);
+        reset(mRotateTextViewController);
+
+        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
+        sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
+
+        verifyHideIndication(INDICATION_TYPE_DISCLOSURE);
+    }
+
+    @Test
+    public void disclosure_deviceOwner_financedDeviceWithOrganizationName() {
+        createController();
+        mController.setVisible(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
+        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
+        when(mDevicePolicyManager.isFinancedDevice()).thenReturn(true);
+        // TODO(b/259908270): remove
+        when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
+                .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
+        sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
+        mController.setVisible(true);
+
+        verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mFinancedDisclosureWithOrganization);
+    }
+
+    @Test
+    public void transientIndication_holdsWakeLock_whenDozing() {
+        // GIVEN animations are enabled and text is visible
+        mTextView.setAnimationsEnabled(true);
+        createController();
+        mController.setVisible(true);
+
+        // WHEN transient text is shown
+        mStatusBarStateListener.onDozingChanged(true);
+        mController.showTransientIndication(TEST_STRING_RES);
+
+        // THEN wake lock is held while the animation is running
+        assertTrue("WakeLock expected: HELD, was: RELEASED", mWakeLock.isHeld());
+    }
+
+    @Test
+    public void transientIndication_releasesWakeLock_whenDozing() {
+        // GIVEN animations aren't enabled
+        mTextView.setAnimationsEnabled(false);
+        createController();
+        mController.setVisible(true);
+
+        // WHEN we show the transient indication
+        mStatusBarStateListener.onDozingChanged(true);
+        mController.showTransientIndication(TEST_STRING_RES);
+
+        // THEN wake lock is RELEASED, not held
+        assertFalse("WakeLock expected: RELEASED, was: HELD", mWakeLock.isHeld());
+    }
+
+    @Test
+    public void transientIndication_visibleWhenDozing() {
+        createController();
+        mController.setVisible(true);
+
+        mStatusBarStateListener.onDozingChanged(true);
+        mController.showTransientIndication(TEST_STRING_RES);
+
+        assertThat(mTextView.getText()).isEqualTo(
+                mContext.getResources().getString(TEST_STRING_RES));
+        assertThat(mTextView.getCurrentTextColor()).isEqualTo(Color.WHITE);
+        assertThat(mTextView.getAlpha()).isEqualTo(1f);
+    }
+
+    @Test
+    public void transientIndication_visibleWhenDozing_unlessSwipeUp_fromHelp() {
+        createController();
+        String message = "A message";
+
+        mController.setVisible(true);
+        mController.getKeyguardCallback().onBiometricHelp(
+                BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message,
+                BiometricSourceType.FACE);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message);
+        reset(mRotateTextViewController);
+        mStatusBarStateListener.onDozingChanged(true);
+
+        assertThat(mTextView.getText()).isNotEqualTo(message);
+    }
+
+    @Test
+    public void transientIndication_visibleWhenWokenUp() {
+        createController();
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+        final String message = "helpMsg";
+
+        // GIVEN screen is off
+        when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_OFF);
+
+        // WHEN fingeprint help message received
+        mController.setVisible(true);
+        mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+                message, BiometricSourceType.FINGERPRINT);
+
+        // THEN message isn't shown right away
+        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+
+        // WHEN the screen turns on
+        mScreenObserver.onScreenTurnedOn();
+        mTestableLooper.processAllMessages();
+
+        // THEN the message is shown
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message);
+    }
+
+    @Test
+    public void onBiometricHelp_coEx_faceFailure() {
+        createController();
+
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
+
+        String message = "A message";
+        mController.setVisible(true);
+
+        // WHEN there's a face not recognized message
+        mController.getKeyguardCallback().onBiometricHelp(
+                BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
+                message,
+                BiometricSourceType.FACE);
+
+        // THEN show sequential messages such as: 'face not recognized' and
+        // 'try fingerprint instead'
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                mContext.getString(R.string.keyguard_face_failed));
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_suggest_fingerprint));
+    }
+
+    @Test
+    public void onBiometricHelp_coEx_faceUnavailable() {
+        createController();
+
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
+
+        String message = "A message";
+        mController.setVisible(true);
+
+        // WHEN there's a face unavailable message
+        mController.getKeyguardCallback().onBiometricHelp(
+                BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
+                message,
+                BiometricSourceType.FACE);
+
+        // THEN show sequential messages such as: 'face unlock unavailable' and
+        // 'try fingerprint instead'
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                message);
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_suggest_fingerprint));
+    }
+
+
+    @Test
+    public void onBiometricHelp_coEx_faceUnavailable_fpNotAllowed() {
+        createController();
+
+        // GIVEN unlocking with fingerprint is possible but not allowed
+        setupFingerprintUnlockPossible(true);
+        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed())
+                .thenReturn(false);
+
+        String message = "A message";
+        mController.setVisible(true);
+
+        // WHEN there's a face unavailable message
+        mController.getKeyguardCallback().onBiometricHelp(
+                BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
+                message,
+                BiometricSourceType.FACE);
+
+        // THEN show sequential messages such as: 'face unlock unavailable' and
+        // 'try fingerprint instead'
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                message);
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() {
+        createController();
+
+        // GIVEN face has already unlocked the device
+        when(mKeyguardUpdateMonitor.isCurrentUserUnlockedWithFace()).thenReturn(true);
+
+        String message = "A message";
+        mController.setVisible(true);
+
+        // WHEN there's a fingerprint not recognized message
+        mController.getKeyguardCallback().onBiometricHelp(
+                BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+                message,
+                BiometricSourceType.FINGERPRINT);
+
+        // THEN show sequential messages such as: 'Unlocked by face' and
+        // 'Swipe up to open'
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                mContext.getString(R.string.keyguard_face_successful_unlock));
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onBiometricHelp_coEx_fpFailure_trustAgentAlreadyUnlocked() {
+        createController();
+
+        // GIVEN trust agent has already unlocked the device
+        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+
+        String message = "A message";
+        mController.setVisible(true);
+
+        // WHEN there's a fingerprint not recognized message
+        mController.getKeyguardCallback().onBiometricHelp(
+                BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+                message,
+                BiometricSourceType.FINGERPRINT);
+
+        // THEN show sequential messages such as: 'Kept unlocked by TrustAgent' and
+        // 'Swipe up to open'
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                mContext.getString(R.string.keyguard_indication_trust_unlocked));
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onBiometricHelp_coEx_fpFailure_trustAgentUnlocked_emptyTrustGrantedMessage() {
+        createController();
+
+        // GIVEN trust agent has already unlocked the device & trust granted message is empty
+        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+        mController.showTrustGrantedMessage(false, "");
+
+        String message = "A message";
+        mController.setVisible(true);
+
+        // WHEN there's a fingerprint not recognized message
+        mController.getKeyguardCallback().onBiometricHelp(
+                BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+                message,
+                BiometricSourceType.FINGERPRINT);
+
+        // THEN show action to unlock (ie: 'Swipe up to open')
+        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void transientIndication_visibleWhenDozing_unlessSwipeUp_fromError() {
+        createController();
+        String message = mContext.getString(R.string.keyguard_unlock);
+
+        mController.setVisible(true);
+        mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT,
+                "A message", BiometricSourceType.FACE);
+
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message);
+        mStatusBarStateListener.onDozingChanged(true);
+
+        assertThat(mTextView.getText()).isNotEqualTo(message);
+    }
+
+    @Test
+    public void transientIndication_visibleWhenDozing_ignoresFingerprintErrorMsg() {
+        createController();
+        mController.setVisible(true);
+        reset(mRotateTextViewController);
+
+        // WHEN a fingerprint error user cancelled message is received
+        mController.getKeyguardCallback().onBiometricError(
+                BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED, "foo",
+                BiometricSourceType.FINGERPRINT);
+
+        // THEN no message is shown
+        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+        verifyNoMessage(INDICATION_TYPE_TRANSIENT);
+    }
+
+    @Test
+    public void transientIndication_swipeUpToRetry() {
+        createController();
+        String message = mContext.getString(R.string.keyguard_retry);
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(false);
+
+        mController.setVisible(true);
+        mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT,
+                "A message", BiometricSourceType.FACE);
+
+        verify(mStatusBarKeyguardViewManager).setKeyguardMessage(eq(message), any(), any());
+    }
+
+    @Test
+    public void transientIndication_swipeUpToRetry_faceAuthenticated() {
+        createController();
+        String message = mContext.getString(R.string.keyguard_retry);
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled()).thenReturn(true);
+
+        mController.setVisible(true);
+        mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT,
+                "A message", BiometricSourceType.FACE);
+
+        verify(mStatusBarKeyguardViewManager, never()).setKeyguardMessage(
+                eq(message), any(), any());
+    }
+
+    @Test
+    public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() {
+        createController();
+        fingerprintUnlockIsPossibleAndAllowed();
+        String message = "A message";
+
+        mController.setVisible(true);
+        mController.getKeyguardCallback().onBiometricError(
+                FACE_ERROR_TIMEOUT, message, BiometricSourceType.FACE);
+        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+    }
+
+    @Test
+    public void sendFaceHelpMessages_fingerprintEnrolled() {
+        createController();
+        mController.mCoExAcquisitionMsgIdsToShowCallback.accept(
+                Set.of(
+                        BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED,
+                        BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED
+                )
+        );
+
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
+
+        // WHEN help messages received that are allowed to show
+        final String helpString = "helpString";
+        final int[] msgIds = new int[]{
+                BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED,
+                BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED
+        };
+        Set<CharSequence> messages = new HashSet<>();
+        for (int msgId : msgIds) {
+            final String message = helpString + msgId;
+            messages.add(message);
+            mKeyguardUpdateMonitorCallback.onBiometricHelp(
+                    msgId, message, BiometricSourceType.FACE);
+        }
+
+        // THEN FACE_ACQUIRED_MOUTH_COVERING_DETECTED and DARK_GLASSES help messages shown
+        verifyIndicationMessages(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                messages);
+    }
+
+    @Test
+    public void doNotSendMostFaceHelpMessages_fingerprintEnrolled() {
+        createController();
+
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
+
+        // WHEN help messages received that aren't supposed to show
+        final String helpString = "helpString";
+        final int[] msgIds = new int[]{
+                BiometricFaceConstants.FACE_ACQUIRED_FACE_OBSCURED,
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_RIGHT,
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_LEFT,
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_HIGH,
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_LOW,
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT
+        };
+        for (int msgId : msgIds) {
+            mKeyguardUpdateMonitorCallback.onBiometricHelp(
+                    msgId,  helpString + msgId, BiometricSourceType.FACE);
+        }
+
+        // THEN no messages shown
+        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+    }
+
+    @Test
+    public void sendAllFaceHelpMessages_fingerprintNotEnrolled() {
+        createController();
+
+        // GIVEN fingerprint NOT possible
+        fingerprintUnlockIsNotPossible();
+
+        // WHEN help messages received
+        final Set<CharSequence> helpStrings = new HashSet<>();
+        final String helpString = "helpString";
+        final int[] msgIds = new int[]{
+                BiometricFaceConstants.FACE_ACQUIRED_FACE_OBSCURED,
+                BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED,
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_RIGHT,
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_LEFT,
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_HIGH,
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_LOW,
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT
+        };
+        for (int msgId : msgIds) {
+            final String numberedHelpString = helpString + msgId;
+            mKeyguardUpdateMonitorCallback.onBiometricHelp(
+                    msgId,  numberedHelpString, BiometricSourceType.FACE);
+            helpStrings.add(numberedHelpString);
+        }
+
+        // THEN message shown for each call
+        verifyIndicationMessages(INDICATION_TYPE_BIOMETRIC_MESSAGE, helpStrings);
+    }
+
+    @Test
+    public void sendTooDarkFaceHelpMessages_onTimeout_noFpEnrolled() {
+        createController();
+
+        // GIVEN fingerprint not possible
+        fingerprintUnlockIsNotPossible();
+
+        // WHEN help message received and deferred message is valid
+        final String helpString = "helpMsg";
+        when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString);
+        when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true);
+        mKeyguardUpdateMonitorCallback.onBiometricHelp(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK,
+                helpString,
+                BiometricSourceType.FACE
+        );
+
+        // THEN help message not shown yet
+        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+
+        // WHEN face timeout error received
+        mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_TIMEOUT, "face timeout",
+                BiometricSourceType.FACE);
+
+        // THEN the low light message shows with suggestion to swipe up to unlock
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, helpString);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void sendTooDarkFaceHelpMessages_onTimeout_fingerprintEnrolled() {
+        createController();
+
+        // GIVEN unlocking with fingerprint is possible and allowed
+        fingerprintUnlockIsPossibleAndAllowed();
+
+        // WHEN help message received and deferredMessage is valid
+        final String helpString = "helpMsg";
+        when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString);
+        when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true);
+        mKeyguardUpdateMonitorCallback.onBiometricHelp(
+                BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK,
+                helpString,
+                BiometricSourceType.FACE
+        );
+
+        // THEN help message not shown yet
+        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+
+        // WHEN face timeout error received
+        mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_TIMEOUT, "face timeout",
+                BiometricSourceType.FACE);
+
+        // THEN the low light message shows and suggests trying fingerprint
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, helpString);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_suggest_fingerprint));
+    }
+
+    @Test
+    public void indicationAreaHidden_untilBatteryInfoArrives() {
+        createController();
+        // level of -1 indicates missing info
+        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_UNKNOWN,
+                -1 /* level */, BatteryManager.BATTERY_PLUGGED_WIRELESS, 100 /* health */,
+                0 /* maxChargingWattage */, true /* present */);
+
+        mController.setVisible(true);
+        mStatusBarStateListener.onDozingChanged(true);
+        reset(mIndicationArea);
+
+        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
+        // VISIBLE is always called first
+        verify(mIndicationArea).setVisibility(VISIBLE);
+        verify(mIndicationArea).setVisibility(GONE);
+    }
+
+    @Test
+    public void onRefreshBatteryInfo_computesChargingTime() throws RemoteException {
+        createController();
+        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
+                80 /* level */, BatteryManager.BATTERY_PLUGGED_WIRELESS, 100 /* health */,
+                0 /* maxChargingWattage */, true /* present */);
+
+        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
+        verify(mIBatteryStats).computeChargeTimeRemaining();
+    }
+
+    @Test
+    public void onRefreshBatteryInfo_computesChargingTime_onlyWhenCharging()
+            throws RemoteException {
+        createController();
+        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
+                80 /* level */, 0 /* plugged */, 100 /* health */,
+                0 /* maxChargingWattage */, true /* present */);
+
+        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
+        verify(mIBatteryStats, never()).computeChargeTimeRemaining();
+    }
+
+    /**
+     * Regression test.
+     * We should not make calls to the system_process when updating the doze state.
+     */
+    @Test
+    public void setDozing_noIBatteryCalls() throws RemoteException {
+        createController();
+        mController.setVisible(true);
+        mStatusBarStateListener.onDozingChanged(true);
+        mStatusBarStateListener.onDozingChanged(false);
+        verify(mIBatteryStats, never()).computeChargeTimeRemaining();
+    }
+
+    @Test
+    public void registersKeyguardStateCallback() {
+        createController();
+        verify(mKeyguardStateController).addCallback(any());
+    }
+
+    @Test
+    public void unlockMethodCache_listenerUpdatesPluggedIndication() {
+        createController();
+        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+        mController.setPowerPluggedIn(true);
+        mController.setVisible(true);
+
+        verifyIndicationMessage(
+                INDICATION_TYPE_TRUST,
+                mContext.getString(R.string.keyguard_indication_trust_unlocked));
+    }
+
+    @Test
+    public void onRefreshBatteryInfo_chargingWithLongLife_presentChargingLimited() {
+        createController();
+        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
+                80 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
+                BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE, 0 /* maxChargingWattage */,
+                true /* present */);
+
+        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
+        mController.setVisible(true);
+
+        verifyIndicationMessage(
+                INDICATION_TYPE_BATTERY,
+                mContext.getString(
+                        R.string.keyguard_plugged_in_charging_limited,
+                        NumberFormat.getPercentInstance().format(80 / 100f)));
+    }
+
+    @Test
+    public void onRefreshBatteryInfo_fullChargedWithLongLife_presentChargingLimited() {
+        createController();
+        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
+                100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
+                BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE, 0 /* maxChargingWattage */,
+                true /* present */);
+
+        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
+        mController.setVisible(true);
+
+        verifyIndicationMessage(
+                INDICATION_TYPE_BATTERY,
+                mContext.getString(
+                        R.string.keyguard_plugged_in_charging_limited,
+                        NumberFormat.getPercentInstance().format(100 / 100f)));
+    }
+
+    @Test
+    public void onRefreshBatteryInfo_fullChargedWithoutLongLife_presentCharged() {
+        createController();
+        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
+                100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
+                BatteryManager.CHARGING_POLICY_DEFAULT, 0 /* maxChargingWattage */,
+                true /* present */);
+
+        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
+        mController.setVisible(true);
+
+        verifyIndicationMessage(
+                INDICATION_TYPE_BATTERY,
+                mContext.getString(R.string.keyguard_charged));
+    }
+
+    @Test
+    public void onRefreshBatteryInfo_dozing_dischargingWithLongLife_presentBatteryPercentage() {
+        createController();
+        mController.setVisible(true);
+        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_DISCHARGING,
+                90 /* level */, 0 /* plugged */, BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE,
+                0 /* maxChargingWattage */, true /* present */);
+
+        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
+        mStatusBarStateListener.onDozingChanged(true);
+
+        String percentage = NumberFormat.getPercentInstance().format(90 / 100f);
+        assertThat(mTextView.getText()).isEqualTo(percentage);
+    }
+
+    @Test
+    public void onRequireUnlockForNfc_showsRequireUnlockForNfcIndication() {
+        createController();
+        mController.setVisible(true);
+        String message = mContext.getString(R.string.require_unlock_for_nfc);
+        mController.getKeyguardCallback().onRequireUnlockForNfc();
+
+        verifyTransientMessage(message);
+    }
+
+    @Test
+    public void testEmptyOwnerInfoHidesIndicationArea() {
+        createController();
+
+        // GIVEN the owner info is set to an empty string & keyguard is showing
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mLockPatternUtils.getDeviceOwnerInfo()).thenReturn("");
+
+        // WHEN asked to update the indication area
+        mController.setVisible(true);
+        mExecutor.runAllReady();
+
+        // THEN the owner info should be hidden
+        verifyHideIndication(INDICATION_TYPE_OWNER_INFO);
+    }
+
+    @Test
+    public void testOnKeyguardShowingChanged_notShowing_resetsMessages() {
+        createController();
+
+        // GIVEN keyguard isn't showing
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+        // WHEN keyguard showing changed called
+        mKeyguardStateControllerCallback.onKeyguardShowingChanged();
+
+        // THEN messages are reset
+        verify(mRotateTextViewController).clearMessages();
+        assertThat(mTextView.getText()).isEqualTo("");
+    }
+
+    @Test
+    public void testOnKeyguardShowingChanged_showing_updatesPersistentMessages() {
+        createController();
+        mController.setVisible(true);
+        mExecutor.runAllReady();
+        reset(mRotateTextViewController);
+
+        // GIVEN keyguard is showing and not dozing
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        mController.setVisible(true);
+        mExecutor.runAllReady();
+        reset(mRotateTextViewController);
+
+        // WHEN keyguard showing changed called
+        mKeyguardStateControllerCallback.onKeyguardShowingChanged();
+        mExecutor.runAllReady();
+
+        // THEN persistent messages are updated (in this case, most messages are hidden since
+        // no info is provided) - verify that this happens
+        verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_DISCLOSURE);
+        verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_OWNER_INFO);
+        verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_BATTERY);
+        verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_TRUST);
+        verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_ALIGNMENT);
+        verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_LOGOUT);
+    }
+
+    @Test
+    public void onTrustGrantedMessageDoesNotShowUntilTrustGranted() {
+        createController();
+        mController.setVisible(true);
+        reset(mRotateTextViewController);
+
+        // GIVEN a trust granted message but trust isn't granted
+        final String trustGrantedMsg = "testing trust granted message";
+        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+                false, false, new TrustGrantFlags(0), trustGrantedMsg);
+
+        verifyHideIndication(INDICATION_TYPE_TRUST);
+
+        // WHEN trust is granted
+        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+        mKeyguardUpdateMonitorCallback.onTrustChanged(getCurrentUser());
+
+        // THEN verify the trust granted message shows
+        verifyIndicationMessage(
+                INDICATION_TYPE_TRUST,
+                trustGrantedMsg);
+    }
+
+    @Test
+    public void onTrustGrantedMessageShowsOnTrustGranted() {
+        createController();
+        mController.setVisible(true);
+
+        // GIVEN trust is granted
+        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+
+        // WHEN the showTrustGranted method is called
+        final String trustGrantedMsg = "testing trust granted message";
+        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+                false, false, new TrustGrantFlags(0), trustGrantedMsg);
+
+        // THEN verify the trust granted message shows
+        verifyIndicationMessage(
+                INDICATION_TYPE_TRUST,
+                trustGrantedMsg);
+    }
+
+    @Test
+    public void onTrustGrantedMessage_nullMessage_showsDefaultMessage() {
+        createController();
+        mController.setVisible(true);
+
+        // GIVEN trust is granted
+        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+
+        // WHEN the showTrustGranted method is called with a null message
+        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+                false, false, new TrustGrantFlags(0), null);
+
+        // THEN verify the default trust granted message shows
+        verifyIndicationMessage(
+                INDICATION_TYPE_TRUST,
+                getContext().getString(R.string.keyguard_indication_trust_unlocked));
+    }
+
+    @Test
+    public void onTrustGrantedMessage_emptyString_showsNoMessage() {
+        createController();
+        mController.setVisible(true);
+
+        // GIVEN trust is granted
+        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+
+        // WHEN the showTrustGranted method is called with an EMPTY string
+        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+                false, false, new TrustGrantFlags(0), "");
+
+        // THEN verify NO trust message is shown
+        verifyNoMessage(INDICATION_TYPE_TRUST);
+    }
+
+    @Test
+    public void coEx_faceSuccess_showsPressToOpen() {
+        // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, no a11y enabled
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
+                .thenReturn(true);
+        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+        when(mAccessibilityManager.isEnabled()).thenReturn(false);
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        createController();
+        mController.setVisible(true);
+
+        // WHEN face auth succeeds
+        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
+        mController.getKeyguardCallback().onBiometricAuthenticated(0,
+                BiometricSourceType.FACE, false);
+
+        // THEN 'face unlocked' then 'press unlock icon to open' message show
+        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+        String pressToOpen = mContext.getString(R.string.keyguard_unlock_press);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, pressToOpen);
+    }
+
+    @Test
+    public void coEx_faceSuccess_touchExplorationEnabled_showsFaceUnlockedSwipeToOpen() {
+        // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y enabled
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
+                .thenReturn(true);
+        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+        when(mAccessibilityManager.isEnabled()).thenReturn(true);
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
+        createController();
+        mController.setVisible(true);
+
+        // WHEN face authenticated
+        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
+        mController.getKeyguardCallback().onBiometricAuthenticated(0,
+                BiometricSourceType.FACE, false);
+
+        // THEN show 'face unlocked' and 'swipe up to open' messages
+        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+        String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
+    }
+
+    @Test
+    public void coEx_faceSuccess_a11yEnabled_showsFaceUnlockedSwipeToOpen() {
+        // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y is enabled
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
+                .thenReturn(true);
+        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+        when(mAccessibilityManager.isEnabled()).thenReturn(true);
+        createController();
+        mController.setVisible(true);
+
+        // WHEN face auth is successful
+        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
+        mController.getKeyguardCallback().onBiometricAuthenticated(0,
+                BiometricSourceType.FACE, false);
+
+        // THEN show 'face unlocked' and 'swipe up to open' messages
+        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+        String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
+    }
+
+    @Test
+    public void faceOnly_faceSuccess_showsFaceUnlockedSwipeToOpen() {
+        // GIVEN bouncer isn't showing, can skip bouncer, no udfps supported
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
+                .thenReturn(true);
+        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
+        createController();
+        mController.setVisible(true);
+
+        // WHEN face auth is successful
+        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
+        mController.getKeyguardCallback().onBiometricAuthenticated(0,
+                BiometricSourceType.FACE, false);
+
+        // THEN show 'face unlocked' and 'swipe up to open' messages
+        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
+        String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
+    }
+
+    @Test
+    public void udfpsOnly_a11yEnabled_showsSwipeToOpen() {
+        // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y is enabled
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
+                .thenReturn(true);
+        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+        when(mAccessibilityManager.isEnabled()).thenReturn(true);
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
+        createController();
+        mController.setVisible(true);
+
+        // WHEN showActionToUnlock
+        mController.showActionToUnlock();
+
+        // THEN show 'swipe up to open' message
+        String swipeToOpen = mContext.getString(R.string.keyguard_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen);
+    }
+
+    @Test
+    public void udfpsOnly_showsPressToOpen() {
+        // GIVEN bouncer isn't showing, udfps is supported, a11y is NOT enabled, can skip bouncer
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
+                .thenReturn(true);
+        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+        when(mAccessibilityManager.isEnabled()).thenReturn(false);
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        createController();
+        mController.setVisible(true);
+
+        // WHEN showActionToUnlock
+        mController.showActionToUnlock();
+
+        // THEN show 'press unlock icon to open' message
+        String pressToOpen = mContext.getString(R.string.keyguard_unlock_press);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, pressToOpen);
+    }
+
+    @Test
+    public void canSkipBouncer_noSecurity_showSwipeToUnlockHint() {
+        // GIVEN bouncer isn't showing, can skip bouncer, no security (udfps isn't supported,
+        // face wasn't authenticated)
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
+                .thenReturn(true);
+        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
+        createController();
+        mController.setVisible(true);
+
+        // WHEN showActionToUnlock
+        mController.showActionToUnlock();
+
+        // THEN show 'swipe up to open' message
+        String swipeToOpen = mContext.getString(R.string.keyguard_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen);
+    }
+
+    @Test
+    public void cannotSkipBouncer_showSwipeToUnlockHint() {
+        // GIVEN bouncer isn't showing and cannot skip bouncer
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
+                .thenReturn(false);
+        createController();
+        mController.setVisible(true);
+
+        // WHEN showActionToUnlock
+        mController.showActionToUnlock();
+
+        // THEN show 'swipe up to open' message
+        String swipeToOpen = mContext.getString(R.string.keyguard_unlock);
+        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen);
+    }
+
+    @Test
+    public void faceOnAcquired_processFrame() {
+        createController();
+
+        // WHEN face sends an acquired message
+        final int acquireInfo = 1;
+        mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo);
+
+        // THEN face help message deferral should process the acquired frame
+        verify(mFaceHelpMessageDeferral).processFrame(acquireInfo);
+    }
+
+    @Test
+    public void fingerprintOnAcquired_noProcessFrame() {
+        createController();
+
+        // WHEN fingerprint sends an acquired message
+        mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FINGERPRINT, 1);
+
+        // THEN face help message deferral should NOT process any acquired frames
+        verify(mFaceHelpMessageDeferral, never()).processFrame(anyInt());
+    }
+
+    @Test
+    public void onBiometricHelp_fingerprint_faceHelpMessageDeferralDoesNothing() {
+        createController();
+
+        // WHEN fingerprint sends an onBiometricHelp
+        mKeyguardUpdateMonitorCallback.onBiometricHelp(
+                1,
+                "placeholder",
+                BiometricSourceType.FINGERPRINT);
+
+        // THEN face help message deferral is NOT: reset, updated, or checked for shouldDefer
+        verify(mFaceHelpMessageDeferral, never()).reset();
+        verify(mFaceHelpMessageDeferral, never()).updateMessage(anyInt(), anyString());
+        verify(mFaceHelpMessageDeferral, never()).shouldDefer(anyInt());
+    }
+
+    @Test
+    public void onBiometricFailed_resetFaceHelpMessageDeferral() {
+        createController();
+
+        // WHEN face sends an onBiometricAuthFailed
+        mKeyguardUpdateMonitorCallback.onBiometricAuthFailed(BiometricSourceType.FACE);
+
+        // THEN face help message deferral is reset
+        verify(mFaceHelpMessageDeferral).reset();
+    }
+
+    @Test
+    public void onBiometricError_resetFaceHelpMessageDeferral() {
+        createController();
+
+        // WHEN face has an error
+        mKeyguardUpdateMonitorCallback.onBiometricError(4, "string",
+                BiometricSourceType.FACE);
+
+        // THEN face help message deferral is reset
+        verify(mFaceHelpMessageDeferral).reset();
+    }
+
+    @Test
+    public void onBiometricHelp_faceAcquiredInfo_faceHelpMessageDeferral() {
+        createController();
+
+        // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED
+        final int msgId = 1;
+        final String helpString = "test";
+        mKeyguardUpdateMonitorCallback.onBiometricHelp(
+                msgId,
+                "test",
+                BiometricSourceType.FACE);
+
+        // THEN face help message deferral is NOT reset and message IS updated
+        verify(mFaceHelpMessageDeferral, never()).reset();
+        verify(mFaceHelpMessageDeferral).updateMessage(msgId, helpString);
+    }
+
+
+    @Test
+    public void onBiometricError_faceLockedOutFirstTime_showsThePassedInMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "first lockout");
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() {
+        createController();
+        fingerprintUnlockIsPossibleAndAllowed();
+        onFaceLockoutError("first lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_suggest_fingerprint));
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutFirstTimeAndFpNotAllowed_showsDefaultFollowup() {
+        createController();
+        fingerprintUnlockIsNotPossible();
+        onFaceLockoutError("first lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutSecondTimeInSession_showsUnavailableMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                mContext.getString(R.string.keyguard_face_unlock_unavailable));
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutSecondTimeOnBouncer_showsUnavailableMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+
+        onFaceLockoutError("second lockout");
+
+        verify(mStatusBarKeyguardViewManager)
+                .setKeyguardMessage(
+                        eq(mContext.getString(R.string.keyguard_face_unlock_unavailable)),
+                        any(),
+                        any()
+                );
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutSecondTimeButUdfpsActive_showsNoMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+
+        when(mAuthController.isUdfpsFingerDown()).thenReturn(true);
+        onFaceLockoutError("second lockout");
+
+        verifyNoMoreInteractions(mRotateTextViewController);
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() {
+        createController();
+        fingerprintUnlockIsPossibleAndAllowed();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_suggest_fingerprint));
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutAgainAndFpNotAllowed_showsDefaultFollowup() {
+        createController();
+        fingerprintUnlockIsNotPossible();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onBiometricError_whenFaceLockoutReset_onLockOutError_showsPassedInMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+        when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(false);
+        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "second lockout");
+    }
+
+    @Test
+    public void onFpLockoutStateChanged_whenFpIsLockedOut_showsPersistentMessage() {
+        createController();
+        mController.setVisible(true);
+        when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(true);
+
+        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+
+        verifyIndicationShown(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onFpLockoutStateChanged_whenFpIsNotLockedOut_showsPersistentMessage() {
+        createController();
+        mController.setVisible(true);
+        clearInvocations(mRotateTextViewController);
+        when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(false);
+
+        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+
+        verifyHideIndication(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE);
+    }
+
+    @Test
+    public void onVisibilityChange_showsPersistentMessage_ifFpIsLockedOut() {
+        createController();
+        mController.setVisible(false);
+        when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(true);
+        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+        clearInvocations(mRotateTextViewController);
+
+        mController.setVisible(true);
+
+        verifyIndicationShown(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onBiometricError_whenFaceIsLocked_onMultipleLockOutErrors_showUnavailableMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+        when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true);
+        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                mContext.getString(R.string.keyguard_face_unlock_unavailable));
+    }
+
+    @Test
+    public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsNotAvailable_showsMessage() {
+        createController();
+        screenIsTurningOn();
+        fingerprintUnlockIsNotPossible();
+
+        onFaceLockoutError("lockout error");
+        verifyNoMoreInteractions(mRotateTextViewController);
+
+        mScreenObserver.onScreenTurnedOn();
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                "lockout error");
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
+        createController();
+        screenIsTurningOn();
+        fingerprintUnlockIsPossibleAndAllowed();
+
+        onFaceLockoutError("lockout error");
+        verifyNoMoreInteractions(mRotateTextViewController);
+
+        mScreenObserver.onScreenTurnedOn();
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                "lockout error");
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_suggest_fingerprint));
+    }
+
+    @Test
+    public void faceErrorMessageDroppedBecauseFingerprintMessageShowing() {
+        createController();
+        mController.setVisible(true);
+        mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+                "fp not recognized", BiometricSourceType.FINGERPRINT);
+        clearInvocations(mRotateTextViewController);
+
+        onFaceLockoutError("lockout");
+        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+    }
+
+    @Test
+    public void faceUnlockedMessageShowsEvenWhenFingerprintMessageShowing() {
+        createController();
+        mController.setVisible(true);
+        mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+                "fp not recognized", BiometricSourceType.FINGERPRINT);
+        clearInvocations(mRotateTextViewController);
+
+        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
+                .thenReturn(true);
+        mController.getKeyguardCallback().onBiometricAuthenticated(0,
+                BiometricSourceType.FACE, false);
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                mContext.getString(R.string.keyguard_face_successful_unlock));
+    }
+
+    @Test
+    public void trustGrantedMessageShowsEvenWhenFingerprintMessageShowing() {
+        createController();
+        mController.setVisible(true);
+        mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+                "fp not recognized", BiometricSourceType.FINGERPRINT);
+        clearInvocations(mRotateTextViewController);
+
+        // GIVEN trust is granted
+        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+
+        // WHEN the showTrustGranted method is called
+        final String trustGrantedMsg = "testing trust granted message after fp message";
+        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+                false, false, new TrustGrantFlags(0), trustGrantedMsg);
+
+        // THEN verify the trust granted message shows
+        verifyIndicationMessage(
+                INDICATION_TYPE_TRUST,
+                trustGrantedMsg);
+    }
+
+    @Test
+    public void updateAdaptiveAuthMessage_whenNotLockedByAdaptiveAuth_doesNotShowMsg() {
+        // When the device is not locked by adaptive auth
+        when(mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(getCurrentUser()))
+                .thenReturn(false);
+        createController();
+        mController.setVisible(true);
+
+        // Verify that the adaptive auth message does not show
+        verifyNoMessage(INDICATION_TYPE_ADAPTIVE_AUTH);
+    }
+
+    @Test
+    public void updateAdaptiveAuthMessage_whenLockedByAdaptiveAuth_cannotSkipBouncer_showsMsg() {
+        // When the device is locked by adaptive auth, and the user cannot skip bouncer
+        when(mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(getCurrentUser()))
+                .thenReturn(true);
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser())).thenReturn(false);
+        createController();
+        mController.setVisible(true);
+
+        // Verify that the adaptive auth message shows
+        String message = mContext.getString(R.string.keyguard_indication_after_adaptive_auth_lock);
+        verifyIndicationMessage(INDICATION_TYPE_ADAPTIVE_AUTH, message);
+    }
+
+    @Test
+    public void updateAdaptiveAuthMessage_whenLockedByAdaptiveAuth_canSkipBouncer_doesNotShowMsg() {
+        createController();
+        mController.setVisible(true);
+
+        // When the device is locked by adaptive auth, but the device unlocked state changes and the
+        // user can skip bouncer
+        when(mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(getCurrentUser()))
+                .thenReturn(true);
+        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser())).thenReturn(true);
+        mKeyguardStateControllerCallback.onUnlockedChanged();
+
+        // Verify that the adaptive auth message does not show
+        verifyNoMessage(INDICATION_TYPE_ADAPTIVE_AUTH);
+    }
+
+    private void screenIsTurningOn() {
+        when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_TURNING_ON);
+    }
+
+    private void sendUpdateDisclosureBroadcast() {
+        mBroadcastReceiver.onReceive(mContext, new Intent());
+    }
+
+    private void verifyIndicationMessages(int type, Set<CharSequence> messages) {
+        verify(mRotateTextViewController, times(messages.size())).updateIndication(eq(type),
+                mKeyguardIndicationCaptor.capture(), anyBoolean());
+        List<KeyguardIndication> kis = mKeyguardIndicationCaptor.getAllValues();
+
+        for (KeyguardIndication ki : kis) {
+            final CharSequence msg = ki.getMessage();
+            assertTrue(messages.contains(msg)); // check message is shown
+            messages.remove(msg);
+        }
+        assertThat(messages.size()).isEqualTo(0); // check that all messages accounted for (removed)
+    }
+
+    private void verifyIndicationMessage(int type, String message) {
+        verify(mRotateTextViewController).updateIndication(eq(type),
+                mKeyguardIndicationCaptor.capture(), anyBoolean());
+        assertThat(mKeyguardIndicationCaptor.getValue().getMessage())
+                .isEqualTo(message);
+    }
+
+    private void verifyHideIndication(int type) {
+        if (type == INDICATION_TYPE_TRANSIENT) {
+            verify(mRotateTextViewController).hideTransient();
+            verify(mRotateTextViewController, never()).showTransient(anyString());
+        } else {
+            verify(mRotateTextViewController).hideIndication(type);
+            verify(mRotateTextViewController, never()).updateIndication(eq(type),
+                    any(), anyBoolean());
+        }
+    }
+
+    private void verifyTransientMessage(String message) {
+        verify(mRotateTextViewController).showTransient(eq(message));
+    }
+
+    private void fingerprintUnlockIsNotPossible() {
+        setupFingerprintUnlockPossible(false);
+    }
+
+    private void fingerprintUnlockIsPossibleAndAllowed() {
+        setupFingerprintUnlockPossible(true);
+        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(true);
+    }
+
+    private void setupFingerprintUnlockPossible(boolean possible) {
+        when(mKeyguardUpdateMonitor
+                .isUnlockWithFingerprintPossible(getCurrentUser()))
+                .thenReturn(possible);
+    }
+
+    private int getCurrentUser() {
+        return mCurrentUserId;
+    }
+
+    private void onFaceLockoutError(String errMsg) {
+        mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_LOCKOUT_PERMANENT,
+                errMsg,
+                BiometricSourceType.FACE);
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
index 9907740..cd66ef3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
@@ -26,10 +26,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.flow.MutableStateFlow
 import org.junit.Before
@@ -76,7 +76,7 @@
                 falsingManager,
                 shadeInteractor,
                 lockscreenShadeTransitionController,
-                dumpManager
+                dumpManager,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
new file mode 100644
index 0000000..e96dd16
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.notification.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.data.repository.activityManagerRepository
+import com.android.systemui.activity.data.repository.fake
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SingleNotificationChipInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    val factory = kosmos.singleNotificationChipInteractorFactory
+
+    @Test
+    fun notificationChip_startsWithStartingModel() =
+        kosmos.runTest {
+            val icon = mock<StatusBarIconView>()
+            val startingNotif =
+                activeNotificationModel(key = "notif1", statusBarChipIcon = icon, whenTime = 5432)
+
+            val underTest = factory.create(startingNotif)
+
+            val latest by collectLastValue(underTest.notificationChip)
+
+            assertThat(latest!!.key).isEqualTo("notif1")
+            assertThat(latest!!.statusBarChipIconView).isEqualTo(icon)
+            assertThat(latest!!.whenTime).isEqualTo(5432)
+        }
+
+    @Test
+    fun notificationChip_updatesAfterSet() =
+        kosmos.runTest {
+            val originalIconView = mock<StatusBarIconView>()
+            val underTest =
+                factory.create(
+                    activeNotificationModel(key = "notif1", statusBarChipIcon = originalIconView)
+                )
+
+            val latest by collectLastValue(underTest.notificationChip)
+
+            val newIconView = mock<StatusBarIconView>()
+            underTest.setNotification(
+                activeNotificationModel(
+                    key = "notif1",
+                    statusBarChipIcon = newIconView,
+                    whenTime = 6543,
+                )
+            )
+
+            assertThat(latest!!.key).isEqualTo("notif1")
+            assertThat(latest!!.statusBarChipIconView).isEqualTo(newIconView)
+            assertThat(latest!!.whenTime).isEqualTo(6543)
+        }
+
+    @Test
+    fun notificationChip_ignoresSetWithDifferentKey() =
+        kosmos.runTest {
+            val originalIconView = mock<StatusBarIconView>()
+            val underTest =
+                factory.create(
+                    activeNotificationModel(key = "notif1", statusBarChipIcon = originalIconView)
+                )
+
+            val latest by collectLastValue(underTest.notificationChip)
+
+            val newIconView = mock<StatusBarIconView>()
+            underTest.setNotification(
+                activeNotificationModel(key = "other_notif", statusBarChipIcon = newIconView)
+            )
+
+            assertThat(latest!!.key).isEqualTo("notif1")
+            assertThat(latest!!.statusBarChipIconView).isEqualTo(originalIconView)
+        }
+
+    @Test
+    fun notificationChip_missingStatusBarIconChipView_inConstructor_emitsNull() =
+        kosmos.runTest {
+            val underTest =
+                factory.create(activeNotificationModel(key = "notif1", statusBarChipIcon = null))
+
+            val latest by collectLastValue(underTest.notificationChip)
+
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun notificationChip_missingStatusBarIconChipView_inSet_emitsNull() =
+        kosmos.runTest {
+            val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = mock())
+            val underTest = factory.create(startingNotif)
+            val latest by collectLastValue(underTest.notificationChip)
+            assertThat(latest).isNotNull()
+
+            underTest.setNotification(
+                activeNotificationModel(key = "notif1", statusBarChipIcon = null)
+            )
+
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun notificationChip_appIsVisibleOnCreation_emitsNull() =
+        kosmos.runTest {
+            activityManagerRepository.fake.startingIsAppVisibleValue = true
+
+            val underTest =
+                factory.create(
+                    activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
+                )
+
+            val latest by collectLastValue(underTest.notificationChip)
+
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun notificationChip_appNotVisibleOnCreation_emitsValue() =
+        kosmos.runTest {
+            activityManagerRepository.fake.startingIsAppVisibleValue = false
+
+            val underTest =
+                factory.create(
+                    activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
+                )
+
+            val latest by collectLastValue(underTest.notificationChip)
+
+            assertThat(latest).isNotNull()
+        }
+
+    @Test
+    fun notificationChip_hidesWhenAppIsVisible() =
+        kosmos.runTest {
+            val underTest =
+                factory.create(
+                    activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
+                )
+
+            val latest by collectLastValue(underTest.notificationChip)
+
+            activityManagerRepository.fake.setIsAppVisible(UID, false)
+            assertThat(latest).isNotNull()
+
+            activityManagerRepository.fake.setIsAppVisible(UID, true)
+            assertThat(latest).isNull()
+
+            activityManagerRepository.fake.setIsAppVisible(UID, false)
+            assertThat(latest).isNotNull()
+        }
+
+    // Note: This test is theoretically impossible because the notification key should contain the
+    // UID, so if the UID changes then the key would also change and a new interactor would be
+    // created. But, test it just in case.
+    @Test
+    fun notificationChip_updatedUid_rechecksAppVisibility_oldObserverUnregistered() =
+        kosmos.runTest {
+            activityManagerRepository.fake.startingIsAppVisibleValue = false
+
+            val hiddenUid = 100
+            val shownUid = 101
+
+            val underTest =
+                factory.create(
+                    activeNotificationModel(
+                        key = "notif",
+                        uid = hiddenUid,
+                        statusBarChipIcon = mock(),
+                    )
+                )
+            val latest by collectLastValue(underTest.notificationChip)
+            assertThat(latest).isNotNull()
+
+            // WHEN the notif gets a new UID that starts as visible
+            activityManagerRepository.fake.startingIsAppVisibleValue = true
+            underTest.setNotification(
+                activeNotificationModel(key = "notif", uid = shownUid, statusBarChipIcon = mock())
+            )
+
+            // THEN we re-fetch the app visibility state with the new UID, and since that UID is
+            // visible, we hide the chip
+            assertThat(latest).isNull()
+        }
+
+    companion object {
+        private const val UID = 885
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
index 19ed6a5..5a894ca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
@@ -16,30 +16,280 @@
 
 package com.android.systemui.statusbar.chips.notification.domain.interactor
 
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@EnableFlags(StatusBarNotifChips.FLAG_NAME)
 class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
     private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val testScope = kosmos.testScope
+    private val activeNotificationListRepository = kosmos.activeNotificationListRepository
 
-    private val underTest = kosmos.statusBarNotificationChipsInteractor
+    private val underTest by lazy {
+        kosmos.statusBarNotificationChipsInteractor.also { it.start() }
+    }
 
     @Test
+    @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun notificationChips_flagOff_noNotifs() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.notificationChips)
+
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = mock<StatusBarIconView>(),
+                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+                    )
+                )
+            )
+
+            assertThat(latest).isEmpty()
+        }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun notificationChips_noNotifs_empty() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.notificationChips)
+
+            setNotifs(emptyList())
+
+            assertThat(latest).isEmpty()
+        }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun notificationChips_notifMissingStatusBarChipIconView_empty() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.notificationChips)
+
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = null,
+                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+                    )
+                )
+            )
+
+            assertThat(latest).isEmpty()
+        }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun notificationChips_onePromotedNotif_statusBarIconViewMatches() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.notificationChips)
+
+            val icon = mock<StatusBarIconView>()
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = icon,
+                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+                    )
+                )
+            )
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].key).isEqualTo("notif")
+            assertThat(latest!![0].statusBarChipIconView).isEqualTo(icon)
+        }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun notificationChips_onlyForPromotedNotifs() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.notificationChips)
+
+            val firstIcon = mock<StatusBarIconView>()
+            val secondIcon = mock<StatusBarIconView>()
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif1",
+                        statusBarChipIcon = firstIcon,
+                        promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+                    ),
+                    activeNotificationModel(
+                        key = "notif2",
+                        statusBarChipIcon = secondIcon,
+                        promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+                    ),
+                    activeNotificationModel(
+                        key = "notif3",
+                        statusBarChipIcon = mock<StatusBarIconView>(),
+                        promotedContent = null,
+                    ),
+                )
+            )
+
+            assertThat(latest).hasSize(2)
+            assertThat(latest!![0].key).isEqualTo("notif1")
+            assertThat(latest!![0].statusBarChipIconView).isEqualTo(firstIcon)
+            assertThat(latest!![1].key).isEqualTo("notif2")
+            assertThat(latest!![1].statusBarChipIconView).isEqualTo(secondIcon)
+        }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun notificationChips_notifUpdatesGoThrough() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.notificationChips)
+
+            val firstIcon = mock<StatusBarIconView>()
+            val secondIcon = mock<StatusBarIconView>()
+            val thirdIcon = mock<StatusBarIconView>()
+
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = firstIcon,
+                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+                    )
+                )
+            )
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].key).isEqualTo("notif")
+            assertThat(latest!![0].statusBarChipIconView).isEqualTo(firstIcon)
+
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = secondIcon,
+                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+                    )
+                )
+            )
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].key).isEqualTo("notif")
+            assertThat(latest!![0].statusBarChipIconView).isEqualTo(secondIcon)
+
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = thirdIcon,
+                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+                    )
+                )
+            )
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].key).isEqualTo("notif")
+            assertThat(latest!![0].statusBarChipIconView).isEqualTo(thirdIcon)
+        }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun notificationChips_promotedNotifDisappearsThenReappears() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.notificationChips)
+
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = mock(),
+                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+                    )
+                )
+            )
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].key).isEqualTo("notif")
+
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = mock(),
+                        promotedContent = null,
+                    )
+                )
+            )
+            assertThat(latest).isEmpty()
+
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = mock(),
+                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+                    )
+                )
+            )
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].key).isEqualTo("notif")
+        }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun notificationChips_notifChangesKey() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.notificationChips)
+
+            val firstIcon = mock<StatusBarIconView>()
+            val secondIcon = mock<StatusBarIconView>()
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif|uid1",
+                        statusBarChipIcon = firstIcon,
+                        promotedContent =
+                            PromotedNotificationContentModel.Builder("notif|uid1").build(),
+                    )
+                )
+            )
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].key).isEqualTo("notif|uid1")
+            assertThat(latest!![0].statusBarChipIconView).isEqualTo(firstIcon)
+
+            // WHEN a notification changes UID, which is a key change
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif|uid2",
+                        statusBarChipIcon = secondIcon,
+                        promotedContent =
+                            PromotedNotificationContentModel.Builder("notif|uid2").build(),
+                    )
+                )
+            )
+
+            // THEN we correctly update
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].key).isEqualTo("notif|uid2")
+            assertThat(latest!![0].statusBarChipIconView).isEqualTo(secondIcon)
+        }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
     fun onPromotedNotificationChipTapped_emitsKeys() =
         testScope.runTest {
             val latest by collectValues(underTest.promotedNotificationChipTapEvent)
@@ -56,6 +306,7 @@
         }
 
     @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
     fun onPromotedNotificationChipTapped_sameKeyTwice_emitsTwice() =
         testScope.runTest {
             val latest by collectValues(underTest.promotedNotificationChipTapEvent)
@@ -67,4 +318,11 @@
             assertThat(latest[0]).isEqualTo("fakeKey")
             assertThat(latest[1]).isEqualTo("fakeKey")
         }
+
+    private fun setNotifs(notifs: List<ActiveNotificationModel>) {
+        activeNotificationListRepository.activeNotifications.value =
+            ActiveNotificationsStore.Builder()
+                .apply { notifs.forEach { addIndividualNotif(it) } }
+                .build()
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 1b41329..11831ca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -22,7 +22,9 @@
 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.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
@@ -30,30 +32,33 @@
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
 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 org.mockito.kotlin.mock
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalCoroutinesApi::class)
 @EnableFlags(StatusBarNotifChips.FLAG_NAME)
 class NotifChipsViewModelTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val activeNotificationListRepository = kosmos.activeNotificationListRepository
 
-    private val underTest = kosmos.notifChipsViewModel
+    private val underTest by lazy { kosmos.notifChipsViewModel }
+
+    @Before
+    fun setUp() {
+        kosmos.statusBarNotificationChipsInteractor.start()
+    }
 
     @Test
     fun chips_noNotifs_empty() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chips)
 
             setNotifs(emptyList())
@@ -63,7 +68,7 @@
 
     @Test
     fun chips_notifMissingStatusBarChipIconView_empty() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chips)
 
             setNotifs(
@@ -71,7 +76,7 @@
                     activeNotificationModel(
                         key = "notif",
                         statusBarChipIcon = null,
-                        isPromoted = true,
+                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
                     )
                 )
             )
@@ -81,7 +86,7 @@
 
     @Test
     fun chips_onePromotedNotif_statusBarIconViewMatches() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chips)
 
             val icon = mock<StatusBarIconView>()
@@ -90,20 +95,20 @@
                     activeNotificationModel(
                         key = "notif",
                         statusBarChipIcon = icon,
-                        isPromoted = true,
+                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
                     )
                 )
             )
 
             assertThat(latest).hasSize(1)
             val chip = latest!![0]
-            assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
+            assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
             assertThat(chip.icon).isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(icon))
         }
 
     @Test
     fun chips_onlyForPromotedNotifs() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chips)
 
             val firstIcon = mock<StatusBarIconView>()
@@ -113,17 +118,17 @@
                     activeNotificationModel(
                         key = "notif1",
                         statusBarChipIcon = firstIcon,
-                        isPromoted = true,
+                        promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
                     ),
                     activeNotificationModel(
                         key = "notif2",
                         statusBarChipIcon = secondIcon,
-                        isPromoted = true,
+                        promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
                     ),
                     activeNotificationModel(
                         key = "notif3",
                         statusBarChipIcon = mock<StatusBarIconView>(),
-                        isPromoted = false,
+                        promotedContent = null,
                     ),
                 )
             )
@@ -135,7 +140,7 @@
 
     @Test
     fun chips_clickingChipNotifiesInteractor() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.chips)
             val latestChipTap by
                 collectLastValue(
@@ -147,7 +152,8 @@
                     activeNotificationModel(
                         key = "clickTest",
                         statusBarChipIcon = mock<StatusBarIconView>(),
-                        isPromoted = true,
+                        promotedContent =
+                            PromotedNotificationContentModel.Builder("clickTest").build(),
                     )
                 )
             )
@@ -163,12 +169,12 @@
             ActiveNotificationsStore.Builder()
                 .apply { notifs.forEach { addIndividualNotif(it) } }
                 .build()
-        testScope.runCurrent()
     }
 
     companion object {
         fun assertIsNotifChip(latest: OngoingActivityChipModel?, expectedIcon: StatusBarIconView) {
-            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
+            assertThat(latest)
+                .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
             assertThat((latest as OngoingActivityChipModel.Shown).icon)
                 .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon))
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 25d5ce5..b2e7feb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
 import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
@@ -38,6 +39,7 @@
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
 import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModelTest.Companion.addDemoNotifChip
 import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.demoNotifChipViewModel
+import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModelTest.Companion.assertIsNotifChip
 import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
@@ -51,6 +53,7 @@
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
@@ -67,6 +70,7 @@
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
@@ -79,7 +83,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @EnableFlags(StatusBarNotifChips.FLAG_NAME)
 class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val testScope = kosmos.testScope
     private val systemClock = kosmos.fakeSystemClock
     private val commandRegistry = kosmos.commandRegistry
@@ -103,12 +107,13 @@
                 .thenReturn(chipBackgroundView)
         }
 
-    private val underTest = kosmos.ongoingActivityChipsViewModel
+    private val underTest by lazy { kosmos.ongoingActivityChipsViewModel }
 
     @Before
     fun setUp() {
         setUpPackageManagerForMediaProjection(kosmos)
         kosmos.demoNotifChipViewModel.start()
+        kosmos.statusBarNotificationChipsInteractor.start()
         val icon =
             BitmapDrawable(
                 context.resources,
@@ -303,7 +308,7 @@
                     activeNotificationModel(
                         key = "notif",
                         statusBarChipIcon = icon,
-                        isPromoted = true,
+                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
                     )
                 )
             )
@@ -324,12 +329,14 @@
                     activeNotificationModel(
                         key = "firstNotif",
                         statusBarChipIcon = firstIcon,
-                        isPromoted = true,
+                        promotedContent =
+                            PromotedNotificationContentModel.Builder("firstNotif").build(),
                     ),
                     activeNotificationModel(
                         key = "secondNotif",
                         statusBarChipIcon = secondIcon,
-                        isPromoted = true,
+                        promotedContent =
+                            PromotedNotificationContentModel.Builder("secondNotif").build(),
                     ),
                 )
             )
@@ -351,17 +358,20 @@
                     activeNotificationModel(
                         key = "firstNotif",
                         statusBarChipIcon = firstIcon,
-                        isPromoted = true,
+                        promotedContent =
+                            PromotedNotificationContentModel.Builder("firstNotif").build(),
                     ),
                     activeNotificationModel(
                         key = "secondNotif",
                         statusBarChipIcon = secondIcon,
-                        isPromoted = true,
+                        promotedContent =
+                            PromotedNotificationContentModel.Builder("secondNotif").build(),
                     ),
                     activeNotificationModel(
                         key = "thirdNotif",
                         statusBarChipIcon = thirdIcon,
-                        isPromoted = true,
+                        promotedContent =
+                            PromotedNotificationContentModel.Builder("thirdNotif").build(),
                     ),
                 )
             )
@@ -382,12 +392,14 @@
                     activeNotificationModel(
                         key = "firstNotif",
                         statusBarChipIcon = firstIcon,
-                        isPromoted = true,
+                        promotedContent =
+                            PromotedNotificationContentModel.Builder("firstNotif").build(),
                     ),
                     activeNotificationModel(
                         key = "secondNotif",
                         statusBarChipIcon = mock<StatusBarIconView>(),
-                        isPromoted = true,
+                        promotedContent =
+                            PromotedNotificationContentModel.Builder("secondNotif").build(),
                     ),
                 )
             )
@@ -408,7 +420,7 @@
                     activeNotificationModel(
                         key = "notif",
                         statusBarChipIcon = mock<StatusBarIconView>(),
-                        isPromoted = true,
+                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
                     )
                 )
             )
@@ -616,6 +628,7 @@
         }
 
     @Test
+    @Ignore("b/364653005") // We'll need to re-do the animation story when we implement RON chips
     fun primaryChip_screenRecordStoppedViaDialog_chipHiddenWithoutAnimation() =
         testScope.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index 2a3878c..5247433 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.os.UserHandle
 import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper.RunWithLooper
 import androidx.lifecycle.Lifecycle
@@ -251,9 +252,25 @@
         val primaryUserMockContext = mock<Context>()
         mContext.prepareCreateContextAsUser(UserHandle.of(PRIMARY_USER_ID), primaryUserMockContext)
         controller.onUserSwitched(PRIMARY_USER_ID)
+
         // Create is expected to be called once when the test starts and a second time when the user
-        // is switched.
+        // is switched. The first WifiPickerTracker should have its onStop() method called prior to
+        // the new WifiPickerTracker being created.
         verify(wifiPickerTrackerFactory, times(2)).create(any(), any(), any(), any())
+        verify(wifiPickerTracker).onStop()
+    }
+
+    @Test
+    @DisableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
+    fun switchUsers_flagDisabled() {
+        val primaryUserMockContext = mock<Context>()
+        mContext.prepareCreateContextAsUser(UserHandle.of(PRIMARY_USER_ID), primaryUserMockContext)
+        controller.onUserSwitched(PRIMARY_USER_ID)
+
+        // Create is expected to only be called once when the test starts, switching users should
+        // have no effects.
+        verify(wifiPickerTrackerFactory, times(1)).create(any(), any(), any(), any())
+        verify(wifiPickerTracker, never()).onStop()
     }
 
     private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
index cb92b77..a1772e3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
@@ -14,11 +14,11 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.HeadsUpUtil
 import com.android.systemui.testKosmos
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
@@ -69,7 +69,7 @@
                 headsUpManager,
                 notification,
                 kosmos.interactionJankMonitor,
-                onFinishAnimationCallback
+                onFinishAnimationCallback,
             )
     }
 
@@ -95,7 +95,7 @@
                 notificationKey,
                 /* releaseImmediately= */ true,
                 /* animate= */ true,
-                /* reason= */ "onIntentStarted(willAnimate=false)"
+                /* reason= */ "onIntentStarted(willAnimate=false)",
             )
         verify(onFinishAnimationCallback).run()
     }
@@ -118,7 +118,7 @@
                 notificationKey,
                 /* releaseImmediately= */ true,
                 /* animate= */ true,
-                /* reason= */ "onLaunchAnimationCancelled()"
+                /* reason= */ "onLaunchAnimationCancelled()",
             )
         verify(onFinishAnimationCallback).run()
     }
@@ -141,7 +141,7 @@
                 notificationKey,
                 /* releaseImmediately= */ true,
                 /* animate= */ false,
-                /* reason= */ "onLaunchAnimationEnd()"
+                /* reason= */ "onLaunchAnimationEnd()",
             )
         verify(onFinishAnimationCallback).run()
     }
@@ -180,14 +180,14 @@
                 summary.key,
                 /* releaseImmediately= */ true,
                 /* animate= */ false,
-                /* reason= */ "onLaunchAnimationEnd()"
+                /* reason= */ "onLaunchAnimationEnd()",
             )
         verify(headsUpManager, never())
             .removeNotification(
                 notification.entry.key,
                 /* releaseImmediately= */ true,
                 /* animate= */ false,
-                /* reason= */ "onLaunchAnimationEnd()"
+                /* reason= */ "onLaunchAnimationEnd()",
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index c4b1b84..0dc01a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -39,13 +39,13 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.ShadeViewController.Companion.WAKEUP_ANIMATION_DELAY_MS
 import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_WAKEUP
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
 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.HeadsUpManager
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 2c488e3..1d7f257 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -38,12 +38,13 @@
 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.mockNotifCollection
-import com.android.systemui.statusbar.notification.collection.notifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
 import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
 import com.android.systemui.statusbar.notification.collection.render.NodeController
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl
@@ -51,8 +52,6 @@
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
 import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback
 import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
-import com.android.systemui.statusbar.policy.BaseHeadsUpManager
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
 import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -84,7 +83,9 @@
 class HeadsUpCoordinatorTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val statusBarNotificationChipsInteractor = kosmos.statusBarNotificationChipsInteractor
+    private val statusBarNotificationChipsInteractor by lazy {
+        kosmos.statusBarNotificationChipsInteractor
+    }
     private val notifCollection = kosmos.mockNotifCollection
 
     private lateinit var coordinator: HeadsUpCoordinator
@@ -101,7 +102,7 @@
 
     private val notifPipeline: NotifPipeline = mock()
     private val logger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true)
-    private val headsUpManager: BaseHeadsUpManager = mock()
+    private val headsUpManager: HeadsUpManagerImpl = mock()
     private val headsUpViewBinder: HeadsUpViewBinder = mock()
     private val visualInterruptionDecisionProvider: VisualInterruptionDecisionProvider = mock()
     private val remoteInputManager: NotificationRemoteInputManager = mock()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
index d772e3e..14148cd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
@@ -21,7 +21,6 @@
 import android.app.NotificationManager.IMPORTANCE_DEFAULT
 import android.app.NotificationManager.IMPORTANCE_LOW
 import android.platform.test.annotations.EnableFlags
-import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -43,6 +42,7 @@
 import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.domain.interactor.lockScreenNotificationMinimalismSetting
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus
 import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
 import com.android.systemui.testKosmos
@@ -394,7 +394,7 @@
             assertThatTopUnseenKey().isEqualTo(solo1.key)
 
             // TEST: even being pinned doesn't take effect immediately
-            hunRepo1.isPinned.value = true
+            hunRepo1.pinnedStatus.value = PinnedStatus.PinnedBySystem
             testScheduler.advanceTimeBy(0.5.seconds)
             onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
             assertThatTopUnseenKey().isEqualTo(solo1.key)
@@ -406,8 +406,8 @@
 
             // TEST: repeat; being heads up and pinned for 1 second triggers seen
             kosmos.headsUpNotificationRepository.orderedHeadsUpRows.value = listOf(hunRepo2)
-            hunRepo1.isPinned.value = false
-            hunRepo2.isPinned.value = true
+            hunRepo1.pinnedStatus.value = PinnedStatus.NotPinned
+            hunRepo2.pinnedStatus.value = PinnedStatus.PinnedBySystem
             testScheduler.advanceTimeBy(1.seconds)
             onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
             assertThatTopUnseenKey().isEqualTo(null)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
index 3fd9c21..d38fb50 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -27,7 +27,6 @@
 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.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -48,9 +47,9 @@
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.lockScreenShowOnlyUnseenNotificationsSetting
 import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
-import com.android.systemui.statusbar.policy.headsUpManager
 import com.android.systemui.testKosmos
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.fakeSettings
@@ -155,7 +154,7 @@
         runKeyguardCoordinatorTest {
             kosmos.setTransition(
                 sceneTransition = Idle(Scenes.Gone),
-                stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+                stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
             )
 
             // WHEN: A notification is posted
@@ -170,7 +169,7 @@
             keyguardRepository.setKeyguardShowing(true)
             kosmos.setTransition(
                 sceneTransition = Idle(Scenes.Lockscreen),
-                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD)
+                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD),
             )
 
             // THEN: The notification is recognized as "seen" and is filtered out.
@@ -180,7 +179,7 @@
             keyguardRepository.setKeyguardShowing(false)
             kosmos.setTransition(
                 sceneTransition = Idle(Scenes.Gone),
-                stateTransition = TransitionStep(KeyguardState.AOD, KeyguardState.GONE)
+                stateTransition = TransitionStep(KeyguardState.AOD, KeyguardState.GONE),
             )
 
             // THEN: The notification is shown regardless
@@ -359,14 +358,14 @@
             keyguardRepository.setKeyguardShowing(false)
             kosmos.setTransition(
                 sceneTransition = Idle(Scenes.Gone),
-                stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+                stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
             )
 
             // WHEN: Keyguard is shown again
             keyguardRepository.setKeyguardShowing(true)
             kosmos.setTransition(
                 sceneTransition = Idle(Scenes.Lockscreen),
-                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD)
+                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD),
             )
 
             // THEN: The notification is now recognized as "seen" and is filtered out.
@@ -412,7 +411,7 @@
         runKeyguardCoordinatorTest {
             kosmos.setTransition(
                 sceneTransition = Idle(Scenes.Lockscreen),
-                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
+                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN),
             )
             val firstEntry = NotificationEntryBuilder().setId(1).build()
             collectionListener.onEntryAdded(firstEntry)
@@ -435,14 +434,14 @@
             keyguardRepository.setKeyguardShowing(false)
             kosmos.setTransition(
                 sceneTransition = Idle(Scenes.Gone),
-                stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+                stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
             )
 
             // WHEN: Keyguard is shown again
             keyguardRepository.setKeyguardShowing(true)
             kosmos.setTransition(
                 sceneTransition = Idle(Scenes.Lockscreen),
-                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
+                stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN),
             )
 
             // THEN: The first notification is considered seen and is filtered out.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index 3ad41a5..657fffb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -45,6 +46,8 @@
 import com.android.systemui.communal.shared.model.CommunalScenes;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.BrokenWithSceneContainer;
+import com.android.systemui.flags.DisableSceneContainer;
+import com.android.systemui.flags.EnableSceneContainer;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -67,7 +70,8 @@
 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.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -110,6 +114,7 @@
     @Mock private VisibilityLocationProvider mVisibilityLocationProvider;
     @Mock private VisualStabilityProvider mVisualStabilityProvider;
     @Mock private VisualStabilityCoordinatorLogger mLogger;
+    @Mock private KeyguardStateController mKeyguardStateController;
 
     @Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
@@ -155,6 +160,7 @@
                 mKosmos.getCommunalSceneInteractor(),
                 mKosmos.getShadeInteractor(),
                 mKosmos.getKeyguardTransitionInteractor(),
+                mKeyguardStateController,
                 mLogger);
         mCoordinator.attach(mNotifPipeline);
         mTestScope.getTestScheduler().runCurrent();
@@ -539,6 +545,25 @@
 
     @Test
     @EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
+    @DisableSceneContainer
+    public void testNotLockscreenInGoneTransitionLegacy_invalidationCalled() {
+        // GIVEN visual stability is being maintained b/c animation is playing
+        doReturn(true).when(mKeyguardStateController).isKeyguardFadingAway();
+        mCoordinator.mKeyguardFadeAwayAnimationCallback.onKeyguardFadingAwayChanged();
+
+        assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
+
+        // WHEN the animation has stopped playing
+        doReturn(false).when(mKeyguardStateController).isKeyguardFadingAway();
+        mCoordinator.mKeyguardFadeAwayAnimationCallback.onKeyguardFadingAwayChanged();
+
+        // invalidate is called, b/c we were previously suppressing the pipeline from running
+        verifyStabilityManagerWasInvalidated(times(1));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
+    @EnableSceneContainer
     @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
     public void testNotLockscreenInGoneTransition_invalidationCalled() {
         // GIVEN visual stability is being maintained b/c animation is playing
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index 99bda85..54ce88b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.notification.shared.CallType
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -169,8 +170,12 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.promotedOngoingNotifications)
 
-            val promoted1 = activeNotificationModel(key = "notif1", isPromoted = true)
-            val notPromoted2 = activeNotificationModel(key = "notif2", isPromoted = false)
+            val promoted1 =
+                activeNotificationModel(
+                    key = "notif1",
+                    promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+                )
+            val notPromoted2 = activeNotificationModel(key = "notif2", promotedContent = null)
 
             activeNotificationListRepository.activeNotifications.value =
                 ActiveNotificationsStore.Builder()
@@ -189,9 +194,9 @@
 
             activeNotificationListRepository.activeNotifications.value =
                 ActiveNotificationsStore.Builder()
-                    .apply { activeNotificationModel(key = "notif1", isPromoted = false) }
-                    .apply { activeNotificationModel(key = "notif2", isPromoted = false) }
-                    .apply { activeNotificationModel(key = "notif3", isPromoted = false) }
+                    .apply { activeNotificationModel(key = "notif1", promotedContent = null) }
+                    .apply { activeNotificationModel(key = "notif2", promotedContent = null) }
+                    .apply { activeNotificationModel(key = "notif3", promotedContent = null) }
                     .build()
 
             assertThat(latest!!).isEmpty()
@@ -203,10 +208,18 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.promotedOngoingNotifications)
 
-            val promoted1 = activeNotificationModel(key = "notif1", isPromoted = true)
-            val notPromoted2 = activeNotificationModel(key = "notif2", isPromoted = false)
-            val notPromoted3 = activeNotificationModel(key = "notif3", isPromoted = false)
-            val promoted4 = activeNotificationModel(key = "notif4", isPromoted = true)
+            val promoted1 =
+                activeNotificationModel(
+                    key = "notif1",
+                    promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+                )
+            val notPromoted2 = activeNotificationModel(key = "notif2", promotedContent = null)
+            val notPromoted3 = activeNotificationModel(key = "notif3", promotedContent = null)
+            val promoted4 =
+                activeNotificationModel(
+                    key = "notif4",
+                    promotedContent = PromotedNotificationContentModel.Builder("notif4").build(),
+                )
 
             activeNotificationListRepository.activeNotifications.value =
                 ActiveNotificationsStore.Builder()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index d717fe4..dc0231f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
 import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.testKosmos
@@ -102,7 +103,7 @@
         }
 
     @Test
-    fun hasPinnedRows_rowGetsPinned_true() =
+    fun hasPinnedRows_rowGetsPinnedNormally_true() =
         testScope.runTest {
             val hasPinnedRows by collectLastValue(underTest.hasPinnedRows)
             // GIVEN no rows are pinned
@@ -115,8 +116,30 @@
             headsUpRepository.setNotifications(rows)
             runCurrent()
 
-            // WHEN a row gets pinned
-            rows[0].isPinned.value = true
+            // WHEN a row gets pinned normally
+            rows[0].pinnedStatus.value = PinnedStatus.PinnedBySystem
+            runCurrent()
+
+            // THEN hasPinnedRows updates to true
+            assertThat(hasPinnedRows).isTrue()
+        }
+
+    @Test
+    fun hasPinnedRows_rowGetsPinnedByUser_true() =
+        testScope.runTest {
+            val hasPinnedRows by collectLastValue(underTest.hasPinnedRows)
+            // GIVEN no rows are pinned
+            val rows =
+                arrayListOf(
+                    fakeHeadsUpRowRepository("key 0"),
+                    fakeHeadsUpRowRepository("key 1"),
+                    fakeHeadsUpRowRepository("key 2"),
+                )
+            headsUpRepository.setNotifications(rows)
+            runCurrent()
+
+            // WHEN a row gets pinned due to a chip tap
+            rows[0].pinnedStatus.value = PinnedStatus.PinnedByUser
             runCurrent()
 
             // THEN hasPinnedRows updates to true
@@ -138,7 +161,7 @@
             runCurrent()
 
             // THEN that row gets unpinned
-            rows[0].isPinned.value = false
+            rows[0].pinnedStatus.value = PinnedStatus.NotPinned
             runCurrent()
 
             // THEN hasPinnedRows updates to false
@@ -246,7 +269,7 @@
             runCurrent()
 
             // WHEN all rows gets pinned
-            rows[2].isPinned.value = true
+            rows[2].pinnedStatus.value = PinnedStatus.PinnedBySystem
             runCurrent()
 
             // THEN no rows are filtered
@@ -271,7 +294,7 @@
             assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
 
             // WHEN all rows gets pinned
-            rows[2].isPinned.value = true
+            rows[2].pinnedStatus.value = PinnedStatus.PinnedBySystem
             runCurrent()
 
             // THEN no change
@@ -329,7 +352,7 @@
             runCurrent()
 
             // WHEN a row gets unpinned
-            rows[0].isPinned.value = false
+            rows[0].pinnedStatus.value = PinnedStatus.NotPinned
             runCurrent()
 
             // THEN the unpinned row is filtered
@@ -351,7 +374,7 @@
             runCurrent()
 
             // WHEN a row gets unpinned
-            rows[0].isPinned.value = false
+            rows[0].pinnedStatus.value = PinnedStatus.NotPinned
             runCurrent()
 
             // THEN all rows are still present
@@ -372,15 +395,15 @@
             headsUpRepository.setNotifications(rows)
             runCurrent()
 
-            rows[0].isPinned.value = true
+            rows[0].pinnedStatus.value = PinnedStatus.PinnedBySystem
             runCurrent()
             assertThat(pinnedHeadsUpRows).containsExactly(rows[0])
 
-            rows[0].isPinned.value = false
+            rows[0].pinnedStatus.value = PinnedStatus.NotPinned
             runCurrent()
             assertThat(pinnedHeadsUpRows).isEmpty()
 
-            rows[0].isPinned.value = true
+            rows[0].pinnedStatus.value = PinnedStatus.PinnedBySystem
             runCurrent()
             assertThat(pinnedHeadsUpRows).containsExactly(rows[0])
         }
@@ -485,7 +508,5 @@
         }
 
     private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) =
-        FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
-            this.isPinned.value = isPinned
-        }
+        FakeHeadsUpRowRepository(key = key, isPinned = isPinned)
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 183f901..5d9aa71 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -16,7 +16,6 @@
 package com.android.systemui.statusbar.notification.domain.interactor
 
 import android.app.Notification
-import android.app.Notification.FLAG_PROMOTED_ONGOING
 import android.platform.test.annotations.EnableFlags
 import android.service.notification.StatusBarNotification
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -29,7 +28,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
-import com.android.systemui.statusbar.notification.promoted.promotedNotificationsProvider
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.notification.shared.byKey
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
@@ -48,11 +47,7 @@
     private val notifsRepository = kosmos.activeNotificationListRepository
     private val notifsInteractor = kosmos.activeNotificationsInteractor
     private val underTest =
-        RenderNotificationListInteractor(
-            notifsRepository,
-            sectionStyleProvider = mock(),
-            promotedNotificationsProvider = kosmos.promotedNotificationsProvider,
-        )
+        RenderNotificationListInteractor(notifsRepository, sectionStyleProvider = mock())
 
     @Test
     fun setRenderedList_preservesOrdering() =
@@ -127,12 +122,16 @@
 
     @Test
     @EnableFlags(PromotedNotificationUi.FLAG_NAME)
-    fun setRenderList_setsPromotionStatus() =
+    fun setRenderList_setsPromotionContent() =
         testScope.runTest {
             val actual by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
 
-            val notPromoted1 = mockNotificationEntry("key1", flag = null)
-            val promoted2 = mockNotificationEntry("key2", flag = FLAG_PROMOTED_ONGOING)
+            val notPromoted1 = mockNotificationEntry("key1", promotedContent = null)
+            val promoted2 =
+                mockNotificationEntry(
+                    "key2",
+                    promotedContent = PromotedNotificationContentModel.Builder("key2").build(),
+                )
 
             underTest.setRenderedList(listOf(notPromoted1, promoted2))
 
@@ -140,22 +139,19 @@
 
             val first = actual!![0]
             assertThat(first.key).isEqualTo("key1")
-            assertThat(first.isPromoted).isFalse()
+            assertThat(first.promotedContent).isNull()
 
             val second = actual!![1]
             assertThat(second.key).isEqualTo("key2")
-            assertThat(second.isPromoted).isTrue()
+            assertThat(second.promotedContent).isNotNull()
         }
 
     private fun mockNotificationEntry(
         key: String,
         rank: Int = 0,
-        flag: Int? = null,
+        promotedContent: PromotedNotificationContentModel? = null,
     ): NotificationEntry {
         val nBuilder = Notification.Builder(context, "a")
-        if (flag != null) {
-            nBuilder.setFlag(flag, true)
-        }
         val notification = nBuilder.build()
 
         val mockSbn =
@@ -169,6 +165,7 @@
             whenever(this.representativeEntry).thenReturn(this)
             whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
             whenever(this.sbn).thenReturn(mockSbn)
+            whenever(this.promotedNotificationContentModel).thenReturn(promotedContent)
         }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt
new file mode 100644
index 0000000..31a2bd0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt
@@ -0,0 +1,421 @@
+/*
+ * 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.headsup
+
+import android.app.Notification
+import android.os.Handler
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerTestUtil.createFullScreenIntentEntry
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.phone.keyguardBypassController
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.concurrency.mockExecutorHandler
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.settings.FakeGlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
+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.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationThrottleHun.FLAG_NAME)
+class AvalancheControllerTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    // For creating mocks
+    @get:Rule var rule: MockitoRule = MockitoJUnit.rule()
+    @Mock private val runnableMock: Runnable? = null
+
+    // For creating AvalancheController
+    @Mock private lateinit var dumpManager: DumpManager
+    private lateinit var mAvalancheController: AvalancheController
+
+    // For creating TestableHeadsUpManager
+    @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
+    private val mUiEventLoggerFake = UiEventLoggerFake()
+    @Mock private lateinit var mHeadsUpManagerLogger: HeadsUpManagerLogger
+    @Mock private lateinit var mBgHandler: Handler
+
+    private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
+    private val mGlobalSettings = FakeGlobalSettings()
+    private val mSystemClock = FakeSystemClock()
+    private val mExecutor = FakeExecutor(mSystemClock)
+    private lateinit var testableHeadsUpManager: HeadsUpManagerImpl
+
+    @Before
+    fun setUp() {
+        // Use default non-a11y timeout
+        Mockito.`when`(
+                mAccessibilityMgr!!.getRecommendedTimeoutMillis(
+                    ArgumentMatchers.anyInt(),
+                    ArgumentMatchers.anyInt(),
+                )
+            )
+            .then { i: InvocationOnMock -> i.getArgument(0) }
+
+        // Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
+        // declaration, where mocks are null
+        mAvalancheController =
+            AvalancheController(dumpManager, mUiEventLoggerFake, mHeadsUpManagerLogger, mBgHandler)
+
+        testableHeadsUpManager =
+            HeadsUpManagerImpl(
+                mContext,
+                mLogger,
+                kosmos.statusBarStateController,
+                kosmos.keyguardBypassController,
+                GroupMembershipManagerImpl(),
+                kosmos.visualStabilityProvider,
+                kosmos.configurationController,
+                mockExecutorHandler(mExecutor),
+                mGlobalSettings,
+                mSystemClock,
+                mExecutor,
+                mAccessibilityMgr,
+                mUiEventLoggerFake,
+                JavaAdapter(kosmos.testScope),
+                kosmos.shadeInteractor,
+                mAvalancheController,
+            )
+    }
+
+    private fun createHeadsUpEntry(id: Int): HeadsUpManagerImpl.HeadsUpEntry {
+        return testableHeadsUpManager.createHeadsUpEntry(
+            NotificationEntryBuilder()
+                .setSbn(HeadsUpManagerTestUtil.createSbn(id, Notification.Builder(mContext, "")))
+                .build()
+        )
+    }
+
+    private fun createFsiHeadsUpEntry(id: Int): HeadsUpManagerImpl.HeadsUpEntry {
+        return testableHeadsUpManager.createHeadsUpEntry(createFullScreenIntentEntry(id, mContext))
+    }
+
+    @Test
+    fun testUpdate_isShowing_runsRunnable() {
+        // Entry is showing
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+        // Update
+        mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Runnable was run
+        Mockito.verify(runnableMock, Mockito.times(1)).run()
+    }
+
+    @Test
+    fun testUpdate_noneShowingAndNotNext_showNow() {
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+
+        // None showing
+        mAvalancheController.headsUpEntryShowing = null
+
+        // Entry is NOT next
+        mAvalancheController.clearNext()
+
+        // Update
+        mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Entry is showing now
+        assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(headsUpEntry)
+    }
+
+    @Test
+    fun testUpdate_isNext_addsRunnable() {
+        // Another entry is already showing
+        val otherShowingEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+        // Entry is next
+        val headsUpEntry = createHeadsUpEntry(id = 1)
+        mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+        // Entry has one Runnable
+        val runnableList: List<Runnable?>? = mAvalancheController.nextMap[headsUpEntry]
+        assertThat(runnableList).isNotNull()
+        assertThat(runnableList!!.size).isEqualTo(1)
+
+        // Update
+        mAvalancheController.update(headsUpEntry, runnableMock, "testLabel")
+
+        // Entry has two Runnables
+        assertThat(runnableList.size).isEqualTo(2)
+    }
+
+    @Test
+    fun testUpdate_isNotNextWithOtherHunShowing_isNext() {
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+
+        // Another entry is already showing
+        val otherShowingEntry = createHeadsUpEntry(id = 1)
+        mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+        // Entry is NOT next
+        mAvalancheController.clearNext()
+
+        // Update
+        mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Entry is next
+        assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isTrue()
+    }
+
+    @Test
+    fun testDelete_untracked_runnableRuns() {
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+
+        // None showing
+        mAvalancheController.headsUpEntryShowing = null
+
+        // Nothing is next
+        mAvalancheController.clearNext()
+
+        // Delete
+        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Runnable was run
+        Mockito.verify(runnableMock, Mockito.times(1)).run()
+    }
+
+    @Test
+    fun testDelete_isNext_removedFromNext_runnableNotRun() {
+        // Entry is next
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+        // Delete
+        mAvalancheController.delete(headsUpEntry, runnableMock, "testLabel")
+
+        // Entry was removed from next
+        assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isFalse()
+
+        // Runnable was not run
+        Mockito.verify(runnableMock, Mockito.times(0)).run()
+    }
+
+    @Test
+    fun testDelete_wasDropped_removedFromDropSet() {
+        // Entry was dropped
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.debugDropSet.add(headsUpEntry)
+
+        // Delete
+        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Entry was removed from dropSet
+        assertThat(mAvalancheController.debugDropSet.contains(headsUpEntry)).isFalse()
+    }
+
+    @Test
+    fun testDelete_wasDropped_runnableNotRun() {
+        // Entry was dropped
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.debugDropSet.add(headsUpEntry)
+
+        // Delete
+        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Runnable was not run
+        Mockito.verify(runnableMock, Mockito.times(0)).run()
+    }
+
+    @Test
+    fun testDelete_isShowing_runnableRun() {
+        // Entry is showing
+        val headsUpEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+        // Delete
+        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+        // Runnable was run
+        Mockito.verify(runnableMock, Mockito.times(1)).run()
+    }
+
+    @Test
+    fun testDelete_isShowing_showNext() {
+        // 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")
+
+        // Next entry is shown
+        assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry)
+    }
+
+    @Test
+    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
+
+        // Delete
+        mAvalancheController.delete(showingEntry, runnableMock!!, "testLabel")
+
+        // Next entry is shown
+        assertThat(mAvalancheController.previousHunKey).isEqualTo("")
+    }
+
+    @Test
+    fun testGetDurationMs_untrackedEntryEmptyAvalanche_useAutoDismissTime() {
+        val givenEntry = createHeadsUpEntry(id = 0)
+
+        // Nothing is showing
+        mAvalancheController.headsUpEntryShowing = null
+
+        // Nothing is next
+        mAvalancheController.clearNext()
+
+        val durationMs = mAvalancheController.getDurationMs(givenEntry, autoDismissMs = 5000)
+        assertThat(durationMs).isEqualTo(5000)
+    }
+
+    @Test
+    fun testGetDurationMs_untrackedEntryNonEmptyAvalanche_useAutoDismissTime() {
+        val givenEntry = createHeadsUpEntry(id = 0)
+
+        // Given entry not tracked
+        mAvalancheController.headsUpEntryShowing = createHeadsUpEntry(id = 1)
+
+        mAvalancheController.clearNext()
+        val nextEntry = createHeadsUpEntry(id = 2)
+        mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+        val durationMs = mAvalancheController.getDurationMs(givenEntry, autoDismissMs = 5000)
+        assertThat(durationMs).isEqualTo(5000)
+    }
+
+    @Test
+    fun testGetDurationMs_lastEntry_useAutoDismissTime() {
+        // Entry is showing
+        val showingEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = showingEntry
+
+        // Nothing is next
+        mAvalancheController.clearNext()
+
+        val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
+        assertThat(durationMs).isEqualTo(5000)
+    }
+
+    @Test
+    fun testGetDurationMs_nextEntryLowerPriority_5000() {
+        // Entry is showing
+        val showingEntry = createFsiHeadsUpEntry(id = 1)
+        mAvalancheController.headsUpEntryShowing = showingEntry
+
+        // There's another entry waiting to show next
+        val nextEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+        // Next entry has lower priority
+        assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(1)
+
+        val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
+        assertThat(durationMs).isEqualTo(5000)
+    }
+
+    @Test
+    fun testGetDurationMs_nextEntrySamePriority_1000() {
+        // 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!!)
+
+        // Same priority
+        assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(0)
+
+        val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
+        assertThat(durationMs).isEqualTo(1000)
+    }
+
+    @Test
+    fun testGetDurationMs_nextEntryHigherPriority_500() {
+        // Entry is showing
+        val showingEntry = createHeadsUpEntry(id = 0)
+        mAvalancheController.headsUpEntryShowing = showingEntry
+
+        // There's another entry waiting to show next
+        val nextEntry = createFsiHeadsUpEntry(id = 1)
+        mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+        // Next entry has higher priority
+        assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(-1)
+
+        val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
+        assertThat(durationMs).isEqualTo(500)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplOldTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplOldTest.kt
new file mode 100644
index 0000000..d2a7c61
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplOldTest.kt
@@ -0,0 +1,705 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.headsup
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.Person
+import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.KosmosJavaAdapter
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl.HeadsUpEntry
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.concurrency.mockExecutorHandler
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.settings.FakeGlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.eq
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@SmallTest
+@RunWithLooper
+@RunWith(ParameterizedAndroidJunit4::class)
+// TODO(b/378142453): Merge this with HeadsUpManagerImplTest.
+open class HeadsUpManagerImplOldTest(flags: FlagsParameterization?) : SysuiTestCase() {
+    protected var mKosmos: KosmosJavaAdapter = KosmosJavaAdapter(this)
+
+    @JvmField @Rule var rule: MockitoRule = MockitoJUnit.rule()
+
+    private val mUiEventLoggerFake = UiEventLoggerFake()
+
+    private val mLogger: HeadsUpManagerLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
+
+    @Mock private val mBgHandler: Handler? = null
+
+    @Mock private val dumpManager: DumpManager? = null
+
+    @Mock private val mShadeInteractor: ShadeInteractor? = null
+    private var mAvalancheController: AvalancheController? = null
+
+    @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
+
+    protected val mGlobalSettings: FakeGlobalSettings = FakeGlobalSettings()
+    protected val mSystemClock: FakeSystemClock = FakeSystemClock()
+    protected val mExecutor: FakeExecutor = FakeExecutor(mSystemClock)
+
+    @Mock protected var mRow: ExpandableNotificationRow? = null
+
+    private fun createHeadsUpManager(): HeadsUpManagerImpl {
+        return HeadsUpManagerImpl(
+            mContext,
+            mLogger,
+            mKosmos.statusBarStateController,
+            mKosmos.keyguardBypassController,
+            GroupMembershipManagerImpl(),
+            mKosmos.visualStabilityProvider,
+            mKosmos.configurationController,
+            mockExecutorHandler(mExecutor),
+            mGlobalSettings,
+            mSystemClock,
+            mExecutor,
+            mAccessibilityMgr,
+            mUiEventLoggerFake,
+            JavaAdapter(mKosmos.testScope),
+            mShadeInteractor,
+            mAvalancheController,
+        )
+    }
+
+    private fun createStickyEntry(id: Int): NotificationEntry {
+        val notif =
+            Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setFullScreenIntent(
+                    Mockito.mock(PendingIntent::class.java), /* highPriority */
+                    true,
+                )
+                .build()
+        return HeadsUpManagerTestUtil.createEntry(id, notif)
+    }
+
+    private fun createStickyForSomeTimeEntry(id: Int): NotificationEntry {
+        val notif =
+            Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setFlag(Notification.FLAG_FSI_REQUESTED_BUT_DENIED, true)
+                .build()
+        return HeadsUpManagerTestUtil.createEntry(id, notif)
+    }
+
+    private fun useAccessibilityTimeout(use: Boolean) {
+        if (use) {
+            Mockito.doReturn(TEST_A11Y_AUTO_DISMISS_TIME)
+                .`when`(mAccessibilityMgr!!)
+                .getRecommendedTimeoutMillis(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
+        } else {
+            Mockito.`when`(
+                    mAccessibilityMgr!!.getRecommendedTimeoutMillis(
+                        ArgumentMatchers.anyInt(),
+                        ArgumentMatchers.anyInt(),
+                    )
+                )
+                .then { i: InvocationOnMock -> i.getArgument(0) }
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags!!)
+    }
+
+    @Throws(Exception::class)
+    override fun SysuiSetup() {
+        super.SysuiSetup()
+        mContext.getOrCreateTestableResources().apply {
+            this.addOverride(R.integer.ambient_notification_extension_time, TEST_EXTENSION_TIME)
+            this.addOverride(R.integer.touch_acceptance_delay, TEST_TOUCH_ACCEPTANCE_TIME)
+            this.addOverride(
+                R.integer.heads_up_notification_minimum_time,
+                TEST_MINIMUM_DISPLAY_TIME,
+            )
+            this.addOverride(
+                R.integer.heads_up_notification_minimum_time_with_throttling,
+                TEST_MINIMUM_DISPLAY_TIME,
+            )
+            this.addOverride(R.integer.heads_up_notification_decay, TEST_AUTO_DISMISS_TIME)
+            this.addOverride(
+                R.integer.sticky_heads_up_notification_time,
+                TEST_STICKY_AUTO_DISMISS_TIME,
+            )
+        }
+
+        mAvalancheController =
+            AvalancheController(dumpManager!!, mUiEventLoggerFake, mLogger, mBgHandler!!)
+        Mockito.`when`(mShadeInteractor!!.isAnyExpanded).thenReturn(MutableStateFlow(true))
+        Mockito.`when`(mKosmos.keyguardBypassController.bypassEnabled).thenReturn(false)
+    }
+
+    @Test
+    fun testHasNotifications_headsUpManagerMapNotEmpty_true() {
+        val bhum = createHeadsUpManager()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        bhum.showNotification(entry)
+
+        Truth.assertThat(bhum.mHeadsUpEntryMap).isNotEmpty()
+        Truth.assertThat(bhum.hasNotifications()).isTrue()
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testHasNotifications_avalancheMapNotEmpty_true() {
+        val bhum = createHeadsUpManager()
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        val headsUpEntry = bhum.createHeadsUpEntry(notifEntry)
+        mAvalancheController!!.addToNext(headsUpEntry) {}
+
+        Truth.assertThat(mAvalancheController!!.getWaitingEntryList()).isNotEmpty()
+        Truth.assertThat(bhum.hasNotifications()).isTrue()
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testHasNotifications_false() {
+        val bhum = createHeadsUpManager()
+        Truth.assertThat(bhum.mHeadsUpEntryMap).isEmpty()
+        Truth.assertThat(mAvalancheController!!.getWaitingEntryList()).isEmpty()
+        Truth.assertThat(bhum.hasNotifications()).isFalse()
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testGetHeadsUpEntryList_includesAvalancheEntryList() {
+        val bhum = createHeadsUpManager()
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        val headsUpEntry = bhum.createHeadsUpEntry(notifEntry)
+        mAvalancheController!!.addToNext(headsUpEntry) {}
+
+        Truth.assertThat(bhum.headsUpEntryList).contains(headsUpEntry)
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testGetHeadsUpEntry_returnsAvalancheEntry() {
+        val bhum = createHeadsUpManager()
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        val headsUpEntry = bhum.createHeadsUpEntry(notifEntry)
+        mAvalancheController!!.addToNext(headsUpEntry) {}
+
+        Truth.assertThat(bhum.getHeadsUpEntry(notifEntry.key)).isEqualTo(headsUpEntry)
+    }
+
+    @Test
+    fun testShowNotification_addsEntry() {
+        val alm = createHeadsUpManager()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        alm.showNotification(entry)
+
+        assertThat(alm.isHeadsUpEntry(entry.key)).isTrue()
+        assertThat(alm.hasNotifications()).isTrue()
+        assertThat(alm.getEntry(entry.key)).isEqualTo(entry)
+    }
+
+    @Test
+    fun testShowNotification_autoDismisses() {
+        val alm = createHeadsUpManager()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        alm.showNotification(entry)
+        mSystemClock.advanceTime((TEST_AUTO_DISMISS_TIME * 3 / 2).toLong())
+
+        assertThat(alm.isHeadsUpEntry(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testRemoveNotification_removeDeferred() {
+        val alm = createHeadsUpManager()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        alm.showNotification(entry)
+
+        val removedImmediately =
+            alm.removeNotification(entry.key, /* releaseImmediately= */ false, "removeDeferred")
+        assertThat(removedImmediately).isFalse()
+        assertThat(alm.isHeadsUpEntry(entry.key)).isTrue()
+    }
+
+    @Test
+    fun testRemoveNotification_forceRemove() {
+        val alm = createHeadsUpManager()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        alm.showNotification(entry)
+
+        val removedImmediately =
+            alm.removeNotification(entry.key, /* releaseImmediately= */ true, "forceRemove")
+        assertThat(removedImmediately).isTrue()
+        assertThat(alm.isHeadsUpEntry(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testReleaseAllImmediately() {
+        val alm = createHeadsUpManager()
+        for (i in 0 until TEST_NUM_NOTIFICATIONS) {
+            val entry = HeadsUpManagerTestUtil.createEntry(i, mContext)
+            entry.row = mRow
+            alm.showNotification(entry)
+        }
+
+        alm.releaseAllImmediately()
+
+        assertThat(alm.allEntries.count()).isEqualTo(0)
+    }
+
+    @Test
+    fun testCanRemoveImmediately_notShownLongEnough() {
+        val alm = createHeadsUpManager()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        alm.showNotification(entry)
+
+        // The entry has just been added so we should not remove immediately.
+        assertThat(alm.canRemoveImmediately(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testHunRemovedLogging() {
+        val hum = createHeadsUpManager()
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        val headsUpEntry = Mockito.mock(HeadsUpEntry::class.java)
+        Mockito.`when`(headsUpEntry.pinnedStatus)
+            .thenReturn(MutableStateFlow(PinnedStatus.NotPinned))
+        headsUpEntry.mEntry = notifEntry
+
+        hum.onEntryRemoved(headsUpEntry, "test")
+
+        Mockito.verify(mLogger, Mockito.times(1)).logNotificationActuallyRemoved(eq(notifEntry))
+    }
+
+    @Test
+    fun testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
+        val hum = createHeadsUpManager()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        useAccessibilityTimeout(false)
+
+        hum.showNotification(entry)
+        mSystemClock.advanceTime((TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME).toLong())
+
+        assertThat(hum.isHeadsUpEntry(entry.key)).isTrue()
+    }
+
+    @Test
+    fun testShowNotification_autoDismissesWithDefaultTimeout() {
+        val hum = createHeadsUpManager()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        useAccessibilityTimeout(false)
+
+        hum.showNotification(entry)
+        mSystemClock.advanceTime(
+            (TEST_TOUCH_ACCEPTANCE_TIME +
+                    (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2)
+                .toLong()
+        )
+
+        assertThat(hum.isHeadsUpEntry(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() {
+        val hum = createHeadsUpManager()
+        val entry = createStickyForSomeTimeEntry(/* id= */ 0)
+        useAccessibilityTimeout(false)
+
+        hum.showNotification(entry)
+        mSystemClock.advanceTime(
+            (TEST_TOUCH_ACCEPTANCE_TIME +
+                    (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2)
+                .toLong()
+        )
+
+        assertThat(hum.isHeadsUpEntry(entry.key)).isTrue()
+    }
+
+    @Test
+    fun testShowNotification_sticky_neverAutoDismisses() {
+        val hum = createHeadsUpManager()
+        val entry = createStickyEntry(/* id= */ 0)
+        useAccessibilityTimeout(false)
+
+        hum.showNotification(entry)
+        mSystemClock.advanceTime(
+            (TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME).toLong()
+        )
+
+        assertThat(hum.isHeadsUpEntry(entry.key)).isTrue()
+    }
+
+    @Test
+    fun testShowNotification_autoDismissesWithAccessibilityTimeout() {
+        val hum = createHeadsUpManager()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        useAccessibilityTimeout(true)
+
+        hum.showNotification(entry)
+        mSystemClock.advanceTime(
+            (TEST_TOUCH_ACCEPTANCE_TIME +
+                    (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2)
+                .toLong()
+        )
+
+        assertThat(hum.isHeadsUpEntry(entry.key)).isTrue()
+    }
+
+    @Test
+    fun testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() {
+        val hum = createHeadsUpManager()
+        val entry = createStickyForSomeTimeEntry(/* id= */ 0)
+        useAccessibilityTimeout(true)
+
+        hum.showNotification(entry)
+        mSystemClock.advanceTime(
+            (TEST_TOUCH_ACCEPTANCE_TIME +
+                    (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2)
+                .toLong()
+        )
+
+        assertThat(hum.isHeadsUpEntry(entry.key)).isTrue()
+    }
+
+    @Test
+    fun testRemoveNotification_beforeMinimumDisplayTime() {
+        val hum = createHeadsUpManager()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        useAccessibilityTimeout(false)
+
+        hum.showNotification(entry)
+
+        val removedImmediately =
+            hum.removeNotification(
+                entry.key,
+                /* releaseImmediately = */ false,
+                "beforeMinimumDisplayTime",
+            )
+        assertThat(removedImmediately).isFalse()
+        assertThat(hum.isHeadsUpEntry(entry.key)).isTrue()
+
+        mSystemClock.advanceTime(
+            ((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2).toLong()
+        )
+
+        assertThat(hum.isHeadsUpEntry(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testRemoveNotification_afterMinimumDisplayTime() {
+        val hum = createHeadsUpManager()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        useAccessibilityTimeout(false)
+
+        hum.showNotification(entry)
+        mSystemClock.advanceTime(
+            ((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2).toLong()
+        )
+
+        assertThat(hum.isHeadsUpEntry(entry.key)).isTrue()
+
+        val removedImmediately =
+            hum.removeNotification(
+                entry.key,
+                /* releaseImmediately = */ false,
+                "afterMinimumDisplayTime",
+            )
+        assertThat(removedImmediately).isTrue()
+        assertThat(hum.isHeadsUpEntry(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testRemoveNotification_releaseImmediately() {
+        val hum = createHeadsUpManager()
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        hum.showNotification(entry)
+
+        val removedImmediately =
+            hum.removeNotification(
+                entry.key,
+                /* releaseImmediately = */ true,
+                "afterMinimumDisplayTime",
+            )
+        assertThat(removedImmediately).isTrue()
+        assertThat(hum.isHeadsUpEntry(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testIsSticky_rowPinnedAndExpanded_true() {
+        val hum = createHeadsUpManager()
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        Mockito.`when`(mRow!!.isPinned).thenReturn(true)
+        notifEntry.row = mRow
+
+        hum.showNotification(notifEntry)
+
+        val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key)
+        headsUpEntry!!.setExpanded(true)
+
+        assertThat(hum.isSticky(notifEntry.key)).isTrue()
+    }
+
+    @Test
+    fun testIsSticky_remoteInputActive_true() {
+        val hum = createHeadsUpManager()
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        hum.showNotification(notifEntry)
+
+        val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key)
+        headsUpEntry!!.mRemoteInputActive = true
+
+        assertThat(hum.isSticky(notifEntry.key)).isTrue()
+    }
+
+    @Test
+    fun testIsSticky_hasFullScreenIntent_true() {
+        val hum = createHeadsUpManager()
+        val notifEntry = HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+
+        hum.showNotification(notifEntry)
+
+        assertThat(hum.isSticky(notifEntry.key)).isTrue()
+    }
+
+    @Test
+    fun testIsSticky_stickyForSomeTime_false() {
+        val hum = createHeadsUpManager()
+        val entry = createStickyForSomeTimeEntry(/* id= */ 0)
+
+        hum.showNotification(entry)
+
+        assertThat(hum.isSticky(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testIsSticky_false() {
+        val hum = createHeadsUpManager()
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        hum.showNotification(notifEntry)
+
+        val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key)
+        headsUpEntry!!.setExpanded(false)
+        headsUpEntry.mRemoteInputActive = false
+
+        assertThat(hum.isSticky(notifEntry.key)).isFalse()
+    }
+
+    @Test
+    fun testCompareTo_withNullEntries() {
+        val hum = createHeadsUpManager()
+        val alertEntry = NotificationEntryBuilder().setTag("alert").build()
+
+        hum.showNotification(alertEntry)
+
+        assertThat(hum.compare(alertEntry, null)).isLessThan(0)
+        assertThat(hum.compare(null, alertEntry)).isGreaterThan(0)
+        assertThat(hum.compare(null, null)).isEqualTo(0)
+    }
+
+    @Test
+    fun testCompareTo_withNonAlertEntries() {
+        val hum = createHeadsUpManager()
+
+        val nonAlertEntry1 = NotificationEntryBuilder().setTag("nae1").build()
+        val nonAlertEntry2 = NotificationEntryBuilder().setTag("nae2").build()
+        val alertEntry = NotificationEntryBuilder().setTag("alert").build()
+        hum.showNotification(alertEntry)
+
+        assertThat(hum.compare(alertEntry, nonAlertEntry1)).isLessThan(0)
+        assertThat(hum.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0)
+        assertThat(hum.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0)
+    }
+
+    @Test
+    fun testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
+        val hum = createHeadsUpManager()
+
+        val ongoingCall =
+            hum.HeadsUpEntry(
+                NotificationEntryBuilder()
+                    .setSbn(
+                        HeadsUpManagerTestUtil.createSbn(
+                            /* id = */ 0,
+                            Notification.Builder(mContext, "")
+                                .setCategory(Notification.CATEGORY_CALL)
+                                .setOngoing(true),
+                        )
+                    )
+                    .build()
+            )
+
+        val activeRemoteInput =
+            hum.HeadsUpEntry(HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext))
+        activeRemoteInput.mRemoteInputActive = true
+
+        assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0)
+        assertThat(activeRemoteInput.compareTo(ongoingCall)).isGreaterThan(0)
+    }
+
+    @Test
+    fun testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() {
+        val hum = createHeadsUpManager()
+
+        val person = Person.Builder().setName("person").build()
+        val intent = Mockito.mock(PendingIntent::class.java)
+        val incomingCall =
+            hum.HeadsUpEntry(
+                NotificationEntryBuilder()
+                    .setSbn(
+                        HeadsUpManagerTestUtil.createSbn(
+                            /* id = */ 0,
+                            Notification.Builder(mContext, "")
+                                .setStyle(
+                                    Notification.CallStyle.forIncomingCall(person, intent, intent)
+                                ),
+                        )
+                    )
+                    .build()
+            )
+
+        val activeRemoteInput =
+            hum.HeadsUpEntry(HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext))
+        activeRemoteInput.mRemoteInputActive = true
+
+        assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0)
+        assertThat(activeRemoteInput.compareTo(incomingCall)).isGreaterThan(0)
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testPinEntry_logsPeek_throttleEnabled() {
+        val hum = createHeadsUpManager()
+
+        // Needs full screen intent in order to be pinned
+        val entryToPin =
+            hum.HeadsUpEntry(
+                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+            )
+
+        // Note: the standard way to show a notification would be calling showNotification rather
+        // than onAlertEntryAdded. However, in practice showNotification in effect adds
+        // the notification and then updates it; in order to not log twice, the entry needs
+        // to have a functional ExpandableNotificationRow that can keep track of whether it's
+        // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
+        hum.onEntryAdded(entryToPin)
+
+        assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(2)
+        assertThat(
+            AvalancheController.ThrottleEvent.AVALANCHE_THROTTLING_HUN_SHOWN.getId(),
+        ).isEqualTo(mUiEventLoggerFake.eventId(0))
+        assertThat(
+            HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.id,
+        ).isEqualTo(mUiEventLoggerFake.eventId(1))
+    }
+
+    @Test
+    @DisableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testPinEntry_logsPeek_throttleDisabled() {
+        val hum = createHeadsUpManager()
+
+        // Needs full screen intent in order to be pinned
+        val entryToPin =
+            hum.HeadsUpEntry(
+                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+            )
+
+        // Note: the standard way to show a notification would be calling showNotification rather
+        // than onAlertEntryAdded. However, in practice showNotification in effect adds
+        // the notification and then updates it; in order to not log twice, the entry needs
+        // to have a functional ExpandableNotificationRow that can keep track of whether it's
+        // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
+        hum.onEntryAdded(entryToPin)
+
+        assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1)
+        assertThat(
+            HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.id,
+        ).isEqualTo(mUiEventLoggerFake.eventId(0))
+    }
+
+    @Test
+    fun testSetUserActionMayIndirectlyRemove() {
+        val hum = createHeadsUpManager()
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        hum.showNotification(notifEntry)
+
+        assertThat(hum.canRemoveImmediately(notifEntry.key)).isFalse()
+
+        hum.setUserActionMayIndirectlyRemove(notifEntry)
+
+        assertThat(hum.canRemoveImmediately(notifEntry.key)).isTrue()
+    }
+
+    companion object {
+        const val TEST_TOUCH_ACCEPTANCE_TIME: Int = 200
+        const val TEST_A11Y_AUTO_DISMISS_TIME: Int = 1000
+        const val TEST_EXTENSION_TIME = 500
+
+        const val TEST_MINIMUM_DISPLAY_TIME: Int = 400
+        const val TEST_AUTO_DISMISS_TIME: Int = 600
+        const val TEST_STICKY_AUTO_DISMISS_TIME: Int = 800
+
+        // Number of notifications to use in tests requiring multiple notifications
+        private const val TEST_NUM_NOTIFICATIONS = 4
+
+        init {
+            Truth.assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME)
+            Truth.assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME)
+            Truth.assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME)
+        }
+
+        @get:Parameters(name = "{0}")
+        @JvmStatic
+        val flags: List<FlagsParameterization>
+            get() = FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
new file mode 100644
index 0000000..96f0201
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.headsup
+
+import android.os.Handler
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
+import android.testing.TestableLooper.RunWithLooper
+import android.view.accessibility.accessibilityManagerWrapper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLoggerFake
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.phone.keyguardBypassController
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.sysuiStatusBarStateController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.mockExecutorHandler
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+@RunWithLooper
+class HeadsUpManagerImplTest(flags: FlagsParameterization) : HeadsUpManagerImplOldTest(flags) {
+
+    private val mHeadsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer())
+
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val testScope = kosmos.testScope
+
+    private val mGroupManager = mock<GroupMembershipManager>()
+    private val mBgHandler = mock<Handler>()
+
+    val statusBarStateController = kosmos.sysuiStatusBarStateController
+    private val mJavaAdapter: JavaAdapter = JavaAdapter(testScope.backgroundScope)
+
+    private lateinit var mAvalancheController: AvalancheController
+    private lateinit var underTest: HeadsUpManagerImpl
+
+    @Before
+    fun setUp() {
+        mContext.getOrCreateTestableResources().apply {
+            this.addOverride(R.integer.ambient_notification_extension_time, TEST_EXTENSION_TIME)
+            this.addOverride(R.integer.touch_acceptance_delay, TEST_TOUCH_ACCEPTANCE_TIME)
+            this.addOverride(
+                R.integer.heads_up_notification_minimum_time,
+                TEST_MINIMUM_DISPLAY_TIME,
+            )
+            this.addOverride(
+                R.integer.heads_up_notification_minimum_time_with_throttling,
+                TEST_MINIMUM_DISPLAY_TIME,
+            )
+            this.addOverride(R.integer.heads_up_notification_decay, TEST_AUTO_DISMISS_TIME)
+            this.addOverride(
+                R.integer.sticky_heads_up_notification_time,
+                TEST_STICKY_AUTO_DISMISS_TIME,
+            )
+        }
+
+        whenever(kosmos.keyguardBypassController.bypassEnabled).thenReturn(false)
+        kosmos.visualStabilityProvider.isReorderingAllowed = true
+        mAvalancheController =
+            AvalancheController(
+                kosmos.dumpManager,
+                kosmos.uiEventLoggerFake,
+                mHeadsUpManagerLogger,
+                mBgHandler,
+            )
+        underTest =
+            HeadsUpManagerImpl(
+                mContext,
+                mHeadsUpManagerLogger,
+                statusBarStateController,
+                kosmos.keyguardBypassController,
+                mGroupManager,
+                kosmos.visualStabilityProvider,
+                kosmos.configurationController,
+                mockExecutorHandler(mExecutor),
+                mGlobalSettings,
+                mSystemClock,
+                mExecutor,
+                kosmos.accessibilityManagerWrapper,
+                kosmos.uiEventLoggerFake,
+                mJavaAdapter,
+                kosmos.shadeInteractor,
+                mAvalancheController,
+            )
+    }
+
+    @Test
+    fun testSnooze() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        underTest.showNotification(entry)
+        underTest.snooze()
+        Assert.assertTrue(underTest.isSnoozed(entry.sbn.packageName))
+    }
+
+    @Test
+    fun testSwipedOutNotification() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        underTest.showNotification(entry)
+        underTest.addSwipedOutNotification(entry.key)
+
+        // Remove should succeed because the notification is swiped out
+        val removedImmediately =
+            underTest.removeNotification(
+                entry.key,
+                /* releaseImmediately= */ false,
+                /* reason= */ "swipe out",
+            )
+        Assert.assertTrue(removedImmediately)
+        Assert.assertFalse(underTest.isHeadsUpEntry(entry.key))
+    }
+
+    @Test
+    fun testCanRemoveImmediately_swipedOut() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        underTest.showNotification(entry)
+        underTest.addSwipedOutNotification(entry.key)
+
+        // Notification is swiped so it can be immediately removed.
+        Assert.assertTrue(underTest.canRemoveImmediately(entry.key))
+    }
+
+    @Ignore("b/141538055")
+    @Test
+    fun testCanRemoveImmediately_notTopEntry() {
+        val earlierEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        val laterEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext)
+        laterEntry.row = mRow
+        underTest.showNotification(earlierEntry)
+        underTest.showNotification(laterEntry)
+
+        // Notification is "behind" a higher priority notification so we can remove it immediately.
+        Assert.assertTrue(underTest.canRemoveImmediately(earlierEntry.key))
+    }
+
+    @Test
+    fun testExtendHeadsUp() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        underTest.showNotification(entry)
+        underTest.extendHeadsUp()
+        mSystemClock.advanceTime(((TEST_AUTO_DISMISS_TIME + TEST_EXTENSION_TIME) / 2).toLong())
+        Assert.assertTrue(underTest.isHeadsUpEntry(entry.key))
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testShowNotification_removeWhenReorderingAllowedTrue() {
+        kosmos.visualStabilityProvider.isReorderingAllowed = true
+
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        underTest.showNotification(notifEntry)
+        assertThat(underTest.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue()
+    }
+
+    class TestAnimationStateHandler : AnimationStateHandler {
+        override fun setHeadsUpGoingAwayAnimationsAllowed(allowed: Boolean) {}
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testReorderingAllowed_clearsListOfEntriesToRemove() {
+        kosmos.visualStabilityProvider.isReorderingAllowed = true
+
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        underTest.showNotification(notifEntry)
+        assertThat(underTest.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue()
+
+        underTest.setAnimationStateHandler(TestAnimationStateHandler())
+        underTest.mOnReorderingAllowedListener.onReorderingAllowed()
+        assertThat(underTest.mEntriesToRemoveWhenReorderingAllowed.isEmpty()).isTrue()
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testShowNotification_reorderNotAllowed_seenInShadeTrue() {
+        kosmos.visualStabilityProvider.isReorderingAllowed = false
+
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        underTest.showNotification(notifEntry)
+        assertThat(notifEntry.isSeenInShade).isTrue()
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testShowNotification_reorderAllowed_seenInShadeFalse() {
+        kosmos.visualStabilityProvider.isReorderingAllowed = true
+
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        underTest.showNotification(notifEntry)
+        assertThat(notifEntry.isSeenInShade).isFalse()
+    }
+
+    @Test
+    fun testShouldHeadsUpBecomePinned_noFSI_false() =
+        kosmos.runTest {
+            statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+            Assert.assertFalse(underTest.shouldHeadsUpBecomePinned(entry))
+        }
+
+    @Test
+    fun testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() =
+        kosmos.runTest {
+            statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+            val notifEntry =
+                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+
+            // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
+            underTest.showNotification(notifEntry)
+
+            val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key)
+            headsUpEntry!!.mWasUnpinned = false
+
+            Assert.assertTrue(underTest.shouldHeadsUpBecomePinned(notifEntry))
+        }
+
+    @Test
+    fun testShouldHeadsUpBecomePinned_wasUnpinned_false() =
+        kosmos.runTest {
+            statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+            val notifEntry =
+                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+
+            // Add notifEntry to ANM mAlertEntries map and make it unpinned
+            underTest.showNotification(notifEntry)
+
+            val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key)
+            headsUpEntry!!.mWasUnpinned = true
+
+            Assert.assertFalse(underTest.shouldHeadsUpBecomePinned(notifEntry))
+        }
+
+    @Test
+    fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() =
+        kosmos.runTest {
+            // GIVEN
+            shadeTestUtil.setShadeExpansion(0f)
+            // TODO(b/381869885): Determine why we need both of these ShadeTestUtil calls.
+            shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(false)
+
+            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+            statusBarStateController.setState(StatusBarState.SHADE)
+
+            // THEN
+            Assert.assertTrue(underTest.shouldHeadsUpBecomePinned(entry))
+        }
+
+    @Test
+    fun shouldHeadsUpBecomePinned_shadeLocked_false() =
+        kosmos.runTest {
+            // GIVEN
+            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+            statusBarStateController.setState(StatusBarState.SHADE_LOCKED)
+
+            // THEN
+            Assert.assertFalse(underTest.shouldHeadsUpBecomePinned(entry))
+        }
+
+    @Test
+    fun shouldHeadsUpBecomePinned_shadeUnknown_false() =
+        kosmos.runTest {
+            // GIVEN
+            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+            statusBarStateController.setState(1207)
+
+            // THEN
+            Assert.assertFalse(underTest.shouldHeadsUpBecomePinned(entry))
+        }
+
+    @Test
+    fun shouldHeadsUpBecomePinned_keyguardWithBypassOn_true() =
+        kosmos.runTest {
+            // GIVEN
+            whenever(keyguardBypassController.bypassEnabled).thenReturn(true)
+
+            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+            statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+            // THEN
+            Assert.assertTrue(underTest.shouldHeadsUpBecomePinned(entry))
+        }
+
+    @Test
+    fun shouldHeadsUpBecomePinned_keyguardWithBypassOff_false() =
+        kosmos.runTest {
+            // GIVEN
+            whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
+
+            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+            statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+            // THEN
+            Assert.assertFalse(underTest.shouldHeadsUpBecomePinned(entry))
+        }
+
+    @Test
+    fun shouldHeadsUpBecomePinned_shadeExpanded_false() =
+        kosmos.runTest {
+            // GIVEN
+            shadeTestUtil.setShadeExpansion(1f)
+            // TODO(b/381869885): Determine why we need both of these ShadeTestUtil calls.
+            shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(true)
+
+            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+            statusBarStateController.setState(StatusBarState.SHADE)
+
+            // THEN
+            Assert.assertFalse(underTest.shouldHeadsUpBecomePinned(entry))
+        }
+
+    companion object {
+        @get:Parameters(name = "{0}")
+        val flags: List<FlagsParameterization>
+            get() = buildList {
+                addAll(
+                    FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME)
+                        .andSceneContainer()
+                )
+            }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerTestUtil.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerTestUtil.java
new file mode 100644
index 0000000..684ce59
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerTestUtil.java
@@ -0,0 +1,83 @@
+/*
+ * 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.headsup;
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+/**
+ * Test helper class for HeadsUpEntry creation.
+ */
+public class HeadsUpManagerTestUtil {
+
+    private static final String TEST_PACKAGE_NAME = "HeadsUpManagerTestUtil";
+    private static final int TEST_UID = 0;
+
+    protected static StatusBarNotification createSbn(int id, Notification.Builder n) {
+        return createSbn(id, n.build());
+    }
+
+    protected static StatusBarNotification createSbn(int id, Context context) {
+        final Notification.Builder b = new Notification.Builder(context, "")
+                .setSmallIcon(com.android.systemui.res.R.drawable.ic_person)
+                .setContentTitle("Title")
+                .setContentText("Text");
+        return createSbn(id, b);
+    }
+
+    protected static StatusBarNotification createSbn(int id, Notification n) {
+        return new StatusBarNotification(
+                TEST_PACKAGE_NAME /* pkg */,
+                TEST_PACKAGE_NAME,
+                id,
+                null /* tag */,
+                TEST_UID,
+                0 /* initialPid */,
+                n,
+                new UserHandle(ActivityManager.getCurrentUser()),
+                null /* overrideGroupKey */,
+                0 /* postTime */);
+    }
+
+    protected static NotificationEntry createEntry(int id, Notification n) {
+        return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
+    }
+
+    protected static NotificationEntry createEntry(int id, Context context) {
+        return new NotificationEntryBuilder().setSbn(
+                HeadsUpManagerTestUtil.createSbn(id, context)).build();
+    }
+
+    protected static NotificationEntry createFullScreenIntentEntry(int id, Context context) {
+        final PendingIntent intent = PendingIntent.getActivity(
+                context, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
+
+        final Notification notif = new Notification.Builder(context, "")
+                .setSmallIcon(com.android.systemui.res.R.drawable.ic_person)
+                .setFullScreenIntent(intent, /* highPriority */ true)
+                .build();
+        return HeadsUpManagerTestUtil.createEntry(id, notif);
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStoreTest.kt
new file mode 100644
index 0000000..483c2be
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStoreTest.kt
@@ -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.systemui.statusbar.notification.icon.ui.viewbinder
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.domain.interactor.displayWindowPropertiesInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.statusbar.RankingBuilder
+import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifCollection
+import com.android.systemui.statusbar.notification.collection.notifPipeline
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.icon.iconManager
+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
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class ConnectedDisplaysStatusBarNotificationIconViewStoreTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private val underTest =
+        ConnectedDisplaysStatusBarNotificationIconViewStore(
+            TEST_DISPLAY_ID,
+            kosmos.notifCollection,
+            kosmos.iconManager,
+            kosmos.displayWindowPropertiesInteractor,
+            kosmos.notifPipeline,
+        )
+
+    private val notifCollectionListeners = mutableListOf<NotifCollectionListener>()
+
+    @Before
+    fun setupNoticCollectionListener() {
+        whenever(kosmos.notifPipeline.addCollectionListener(any())).thenAnswer { invocation ->
+            notifCollectionListeners.add(invocation.arguments[0] as NotifCollectionListener)
+        }
+    }
+
+    @Before
+    fun activate() {
+        underTest.activateIn(kosmos.testScope)
+    }
+
+    @Test
+    fun iconView_unknownKey_returnsNull() =
+        kosmos.testScope.runTest {
+            val unknownKey = "unknown key"
+
+            assertThat(underTest.iconView(unknownKey)).isNull()
+        }
+
+    @Test
+    fun iconView_knownKey_returnsNonNull() =
+        kosmos.testScope.runTest {
+            val entry = createEntry()
+
+            whenever(kosmos.notifCollection.getEntry(entry.key)).thenReturn(entry)
+
+            assertThat(underTest.iconView(entry.key)).isNotNull()
+        }
+
+    @Test
+    fun iconView_knownKey_calledMultipleTimes_returnsSameInstance() =
+        kosmos.testScope.runTest {
+            val entry = createEntry()
+
+            whenever(kosmos.notifCollection.getEntry(entry.key)).thenReturn(entry)
+
+            val first = underTest.iconView(entry.key)
+            val second = underTest.iconView(entry.key)
+
+            assertThat(first).isSameInstanceAs(second)
+        }
+
+    @Test
+    fun iconView_knownKey_afterNotificationRemoved_returnsNewInstance() =
+        kosmos.testScope.runTest {
+            val entry = createEntry()
+
+            whenever(kosmos.notifCollection.getEntry(entry.key)).thenReturn(entry)
+
+            val first = underTest.iconView(entry.key)
+
+            notifCollectionListeners.forEach { it.onEntryRemoved(entry, /* reason= */ 0) }
+
+            val second = underTest.iconView(entry.key)
+
+            assertThat(first).isNotSameInstanceAs(second)
+        }
+
+    private fun createEntry(): NotificationEntry {
+        val channelId = "channelId"
+        val notificationChannel =
+            NotificationChannel(channelId, "name", NotificationManager.IMPORTANCE_DEFAULT)
+        val notification =
+            Notification.Builder(context, channelId)
+                .setContentTitle("Title")
+                .setContentText("Content text")
+                .setSmallIcon(com.android.systemui.res.R.drawable.icon)
+                .build()
+        val statusBarNotification = SbnBuilder().setNotification(notification).build()
+        val ranking =
+            RankingBuilder()
+                .setChannel(notificationChannel)
+                .setKey(statusBarNotification.key)
+                .build()
+        return NotificationEntry(
+            /* sbn = */ statusBarNotification,
+            /* ranking = */ ranking,
+            /* creationTime = */ 1234L,
+        )
+    }
+
+    private companion object {
+        const val TEST_DISPLAY_ID = 1234
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 284efc7..d3bde84 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -70,13 +70,13 @@
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN
 import com.android.systemui.statusbar.policy.FakeDeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.util.FakeEventLog
 import com.android.systemui.util.settings.FakeGlobalSettings
 import com.android.systemui.util.settings.FakeSettings
@@ -109,7 +109,7 @@
 
                     override fun isTagLoggable(tagName: String, level: LogLevel): Boolean = true
                 },
-            systrace = false
+            systrace = false,
         )
 
     private val leakCheck = LeakCheckedTest.SysuiLeakCheck()
@@ -162,10 +162,10 @@
     @Before
     fun setUp() {
         val userId = ActivityManager.getCurrentUser()
-        val user = UserInfo(userId, "Current user", /* flags = */ 0)
+        val user = UserInfo(userId, "Current user", /* flags= */ 0)
 
         deviceProvisionedController.currentUser = userId
-        userTracker.set(listOf(user), /* currentUserIndex = */ 0)
+        userTracker.set(listOf(user), /* currentUserIndex= */ 0)
         systemSettings = FakeSettings()
         whenever(bubbles.canShowBubbleNotification()).thenReturn(true)
         whenever(settingsInteractor.isCooldownEnabled).thenReturn(MutableStateFlow(true))
@@ -491,7 +491,7 @@
 
     private fun withPeekAndPulseEntry(
         extendEntry: EntryBuilder.() -> Unit,
-        block: (NotificationEntry) -> Unit
+        block: (NotificationEntry) -> Unit,
     ) {
         ensurePeekState()
         block(buildPeekEntry(extendEntry))
@@ -540,10 +540,10 @@
     @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
     fun testShouldNotHeadsUp_silentNotification() {
         withPeekAndPulseEntry({
-                                  isGrouped = false
-                                  isGroupSummary = false
-                                  isSilent = true
-                              }) {
+            isGrouped = false
+            isGroupSummary = false
+            isSilent = true
+        }) {
             assertShouldNotHeadsUp(it)
             assertNoEventsLogged()
         }
@@ -553,10 +553,10 @@
     @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
     fun testShouldHeadsUp_silentNotificationFalse() {
         withPeekAndPulseEntry({
-                                  isGrouped = false
-                                  isGroupSummary = false
-                                  isSilent = false
-                              }) {
+            isGrouped = false
+            isGroupSummary = false
+            isSilent = false
+        }) {
             assertShouldHeadsUp(it)
             assertNoEventsLogged()
         }
@@ -697,7 +697,7 @@
         forEachFsiState {
             assertShouldNotFsi(
                 buildFsiEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_FULL_SCREEN_INTENT },
-                expectWouldInterruptWithoutDnd = true
+                expectWouldInterruptWithoutDnd = true,
             )
             assertNoEventsLogged()
         }
@@ -719,7 +719,7 @@
                     suppressedVisualEffects = SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
                     importance = IMPORTANCE_DEFAULT
                 },
-                expectWouldInterruptWithoutDnd = false
+                expectWouldInterruptWithoutDnd = false,
             )
             assertNoEventsLogged()
         }
@@ -754,7 +754,7 @@
         assertUiEventLogged(
             FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
             entry.sbn.uid,
-            entry.sbn.packageName
+            entry.sbn.packageName,
         )
         assertSystemEventLogged("231322873", entry.sbn.uid, "groupAlertBehavior")
     }
@@ -813,7 +813,7 @@
         assertUiEventLogged(
             FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA,
             entry.sbn.uid,
-            entry.sbn.packageName
+            entry.sbn.packageName,
         )
         assertSystemEventLogged("274759612", entry.sbn.uid, "bubbleMetadata")
     }
@@ -960,7 +960,7 @@
         var keyguardIsShowing: Boolean = false,
         var keyguardIsOccluded: Boolean = false,
         var deviceProvisioned: Boolean = true,
-        var currentUserSetup: Boolean = true
+        var currentUserSetup: Boolean = true,
     )
 
     protected fun setState(state: State): Unit =
@@ -1131,7 +1131,7 @@
 
     protected fun withLegacySuppressor(
         suppressor: NotificationInterruptSuppressor,
-        block: () -> Unit
+        block: () -> Unit,
     ) {
         provider.addLegacySuppressor(suppressor)
         block()
@@ -1166,7 +1166,7 @@
 
     protected fun assertShouldNotFsi(
         entry: NotificationEntry,
-        expectWouldInterruptWithoutDnd: Boolean? = null
+        expectWouldInterruptWithoutDnd: Boolean? = null,
     ) =
         provider.makeUnloggedFullScreenIntentDecision(entry).let {
             provider.logFullScreenIntentDecision(it)
@@ -1175,7 +1175,7 @@
                 assertEquals(
                     "unexpected wouldInterruptWithoutDnd for FSI: ${it.logReason}",
                     expectWouldInterruptWithoutDnd,
-                    it.wouldInterruptWithoutDnd
+                    it.wouldInterruptWithoutDnd,
                 )
             }
         }
@@ -1227,9 +1227,9 @@
                             context,
                             /* requestCode = */ 0,
                             Intent().setPackage(context.packageName),
-                            FLAG_MUTABLE
+                            FLAG_MUTABLE,
                         ),
-                        Icon.createWithResource(context.resources, R.drawable.android)
+                        Icon.createWithResource(context.resources, R.drawable.android),
                     )
                 }
 
@@ -1272,7 +1272,7 @@
                     }
 
                     if (hasFsi) {
-                        nb.setFullScreenIntent(mock(), /* highPriority = */ true)
+                        nb.setFullScreenIntent(mock(), /* highPriority= */ true)
                     }
                 }
                 .build()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
index 7fd9c9f..ff8ef18 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
@@ -27,9 +27,9 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.EventLog
 import com.android.systemui.util.settings.GlobalSettings
@@ -63,7 +63,7 @@
         bubbles: Optional<Bubbles>,
         context: Context,
         notificationManager: NotificationManager,
-        settingsInteractor: NotificationSettingsInteractor
+        settingsInteractor: NotificationSettingsInteractor,
     ): VisualInterruptionDecisionProvider {
         return if (VisualInterruptionRefactor.isEnabled) {
             VisualInterruptionDecisionProviderImpl(
@@ -88,7 +88,7 @@
                 bubbles,
                 context,
                 notificationManager,
-                settingsInteractor
+                settingsInteractor,
             )
         } else {
             NotificationInterruptStateProviderWrapper(
@@ -109,7 +109,7 @@
                     systemClock,
                     globalSettings,
                     eventLog,
-                    bubbles
+                    bubbles,
                 )
             )
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt
new file mode 100644
index 0000000..6736ccf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import android.app.Notification
+import android.app.Notification.BigPictureStyle
+import android.app.Notification.BigTextStyle
+import android.app.Notification.CallStyle
+import android.app.Notification.MessagingStyle
+import android.app.Notification.ProgressStyle
+import android.app.Notification.ProgressStyle.Segment
+import android.app.PendingIntent
+import android.app.Person
+import android.content.Intent
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PromotedNotificationContentExtractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    private val provider =
+        FakePromotedNotificationsProvider().also { kosmos.promotedNotificationsProvider = it }
+
+    private val underTest = kosmos.promotedNotificationContentExtractor
+
+    @Test
+    @DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+    fun shouldNotExtract_bothFlagsDisabled() {
+        val notif = createEntry().also { provider.promotedEntries.add(it) }
+        val content = extractContent(notif)
+        assertThat(content).isNull()
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+    @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun shouldExtract_promotedNotificationUiFlagEnabled() {
+        val entry = createEntry().also { provider.promotedEntries.add(it) }
+        val content = extractContent(entry)
+        assertThat(content).isNotNull()
+    }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    @DisableFlags(PromotedNotificationUi.FLAG_NAME)
+    fun shouldExtract_statusBarNotifChipsFlagEnabled() {
+        val entry = createEntry().also { provider.promotedEntries.add(it) }
+        val content = extractContent(entry)
+        assertThat(content).isNotNull()
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+    fun shouldExtract_bothFlagsEnabled() {
+        val entry = createEntry().also { provider.promotedEntries.add(it) }
+        val content = extractContent(entry)
+        assertThat(content).isNotNull()
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+    fun shouldNotExtract_providerDidNotPromote() {
+        val entry = createEntry().also { provider.promotedEntries.remove(it) }
+        val content = extractContent(entry)
+        assertThat(content).isNull()
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+    fun extractContent_commonFields() {
+        val entry =
+            createEntry {
+                    setSubText(TEST_SUB_TEXT)
+                    setContentTitle(TEST_CONTENT_TITLE)
+                    setContentText(TEST_CONTENT_TEXT)
+                }
+                .also { provider.promotedEntries.add(it) }
+
+        val content = extractContent(entry)
+
+        assertThat(content).isNotNull()
+        assertThat(content?.subText).isEqualTo(TEST_SUB_TEXT)
+        assertThat(content?.title).isEqualTo(TEST_CONTENT_TITLE)
+        assertThat(content?.text).isEqualTo(TEST_CONTENT_TEXT)
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+    fun extractContent_fromBigPictureStyle() {
+        val entry =
+            createEntry { setStyle(BigPictureStyle()) }.also { provider.promotedEntries.add(it) }
+
+        val content = extractContent(entry)
+
+        assertThat(content).isNotNull()
+        assertThat(content?.style).isEqualTo(Style.BigPicture)
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+    fun extractContent_fromBigTextStyle() {
+        val entry =
+            createEntry { setStyle(BigTextStyle()) }.also { provider.promotedEntries.add(it) }
+
+        val content = extractContent(entry)
+
+        assertThat(content).isNotNull()
+        assertThat(content?.style).isEqualTo(Style.BigText)
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+    fun extractContent_fromCallStyle() {
+        val hangUpIntent =
+            PendingIntent.getBroadcast(context, 0, Intent("hangup"), PendingIntent.FLAG_IMMUTABLE)
+
+        val entry =
+            createEntry { setStyle(CallStyle.forOngoingCall(TEST_PERSON, hangUpIntent)) }
+                .also { provider.promotedEntries.add(it) }
+
+        val content = extractContent(entry)
+
+        assertThat(content).isNotNull()
+        assertThat(content?.style).isEqualTo(Style.Call)
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+    fun extractContent_fromProgressStyle() {
+        val entry =
+            createEntry {
+                    setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75))
+                }
+                .also { provider.promotedEntries.add(it) }
+
+        val content = extractContent(entry)
+
+        assertThat(content).isNotNull()
+        assertThat(content?.style).isEqualTo(Style.Progress)
+        assertThat(content?.progress).isNotNull()
+        assertThat(content?.progress?.progress).isEqualTo(75)
+        assertThat(content?.progress?.progressMax).isEqualTo(100)
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+    fun extractContent_fromIneligibleStyle() {
+        val entry =
+            createEntry {
+                    setStyle(
+                        MessagingStyle(TEST_PERSON).addMessage("message text", 0L, TEST_PERSON)
+                    )
+                }
+                .also { provider.promotedEntries.add(it) }
+
+        val content = extractContent(entry)
+
+        assertThat(content).isNotNull()
+        assertThat(content?.style).isEqualTo(Style.Ineligible)
+    }
+
+    private fun extractContent(entry: NotificationEntry): PromotedNotificationContentModel? {
+        val recoveredBuilder = Notification.Builder(context, entry.sbn.notification)
+        return underTest.extractContent(entry, recoveredBuilder)
+    }
+
+    private fun createEntry(builderBlock: Notification.Builder.() -> Unit = {}): NotificationEntry {
+        val notif = Notification.Builder(context, "a").also(builderBlock).build()
+        return NotificationEntryBuilder().setNotification(notif).build()
+    }
+
+    companion object {
+        private const val TEST_SUB_TEXT = "sub text"
+        private const val TEST_CONTENT_TITLE = "content title"
+        private const val TEST_CONTENT_TEXT = "content text"
+
+        private const val TEST_PERSON_NAME = "person name"
+        private const val TEST_PERSON_KEY = "person key"
+        private val TEST_PERSON =
+            Person.Builder().setKey(TEST_PERSON_KEY).setName(TEST_PERSON_NAME).build()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index 657e9df..ca0f9ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.statusbar.notification.collection.render.FakeNodeController
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
@@ -49,7 +50,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger
 import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.SmartReplyConstants
 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent
 import com.android.systemui.util.mockito.any
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index 1c5f37c..979a1d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -40,8 +40,9 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -91,7 +92,7 @@
         ExpandableNotificationRowDragController controller = createSpyController();
         mRow.setDragController(controller);
         mRow.setHeadsUp(true);
-        mRow.setPinned(true);
+        mRow.setPinnedStatus(PinnedStatus.PinnedBySystem);
 
         mRow.doLongClickCallback(0, 0);
         mRow.doDragCallback(0, 0);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index b278f1a..6eb2764 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -30,6 +30,7 @@
 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.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -42,6 +43,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.TypedValue;
@@ -56,8 +58,12 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.media.controls.util.MediaFeatureFlag;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -99,6 +105,7 @@
     @Mock private NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;
     @Mock private HeadsUpStyleProvider mHeadsUpStyleProvider;
     @Mock private NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
+    @Mock private PromotedNotificationContentExtractor mPromotedNotificationContentExtractor;
 
     private final SmartReplyStateInflater mSmartReplyStateInflater =
             new SmartReplyStateInflater() {
@@ -142,6 +149,7 @@
                 mSmartReplyStateInflater,
                 mNotifLayoutInflaterFactoryProvider,
                 mHeadsUpStyleProvider,
+                mPromotedNotificationContentExtractor,
                 mock(NotificationRowContentBinderLogger.class));
     }
 
@@ -382,6 +390,75 @@
         verify(mRow, times(0)).onNotificationUpdated();
     }
 
+    @Test
+    @DisableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
+    public void testExtractsPromotedContent_notWhenBothFlagsDisabled() throws Exception {
+        final PromotedNotificationContentModel content =
+                new PromotedNotificationContentModel.Builder("key").build();
+        when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
+                .thenReturn(content);
+
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
+
+        verify(mPromotedNotificationContentExtractor, never()).extractContent(any(), any());
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+    @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+    public void testExtractsPromotedContent_whenPromotedNotificationUiFlagEnabled()
+            throws Exception {
+        final PromotedNotificationContentModel content =
+                new PromotedNotificationContentModel.Builder("key").build();
+        when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
+                .thenReturn(content);
+
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
+
+        verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+        assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
+    }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    @DisableFlags(PromotedNotificationUi.FLAG_NAME)
+    public void testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() throws Exception {
+        final PromotedNotificationContentModel content =
+                new PromotedNotificationContentModel.Builder("key").build();
+        when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
+                .thenReturn(content);
+
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
+
+        verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+        assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
+    }
+
+    @Test
+    @EnableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
+    public void testExtractsPromotedContent_whenBothFlagsEnabled() throws Exception {
+        final PromotedNotificationContentModel content =
+                new PromotedNotificationContentModel.Builder("key").build();
+        when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
+                .thenReturn(content);
+
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
+
+        verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+        assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
+    }
+
+    @Test
+    @EnableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
+    public void testExtractsPromotedContent_null() throws Exception {
+        when(mPromotedNotificationContentExtractor.extractContent(any(), any())).thenReturn(null);
+
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
+
+        verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+        assertNull(mRow.getEntry().getPromotedNotificationContentModel());
+    }
+
     private static void inflateAndWait(NotificationContentInflater inflater,
             @InflationFlag int contentToInflate,
             ExpandableNotificationRow row)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
index b16d3ea..6a0a5bb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
@@ -71,10 +71,10 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.testKosmos
 import com.android.systemui.util.kotlin.JavaAdapter
 import com.android.systemui.wmshell.BubblesManager
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 48608eb..1851799 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -21,6 +21,7 @@
 import android.os.AsyncTask
 import android.os.Build
 import android.os.CancellationSignal
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper.RunWithLooper
 import android.util.TypedValue
@@ -33,8 +34,12 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
@@ -62,6 +67,7 @@
 import org.mockito.kotlin.any
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
 import org.mockito.kotlin.spy
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
@@ -82,7 +88,7 @@
         object : NotifLayoutInflaterFactory.Provider {
             override fun provide(
                 row: ExpandableNotificationRow,
-                layoutType: Int
+                layoutType: Int,
             ): NotifLayoutInflaterFactory = mock()
         }
     private val smartReplyStateInflater: SmartReplyStateInflater =
@@ -95,7 +101,7 @@
                 notifPackageContext: Context,
                 entry: NotificationEntry,
                 existingSmartReplyState: InflatedSmartReplyState?,
-                newSmartReplyState: InflatedSmartReplyState
+                newSmartReplyState: InflatedSmartReplyState,
             ): InflatedSmartReplyViewHolder {
                 return inflatedSmartReplies
             }
@@ -104,6 +110,7 @@
                 return inflatedSmartReplyState
             }
         }
+    private val promotedNotificationContentExtractor: PromotedNotificationContentExtractor = mock()
 
     @Before
     fun setUp() {
@@ -125,7 +132,8 @@
                 smartReplyStateInflater,
                 layoutInflaterFactoryProvider,
                 mock<HeadsUpStyleProvider>(),
-                mock()
+                promotedNotificationContentExtractor,
+                mock(),
             )
     }
 
@@ -142,7 +150,8 @@
             FLAG_CONTENT_VIEW_ALL,
             builder,
             mContext,
-            smartReplyStateInflater
+            smartReplyStateInflater,
+            mock(),
         )
         verify(builder).createHeadsUpContentView(true)
     }
@@ -160,7 +169,8 @@
             FLAG_CONTENT_VIEW_ALL,
             builder,
             mContext,
-            smartReplyStateInflater
+            smartReplyStateInflater,
+            mock(),
         )
         verify(builder).createContentView(true)
     }
@@ -187,7 +197,7 @@
             true /* expectingException */,
             notificationInflater,
             FLAG_CONTENT_VIEW_ALL,
-            row
+            row,
         )
         Assert.assertTrue(row.privateLayout.childCount == 0)
         verify(row, times(0)).onNotificationUpdated()
@@ -210,7 +220,7 @@
             FLAG_CONTENT_VIEW_ALL,
             BindParams(),
             false /* forceInflate */,
-            null /* callback */
+            null, /* callback */
         )
         Assert.assertNull(row.entry.runningTask)
     }
@@ -223,7 +233,8 @@
             NotificationRowContentBinderImpl.InflationProgress(
                 packageContext = mContext,
                 remoteViews = NewRemoteViews(),
-                contentModel = NotificationContentModel(headsUpStatusBarModel)
+                contentModel = NotificationContentModel(headsUpStatusBarModel),
+                extractedPromotedNotificationContentModel = null,
             )
         val countDownLatch = CountDownLatch(1)
         NotificationRowContentBinderImpl.applyRemoteView(
@@ -261,7 +272,7 @@
                         get() =
                             AsyncFailRemoteView(
                                 mContext.packageName,
-                                com.android.systemui.tests.R.layout.custom_view_dark
+                                com.android.systemui.tests.R.layout.custom_view_dark,
                             )
                 },
             logger = mock(),
@@ -280,7 +291,7 @@
         val decoratedMediaView = builder.createContentView()
         Assert.assertFalse(
             "The decorated media style doesn't allow a view to be reapplied!",
-            NotificationRowContentBinderImpl.canReapplyRemoteView(mediaView, decoratedMediaView)
+            NotificationRowContentBinderImpl.canReapplyRemoteView(mediaView, decoratedMediaView),
         )
     }
 
@@ -304,7 +315,7 @@
         Assert.assertEquals(
             "Binder inflated a new view even though the old one was cached and usable.",
             view,
-            row.privateLayout.contractedChild
+            row.privateLayout.contractedChild,
         )
     }
 
@@ -327,7 +338,7 @@
         Assert.assertNotEquals(
             "Binder (somehow) used the same view when inflating.",
             view,
-            row.privateLayout.contractedChild
+            row.privateLayout.contractedChild,
         )
     }
 
@@ -396,7 +407,7 @@
     private fun getValidationError(
         measuredHeightDp: Float,
         targetSdk: Int,
-        contentView: RemoteViews?
+        contentView: RemoteViews?,
     ): String? {
         val view: View = mock()
         whenever(view.measuredHeight)
@@ -404,7 +415,7 @@
                 TypedValue.applyDimension(
                         COMPLEX_UNIT_SP,
                         measuredHeightDp,
-                        mContext.resources.displayMetrics
+                        mContext.resources.displayMetrics,
                     )
                     .toInt()
             )
@@ -419,7 +430,7 @@
         row.entry.sbn.notification.contentView =
             RemoteViews(
                 mContext.packageName,
-                com.android.systemui.tests.R.layout.invalid_notification_height
+                com.android.systemui.tests.R.layout.invalid_notification_height,
             )
         inflateAndWait(true, notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
         Assert.assertEquals(0, row.privateLayout.childCount.toLong())
@@ -451,7 +462,7 @@
             false,
             notificationInflater,
             FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
-            messagingRow
+            messagingRow,
         )
         Assert.assertNotNull(messagingRow.publicLayout.mSingleLineView)
         // assert this is the conversation layout
@@ -460,6 +471,59 @@
         )
     }
 
+    @Test
+    @DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+    fun testExtractsPromotedContent_notWhenBothFlagsDisabled() {
+        val content = PromotedNotificationContentModel.Builder("key").build()
+        whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
+            .thenReturn(content)
+
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
+
+        verify(promotedNotificationContentExtractor, never()).extractContent(any(), any())
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+    @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun testExtractsPromotedContent_whenPromotedNotificationUiFlagEnabled() {
+        val content = PromotedNotificationContentModel.Builder("key").build()
+        whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
+            .thenReturn(content)
+
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
+
+        verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any())
+        Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
+    }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    @DisableFlags(PromotedNotificationUi.FLAG_NAME)
+    fun testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() {
+        val content = PromotedNotificationContentModel.Builder("key").build()
+        whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
+            .thenReturn(content)
+
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
+
+        verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any())
+        Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+    fun testExtractsPromotedContent_whenBothFlagsEnabled() {
+        val content = PromotedNotificationContentModel.Builder("key").build()
+        whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
+            .thenReturn(content)
+
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
+
+        verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any())
+        Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
+    }
+
     private class ExceptionHolder {
         var exception: Exception? = null
     }
@@ -476,7 +540,7 @@
             parent: ViewGroup,
             executor: Executor,
             listener: OnViewAppliedListener,
-            handler: InteractionHandler?
+            handler: InteractionHandler?,
         ): CancellationSignal {
             executor.execute { listener.onError(RuntimeException("Failed to inflate async")) }
             return CancellationSignal()
@@ -486,7 +550,7 @@
             context: Context,
             parent: ViewGroup,
             executor: Executor,
-            listener: OnViewAppliedListener
+            listener: OnViewAppliedListener,
         ): CancellationSignal {
             return applyAsync(context, parent, executor, listener, null)
         }
@@ -496,7 +560,7 @@
         private fun inflateAndWait(
             inflater: NotificationRowContentBinderImpl,
             @InflationFlag contentToInflate: Int,
-            row: ExpandableNotificationRow
+            row: ExpandableNotificationRow,
         ) {
             inflateAndWait(false /* expectingException */, inflater, contentToInflate, row)
         }
@@ -535,7 +599,7 @@
                 contentToInflate,
                 BindParams(),
                 false /* forceInflate */,
-                callback /* callback */
+                callback, /* callback */
             )
             Assert.assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS))
             exceptionHolder.exception?.let { throw it }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 2340d02..080ac3f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -82,13 +82,14 @@
 import com.android.systemui.statusbar.notification.icon.IconBuilder;
 import com.android.systemui.statusbar.notification.icon.IconManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
@@ -204,6 +205,7 @@
                                 new MockSmartReplyInflater(),
                                 mock(NotifLayoutInflaterFactory.Provider.class),
                                 mock(HeadsUpStyleProvider.class),
+                                mock(PromotedNotificationContentExtractor.class),
                                 mock(NotificationRowContentBinderLogger.class))
                         : new NotificationContentInflater(
                                 mock(NotifRemoteViewCache.class),
@@ -214,6 +216,7 @@
                                 new MockSmartReplyInflater(),
                                 mock(NotifLayoutInflaterFactory.Provider.class),
                                 mock(HeadsUpStyleProvider.class),
+                                mock(PromotedNotificationContentExtractor.class),
                                 mock(NotificationRowContentBinderLogger.class));
         contentBinder.setInflateSynchronously(true);
         mBindStage = new RowContentBindStage(contentBinder,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 07935e4..92b8c3a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -24,8 +24,8 @@
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.headsup.AvalancheController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.policy.AvalancheController
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index b8745b3..2eb4590 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -33,7 +33,6 @@
 import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.render.MediaContainerController;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -57,7 +56,6 @@
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private ConfigurationController mConfigurationController;
     @Mock private KeyguardMediaController mKeyguardMediaController;
-    @Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
     @Mock private MediaContainerController mMediaContainerController;
     @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
     @Mock private SectionHeaderController mIncomingHeaderController;
@@ -73,26 +71,10 @@
 
     @Before
     public void setUp() {
-        when(mSectionsFeatureManager.getNumberOfBuckets()).thenAnswer(
-                invocation -> {
-                    int count = 2;
-                    if (mSectionsFeatureManager.isFilteringEnabled()) {
-                        count = 5;
-                    }
-                    if (mSectionsFeatureManager.isMediaControlsEnabled()) {
-                        if (!mSectionsFeatureManager.isFilteringEnabled()) {
-                            count = 5;
-                        } else {
-                            count += 1;
-                        }
-                    }
-                    return count;
-                });
         mSectionsManager =
                 new NotificationSectionsManager(
                         mConfigurationController,
                         mKeyguardMediaController,
-                        mSectionsFeatureManager,
                         mMediaContainerController,
                         mNotificationRoundnessManager,
                         mIncomingHeaderController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 6425da4..de40abb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -83,7 +83,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -107,7 +107,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
 import com.android.systemui.statusbar.policy.ZenModeController;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index b877456..dcac294 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -3,7 +3,6 @@
 import android.annotation.DimenRes
 import android.content.pm.PackageManager
 import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
@@ -27,11 +26,10 @@
 import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView.FooterViewState
+import com.android.systemui.statusbar.notification.headsup.AvalancheController
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.policy.AvalancheController
 import com.google.common.truth.Expect
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -154,22 +152,6 @@
 
     @Test
     @DisableSceneContainer
-    fun resetViewStates_defaultHun_yTranslationIsInset() {
-        whenever(notificationRow.isPinned).thenReturn(true)
-        whenever(notificationRow.isHeadsUp).thenReturn(true)
-        resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
-    }
-
-    @Test
-    @DisableSceneContainer
-    fun resetViewStates_defaultHunWithStackMargin_changesHunYTranslation() {
-        whenever(notificationRow.isPinned).thenReturn(true)
-        whenever(notificationRow.isHeadsUp).thenReturn(true)
-        resetViewStates_stackMargin_changesHunYTranslation()
-    }
-
-    @Test
-    @DisableSceneContainer
     fun resetViewStates_defaultHunWhenShadeIsOpening_yTranslationIsInset() {
         whenever(notificationRow.isPinned).thenReturn(true)
         whenever(notificationRow.isHeadsUp).thenReturn(true)
@@ -183,24 +165,7 @@
 
     @Test
     @DisableSceneContainer
-    @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
-    fun resetViewStates_hunAnimatingAway_yTranslationIsInset() {
-        whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
-        resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
-    }
-
-    @Test
-    @DisableSceneContainer
-    @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
-    fun resetViewStates_hunAnimatingAway_StackMarginChangesHunYTranslation() {
-        whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
-        resetViewStates_stackMargin_changesHunYTranslation()
-    }
-
-    @Test
-    @DisableSceneContainer
-    @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
-    fun resetViewStates_defaultHun_newHeadsUpAnim_yTranslationIsInset() {
+    fun resetViewStates_defaultHun_yTranslationIsInset() {
         whenever(notificationRow.isPinned).thenReturn(true)
         whenever(notificationRow.isHeadsUp).thenReturn(true)
         resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
@@ -208,8 +173,7 @@
 
     @Test
     @DisableSceneContainer
-    @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
-    fun resetViewStates_defaultHunWithStackMargin_newHeadsUpAnim_changesHunYTranslation() {
+    fun resetViewStates_defaultHunWithStackMargin_changesHunYTranslation() {
         whenever(notificationRow.isPinned).thenReturn(true)
         whenever(notificationRow.isHeadsUp).thenReturn(true)
         resetViewStates_stackMargin_changesHunYTranslation()
@@ -399,8 +363,7 @@
 
     @Test
     @DisableSceneContainer
-    @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
-    fun resetViewStates_defaultHun_showingQS_newHeadsUpAnim_hunTranslatedToMax() {
+    fun resetViewStates_defaultHun_showingQS_hunTranslatedToMax() {
         // Given: the shade is open and scrolled to the bottom to show the QuickSettings
         val maxHunTranslation = 2000f
         ambientState.maxHeadsUpTranslation = maxHunTranslation
@@ -416,8 +379,7 @@
 
     @Test
     @DisableSceneContainer
-    @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
-    fun resetViewStates_hunAnimatingAway_showingQS_newHeadsUpAnim_hunTranslatedToBottomOfScreen() {
+    fun resetViewStates_hunAnimatingAway_showingQS_hunTranslatedToBottomOfScreen() {
         // Given: the shade is open and scrolled to the bottom to show the QuickSettings
         val bottomOfScreen = 2600f
         val maxHunTranslation = 2000f
@@ -437,8 +399,7 @@
     }
 
     @Test
-    @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
-    fun resetViewStates_hunAnimatingAway_newHeadsUpAnim_hunTranslatedToTopOfScreen() {
+    fun resetViewStates_hunAnimatingAway_hunTranslatedToTopOfScreen() {
         val topMargin = 100f
         ambientState.maxHeadsUpTranslation = 2000f
         ambientState.stackTopMargin = topMargin.toInt()
@@ -461,7 +422,6 @@
 
     @Test
     @DisableSceneContainer
-    @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun resetViewStates_hunAnimatingAwayWhileDozing_yTranslationIsInset() {
         whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
 
@@ -472,7 +432,6 @@
 
     @Test
     @DisableSceneContainer
-    @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun resetViewStates_hunAnimatingAwayWhileDozing_hasStackMargin_changesHunYTranslation() {
         whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
 
@@ -494,18 +453,6 @@
     }
 
     @Test
-    @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
-    fun resetViewStates_hunsOverlappingAndBottomHunAnimatingAway_bottomHunClipped() {
-        val topHun = mockExpandableNotificationRow()
-        val bottomHun = mockExpandableNotificationRow()
-        whenever(topHun.isHeadsUp).thenReturn(true)
-        whenever(topHun.isPinned).thenReturn(true)
-        whenever(bottomHun.isHeadsUpAnimatingAway).thenReturn(true)
-
-        resetViewStates_hunsOverlapping_bottomHunClipped(topHun, bottomHun)
-    }
-
-    @Test
     @EnableSceneContainer
     fun resetViewStates_emptyShadeView_isCenteredVertically_withSceneContainer() {
         stackScrollAlgorithm.initView(context)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
index 798465e..e4dd29a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification.stack
 
-import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -24,7 +23,6 @@
 import com.android.systemui.animation.AnimatorTestRule
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR
@@ -60,11 +58,12 @@
     private val viewState: ExpandableViewState =
         ExpandableViewState().apply { height = VIEW_HEIGHT }
     private val runnableCaptor: ArgumentCaptor<Runnable> = argumentCaptor()
+
     @Before
     fun setUp() {
         overrideResource(
             R.dimen.go_to_full_shade_appearing_translation,
-            FULL_SHADE_APPEAR_TRANSLATION
+            FULL_SHADE_APPEAR_TRANSLATION,
         )
         overrideResource(R.dimen.heads_up_appear_y_above_screen, HEADS_UP_ABOVE_SCREEN)
 
@@ -74,7 +73,6 @@
     }
 
     @Test
-    @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun startAnimationForEvents_headsUpFromTop_startsHeadsUpAppearAnim() {
         val topMargin = 50f
         val expectedStartY = -topMargin - stackStateAnimator.mHeadsUpAppearStartAboveScreen
@@ -90,12 +88,11 @@
                 /* delay= */ 0L,
                 /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
                 /* isHeadsUpAppear= */ true,
-                /* onEndRunnable= */ null
+                /* onEndRunnable= */ null,
             )
     }
 
     @Test
-    @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun startAnimationForEvents_headsUpFromBottom_startsHeadsUpAppearAnim() {
         val screenHeight = 2000f
         val expectedStartY = screenHeight + stackStateAnimator.mHeadsUpAppearStartAboveScreen
@@ -114,12 +111,11 @@
                 /* delay= */ 0L,
                 /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
                 /* isHeadsUpAppear= */ true,
-                /* onEndRunnable= */ null
+                /* onEndRunnable= */ null,
             )
     }
 
     @Test
-    @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun startAnimationForEvents_startsHeadsUpDisappearAnim() {
         val disappearDuration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR.toLong()
         val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR)
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 bf14472..e592e4b 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
@@ -42,6 +42,7 @@
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
 import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
 import com.android.systemui.statusbar.policy.fakeConfigurationController
@@ -522,21 +523,21 @@
             assertThat(pinnedHeadsUpRows).isEmpty()
 
             // WHEN a row gets pinned
-            rows[0].isPinned.value = true
+            rows[0].pinnedStatus.value = PinnedStatus.PinnedBySystem
             runCurrent()
 
             // THEN it's added to the list
             assertThat(pinnedHeadsUpRows).containsExactly(rows[0])
 
             // WHEN more rows are pinned
-            rows[1].isPinned.value = true
+            rows[1].pinnedStatus.value = PinnedStatus.PinnedBySystem
             runCurrent()
 
             // THEN they are all in the list
             assertThat(pinnedHeadsUpRows).containsExactly(rows[0], rows[1])
 
             // WHEN a row gets unpinned
-            rows[0].isPinned.value = false
+            rows[0].pinnedStatus.value = PinnedStatus.NotPinned
             runCurrent()
 
             // THEN it's removed from the list
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index d9e9495..f76f1ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -57,7 +57,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 6441405..ad8b675 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -53,7 +53,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
deleted file mode 100644
index dd03ab3..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.platform.test.annotations.DisableFlags;
-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;
-import com.android.systemui.flags.FakeFeatureFlagsClassic;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeHeadsUpTracker;
-import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.HeadsUpStatusBarView;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
-import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.policy.Clock;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@RunWithLooper
-public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
-
-    private final NotificationStackScrollLayoutController mStackScrollerController =
-            mock(NotificationStackScrollLayoutController.class);
-    private final ShadeViewController mShadeViewController =
-            mock(ShadeViewController.class);
-    private final ShadeHeadsUpTracker mShadeHeadsUpTracker = mock(ShadeHeadsUpTracker.class);
-    private final DarkIconDispatcher mDarkIconDispatcher = mock(DarkIconDispatcher.class);
-    private HeadsUpAppearanceController mHeadsUpAppearanceController;
-    private NotificationTestHelper mTestHelper;
-    private ExpandableNotificationRow mRow;
-    private NotificationEntry mEntry;
-    private HeadsUpStatusBarView mHeadsUpStatusBarView;
-    private HeadsUpManager mHeadsUpManager;
-    private View mOperatorNameView;
-    private StatusBarStateController mStatusbarStateController;
-    private PhoneStatusBarTransitions mPhoneStatusBarTransitions;
-    private KeyguardBypassController mBypassController;
-    private NotificationWakeUpCoordinator mWakeUpCoordinator;
-    private KeyguardStateController mKeyguardStateController;
-    private CommandQueue mCommandQueue;
-    private NotificationRoundnessManager mNotificationRoundnessManager;
-    private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
-
-    @Before
-    public void setUp() throws Exception {
-        allowTestableLooperAsMainThread();
-        mTestHelper = new NotificationTestHelper(
-                mContext,
-                mDependency,
-                TestableLooper.get(this));
-        mRow = mTestHelper.createRow();
-        mEntry = mRow.getEntry();
-        mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class),
-                mock(TextView.class));
-        mHeadsUpManager = mock(HeadsUpManager.class);
-        mOperatorNameView = new View(mContext);
-        mStatusbarStateController = mock(StatusBarStateController.class);
-        mPhoneStatusBarTransitions = mock(PhoneStatusBarTransitions.class);
-        mBypassController = mock(KeyguardBypassController.class);
-        mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
-        mKeyguardStateController = mock(KeyguardStateController.class);
-        mCommandQueue = mock(CommandQueue.class);
-        mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
-        when(mShadeViewController.getShadeHeadsUpTracker()).thenReturn(mShadeHeadsUpTracker);
-        mHeadsUpAppearanceController = new HeadsUpAppearanceController(
-                mHeadsUpManager,
-                mStatusbarStateController,
-                mPhoneStatusBarTransitions,
-                mBypassController,
-                mWakeUpCoordinator,
-                mDarkIconDispatcher,
-                mKeyguardStateController,
-                mCommandQueue,
-                mStackScrollerController,
-                mShadeViewController,
-                mNotificationRoundnessManager,
-                mHeadsUpStatusBarView,
-                new Clock(mContext, null),
-                mFeatureFlags,
-                mock(HeadsUpNotificationIconInteractor.class),
-                Optional.of(mOperatorNameView));
-        mHeadsUpAppearanceController.setAppearFraction(0.0f, 0.0f);
-    }
-
-    @Test
-    public void testShowinEntryUpdated() {
-        mRow.setPinned(true);
-        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
-        when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
-        mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
-        assertEquals(mRow.getEntry(), mHeadsUpStatusBarView.getShowingEntry());
-
-        mRow.setPinned(false);
-        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
-        mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
-        assertEquals(null, mHeadsUpStatusBarView.getShowingEntry());
-    }
-
-    @Test
-    public void testShownUpdated() {
-        mRow.setPinned(true);
-        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
-        when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
-        mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
-        assertTrue(mHeadsUpAppearanceController.isShown());
-
-        mRow.setPinned(false);
-        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
-        mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
-        Assert.assertFalse(mHeadsUpAppearanceController.isShown());
-    }
-
-    @Test
-    @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
-    public void testHeaderUpdated() {
-        mRow.setPinned(true);
-        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
-        when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
-        mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
-        assertEquals(mRow.getHeaderVisibleAmount(), 0.0f, 0.0f);
-
-        mRow.setPinned(false);
-        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
-        mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
-        assertEquals(mRow.getHeaderVisibleAmount(), 1.0f, 0.0f);
-    }
-
-    @Test
-    public void testOperatorNameViewUpdated() {
-        mHeadsUpAppearanceController.setAnimationsEnabled(false);
-
-        mRow.setPinned(true);
-        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
-        when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
-        mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
-        assertEquals(View.INVISIBLE, mOperatorNameView.getVisibility());
-
-        mRow.setPinned(false);
-        when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
-        mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
-        assertEquals(View.VISIBLE, mOperatorNameView.getVisibility());
-    }
-
-    @Test
-    public void constructor_animationValuesUpdated() {
-        float appearFraction = .75f;
-        float expandedHeight = 400f;
-        when(mStackScrollerController.getAppearFraction()).thenReturn(appearFraction);
-        when(mStackScrollerController.getExpandedHeight()).thenReturn(expandedHeight);
-
-        HeadsUpAppearanceController newController = new HeadsUpAppearanceController(
-                mHeadsUpManager,
-                mStatusbarStateController,
-                mPhoneStatusBarTransitions,
-                mBypassController,
-                mWakeUpCoordinator,
-                mDarkIconDispatcher,
-                mKeyguardStateController,
-                mCommandQueue,
-                mStackScrollerController,
-                mShadeViewController,
-                mNotificationRoundnessManager,
-                mHeadsUpStatusBarView,
-                new Clock(mContext, null),
-                mFeatureFlags, mock(HeadsUpNotificationIconInteractor.class),
-                Optional.empty());
-
-        assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f);
-        assertEquals(appearFraction, newController.mAppearFraction, 0.0f);
-    }
-
-    @Test
-    public void testDestroy() {
-        reset(mHeadsUpManager);
-        reset(mDarkIconDispatcher);
-        reset(mShadeHeadsUpTracker);
-        reset(mStackScrollerController);
-
-        mHeadsUpAppearanceController.onViewDetached();
-
-        verify(mHeadsUpManager).removeListener(any());
-        verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any());
-        verify(mShadeHeadsUpTracker).removeTrackingHeadsUpListener(any());
-        verify(mShadeHeadsUpTracker).setHeadsUpAppearanceController(isNull());
-        verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any());
-    }
-
-    @Test
-    public void testPulsingRoundness_onUpdateHeadsUpAndPulsingRoundness() {
-        // Pulsing: Enable flag and dozing
-        when(mNotificationRoundnessManager.shouldRoundNotificationPulsing()).thenReturn(true);
-        when(mTestHelper.getStatusBarStateController().isDozing()).thenReturn(true);
-
-        // Pulsing: Enabled
-        mRow.setHeadsUp(true);
-        mHeadsUpAppearanceController.updateHeadsUpAndPulsingRoundness(mEntry);
-
-        String debugString = mRow.getRoundableState().debugString();
-        assertEquals(
-                "If Pulsing is enabled, roundness should be set to 1. Value: " + debugString,
-                /* expected = */ 1,
-                /* actual = */ mRow.getTopRoundness(),
-                /* delta = */ 0.001
-        );
-        assertTrue(debugString.contains("Pulsing"));
-
-        // Pulsing: Disabled
-        mRow.setHeadsUp(false);
-        mHeadsUpAppearanceController.updateHeadsUpAndPulsingRoundness(mEntry);
-
-        assertEquals(
-                "If Pulsing is disabled, roundness should be set to 0. Value: "
-                        + mRow.getRoundableState().debugString(),
-                /* expected = */ 0,
-                /* actual = */ mRow.getTopRoundness(),
-                /* delta = */ 0.001
-        );
-    }
-
-    @Test
-    public void testPulsingRoundness_onHeadsUpStateChanged() {
-        // Pulsing: Enable flag and dozing
-        when(mNotificationRoundnessManager.shouldRoundNotificationPulsing()).thenReturn(true);
-        when(mTestHelper.getStatusBarStateController().isDozing()).thenReturn(true);
-
-        // Pulsing: Enabled
-        mEntry.setHeadsUp(true);
-        mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, true);
-
-        String debugString = mRow.getRoundableState().debugString();
-        assertEquals(
-                "If Pulsing is enabled, roundness should be set to 1. Value: " + debugString,
-                /* expected = */ 1,
-                /* actual = */ mRow.getTopRoundness(),
-                /* delta = */ 0.001
-        );
-        assertTrue(debugString.contains("Pulsing"));
-
-        // Pulsing: Disabled
-        mEntry.setHeadsUp(false);
-        mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, false);
-
-        assertEquals(
-                "If Pulsing is disabled, roundness should be set to 0. Value: "
-                        + mRow.getRoundableState().debugString(),
-                /* expected = */ 0,
-                /* actual = */ mRow.getTopRoundness(),
-                /* delta = */ 0.001
-        );
-    }
-
-    @Test
-    public void onHeadsUpStateChanged_true_transitionsNotified() {
-        mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, true);
-
-        verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(true);
-    }
-
-    @Test
-    public void onHeadsUpStateChanged_false_transitionsNotified() {
-        mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, false);
-
-        verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(false);
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt
new file mode 100644
index 0000000..d017402
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar.phone
+
+import android.platform.test.annotations.DisableFlags
+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
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeHeadsUpTracker
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.HeadsUpStatusBarView
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.policy.Clock
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+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.reset
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+class HeadsUpAppearanceControllerTest : SysuiTestCase() {
+    private val mStackScrollerController: NotificationStackScrollLayoutController =
+        mock<NotificationStackScrollLayoutController>()
+    private val mShadeViewController: ShadeViewController = mock<ShadeViewController>()
+    private val mShadeHeadsUpTracker: ShadeHeadsUpTracker = mock<ShadeHeadsUpTracker>()
+    private val mDarkIconDispatcher: DarkIconDispatcher = mock<DarkIconDispatcher>()
+    private var mHeadsUpAppearanceController: HeadsUpAppearanceController? = null
+    private var mTestHelper: NotificationTestHelper? = null
+    private var mRow: ExpandableNotificationRow? = null
+    private var mEntry: NotificationEntry? = null
+    private var mHeadsUpStatusBarView: HeadsUpStatusBarView? = null
+    private var mHeadsUpManager: HeadsUpManager? = null
+    private var mOperatorNameView: View? = null
+    private var mStatusbarStateController: StatusBarStateController? = null
+    private var mPhoneStatusBarTransitions: PhoneStatusBarTransitions? = null
+    private var mBypassController: KeyguardBypassController? = null
+    private var mWakeUpCoordinator: NotificationWakeUpCoordinator? = null
+    private var mKeyguardStateController: KeyguardStateController? = null
+    private var mCommandQueue: CommandQueue? = null
+    private var mNotificationRoundnessManager: NotificationRoundnessManager? = null
+
+    @Before
+    @Throws(Exception::class)
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        mTestHelper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+        mRow = mTestHelper!!.createRow()
+        mEntry = mRow!!.entry
+        mHeadsUpStatusBarView = HeadsUpStatusBarView(mContext, mock<View>(), mock<TextView>())
+        mHeadsUpManager = mock<HeadsUpManager>()
+        mOperatorNameView = View(mContext)
+        mStatusbarStateController = mock<StatusBarStateController>()
+        mPhoneStatusBarTransitions = mock<PhoneStatusBarTransitions>()
+        mBypassController = mock<KeyguardBypassController>()
+        mWakeUpCoordinator = mock<NotificationWakeUpCoordinator>()
+        mKeyguardStateController = mock<KeyguardStateController>()
+        mCommandQueue = mock<CommandQueue>()
+        mNotificationRoundnessManager = mock<NotificationRoundnessManager>()
+        whenever(mShadeViewController.shadeHeadsUpTracker).thenReturn(mShadeHeadsUpTracker)
+        mHeadsUpAppearanceController =
+            HeadsUpAppearanceController(
+                mHeadsUpManager,
+                mStatusbarStateController,
+                mPhoneStatusBarTransitions,
+                mBypassController,
+                mWakeUpCoordinator,
+                mDarkIconDispatcher,
+                mKeyguardStateController,
+                mCommandQueue,
+                mStackScrollerController,
+                mShadeViewController,
+                mNotificationRoundnessManager,
+                mHeadsUpStatusBarView,
+                Clock(mContext, null),
+                mock<HeadsUpNotificationIconInteractor>(),
+                Optional.of(mOperatorNameView!!),
+            )
+        mHeadsUpAppearanceController!!.setAppearFraction(0.0f, 0.0f)
+    }
+
+    @Test
+    fun testShowinEntryUpdated() {
+        mRow!!.setPinnedStatus(PinnedStatus.PinnedBySystem)
+        whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(true)
+        whenever(mHeadsUpManager!!.getTopEntry()).thenReturn(mEntry)
+        mHeadsUpAppearanceController!!.onHeadsUpPinned(mEntry)
+        assertThat(mHeadsUpStatusBarView!!.showingEntry).isEqualTo(mRow!!.entry)
+
+        mRow!!.setPinnedStatus(PinnedStatus.NotPinned)
+        whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(false)
+        mHeadsUpAppearanceController!!.onHeadsUpUnPinned(mEntry)
+        assertThat(mHeadsUpStatusBarView!!.showingEntry).isNull()
+    }
+
+    @Test
+    fun testPinnedStatusUpdated() {
+        mRow!!.setPinnedStatus(PinnedStatus.PinnedBySystem)
+        whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(true)
+        whenever(mHeadsUpManager!!.getTopEntry()).thenReturn(mEntry)
+        mHeadsUpAppearanceController!!.onHeadsUpPinned(mEntry)
+        assertThat(mHeadsUpAppearanceController!!.pinnedStatus)
+            .isEqualTo(PinnedStatus.PinnedBySystem)
+
+        mRow!!.setPinnedStatus(PinnedStatus.NotPinned)
+        whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(false)
+        mHeadsUpAppearanceController!!.onHeadsUpUnPinned(mEntry)
+        assertThat(mHeadsUpAppearanceController!!.pinnedStatus).isEqualTo(PinnedStatus.NotPinned)
+    }
+
+    @Test
+    @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
+    fun testHeaderUpdated() {
+        mRow!!.setPinnedStatus(PinnedStatus.PinnedBySystem)
+        whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(true)
+        whenever(mHeadsUpManager!!.getTopEntry()).thenReturn(mEntry)
+        mHeadsUpAppearanceController!!.onHeadsUpPinned(mEntry)
+        assertThat(mRow!!.headerVisibleAmount).isEqualTo(0.0f)
+
+        mRow!!.setPinnedStatus(PinnedStatus.NotPinned)
+        whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(false)
+        mHeadsUpAppearanceController!!.onHeadsUpUnPinned(mEntry)
+        assertThat(mRow!!.headerVisibleAmount).isEqualTo(1.0f)
+    }
+
+    @Test
+    fun testOperatorNameViewUpdated() {
+        mHeadsUpAppearanceController!!.setAnimationsEnabled(false)
+
+        mRow!!.setPinnedStatus(PinnedStatus.PinnedBySystem)
+        whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(true)
+        whenever(mHeadsUpManager!!.getTopEntry()).thenReturn(mEntry)
+        mHeadsUpAppearanceController!!.onHeadsUpPinned(mEntry)
+        assertThat(mOperatorNameView!!.visibility).isEqualTo(View.INVISIBLE)
+
+        mRow!!.setPinnedStatus(PinnedStatus.NotPinned)
+        whenever(mHeadsUpManager!!.hasPinnedHeadsUp()).thenReturn(false)
+        mHeadsUpAppearanceController!!.onHeadsUpUnPinned(mEntry)
+        assertThat(mOperatorNameView!!.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    fun constructor_animationValuesUpdated() {
+        val appearFraction = .75f
+        val expandedHeight = 400f
+        whenever(mStackScrollerController.appearFraction).thenReturn(appearFraction)
+        whenever(mStackScrollerController.expandedHeight).thenReturn(expandedHeight)
+
+        val newController =
+            HeadsUpAppearanceController(
+                mHeadsUpManager,
+                mStatusbarStateController,
+                mPhoneStatusBarTransitions,
+                mBypassController,
+                mWakeUpCoordinator,
+                mDarkIconDispatcher,
+                mKeyguardStateController,
+                mCommandQueue,
+                mStackScrollerController,
+                mShadeViewController,
+                mNotificationRoundnessManager,
+                mHeadsUpStatusBarView,
+                Clock(mContext, null),
+                mock<HeadsUpNotificationIconInteractor>(),
+                Optional.empty(),
+            )
+
+        assertThat(newController.mExpandedHeight).isEqualTo(expandedHeight)
+        assertThat(newController.mAppearFraction).isEqualTo(appearFraction)
+    }
+
+    @Test
+    fun testDestroy() {
+        reset(mHeadsUpManager)
+        reset(mDarkIconDispatcher)
+        reset(mShadeHeadsUpTracker)
+        reset(mStackScrollerController)
+
+        mHeadsUpAppearanceController!!.onViewDetached()
+
+        verify(mHeadsUpManager!!).removeListener(any())
+        verify(mDarkIconDispatcher).removeDarkReceiver(any())
+        verify(mShadeHeadsUpTracker).removeTrackingHeadsUpListener(any())
+        verify(mShadeHeadsUpTracker).setHeadsUpAppearanceController(null)
+        verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any())
+    }
+
+    @Test
+    fun testPulsingRoundness_onUpdateHeadsUpAndPulsingRoundness() {
+        // Pulsing: Enable flag and dozing
+        whenever(mNotificationRoundnessManager!!.shouldRoundNotificationPulsing()).thenReturn(true)
+        whenever(mTestHelper!!.statusBarStateController.isDozing).thenReturn(true)
+
+        // Pulsing: Enabled
+        mRow!!.isHeadsUp = true
+        mHeadsUpAppearanceController!!.updateHeadsUpAndPulsingRoundness(mEntry)
+
+        val debugString: String = mRow!!.roundableState.debugString()
+        // If Pulsing is enabled, roundness should be set to 1
+        assertThat(mRow!!.topRoundness.toDouble()).isWithin(0.001).of(1.0)
+        assertThat(debugString).contains("Pulsing")
+
+        // Pulsing: Disabled
+        mRow!!.isHeadsUp = false
+        mHeadsUpAppearanceController!!.updateHeadsUpAndPulsingRoundness(mEntry)
+
+        // If Pulsing is disabled, roundness should be set to 0
+        assertThat(mRow!!.topRoundness.toDouble()).isWithin(0.001).of(0.0)
+    }
+
+    @Test
+    fun testPulsingRoundness_onHeadsUpStateChanged() {
+        // Pulsing: Enable flag and dozing
+        whenever(mNotificationRoundnessManager!!.shouldRoundNotificationPulsing()).thenReturn(true)
+        whenever(mTestHelper!!.statusBarStateController.isDozing).thenReturn(true)
+
+        // Pulsing: Enabled
+        mEntry!!.setHeadsUp(true)
+        mHeadsUpAppearanceController!!.onHeadsUpStateChanged(mEntry!!, true)
+
+        val debugString: String = mRow!!.roundableState.debugString()
+        // If Pulsing is enabled, roundness should be set to 1
+        assertThat(mRow!!.topRoundness.toDouble()).isWithin(0.001).of(1.0)
+        assertThat(debugString).contains("Pulsing")
+
+        // Pulsing: Disabled
+        mEntry!!.setHeadsUp(false)
+        mHeadsUpAppearanceController!!.onHeadsUpStateChanged(mEntry!!, false)
+
+        // If Pulsing is disabled, roundness should be set to 0
+        assertThat(mRow!!.topRoundness.toDouble()).isWithin(0.001).of(0.0)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_true_transitionsNotified() {
+        mHeadsUpAppearanceController!!.onHeadsUpStateChanged(mEntry!!, true)
+
+        verify(mPhoneStatusBarTransitions!!).onHeadsUpStateChanged(true)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_false_transitionsNotified() {
+        mHeadsUpAppearanceController!!.onHeadsUpStateChanged(mEntry!!, false)
+
+        verify(mPhoneStatusBarTransitions!!).onHeadsUpStateChanged(false)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt
new file mode 100644
index 0000000..90506a1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplayAutoHideControllerStoreTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val testScope = kosmos.testScope
+    private val fakeDisplayRepository = kosmos.displayRepository
+
+    // Lazy so that @EnableFlags has time to run before underTest is instantiated.
+    private val underTest by lazy { kosmos.multiDisplayAutoHideControllerStore }
+
+    @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+    @Before
+    fun start() {
+        underTest.start()
+    }
+
+    @Test
+    fun beforeDisplayRemoved_doesNotStopInstances() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            verify(instance, never()).stop()
+        }
+
+    @Test
+    fun displayRemoved_stopsInstance() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+            verify(instance).stop()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManagerTest.kt
new file mode 100644
index 0000000..d82cb86
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManagerTest.kt
@@ -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.systemui.statusbar.phone
+
+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.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+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.kotlin.getValue
+import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+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
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class ShadeTouchableRegionManagerTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneRepository = kosmos.sceneContainerRepository
+
+    private val underTest by Lazy { kosmos.shadeTouchableRegionManager }
+
+    @Test
+    @EnableSceneContainer
+    fun entireScreenTouchable_sceneContainerEnabled_isRemoteUserInteractionOngoing() =
+        testScope.runTest {
+            sceneRepository.setTransitionState(
+                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone))
+            )
+            runCurrent()
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
+
+            sceneRepository.isRemoteUserInputOngoing.value = true
+            runCurrent()
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue()
+
+            sceneRepository.isRemoteUserInputOngoing.value = false
+            runCurrent()
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
+        }
+
+    @Test
+    @DisableSceneContainer
+    fun entireScreenTouchable_sceneContainerDisabled_isRemoteUserInteractionOngoing() =
+        testScope.runTest {
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
+
+            sceneRepository.isRemoteUserInputOngoing.value = true
+            runCurrent()
+
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun entireScreenTouchable_sceneContainerEnabled_isIdleOnGone() =
+        testScope.runTest {
+            sceneRepository.setTransitionState(
+                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone))
+            )
+            runCurrent()
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
+
+            sceneRepository.setTransitionState(
+                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Shade))
+            )
+            runCurrent()
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue()
+
+            sceneRepository.setTransitionState(
+                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone))
+            )
+            runCurrent()
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
+        }
+
+    @Test
+    @DisableSceneContainer
+    fun entireScreenTouchable_sceneContainerDisabled_isIdleOnGone() =
+        testScope.runTest {
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
+
+            sceneRepository.setTransitionState(
+                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Shade))
+            )
+            runCurrent()
+
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
+        }
+
+    @Test
+    @DisableSceneContainer
+    fun entireScreenTouchable_communalVisible() =
+        testScope.runTest {
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
+
+            kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+            runCurrent()
+
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue()
+
+            kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank)
+            runCurrent()
+
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 0eb6203..d174484 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -190,6 +190,8 @@
     private ArgumentCaptor<OnBackInvokedCallback> mBackCallbackCaptor;
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
+    @Mock
+    private KeyguardDismissActionInteractor mKeyguardDismissActionInteractor;
 
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -236,7 +238,7 @@
                         mKeyguardTransitionInteractor,
                         mock(KeyguardDismissTransitionInteractor.class),
                         StandardTestDispatcher(null, null),
-                        () -> mock(KeyguardDismissActionInteractor.class),
+                        () -> mKeyguardDismissActionInteractor,
                         mSelectedUserInteractor,
                         mock(JavaAdapter.class),
                         () -> mSceneInteractor,
@@ -968,4 +970,33 @@
         );
         verify(mAlternateBouncerInteractor).hide();
     }
+
+    @Test
+    public void hideAlternateBouncer_clearsDismissActionByDefault() {
+        clearInvocations(mKeyguardDismissActionInteractor);
+
+        mStatusBarKeyguardViewManager.hideAlternateBouncer(/* updateScrim= */ true);
+
+        verify(mKeyguardDismissActionInteractor).clearDismissAction();
+    }
+
+    @Test
+    public void hideAlternateBouncer_clearsDismissActionExplicitly() {
+        clearInvocations(mKeyguardDismissActionInteractor);
+
+        mStatusBarKeyguardViewManager.hideAlternateBouncer(
+                /* updateScrim= */ true, /* clearDismissAction= */ true);
+
+        verify(mKeyguardDismissActionInteractor).clearDismissAction();
+    }
+
+    @Test
+    public void hideAlternateBouncer_doNotClearDismissActionExplicitly() {
+        clearInvocations(mKeyguardDismissActionInteractor);
+
+        mStatusBarKeyguardViewManager.hideAlternateBouncer(
+                /* updateScrim= */ true, /* clearDismissAction= */ false);
+
+        verify(mKeyguardDismissActionInteractor, never()).clearDismissAction();
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 95472cad..41782a1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -73,7 +73,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
deleted file mode 100644
index a008588..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
+++ /dev/null
@@ -1,135 +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.statusbar.phone
-
-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.communal.data.repository.fakeCommunalSceneRepository
-import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.flags.DisableSceneContainer
-import com.android.systemui.flags.EnableSceneContainer
-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.kotlin.getValue
-import com.google.common.truth.Truth.assertThat
-import dagger.Lazy
-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
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalCoroutinesApi::class)
-class StatusBarTouchableRegionManagerTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-    private val sceneRepository = kosmos.sceneContainerRepository
-
-    private val underTest by Lazy { kosmos.statusBarTouchableRegionManager }
-
-    @Test
-    @EnableSceneContainer
-    fun entireScreenTouchable_sceneContainerEnabled_isRemoteUserInteractionOngoing() =
-        testScope.runTest {
-            sceneRepository.setTransitionState(
-                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone))
-            )
-            runCurrent()
-            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
-
-            sceneRepository.isRemoteUserInputOngoing.value = true
-            runCurrent()
-            assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue()
-
-            sceneRepository.isRemoteUserInputOngoing.value = false
-            runCurrent()
-            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
-        }
-
-    @Test
-    @DisableSceneContainer
-    fun entireScreenTouchable_sceneContainerDisabled_isRemoteUserInteractionOngoing() =
-        testScope.runTest {
-            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
-
-            sceneRepository.isRemoteUserInputOngoing.value = true
-            runCurrent()
-
-            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
-        }
-
-    @Test
-    @EnableSceneContainer
-    fun entireScreenTouchable_sceneContainerEnabled_isIdleOnGone() =
-        testScope.runTest {
-            sceneRepository.setTransitionState(
-                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone))
-            )
-            runCurrent()
-            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
-
-            sceneRepository.setTransitionState(
-                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Shade))
-            )
-            runCurrent()
-            assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue()
-
-            sceneRepository.setTransitionState(
-                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone))
-            )
-            runCurrent()
-            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
-        }
-
-    @Test
-    @DisableSceneContainer
-    fun entireScreenTouchable_sceneContainerDisabled_isIdleOnGone() =
-        testScope.runTest {
-            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
-
-            sceneRepository.setTransitionState(
-                flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Shade))
-            )
-            runCurrent()
-
-            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
-        }
-
-    @Test
-    @DisableSceneContainer
-    fun entireScreenTouchable_communalVisible() =
-        testScope.runTest {
-            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
-
-            kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
-            runCurrent()
-
-            assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue()
-
-            kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank)
-            runCurrent()
-
-            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
index 110dec6..f76ee5e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
@@ -87,7 +87,7 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 @OptIn(ExperimentalCoroutinesApi::class)
-@DisableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP)
+@DisableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP, StatusBarChipsModernization.FLAG_NAME)
 class OngoingCallControllerViaListenerTest : SysuiTestCase() {
     private val kosmos = Kosmos()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
index 2ad50cc..647b5f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
@@ -29,6 +29,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON
+import com.android.systemui.Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION
 import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS
 import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP
 import com.android.systemui.SysuiTestCase
@@ -77,6 +78,7 @@
 @TestableLooper.RunWithLooper
 @OptIn(ExperimentalCoroutinesApi::class)
 @EnableFlags(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP)
+@DisableFlags(StatusBarChipsModernization.FLAG_NAME)
 class OngoingCallControllerViaRepoTest : SysuiTestCase() {
     private val kosmos = Kosmos()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
index 4c6eaa5..a446313 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
@@ -16,10 +16,13 @@
 
 package com.android.systemui.statusbar.phone.ongoingcall.data.repository
 
+import android.platform.test.annotations.DisableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
 import com.google.common.truth.Truth.assertThat
@@ -28,6 +31,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@DisableFlags(StatusBarChipsModernization.FLAG_NAME)
 class OngoingCallRepositoryTest : SysuiTestCase() {
     private val kosmos = Kosmos()
     private val underTest = kosmos.ongoingCallRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
new file mode 100644
index 0000000..ca1413e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.phone.ongoingcall.domain.interactor
+
+import android.app.PendingIntent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.data.repository.activityManagerRepository
+import com.android.systemui.activity.data.repository.fake
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.CallType
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OngoingCallInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos().useUnconfinedTestDispatcher()
+    private val repository = kosmos.activeNotificationListRepository
+    private val underTest = kosmos.ongoingCallInteractor
+
+    @Test
+    fun noNotification_emitsNoCall() = runTest {
+        val state by collectLastValue(underTest.ongoingCallState)
+        assertThat(state).isInstanceOf(OngoingCallModel.NoCall::class.java)
+    }
+
+    @Test
+    fun ongoingCallNotification_setsNotificationIconAndIntent() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.ongoingCallState)
+
+            // Set up notification with icon view and intent
+            val testIconView: StatusBarIconView = mock()
+            val testIntent: PendingIntent = mock()
+            repository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply {
+                        addIndividualNotif(
+                            activeNotificationModel(
+                                key = "notif1",
+                                whenTime = 1000L,
+                                callType = CallType.Ongoing,
+                                statusBarChipIcon = testIconView,
+                                contentIntent = testIntent
+                            )
+                        )
+                    }
+                    .build()
+
+            // Verify model is InCall and has the correct icon and intent.
+            assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
+            val model = latest as OngoingCallModel.InCall
+            assertThat(model.notificationIconView).isSameInstanceAs(testIconView)
+            assertThat(model.intent).isSameInstanceAs(testIntent)
+        }
+
+    @Test
+    fun ongoingCallNotification_emitsInCall() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.ongoingCallState)
+
+            repository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply {
+                        addIndividualNotif(
+                            activeNotificationModel(
+                                key = "notif1", whenTime = 1000L, callType = CallType.Ongoing
+                            )
+                        )
+                    }
+                    .build()
+
+            assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
+        }
+
+    @Test
+    fun notificationRemoved_emitsNoCall() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.ongoingCallState)
+
+            repository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply {
+                        addIndividualNotif(
+                            activeNotificationModel(
+                                key = "notif1", whenTime = 1000L, callType = CallType.Ongoing
+                            )
+                        )
+                    }
+                    .build()
+
+            repository.activeNotifications.value = ActiveNotificationsStore()
+            assertThat(latest).isInstanceOf(OngoingCallModel.NoCall::class.java)
+        }
+
+    @Test
+    fun ongoingCallNotification_appVisibleInitially_emitsInCallWithVisibleApp() =
+        kosmos.runTest {
+            kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = true
+            val latest by collectLastValue(underTest.ongoingCallState)
+
+            repository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply {
+                        addIndividualNotif(
+                            activeNotificationModel(
+                                key = "notif1",
+                                whenTime = 1000L,
+                                callType = CallType.Ongoing,
+                                uid = UID
+                            )
+                        )
+                    }
+                    .build()
+
+            assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java)
+        }
+
+    @Test
+    fun ongoingCallNotification_appNotVisibleInitially_emitsInCall() =
+        kosmos.runTest {
+            kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false
+            val latest by collectLastValue(underTest.ongoingCallState)
+
+            repository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply {
+                        addIndividualNotif(
+                            activeNotificationModel(
+                                key = "notif1",
+                                whenTime = 1000L,
+                                callType = CallType.Ongoing,
+                                uid = UID
+                            )
+                        )
+                    }
+                    .build()
+
+            assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
+        }
+
+    @Test
+    fun ongoingCallNotification_visibilityChanges_updatesState() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.ongoingCallState)
+
+            // Start with notification and app not visible
+            kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false
+            repository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply {
+                        addIndividualNotif(
+                            activeNotificationModel(
+                                key = "notif1",
+                                whenTime = 1000L,
+                                callType = CallType.Ongoing,
+                                uid = UID
+                            )
+                        )
+                    }
+                    .build()
+            assertThat(latest)
+                .isInstanceOf(OngoingCallModel.InCall::class.java)
+
+            // App becomes visible
+            kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true)
+            assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java)
+
+            // App becomes invisible again
+            kosmos.activityManagerRepository.fake.setIsAppVisible(UID, false)
+            assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
+        }
+
+    companion object {
+        private const val UID = 885
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index b9cdd06..7b04fc8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -28,8 +28,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -39,7 +37,9 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.collectValues
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.assertLogsWtf
@@ -52,6 +52,7 @@
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsScreenRecordChip
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip
@@ -66,15 +67,17 @@
 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -83,35 +86,22 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class HomeStatusBarViewModelImplTest : SysuiTestCase() {
-    private val kosmos =
-        Kosmos().also {
-            it.testCase = this
-            it.testDispatcher = UnconfinedTestDispatcher()
-        }
-
-    private val testScope = kosmos.testScope
-
-    private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository
-    private val activeNotificationListRepository = kosmos.activeNotificationListRepository
-    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val disableFlagsRepository = kosmos.fakeDisableFlagsRepository
-    private val systemStatusEventAnimationRepository = kosmos.systemStatusEventAnimationRepository
-
-    private lateinit var underTest: HomeStatusBarViewModel
+    private val kosmos by lazy {
+        testKosmos().also { it.testDispatcher = UnconfinedTestDispatcher() }
+    }
+    private val Kosmos.underTest by Kosmos.Fixture { kosmos.homeStatusBarViewModel }
 
     @Before
     fun setUp() {
         setUpPackageManagerForMediaProjection(kosmos)
-        // Initialize here because some flags are checked when this class is constructed
-        underTest = kosmos.homeStatusBarViewModel
     }
 
     @Test
     fun isTransitioningFromLockscreenToOccluded_started_isTrue() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
 
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.OCCLUDED,
@@ -125,10 +115,10 @@
 
     @Test
     fun isTransitioningFromLockscreenToOccluded_running_isTrue() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
 
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.OCCLUDED,
@@ -142,10 +132,10 @@
 
     @Test
     fun isTransitioningFromLockscreenToOccluded_finished_isFalse() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
 
-            keyguardTransitionRepository.sendTransitionSteps(
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.OCCLUDED,
                 testScope.testScheduler,
@@ -156,10 +146,10 @@
 
     @Test
     fun isTransitioningFromLockscreenToOccluded_canceled_isFalse() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
 
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.OCCLUDED,
@@ -173,10 +163,10 @@
 
     @Test
     fun isTransitioningFromLockscreenToOccluded_irrelevantTransition_isFalse() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
 
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.AOD,
                     KeyguardState.LOCKSCREEN,
@@ -190,10 +180,10 @@
 
     @Test
     fun isTransitioningFromLockscreenToOccluded_followsRepoUpdates() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
 
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.OCCLUDED,
@@ -205,7 +195,7 @@
             assertThat(latest).isTrue()
 
             // WHEN the repo updates the transition to finished
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.OCCLUDED,
@@ -220,10 +210,10 @@
 
     @Test
     fun transitionFromLockscreenToDreamStartedEvent_started_emitted() =
-        testScope.runTest {
+        kosmos.runTest {
             val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
 
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.DREAMING,
@@ -237,10 +227,10 @@
 
     @Test
     fun transitionFromLockscreenToDreamStartedEvent_startedMultiple_emittedMultiple() =
-        testScope.runTest {
+        kosmos.runTest {
             val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
 
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.DREAMING,
@@ -249,7 +239,7 @@
                 )
             )
 
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.DREAMING,
@@ -258,7 +248,7 @@
                 )
             )
 
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.DREAMING,
@@ -272,10 +262,10 @@
 
     @Test
     fun transitionFromLockscreenToDreamStartedEvent_startedThenRunning_emittedOnlyOne() =
-        testScope.runTest {
+        kosmos.runTest {
             val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
 
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.DREAMING,
@@ -287,7 +277,7 @@
 
             // WHEN the transition progresses through its animation by going through the RUNNING
             // step with increasing fractions
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.DREAMING,
@@ -296,7 +286,7 @@
                 )
             )
 
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.DREAMING,
@@ -305,7 +295,7 @@
                 )
             )
 
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.DREAMING,
@@ -321,10 +311,10 @@
 
     @Test
     fun transitionFromLockscreenToDreamStartedEvent_irrelevantTransition_notEmitted() =
-        testScope.runTest {
+        kosmos.runTest {
             val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
 
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.OCCLUDED,
@@ -338,10 +328,10 @@
 
     @Test
     fun transitionFromLockscreenToDreamStartedEvent_irrelevantTransitionState_notEmitted() =
-        testScope.runTest {
+        kosmos.runTest {
             val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
 
-            keyguardTransitionRepository.sendTransitionStep(
+            fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
                     KeyguardState.LOCKSCREEN,
                     KeyguardState.DREAMING,
@@ -359,8 +349,8 @@
     @Test
     @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
     fun areNotificationsLightsOut_lowProfileWithNotifications_true() =
-        testScope.runTest {
-            statusBarModeRepository.defaultDisplay.statusBarMode.value =
+        kosmos.runTest {
+            fakeStatusBarModeRepository.defaultDisplay.statusBarMode.value =
                 StatusBarMode.LIGHTS_OUT_TRANSPARENT
             activeNotificationListRepository.activeNotifications.value =
                 activeNotificationsStore(testNotifications)
@@ -373,8 +363,8 @@
     @Test
     @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
     fun areNotificationsLightsOut_lowProfileWithoutNotifications_false() =
-        testScope.runTest {
-            statusBarModeRepository.defaultDisplay.statusBarMode.value =
+        kosmos.runTest {
+            fakeStatusBarModeRepository.defaultDisplay.statusBarMode.value =
                 StatusBarMode.LIGHTS_OUT_TRANSPARENT
             activeNotificationListRepository.activeNotifications.value =
                 activeNotificationsStore(emptyList())
@@ -387,8 +377,9 @@
     @Test
     @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
     fun areNotificationsLightsOut_defaultStatusBarModeWithoutNotifications_false() =
-        testScope.runTest {
-            statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT
+        kosmos.runTest {
+            fakeStatusBarModeRepository.defaultDisplay.statusBarMode.value =
+                StatusBarMode.TRANSPARENT
             activeNotificationListRepository.activeNotifications.value =
                 activeNotificationsStore(emptyList())
 
@@ -400,8 +391,9 @@
     @Test
     @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
     fun areNotificationsLightsOut_defaultStatusBarModeWithNotifications_false() =
-        testScope.runTest {
-            statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT
+        kosmos.runTest {
+            fakeStatusBarModeRepository.defaultDisplay.statusBarMode.value =
+                StatusBarMode.TRANSPARENT
             activeNotificationListRepository.activeNotifications.value =
                 activeNotificationsStore(testNotifications)
 
@@ -413,7 +405,7 @@
     @Test
     @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
     fun areNotificationsLightsOut_requiresFlagEnabled() =
-        testScope.runTest {
+        kosmos.runTest {
             assertLogsWtf {
                 val flow = underTest.areNotificationsLightsOut(DISPLAY_ID)
                 assertThat(flow).isEqualTo(emptyFlow<Boolean>())
@@ -422,7 +414,7 @@
 
     @Test
     fun primaryOngoingActivityChip_matchesViewModel() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.primaryOngoingActivityChip)
 
             kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
@@ -441,7 +433,7 @@
 
     @Test
     fun isHomeStatusBarAllowedByScene_sceneLockscreen_notOccluded_false() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
@@ -452,7 +444,7 @@
 
     @Test
     fun isHomeStatusBarAllowedByScene_sceneLockscreen_occluded_true() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
@@ -463,7 +455,7 @@
 
     @Test
     fun isHomeStatusBarAllowedByScene_sceneBouncer_false() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Bouncer)
@@ -473,7 +465,7 @@
 
     @Test
     fun isHomeStatusBarAllowedByScene_sceneCommunal_false() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Communal)
@@ -483,7 +475,7 @@
 
     @Test
     fun isHomeStatusBarAllowedByScene_sceneShade_false() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Shade)
@@ -493,7 +485,7 @@
 
     @Test
     fun isHomeStatusBarAllowedByScene_sceneGone_true() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Gone)
@@ -503,35 +495,91 @@
 
     @Test
     fun isClockVisible_allowedByDisableFlags_visible() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isClockVisible)
             transitionKeyguardToGone()
 
-            disableFlagsRepository.disableFlags.value =
+            fakeDisableFlagsRepository.disableFlags.value =
                 DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
 
             assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
         }
 
     @Test
-    fun isClockVisible_notAllowedByDisableFlags_gone() =
-        testScope.runTest {
+    fun isClockVisible_notAllowedByDisableFlags_invisible() =
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isClockVisible)
             transitionKeyguardToGone()
 
-            disableFlagsRepository.disableFlags.value =
+            fakeDisableFlagsRepository.disableFlags.value =
                 DisableFlagsModel(DISABLE_CLOCK, DISABLE2_NONE)
 
-            assertThat(latest!!.visibility).isEqualTo(View.GONE)
+            assertThat(latest!!.visibility).isEqualTo(View.INVISIBLE)
+        }
+
+    @Test
+    fun isClockVisible_allowedByFlags_hunActive_notVisible() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.isClockVisible)
+            transitionKeyguardToGone()
+
+            fakeDisableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+            // there is an active HUN
+            headsUpNotificationRepository.setNotifications(
+                fakeHeadsUpRowRepository(isPinned = true)
+            )
+
+            assertThat(latest!!.visibility).isEqualTo(View.INVISIBLE)
+        }
+
+    @Test
+    fun isClockVisible_allowedByFlags_hunBecomesInactive_visibleAgain() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.isClockVisible)
+            transitionKeyguardToGone()
+
+            fakeDisableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+            // there is an active HUN
+            headsUpNotificationRepository.setNotifications(
+                fakeHeadsUpRowRepository(isPinned = true)
+            )
+
+            // hun goes away
+            headsUpNotificationRepository.setNotifications(listOf())
+
+            assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
+        }
+
+    @Test
+    fun isClockVisible_disabledByFlags_hunBecomesInactive_neverVisible() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.isClockVisible)
+            transitionKeyguardToGone()
+
+            fakeDisableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(DISABLE_CLOCK, DISABLE2_NONE)
+            // there is an active HUN
+            headsUpNotificationRepository.setNotifications(
+                fakeHeadsUpRowRepository(isPinned = true)
+            )
+
+            assertThat(latest!!.visibility).isEqualTo(View.INVISIBLE)
+
+            // hun goes away
+            headsUpNotificationRepository.setNotifications(listOf())
+
+            assertThat(latest!!.visibility).isEqualTo(View.INVISIBLE)
         }
 
     @Test
     fun isNotificationIconContainerVisible_allowedByDisableFlags_visible() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
             transitionKeyguardToGone()
 
-            disableFlagsRepository.disableFlags.value =
+            fakeDisableFlagsRepository.disableFlags.value =
                 DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
 
             assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
@@ -539,23 +587,55 @@
 
     @Test
     fun isNotificationIconContainerVisible_notAllowedByDisableFlags_gone() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
             transitionKeyguardToGone()
 
-            disableFlagsRepository.disableFlags.value =
+            fakeDisableFlagsRepository.disableFlags.value =
                 DisableFlagsModel(DISABLE_NOTIFICATION_ICONS, DISABLE2_NONE)
 
             assertThat(latest!!.visibility).isEqualTo(View.GONE)
         }
 
     @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun isNotificationIconContainerVisible_anyChipShowing_PromotedNotifsOn() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
+            transitionKeyguardToGone()
+
+            kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+            assertThat(latest!!.visibility).isEqualTo(View.GONE)
+
+            kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing
+
+            assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
+        }
+
+    @Test
+    @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun isNotificationIconContainerVisible_anyChipShowing_PromotedNotifsOff() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
+            transitionKeyguardToGone()
+
+            kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+            assertThat(latest!!.visibility).isEqualTo(View.GONE)
+
+            kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.DoingNothing
+
+            assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
+        }
+
+    @Test
     fun isSystemInfoVisible_allowedByDisableFlags_visible() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.systemInfoCombinedVis)
             transitionKeyguardToGone()
 
-            disableFlagsRepository.disableFlags.value =
+            fakeDisableFlagsRepository.disableFlags.value =
                 DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
 
             assertThat(latest!!.baseVisibility.visibility).isEqualTo(View.VISIBLE)
@@ -563,11 +643,11 @@
 
     @Test
     fun isSystemInfoVisible_notAllowedByDisableFlags_gone() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.systemInfoCombinedVis)
             transitionKeyguardToGone()
 
-            disableFlagsRepository.disableFlags.value =
+            fakeDisableFlagsRepository.disableFlags.value =
                 DisableFlagsModel(DISABLE_SYSTEM_INFO, DISABLE2_NONE)
 
             assertThat(latest!!.baseVisibility.visibility).isEqualTo(View.GONE)
@@ -575,7 +655,7 @@
 
     @Test
     fun systemInfoCombineVis_animationsPassThrough() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.systemInfoCombinedVis)
             transitionKeyguardToGone()
 
@@ -601,18 +681,18 @@
     @Test
     @DisableSceneContainer
     fun lockscreenVisible_sceneFlagOff_noStatusBarViewsShown() =
-        testScope.runTest {
+        kosmos.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
             val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
-            keyguardTransitionRepository.sendTransitionSteps(
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.GONE,
                 to = KeyguardState.LOCKSCREEN,
-                testScope = this,
+                testScope = testScope,
             )
 
-            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
@@ -620,14 +700,14 @@
     @Test
     @EnableSceneContainer
     fun lockscreenVisible_sceneFlagOn_noStatusBarViewsShown() =
-        testScope.runTest {
+        kosmos.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
             val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
 
-            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
@@ -635,18 +715,18 @@
     @Test
     @DisableSceneContainer
     fun bouncerVisible_sceneFlagOff_noStatusBarViewsShown() =
-        testScope.runTest {
+        kosmos.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
             val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
-            keyguardTransitionRepository.sendTransitionSteps(
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.PRIMARY_BOUNCER,
-                testScope = this,
+                testScope = testScope,
             )
 
-            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
@@ -654,14 +734,14 @@
     @Test
     @EnableSceneContainer
     fun bouncerVisible_sceneFlagOn_noStatusBarViewsShown() =
-        testScope.runTest {
+        kosmos.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
             val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Bouncer)
 
-            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
@@ -669,15 +749,15 @@
     @Test
     @DisableSceneContainer
     fun keyguardIsOccluded_sceneFlagOff_statusBarViewsShown() =
-        testScope.runTest {
+        kosmos.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
             val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
-            keyguardTransitionRepository.sendTransitionSteps(
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.OCCLUDED,
-                testScope = this,
+                testScope = testScope,
             )
 
             assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE)
@@ -688,7 +768,7 @@
     @Test
     @EnableSceneContainer
     fun keyguardIsOccluded_sceneFlagOn_statusBarViewsShown() =
-        testScope.runTest {
+        kosmos.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
             val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
@@ -704,7 +784,7 @@
     @Test
     @DisableSceneContainer
     fun keyguardNotShown_sceneFlagOff_statusBarViewsShown() =
-        testScope.runTest {
+        kosmos.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
             val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
@@ -719,7 +799,7 @@
     @Test
     @DisableSceneContainer
     fun shadeNotShown_sceneFlagOff_statusBarViewsShown() =
-        testScope.runTest {
+        kosmos.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
             val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
@@ -735,7 +815,7 @@
     @Test
     @EnableSceneContainer
     fun keyguardNotShownAndShadeNotShown_sceneFlagOn_statusBarViewsShown() =
-        testScope.runTest {
+        kosmos.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
             val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
@@ -750,7 +830,7 @@
     @Test
     @DisableSceneContainer
     fun shadeShown_sceneFlagOff_noStatusBarViewsShown() =
-        testScope.runTest {
+        kosmos.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
             val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
@@ -758,7 +838,7 @@
 
             kosmos.shadeTestUtil.setShadeExpansion(1f)
 
-            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
@@ -766,7 +846,7 @@
     @Test
     @EnableSceneContainer
     fun shadeShown_sceneFlagOn_noStatusBarViewsShown() =
-        testScope.runTest {
+        kosmos.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
             val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
@@ -774,7 +854,7 @@
 
             kosmos.sceneContainerRepository.snapToScene(Scenes.Shade)
 
-            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
@@ -782,20 +862,20 @@
     @Test
     @DisableSceneContainer
     fun secureCameraActive_sceneFlagOff_noStatusBarViewsShown() =
-        testScope.runTest {
+        kosmos.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
             val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
 
             // Secure camera is an occluding activity
-            keyguardTransitionRepository.sendTransitionSteps(
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.OCCLUDED,
-                testScope = this,
+                testScope = testScope,
             )
             kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
 
-            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
@@ -803,7 +883,7 @@
     @Test
     @EnableSceneContainer
     fun secureCameraActive_sceneFlagOn_noStatusBarViewsShown() =
-        testScope.runTest {
+        kosmos.runTest {
             val clockVisible by collectLastValue(underTest.isClockVisible)
             val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
             val systemInfoVisible by collectLastValue(underTest.systemInfoCombinedVis)
@@ -813,21 +893,26 @@
             kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, taskInfo = null)
             kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
 
-            assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+            assertThat(clockVisible!!.visibility).isEqualTo(View.INVISIBLE)
             assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
             assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE)
         }
 
+    // Cribbed from [HeadsUpNotificationInteractorTest.kt]
+    private fun fakeHeadsUpRowRepository(key: String = "test key", isPinned: Boolean = false) =
+        FakeHeadsUpRowRepository(key = key, isPinned = isPinned)
+
     private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) =
         ActiveNotificationsStore.Builder()
             .apply { notifications.forEach(::addIndividualNotif) }
             .build()
 
-    private val testNotifications =
+    private val testNotifications by lazy {
         listOf(activeNotificationModel(key = "notif1"), activeNotificationModel(key = "notif2"))
+    }
 
-    private suspend fun transitionKeyguardToGone() {
-        keyguardTransitionRepository.sendTransitionSteps(
+    private suspend fun Kosmos.transitionKeyguardToGone() {
+        fakeKeyguardTransitionRepository.sendTransitionSteps(
             from = KeyguardState.LOCKSCREEN,
             to = KeyguardState.GONE,
             testScope = testScope,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 4eef308..f8d45e4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -1248,10 +1248,12 @@
                 otherUserMockContext,
             )
             userRepository.setSelectedUserInfo(ANOTHER_USER)
+            verify(wifiPickerTracker).onStop()
 
             // THEN we use the different user's context to create WifiPickerTracker
             val newCaptor = argumentCaptor<Context>()
             verify(wifiPickerTrackerFactory).create(newCaptor.capture(), any(), any(), any())
+            verify(wifiPickerTracker).onStop()
             assertThat(newCaptor.firstValue).isEqualTo(otherUserMockContext)
         }
 
@@ -1288,6 +1290,7 @@
 
             // THEN we do NOT re-create WifiPickerTracker because the multiuser flag is off
             verify(wifiPickerTrackerFactory, never()).create(any(), any(), any(), any())
+            verify(wifiPickerTracker, never()).onStop()
         }
 
     private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback {
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
deleted file mode 100644
index c5eed73..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ /dev/null
@@ -1,417 +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.statusbar.policy
-
-import android.app.Notification
-import android.os.Handler
-import android.platform.test.annotations.EnableFlags
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.statusbar.statusBarStateController
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
-import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
-import com.android.systemui.statusbar.phone.keyguardBypassController
-import com.android.systemui.statusbar.policy.HeadsUpManagerTestUtil.createFullScreenIntentEntry
-import com.android.systemui.testKosmos
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.kotlin.JavaAdapter
-import com.android.systemui.util.settings.FakeGlobalSettings
-import com.android.systemui.util.time.FakeSystemClock
-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.ArgumentMatchers
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.invocation.InvocationOnMock
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-
-@SmallTest
-@RunWithLooper
-@RunWith(AndroidJUnit4::class)
-@EnableFlags(NotificationThrottleHun.FLAG_NAME)
-class AvalancheControllerTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-
-    // For creating mocks
-    @get:Rule var rule: MockitoRule = MockitoJUnit.rule()
-    @Mock private val runnableMock: Runnable? = null
-
-    // For creating AvalancheController
-    @Mock private lateinit var dumpManager: DumpManager
-    private lateinit var mAvalancheController: AvalancheController
-
-    // For creating TestableHeadsUpManager
-    @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
-    private val mUiEventLoggerFake = UiEventLoggerFake()
-    @Mock private lateinit var mHeadsUpManagerLogger: HeadsUpManagerLogger
-    @Mock private lateinit var mBgHandler: Handler
-
-    private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
-    private val mGlobalSettings = FakeGlobalSettings()
-    private val mSystemClock = FakeSystemClock()
-    private val mExecutor = FakeExecutor(mSystemClock)
-    private lateinit var testableHeadsUpManager: BaseHeadsUpManager
-
-    @Before
-    fun setUp() {
-        // Use default non-a11y timeout
-        Mockito.`when`(
-                mAccessibilityMgr!!.getRecommendedTimeoutMillis(
-                    ArgumentMatchers.anyInt(),
-                    ArgumentMatchers.anyInt(),
-                )
-            )
-            .then { i: InvocationOnMock -> i.getArgument(0) }
-
-        // Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
-        // declaration, where mocks are null
-        mAvalancheController =
-            AvalancheController(dumpManager, mUiEventLoggerFake, mHeadsUpManagerLogger, mBgHandler)
-
-        testableHeadsUpManager =
-            TestableHeadsUpManager(
-                mContext,
-                mLogger,
-                kosmos.statusBarStateController,
-                kosmos.keyguardBypassController,
-                GroupMembershipManagerImpl(),
-                kosmos.visualStabilityProvider,
-                kosmos.configurationController,
-                mExecutor,
-                mGlobalSettings,
-                mSystemClock,
-                mAccessibilityMgr,
-                mUiEventLoggerFake,
-                JavaAdapter(kosmos.testScope),
-                kosmos.shadeInteractor,
-                mAvalancheController,
-            )
-    }
-
-    private fun createHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
-        return testableHeadsUpManager.createHeadsUpEntry(
-            NotificationEntryBuilder()
-                .setSbn(HeadsUpManagerTestUtil.createSbn(id, Notification.Builder(mContext, "")))
-                .build()
-        )
-    }
-
-    private fun createFsiHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
-        return testableHeadsUpManager.createHeadsUpEntry(createFullScreenIntentEntry(id, mContext))
-    }
-
-    @Test
-    fun testUpdate_isShowing_runsRunnable() {
-        // Entry is showing
-        val headsUpEntry = createHeadsUpEntry(id = 0)
-        mAvalancheController.headsUpEntryShowing = headsUpEntry
-
-        // Update
-        mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
-
-        // Runnable was run
-        Mockito.verify(runnableMock, Mockito.times(1)).run()
-    }
-
-    @Test
-    fun testUpdate_noneShowingAndNotNext_showNow() {
-        val headsUpEntry = createHeadsUpEntry(id = 0)
-
-        // None showing
-        mAvalancheController.headsUpEntryShowing = null
-
-        // Entry is NOT next
-        mAvalancheController.clearNext()
-
-        // Update
-        mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
-
-        // Entry is showing now
-        assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(headsUpEntry)
-    }
-
-    @Test
-    fun testUpdate_isNext_addsRunnable() {
-        // Another entry is already showing
-        val otherShowingEntry = createHeadsUpEntry(id = 0)
-        mAvalancheController.headsUpEntryShowing = otherShowingEntry
-
-        // Entry is next
-        val headsUpEntry = createHeadsUpEntry(id = 1)
-        mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
-
-        // Entry has one Runnable
-        val runnableList: List<Runnable?>? = mAvalancheController.nextMap[headsUpEntry]
-        assertThat(runnableList).isNotNull()
-        assertThat(runnableList!!.size).isEqualTo(1)
-
-        // Update
-        mAvalancheController.update(headsUpEntry, runnableMock, "testLabel")
-
-        // Entry has two Runnables
-        assertThat(runnableList.size).isEqualTo(2)
-    }
-
-    @Test
-    fun testUpdate_isNotNextWithOtherHunShowing_isNext() {
-        val headsUpEntry = createHeadsUpEntry(id = 0)
-
-        // Another entry is already showing
-        val otherShowingEntry = createHeadsUpEntry(id = 1)
-        mAvalancheController.headsUpEntryShowing = otherShowingEntry
-
-        // Entry is NOT next
-        mAvalancheController.clearNext()
-
-        // Update
-        mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
-
-        // Entry is next
-        assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isTrue()
-    }
-
-    @Test
-    fun testDelete_untracked_runnableRuns() {
-        val headsUpEntry = createHeadsUpEntry(id = 0)
-
-        // None showing
-        mAvalancheController.headsUpEntryShowing = null
-
-        // Nothing is next
-        mAvalancheController.clearNext()
-
-        // Delete
-        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
-
-        // Runnable was run
-        Mockito.verify(runnableMock, Mockito.times(1)).run()
-    }
-
-    @Test
-    fun testDelete_isNext_removedFromNext_runnableNotRun() {
-        // Entry is next
-        val headsUpEntry = createHeadsUpEntry(id = 0)
-        mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
-
-        // Delete
-        mAvalancheController.delete(headsUpEntry, runnableMock, "testLabel")
-
-        // Entry was removed from next
-        assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isFalse()
-
-        // Runnable was not run
-        Mockito.verify(runnableMock, Mockito.times(0)).run()
-    }
-
-    @Test
-    fun testDelete_wasDropped_removedFromDropSet() {
-        // Entry was dropped
-        val headsUpEntry = createHeadsUpEntry(id = 0)
-        mAvalancheController.debugDropSet.add(headsUpEntry)
-
-        // Delete
-        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
-
-        // Entry was removed from dropSet
-        assertThat(mAvalancheController.debugDropSet.contains(headsUpEntry)).isFalse()
-    }
-
-    @Test
-    fun testDelete_wasDropped_runnableNotRun() {
-        // Entry was dropped
-        val headsUpEntry = createHeadsUpEntry(id = 0)
-        mAvalancheController.debugDropSet.add(headsUpEntry)
-
-        // Delete
-        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
-
-        // Runnable was not run
-        Mockito.verify(runnableMock, Mockito.times(0)).run()
-    }
-
-    @Test
-    fun testDelete_isShowing_runnableRun() {
-        // Entry is showing
-        val headsUpEntry = createHeadsUpEntry(id = 0)
-        mAvalancheController.headsUpEntryShowing = headsUpEntry
-
-        // Delete
-        mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
-
-        // Runnable was run
-        Mockito.verify(runnableMock, Mockito.times(1)).run()
-    }
-
-    @Test
-    fun testDelete_isShowing_showNext() {
-        // 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")
-
-        // Next entry is shown
-        assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry)
-    }
-
-    @Test
-    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
-
-        // Delete
-        mAvalancheController.delete(showingEntry, runnableMock!!, "testLabel")
-
-        // Next entry is shown
-        assertThat(mAvalancheController.previousHunKey).isEqualTo("")
-    }
-
-    @Test
-    fun testGetDurationMs_untrackedEntryEmptyAvalanche_useAutoDismissTime() {
-        val givenEntry = createHeadsUpEntry(id = 0)
-
-        // Nothing is showing
-        mAvalancheController.headsUpEntryShowing = null
-
-        // Nothing is next
-        mAvalancheController.clearNext()
-
-        val durationMs = mAvalancheController.getDurationMs(givenEntry, autoDismissMs = 5000)
-        assertThat(durationMs).isEqualTo(5000)
-    }
-
-    @Test
-    fun testGetDurationMs_untrackedEntryNonEmptyAvalanche_useAutoDismissTime() {
-        val givenEntry = createHeadsUpEntry(id = 0)
-
-        // Given entry not tracked
-        mAvalancheController.headsUpEntryShowing = createHeadsUpEntry(id = 1)
-
-        mAvalancheController.clearNext()
-        val nextEntry = createHeadsUpEntry(id = 2)
-        mAvalancheController.addToNext(nextEntry, runnableMock!!)
-
-        val durationMs = mAvalancheController.getDurationMs(givenEntry, autoDismissMs = 5000)
-        assertThat(durationMs).isEqualTo(5000)
-    }
-
-    @Test
-    fun testGetDurationMs_lastEntry_useAutoDismissTime() {
-        // Entry is showing
-        val showingEntry = createHeadsUpEntry(id = 0)
-        mAvalancheController.headsUpEntryShowing = showingEntry
-
-        // Nothing is next
-        mAvalancheController.clearNext()
-
-        val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
-        assertThat(durationMs).isEqualTo(5000)
-    }
-
-    @Test
-    fun testGetDurationMs_nextEntryLowerPriority_5000() {
-        // Entry is showing
-        val showingEntry = createFsiHeadsUpEntry(id = 1)
-        mAvalancheController.headsUpEntryShowing = showingEntry
-
-        // There's another entry waiting to show next
-        val nextEntry = createHeadsUpEntry(id = 0)
-        mAvalancheController.addToNext(nextEntry, runnableMock!!)
-
-        // Next entry has lower priority
-        assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(1)
-
-        val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
-        assertThat(durationMs).isEqualTo(5000)
-    }
-
-    @Test
-    fun testGetDurationMs_nextEntrySamePriority_1000() {
-        // 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!!)
-
-        // Same priority
-        assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(0)
-
-        val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
-        assertThat(durationMs).isEqualTo(1000)
-    }
-
-    @Test
-    fun testGetDurationMs_nextEntryHigherPriority_500() {
-        // Entry is showing
-        val showingEntry = createHeadsUpEntry(id = 0)
-        mAvalancheController.headsUpEntryShowing = showingEntry
-
-        // There's another entry waiting to show next
-        val nextEntry = createFsiHeadsUpEntry(id = 1)
-        mAvalancheController.addToNext(nextEntry, runnableMock!!)
-
-        // Next entry has higher priority
-        assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(-1)
-
-        val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
-        assertThat(durationMs).isEqualTo(500)
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
deleted file mode 100644
index 0fbee6d..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ /dev/null
@@ -1,662 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
-
-import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Person;
-import android.os.Handler;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.FlagsParameterization;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.res.R;
-import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.systemui.util.settings.FakeGlobalSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
-import java.util.List;
-
-@SmallTest
-@TestableLooper.RunWithLooper
-@RunWith(ParameterizedAndroidJunit4.class)
-// TODO(b/378142453): Merge this with BaseHeadsUpManagerTest.
-public class BaseHeadsUpManagerTest extends SysuiTestCase {
-    protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
-
-    @Rule
-    public MockitoRule rule = MockitoJUnit.rule();
-
-    static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
-    static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
-
-    private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
-
-    private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
-    @Mock private Handler mBgHandler;
-    @Mock private DumpManager dumpManager;
-    @Mock private ShadeInteractor mShadeInteractor;
-    private AvalancheController mAvalancheController;
-
-    @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
-
-    protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
-    protected static final int TEST_AUTO_DISMISS_TIME = 600;
-    protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
-    // Number of notifications to use in tests requiring multiple notifications
-    private static final int TEST_NUM_NOTIFICATIONS = 4;
-
-    protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
-    protected final FakeSystemClock mSystemClock = new FakeSystemClock();
-    protected final FakeExecutor mExecutor = new FakeExecutor(mSystemClock);
-
-    @Mock protected ExpandableNotificationRow mRow;
-
-    static {
-        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
-        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
-        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
-    }
-
-    private BaseHeadsUpManager createHeadsUpManager() {
-        return new TestableHeadsUpManager(
-                mContext,
-                mLogger,
-                mKosmos.getStatusBarStateController(),
-                mKosmos.getKeyguardBypassController(),
-                new GroupMembershipManagerImpl(),
-                mKosmos.getVisualStabilityProvider(),
-                mKosmos.getConfigurationController(),
-                mExecutor,
-                mGlobalSettings,
-                mSystemClock,
-                mAccessibilityMgr,
-                mUiEventLoggerFake,
-                new JavaAdapter(mKosmos.getTestScope()),
-                mShadeInteractor,
-                mAvalancheController);
-    }
-
-    private NotificationEntry createStickyEntry(int id) {
-        final Notification notif = new Notification.Builder(mContext, "")
-                .setSmallIcon(R.drawable.ic_person)
-                .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
-                .build();
-        return HeadsUpManagerTestUtil.createEntry(id, notif);
-    }
-
-    private NotificationEntry createStickyForSomeTimeEntry(int id) {
-        final Notification notif = new Notification.Builder(mContext, "")
-                .setSmallIcon(R.drawable.ic_person)
-                .setFlag(FLAG_FSI_REQUESTED_BUT_DENIED, true)
-                .build();
-        return HeadsUpManagerTestUtil.createEntry(id, notif);
-    }
-
-    private void useAccessibilityTimeout(boolean use) {
-        if (use) {
-            doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
-                    .getRecommendedTimeoutMillis(anyInt(), anyInt());
-        } else {
-            when(mAccessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt())).then(
-                    i -> i.getArgument(0));
-        }
-    }
-
-    @Parameters(name = "{0}")
-    public static List<FlagsParameterization> getFlags() {
-        return FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME);
-    }
-
-    public BaseHeadsUpManagerTest(FlagsParameterization flags) {
-        mSetFlagsRule.setFlagsParameterization(flags);
-    }
-
-    @Override
-    public void SysuiSetup() throws Exception {
-        super.SysuiSetup();
-        mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mLogger,
-                mBgHandler);
-        when(mShadeInteractor.isAnyExpanded()).thenReturn(MutableStateFlow(true));
-        when(mKosmos.getKeyguardBypassController().getBypassEnabled()).thenReturn(false);
-    }
-
-    @Test
-    public void testHasNotifications_headsUpManagerMapNotEmpty_true() {
-        final BaseHeadsUpManager bhum = createHeadsUpManager();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-        bhum.showNotification(entry);
-
-        assertThat(bhum.mHeadsUpEntryMap).isNotEmpty();
-        assertThat(bhum.hasNotifications()).isTrue();
-    }
-
-    @Test
-    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
-    public void testHasNotifications_avalancheMapNotEmpty_true() {
-        final BaseHeadsUpManager bhum = createHeadsUpManager();
-        final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
-                mContext);
-        final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry);
-        mAvalancheController.addToNext(headsUpEntry, () -> {});
-
-        assertThat(mAvalancheController.getWaitingEntryList()).isNotEmpty();
-        assertThat(bhum.hasNotifications()).isTrue();
-    }
-
-    @Test
-    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
-    public void testHasNotifications_false() {
-        final BaseHeadsUpManager bhum = createHeadsUpManager();
-        assertThat(bhum.mHeadsUpEntryMap).isEmpty();
-        assertThat(mAvalancheController.getWaitingEntryList()).isEmpty();
-        assertThat(bhum.hasNotifications()).isFalse();
-    }
-
-    @Test
-    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
-    public void testGetHeadsUpEntryList_includesAvalancheEntryList() {
-        final BaseHeadsUpManager bhum = createHeadsUpManager();
-        final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
-                mContext);
-        final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry);
-        mAvalancheController.addToNext(headsUpEntry, () -> {});
-
-        assertThat(bhum.getHeadsUpEntryList()).contains(headsUpEntry);
-    }
-
-    @Test
-    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
-    public void testGetHeadsUpEntry_returnsAvalancheEntry() {
-        final BaseHeadsUpManager bhum = createHeadsUpManager();
-        final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
-                mContext);
-        final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry);
-        mAvalancheController.addToNext(headsUpEntry, () -> {});
-
-        assertThat(bhum.getHeadsUpEntry(notifEntry.getKey())).isEqualTo(headsUpEntry);
-    }
-
-    @Test
-    public void testShowNotification_addsEntry() {
-        final BaseHeadsUpManager alm = createHeadsUpManager();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
-        alm.showNotification(entry);
-
-        assertTrue(alm.isHeadsUpEntry(entry.getKey()));
-        assertTrue(alm.hasNotifications());
-        assertEquals(entry, alm.getEntry(entry.getKey()));
-    }
-
-    @Test
-    public void testShowNotification_autoDismisses() {
-        final BaseHeadsUpManager alm = createHeadsUpManager();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
-        alm.showNotification(entry);
-        mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME * 3 / 2);
-
-        assertFalse(alm.isHeadsUpEntry(entry.getKey()));
-    }
-
-    @Test
-    public void testRemoveNotification_removeDeferred() {
-        final BaseHeadsUpManager alm = createHeadsUpManager();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
-        alm.showNotification(entry);
-
-        final boolean removedImmediately = alm.removeNotification(
-                entry.getKey(), /* releaseImmediately = */ false, "removeDeferred");
-        assertFalse(removedImmediately);
-        assertTrue(alm.isHeadsUpEntry(entry.getKey()));
-    }
-
-    @Test
-    public void testRemoveNotification_forceRemove() {
-        final BaseHeadsUpManager alm = createHeadsUpManager();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
-        alm.showNotification(entry);
-
-        final boolean removedImmediately = alm.removeNotification(
-                entry.getKey(), /* releaseImmediately = */ true, "forceRemove");
-        assertTrue(removedImmediately);
-        assertFalse(alm.isHeadsUpEntry(entry.getKey()));
-    }
-
-    @Test
-    public void testReleaseAllImmediately() {
-        final BaseHeadsUpManager alm = createHeadsUpManager();
-        for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
-            final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(i, mContext);
-            entry.setRow(mRow);
-            alm.showNotification(entry);
-        }
-
-        alm.releaseAllImmediately();
-
-        assertEquals(0, alm.getAllEntries().count());
-    }
-
-    @Test
-    public void testCanRemoveImmediately_notShownLongEnough() {
-        final BaseHeadsUpManager alm = createHeadsUpManager();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
-        alm.showNotification(entry);
-
-        // The entry has just been added so we should not remove immediately.
-        assertFalse(alm.canRemoveImmediately(entry.getKey()));
-    }
-
-    @Test
-    public void testHunRemovedLogging() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
-                mContext);
-        final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = mock(
-                BaseHeadsUpManager.HeadsUpEntry.class);
-        headsUpEntry.mEntry = notifEntry;
-
-        hum.onEntryRemoved(headsUpEntry, "test");
-
-        verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry));
-    }
-
-
-    @Test
-    public void testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-        useAccessibilityTimeout(false);
-
-        hum.showNotification(entry);
-        mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME);
-
-        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
-    }
-
-
-    @Test
-    public void testShowNotification_autoDismissesWithDefaultTimeout() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-        useAccessibilityTimeout(false);
-
-        hum.showNotification(entry);
-        mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
-                + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
-
-        assertFalse(hum.isHeadsUpEntry(entry.getKey()));
-    }
-
-
-    @Test
-    public void testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
-        useAccessibilityTimeout(false);
-
-        hum.showNotification(entry);
-        mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
-                + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2);
-
-        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
-    }
-
-
-    @Test
-    public void testShowNotification_sticky_neverAutoDismisses() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = createStickyEntry(/* id = */ 0);
-        useAccessibilityTimeout(false);
-
-        hum.showNotification(entry);
-        mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME);
-
-        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
-    }
-
-
-    @Test
-    public void testShowNotification_autoDismissesWithAccessibilityTimeout() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-        useAccessibilityTimeout(true);
-
-        hum.showNotification(entry);
-        mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
-                + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
-
-        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
-    }
-
-
-    @Test
-    public void testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
-        useAccessibilityTimeout(true);
-
-        hum.showNotification(entry);
-        mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
-                + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
-
-        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
-    }
-
-
-    @Test
-    public void testRemoveNotification_beforeMinimumDisplayTime() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-        useAccessibilityTimeout(false);
-
-        hum.showNotification(entry);
-
-        final boolean removedImmediately = hum.removeNotification(
-                entry.getKey(), /* releaseImmediately = */ false, "beforeMinimumDisplayTime");
-        assertFalse(removedImmediately);
-        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
-
-        mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2);
-
-        assertFalse(hum.isHeadsUpEntry(entry.getKey()));
-    }
-
-
-    @Test
-    public void testRemoveNotification_afterMinimumDisplayTime() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-        useAccessibilityTimeout(false);
-
-        hum.showNotification(entry);
-        mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2);
-
-        assertTrue(hum.isHeadsUpEntry(entry.getKey()));
-
-        final boolean removedImmediately = hum.removeNotification(
-                entry.getKey(), /* releaseImmediately = */ false, "afterMinimumDisplayTime");
-        assertTrue(removedImmediately);
-        assertFalse(hum.isHeadsUpEntry(entry.getKey()));
-    }
-
-
-    @Test
-    public void testRemoveNotification_releaseImmediately() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
-        hum.showNotification(entry);
-
-        final boolean removedImmediately = hum.removeNotification(
-                entry.getKey(), /* releaseImmediately = */ true, "afterMinimumDisplayTime");
-        assertTrue(removedImmediately);
-        assertFalse(hum.isHeadsUpEntry(entry.getKey()));
-    }
-
-
-    @Test
-    public void testIsSticky_rowPinnedAndExpanded_true() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
-                mContext);
-        when(mRow.isPinned()).thenReturn(true);
-        notifEntry.setRow(mRow);
-
-        hum.showNotification(notifEntry);
-
-        final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
-                notifEntry.getKey());
-        headsUpEntry.setExpanded(true);
-
-        assertTrue(hum.isSticky(notifEntry.getKey()));
-    }
-
-    @Test
-    public void testIsSticky_remoteInputActive_true() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
-                mContext);
-
-        hum.showNotification(notifEntry);
-
-        final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
-                notifEntry.getKey());
-        headsUpEntry.mRemoteInputActive = true;
-
-        assertTrue(hum.isSticky(notifEntry.getKey()));
-    }
-
-    @Test
-    public void testIsSticky_hasFullScreenIntent_true() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry =
-                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext);
-
-        hum.showNotification(notifEntry);
-
-        assertTrue(hum.isSticky(notifEntry.getKey()));
-    }
-
-
-    @Test
-    public void testIsSticky_stickyForSomeTime_false() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
-
-        hum.showNotification(entry);
-
-        assertFalse(hum.isSticky(entry.getKey()));
-    }
-
-
-    @Test
-    public void testIsSticky_false() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
-                mContext);
-
-        hum.showNotification(notifEntry);
-
-        final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
-                notifEntry.getKey());
-        headsUpEntry.setExpanded(false);
-        headsUpEntry.mRemoteInputActive = false;
-
-        assertFalse(hum.isSticky(notifEntry.getKey()));
-    }
-
-    @Test
-    public void testCompareTo_withNullEntries() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
-
-        hum.showNotification(alertEntry);
-
-        assertThat(hum.compare(alertEntry, null)).isLessThan(0);
-        assertThat(hum.compare(null, alertEntry)).isGreaterThan(0);
-        assertThat(hum.compare(null, null)).isEqualTo(0);
-    }
-
-    @Test
-    public void testCompareTo_withNonAlertEntries() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-
-        final NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag(
-                "nae1").build();
-        final NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag(
-                "nae2").build();
-        final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
-        hum.showNotification(alertEntry);
-
-        assertThat(hum.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
-        assertThat(hum.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
-        assertThat(hum.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
-    }
-
-    @Test
-    public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-
-        final BaseHeadsUpManager.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry(
-                new NotificationEntryBuilder()
-                        .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0,
-                                new Notification.Builder(mContext, "")
-                                        .setCategory(Notification.CATEGORY_CALL)
-                                        .setOngoing(true)))
-                        .build());
-
-        final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry(
-                HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext));
-        activeRemoteInput.mRemoteInputActive = true;
-
-        assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0);
-        assertThat(activeRemoteInput.compareTo(ongoingCall)).isGreaterThan(0);
-    }
-
-    @Test
-    public void testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-
-        final Person person = new Person.Builder().setName("person").build();
-        final PendingIntent intent = mock(PendingIntent.class);
-        final BaseHeadsUpManager.HeadsUpEntry incomingCall = hum.new HeadsUpEntry(
-                new NotificationEntryBuilder()
-                        .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0,
-                                new Notification.Builder(mContext, "")
-                                        .setStyle(Notification.CallStyle
-                                                .forIncomingCall(person, intent, intent))))
-                        .build());
-
-        final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry(
-                HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext));
-        activeRemoteInput.mRemoteInputActive = true;
-
-        assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0);
-        assertThat(activeRemoteInput.compareTo(incomingCall)).isGreaterThan(0);
-    }
-
-    @Test
-    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
-    public void testPinEntry_logsPeek_throttleEnabled() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-
-        // Needs full screen intent in order to be pinned
-        final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry(
-                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext));
-
-        // Note: the standard way to show a notification would be calling showNotification rather
-        // than onAlertEntryAdded. However, in practice showNotification in effect adds
-        // the notification and then updates it; in order to not log twice, the entry needs
-        // to have a functional ExpandableNotificationRow that can keep track of whether it's
-        // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
-        hum.onEntryAdded(entryToPin);
-
-        assertEquals(2, mUiEventLoggerFake.numLogs());
-        assertEquals(AvalancheController.ThrottleEvent.AVALANCHE_THROTTLING_HUN_SHOWN.getId(),
-                mUiEventLoggerFake.eventId(0));
-        assertEquals(BaseHeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
-                mUiEventLoggerFake.eventId(1));
-    }
-
-    @Test
-    @DisableFlags(NotificationThrottleHun.FLAG_NAME)
-    public void testPinEntry_logsPeek_throttleDisabled() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-
-        // Needs full screen intent in order to be pinned
-        final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry(
-                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext));
-
-        // Note: the standard way to show a notification would be calling showNotification rather
-        // than onAlertEntryAdded. However, in practice showNotification in effect adds
-        // the notification and then updates it; in order to not log twice, the entry needs
-        // to have a functional ExpandableNotificationRow that can keep track of whether it's
-        // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
-        hum.onEntryAdded(entryToPin);
-
-        assertEquals(1, mUiEventLoggerFake.numLogs());
-        assertEquals(BaseHeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
-                mUiEventLoggerFake.eventId(0));
-    }
-
-    @Test
-    public void testSetUserActionMayIndirectlyRemove() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
-                mContext);
-
-        hum.showNotification(notifEntry);
-
-        assertFalse(hum.canRemoveImmediately(notifEntry.getKey()));
-
-        hum.setUserActionMayIndirectlyRemove(notifEntry);
-
-        assertTrue(hum.canRemoveImmediately(notifEntry.getKey()));
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
deleted file mode 100644
index 6175e05..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ /dev/null
@@ -1,387 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.policy
-
-import android.os.Handler
-import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.FlagsParameterization
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.andSceneContainer
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.res.R
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.statusbar.FakeStatusBarStateController
-import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
-import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
-import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.testKosmos
-import com.android.systemui.util.concurrency.mockExecutorHandler
-import com.android.systemui.util.kotlin.JavaAdapter
-import com.google.common.truth.Truth.assertThat
-import junit.framework.Assert
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
-import org.mockito.Mock
-import org.mockito.kotlin.whenever
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(ParameterizedAndroidJunit4::class)
-@RunWithLooper
-class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManagerTest(flags) {
-
-    private val mHeadsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer())
-
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-
-    @Mock private lateinit var mGroupManager: GroupMembershipManager
-
-    @Mock private lateinit var mVSProvider: VisualStabilityProvider
-
-    val statusBarStateController = FakeStatusBarStateController()
-
-    @Mock private lateinit var mBypassController: KeyguardBypassController
-
-    @Mock private lateinit var mConfigurationController: ConfigurationControllerImpl
-
-    @Mock private lateinit var mAccessibilityManagerWrapper: AccessibilityManagerWrapper
-
-    @Mock private lateinit var mUiEventLogger: UiEventLogger
-
-    private val mJavaAdapter: JavaAdapter = JavaAdapter(testScope.backgroundScope)
-
-    @Mock private lateinit var mShadeInteractor: ShadeInteractor
-    @Mock private lateinit var dumpManager: DumpManager
-    private lateinit var mAvalancheController: AvalancheController
-
-    @Mock private lateinit var mBgHandler: Handler
-
-    private fun createHeadsUpManagerPhone(): BaseHeadsUpManager {
-        return BaseHeadsUpManager(
-            mContext,
-            mHeadsUpManagerLogger,
-            statusBarStateController,
-            mBypassController,
-            mGroupManager,
-            mVSProvider,
-            mConfigurationController,
-            mockExecutorHandler(mExecutor),
-            mGlobalSettings,
-            mSystemClock,
-            mExecutor,
-            mAccessibilityManagerWrapper,
-            mUiEventLogger,
-            mJavaAdapter,
-            mShadeInteractor,
-            mAvalancheController,
-        )
-    }
-
-    @Before
-    fun setUp() {
-        whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(false))
-        whenever(mShadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
-        whenever(mBypassController.bypassEnabled).thenReturn(false)
-        whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
-        val accessibilityMgr =
-            mDependency.injectMockDependency(AccessibilityManagerWrapper::class.java)
-        whenever(
-                accessibilityMgr.getRecommendedTimeoutMillis(
-                    ArgumentMatchers.anyInt(),
-                    ArgumentMatchers.anyInt(),
-                )
-            )
-            .thenReturn(TEST_AUTO_DISMISS_TIME)
-        mDependency.injectMockDependency(NotificationShadeWindowController::class.java)
-        mContext
-            .getOrCreateTestableResources()
-            .addOverride(R.integer.ambient_notification_extension_time, 500)
-        mAvalancheController =
-            AvalancheController(dumpManager, mUiEventLogger, mHeadsUpManagerLogger, mBgHandler)
-    }
-
-    @Test
-    fun testSnooze() {
-        val hmp: HeadsUpManager = createHeadsUpManagerPhone()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        hmp.showNotification(entry)
-        hmp.snooze()
-        Assert.assertTrue(hmp.isSnoozed(entry.sbn.packageName))
-    }
-
-    @Test
-    fun testSwipedOutNotification() {
-        val hmp: HeadsUpManager = createHeadsUpManagerPhone()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        hmp.showNotification(entry)
-        hmp.addSwipedOutNotification(entry.key)
-
-        // Remove should succeed because the notification is swiped out
-        val removedImmediately =
-            hmp.removeNotification(
-                entry.key,
-                /* releaseImmediately= */ false,
-                /* reason= */ "swipe out",
-            )
-        Assert.assertTrue(removedImmediately)
-        Assert.assertFalse(hmp.isHeadsUpEntry(entry.key))
-    }
-
-    @Test
-    fun testCanRemoveImmediately_swipedOut() {
-        val hmp: HeadsUpManager = createHeadsUpManagerPhone()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        hmp.showNotification(entry)
-        hmp.addSwipedOutNotification(entry.key)
-
-        // Notification is swiped so it can be immediately removed.
-        Assert.assertTrue(hmp.canRemoveImmediately(entry.key))
-    }
-
-    @Ignore("b/141538055")
-    @Test
-    fun testCanRemoveImmediately_notTopEntry() {
-        val hmp: HeadsUpManager = createHeadsUpManagerPhone()
-        val earlierEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        val laterEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext)
-        laterEntry.row = mRow
-        hmp.showNotification(earlierEntry)
-        hmp.showNotification(laterEntry)
-
-        // Notification is "behind" a higher priority notification so we can remove it immediately.
-        Assert.assertTrue(hmp.canRemoveImmediately(earlierEntry.key))
-    }
-
-    @Test
-    fun testExtendHeadsUp() {
-        val hmp = createHeadsUpManagerPhone()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        hmp.showNotification(entry)
-        hmp.extendHeadsUp()
-        mSystemClock.advanceTime((TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2).toLong())
-        Assert.assertTrue(hmp.isHeadsUpEntry(entry.key))
-    }
-
-    @Test
-    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
-    fun testShowNotification_removeWhenReorderingAllowedTrue() {
-        whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
-        val hmp = createHeadsUpManagerPhone()
-
-        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        hmp.showNotification(notifEntry)
-        assertThat(hmp.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue()
-    }
-
-    class TestAnimationStateHandler : AnimationStateHandler {
-        override fun setHeadsUpGoingAwayAnimationsAllowed(allowed: Boolean) {}
-    }
-
-    @Test
-    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
-    fun testReorderingAllowed_clearsListOfEntriesToRemove() {
-        whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
-        val hmp = createHeadsUpManagerPhone()
-
-        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        hmp.showNotification(notifEntry)
-        assertThat(hmp.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue()
-
-        hmp.setAnimationStateHandler(TestAnimationStateHandler())
-        hmp.mOnReorderingAllowedListener.onReorderingAllowed()
-        assertThat(hmp.mEntriesToRemoveWhenReorderingAllowed.isEmpty()).isTrue()
-    }
-
-    @Test
-    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
-    fun testShowNotification_reorderNotAllowed_seenInShadeTrue() {
-        whenever(mVSProvider.isReorderingAllowed).thenReturn(false)
-        val hmp = createHeadsUpManagerPhone()
-
-        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        hmp.showNotification(notifEntry)
-        assertThat(notifEntry.isSeenInShade).isTrue()
-    }
-
-    @Test
-    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
-    fun testShowNotification_reorderAllowed_seenInShadeFalse() {
-        whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
-        val hmp = createHeadsUpManagerPhone()
-
-        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        hmp.showNotification(notifEntry)
-        assertThat(notifEntry.isSeenInShade).isFalse()
-    }
-
-    @Test
-    fun testShouldHeadsUpBecomePinned_noFSI_false() =
-        testScope.runTest {
-            val hum = createHeadsUpManagerPhone()
-            statusBarStateController.setState(StatusBarState.KEYGUARD)
-
-            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-
-            Assert.assertFalse(hum.shouldHeadsUpBecomePinned(entry))
-        }
-
-    @Test
-    fun testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() =
-        testScope.runTest {
-            val hum = createHeadsUpManagerPhone()
-            statusBarStateController.setState(StatusBarState.KEYGUARD)
-
-            val notifEntry =
-                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
-
-            // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
-            hum.showNotification(notifEntry)
-
-            val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key)
-            headsUpEntry!!.mWasUnpinned = false
-
-            Assert.assertTrue(hum.shouldHeadsUpBecomePinned(notifEntry))
-        }
-
-    @Test
-    fun testShouldHeadsUpBecomePinned_wasUnpinned_false() =
-        testScope.runTest {
-            val hum = createHeadsUpManagerPhone()
-            statusBarStateController.setState(StatusBarState.KEYGUARD)
-
-            val notifEntry =
-                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
-
-            // Add notifEntry to ANM mAlertEntries map and make it unpinned
-            hum.showNotification(notifEntry)
-
-            val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key)
-            headsUpEntry!!.mWasUnpinned = true
-
-            Assert.assertFalse(hum.shouldHeadsUpBecomePinned(notifEntry))
-        }
-
-    @Test
-    fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() =
-        testScope.runTest {
-            // GIVEN
-            whenever(mShadeInteractor.isAnyFullyExpanded).thenReturn(MutableStateFlow(false))
-            val hmp = createHeadsUpManagerPhone()
-            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-            statusBarStateController.setState(StatusBarState.SHADE)
-            runCurrent()
-
-            // THEN
-            Assert.assertTrue(hmp.shouldHeadsUpBecomePinned(entry))
-        }
-
-    @Test
-    fun shouldHeadsUpBecomePinned_shadeLocked_false() =
-        testScope.runTest {
-            // GIVEN
-            val hmp = createHeadsUpManagerPhone()
-            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-            statusBarStateController.setState(StatusBarState.SHADE_LOCKED)
-            runCurrent()
-
-            // THEN
-            Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
-        }
-
-    @Test
-    fun shouldHeadsUpBecomePinned_shadeUnknown_false() =
-        testScope.runTest {
-            // GIVEN
-            val hmp = createHeadsUpManagerPhone()
-            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-            statusBarStateController.setState(1207)
-            runCurrent()
-
-            // THEN
-            Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
-        }
-
-    @Test
-    fun shouldHeadsUpBecomePinned_keyguardWithBypassOn_true() =
-        testScope.runTest {
-            // GIVEN
-            whenever(mBypassController.bypassEnabled).thenReturn(true)
-            val hmp = createHeadsUpManagerPhone()
-            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-            statusBarStateController.setState(StatusBarState.KEYGUARD)
-            runCurrent()
-
-            // THEN
-            Assert.assertTrue(hmp.shouldHeadsUpBecomePinned(entry))
-        }
-
-    @Test
-    fun shouldHeadsUpBecomePinned_keyguardWithBypassOff_false() =
-        testScope.runTest {
-            // GIVEN
-            whenever(mBypassController.bypassEnabled).thenReturn(false)
-            val hmp = createHeadsUpManagerPhone()
-            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-            statusBarStateController.setState(StatusBarState.KEYGUARD)
-            runCurrent()
-
-            // THEN
-            Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
-        }
-
-    @Test
-    fun shouldHeadsUpBecomePinned_shadeExpanded_false() =
-        testScope.runTest {
-            // GIVEN
-            whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(true))
-            val hmp = createHeadsUpManagerPhone()
-            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-            statusBarStateController.setState(StatusBarState.SHADE)
-            runCurrent()
-
-            // THEN
-            Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
-        }
-
-    companion object {
-        @get:Parameters(name = "{0}")
-        val flags: List<FlagsParameterization>
-            get() = buildList {
-                addAll(
-                    FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME)
-                        .andSceneContainer()
-                )
-            }
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java
deleted file mode 100644
index 306d6efd..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java
+++ /dev/null
@@ -1,83 +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.statusbar.policy;
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-
-/**
- * Test helper class for HeadsUpEntry creation.
- */
-public class HeadsUpManagerTestUtil {
-
-    private static final String TEST_PACKAGE_NAME = "HeadsUpManagerTestUtil";
-    private static final int TEST_UID = 0;
-
-    protected static StatusBarNotification createSbn(int id, Notification.Builder n) {
-        return createSbn(id, n.build());
-    }
-
-    protected static StatusBarNotification createSbn(int id, Context context) {
-        final Notification.Builder b = new Notification.Builder(context, "")
-                .setSmallIcon(com.android.systemui.res.R.drawable.ic_person)
-                .setContentTitle("Title")
-                .setContentText("Text");
-        return createSbn(id, b);
-    }
-
-    protected static StatusBarNotification createSbn(int id, Notification n) {
-        return new StatusBarNotification(
-                TEST_PACKAGE_NAME /* pkg */,
-                TEST_PACKAGE_NAME,
-                id,
-                null /* tag */,
-                TEST_UID,
-                0 /* initialPid */,
-                n,
-                new UserHandle(ActivityManager.getCurrentUser()),
-                null /* overrideGroupKey */,
-                0 /* postTime */);
-    }
-
-    protected static NotificationEntry createEntry(int id, Notification n) {
-        return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
-    }
-
-    protected static NotificationEntry createEntry(int id, Context context) {
-        return new NotificationEntryBuilder().setSbn(
-                HeadsUpManagerTestUtil.createSbn(id, context)).build();
-    }
-
-    protected static NotificationEntry createFullScreenIntentEntry(int id, Context context) {
-        final PendingIntent intent = PendingIntent.getActivity(
-                context, 0, new Intent(),
-                PendingIntent.FLAG_IMMUTABLE);
-
-        final Notification notif = new Notification.Builder(context, "")
-                .setSmallIcon(com.android.systemui.res.R.drawable.ic_person)
-                .setFullScreenIntent(intent, /* highPriority */ true)
-                .build();
-        return HeadsUpManagerTestUtil.createEntry(id, notif);
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
deleted file mode 100644
index 59987f4..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
+++ /dev/null
@@ -1,160 +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.statusbar.policy;
-
-import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
-
-import static org.mockito.Mockito.spy;
-
-import android.content.Context;
-import android.graphics.Region;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.time.SystemClock;
-
-class TestableHeadsUpManager extends BaseHeadsUpManager {
-
-    private HeadsUpEntry mLastCreatedEntry;
-
-    TestableHeadsUpManager(
-            Context context,
-            HeadsUpManagerLogger logger,
-            StatusBarStateController statusBarStateController,
-            KeyguardBypassController bypassController,
-            GroupMembershipManager groupMembershipManager,
-            VisualStabilityProvider visualStabilityProvider,
-            ConfigurationController configurationController,
-            DelayableExecutor executor,
-            GlobalSettings globalSettings,
-            SystemClock systemClock,
-            AccessibilityManagerWrapper accessibilityManagerWrapper,
-            UiEventLogger uiEventLogger,
-            JavaAdapter javaAdapter,
-            ShadeInteractor shadeInteractor,
-            AvalancheController avalancheController) {
-        super(
-                context,
-                logger,
-                statusBarStateController,
-                bypassController,
-                groupMembershipManager,
-                visualStabilityProvider,
-                configurationController,
-                mockExecutorHandler(executor),
-                globalSettings,
-                systemClock,
-                executor,
-                accessibilityManagerWrapper,
-                uiEventLogger,
-                javaAdapter,
-                shadeInteractor,
-                avalancheController);
-
-        mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME;
-        mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME;
-        mAutoDismissTime = BaseHeadsUpManagerTest.TEST_AUTO_DISMISS_TIME;
-        mStickyForSomeTimeAutoDismissTime = BaseHeadsUpManagerTest.TEST_STICKY_AUTO_DISMISS_TIME;
-    }
-
-    @NonNull
-    @Override
-    protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) {
-        mLastCreatedEntry = spy(super.createHeadsUpEntry(entry));
-        return mLastCreatedEntry;
-    }
-
-    // The following are only implemented by HeadsUpManagerPhone. If you need them, use that.
-    @Override
-    public void addHeadsUpPhoneListener(@NonNull OnHeadsUpPhoneListenerChange listener) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void addSwipedOutNotification(@NonNull String key) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void extendHeadsUp() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Nullable
-    @Override
-    public Region getTouchableRegion() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isHeadsUpAnimatingAwayValue() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void onExpandingFinished() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean removeNotification(@NonNull String key, boolean releaseImmediately,
-            boolean animate, @NonNull String reason) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void setAnimationStateHandler(@NonNull AnimationStateHandler handler) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void setRemoteInputActive(@NonNull NotificationEntry entry,
-            boolean remoteInputActive) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void setTrackingHeadsUp(boolean tracking) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean shouldSwallowClick(@NonNull String key) {
-        throw new UnsupportedOperationException();
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 74d4178..4e33a59 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -378,8 +378,7 @@
 
             assertThat(dndMode!!.isActive).isFalse()
 
-            zenModeRepository.removeMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
-            zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+            zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND_INACTIVE.id)
             runCurrent()
 
             assertThat(dndMode!!.isActive).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt
index 32ef501..09ea767 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt
@@ -90,15 +90,15 @@
 
         assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.EASE_IN)
 
-        animatorTestRule.advanceTimeBy(config.easeInDuration + 50L)
+        animatorTestRule.advanceAnimationDuration(config.easeInDuration + 50L)
 
         assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.MAIN)
 
-        animatorTestRule.advanceTimeBy(config.duration + 50L)
+        animatorTestRule.advanceAnimationDuration(config.duration + 50L)
 
         assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.EASE_OUT)
 
-        animatorTestRule.advanceTimeBy(config.easeOutDuration + 50L)
+        animatorTestRule.advanceAnimationDuration(config.easeOutDuration + 50L)
 
         assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.NOT_PLAYING)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
index 67a4219..c9be7e3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
@@ -117,9 +117,9 @@
         loadingEffect.play()
 
         // Execute all the animators by advancing each duration with some buffer.
-        animatorTestRule.advanceTimeBy(config.easeInDuration.toLong())
-        animatorTestRule.advanceTimeBy(config.maxDuration.toLong())
-        animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong())
+        animatorTestRule.advanceAnimationDuration(config.easeInDuration.toLong())
+        animatorTestRule.advanceAnimationDuration(config.maxDuration.toLong())
+        animatorTestRule.advanceAnimationDuration(config.easeOutDuration.toLong())
         animatorTestRule.advanceTimeBy(500)
 
         assertThat(states)
@@ -206,12 +206,12 @@
         assertThat(isFinished).isFalse()
 
         loadingEffect.play()
-        animatorTestRule.advanceTimeBy(config.easeInDuration.toLong() + 500L)
+        animatorTestRule.advanceAnimationDuration(config.easeInDuration.toLong() + 500L)
 
         assertThat(isFinished).isFalse()
 
         loadingEffect.finish()
-        animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong() + 500L)
+        animatorTestRule.advanceAnimationDuration(config.easeOutDuration.toLong() + 500L)
 
         assertThat(isFinished).isTrue()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index e7ca1dd..7b52dd8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -434,13 +434,13 @@
     @Test
     public void onSettingChanged_honorThemeStyle() {
         when(mDeviceProvisionedController.isUserSetup(anyInt())).thenReturn(true);
-        List<Style> validStyles = Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ, Style.TONAL_SPOT,
-                Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT);
-        for (Style style : validStyles) {
+        @Style.Type List<Integer> validStyles = Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ,
+                Style.TONAL_SPOT, Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT);
+        for (@Style.Type int style : validStyles) {
             reset(mSecureSettings);
 
             String jsonString = "{\"android.theme.customization.system_palette\":\"A16B00\","
-                    + "\"android.theme.customization.theme_style\":\"" + style.name() + "\"}";
+                    + "\"android.theme.customization.theme_style\":\"" + Style.name(style) + "\"}";
 
             when(mSecureSettings.getStringForUser(
                     eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt
index 7d3ed92..7aa389a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt
@@ -20,10 +20,12 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.testKosmos
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Finished
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
 import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
+import com.android.systemui.touchpad.ui.gesture.fakeVelocityTracker
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -33,12 +35,24 @@
 @RunWith(AndroidJUnit4::class)
 class HomeGestureRecognizerTest : SysuiTestCase() {
 
+    companion object {
+        const val THRESHOLD_VELOCITY_PX_PER_MS = 1f
+        const val SLOW = THRESHOLD_VELOCITY_PX_PER_MS - 0.01f
+        const val FAST = THRESHOLD_VELOCITY_PX_PER_MS + 0.01f
+    }
+
     private var gestureState: GestureState = GestureState.NotStarted
+    private var velocityTracker = testKosmos().fakeVelocityTracker
     private val gestureRecognizer =
-        HomeGestureRecognizer(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt())
+        HomeGestureRecognizer(
+            gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
+            velocityThresholdPxPerMs = THRESHOLD_VELOCITY_PX_PER_MS,
+            velocityTracker = velocityTracker,
+        )
 
     @Before
     fun before() {
+        velocityTracker.setVelocity(Velocity(FAST))
         gestureRecognizer.addGestureStateCallback { gestureState = it }
     }
 
@@ -48,6 +62,12 @@
     }
 
     @Test
+    fun doesntTriggerGestureFinished_onGestureSpeedTooSlow() {
+        velocityTracker.setVelocity(Velocity(SLOW))
+        assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = NotStarted)
+    }
+
+    @Test
     fun triggersGestureProgressForThreeFingerGestureStarted() {
         assertStateAfterEvents(
             events = ThreeFingerGesture.startEvents(x = 0f, y = 0f),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt
index c5c0d59..cb74e65 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt
@@ -17,21 +17,19 @@
 package com.android.systemui.touchpad.tutorial.ui.gesture
 
 import android.view.MotionEvent
-import androidx.compose.ui.input.pointer.util.VelocityTracker1D
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.testKosmos
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Finished
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
 import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
+import com.android.systemui.touchpad.ui.gesture.fakeVelocityTracker
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -39,26 +37,23 @@
 
     companion object {
         const val THRESHOLD_VELOCITY_PX_PER_MS = 0.1f
-        // multiply by 1000 to get px/ms instead of px/s which is unit used by velocity tracker
-        const val SLOW = THRESHOLD_VELOCITY_PX_PER_MS * 1000 - 1
-        const val FAST = THRESHOLD_VELOCITY_PX_PER_MS * 1000 + 1
+        const val SLOW = THRESHOLD_VELOCITY_PX_PER_MS - 0.01f
+        const val FAST = THRESHOLD_VELOCITY_PX_PER_MS + 0.01f
     }
 
+    private var velocityTracker = testKosmos().fakeVelocityTracker
+
     private var gestureState: GestureState = GestureState.NotStarted
-    private val velocityTracker1D =
-        mock<VelocityTracker1D> {
-            // by default return correct speed for the gesture - as if pointer is slowing down
-            on { calculateVelocity() } doReturn SLOW
-        }
     private val gestureRecognizer =
         RecentAppsGestureRecognizer(
             gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
             velocityThresholdPxPerMs = THRESHOLD_VELOCITY_PX_PER_MS,
-            velocityTracker = VerticalVelocityTracker(velocityTracker1D),
+            velocityTracker = velocityTracker,
         )
 
     @Before
     fun before() {
+        velocityTracker.setVelocity(Velocity(SLOW))
         gestureRecognizer.addGestureStateCallback { gestureState = it }
     }
 
@@ -69,7 +64,7 @@
 
     @Test
     fun doesntTriggerGestureFinished_onGestureSpeedTooHigh() {
-        whenever(velocityTracker1D.calculateVelocity()).thenReturn(FAST)
+        velocityTracker.setVelocity(Velocity(FAST))
         assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = NotStarted)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
index 266a60d..665f55b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
@@ -193,7 +193,11 @@
         }
 
     private fun TestScope.advanceTime(timeMs: Long) {
-        animatorTestRule.advanceTimeBy(timeMs)
+        if (timeMs == ANIMATION_DURATION) {
+            animatorTestRule.advanceAnimationDuration(timeMs)
+        } else {
+            animatorTestRule.advanceTimeBy(timeMs)
+        }
         advanceTimeBy(timeMs)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
index ef2d4ce..c7b685f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
@@ -182,7 +182,7 @@
 
     private fun runOnProgressThreadWithInterval(
         vararg blocks: () -> Unit,
-        intervalMillis: Long = 60,
+        intervalMillis: Long = 100,
     ) {
         blocks.forEach {
             bgHandler.post(it)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
index 20b273a..d5651ec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -22,21 +22,25 @@
 import android.content.pm.UserInfo
 import android.os.UserHandle
 import android.os.UserManager
+import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.GuestResetOrExitSessionReceiver
 import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.domain.model.ShowDialogRequestModel
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.kotlinArgumentCaptor
 import com.android.systemui.util.mockito.whenever
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -48,6 +52,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
 class GuestUserInteractorTest : SysuiTestCase() {
 
     @Mock private lateinit var manager: UserManager
@@ -64,16 +69,15 @@
 
     private lateinit var underTest: GuestUserInteractor
 
-    private lateinit var scope: TestCoroutineScope
-    private lateinit var repository: FakeUserRepository
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val scope = kosmos.testScope
+    private val repository = FakeUserRepository()
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(manager.createGuest(any())).thenReturn(GUEST_USER_INFO)
 
-        scope = TestCoroutineScope()
-        repository = FakeUserRepository()
         repository.setUserInfos(ALL_USERS)
 
         underTest = initGuestUserInteractor(context)
@@ -83,8 +87,8 @@
         GuestUserInteractor(
             applicationContext = context,
             applicationScope = scope,
-            mainDispatcher = IMMEDIATE,
-            backgroundDispatcher = IMMEDIATE,
+            mainDispatcher = kosmos.testDispatcher,
+            backgroundDispatcher = kosmos.testDispatcher,
             manager = manager,
             repository = repository,
             deviceProvisionedController = deviceProvisionedController,
@@ -92,7 +96,7 @@
             refreshUsersScheduler =
                 RefreshUsersScheduler(
                     applicationScope = scope,
-                    mainDispatcher = IMMEDIATE,
+                    mainDispatcher = kosmos.testDispatcher,
                     repository = repository,
                 ),
             uiEventLogger = uiEventLogger,
@@ -118,7 +122,7 @@
 
     @Test
     fun onDeviceBootCompleted_allowedToAdd_createGuest() =
-        runBlocking(IMMEDIATE) {
+        kosmos.runTest {
             setAllowedToAdd()
 
             underTest.onDeviceBootCompleted()
@@ -129,7 +133,7 @@
 
     @Test
     fun onDeviceBootCompleted_awaitProvisioning_andCreateGuest() =
-        runBlocking(IMMEDIATE) {
+        kosmos.runTest {
             setAllowedToAdd(isAllowed = false)
             underTest.onDeviceBootCompleted()
             val captor =
@@ -145,7 +149,7 @@
 
     @Test
     fun createAndSwitchTo() =
-        runBlocking(IMMEDIATE) {
+        kosmos.runTest {
             underTest.createAndSwitchTo(
                 showDialog = showDialog,
                 dismissDialog = dismissDialog,
@@ -160,7 +164,7 @@
 
     @Test
     fun createAndSwitchTo_failsToCreate_doesNotSwitchTo() =
-        runBlocking(IMMEDIATE) {
+        kosmos.runTest {
             whenever(manager.createGuest(any())).thenReturn(null)
 
             underTest.createAndSwitchTo(
@@ -177,7 +181,7 @@
 
     @Test
     fun exit_returnsToTargetUser() =
-        runBlocking(IMMEDIATE) {
+        kosmos.runTest {
             repository.setSelectedUserInfo(GUEST_USER_INFO)
 
             val targetUserId = NON_GUEST_USER_INFO.id
@@ -197,7 +201,7 @@
 
     @Test
     fun exit_returnsToLastNonGuest() =
-        runBlocking(IMMEDIATE) {
+        kosmos.runTest {
             val expectedUserId = NON_GUEST_USER_INFO.id
             whenever(manager.getUserInfo(expectedUserId)).thenReturn(NON_GUEST_USER_INFO)
             repository.lastSelectedNonGuestUserId = expectedUserId
@@ -219,7 +223,7 @@
 
     @Test
     fun exit_lastNonGuestWasRemoved_returnsToMainUser() =
-        runBlocking(IMMEDIATE) {
+        kosmos.runTest {
             val removedUserId = 310
             val mainUserId = 10
             repository.lastSelectedNonGuestUserId = removedUserId
@@ -242,7 +246,7 @@
 
     @Test
     fun exit_guestWasEphemeral_itIsRemoved() =
-        runBlocking(IMMEDIATE) {
+        kosmos.runTest {
             whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
             repository.setUserInfos(listOf(NON_GUEST_USER_INFO, EPHEMERAL_GUEST_USER_INFO))
             repository.setSelectedUserInfo(EPHEMERAL_GUEST_USER_INFO)
@@ -265,7 +269,7 @@
 
     @Test
     fun exit_forceRemoveGuest_itIsRemoved() =
-        runBlocking(IMMEDIATE) {
+        kosmos.runTest {
             whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
             repository.setSelectedUserInfo(GUEST_USER_INFO)
             val targetUserId = NON_GUEST_USER_INFO.id
@@ -287,7 +291,7 @@
 
     @Test
     fun exit_selectedDifferentFromGuestUser_doNothing() =
-        runBlocking(IMMEDIATE) {
+        kosmos.runTest {
             repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
 
             underTest.exit(
@@ -304,7 +308,7 @@
 
     @Test
     fun exit_selectedIsActuallyNotAguestUser_doNothing() =
-        runBlocking(IMMEDIATE) {
+        kosmos.runTest {
             repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
 
             underTest.exit(
@@ -321,7 +325,7 @@
 
     @Test
     fun remove_returnsToTargetUser() =
-        runBlocking(IMMEDIATE) {
+        kosmos.runTest {
             whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
             repository.setSelectedUserInfo(GUEST_USER_INFO)
 
@@ -342,7 +346,7 @@
 
     @Test
     fun remove_selectedDifferentFromGuestUser_doNothing() =
-        runBlocking(IMMEDIATE) {
+        kosmos.runTest {
             whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
             repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
 
@@ -359,7 +363,7 @@
 
     @Test
     fun remove_selectedIsActuallyNotAguestUser_doNothing() =
-        runBlocking(IMMEDIATE) {
+        kosmos.runTest {
             whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
             repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
 
@@ -395,11 +399,7 @@
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
         private val NON_GUEST_USER_INFO =
-            UserInfo(
-                /* id= */ 818,
-                /* name= */ "non_guest",
-                /* flags= */ UserInfo.FLAG_FULL,
-            )
+            UserInfo(/* id= */ 818, /* name= */ "non_guest", /* flags= */ UserInfo.FLAG_FULL)
         private val GUEST_USER_INFO =
             UserInfo(
                 /* id= */ 669,
@@ -416,10 +416,6 @@
                 /* flags= */ UserInfo.FLAG_EPHEMERAL or UserInfo.FLAG_FULL,
                 UserManager.USER_TYPE_FULL_GUEST,
             )
-        private val ALL_USERS =
-            listOf(
-                NON_GUEST_USER_INFO,
-                GUEST_USER_INFO,
-            )
+        private val ALL_USERS = listOf(NON_GUEST_USER_INFO, GUEST_USER_INFO)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorTest.kt
new file mode 100644
index 0000000..8acf538
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.domain.interactor
+
+import android.app.ActivityManager
+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.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.plugins.fakeVolumeDialogController
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibilityRepository
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class VolumeDialogSafetyWarningInteractorTest : SysuiTestCase() {
+
+    private val kosmos: Kosmos = testKosmos()
+
+    private lateinit var underTest: VolumeDialogSafetyWarningInteractor
+
+    @Before
+    fun setup() {
+        kosmos.useUnconfinedTestDispatcher()
+        underTest = kosmos.volumeDialogSafetyWarningInteractor
+    }
+
+    @Test
+    fun dismiss_isShowingSafetyWarning_isFalse() =
+        with(kosmos) {
+            runTest {
+                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+
+                underTest.onSafetyWarningDismissed()
+
+                assertThat(isShowingSafetyWarning).isFalse()
+            }
+        }
+
+    @Test
+    fun flagShowUi_isShowingSafetyWarning_isTrue() =
+        with(kosmos) {
+            runTest {
+                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+
+                fakeVolumeDialogController.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI)
+
+                assertThat(isShowingSafetyWarning).isTrue()
+            }
+        }
+
+    @Test
+    fun flagShowUiWarnings_isShowingSafetyWarning_isTrue() =
+        with(kosmos) {
+            runTest {
+                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+
+                fakeVolumeDialogController.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI_WARNINGS)
+
+                assertThat(isShowingSafetyWarning).isTrue()
+            }
+        }
+
+    @Test
+    fun invisibleAndNoFlags_isShowingSafetyWarning_isFalse() =
+        with(kosmos) {
+            runTest {
+                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+                volumeDialogVisibilityRepository.updateVisibility {
+                    VolumeDialogVisibilityModel.Invisible
+                }
+
+                fakeVolumeDialogController.onShowSafetyWarning(0)
+
+                assertThat(isShowingSafetyWarning).isFalse()
+            }
+        }
+
+    @Test
+    fun visibleAndNoFlags_isShowingSafetyWarning_isTrue() =
+        with(kosmos) {
+            runTest {
+                val isShowingSafetyWarning by collectLastValue(underTest.isShowingSafetyWarning)
+                volumeDialogVisibilityRepository.updateVisibility {
+                    VolumeDialogVisibilityModel.Visible(
+                        Events.SHOW_REASON_VOLUME_CHANGED,
+                        false,
+                        ActivityManager.LOCK_TASK_MODE_LOCKED,
+                    )
+                }
+
+                fakeVolumeDialogController.onShowSafetyWarning(0)
+
+                assertThat(isShowingSafetyWarning).isTrue()
+            }
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
index 1e6e52a..d8184db 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
@@ -83,7 +83,7 @@
 
             assertThat(ringerViewModel).isInstanceOf(RingerViewModelState.Available::class.java)
             assertThat((ringerViewModel as RingerViewModelState.Available).uiModel.drawerState)
-                .isEqualTo(RingerDrawerState.Closed(normalRingerMode))
+                .isEqualTo(RingerDrawerState.Closed(normalRingerMode, normalRingerMode))
         }
 
     @Test
@@ -91,8 +91,9 @@
         testScope.runTest {
             val ringerViewModel by collectLastValue(underTest.ringerViewModel)
             val vibrateRingerMode = RingerMode(RINGER_MODE_VIBRATE)
+            val normalRingerMode = RingerMode(RINGER_MODE_NORMAL)
 
-            setUpRingerModeAndOpenDrawer(RingerMode(RINGER_MODE_NORMAL))
+            setUpRingerModeAndOpenDrawer(normalRingerMode)
             // Select vibrate ringer mode.
             underTest.onRingerButtonClicked(vibrateRingerMode)
             controller.getState()
@@ -103,7 +104,8 @@
             var uiModel = (ringerViewModel as RingerViewModelState.Available).uiModel
             assertThat(uiModel.availableButtons[uiModel.currentButtonIndex]?.ringerMode)
                 .isEqualTo(vibrateRingerMode)
-            assertThat(uiModel.drawerState).isEqualTo(RingerDrawerState.Closed(vibrateRingerMode))
+            assertThat(uiModel.drawerState)
+                .isEqualTo(RingerDrawerState.Closed(vibrateRingerMode, normalRingerMode))
 
             val silentRingerMode = RingerMode(RINGER_MODE_SILENT)
             // Open drawer
@@ -120,7 +122,8 @@
             uiModel = (ringerViewModel as RingerViewModelState.Available).uiModel
             assertThat(uiModel.availableButtons[uiModel.currentButtonIndex]?.ringerMode)
                 .isEqualTo(silentRingerMode)
-            assertThat(uiModel.drawerState).isEqualTo(RingerDrawerState.Closed(silentRingerMode))
+            assertThat(uiModel.drawerState)
+                .isEqualTo(RingerDrawerState.Closed(silentRingerMode, vibrateRingerMode))
             assertThat(controller.hasScheduledTouchFeedback).isFalse()
             assertThat(vibratorHelper.totalVibrations).isEqualTo(2)
         }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt b/packages/SystemUI/multivalentTests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt
rename to packages/SystemUI/multivalentTests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index 6212e2b..2cd3346 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -55,6 +55,8 @@
         "SystemUICommon",
         "SystemUILogLib",
         "androidx.annotation_annotation",
+        "androidx.compose.ui_ui",
+        "androidx.compose.runtime_runtime",
     ],
 }
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/AuthContextPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/AuthContextPlugin.kt
new file mode 100644
index 0000000..773c2a2
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/AuthContextPlugin.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.plugins
+
+import android.os.IBinder
+import android.view.View
+import com.android.systemui.plugins.annotations.ProvidesInterface
+
+/**
+ * Plugin for experimental "Contextual Auth" features.
+ *
+ * These plugins will get raw access to low-level events about the user's environment, such as
+ * moving in/out of trusted locations, connection status of trusted devices, auth attempts, etc.
+ * They will also receive callbacks related to system events & transitions to enable prototypes on
+ * sensitive surfaces like lock screen and BiometricPrompt.
+ *
+ * Note to rebuild the plugin jar run: m PluginDummyLib
+ */
+@ProvidesInterface(action = AuthContextPlugin.ACTION, version = AuthContextPlugin.VERSION)
+interface AuthContextPlugin : Plugin {
+
+    /**
+     * Called in the background when the plugin is enabled.
+     *
+     * This is a good time to ask your friendly [saucier] to cook up something special. The
+     * [Plugin.onCreate] can also be used for initialization.
+     */
+    fun activated(saucier: Saucier)
+
+    /**
+     * Called when a [SensitiveSurface] is first shown.
+     *
+     * This may be called repeatedly if the state of the surface changes after it is shown. For
+     * example, [SensitiveSurface.BiometricPrompt.isCredential] will change if the user falls back
+     * to a credential-based auth method.
+     */
+    fun onShowingSensitiveSurface(surface: SensitiveSurface)
+
+    /**
+     * Called when a [SensitiveSurface] sensitive surface is hidden.
+     *
+     * This method may still be called without [onShowingSensitiveSurface] in cases of rapid
+     * dismissal and plugins implementations should typically be idempotent.
+     */
+    fun onHidingSensitiveSurface(surface: SensitiveSurface)
+
+    companion object {
+        /** Plugin action. */
+        const val ACTION = "com.android.systemui.action.PLUGIN_AUTH_CONTEXT"
+        /** Plugin version. */
+        const val VERSION = 1
+    }
+
+    /** Information about a sensitive surface in the framework, which the Plugin may augment. */
+    sealed interface SensitiveSurface {
+
+        /** Information about the BiometricPrompt that is being shown to the user. */
+        data class BiometricPrompt(val view: View? = null, val isCredential: Boolean = false) :
+            SensitiveSurface
+
+        /** Information about bouncer. */
+        data class LockscreenBouncer(val view: View? = null) : SensitiveSurface
+    }
+
+    /** Ask for the special. */
+    interface Saucier {
+
+        /** What [flavor] would you like? */
+        fun getSauce(flavor: String): IBinder?
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockAnimations.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockAnimations.kt
new file mode 100644
index 0000000..2df14a8
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockAnimations.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins.clocks
+
+import android.view.View
+import com.android.systemui.plugins.annotations.ProtectedInterface
+
+/** Methods which trigger various clock animations */
+@ProtectedInterface
+interface ClockAnimations {
+    /** Runs an enter animation (if any) */
+    fun enter()
+
+    /** Sets how far into AOD the device currently is. */
+    fun doze(fraction: Float)
+
+    /** Sets how far into the folding animation the device is. */
+    fun fold(fraction: Float)
+
+    /** Runs the battery animation (if any). */
+    fun charge()
+
+    /**
+     * Runs when the clock's position changed during the move animation.
+     *
+     * @param fromLeft the [View.getLeft] position of the clock, before it started moving.
+     * @param direction the direction in which it is moving. A positive number means right, and
+     *   negative means left.
+     * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
+     *   it finished moving.
+     * @deprecated use {@link #onPositionUpdated(float, float)} instead.
+     */
+    fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float)
+
+    /**
+     * Runs when the clock's position changed during the move animation.
+     *
+     * @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.
+     */
+    fun onPositionUpdated(distance: Float, fraction: Float)
+
+    /**
+     * Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview,
+     * 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize
+     */
+    fun onPickerCarouselSwiping(swipingFraction: Float)
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockConfig.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockConfig.kt
new file mode 100644
index 0000000..d84d890
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockConfig.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.plugins.clocks
+
+/**
+ * Exposes the rendering capabilities of this clock to SystemUI so that it can be hosted and render
+ * correctly in SystemUI's process. Ideally all clocks could be rendered identically, but in
+ * practice we different clocks require different behavior from SystemUI.
+ */
+data class ClockConfig(
+    val id: ClockId,
+
+    /** Localized name of the clock */
+    val name: String,
+
+    /** Localized accessibility description for the clock */
+    val description: String,
+
+    /** Transition to AOD should move smartspace like large clock instead of small clock */
+    val useAlternateSmartspaceAODTransition: Boolean = false,
+
+    /** Deprecated version of isReactiveToTone; moved to ClockPickerConfig */
+    @Deprecated("TODO(b/352049256): Remove in favor of ClockPickerConfig.isReactiveToTone")
+    val isReactiveToTone: Boolean = true,
+
+    /** True if the clock is large frame clock, which will use weather in compose. */
+    val useCustomClockScene: Boolean = false,
+)
+
+/** Render configuration options for a specific clock face. */
+data class ClockFaceConfig(
+    /** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */
+    val tickRate: ClockTickRate = ClockTickRate.PER_MINUTE,
+
+    /** Call to check whether the clock consumes weather data */
+    val hasCustomWeatherDataDisplay: Boolean = false,
+
+    /**
+     * Whether this clock has a custom position update animation. If true, the keyguard will call
+     * `onPositionUpdated` to notify the clock of a position update animation. If false, a default
+     * animation will be used (e.g. a simple translation).
+     */
+    val hasCustomPositionUpdatedAnimation: Boolean = false,
+
+    /** True if the clock is large frame clock, which will use weatherBlueprint in compose. */
+    val useCustomClockScene: Boolean = false,
+)
+
+/** Tick rates for clocks */
+enum class ClockTickRate(val value: Int) {
+    PER_MINUTE(2), // Update the clock once per minute.
+    PER_SECOND(1), // Update the clock once per second.
+    PER_FRAME(0), // Update the clock every second.
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt
new file mode 100644
index 0000000..32fec32
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.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.plugins.clocks
+
+import com.android.systemui.plugins.annotations.ProtectedInterface
+import com.android.systemui.plugins.annotations.SimpleProperty
+import java.io.PrintWriter
+
+/** Interface for controlling an active clock */
+@ProtectedInterface
+interface ClockController {
+    @get:SimpleProperty
+    /** A small version of the clock, appropriate for smaller viewports */
+    val smallClock: ClockFaceController
+
+    @get:SimpleProperty
+    /** A large version of the clock, appropriate when a bigger viewport is available */
+    val largeClock: ClockFaceController
+
+    @get:SimpleProperty
+    /** Determines the way the hosting app should behave when rendering either clock face */
+    val config: ClockConfig
+
+    @get:SimpleProperty
+    /** Events that clocks may need to respond to */
+    val events: ClockEvents
+
+    /** Initializes various rendering parameters. If never called, provides reasonable defaults. */
+    fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float)
+
+    /** Optional method for dumping debug information */
+    fun dump(pw: PrintWriter)
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt
new file mode 100644
index 0000000..235475f
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.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.plugins.clocks
+
+import com.android.systemui.plugins.annotations.ProtectedInterface
+import com.android.systemui.plugins.annotations.ProtectedReturn
+import java.util.Locale
+import java.util.TimeZone
+
+/** Events that should call when various rendering parameters change */
+@ProtectedInterface
+interface ClockEvents {
+    @get:ProtectedReturn("return false;")
+    /** Set to enable or disable swipe interaction */
+    var isReactiveTouchInteractionEnabled: Boolean // TODO(b/364664388): Remove/Rename
+
+    /** Call whenever timezone changes */
+    fun onTimeZoneChanged(timeZone: TimeZone)
+
+    /** Call whenever the text time format changes (12hr vs 24hr) */
+    fun onTimeFormatChanged(is24Hr: Boolean)
+
+    /** Call whenever the locale changes */
+    fun onLocaleChanged(locale: Locale)
+
+    /** Call whenever the weather data should update */
+    fun onWeatherDataChanged(data: WeatherData)
+
+    /** Call with alarm information */
+    fun onAlarmDataChanged(data: AlarmData)
+
+    /** Call with zen/dnd information */
+    fun onZenDataChanged(data: ZenData)
+
+    /** Update reactive axes for this clock */
+    fun onFontAxesChanged(axes: List<ClockFontAxisSetting>)
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceController.kt
new file mode 100644
index 0000000..8a023f1
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceController.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.plugins.clocks
+
+import android.view.View
+import com.android.systemui.plugins.annotations.ProtectedInterface
+import com.android.systemui.plugins.annotations.SimpleProperty
+
+/** Interface for a specific clock face version rendered by the clock */
+@ProtectedInterface
+interface ClockFaceController {
+    @get:SimpleProperty
+    @Deprecated("Prefer use of layout")
+    /** View that renders the clock face */
+    val view: View
+
+    @get:SimpleProperty
+    /** Layout specification for this clock */
+    val layout: ClockFaceLayout
+
+    @get:SimpleProperty
+    /** Determines the way the hosting app should behave when rendering this clock face */
+    val config: ClockFaceConfig
+
+    @get:SimpleProperty
+    /** Current theme information the clock is using */
+    val theme: ThemeConfig
+
+    @get:SimpleProperty
+    /** Events specific to this clock face */
+    val events: ClockFaceEvents
+
+    @get:SimpleProperty
+    /** Triggers for various animations */
+    val animations: ClockAnimations
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt
new file mode 100644
index 0000000..029e546
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.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.plugins.clocks
+
+import android.graphics.Rect
+import com.android.systemui.plugins.annotations.ProtectedInterface
+
+/** Events that have specific data about the related face */
+@ProtectedInterface
+interface ClockFaceEvents {
+    /** Call every tick to update the rendered time */
+    fun onTimeTick()
+
+    /**
+     * Call whenever the theme or seedColor is updated
+     *
+     * Theme can be specific to the clock face.
+     * - isDarkTheme -> clock should be light
+     * - !isDarkTheme -> clock should be dark
+     */
+    fun onThemeChanged(theme: ThemeConfig)
+
+    /**
+     * Call whenever font settings change. Pass in a target font size in pixels. The specific clock
+     * design is allowed to ignore this target size on a case-by-case basis.
+     */
+    fun onFontSettingChanged(fontSizePx: Float)
+
+    /**
+     * Target region information for the clock face. For small clock, this will match the bounds of
+     * the parent view mostly, but have a target height based on the height of the default clock.
+     * For large clocks, the parent view is the entire device size, but most clocks will want to
+     * render within the centered targetRect to avoid obstructing other elements. The specified
+     * targetRegion is relative to the parent view.
+     */
+    fun onTargetRegionChanged(targetRegion: Rect?)
+
+    /** Called to notify the clock about its display. */
+    fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean)
+}
+
+/** Contains Theming information for the clock face */
+data class ThemeConfig(
+    /** True if the clock should use dark theme (light text on dark background) */
+    val isDarkTheme: Boolean,
+
+    /**
+     * A clock specific seed color to use when theming, if any was specified by the user. A null
+     * value denotes that we should use the seed color for the current system theme.
+     */
+    val seedColor: Int?,
+)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt
new file mode 100644
index 0000000..fb5ef02
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.plugins.clocks
+
+import android.content.Context
+import android.util.DisplayMetrics
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
+import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.plugins.annotations.GeneratedImport
+import com.android.systemui.plugins.annotations.ProtectedInterface
+import com.android.systemui.plugins.annotations.ProtectedReturn
+
+/** Specifies layout information for the clock face */
+@ProtectedInterface
+@GeneratedImport("java.util.ArrayList")
+@GeneratedImport("android.view.View")
+interface ClockFaceLayout {
+    @get:ProtectedReturn("return new ArrayList<View>();")
+    /** All clock views to add to the root constraint layout before applying constraints. */
+    val views: List<View>
+
+    @ProtectedReturn("return constraints;")
+    /** Custom constraints to apply to Lockscreen ConstraintLayout. */
+    fun applyConstraints(constraints: ConstraintSet): ConstraintSet
+
+    @ProtectedReturn("return constraints;")
+    /** Custom constraints to apply to preview ConstraintLayout. */
+    fun applyPreviewConstraints(
+        clockPreviewConfig: ClockPreviewConfig,
+        constraints: ConstraintSet,
+    ): ConstraintSet
+
+    /** Apply specified AOD BurnIn parameters to this layout */
+    fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel)
+}
+
+/** Data class to contain AOD BurnIn information for correct aod rendering */
+data class AodClockBurnInModel(
+    /** Scale that the clock should render at to mitigate burnin */
+    val scale: Float,
+
+    /** X-Translation for the clock to mitigate burnin */
+    val translationX: Float,
+
+    /** Y-Translation for the clock to mitigate burnin */
+    val translationY: Float,
+)
+
+/** A ClockFaceLayout that applies the default lockscreen layout to a single view */
+class DefaultClockFaceLayout(val view: View) : ClockFaceLayout {
+    override val views = listOf(view)
+
+    override fun applyConstraints(constraints: ConstraintSet): ConstraintSet {
+        if (views.size != 1) {
+            throw IllegalArgumentException(
+                "Should have only one container view when using DefaultClockFaceLayout"
+            )
+        }
+        return constraints
+    }
+
+    override fun applyPreviewConstraints(
+        clockPreviewConfig: ClockPreviewConfig,
+        constraints: ConstraintSet,
+    ): ConstraintSet {
+        return applyDefaultPreviewConstraints(clockPreviewConfig, constraints)
+    }
+
+    override fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) {
+        // Default clock doesn't need detailed control of view
+    }
+
+    companion object {
+        fun applyDefaultPreviewConstraints(
+            clockPreviewConfig: ClockPreviewConfig,
+            constraints: ConstraintSet,
+        ): ConstraintSet {
+            constraints.apply {
+                val context = clockPreviewConfig.previewContext
+                val lockscreenClockViewLargeId = getId(context, "lockscreen_clock_view_large")
+                constrainWidth(lockscreenClockViewLargeId, WRAP_CONTENT)
+                constrainHeight(lockscreenClockViewLargeId, WRAP_CONTENT)
+                constrainMaxHeight(lockscreenClockViewLargeId, 0)
+
+                val largeClockTopMargin =
+                    SystemBarUtils.getStatusBarHeight(context) +
+                        getDimen(context, "small_clock_padding_top") +
+                        getDimen(context, "keyguard_smartspace_top_offset") +
+                        getDimen(context, "date_weather_view_height") +
+                        getDimen(context, "enhanced_smartspace_height")
+                connect(lockscreenClockViewLargeId, TOP, PARENT_ID, TOP, largeClockTopMargin)
+                connect(lockscreenClockViewLargeId, START, PARENT_ID, START)
+                connect(lockscreenClockViewLargeId, END, PARENT_ID, END)
+
+                // In preview, we'll show UDFPS icon for UDFPS devices
+                // and nothing for non-UDFPS devices,
+                // and we're not planning to add this vide in clockHostView
+                // so we only need position of device entry icon to constrain clock
+                // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
+                val bottomPaddingPx = getDimen(context, "lock_icon_margin_bottom")
+                val defaultDensity =
+                    DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+                        DisplayMetrics.DENSITY_DEFAULT.toFloat()
+                val lockIconRadiusPx = (defaultDensity * 36).toInt()
+                val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
+
+                connect(lockscreenClockViewLargeId, BOTTOM, PARENT_ID, BOTTOM, clockBottomMargin)
+                val smallClockViewId = getId(context, "lockscreen_clock_view")
+                constrainWidth(smallClockViewId, WRAP_CONTENT)
+                constrainHeight(smallClockViewId, getDimen(context, "small_clock_height"))
+                connect(
+                    smallClockViewId,
+                    START,
+                    PARENT_ID,
+                    START,
+                    getDimen(context, "clock_padding_start") +
+                        getDimen(context, "status_view_margin_horizontal"),
+                )
+                val smallClockTopMargin =
+                    getSmallClockTopPadding(
+                        clockPreviewConfig = clockPreviewConfig,
+                        SystemBarUtils.getStatusBarHeight(context),
+                    )
+                connect(smallClockViewId, TOP, PARENT_ID, TOP, smallClockTopMargin)
+            }
+            return constraints
+        }
+
+        fun getId(context: Context, name: String): Int {
+            val packageName = context.packageName
+            val res = context.packageManager.getResourcesForApplication(packageName)
+            val id = res.getIdentifier(name, "id", packageName)
+            return id
+        }
+
+        fun getDimen(context: Context, name: String): Int {
+            val packageName = context.packageName
+            val res = context.resources
+            val id = res.getIdentifier(name, "dimen", packageName)
+            return if (id == 0) 0 else res.getDimensionPixelSize(id)
+        }
+
+        fun getSmallClockTopPadding(
+            clockPreviewConfig: ClockPreviewConfig,
+            statusBarHeight: Int,
+        ): Int {
+            return if (clockPreviewConfig.isShadeLayoutWide) {
+                getDimen(clockPreviewConfig.previewContext, "keyguard_split_shade_top_margin") -
+                    if (clockPreviewConfig.isSceneContainerFlagEnabled) statusBarHeight else 0
+            } else {
+                getDimen(clockPreviewConfig.previewContext, "keyguard_clock_top_margin") +
+                    if (!clockPreviewConfig.isSceneContainerFlagEnabled) statusBarHeight else 0
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockMessageBuffers.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockMessageBuffers.kt
new file mode 100644
index 0000000..bec589a
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockMessageBuffers.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.plugins.clocks
+
+import com.android.systemui.log.core.MessageBuffer
+
+/** MessageBuffers for clocks that want to log information to SystemUI dumps */
+data class ClockMessageBuffers(
+    /** Message buffer for general infrastructure */
+    val infraMessageBuffer: MessageBuffer,
+
+    /** Message buffer for small clock rendering */
+    val smallClockMessageBuffer: MessageBuffer,
+
+    /** Message buffer for large clock rendering */
+    val largeClockMessageBuffer: MessageBuffer,
+) {
+    constructor(buffer: MessageBuffer) : this(buffer, buffer, buffer) {}
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt
new file mode 100644
index 0000000..1bc9367
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.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.plugins.clocks
+
+import android.graphics.drawable.Drawable
+
+data class ClockPickerConfig
+@JvmOverloads
+constructor(
+    val id: String,
+
+    /** Localized name of the clock */
+    val name: String,
+
+    /** Localized accessibility description for the clock */
+    val description: String,
+
+    /* Static & lightweight thumbnail version of the clock */
+    val thumbnail: Drawable,
+
+    /** True if the clock will react to tone changes in the seed color */
+    val isReactiveToTone: Boolean = true,
+
+    /** Font axes that can be modified on this clock */
+    val axes: List<ClockFontAxis> = listOf(),
+)
+
+/** Represents an Axis that can be modified */
+data class ClockFontAxis(
+    /** Axis key, not user renderable */
+    val key: String,
+
+    /** Intended mode of user interaction */
+    val type: AxisType,
+
+    /** Maximum value the axis supports */
+    val maxValue: Float,
+
+    /** Minimum value the axis supports */
+    val minValue: Float,
+
+    /** Current value the axis is set to */
+    val currentValue: Float,
+
+    /** User-renderable name of the axis */
+    val name: String,
+
+    /** Description of the axis */
+    val description: String,
+) {
+    fun toSetting() = ClockFontAxisSetting(key, currentValue)
+
+    companion object {
+        fun merge(
+            fontAxes: List<ClockFontAxis>,
+            axisSettings: List<ClockFontAxisSetting>,
+        ): List<ClockFontAxis> {
+            val result = mutableListOf<ClockFontAxis>()
+            for (axis in fontAxes) {
+                val setting = axisSettings.firstOrNull { axis.key == it.key }
+                val output = setting?.let { axis.copy(currentValue = it.value) } ?: axis
+                result.add(output)
+            }
+            return result
+        }
+    }
+}
+
+/** Axis user interaction modes */
+enum class AxisType {
+    /** Continuous range between minValue & maxValue. */
+    Float,
+
+    /** Only minValue & maxValue are valid. No intermediate values between them are allowed. */
+    Boolean,
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt
new file mode 100644
index 0000000..544b705
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.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.plugins.clocks
+
+import android.content.Context
+
+data class ClockPreviewConfig(
+    val previewContext: Context,
+    val isShadeLayoutWide: Boolean,
+    val isSceneContainerFlagEnabled: Boolean = false,
+)
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 fb9e96c..7426f06 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
@@ -13,35 +13,11 @@
  */
 package com.android.systemui.plugins.clocks
 
-import android.content.Context
-import android.graphics.Rect
-import android.graphics.drawable.Drawable
-import android.util.DisplayMetrics
-import android.view.View
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.END
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
-import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
-import com.android.internal.annotations.Keep
-import com.android.internal.policy.SystemBarUtils
-import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.plugins.Plugin
 import com.android.systemui.plugins.annotations.GeneratedImport
 import com.android.systemui.plugins.annotations.ProtectedInterface
 import com.android.systemui.plugins.annotations.ProtectedReturn
 import com.android.systemui.plugins.annotations.ProvidesInterface
-import com.android.systemui.plugins.annotations.SimpleProperty
-import java.io.PrintWriter
-import java.util.Locale
-import java.util.TimeZone
-import org.json.JSONArray
-import org.json.JSONObject
-
-/** Identifies a clock design */
-typealias ClockId = String
 
 /** A Plugin which exposes the ClockProvider interface */
 @ProtectedInterface
@@ -74,509 +50,8 @@
     fun getClockPickerConfig(settings: ClockSettings): ClockPickerConfig
 }
 
-/** Interface for controlling an active clock */
-@ProtectedInterface
-interface ClockController {
-    @get:SimpleProperty
-    /** A small version of the clock, appropriate for smaller viewports */
-    val smallClock: ClockFaceController
-
-    @get:SimpleProperty
-    /** A large version of the clock, appropriate when a bigger viewport is available */
-    val largeClock: ClockFaceController
-
-    @get:SimpleProperty
-    /** Determines the way the hosting app should behave when rendering either clock face */
-    val config: ClockConfig
-
-    @get:SimpleProperty
-    /** Events that clocks may need to respond to */
-    val events: ClockEvents
-
-    /** Initializes various rendering parameters. If never called, provides reasonable defaults. */
-    fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float)
-
-    /** Optional method for dumping debug information */
-    fun dump(pw: PrintWriter)
-}
-
-/** Interface for a specific clock face version rendered by the clock */
-@ProtectedInterface
-interface ClockFaceController {
-    @get:SimpleProperty
-    @Deprecated("Prefer use of layout")
-    /** View that renders the clock face */
-    val view: View
-
-    @get:SimpleProperty
-    /** Layout specification for this clock */
-    val layout: ClockFaceLayout
-
-    @get:SimpleProperty
-    /** Determines the way the hosting app should behave when rendering this clock face */
-    val config: ClockFaceConfig
-
-    @get:SimpleProperty
-    /** Current theme information the clock is using */
-    val theme: ThemeConfig
-
-    @get:SimpleProperty
-    /** Events specific to this clock face */
-    val events: ClockFaceEvents
-
-    @get:SimpleProperty
-    /** Triggers for various animations */
-    val animations: ClockAnimations
-}
-
-/** For clocks that want to report debug information */
-data class ClockMessageBuffers(
-    /** Message buffer for general infra */
-    val infraMessageBuffer: MessageBuffer,
-
-    /** Message buffer for small clock renering */
-    val smallClockMessageBuffer: MessageBuffer,
-
-    /** Message buffer for large clock rendering */
-    val largeClockMessageBuffer: MessageBuffer,
-) {
-    constructor(buffer: MessageBuffer) : this(buffer, buffer, buffer) {}
-}
-
-data class AodClockBurnInModel(val scale: Float, val translationX: Float, val translationY: Float)
-
-/** Specifies layout information for the clock face */
-@ProtectedInterface
-@GeneratedImport("java.util.ArrayList")
-@GeneratedImport("android.view.View")
-interface ClockFaceLayout {
-    @get:ProtectedReturn("return new ArrayList<View>();")
-    /** All clock views to add to the root constraint layout before applying constraints. */
-    val views: List<View>
-
-    @ProtectedReturn("return constraints;")
-    /** Custom constraints to apply to Lockscreen ConstraintLayout. */
-    fun applyConstraints(constraints: ConstraintSet): ConstraintSet
-
-    @ProtectedReturn("return constraints;")
-    /** Custom constraints to apply to preview ConstraintLayout. */
-    fun applyPreviewConstraints(context: Context, constraints: ConstraintSet): ConstraintSet
-
-    fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel)
-}
-
-/** A ClockFaceLayout that applies the default lockscreen layout to a single view */
-class DefaultClockFaceLayout(val view: View) : ClockFaceLayout {
-    // both small and large clock should have a container (RelativeLayout in
-    // SimpleClockFaceController)
-    override val views = listOf(view)
-
-    override fun applyConstraints(constraints: ConstraintSet): ConstraintSet {
-        if (views.size != 1) {
-            throw IllegalArgumentException(
-                "Should have only one container view when using DefaultClockFaceLayout"
-            )
-        }
-        return constraints
-    }
-
-    override fun applyPreviewConstraints(
-        context: Context,
-        constraints: ConstraintSet,
-    ): ConstraintSet {
-        return applyDefaultPreviewConstraints(context, constraints)
-    }
-
-    override fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) {
-        // Default clock doesn't need detailed control of view
-    }
-
-    companion object {
-        fun applyDefaultPreviewConstraints(
-            context: Context,
-            constraints: ConstraintSet,
-        ): ConstraintSet {
-            constraints.apply {
-                val lockscreenClockViewLargeId = getId(context, "lockscreen_clock_view_large")
-                constrainWidth(lockscreenClockViewLargeId, WRAP_CONTENT)
-                constrainHeight(lockscreenClockViewLargeId, WRAP_CONTENT)
-                constrainMaxHeight(lockscreenClockViewLargeId, 0)
-
-                val largeClockTopMargin =
-                    SystemBarUtils.getStatusBarHeight(context) +
-                        getDimen(context, "small_clock_padding_top") +
-                        getDimen(context, "keyguard_smartspace_top_offset") +
-                        getDimen(context, "date_weather_view_height") +
-                        getDimen(context, "enhanced_smartspace_height")
-                connect(lockscreenClockViewLargeId, TOP, PARENT_ID, TOP, largeClockTopMargin)
-                connect(lockscreenClockViewLargeId, START, PARENT_ID, START)
-                connect(lockscreenClockViewLargeId, END, PARENT_ID, END)
-
-                // In preview, we'll show UDFPS icon for UDFPS devices
-                // and nothing for non-UDFPS devices,
-                // and we're not planning to add this vide in clockHostView
-                // so we only need position of device entry icon to constrain clock
-                // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
-                val bottomPaddingPx = getDimen(context, "lock_icon_margin_bottom")
-                val defaultDensity =
-                    DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
-                        DisplayMetrics.DENSITY_DEFAULT.toFloat()
-                val lockIconRadiusPx = (defaultDensity * 36).toInt()
-                val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
-
-                connect(lockscreenClockViewLargeId, BOTTOM, PARENT_ID, BOTTOM, clockBottomMargin)
-                val smallClockViewId = getId(context, "lockscreen_clock_view")
-                constrainWidth(smallClockViewId, WRAP_CONTENT)
-                constrainHeight(smallClockViewId, getDimen(context, "small_clock_height"))
-                connect(
-                    smallClockViewId,
-                    START,
-                    PARENT_ID,
-                    START,
-                    getDimen(context, "clock_padding_start") +
-                        getDimen(context, "status_view_margin_horizontal"),
-                )
-                val smallClockTopMargin =
-                    getDimen(context, "keyguard_clock_top_margin") +
-                        SystemBarUtils.getStatusBarHeight(context)
-                connect(smallClockViewId, TOP, PARENT_ID, TOP, smallClockTopMargin)
-            }
-            return constraints
-        }
-
-        fun getId(context: Context, name: String): Int {
-            val packageName = context.packageName
-            val res = context.packageManager.getResourcesForApplication(packageName)
-            val id = res.getIdentifier(name, "id", packageName)
-            return id
-        }
-
-        fun getDimen(context: Context, name: String): Int {
-            val packageName = context.packageName
-            val res = context.packageManager.getResourcesForApplication(packageName)
-            val id = res.getIdentifier(name, "dimen", packageName)
-            return if (id == 0) 0 else res.getDimensionPixelSize(id)
-        }
-    }
-}
-
-/** Events that should call when various rendering parameters change */
-@ProtectedInterface
-interface ClockEvents {
-    @get:ProtectedReturn("return false;")
-    /** Set to enable or disable swipe interaction */
-    var isReactiveTouchInteractionEnabled: Boolean // TODO(b/364664388): Remove/Rename
-
-    /** Call whenever timezone changes */
-    fun onTimeZoneChanged(timeZone: TimeZone)
-
-    /** Call whenever the text time format changes (12hr vs 24hr) */
-    fun onTimeFormatChanged(is24Hr: Boolean)
-
-    /** Call whenever the locale changes */
-    fun onLocaleChanged(locale: Locale)
-
-    /** Call whenever the weather data should update */
-    fun onWeatherDataChanged(data: WeatherData)
-
-    /** Call with alarm information */
-    fun onAlarmDataChanged(data: AlarmData)
-
-    /** Call with zen/dnd information */
-    fun onZenDataChanged(data: ZenData)
-
-    /** Update reactive axes for this clock */
-    fun onFontAxesChanged(axes: List<ClockFontAxisSetting>)
-}
-
-/** Axis setting value for a clock */
-data class ClockFontAxisSetting(
-    /** Axis key; matches ClockFontAxis.key */
-    val key: String,
-
-    /** Value to set this axis to */
-    val value: Float,
-) {
-    companion object {
-        private val KEY_AXIS_KEY = "key"
-        private val KEY_AXIS_VALUE = "value"
-
-        fun toJson(setting: ClockFontAxisSetting): JSONObject {
-            return JSONObject().apply {
-                put(KEY_AXIS_KEY, setting.key)
-                put(KEY_AXIS_VALUE, setting.value)
-            }
-        }
-
-        fun toJson(settings: List<ClockFontAxisSetting>): JSONArray {
-            return JSONArray().apply {
-                for (axis in settings) {
-                    put(toJson(axis))
-                }
-            }
-        }
-
-        fun fromJson(jsonObj: JSONObject): ClockFontAxisSetting {
-            return ClockFontAxisSetting(
-                key = jsonObj.getString(KEY_AXIS_KEY),
-                value = jsonObj.getDouble(KEY_AXIS_VALUE).toFloat(),
-            )
-        }
-
-        fun fromJson(jsonArray: JSONArray): List<ClockFontAxisSetting> {
-            val result = mutableListOf<ClockFontAxisSetting>()
-            for (i in 0..jsonArray.length() - 1) {
-                val obj = jsonArray.getJSONObject(i)
-                if (obj == null) continue
-                result.add(fromJson(obj))
-            }
-            return result
-        }
-
-        fun toFVar(settings: List<ClockFontAxisSetting>): String {
-            val sb = StringBuilder()
-            for (axis in settings) {
-                if (sb.length > 0) sb.append(", ")
-                sb.append("'${axis.key}' ${axis.value.toInt()}")
-            }
-            return sb.toString()
-        }
-    }
-}
-
-/** Methods which trigger various clock animations */
-@ProtectedInterface
-interface ClockAnimations {
-    /** Runs an enter animation (if any) */
-    fun enter()
-
-    /** Sets how far into AOD the device currently is. */
-    fun doze(fraction: Float)
-
-    /** Sets how far into the folding animation the device is. */
-    fun fold(fraction: Float)
-
-    /** Runs the battery animation (if any). */
-    fun charge()
-
-    /**
-     * Runs when the clock's position changed during the move animation.
-     *
-     * @param fromLeft the [View.getLeft] position of the clock, before it started moving.
-     * @param direction the direction in which it is moving. A positive number means right, and
-     *   negative means left.
-     * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
-     *   it finished moving.
-     * @deprecated use {@link #onPositionUpdated(float, float)} instead.
-     */
-    fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float)
-
-    /**
-     * Runs when the clock's position changed during the move animation.
-     *
-     * @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.
-     */
-    fun onPositionUpdated(distance: Float, fraction: Float)
-
-    /**
-     * Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview,
-     * 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize
-     */
-    fun onPickerCarouselSwiping(swipingFraction: Float)
-}
-
-/** Events that have specific data about the related face */
-@ProtectedInterface
-interface ClockFaceEvents {
-    /** Call every time tick */
-    fun onTimeTick()
-
-    /**
-     * Call whenever the theme or seedColor is updated
-     *
-     * Theme can be specific to the clock face.
-     * - isDarkTheme -> clock should be light
-     * - !isDarkTheme -> clock should be dark
-     */
-    fun onThemeChanged(theme: ThemeConfig)
-
-    /**
-     * Call whenever font settings change. Pass in a target font size in pixels. The specific clock
-     * design is allowed to ignore this target size on a case-by-case basis.
-     */
-    fun onFontSettingChanged(fontSizePx: Float)
-
-    /**
-     * Target region information for the clock face. For small clock, this will match the bounds of
-     * the parent view mostly, but have a target height based on the height of the default clock.
-     * For large clocks, the parent view is the entire device size, but most clocks will want to
-     * render within the centered targetRect to avoid obstructing other elements. The specified
-     * targetRegion is relative to the parent view.
-     */
-    fun onTargetRegionChanged(targetRegion: Rect?)
-
-    /** Called to notify the clock about its display. */
-    fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean)
-}
-
-data class ThemeConfig(val isDarkTheme: Boolean, val seedColor: Int?)
-
-/** Tick rates for clocks */
-enum class ClockTickRate(val value: Int) {
-    PER_MINUTE(2), // Update the clock once per minute.
-    PER_SECOND(1), // Update the clock once per second.
-    PER_FRAME(0), // Update the clock every second.
-}
+/** Identifies a clock design */
+typealias ClockId = String
 
 /** Some data about a clock design */
 data class ClockMetadata(val clockId: ClockId)
-
-data class ClockPickerConfig
-@JvmOverloads
-constructor(
-    val id: String,
-
-    /** Localized name of the clock */
-    val name: String,
-
-    /** Localized accessibility description for the clock */
-    val description: String,
-
-    /* Static & lightweight thumbnail version of the clock */
-    val thumbnail: Drawable,
-
-    /** True if the clock will react to tone changes in the seed color */
-    val isReactiveToTone: Boolean = true,
-
-    /** Font axes that can be modified on this clock */
-    val axes: List<ClockFontAxis> = listOf(),
-)
-
-/** Represents an Axis that can be modified */
-data class ClockFontAxis(
-    /** Axis key, not user renderable */
-    val key: String,
-
-    /** Intended mode of user interaction */
-    val type: AxisType,
-
-    /** Maximum value the axis supports */
-    val maxValue: Float,
-
-    /** Minimum value the axis supports */
-    val minValue: Float,
-
-    /** Current value the axis is set to */
-    val currentValue: Float,
-
-    /** User-renderable name of the axis */
-    val name: String,
-
-    /** Description of the axis */
-    val description: String,
-) {
-    fun toSetting() = ClockFontAxisSetting(key, currentValue)
-
-    companion object {
-        fun merge(
-            fontAxes: List<ClockFontAxis>,
-            axisSettings: List<ClockFontAxisSetting>,
-        ): List<ClockFontAxis> {
-            val result = mutableListOf<ClockFontAxis>()
-            for (axis in fontAxes) {
-                val setting = axisSettings.firstOrNull { axis.key == it.key }
-                val output = setting?.let { axis.copy(currentValue = it.value) } ?: axis
-                result.add(output)
-            }
-            return result
-        }
-    }
-}
-
-/** Axis user interaction modes */
-enum class AxisType {
-    /** Continuous range between minValue & maxValue. */
-    Float,
-
-    /** Only minValue & maxValue are valid. No intermediate values between them are allowed. */
-    Boolean,
-}
-
-/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */
-data class ClockConfig(
-    val id: String,
-
-    /** Localized name of the clock */
-    val name: String,
-
-    /** Localized accessibility description for the clock */
-    val description: String,
-
-    /** Transition to AOD should move smartspace like large clock instead of small clock */
-    val useAlternateSmartspaceAODTransition: Boolean = false,
-
-    /** Deprecated version of isReactiveToTone; moved to ClockPickerConfig */
-    @Deprecated("TODO(b/352049256): Remove in favor of ClockPickerConfig.isReactiveToTone")
-    val isReactiveToTone: Boolean = true,
-
-    /** True if the clock is large frame clock, which will use weather in compose. */
-    val useCustomClockScene: Boolean = false,
-)
-
-/** Render configuration options for a clock face. Modifies the way SystemUI behaves. */
-data class ClockFaceConfig(
-    /** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */
-    val tickRate: ClockTickRate = ClockTickRate.PER_MINUTE,
-
-    /** Call to check whether the clock consumes weather data */
-    val hasCustomWeatherDataDisplay: Boolean = false,
-
-    /**
-     * Whether this clock has a custom position update animation. If true, the keyguard will call
-     * `onPositionUpdated` to notify the clock of a position update animation. If false, a default
-     * animation will be used (e.g. a simple translation).
-     */
-    val hasCustomPositionUpdatedAnimation: Boolean = false,
-
-    /** True if the clock is large frame clock, which will use weatherBlueprint in compose. */
-    val useCustomClockScene: Boolean = false,
-)
-
-/** Structure for keeping clock-specific settings */
-@Keep
-data class ClockSettings(
-    val clockId: ClockId? = null,
-    val seedColor: Int? = null,
-    val axes: List<ClockFontAxisSetting> = listOf(),
-) {
-    // Exclude metadata from equality checks
-    var metadata: JSONObject = JSONObject()
-
-    companion object {
-        private val KEY_CLOCK_ID = "clockId"
-        private val KEY_SEED_COLOR = "seedColor"
-        private val KEY_METADATA = "metadata"
-        private val KEY_AXIS_LIST = "axes"
-
-        fun toJson(setting: ClockSettings): JSONObject {
-            return JSONObject().apply {
-                put(KEY_CLOCK_ID, setting.clockId)
-                put(KEY_SEED_COLOR, setting.seedColor)
-                put(KEY_METADATA, setting.metadata)
-                put(KEY_AXIS_LIST, ClockFontAxisSetting.toJson(setting.axes))
-            }
-        }
-
-        fun fromJson(json: JSONObject): ClockSettings {
-            val clockId = if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null
-            val seedColor = if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null
-            val axisList = json.optJSONArray(KEY_AXIS_LIST)?.let(ClockFontAxisSetting::fromJson)
-            return ClockSettings(clockId, seedColor, axisList ?: listOf()).apply {
-                metadata = json.optJSONObject(KEY_METADATA) ?: JSONObject()
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt
new file mode 100644
index 0000000..6128c00
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.plugins.clocks
+
+import com.android.internal.annotations.Keep
+import org.json.JSONArray
+import org.json.JSONObject
+
+@Keep
+/** Structure for keeping clock-specific settings */
+data class ClockSettings(
+    val clockId: ClockId? = null,
+    val seedColor: Int? = null,
+    val axes: List<ClockFontAxisSetting> = listOf(),
+) {
+    // Exclude metadata from equality checks
+    var metadata: JSONObject = JSONObject()
+
+    companion object {
+        private val KEY_CLOCK_ID = "clockId"
+        private val KEY_SEED_COLOR = "seedColor"
+        private val KEY_METADATA = "metadata"
+        private val KEY_AXIS_LIST = "axes"
+
+        fun toJson(setting: ClockSettings): JSONObject {
+            return JSONObject().apply {
+                put(KEY_CLOCK_ID, setting.clockId)
+                put(KEY_SEED_COLOR, setting.seedColor)
+                put(KEY_METADATA, setting.metadata)
+                put(KEY_AXIS_LIST, ClockFontAxisSetting.toJson(setting.axes))
+            }
+        }
+
+        fun fromJson(json: JSONObject): ClockSettings {
+            val clockId = if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null
+            val seedColor = if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null
+            val axisList = json.optJSONArray(KEY_AXIS_LIST)?.let(ClockFontAxisSetting::fromJson)
+            return ClockSettings(clockId, seedColor, axisList ?: listOf()).apply {
+                metadata = json.optJSONObject(KEY_METADATA) ?: JSONObject()
+            }
+        }
+    }
+}
+
+@Keep
+/** Axis setting value for a clock */
+data class ClockFontAxisSetting(
+    /** Axis key; matches ClockFontAxis.key */
+    val key: String,
+
+    /** Value to set this axis to */
+    val value: Float,
+) {
+    companion object {
+        private val KEY_AXIS_KEY = "key"
+        private val KEY_AXIS_VALUE = "value"
+
+        fun toJson(setting: ClockFontAxisSetting): JSONObject {
+            return JSONObject().apply {
+                put(KEY_AXIS_KEY, setting.key)
+                put(KEY_AXIS_VALUE, setting.value)
+            }
+        }
+
+        fun toJson(settings: List<ClockFontAxisSetting>): JSONArray {
+            return JSONArray().apply {
+                for (axis in settings) {
+                    put(toJson(axis))
+                }
+            }
+        }
+
+        fun fromJson(jsonObj: JSONObject): ClockFontAxisSetting {
+            return ClockFontAxisSetting(
+                key = jsonObj.getString(KEY_AXIS_KEY),
+                value = jsonObj.getDouble(KEY_AXIS_VALUE).toFloat(),
+            )
+        }
+
+        fun fromJson(jsonArray: JSONArray): List<ClockFontAxisSetting> {
+            val result = mutableListOf<ClockFontAxisSetting>()
+            for (i in 0..jsonArray.length() - 1) {
+                val obj = jsonArray.getJSONObject(i)
+                if (obj == null) continue
+                result.add(fromJson(obj))
+            }
+            return result
+        }
+
+        fun toFVar(settings: List<ClockFontAxisSetting>): String {
+            val sb = StringBuilder()
+            for (axis in settings) {
+                if (sb.length > 0) sb.append(", ")
+                sb.append("'${axis.key}' ${axis.value.toInt()}")
+            }
+            return sb.toString()
+        }
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 73626b4..e3cbd66 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -120,6 +120,11 @@
      */
     boolean isListening();
 
+    /**
+     * Return this tile's {@link TileDetailsViewModel} to be used to render the TileDetailsView.
+     */
+    default TileDetailsViewModel getDetailsViewModel() { return null; }
+
     @ProvidesInterface(version = Callback.VERSION)
     interface Callback {
         static final int VERSION = 2;
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt
new file mode 100644
index 0000000..eab7d79
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.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.plugins.qs
+
+import androidx.compose.runtime.Composable
+
+/**
+ * The base view model class for rendering the Tile's TileDetailsView.
+ */
+abstract class TileDetailsViewModel {
+
+    // The view content of this tile details view.
+    @Composable
+    abstract fun GetContentView()
+
+    // The callback when the settings button is clicked. Currently this is the same as the on tile
+    // long press callback
+    abstract fun clickOnSettingsButton()
+
+    abstract fun getTitle(): String
+
+    abstract fun getSubTitle(): String
+}
diff --git a/packages/SystemUI/res/drawable/volume_background_top_legacy.xml b/packages/SystemUI/res/drawable/volume_background_top_legacy.xml
index 3cd87fc..58ae150 100644
--- a/packages/SystemUI/res/drawable/volume_background_top_legacy.xml
+++ b/packages/SystemUI/res/drawable/volume_background_top_legacy.xml
@@ -16,7 +16,7 @@
   -->
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <item>
+    <item android:gravity="bottom|end">
         <shape>
             <size android:width="@dimen/volume_dialog_panel_width" />
             <solid android:color="?androidprv:attr/colorSurface" />
diff --git a/packages/SystemUI/res/layout/audio_sharing_dialog.xml b/packages/SystemUI/res/layout/audio_sharing_dialog.xml
index 7534e15..014b7f7 100644
--- a/packages/SystemUI/res/layout/audio_sharing_dialog.xml
+++ b/packages/SystemUI/res/layout/audio_sharing_dialog.xml
@@ -84,7 +84,7 @@
         android:id="@+id/share_audio_button"
         style="@style/SettingsLibActionButton"
         android:textColor="?androidprv:attr/textColorOnAccent"
-        android:background="@drawable/audio_sharing_rounded_bg_ripple"
+        android:background="@drawable/audio_sharing_rounded_bg_ripple_top"
         android:layout_marginBottom="4dp"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
@@ -101,7 +101,7 @@
         android:id="@+id/switch_active_button"
         style="@style/SettingsLibActionButton"
         android:textColor="?androidprv:attr/textColorOnAccent"
-        android:background="@drawable/audio_sharing_rounded_bg_ripple"
+        android:background="@drawable/audio_sharing_rounded_bg_ripple_bottom"
         android:layout_marginBottom="20dp"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml
index d8967d4..e90471e 100644
--- a/packages/SystemUI/res/layout/controls_management.xml
+++ b/packages/SystemUI/res/layout/controls_management.xml
@@ -22,7 +22,6 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:gravity="center_horizontal"
-    android:paddingTop="@dimen/controls_management_top_padding"
     android:paddingStart="@dimen/controls_management_side_padding"
     android:paddingEnd="@dimen/controls_management_side_padding" >
 
diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
index ff5d9d3..a338e4c 100644
--- a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
+++ b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
@@ -29,16 +29,18 @@
         android:layout_height="@dimen/notification_2025_single_line_face_pile_size"
         android:layout_marginEnd="8dp"
     >
-        <ImageView
+        <com.android.internal.widget.CachingIconView
             android:id="@*android:id/conversation_icon"
             android:layout_width="@dimen/notification_2025_single_line_avatar_size"
             android:layout_height="@dimen/notification_2025_single_line_avatar_size"
             android:layout_gravity="center_vertical|end"
+            android:background="@*android:drawable/notification_icon_circle"
+            android:clipToOutline="true"
         />
 
         <ViewStub
             android:id="@*android:id/conversation_face_pile"
-            android:layout="@*android:layout/conversation_face_pile_layout"
+            android:layout="@*android:layout/notification_2025_conversation_face_pile_layout"
             android:layout_width="@dimen/notification_2025_single_line_face_pile_size"
             android:layout_height="@dimen/notification_2025_single_line_face_pile_size"
             android:layout_gravity="center_vertical|end"
diff --git a/packages/SystemUI/res/layout/volume_dialog_legacy.xml b/packages/SystemUI/res/layout/volume_dialog_legacy.xml
index 9010ab7..d44b6a8 100644
--- a/packages/SystemUI/res/layout/volume_dialog_legacy.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_legacy.xml
@@ -93,9 +93,7 @@
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:background="@drawable/volume_background_bottom"
-                    android:paddingLeft="@dimen/volume_dialog_ringer_rows_padding"
-                    android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding"
-                    android:paddingRight="@dimen/volume_dialog_ringer_rows_padding">
+                    android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding">
                     <com.android.keyguard.AlphaOptimizedImageButton
                         android:id="@+id/settings"
                         android:src="@drawable/horizontal_ellipsis"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index ebee157..22d8013 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gebruik Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Gekoppel"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Oudiodeling"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tik om oor te skakel of oudio te deel"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Steun oudiodeling"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gestoor"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ontkoppel"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveer"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Sluitskermlegstukke"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Om ’n app met ’n legstuk oop te maak, sal jy moet verifieer dat dit jy is. Hou ook in gedagte dat enigeen dit kan bekyk, selfs wanneer jou tablet gesluit is. Sommige legstukke is moontlik nie vir jou sluitskerm bedoel nie en dit kan onveilig wees om dit hier by te voeg."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Het dit"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Legstukke"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"As jy legstukke as ’n kortpad op die sluitskerm wil byvoeg, moet jy seker maak dit is in instellings geaktiveer."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Word aan die bokant van gesprekskennisgewings en as \'n profielfoto op sluitskerm gewys, verskyn as \'n borrel, onderbreek Moenie Steur Nie"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioriteit"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> steun nie gesprekskenmerke nie"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Verskaf bondelterugvoer"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Hierdie kennisgewings kan nie gewysig word nie."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Oproepkennisgewings kan nie gewysig word nie."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Hierdie groep kennisgewings kan nie hier opgestel word nie"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Skakel oor na app links of bo terwyl jy verdeelde skerm gebruik"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Tydens verdeelde skerm: verplaas ’n app van een skerm na ’n ander"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Skuif aktiewe venster tussen skerms"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Invoer"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Skakel oor na volgende taal"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Skakel oor na vorige taal"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Gebruik minder as <xliff:g id="LENGTH">%1$d</xliff:g> karakters"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Bounommer"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Bounommer is na knipbord gekopieer."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopieer na knipbord."</string>
     <string name="basic_status" msgid="2315371112182658176">"Maak gesprek oop"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Gespreklegstukke"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Tik op \'n gesprek om dit by jou tuisskerm te voeg"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Keer die foon om vir hoër resolusie"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Voubare toestel word ontvou"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Voubare toestel word omgekeer"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Voorste skerm is aangeskakel"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"gevou"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"oopgevou"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Toeganklikheid"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Kortpadsleutels"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Pasmaak kortpadsleutels"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Druk sleutel om kortpad toe te wys"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Verwyder kortpad?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Druk sleutel om kortpad toe te wys"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Dit sal jou gepasmaakte kortpad permanent uitvee."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Soekkortpaaie"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Geen soekresultate nie"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Vou ikoon in"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Klaar"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Vou ikoon uit"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"of"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Sleephandvatsel"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Sleutelbordinstellings"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Stel kortpad"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Verwyder"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Kanselleer"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Druk sleutel"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Sleutelkombinasie is reeds in gebruik. Probeer ’n ander sleutel."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Sleutelkombinasie is reeds in gebruik. Probeer ’n ander sleutel."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Kortpad kan nie gestel word nie."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigeer met jou sleutelbord"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Leer kortpadsleutels"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigeer met jou raakpaneel"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 0351c75..e36aab2 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"ኦዲዮ ለመቀየር ወይም ለማጋራት መታ ያድርጉ"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"የድምፅ ማጋራትን ይደግፋል"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"የማያ ገፅ ቁልፍ ምግብሮች"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ምግብር በመጠቀም መተግበሪያ ለመክፈት እርስዎ መሆንዎን ማረጋገጥ አለብዎት። እንዲሁም የእርስዎ ጡባዊ በተቆለፈበት ጊዜ እንኳን ማንኛውም ሰው እነሱን ማየት እንደሚችል ከግምት ውስጥ ያስገቡ። አንዳንድ ምግብሮች ለማያ ገፅ ቁልፍዎ የታሰቡ ላይሆኑ ይችላሉ እና እዚህ ለማከል አስተማማኝ ላይሆኑ ይችላሉ።"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ገባኝ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ምግብሮች"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"ማያ ገጽ ቁልፍ ላይ ምግብሮችን እንደ አቋራጭ ለማከል በቅንብሮች ውስጥ መንቃቱን ያረጋግጡ።"</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"በውይይት ማሳወቂያዎች አናት ላይ እና በማያ ገፅ መቆለፊያ ላይ እንደ መገለጫ ምስል ይታያል፣ እንደ አረፋ ሆኖ ይታያል፣ አትረብሽን ያቋርጣል"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"ቅድሚያ"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> የውይይት ባህሪያትን አይደግፍም"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"የቅርቅብ ግብረመልስ አቅርብ"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"እነዚህ ማሳወቂያዎች ሊሻሻሉ አይችሉም።"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"የጥሪ ማሳወቂያዎች ሊቀየሩ አይችሉም።"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"የማሳወቂያዎች ይህ ቡድን እዚህ ላይ ሊዋቀር አይችልም"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"የተከፈለ ማያ ገጽን ሲጠቀሙ በቀኝ ወይም ከላይ ወዳለ መተግበሪያ ይቀይሩ"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"በተከፈለ ማያ ገጽ ወቅት፡- መተግበሪያን ከአንዱ ወደ ሌላው ተካ"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"በማሳያዎች መካከል ንቁ መስኮትን ያንቀሳቅሱ"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ግቤት"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"ወደ ቀጣዩ ቋንቋ ቀይር"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"ወደ ቀዳሚ ቋንቋ ቀይር"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"ከ<xliff:g id="LENGTH">%1$d</xliff:g> የሚያንሱ ቁምፊዎችን ይጠቀሙ"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"የግንብ ቁጥር"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"የገንባ ቁጥር ወደ ቅንጥብ ሰሌዳ ተቀድቷል።"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ወደ ቅንጥብ ሰሌዳ ቅዳ።"</string>
     <string name="basic_status" msgid="2315371112182658176">"ውይይት ይክፈቱ"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"የውይይት ምግብሮች"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"በመነሻ ማያ ገጽዎ ላይ ለማከል አንድ ውይይት መታ ያድርጉ"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ለከፍተኛ ጥራት ስልኩን ይቀይሩ"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"መታጠፍ የሚችል መሣሪያ እየተዘረጋ ነው"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"መታጠፍ የሚችል መሣሪያ እየተገለበጠ ነው"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"የፊት ለፊት ማያ ገፅ በርቷል"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"የታጠፈ"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"የተዘረጋ"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ተደራሽነት"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"የቁልፍ ሰሌዳ አቋራጮች"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"የቁልፍ ሰሌዳ አቋራጮችን ያብጁ"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"አቋራጭ ለመመደብ ቁልፍ ይጫኑ"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"አቋራጭ ይወገድ?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"አቋራጭ ለመመደብ ቁልፍ ይጫኑ"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ይህ ብጁ አቋራጭዎን በቋሚነት ይሰርዛል።"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"የፍለጋ አቋራጮች"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ምንም የፍለጋ ውጤቶች የሉም"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"መሰብሰቢያ አዶ"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"ተከናውኗል"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"መዘርጊያ አዶ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ወይም"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"መያዣ ይጎትቱ"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"የቁልፍ ሰሌዳ ቅንብሮች"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"አቋራጭ አቀናብር"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"አስወግድ"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ይቅር"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"ቁልፍ ይጫኑ"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"የቁልፍ ጥምረት አስቀድሞ በሥራ ላይ ነው። ሌላ ቁልፍ ይሞክሩ።"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"የቁልፍ ጥምረት አስቀድሞ በሥራ ላይ ነው። ሌላ ቁልፍ ይሞክሩ።"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"አቋራጩ ሊቀናበር አይችልም።"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"የቁልፍ ሰሌዳዎን በመጠቀም ያስሱ"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"የቁልፍ ሰሌዳ አቋራጮችን ይወቁ"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"የመዳሰሻ ሰሌዳዎን በመጠቀም ያስሱ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 645d880..d6e91cc 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -113,7 +113,7 @@
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"تسجيل شاشة تطبيق واحد"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"تسجيل الشاشة بكاملها"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen_for_display" msgid="3754611651558838691">"‏تسجيل محتوى الشاشة بالكامل: %s"</string>
-    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"أثناء تسجيل محتوى الشاشة بالكامل، يتم تسجيل كل المحتوى المعروض على شاشتك. لذا يُرجى توخي الحذر بشأن المعلومات، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
+    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"أثناء تسجيل محتوى الشاشة بالكامل، يتم تسجيل كل المحتوى المعروض على شاشتك، لذا يُرجى توخي الحذر بشأن المعلومات الظاهرة، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور والمقاطع الصوتية والفيديوهات."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"سيتم تسجيل كل المحتوى المعروض أو المشغَّل على شاشة التطبيق، لذا يُرجى توخي الحذر بشأن المعلومات الظاهرة، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور والمقاطع الصوتية والفيديوهات."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"تسجيل الشاشة"</string>
     <string name="screenrecord_app_selector_title" msgid="3854492366333954736">"يُرجى اختيار تطبيق لتسجيل شاشته"</string>
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"انقر لتبديل مصدر الصوت أو مشاركته"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"تتوفّر ميزة \"مشاركة الصوت\""</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"التطبيقات المصغّرة المصمَّمة لشاشة القفل"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"لفتح تطبيق باستخدام تطبيق مصغَّر، عليك إثبات هويتك. يُرجى ملاحظة أنّ أي شخص يمكنه الاطّلاع محتوى التطبيقات المصغَّرة، حتى وإن كان جهازك اللوحي مُقفلاً. بعض التطبيقات المصغّرة قد لا تكون مُصمَّمة لإضافتها إلى شاشة القفل، وقد يكون هذا الإجراء غير آمن."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"حسنًا"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"التطبيقات المصغَّرة"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"لإضافة التطبيقات المصغّرة على شاشة القفل كاختصار، تأكَّد من تفعيلها في الإعدادات."</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>
@@ -695,10 +697,10 @@
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"‏%1$s. انقر للتجاهل. قد يتم تجاهل خدمات \"سهولة الاستخدام\"."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"‏%1$s. انقر للتعيين على الاهتزاز."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"‏%1$s. انقر لكتم الصوت."</string>
-    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"التحكُّم في مستوى الضوضاء"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"التحكّم بالضوضاء"</string>
     <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_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_ringer_change" msgid="3574969197796055532">"انقر لتغيير وضع الرنين."</string>
     <string name="volume_ringer_mode" msgid="6867838048430807128">"وضع الرنين"</string>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"تظهر في أعلى إشعارات المحادثات وكصورة ملف شخصي على شاشة القفل وتظهر على شكل فقاعة لمقاطعة ميزة \"عدم الإزعاج\"."</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"الأولوية"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"لا يدعم تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> ميزات المحادثات."</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"تقديم ملاحظات مُجمّعة"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"يتعذّر تعديل هذه الإشعارات."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"لا يمكن تعديل إشعارات المكالمات."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"يتعذّر ضبط مجموعة الإشعارات هذه هنا."</string>
@@ -872,6 +875,10 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"التبديل إلى التطبيق على اليمين أو الأعلى أثناء استخدام \"تقسيم الشاشة\""</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"استبدال تطبيق بآخر في وضع \"تقسيم الشاشة\""</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"نقل نافذة نشطة بين شاشات العرض"</string>
+    <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"نقل النافذة إلى اليمين"</string>
+    <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"نقل النافذة إلى اليسار"</string>
+    <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"تكبير النافذة"</string>
+    <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"تصغير النافذة"</string>
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"الإدخال"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"التبديل إلى اللغة التالية"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"التبديل إلى اللغة السابقة"</string>
@@ -1220,8 +1227,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"يجب استخدام أقل من <xliff:g id="LENGTH">%1$d</xliff:g> حرف."</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"رقم الإصدار"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"تم نسخ رقم الإصدار إلى الحافظة."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"نسخ المحتوى إلى الحافظة"</string>
     <string name="basic_status" msgid="2315371112182658176">"محادثة مفتوحة"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"التطبيقات المصغّرة للمحادثات"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"انقر على محادثة لإضافتها إلى \"الشاشة الرئيسية\""</string>
@@ -1357,8 +1363,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"للحصول على درجة دقة أعلى، اقلِب الهاتف."</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"جهاز قابل للطي يجري فتحه"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"جهاز قابل للطي يجري قلبه"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"تم تفعيل الشاشة الأمامية"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"مطوي"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"غير مطوي"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"‫%1$s / %2$s"</string>
@@ -1418,7 +1423,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"تسهيل الاستخدام"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"اختصارات لوحة المفاتيح"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"تخصيص اختصارات لوحة المفاتيح"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"اضغط على مفتاح لتخصيص الاختصار"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"هل تريد إزالة هذا الاختصار؟"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"اضغط على مفتاح لتخصيص الاختصار"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"سيؤدي هذا الإجراء إلى حذف الاختصار المخصّص نهائيًا."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"البحث في الاختصارات"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ما مِن نتائج بحث"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"رمز التصغير"</string>
@@ -1428,12 +1435,17 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"تم"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"رمز التوسيع"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"أو"</string>
+    <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"زائد"</string>
+    <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"شرطة مائلة للأمام"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"مقبض السحب"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"إعدادات لوحة المفاتيح"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ضبط الاختصار"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"إزالة"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"إلغاء"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"اضغط على مفتاح"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"يتم حاليًا استخدام مجموعة المفاتيح هذه. يُرجى تجربة مفتاح آخر."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"يتم حاليًا استخدام مجموعة المفاتيح هذه. يُرجى تجربة مفتاح آخر."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"تعذَّر ضبط الاختصار."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"التنقّل باستخدام لوحة المفاتيح"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"تعرَّف على اختصارات لوحة المفاتيح"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"التنقّل باستخدام لوحة اللمس"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index e14f4cc..8f5b1e1 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"অডিঅ’ সলনি কৰিবলৈ বা শ্বেয়াৰ কৰিলৈ টিপক"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"অডিঅ’ শ্বেয়াৰিং সমৰ্থন কৰে"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"লক স্ক্ৰীন ৱিজেট"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"এটা ৱিজেট ব্যৱহাৰ কৰি কোনো এপ্ খুলিবলৈ, এয়া আপুনিয়েই বুলি সত্যাপন পৰীক্ষা কৰিব লাগিব। লগতে, মনত ৰাখিব যে যিকোনো লোকেই সেইবোৰ চাব পাৰে, আনকি আপোনাৰ টেবলেটটো লক হৈ থাকিলেও। কিছুমান ৱিজেট হয়তো আপোনাৰ লক স্ক্ৰীনৰ বাবে কৰা হোৱা নাই আৰু ইয়াত যোগ কৰাটো অসুৰক্ষিত হ’ব পাৰে।"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"বুজি পালোঁ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ৱিজেট"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"লক স্ক্ৰীনৰ ৱিজেট সুবিধাটো শ্বৰ্টকাট হিচাপে যোগ দিবলৈ ছেটিঙত সেয়া সক্ষম হৈ থকাটো নিশ্চিত কৰক।"</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"বাৰ্তালাপৰ জাননীৰ শীৰ্ষত আৰু প্ৰ’ফাইল চিত্ৰ হিচাপে লক স্ক্ৰীনত দেখুৱায়, এটা বাবল হিচাপে দেখা পোৱা যায়, অসুবিধা নিদিব ম’ডত ব্যাঘাত জন্মায়"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"অগ্ৰাধিকাৰ"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ বাৰ্তালাপৰ সুবিধাসমূহ সমৰ্থন নকৰে"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"বাণ্ডল হিচাপে থকা জাননীত মতামত প্ৰদান কৰক"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"এই জাননীসমূহ সংশোধন কৰিব নোৱাৰি।"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"কলৰ জাননীসমূহ সংশোধন কৰিব নোৱাৰি।"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"এই ধৰণৰ জাননীবোৰ ইয়াত কনফিগাৰ কৰিব পৰা নাযায়"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"বিভাজিত স্ক্ৰীন ব্যৱহাৰ কৰাৰ সময়ত বাওঁফালে অথবা ওপৰত থকা এপলৈ সলনি কৰক"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"বিভাজিত স্ক্ৰীনৰ ব্যৱহাৰ কৰাৰ সময়ত: কোনো এপ্ এখন স্ক্ৰীনৰ পৰা আনখনলৈ নিয়ক"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ডিছপ্লে’সমূহৰ মাজত সক্রিয় হৈ থকা ৱিণ্ড’ সলনা সলনিকৈ ব্যৱহাৰ কৰক"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ইনপুট"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"পৰৱৰ্তী ভাষাটোলৈ সলনি কৰক"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"পূৰ্বৰ ভাষালৈ সলনি কৰক"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> টাতকৈ কম বৰ্ণ ব্যৱহাৰ কৰক"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"বিল্ডৰ নম্বৰ"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"ক্লিপব’ৰ্ডলৈ বিল্ডৰ নম্বৰ প্ৰতিলিপি কৰা হ’ল।"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ক্লিপব\'ৰ্ডলৈ প্ৰতিলিপি কৰক।"</string>
     <string name="basic_status" msgid="2315371112182658176">"বাৰ্তালাপ খোলক"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"বাৰ্তালাপ ৱিজেট"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"আপোনাৰ গৃহ স্ক্ৰীনত কোনো বাৰ্তালাপ যোগ দিবলৈ সেইটোত টিপক"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"অধিক ৰিজ’লিউশ্বনৰ বাবে, ফ’নটো লুটিয়াই দিয়ক"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"জপাব পৰা ডিভাইচৰ জাপ খুলি থকা হৈছে"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"জপাব পৰা ডিভাইচৰ ওলোটাই থকা হৈছে"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"সন্মুখৰ স্ক্ৰীনখন অন কৰা হৈছে"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ফ’ল্ড কৰা"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"আনফ’ল্ড কৰা"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"সাধ্য সুবিধা"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"কীব’ৰ্ডৰ শ্বৰ্টকাট"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"কীব’ৰ্ডৰ শ্বৰ্টকাট কাষ্টমাইজ কৰক"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"শ্বৰ্টকাটৰ ভূমিকা অৰ্পণ কৰিবলৈ কী টিপক"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"শ্বৰ্টকাট আঁতৰাবনে?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"শ্বৰ্টকাটৰ ভূমিকা অৰ্পণ কৰিবলৈ কী টিপক"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"এইটোৱে আপোনাৰ কাষ্টম শ্বৰ্টকাট মচিব।"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"সন্ধানৰ শ্বৰ্টকাট"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"সন্ধানৰ কোনো ফলাফল নাই"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"সংকোচন কৰাৰ চিহ্ন"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"হ’ল"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"বিস্তাৰ কৰাৰ চিহ্ন"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"অথবা"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ড্ৰেগ হেণ্ডেল"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"কীব’ৰ্ডৰ ছেটিং"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"শ্বৰ্টকাট ছেট কৰক"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"আঁতৰাওক"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"বাতিল কৰক"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"কী টিপক"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"কীৰ মিশ্ৰণ ইতিমধ্যে ব্যৱহাৰ হৈ আছে। অন্য এটা কী ব্যৱহাৰ কৰি চাওক।"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"কীৰ মিশ্ৰণ ইতিমধ্যে ব্যৱহাৰ হৈ আছে। অন্য এটা কী ব্যৱহাৰ কৰি চাওক।"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"শ্বৰ্টকাট ছেট কৰিব নোৱাৰি।"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"কীব’ৰ্ড ব্যৱহাৰ কৰি নেভিগে’ট কৰক"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"কীব’ৰ্ডৰ শ্বৰ্টকাটসমূহৰ বিষয়ে জানক"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"আপোনাৰ টাচ্চপেড ব্যৱহাৰ কৰি নেভিগে’ট কৰক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 9016870..1126da1 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-u açın"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Qoşulub"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio paylaşma"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Dəyişmək və ya audio paylaşmaq üçün toxunun"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Audio paylaşma dəstəklənir"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Yadda saxlandı"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"əlaqəni kəsin"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivləşdirin"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Kilid ekranı vidcetləri"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Vidcetdən istifadə edərək tətbiqi açmaq üçün kimliyi doğrulamalısınız. Planşet kilidli olsa da, hər kəs vidcetlərə baxa bilər. Bəzi vidcetlər kilid ekranı üçün nəzərdə tutulmayıb və bura əlavə etmək təhlükəli ola bilər."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Anladım"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Vidcetlər"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Kilidli ekrana qısayol kimi Vidcet əlavə etmək üçün onun ayarlarda aktiv olduğundan əmin olun."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Söhbət bildirişlərinin yuxarısında və kilid ekranında profil şəkli kimi göstərilir, baloncuq kimi görünür, Narahat Etməyin rejimini kəsir"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritet"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> söhbət funksiyalarını dəstəkləmir"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Paket rəyi təmin edin"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Bu bildirişlər dəyişdirilə bilməz."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Zəng bildirişləri dəyişdirilə bilməz."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Bu bildiriş qrupunu burada konfiqurasiya etmək olmaz"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Bölünmüş ekran istifadə edərkən solda və ya yuxarıda tətbiqə keçin"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Bölünmüş ekran rejimində: tətbiqi birindən digərinə dəyişin"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Aktiv pəncərəni displeylər arasında hərəkət etdirin"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Daxiletmə"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Növbəti dilə keçin"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Əvvəlki dilə keçin"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Maksimum <xliff:g id="LENGTH">%1$d</xliff:g> simvol istifadə edin"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Montaj nömrəsi"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Versiya nömrəsi mübadilə buferinə kopyalandı."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"mübadilə buferinə kopiyalayın."</string>
     <string name="basic_status" msgid="2315371112182658176">"Açıq söhbət"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Söhbət vidcetləri"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Əsas ekranınıza əlavə etmək üçün söhbətə toxunun"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Daha yüksək ayırdetmə dəqiqliyi üçün telefonu çevirin"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Qatlana bilən cihaz açılır"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Qatlana bilən cihaz fırladılır"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Ön ekran aktiv edildi"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"qatlanmış"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"açıq"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Xüsusi imkanlar"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klaviatura qısayolları"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Klaviatura qısayollarını fərdiləşdirin"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Qısayol təyin etmək üçün düyməni basın"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Qısayol silinsin?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Qısayol təyin etmək üçün düyməni basın"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Bu, fərdi qısayolunuzu həmişəlik siləcək."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Axtarış qısayolları"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Axtarış nəticəsi yoxdur"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"İkonanı yığcamlaşdırın"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Hazırdır"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"İkonanı genişləndirin"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"və ya"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Dəstəyi çəkin"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Klaviatura ayarları"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Qısayol ayarlayın"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Silin"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Ləğv edin"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Düyməni basın"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Düymə kombinasiyası artıq istifadə olunur. Başqa düyməni sınayın."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Düymə kombinasiyası artıq istifadə olunur. Başqa düyməni sınayın."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Qısayol ayarlana bilməz."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Klaviaturadan istifadə edərək hərəkət edin"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Klaviatura qısayolları haqqında öyrənin"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Taçpeddən istifadə edərək hərəkət edin"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index d2636c0..a996c8a 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Deljenje zvuka"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Dodirnite da biste prebacili ili delili zvuk"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Podržava deljenje zvuka"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sačuvano"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekinite vezu"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivirajte"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Vidžeti za zaključani ekran"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Da biste otvorili aplikaciju koja koristi vidžet, treba da potvrdite da ste to vi. Imajte u vidu da svako može da ga vidi, čak i kada je tablet zaključan. Neki vidžeti možda nisu namenjeni za zaključani ekran i možda nije bezbedno da ih tamo dodate."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Važi"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Vidžeti"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Da biste dodali vidžete na zaključani ekran kao prečicu, uverite se da je to omogućeno u podešavanjima."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Prikazuje se u vrhu obaveštenja o konverzacijama i kao slika profila na zaključanom ekranu, pojavljuje se kao oblačić, prekida režim Ne uznemiravaj"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritetno"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ne podržava funkcije konverzacije"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Pružite povratne informacije o skupu"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Ova obaveštenja ne mogu da se menjaju."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Obaveštenja o pozivima ne mogu da se menjaju."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Ova grupa obaveštenja ne može da se konfiguriše ovde"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Pređite u aplikaciju sleva ili iznad dok koristite podeljeni ekran"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"U režimu podeljenog ekrana: zamena jedne aplikacije drugom"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Premesti aktivan prozor na sledeći ekran"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Unos"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Pređi na sledeći jezik"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Pređi na prethodni jezik"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Koristite manji broj znakova od <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj verzije"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Broj verzije je kopiran u privremenu memoriju."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopirajte u privremenu memoriju."</string>
     <string name="basic_status" msgid="2315371112182658176">"Otvorite konverzaciju"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Vidžeti za konverzaciju"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Dodirnite konverzaciju da biste je dodali na početni ekran"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za veću rezoluciju obrnite telefon"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Uređaj na preklop se otvara"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Uređaj na preklop se obrće"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Prednji ekran je uključen"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zatvoreno"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"otvoreno"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pristupačnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tasterske prečice"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Prilagodite tasterske prečice"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pritisnite taster da biste dodelili prečicu"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Želite da uklonite prečicu?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pritisnite taster da biste dodelili prečicu"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ovim ćete trajno izbrisati prilagođenu prečicu."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pretražite prečice"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nema rezultata pretrage"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona za skupljanje"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Gotovo"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona za proširivanje"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Marker za prevlačenje"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Podešavanja tastature"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Podesi prečicu"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Ukloni"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Otkaži"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pritisnite taster"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinacija tastera se već koristi. Probajte sa drugim tasterom."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinacija tastera se već koristi. Probajte sa drugim tasterom."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Podešavanje prečice nije uspelo."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Krećite se pomoću tastature"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Saznajte više o tasterskim prečicama"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Krećite se pomoću tačpeda"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 388950c..c14d1b5 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"Націсніце, каб пераключыць або абагуліць аўдыя"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Падтрымліваецца абагульванне аўдыя"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Віджэты на экране блакіроўкі"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Каб адкрыць праграму з дапамогай віджэта, вам неабходна будзе пацвердзіць сваю асобу. Таксама памятайце, што такія віджэты могуць пабачыць іншыя людзі, нават калі экран планшэта заблакіраваны. Некаторыя віджэты могуць не падыходзіць для выкарыстання на экране блакіроўкі, і дадаваць іх сюды можа быць небяспечна."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Зразумела"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Віджэты"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Каб можна было дадаць віджэты на экран блакіроўкі ў якасці спалучэння клавіш, яны павінны быць уключаны ў наладах."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"З’яўляецца ўверсе раздзела размоў як усплывальнае апавяшчэнне, якое перарывае рэжым \"Не турбаваць\" і паказвае на экране блакіроўкі відарыс профілю"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Прыярытэт"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> не падтрымлівае функцыі размовы"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Пакінуць групавы водгук"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Гэтыя апавяшчэнні нельга змяніць."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Апавяшчэнні пра выклікі нельга змяніць."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Тут канфігурыраваць гэту групу апавяшчэнняў забаронена"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Пераключыцца на праграму злева або ўверсе на падзеленым экране"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"У рэжыме падзеленага экрана замяніць адну праграму на іншую"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Перамясціць актыўнае акно паміж дысплэямі"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Увод"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Пераключыцца на наступную мову"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Пераключыцца на папярэднюю мову"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Колькасць сімвалаў павінна быць меншай за <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Нумар зборкі"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Нумар зборкі скапіраваны ў буфер абмену."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"скапіраваць у буфер абмену."</string>
     <string name="basic_status" msgid="2315371112182658176">"Адкрытая размова"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Віджэты размовы"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Націсніце на размову, каб дадаць яе на галоўны экран"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Каб зрабіць фота з больш высокай раздзяляльнасцю, павярніце тэлефон"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Складная прылада ў раскладзеным выглядзе"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Перавернутая складная прылада"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Пярэдні экран уключаны"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"складзена"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"раскладзена"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Спецыяльныя магчымасці"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Спалучэнні клавіш"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Наладзіць спалучэнні клавіш"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Націсніце клавішу, каб прызначыць спалучэнне клавіш"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Выдаліць спалучэнне клавіш?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Націсніце клавішу, каб прызначыць спалучэнне клавіш"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Гэта дзеянне назаўсёды выдаліць прызначанае вамі спалучэнне клавіш."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Пошук спалучэнняў клавіш"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Няма вынікаў пошуку"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок \"Згарнуць\""</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Гатова"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок \"Разгарнуць\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"або"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер перацягвання"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Налады клавіятуры"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Наладзіць спалучэнне клавіш"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Выдаліць"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Скасаваць"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Націсніце клавішу"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Гэта спалучэнне клавіш ужо выкарыстоўваецца. Паспрабуйце іншую клавішу."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Гэта спалучэнне клавіш ужо выкарыстоўваецца. Паспрабуйце іншую клавішу."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Не ўдаецца наладзіць спалучэнне клавіш."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Навігацыя з дапамогай клавіятуры"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Азнаёмцеся са спалучэннямі клавіш"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Навігацыя з дапамогай сэнсарнай панэлі"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 2d68f91..73de179 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"Докоснете, за да превключите или споделите аудио"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Поддържа споделяне на звука"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Приспособления за заключения екран"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"За да отворите дадено приложение посредством приспособление, ще трябва да потвърдите, че това сте вие. Също така имайте предвид, че всеки ще вижда приспособленията дори когато таблетът ви е заключен. Възможно е някои от тях да не са предназначени за заключения екран и добавянето им на него може да е опасно."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Разбрах"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Приспособления"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"За да добавите приспособления към заключения екран като пряк път, уверете се, че са активирани в настройките."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Показва се в горната част на известията за разговори и като снимка на потребителския профил на заключения екран, изглежда като балонче, прекъсва режима „Не безпокойте“"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Приоритет"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> не поддържа функциите за разговор"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Предоставяне на отзиви за пакета"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Тези известия не могат да бъдат променяни."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Известията за обаждания не могат да бъдат променяни."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Тази група от известия не може да бъде конфигурирана тук"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Превключване към приложението вляво/отгоре в режима на разделен екран"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"При разделен екран: замяна на дадено приложение с друго"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Преместване на активния прозорец между екраните"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Въвеждане"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Превключване към следващия език"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Превключване към предишния език"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Използвайте по-малко от <xliff:g id="LENGTH">%1$d</xliff:g> знака"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер на компилацията"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Номерът на компилацията е копиран в буферната памет."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"копиране в буферната памет."</string>
     <string name="basic_status" msgid="2315371112182658176">"Отворен разговор"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Приспособления за разговор"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Докоснете разговор, за да го добавите към началния си екран"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"За по-висока разделителна способност обърнете телефона"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Разгъване на сгъваемо устройство"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Обръщане на сгъваемо устройство"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Предният екран е включен"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"затворено"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"отворено"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Достъпност"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Клавишни комбинации"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Персонализиране на клавишните комбинации"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Натиснете клавиш, за да зададете клавишна комбинация"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Да се премахне ли клавишната комбинация?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Натиснете клавиш, за да зададете клавишна комбинация"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Това ще изтрие персонализираната клавишна комбинация за постоянно."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Търсете клавишни комбинации"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Няма резултати от търсенето"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за свиване"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Готово"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за разгъване"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Манипулатор за преместване с плъзгане"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Настройки на клавиатурата"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Задаване на клавишна комбинация"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Премахване"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Отказ"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Натиснете клавиш"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Клавишната комбинация вече се използва. Опитайте с друг клавиш."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Клавишната комбинация вече се използва. Опитайте с друг клавиш."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Прекият път не може да се зададе."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Навигирайте посредством клавиатурата си"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Научете за клавишните комбинации"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Навигирайте посредством сензорния панел"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 4281ea1..edbf73e 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"অডিও শেয়ার বা পরিবর্তন করতে ট্যাপ করুন"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"\'অডিও শেয়ারিং\' ফিচার কাজ করে"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"লক স্ক্রিন উইজেট"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"উইজেট ব্যবহার করে কোনও অ্যাপ খুলতে, আপনাকে নিজের পরিচয় যাচাই করতে হবে। এছাড়াও, মনে রাখবেন, আপনার ট্যাবলেট লক থাকলেও যেকেউ তা দেখতে পারবেন। কিছু উইজেট আপনার লক স্ক্রিনের উদ্দেশ্যে তৈরি করা হয়নি এবং এখানে যোগ করা নিরাপদ নাও হতে পারে।"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"বুঝেছি"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"উইজেট"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"শর্টকাট হিসেবে লক স্ক্রিনে উইজেট যোগ করলে, সেটি সেটিংস থেকে চালু আছে কিনা ভালো করে দেখে নিন।"</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"কথোপকথনের বিজ্ঞপ্তির উপরের দিকে এবং প্রোফাইল ছবি হিসেবে লক স্ক্রিনে দেখানো হয়, বাবল হিসেবেও এটি দেখা যায় এবং এর ফলে \'বিরক্ত করবে না\' মোডে কাজ করতে অসুবিধা হয়"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"অগ্রাধিকার"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এ কথোপকথন ফিচার কাজ করে না"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"বান্ডেল সম্পর্কে মতামত দিন"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"এই বিজ্ঞপ্তিগুলি পরিবর্তন করা যাবে না।"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"কল বিজ্ঞপ্তি পরিবর্তন করা যাবে না।"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"এই সমস্ত বিজ্ঞপ্তিকে এখানে কনফিগার করা যাবে না"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"স্প্লিট স্ক্রিন ব্যবহার করার সময় বাঁদিকের বা উপরের অ্যাপে পাল্টে নিন"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"\'স্প্লিট স্ক্রিন\' থাকাকালীন: একটি অ্যাপ থেকে অন্যটিতে পাল্টান"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ডিসপ্লের মধ্যে একটি থেকে অপরটিতে অ্যাক্টিভ উইন্ডোটি সরান"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ইনপুট"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"পরবর্তী ভাষায় পাল্টান"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"আগের ভাষায় পাল্টান"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g>টির চেয়ে কম অক্ষর ব্যবহার করুন"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"বিল্ড নম্বর"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"বিল্ড নম্বর ক্লিপবোর্ডে কপি করা হয়েছে।"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ক্লিপবোর্ডে কপি করুন।"</string>
     <string name="basic_status" msgid="2315371112182658176">"খোলা কথোপকথন"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"কথোপকথন উইজেট"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"কোনও কথোপথন আপনার হোম স্ক্রিনে যোগ করার জন্য এতে ট্যাপ করুন"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"আরও বেশি রেজোলিউশনের জন্য, ফোন ফ্লিপ করুন"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ফোল্ড করা যায় এমন ডিভাইস খোলা হচ্ছে"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ফোল্ড করা যায় এমন ডিভাইস উল্টানো হচ্ছে"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ফ্রন্ট স্ক্রিন চালু আছে"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ফোল্ড করা রয়েছে"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ফোল্ড করা নেই"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"অ্যাক্সেসিবিলিটি"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"কীবোর্ড শর্টকাট"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"কীবোর্ড শর্টকাট কাস্টমাইজ করুন"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"শর্টকাট অ্যাসাইন করতে কী প্রেস করুন"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"শর্টকাট সরাবেন?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"শর্টকাট অ্যাসাইন করতে কী প্রেস করুন"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"এটি আপনার কাস্টম শর্টকাট স্থায়ীভাবে মুছে ফেলবে।"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"শর্টকাট সার্চ করুন"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"কোনও সার্চ ফলাফল নেই"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"আইকন আড়াল করুন"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"হয়ে গেছে"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"আইকন বড় করুন"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"অথবা"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"টেনে আনার হ্যান্ডেল"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"কীবোর্ড সেটিংস"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"শর্টকাট সেট করুন"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"সরান"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"বাতিল করুন"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"কী প্রেস করুন"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"কী কম্বিনেশন আগে থেকে ব্যবহার হচ্ছে। অন্য কী ব্যবহার করে দেখুন।"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"কী কম্বিনেশন আগে থেকে ব্যবহার হচ্ছে। অন্য কী ব্যবহার করে দেখুন।"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"শর্টকাট সেট করা যায়নি।"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"আপনার কীবোর্ড ব্যবহার করে নেভিগেট করুন"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"কীবোর্ড শর্টকাট সম্পর্কে জানুন"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"আপনার টাচপ্যাড ব্যবহার করে নেভিগেট করুন"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 9d9bf7f..ab5ffae 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Dijeljenje zvuka"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Dodirnite da uključite ili dijelite zvučni zapis"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Podržava dijeljenje zvuka"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sačuvano"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekid veze"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviranje"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Vidžeti na zaključanom ekranu"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Da otvorite aplikaciju pomoću vidžeta, morat ćete potvrditi identitet. Također imajte na umu da ih svako može pregledati, čak i ako je tablet zaključan. Neki vidžeti možda nisu namijenjeni za vaš zaključani ekran i njihovo dodavanje ovdje možda nije sigurno."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Razumijem"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Vidžeti"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Da dodate vidžete na zaključani ekran kao prečicu, provjerite je li ovo omogućeno u postavkama."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Prikazuje se na vrhu obavještenja u razgovorima i kao slika profila na zaključanom ekranu, izgleda kao oblačić, prekida funkciju Ne ometaj"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritetno"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> ne podržava funkcije razgovora"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Pošaljite povratne informacije o paketu"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Ta obavještenja se ne mogu izmijeniti."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Nije moguće izmijeniti obavještenja o pozivima."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Ovu grupu obavještenja nije moguće konfigurirati ovdje"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Pređite u aplikaciju lijevo ili iznad dok koristite podijeljeni ekran"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Za vrijeme podijeljenog ekrana: zamjena jedne aplikacije drugom"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Premještanje aktivnog prozora između ekrana"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Unos"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Prebacivanje na sljedeći jezik"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Prebacivanje na prethodni jezik"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Koristite manje od <xliff:g id="LENGTH">%1$d</xliff:g> znak(ov)a"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj verzije"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Broj verzije je kopiran u međumemoriju."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopiranje u međumemoriju."</string>
     <string name="basic_status" msgid="2315371112182658176">"Otvoreni razgovor"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Vidžeti razgovora"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Dodirnite razgovor da ga dodate na početni ekran"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za višu rezoluciju obrnite telefon"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Sklopivi uređaj se rasklapa"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Sklopivi uređaj se obrće"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Prednji ekran je uključen"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"sklopljeno"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"otklopljeno"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pristupačnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Prečice tastature"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Prilagodite prečice na tastaturi"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pritisnite tipku da dodijelite prečicu"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Ukloniti prečicu?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pritisnite tipku da dodijelite prečicu"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ovo će trajno izbrisati prilagođenu prečicu."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Prečica pretraživanja"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nema rezultata pretraživanja"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona sužavanja"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Gotovo"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona proširivanja"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ručica za prevlačenje"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Postavke tastature"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Postavi prečicu"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Ukloni"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Otkaži"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pritisnite tipku"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Ta se kombinacija tipki već koristi. Pokušajte s drugom tipkom."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ta se kombinacija tipki već koristi. Pokušajte s drugom tipkom."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Prečica se ne može postaviti."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Krećite se pomoću tastature"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Saznajte više o prečicama tastature"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Krećite se pomoću dodirne podloge"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 0be3d80..152e1ad 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Utilitza el Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connectat"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Compartició d\'àudio"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Toca per canviar o compartir l\'àudio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Admet compartició d\'àudio"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Desat"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconnecta"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activa"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets de la pantalla de bloqueig"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Per obrir una aplicació utilitzant un widget, necessitaràs verificar la teva identitat. També has de tenir en compte que qualsevol persona pot veure els widgets, fins i tot quan la tauleta està bloquejada. És possible que alguns widgets no estiguin pensats per a la pantalla de bloqueig i que no sigui segur afegir-los-hi."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Entesos"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Per afegir widgets a la pantalla de bloqueig com a drecera, assegura\'t que estiguin activats a la configuració."</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>
@@ -715,7 +717,7 @@
     <string name="volume_panel_hint_muted" msgid="1124844870181285320">"silenciat"</string>
     <string name="volume_panel_hint_vibrate" msgid="4136223145435914132">"vibra"</string>
     <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>
+    <string name="media_output_title_without_playing" msgid="3825663683169305013">"L\'àudio es reproduirà a"</string>
     <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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Es mostra a la part superior de les notificacions de les converses i com a foto de perfil a la pantalla de bloqueig, apareix com una bombolla, interromp el mode No molestis"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritat"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> no admet les funcions de converses"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Proporciona suggeriments sobre el paquet"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Aquestes notificacions no es poden modificar."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Les notificacions de trucades no es poden modificar."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Aquest grup de notificacions no es pot configurar aquí"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Canvia a l\'aplicació de l\'esquerra o de dalt amb la pantalla dividida"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Durant el mode de pantalla dividida: substitueix una app per una altra"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mou la finestra activa entre pantalles"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrada"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Canvia a l\'idioma següent"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Caniva a l\'idioma anterior"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Utilitza menys de <xliff:g id="LENGTH">%1$d</xliff:g> caràcters"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilació"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"El número de compilació s\'ha copiat al porta-retalls."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiar al porta-retalls."</string>
     <string name="basic_status" msgid="2315371112182658176">"Conversa oberta"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversa"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Toca una conversa per afegir-la a la teva pantalla d\'inici"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Per a una resolució més alta, gira el telèfon"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositiu plegable desplegant-se"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositiu plegable girant"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"La pantalla frontal està activada"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plegat"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"desplegat"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilitat"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tecles de drecera"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalitza les tecles de drecera"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Prem la tecla per assignar la drecera"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Vols suprimir la drecera?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Prem la tecla per assignar la drecera"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Aquesta acció suprimirà la drecera personalitzada permanentment."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Dreceres de cerca"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No hi ha cap resultat de la cerca"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Replega la icona"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Fet"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Desplega la icona"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ansa per arrossegar"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Configuració del teclat"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Configura la drecera"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Suprimeix"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancel·la"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Prem una tecla"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"La combinació de tecles ja s\'està utilitzant. Prova-ho amb una altra tecla."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"La combinació de tecles ja s\'està utilitzant. Prova-ho amb una altra tecla."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"No es pot configurar la drecera."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navega amb el teclat"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprèn les tecles de drecera"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navega amb el ratolí tàctil"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 1bbef7b..ac0c6d6 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -306,7 +306,7 @@
     <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_audio_sharing_or_switch_active" msgid="3227408556754456024">"Klepnutím přepnete nebo nasdílíte zvuk"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Podporuje 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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgety na obrazovce uzamčení"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"K otevření aplikace pomocí widgetu budete muset ověřit svou totožnost. Také mějte na paměti, že widgety uvidí kdokoli, i když tablet bude uzamčen. Některé widgety nemusí být pro obrazovku uzamčení určeny a nemusí být bezpečné je na ni přidat."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Rozumím"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgety"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Pokud chcete přidat Widgety na obrazovku uzamčení jako zkratku, musí to být povoleno v nastavení."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Zobrazuje se v horní části sekce konverzací a na obrazovce uzamčení se objevuje jako profilová fotka, má podobu bubliny a deaktivuje režim Nerušit"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritní"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> funkce konverzace nepodporuje"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Zpětná vazba k balíčku"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Tato oznámení nelze upravit."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Upozornění na hovor nelze upravit."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Tuto skupinu oznámení tady nelze nakonfigurovat"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Přepnout na aplikaci vlevo nebo nahoře v režimu rozdělené obrazovky"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"V režimu rozdělené obrazovky: nahradit jednu aplikaci druhou"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Přesunout aktivní okno mezi obrazovkami"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Vstup"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Přepnout na další jazyk"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Přepnout na předchozí jazyk"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Použijte méně než <xliff:g id="LENGTH">%1$d</xliff:g> znaků"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Číslo sestavení"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Číslo sestavení bylo zkopírováno do schránky."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"zkopírovat do schránky"</string>
     <string name="basic_status" msgid="2315371112182658176">"Otevřít konverzaci"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widgety konverzací"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Klepnutím na konverzaci ji přidáte na plochu"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Otočte telefon, abyste dosáhli vyššího rozlišení"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rozkládání rozkládacího zařízení"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Otáčení rozkládacího zařízení"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Přední obrazovka je zapnutá"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"složené"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"rozložené"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Přístupnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klávesové zkratky"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Přizpůsobení klávesových zkratek"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Nastavte zkratku stisknutím klávesy"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Odstrabit zkratku?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Nastavte zkratku stisknutím klávesy"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Vlastní zkratka se trvale smaže."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Vyhledat zkratky"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Žádné výsledky hledání"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona sbalení"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Hotovo"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozbalení"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"nebo"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Úchyt pro přetažení"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Nastavení klávesnice"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Nastavit zkratku"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Odstranit"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Zrušit"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Stiskněte klávesu"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinace kláves se už používá. Použijte jinou klávesu."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinace kláves se už používá. Použijte jinou klávesu."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Zkratku není možné nastavit."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigujte pomocí klávesnice"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Naučte se klávesové zkratky"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigujte pomocí touchpadu"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index b2e17a9..968d44c 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Brug Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Der er oprettet forbindelse"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Lyddeling"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tryk for at skifte eller dele lyd"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Understøtter lyddeling"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gemt"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"afbryd forbindelse"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivér"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets på låseskærmen"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Hvis du vil åbne en app ved hjælp af en widget, skal du verificere din identitet. Husk også, at alle kan se dem, også når din tablet er låst. Nogle widgets er muligvis ikke beregnet til låseskærmen, og det kan være usikkert at tilføje dem her."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Hvis du vil tilføje widgets på låseskærmen som en genvej, skal du sørge for, at funktionen er aktiveret i indstillingerne."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Vises øverst i samtalenotifikationer og som et profilbillede på låseskærmen. Vises som en boble, der afbryder Forstyr ikke"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritet"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> understøtter ikke samtalefunktioner"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Giv feedback om pakker"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Disse notifikationer kan ikke redigeres."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Opkaldsnotifikationer kan ikke redigeres."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Du kan ikke konfigurere denne gruppe notifikationer her"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Skift til en app til venstre eller ovenfor, når du bruger opdelt skærm"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Ved opdelt skærm: Udskift én app med en anden"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Flyt det aktive vindue fra skærm til skærm"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Skift til næste sprog"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Skift til forrige sprog"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Angiv færre end <xliff:g id="LENGTH">%1$d</xliff:g> tegn"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Buildnummer"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Buildnummeret blev kopieret til udklipsholderen."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopiér til udklipsholderen."</string>
     <string name="basic_status" msgid="2315371112182658176">"Åben samtale"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Samtalewidgets"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Tryk på en samtale for at føje den til din startskærm"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Vend telefonen for at få højere opløsning"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldbar enhed foldes ud"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldbar enhed vendes om"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Frontskærmen er aktiveret"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"foldet"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"foldet ud"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Hjælpefunktioner"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tastaturgenveje"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Tilpas tastaturgenveje"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Tryk på en tast for at tildele genvejen"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Skal genvejen fjernes?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Tryk på en tast for at tildele genvej"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Denne handling sletter din tilpassede genvej permanent."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Genveje til søgning"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Der er ingen søgeresultater"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikon for Skjul"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Udfør"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikon for Udvid"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Håndtag"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Tastaturindstillinger"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Konfigurer genvej"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Fjern"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Annuller"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Tryk på en tast"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Tastekombinationen er allerede i brug. Prøv en anden tast."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tastekombinationen er allerede i brug. Prøv en anden tast."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Genvejen kan ikke konfigureres."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviger ved hjælp af dit tastatur"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Se tastaturgenveje"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviger ved hjælp af din touchplade"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 5f7128e..4994130 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth verwenden"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Verbunden"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audiofreigabe"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Zum Wechseln oder Teilen des Audiostreams tippen"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Unterstützt Audiofreigabe"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gespeichert"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"Verknüpfung aufheben"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivieren"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Sperrbildschirm-Widgets"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Wenn du eine App mit einem Widget öffnen möchtest, musst du deine Identität bestätigen. Beachte auch, dass jeder die Widgets sehen kann, auch wenn dein Tablet gesperrt ist. Einige Widgets sind möglicherweise nicht für den Sperrbildschirm vorgesehen, sodass es unsicher sein kann, sie hier hinzuzufügen."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Ok"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Wenn du die Funktion „Widgets auf dem Sperrbildschirm“ als Verknüpfung hinzufügen möchtest, musst du diese Funktion zuerst in den Einstellungen aktivieren."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Wird oben im Bereich „Unterhaltungen“ sowie als Profilbild auf dem Sperrbildschirm angezeigt, erscheint als Bubble, unterbricht „Bitte nicht stören“"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Priorität"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> unterstützt keine Funktionen für Unterhaltungen"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Feedback zu gebündelten Nachrichten geben"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Diese Benachrichtigungen können nicht geändert werden."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Anrufbenachrichtigungen können nicht geändert werden."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Die Benachrichtigungsgruppe kann hier nicht konfiguriert werden"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Im Splitscreen-Modus zu einer App links oder oben wechseln"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Im Splitscreen: eine App durch eine andere ersetzen"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Aktives Fenster auf anderes Display verschieben"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Eingabe"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Zur nächsten Sprache wechseln"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Zur vorherigen Sprache wechseln"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Maximal <xliff:g id="LENGTH">%1$d</xliff:g> Zeichen möglich"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build-Nummer"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Build-Nummer in Zwischenablage kopiert."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"In die Zwischenablage kopieren."</string>
     <string name="basic_status" msgid="2315371112182658176">"Offene Unterhaltung"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Unterhaltungs-Widgets"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Tippe auf eine Unterhaltung, um sie deinem Startbildschirm hinzuzufügen"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Für höhere Auflösung Smartphone umdrehen"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Faltbares Gerät wird geöffnet"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Faltbares Gerät wird umgeklappt"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Frontdisplay aktiviert"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zugeklappt"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"aufgeklappt"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Bedienungshilfen"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tastenkürzel"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Tastenkombinationen anpassen"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Drücke eine Taste, um eine Tastenkombination festzulegen"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Tastenkombination entfernen?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Drücke eine Taste, um eine Tastenkombination festzulegen"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Dadurch wird die benutzerdefinierte Tastenkombination endgültig gelöscht."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Tastenkürzel suchen"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Keine Suchergebnisse"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Symbol „Minimieren“"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Fertig"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Symbol „Maximieren“"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"oder"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ziehpunkt"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Tastatureinstellungen"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Tastenkombination festlegen"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Entfernen"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Abbrechen"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Taste drücken"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Diese Tastenkombination wird bereits verwendet. Versuche es mit einer anderen Taste."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Diese Tastenkombination wird bereits verwendet. Versuche es mit einer anderen Taste."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Die Tastenkombination kann nicht festgelegt werden."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigation mit der Tastatur"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Informationen zu Tastenkombinationen"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigation mit dem Touchpad"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index fd8e37a..1e4ef85 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"Πατήστε για εναλλαγή ή κοινή χρήση ήχου"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Υποστηρίζει κοινή χρήση ήχου"</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>
@@ -450,7 +450,7 @@
     <string name="zen_modes_dialog_done" msgid="6654130880256438950">"Τέλος"</string>
     <string name="zen_modes_dialog_settings" msgid="2310248023728936697">"Ρυθμίσεις"</string>
     <string name="zen_mode_on" msgid="9085304934016242591">"Ενεργό"</string>
-    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Ενεργή • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
+    <string name="zen_mode_on_with_details" msgid="7416143430557895497">"Ενεργό • <xliff:g id="TRIGGER_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="zen_mode_off" msgid="1736604456618147306">"Ανενεργό"</string>
     <string name="zen_mode_set_up" msgid="8231201163894922821">"Δεν έχει οριστεί"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Διαχείριση στις ρυθμίσεις"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Γραφικά στοιχεία οθόνης κλειδώματος"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Για να ανοίξετε μια εφαρμογή χρησιμοποιώντας ένα γραφικό στοιχείο, θα πρέπει να επαληθεύσετε την ταυτότητά σας. Επίσης, λάβετε υπόψη ότι η προβολή τους είναι δυνατή από οποιονδήποτε, ακόμα και όταν το tablet σας είναι κλειδωμένο. Ορισμένα γραφικά στοιχεία μπορεί να μην προορίζονται για την οθόνη κλειδώματος και η προσθήκη τους εδώ ενδέχεται να μην είναι ασφαλής."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Το κατάλαβα"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Για να προσθέσετε το Widgets στην οθόνη κλειδώματος ως συντόμευση, βεβαιωθείτε ότι είναι ενεργοποιημένο στις ρυθμίσεις."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Εμφανίζεται στην κορυφή των ειδοποιήσεων συζήτησης και ως φωτογραφία προφίλ στην οθόνη κλειδώματος, εμφανίζεται ως συννεφάκι, διακόπτει τη λειτουργία Μην ενοχλείτε"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Προτεραιότητα"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> δεν υποστηρίζει τις λειτουργίες συζήτησης"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Παροχή σχολίων για πακέτο"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Δεν είναι δυνατή η τροποποίηση αυτών των ειδοποιήσεων"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Δεν είναι δυνατή η τροποποίηση των ειδοποιήσεων κλήσεων."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Δεν είναι δυνατή η διαμόρφωση αυτής της ομάδας ειδοποιήσεων εδώ"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Εναλλαγή σε εφαρμογή αριστερά ή επάνω κατά τη χρήση διαχωρισμού οθόνης"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Κατά τον διαχωρισμό οθόνης: αντικατάσταση μιας εφαρμογής με άλλη"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Μετακίνηση ενεργού παραθύρου μεταξύ οθονών"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Είσοδος"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Εναλλαγή στην επόμενη γλώσσα"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Εναλλαγή στην προηγούμενη γλώσσα"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Χρησιμοποιήστε λιγότερους από <xliff:g id="LENGTH">%1$d</xliff:g> χαρακτήρες"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Αριθμός έκδοσης"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Ο αριθμός έκδοσης αντιγράφηκε στο πρόχειρο."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"αντιγραφή στο πρόχειρο."</string>
     <string name="basic_status" msgid="2315371112182658176">"Άνοιγμα συνομιλίας"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Γραφικά στοιχεία συνομιλίας"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Πατήστε μια συνομιλία για να την προσθέσετε στην αρχική οθόνη"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Για υψηλότερη ανάλυση, αναστρέψτε το τηλέφωνο"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Αναδιπλούμενη συσκευή που ξεδιπλώνει"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Αναδιπλούμενη συσκευή που διπλώνει"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Η μπροστινή οθόνη ενεργοποιήθηκε"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"διπλωμένη"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ξεδιπλωμένη"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Προσβασιμότητα"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Συντομεύσεις πληκτρολογίου"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Προσαρμογή συντομεύσεων πληκτρολογίου"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Πατήστε το πλήκτρο για εκχώρηση της συντόμευσης"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Κατάργηση συντόμευσης;"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Πατήστε το πλήκτρο για ανάθεση της συντόμευσης"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Με αυτή την ενέργεια, η προσαρμοσμένη συντόμευση θα διαγραφεί οριστικά."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Συντομεύσεις αναζήτησης"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Κανένα αποτέλεσμα αναζήτησης"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Εικονίδιο σύμπτυξης"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Τέλος"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Εικονίδιο ανάπτυξης"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ή"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Λαβή μεταφοράς"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Ρυθμίσεις πληκτρολογίου"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Ορισμός συντόμευσης"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Κατάργηση"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Ακύρωση"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Πατήστε ένα πλήκτρο"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Ο συνδυασμός πλήκτρων χρησιμοποιείται ήδη. Δοκιμάστε άλλο πλήκτρο."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ο συνδυασμός πλήκτρων χρησιμοποιείται ήδη. Δοκιμάστε άλλο πλήκτρο."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Δεν είναι δυνατή η ρύθμιση της συντόμευσης."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Πλοήγηση με το πληκτρολόγιο"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Μάθετε συντομεύσεις πληκτρολογίου"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Πλοήγηση με την επιφάνεια αφής"</string>
diff --git a/packages/SystemUI/res/values-el/tiles_states_strings.xml b/packages/SystemUI/res/values-el/tiles_states_strings.xml
index ec2a930..5ce4b8c 100644
--- a/packages/SystemUI/res/values-el/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-el/tiles_states_strings.xml
@@ -58,7 +58,7 @@
   </string-array>
   <string-array name="tile_states_flashlight">
     <item msgid="3465257127433353857">"Μη διαθέσιμο"</item>
-    <item msgid="5044688398303285224">"Ανενεργό"</item>
+    <item msgid="5044688398303285224">"Ανενεργός"</item>
     <item msgid="8527389108867454098">"Ενεργό"</item>
   </string-array>
   <string-array name="tile_states_rotation">
@@ -73,7 +73,7 @@
   </string-array>
   <string-array name="tile_states_airplane">
     <item msgid="1985366811411407764">"Μη διαθέσιμο"</item>
-    <item msgid="4801037224991420996">"Ανενεργό"</item>
+    <item msgid="4801037224991420996">"Ανενεργή"</item>
     <item msgid="1982293347302546665">"Ενεργό"</item>
   </string-array>
   <string-array name="tile_states_location">
@@ -113,7 +113,7 @@
   </string-array>
   <string-array name="tile_states_cast">
     <item msgid="6032026038702435350">"Μη διαθέσιμο"</item>
-    <item msgid="1488620600954313499">"Ανενεργό"</item>
+    <item msgid="1488620600954313499">"Ανενεργή"</item>
     <item msgid="588467578853244035">"Ενεργό"</item>
   </string-array>
   <string-array name="tile_states_night">
@@ -152,7 +152,7 @@
     <item msgid="8998632451221157987">"Ενεργό"</item>
   </string-array>
   <string-array name="tile_states_controls">
-    <item msgid="8199009425335668294">"Μη διαθέσιμο"</item>
+    <item msgid="8199009425335668294">"Μη διαθέσιμα"</item>
     <item msgid="4544919905196727508">"Ανενεργό"</item>
     <item msgid="3422023746567004609">"Ενεργό"</item>
   </string-array>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 4280ff2..938caa1 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio sharing"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tap to switch or share audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Supports audio sharing"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Lock screen widgets"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"To open an app using a widget, you\'ll need to verify that it\'s you. Also, bear in mind that anyone can view them, even when your tablet\'s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Got it"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"To add widgets on the lock screen as a shortcut, make sure that it is enabled in settings."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble, interrupts Do Not Disturb"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Priority"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> doesn’t support conversation features"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Provide bundle feedback"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"These notifications can\'t be modified."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Call notifications can\'t be modified."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"This group of notifications cannot be configured here"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Switch to the app on the left or above while using split screen"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"During split screen: Replace an app from one to another"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Move active window between displays"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Switch to next language"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Switch to previous language"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Use fewer than <xliff:g id="LENGTH">%1$d</xliff:g> characters"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Build number copied to clipboard."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copy to clipboard."</string>
     <string name="basic_status" msgid="2315371112182658176">"Open conversation"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Conversation widgets"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Tap a conversation to add it to your home screen"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Front screen turned on"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Customise keyboard shortcuts"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Press key to assign shortcut"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remove shortcut?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Press key to assign shortcut"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"This will delete your custom shortcut permanently."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Done"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Keyboard settings"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Set shortcut"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Remove"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancel"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Press key"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Key combination already in use. Try another key."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Key combination already in use. Try another key."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Shortcut cannot be set."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigate using your keyboard"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Learn keyboards shortcuts"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigate using your touchpad"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 3b009a1..dada5e9 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio Sharing"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tap to switch or share audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Supports audio sharing"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Lock screen widgets"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"To open an app using a widget, you’ll need to verify it’s you. Also, keep in mind that anyone can view them, even when your tablet’s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Got it"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"To add Widgets on the lock screen as a shortcut, make sure it is enabled in settings."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble, interrupts Do Not Disturb"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Priority"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> doesn’t support conversation features"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Provide Bundle Feedback"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"These notifications can\'t be modified."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Call notifications can\'t be modified."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"This group of notifications cannot be configured here"</string>
@@ -872,6 +875,10 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Switch to app on left or above while using split screen"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"During split screen: replace an app from one to another"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Move active window between displays"</string>
+    <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Move window to the left"</string>
+    <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Move window to the right"</string>
+    <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximize window"</string>
+    <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimize window"</string>
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Switch to next language"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Switch to previous language"</string>
@@ -1220,8 +1227,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Use fewer than <xliff:g id="LENGTH">%1$d</xliff:g> characters"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Build number copied to clipboard."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copy to clipboard."</string>
     <string name="basic_status" msgid="2315371112182658176">"Open conversation"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Conversation widgets"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Tap a conversation to add it to your Home screen"</string>
@@ -1417,7 +1423,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Customize keyboard shortcuts"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Press key to assign shortcut"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remove shortcut?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Press key to assign shortcut"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"This will delete your custom shortcut permanently."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
@@ -1427,12 +1435,17 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Done"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
+    <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+    <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"forward slash"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Keyboard Settings"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Set shortcut"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Remove"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancel"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Press key"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Key combination already in use. Try another key."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Key combination already in use. Try another key."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Shortcut cannot be set."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigate using your keyboard"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Learn keyboards shortcuts"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigate using your touchpad"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 4280ff2..938caa1 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio sharing"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tap to switch or share audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Supports audio sharing"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Lock screen widgets"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"To open an app using a widget, you\'ll need to verify that it\'s you. Also, bear in mind that anyone can view them, even when your tablet\'s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Got it"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"To add widgets on the lock screen as a shortcut, make sure that it is enabled in settings."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble, interrupts Do Not Disturb"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Priority"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> doesn’t support conversation features"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Provide bundle feedback"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"These notifications can\'t be modified."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Call notifications can\'t be modified."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"This group of notifications cannot be configured here"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Switch to the app on the left or above while using split screen"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"During split screen: Replace an app from one to another"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Move active window between displays"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Switch to next language"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Switch to previous language"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Use fewer than <xliff:g id="LENGTH">%1$d</xliff:g> characters"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Build number copied to clipboard."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copy to clipboard."</string>
     <string name="basic_status" msgid="2315371112182658176">"Open conversation"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Conversation widgets"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Tap a conversation to add it to your home screen"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Front screen turned on"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Customise keyboard shortcuts"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Press key to assign shortcut"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remove shortcut?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Press key to assign shortcut"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"This will delete your custom shortcut permanently."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Done"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Keyboard settings"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Set shortcut"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Remove"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancel"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Press key"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Key combination already in use. Try another key."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Key combination already in use. Try another key."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Shortcut cannot be set."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigate using your keyboard"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Learn keyboards shortcuts"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigate using your touchpad"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 4280ff2..938caa1 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio sharing"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tap to switch or share audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Supports audio sharing"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Lock screen widgets"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"To open an app using a widget, you\'ll need to verify that it\'s you. Also, bear in mind that anyone can view them, even when your tablet\'s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Got it"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"To add widgets on the lock screen as a shortcut, make sure that it is enabled in settings."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble, interrupts Do Not Disturb"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Priority"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> doesn’t support conversation features"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Provide bundle feedback"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"These notifications can\'t be modified."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Call notifications can\'t be modified."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"This group of notifications cannot be configured here"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Switch to the app on the left or above while using split screen"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"During split screen: Replace an app from one to another"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Move active window between displays"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Switch to next language"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Switch to previous language"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Use fewer than <xliff:g id="LENGTH">%1$d</xliff:g> characters"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Build number copied to clipboard."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copy to clipboard."</string>
     <string name="basic_status" msgid="2315371112182658176">"Open conversation"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Conversation widgets"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Tap a conversation to add it to your home screen"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Front screen turned on"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Customise keyboard shortcuts"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Press key to assign shortcut"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remove shortcut?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Press key to assign shortcut"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"This will delete your custom shortcut permanently."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Done"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Keyboard settings"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Set shortcut"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Remove"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancel"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Press key"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Key combination already in use. Try another key."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Key combination already in use. Try another key."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Shortcut cannot be set."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigate using your keyboard"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Learn keyboards shortcuts"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigate using your touchpad"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index ad403b1..5389aba 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Uso compartido de audio"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Presiona para cambiar o compartir el audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Admite el uso compartido de audio"</string>
     <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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets en la pantalla de bloqueo"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para abrir una app usando un widget, debes verificar tu identidad. Además, ten en cuenta que cualquier persona podrá verlo, incluso cuando la tablet esté bloqueada. Es posible que algunos widgets no se hayan diseñados para la pantalla de bloqueo y podría ser peligroso agregarlos allí."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Entendido"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Para agregar Widgets en la pantalla de bloqueo como combinación de teclas, asegúrate de que la función esté habilitada en la configuración."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Aparece en forma de burbuja y como foto de perfil en la parte superior de las notificaciones de conversación, en la pantalla de bloqueo, y detiene el modo No interrumpir"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritaria"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> no admite funciones de conversación"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Proporcionar comentarios sobre el conjunto"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"No se pueden modificar estas notificaciones."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"No se pueden modificar las notificaciones de llamada."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"No se puede configurar aquí este grupo de notificaciones"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Ubicar la app a la izquierda o arriba cuando usas la pantalla dividida"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Durante pantalla dividida: Reemplaza una app con otra"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mover la ventana activa de una pantalla a otra"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrada"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Cambiar al próximo idioma"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Cambiar al idioma anterior"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Usa menos de <xliff:g id="LENGTH">%1$d</xliff:g> caracteres"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Se copió el número de compilación en el portapapeles."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiar en el portapapeles."</string>
     <string name="basic_status" msgid="2315371112182658176">"Conversación abierta"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversación"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Presiona una conversación para agregarla a tu pantalla principal"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para obtener una resolución más alta, gira el teléfono"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo plegable siendo desplegado"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo plegable siendo girado"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Pantalla frontal activada"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plegado"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"desplegado"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilidad"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Combinaciones de teclas"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personaliza las combinaciones de teclas"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Presiona la tecla para asignar el acceso directo"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"¿Quieres quitar el acceso directo?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Presiona la tecla para asignar el acceso directo"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Esta acción borrará tu acceso directo personalizado de forma permanente."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Buscar combinaciones de teclas"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"La búsqueda no arrojó resultados"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícono de contraer"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Listo"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícono de expandir"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Configuración del teclado"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Establecer combinación de teclas"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Quitar"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancelar"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Presiona una tecla"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"La combinación de teclas ya está en uso. Prueba con otra."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"La combinación de teclas ya está en uso. Prueba con otra."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"No se puede establecer la combinación de teclas."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navega con el teclado"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprende combinaciones de teclas"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navega con el panel táctil"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 50de6d2..2ff38b0 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Compartir audio"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Toca para cambiar o compartir el audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Permite compartir audio"</string>
     <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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets para la pantalla de bloqueo"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para abrir una aplicación usando un widget, deberás verificar que eres tú. Además, ten en cuenta que cualquier persona podrá verlos, incluso aunque tu tablet esté bloqueada. Es posible que algunos widgets no estén pensados para la pantalla de bloqueo y no sea seguro añadirlos aquí."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Entendido"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Para añadir la función de widgets en la pantalla de bloqueo como combinación de teclas, asegúrate de que la opción esté habilitada en los ajustes."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Se muestra encima de las notificaciones de conversaciones y como imagen de perfil en la pantalla de bloqueo, aparece como burbuja e interrumpe el modo No molestar"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioridad"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> no admite funciones de conversación"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Enviar comentarios sobre el paquete"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Estas notificaciones no se pueden modificar."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Las notificaciones de llamada no se pueden modificar."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Este grupo de notificaciones no se puede configurar aquí"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Cambiar a la app de la izquierda o de arriba en pantalla dividida"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Con pantalla dividida: reemplazar una aplicación por otra"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mover ventana activa de una pantalla a otra"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrada"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Cambiar a siguiente idioma"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Cambiar a idioma anterior"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Usa menos de <xliff:g id="LENGTH">%1$d</xliff:g> caracteres"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Número de compilación copiado en el portapapeles."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiar en el portapapeles."</string>
     <string name="basic_status" msgid="2315371112182658176">"Conversación abierta"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversación"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Toca una conversación para añadirla a la pantalla de inicio"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para una mayor resolución, gira el teléfono"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo plegable desplegándose"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo plegable mostrado desde varios ángulos"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Pantalla frontal encendida"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plegado"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"desplegado"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilidad"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Combinaciones de teclas"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalizar las combinaciones de teclas"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pulsa una tecla para asignar una combinación de teclas"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"¿Eliminar combinación de teclas?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pulsa una tecla para asignar una combinación de teclas"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Se eliminará tu combinación de teclas personalizada de forma permanente."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Atajos de búsqueda"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No hay resultados de búsqueda"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icono de contraer"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Hecho"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icono de desplegar"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Ajustes del teclado"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Establecer combinación de teclas"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Eliminar"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancelar"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pulsa una tecla"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"La combinación de teclas ya se está usando. Prueba con otra tecla."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"La combinación de teclas ya se está usando. Prueba con otra tecla."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"No se puede configurar la combinación de teclas."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Desplázate con el teclado"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprende combinaciones de teclas"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Desplázate con el panel táctil"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 9c0d56d..f646e25 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Kasuta Bluetoothi"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ühendatud"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Heli jagamine"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Puudutage helile lülitumiseks või selle jagamiseks"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Toetab heli jagamist"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvestatud"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkesta ühendus"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveeri"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Lukustuskuva vidinad"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Rakenduse avamiseks vidina abil peate kinnitama, et see olete teie. Samuti pidage meeles, et kõik saavad vidinaid vaadata, isegi kui teie tahvelarvuti on lukus. Mõni vidin ei pruugi olla ette nähtud teie lukustuskuva jaoks ja seda pole turvaline siia lisada."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Selge"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Vidinad"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Selleks et lisada vidinad otseteena lukustuskuvale, veenduge, et see oleks seadetes lubatud."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Kuvatakse mullina vestluste märguannete ülaosas ja profiilipildina lukustuskuval ning katkestab režiimi Mitte segada"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioriteetne"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei toeta vestlusfunktsioone"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Kogumi kohta tagasiside andmine"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Neid märguandeid ei saa muuta."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Kõnemärguandeid ei saa muuta."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Seda märguannete rühma ei saa siin seadistada"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Vasakule või ülemisele rakendusele lülitamine jagatud ekraani ajal"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Ekraanikuva jagamise ajal: ühe rakenduse asendamine teisega"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Aktiivse akna teisaldamine ekraanide vahel"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Sisend"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Järgmisele keelele lülitamine"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Eelmisele keelele lülitamine"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Kasutage vähem kui <xliff:g id="LENGTH">%1$d</xliff:g> tähemärki"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Järgunumber"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Järgunumber kopeeriti lõikelauale."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"lõikelauale kopeerimine."</string>
     <string name="basic_status" msgid="2315371112182658176">"Avage vestlus"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Vestlusvidinad"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Puudutage vestlust, et lisada see oma avakuvale"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Suurema eraldusvõime saavutamiseks pöörake telefon ümber"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Volditava seadme lahtivoltimine"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Volditava seadme ümberpööramine"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Esiekraan on sisse lülitatud"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"kokku volditud"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"lahti volditud"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Juurdepääsetavus"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klaviatuuri otseteed"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Klaviatuuri otseteede kohandamine"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Otsetee lisamiseks vajutage klahvi"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Kas soovite otsetee eemaldada?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Otsetee lisamiseks vajutage klahvi"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"See kustutab teie kohandatud otsetee jäädavalt."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Otsige otseteid"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Otsingutulemused puuduvad"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ahendamisikoon"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Valmis"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Laiendamisikoon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"või"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Lohistamispide"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Klaviatuuri seaded"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Määrake otsetee"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Eemalda"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Tühista"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Vajutage klahvi"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Klahvikombinatsioon juba kasutusel. Proovige mõnda muud klahvi."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Klahvikombinatsioon on juba kasutusel. Proovige mõnda muud klahvi."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Otseteed ei saa seadistada."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigeerige klaviatuuri abil"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Õppige klaviatuuri otseteid"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigeerige puuteplaadi abil"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index a971d1c..7d3c733 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Erabili Bluetootha"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Konektatuta"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audioa partekatzea"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Audioa aldatu edo partekatzeko, sakatu hau"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Audioa partekatzeko eginbidea onartzen du"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gordeta"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"deskonektatu"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktibatu"</string>
@@ -384,8 +384,8 @@
     <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Egunsentira arte"</string>
     <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Aktibatze-ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
     <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Desaktibatze-ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
-    <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"Aktibatuta lo egiteko garaian"</string>
-    <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends" msgid="1790772410777123685">"Lo egiteko garaia amaitzen den arte"</string>
+    <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"Aktibatuta lotara joateko garaian"</string>
+    <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends" msgid="1790772410777123685">"Lotara joateko garaia amaitzen den arte"</string>
     <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFCa"</string>
     <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Desgaituta dago NFC"</string>
     <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Gaituta dago NFC"</string>
@@ -455,7 +455,7 @@
     <string name="zen_mode_set_up" msgid="8231201163894922821">"Ezarri gabe"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"Kudeatu ezarpenetan"</string>
     <string name="zen_mode_active_modes" msgid="1625850411578488856">"{count,plural, =0{Ez dago modurik aktibo}=1{\"{mode}\" aktibo dago}other{# modu aktibo daude}}"</string>
-    <string name="zen_priority_introduction" msgid="3159291973383796646">"Gailuak ez du egingo ez soinurik ez dardararik, baina alarmak, gertaera eta abisuen tonuak, eta aukeratzen dituzun deitzaileen dei-tonuak joko ditu. Bestalde, zuk erreproduzitutako guztia entzungo duzu, besteak beste, musika, bideoak eta jokoak."</string>
+    <string name="zen_priority_introduction" msgid="3159291973383796646">"Gailuak ez du egingo ez soinurik ez dardararik, baina alarmak, gertaera eta gogorarazpenen tonuak, eta aukeratzen dituzun deitzaileen dei-tonuak joko ditu. Bestalde, zuk erreproduzitutako guztia entzungo duzu, besteak beste, musika, bideoak eta jokoak."</string>
     <string name="zen_alarms_introduction" msgid="3987266042682300470">"Gailuak ez du egingo ez soinurik ez dardararik, baina alarmak joko ditu. Hala ere, zuk erreproduzitutako guztia entzun ahal izango duzu, besteak beste, musika, bideoak eta jokoak."</string>
     <string name="zen_priority_customize_button" msgid="4119213187257195047">"Pertsonalizatu"</string>
     <string name="zen_silence_introduction_voice" msgid="853573681302712348">"Soinu eta dardara GUZTIAK blokeatuko dira, besteak beste, alarmak, musika, bideoak eta jokoak. Telefono-deiak egiteko aukera izaten jarraituko duzu."</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Pantaila blokeatuko widgetak"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Aplikazio bat widget baten bidez irekitzeko, zeu zarela egiaztatu beharko duzu. Gainera, kontuan izan edonork ikusi ahalko dituela halako widgetak, tableta blokeatuta badago ere. Baliteke widget batzuk pantaila blokeaturako egokiak ez izatea, eta agian ez da segurua haiek bertan gehitzea."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Ados"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgetak"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Pantaila blokeatuan lasterbide gisa widgetak gehitzeko, ziurtatu aukera hori gaituta dagoela ezarpenetan."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Elkarrizketen jakinarazpenen goialdean eta profileko argazki gisa agertzen da pantaila blokeatuan, burbuila batean, eta ez molestatzeko modua eteten du"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Lehentasuna"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak ez ditu onartzen elkarrizketetarako eginbideak"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Bidali sortari buruzko oharrak"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Jakinarazpen horiek ezin dira aldatu."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Deien jakinarazpenak ezin dira aldatu."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Jakinarazpen talde hau ezin da konfiguratu hemen"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Aldatu ezkerreko edo goiko aplikaziora pantaila zatitua erabiltzean"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Pantaila zatituan zaudela, ordeztu aplikazio bat beste batekin"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Eraman leiho aktiboa pantaila batetik bestera"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Sarrera"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Aldatu hurrengo hizkuntzara"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Aldatu aurreko hizkuntzara"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Erabili <xliff:g id="LENGTH">%1$d</xliff:g> karaktere baino gutxiago"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Konpilazio-zenbakia"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Kopiatu da konpilazio-zenbakia arbelean."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopiatu arbelean."</string>
     <string name="basic_status" msgid="2315371112182658176">"Elkarrizketa irekia"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Elkarrizketa-widgetak"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Sakatu elkarrizketa bat orri nagusian gehitzeko"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Irauli telefonoa bereizmen handiago a lortzeko"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Gailu tolesgarria zabaltzen"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Gailu tolesgarria biratzen"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Aurreko pantaila piztuta dago"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"tolestuta"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"tolestu gabe"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Erabilerraztasuna"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Lasterbideak"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Pertsonalizatu lasterbideak"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Sakatu tekla lasterbidea esleitzeko"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Lasterbidea kendu nahi duzu?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Sakatu tekla lasterbidea esleitzeko"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Betiko ezabatuko da lasterbide pertsonalizatua."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Bilatu lasterbideak"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ez dago bilaketa-emaitzarik"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Tolesteko ikonoa"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Eginda"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Zabaltzeko ikonoa"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"edo"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Arrastatzeko kontrol-puntua"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Teklatuaren ezarpenak"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Ezarri lasterbidea"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Kendu"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Utzi"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Sakatu tekla"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Tekla-konbinazio hori erabili da dagoeneko. Probatu beste tekla bat."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tekla-konbinazio hori erabili da dagoeneko. Probatu beste tekla bat."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Ezin da ezarri lasterbidea."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Nabigatu teklatua erabilita"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Ikasi lasterbideak"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Nabigatu ukipen-panela erabilita"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index bf7ccf0..153a460 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"برای فعال کردن و هم‌رسانی صدا، تک‌ضرب بزنید"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"از اشتراک صدا پشتیبانی می‌کند"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ابزاره‌های صفحه قفل"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"برای باز کردن برنامه بااستفاده از ابزاره، باید هویت خودتان را به‌تأیید برسانید. همچنین، به‌خاطر داشته باشید که همه می‌توانند آن‌ها را مشاهده کنند، حتی وقتی رایانه لوحی‌تان قفل است. برخی‌از ابزاره‌ها ممکن است برای صفحه قفل درنظر گرفته نشده باشند و ممکن است اضافه کردن آن‌ها در اینجا ناامن باشد."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"متوجه‌ام"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ابزاره‌ها"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"برای افزودن «ابزاره‌ها» در صفحه قفل به‌عنوان میان‌بر، مطمئن شوید این گزینه در تنظیمات فعال باشد."</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>
@@ -695,7 +697,7 @@
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"‏%1$s. برای صامت کردن تک‌ضرب بزنید. ممکن است سرویس‌های دسترس‌پذیری صامت شود."</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"‏%1$s. برای تنظیم روی لرزش، تک‌ضرب بزنید."</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"‏%1$s. برای صامت کردن تک‌ضرب بزنید."</string>
-    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"کنترل صدای محیط"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"کنترل نوفه زمینه"</string>
     <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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"در بالای اعلان‌های مکالمه و به‌صورت عکس نمایه در صفحه قفل نشان داده می‌شود، به‌صورت حبابک ظاهر می‌شود، در حالت «مزاحم نشوید» وقفه ایجاد می‌کند"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"اولویت"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> از ویژگی‌های مکالمه پشتیبانی نمی‌کند"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ارائه بازخورد دسته‌ای"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"این اعلان‌ها قابل اصلاح نیستند."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"این اعلان‌ها قابل‌اصلاح نیستند."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"نمی‌توانید این گروه اعلان‌ها را در اینجا پیکربندی کنید"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"رفتن به برنامه سمت چپ یا بالا درحین استفاده از صفحهٔ دونیمه"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"درحین صفحهٔ دونیمه: برنامه‌ای را با دیگری جابه‌جا می‌کند"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"جابه‌جا کردن پنجره فعال بین نمایشگرها"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ورودی"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"رفتن به زبان بعدی"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"رفتن به زبان قبلی"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"از کمتر از <xliff:g id="LENGTH">%1$d</xliff:g> نویسه استفاده کنید"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"شماره ساخت"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"شماره ساخت در بریده‌دان کپی شد."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"کپی کردن در بریده‌دان."</string>
     <string name="basic_status" msgid="2315371112182658176">"باز کردن مکالمه"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"ابزارک‌های مکالمه"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"روی مکالمه‌ای تک‌ضرب بزنید تا به «صفحه اصلی» اضافه شود"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"برای وضوح بیشتر، تلفن را بچرخانید"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"دستگاه تاشو درحال باز شدن"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"دستگاه تاشو درحال چرخش به اطراف"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"صفحه‌نمایش جلو روشن شد"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"تاشده"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"تانشده"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"دسترس‌پذیری"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"میان‌برهای صفحه‌کلید"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"سفارشی‌سازی کردن میان‌برهای صفحه‌کلید"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"برای اختصاص دادن میان‌بر، کلید را فشار دهید"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"میان‌بر حذف شود؟"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"برای اختصاص دادن میان‌بر، کلید را فشار دهید"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"با این کار، میان‌بر سفارشی شما برای همیشه حذف می‌شود."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"جستجوی میان‌برها"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"نتیجه‌ای برای جستجو پیدا نشد"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"نماد جمع کردن"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"تمام"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"نماد ازهم بازکردن"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"یا"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"دستگیره کشاندن"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"تنظیمات صفحه‌کلید"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"تنظیم میان‌بر"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"حذف"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"لغو"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"کلید را فشار دهید"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"ترکیب کلید ازقبل درحال استفاده است. کلید دیگری را امتحان کنید."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ترکیب کلید ازقبل درحال استفاده است. کلید دیگری را امتحان کنید."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"میان‌بر تنظیم نشد."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"پیمایش کردن بااستفاده از صفحه‌کلید"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"آشنایی با میان‌برهای صفحه‌کلید"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"پیمایش کردن بااستفاده از صفحه لمسی"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 89ed95f..76d66c5 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Käytä Bluetoothia"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Yhdistetty"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audionjako"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Vaihda tai jaa audiota napauttamalla"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Tukee audionjakoa"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Tallennettu"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkaise yhteys"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivoi"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Lukitusnäytön widgetit"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Jos haluat avata sovelluksen käyttämällä widgetiä, sinun täytyy vahvistaa henkilöllisyytesi. Muista myös, että widgetit näkyvät kaikille, vaikka tabletti olisi lukittuna. Jotkin widgetit on ehkä tarkoitettu lukitusnäytölle, ja niiden lisääminen tänne ei välttämättä ole turvallista."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Selvä"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgetit"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Jos haluat lisätä Widgetit lukitusnäytöllä ‑ominaisuuden pikakuvakkeeksi, varmista, että se on otettu käyttöön asetuksissa."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Näkyy keskusteluilmoitusten yläosassa ja profiilikuvana lukitusnäytöllä, näkyy kuplana, keskeyttää Älä häiritse ‑tilan"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Tärkeä"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei tue keskusteluominaisuuksia"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Anna palautetta paketista"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Näitä ilmoituksia ei voi muokata"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Puheluilmoituksia ei voi muokata."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Tätä ilmoitusryhmää ei voi määrittää tässä"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Vaihda sovellukseen vasemmalla tai yläpuolella jaetussa näytössä"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Jaetun näytön aikana: korvaa sovellus toisella"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Siirrä aktiivinen ikkuna näytöltä toiselle"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Syöttötapa"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Vaihda seuraavaan kieleen"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Vaihda aiempaan kieleen"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Käytä alle <xliff:g id="LENGTH">%1$d</xliff:g> merkkiä"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Koontiversion numero"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Koontiversion numero kopioitu leikepöydälle"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopioi leikepöydälle."</string>
     <string name="basic_status" msgid="2315371112182658176">"Avaa keskustelu"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Keskusteluwidgetit"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Lisää keskustelu aloitusnäytölle napauttamalla sitä"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Resoluutio on parempi, kun käännät puhelimen"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Taitettava laite taitetaan"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Taitettava laite käännetään ympäri"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Etunäyttö päällä"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"taitettu"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"taittamaton"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Saavutettavuus"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Pikanäppäimet"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Pikanäppäimien muokkaaminen"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Määritä pikanäppäin painamalla näppäintä"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Poistetaanko pikanäppäin?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Määritä pikanäppäin painamalla näppäintä"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Oma pikanäppäin poistetaan pysyvästi."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pikahaut"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ei hakutuloksia"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Tiivistyskuvake"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Valmis"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Laajennuskuvake"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"tai"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vetokahva"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Näppäimistön asetukset"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Valitse pikanäppäin"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Poista"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Peru"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Paina näppäintä"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Näppäinyhdistelmä on jo käytössä. Kokeile toista näppäintä."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Näppäinyhdistelmä on jo käytössä. Kokeile toista näppäintä."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Pikakuvaketta ei voi lisätä."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Siirry käyttämällä näppäimistöä"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Opettele pikanäppäimiä"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Siirry käyttämällä kosketuslevyä"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 708d8b9..6997b26 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Utiliser le Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connecté"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Partage audio"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Touchez pour passer d\'un appareil à l\'autre ou pour partager le contenu audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Prise en charge du partage audio"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Enregistré"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"Déconnecter"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"Activer"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets de l\'écran de verrouillage"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Pour ouvrir une appli à l\'aide d\'un widget, vous devrez confirmer votre identité. En outre, gardez à l\'esprit que tout le monde peut voir les widgets, même lorsque votre tablette est verrouillée. Certains widgets n\'ont peut-être pas été conçus pour votre écran de verrouillage, et il pourrait être dangereux de les ajouter ici."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Pour ajouter des widgets comme raccourci à l\'écran de verrouillage, assurez-vous que cette fonctionnalité soit activée dans les paramètres."</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 applis et les données de cette session seront supprimées."</string>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"S\'affiche dans le haut des notifications de conversation et comme photo de profil à l\'écran de verrouillage, s\'affiche comme bulle, interrompt le mode Ne pas déranger"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritaire"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ne prend pas en charge les fonctionnalités de conversation"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Fournir des commentaires groupés"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Ces notifications ne peuvent pas être modifiées"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Les notifications d\'appel ne peuvent pas être modifiées."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Ce groupe de notifications ne peut pas être configuré ici"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Passer à l\'appli à gauche ou au-dessus avec l\'Écran divisé"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"En mode d\'écran divisé : remplacer une appli par une autre"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Déplacer la fenêtre active d\'un écran à l\'autre"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrée"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Passer à la langue suivante"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Passer à la langue précédente"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Utilisez moins de <xliff:g id="LENGTH">%1$d</xliff:g> caractères"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numéro de version"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Le numéro de version a été copié dans le presse-papiers."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"Copier le contenu dans le presse-papiers"</string>
     <string name="basic_status" msgid="2315371112182658176">"Ouvrir la conversation"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversation"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Touchez une conversation pour l\'ajouter à votre écran d\'accueil"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Pour une meilleure résolution, retournez le téléphone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Appareil pliable en cours de dépliage"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Appareil pliable en train d\'être retourné"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Écran avant activé"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plié"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"déplié"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilité"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Raccourcis-clavier"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personnaliser les raccourcis-clavier"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Appuyez sur la touche pour attribuer un raccourci"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Supprimer le raccourci?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Appuyez sur la touche pour attribuer un raccourci"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Cela supprimera définitivement votre raccourci personnalisé."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Rechercher des raccourcis"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Aucun résultat de recherche"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icône Réduire"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Terminé"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icône Développer"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Poignée de déplacement"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Paramètres du clavier"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Définir un raccourci"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Supprimer"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Annuler"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Appuyez sur la touche"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Combinaison de touches déjà utilisée. Essayez une autre touche."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"La combinaison de touches est déjà utilisée. Essayez une autre touche."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Le raccourci ne peut pas être défini."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviguer à l\'aide de votre clavier"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Apprenez à utiliser les raccourcis-clavier"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviguer à l\'aide de votre pavé tactile"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 41bd910..3e7fc38 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Utiliser le Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connecté"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Partage audio"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Appuyez pour activer ou partager l\'audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Compatible avec le partage audio"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Enregistré"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"dissocier"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activer"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets pour l\'écran de verrouillage"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Pour ouvrir une appli à l\'aide d\'un widget, vous devez confirmer qu\'il s\'agit bien de vous. N\'oubliez pas non plus que tout le monde peut voir vos widgets, même lorsque votre tablette est verrouillée. Certains d\'entre eux n\'ont pas été conçus pour l\'écran de verrouillage et les ajouter à cet endroit peut s\'avérer dangereux."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Pour ajouter des widgets à l\'écran de verrouillage en tant que raccourcis, assurez-vous que cette option est activée dans les paramètres."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"S\'affiche en haut des notifications de conversation et en tant que photo de profil sur l\'écran de verrouillage, apparaît sous forme de bulle, interrompt le mode Ne pas déranger"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritaire"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> n\'est pas compatible avec les fonctionnalités de conversation"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Envoyer des commentaires groupés"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Impossible de modifier ces notifications."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Impossible de modifier les notifications d\'appel."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Vous ne pouvez pas configurer ce groupe de notifications ici"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Passez à l\'appli à gauche ou au-dessus avec l\'écran partagé"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"En mode écran partagé : Remplacer une appli par une autre"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Déplacer la fenêtre active d\'un écran à l\'autre"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Saisie"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Passer à la langue suivante"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Revenir à la langue précédente"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Utilisez moins de <xliff:g id="LENGTH">%1$d</xliff:g> caractères"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numéro de build"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Numéro de build copié dans le presse-papiers."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copier dans le presse-papiers."</string>
     <string name="basic_status" msgid="2315371112182658176">"Conversation ouverte"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversation"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Appuyez sur une conversation pour l\'ajouter à votre écran d\'accueil"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Pour une résolution plus élevée, retournez le téléphone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Appareil pliable qui est déplié"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Appareil pliable qui est retourné"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Écran avant activé"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plié"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"déplié"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilité"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Raccourcis clavier"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personnaliser les raccourcis clavier"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Appuyez sur une touche pour attribuer un raccourci"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Supprimer le raccourci ?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Appuyez sur une touche pour attribuer un raccourci"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Votre raccourci personnalisé sera définitivement supprimé."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Rechercher des raccourcis"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Aucun résultat de recherche"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icône Réduire"</string>
@@ -1428,18 +1439,25 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"OK"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icône Développer"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Poignée de déplacement"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Paramètres du clavier"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Définir un raccourci"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Supprimer"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Annuler"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Appuyez sur la touche"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Combinaison de touches déjà utilisée. Essayez une autre touche."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Combinaison de touches déjà utilisée. Essayez une autre touche."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Impossible de définir le raccourci."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviguer à l\'aide du clavier"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Apprenez à utiliser les raccourcis clavier"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviguer à l\'aide de votre pavé tactile"</string>
     <string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"Découvrez les gestes au pavé tactile"</string>
     <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Naviguer à l\'aide de votre clavier et de votre pavé tactile"</string>
-    <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Découvrir les gestes au pavé tactile, les raccourcis clavier et plus encore"</string>
+    <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Découvrir les gestes du pavé tactile, les raccourcis clavier et plus encore"</string>
     <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Retour"</string>
     <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Retour à l\'accueil"</string>
     <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Afficher les applis récentes"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 3983606..d36a24d 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Estableceuse a conexión"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio compartido"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Toca para cambiar ou compartir o audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Compatible con audio compartido"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gardouse"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets da pantalla de bloqueo"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para abrir unha aplicación mediante un widget, tes que verificar a túa identidade. Ten en conta que pode velos calquera persoa, mesmo coa tableta bloqueada. Pode ser que algúns widgets non estean pensados para a túa pantalla de bloqueo, polo que talvez non sexa seguro engadilos aquí."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Entendido"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Para poderes engadir widgets á pantalla de bloqueo como atallos, debe estar activada na configuración a opción correspondente."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Móstrase na parte superior das notificacións das conversas e como imaxe do perfil na pantalla de bloqueo, aparece como unha burbulla e interrompe o modo Non molestar"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioridade"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> non admite funcións de conversa"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Enviar comentarios agrupados"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Estas notificacións non se poden modificar."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"As notificacións de chamadas non se poden modificar."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Aquí non se pode configurar este grupo de notificacións"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Cambiar á aplicación da esquerda ou de arriba coa pantalla dividida"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"En modo de pantalla dividida: Substituír unha aplicación por outra"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mover ventá activa entre pantallas"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrada"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Cambiar ao seguinte idioma"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Cambiar ao idioma anterior"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Utiliza menos de <xliff:g id="LENGTH">%1$d</xliff:g> caracteres"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Copiouse o número de compilación no portapapeis."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiar no portapapeis."</string>
     <string name="basic_status" msgid="2315371112182658176">"Conversa aberta"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversa"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Toca unha conversa para engadila á pantalla de inicio"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Dálle a volta ao teléfono para gozar dunha maior resolución"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo pregable abríndose"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo pregable xirando"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Activouse a pantalla dianteira"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"dispositivo pregado"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"dispositivo despregado"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilidade"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Atallos de teclado"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalizar os atallos de teclado"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Preme a tecla para asignar o atallo"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Queres quitar o atallo?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Preme a tecla para asignar o atallo"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Eliminarase de forma permanente o teu atallo personalizado."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Busca atallos"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Non hai resultados de busca"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icona de contraer"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Feito"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icona de despregar"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Configuración do teclado"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Definir atallo"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Quitar"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancelar"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Preme unha tecla"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Xa se está usando esta combinación de teclas. Proba con outra."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Xa se está usando esta combinación de teclas. Proba con outra."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Non se puido definir o atallo."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navega co teclado"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprende a usar os atallos de teclado"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navega co panel táctil"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 8901269..1c52868 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"ઑડિયો પર સ્વિચ કરવા કે તેને શેર કરવા માટે બે વાર ટૅપ કરો"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ઑડિયો શેરિંગને સપોર્ટ કરે છે"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"લૉક સ્ક્રીન વિજેટ"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"વિજેટનો ઉપયોગ કરીને ઍપ ખોલવા માટે, તમારે એ ચકાસણી કરવાની જરૂર રહેશે કે આ તમે જ છો. તે ઉપરાંત, ધ્યાનમાં રાખો કે તમારું ટૅબ્લેટ લૉક કરેલું હોય તો પણ કોઈપણ વ્યક્તિ તેમને જોઈ શકે છે. અમુક વિજેટ કદાચ તમારી લૉક સ્ક્રીન માટે બનાવવામાં આવ્યા ન હોઈ શકે છે અને તેમને અહીં ઉમેરવાનું અસલામત હોઈ શકે છે."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"સમજાઈ ગયું"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"વિજેટ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"લૉક સ્ક્રીન પર શૉર્ટકટ તરીકે વિજેટ ઉમેરવા માટે, ખાતરી કરો કે તે સેટિંગમાં ચાલુ કરેલા હોય."</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>
@@ -672,9 +674,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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"વાતચીતના નોટિફિકેશન વિભાગની ટોચ પર અને લૉક કરેલી સ્ક્રીન પર પ્રોફાઇલ ફોટો તરીકે બતાવે છે, બબલ તરીકે દેખાય છે, ખલેલ પાડશો નહીં મોડમાં વિક્ષેપ ઊભો કરે છે"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"પ્રાધાન્યતા"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> વાતચીતની સુવિધાઓને સપોર્ટ આપતી નથી"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"બંડલ પ્રતિસાદ પ્રદાન કરો"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"આ નોટિફિકેશનમાં કોઈ ફેરફાર થઈ શકશે નહીં."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"કૉલના નોટિફિકેશનમાં કોઈ ફેરફાર કરી શકાતો નથી."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"નોટિફિકેશનના આ ગ્રૂપની ગોઠવણી અહીં કરી શકાશે નહીં"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"વિભાજિત સ્ક્રીનનો ઉપયોગ કરતી વખતે ડાબી બાજુની કે ઉપરની ઍપ પર સ્વિચ કરો"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"વિભાજિત સ્ક્રીન દરમિયાન: એક ઍપને બીજી ઍપમાં બદલો"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"સક્રિય વિન્ડોને ડિસ્પ્લેની વચ્ચે ખસેડો"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ઇનપુટ"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"આગલી ભાષા પર સ્વિચ કરો"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"પાછલી ભાષા પર સ્વિચ કરો"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> કરતાં ઓછા અક્ષરનો ઉપયોગ કરો"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"બિલ્ડ નંબર"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"બિલ્ડ નંબર ક્લિપબૉર્ડ પર કૉપિ કર્યો."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ક્લિપબોર્ડ પર કૉપિ કરો."</string>
     <string name="basic_status" msgid="2315371112182658176">"વાતચીત ખોલો"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"વાતચીતના વિજેટ"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"તમારી હોમ સ્ક્રીનમાં વાતચીત ઉમેરવા માટે તેના પર ટૅપ કરો"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"વધુ રિઝોલ્યુશન માટે, ફોનને ફ્લિપ કરો"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ફોલ્ડ કરી શકાય એવું ડિવાઇસ અનફોલ્ડ કરવામાં આવી રહ્યું છે"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ફોલ્ડ કરી શકાય એવું ડિવાઇસ ફ્લિપ કરવામાં આવી રહ્યું છે"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ફ્રન્ટ સ્ક્રીનની સુવિધા ચાલુ કરેલી છે"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ફોલ્ડ કરેલું"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"અનફોલ્ડ કરેલું"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ઍક્સેસિબિલિટી"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"કીબોર્ડ શૉર્ટકટ"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"કીબોર્ડ શૉર્ટકટને કસ્ટમાઇઝ કરો"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"શૉર્ટકટ સોંપવા માટે દી દબાવો"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"શું શૉર્ટકટ કાઢી નાખીએ?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"શૉર્ટકટ સોંપવા માટે કી દબાવો"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"આ તમારા કસ્ટમ શૉર્ટકટને કાયમી રીતે ડિલીટ કરશે."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"શૉર્ટકટ શોધો"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"કોઈ શોધ પરિણામો નથી"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"\'નાનું કરો\'નું આઇકન"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"થઈ ગયું"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"\'મોટું કરો\'નું આઇકન"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"અથવા"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ઑબ્જેક્ટ ખેંચવાનું હૅન્ડલ"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"કીબોર્ડના સેટિંગ"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"શૉર્ટકટ સેટ કરો"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"કાઢી નાખો"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"રદ કરો"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"કી દબાવો"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"કી સંયોજન પેહલેથી ઉપયોગમાં છે. અન્ય કી અજમાવી જુઓ."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"કી સંયોજન પેહલેથી ઉપયોગમાં છે. અન્ય કી અજમાવી જુઓ."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"શૉર્ટકટ સેટ કરી શકાતો નથી."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"તમારા કીબોર્ડ વડે નૅવિગેટ કરો"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"કીબોર્ડ શૉર્ટકર્ટ જાણો"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"તમારા ટચપૅડ વડે નૅવિગેટ કરો"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 241e2e6..fafb045 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"ऑडियो को स्विच या शेयर करने के लिए टैप करें"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ऑडियो शेयर करने की सुविधा काम करती है"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"लॉक स्क्रीन विजेट"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"किसी विजेट से कोई ऐप्लिकेशन खोलने के लिए, आपको अपनी पहचान की पुष्टि करनी होगी. ध्यान रखें कि आपके टैबलेट के लॉक होने पर भी, कोई व्यक्ति विजेट देख सकता है. ऐसा हो सकता है कि कुछ विजेट, लॉक स्क्रीन पर दिखाने के लिए न बने हों. इन्हें लॉक स्क्रीन पर जोड़ना असुरक्षित हो सकता है."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ठीक है"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"विजेट"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"लॉक स्क्रीन पर शॉर्टकट के तौर पर विजेट जोड़ने के लिए, पक्का करें कि सेटिंग में यह सुविधा चालू हो."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"यह कई तरीकों से दिखती है, जैसे कि बातचीत वाली सूचनाओं में सबसे ऊपर, बबल के तौर पर, और लॉक स्क्रीन पर प्रोफ़ाइल फ़ोटो के तौर पर. साथ ही, यह \'परेशान न करें\' मोड को बायपास कर सकती है"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"प्राथमिकता"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> पर बातचीत की सुविधाएं काम नहीं करतीं"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"बंडल के बारे में सुझाव/राय दें या शिकायत करें"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"ये सूचनाएं नहीं बदली जा सकती हैं."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"कॉल से जुड़ी सूचनाओं को ब्लॉक नहीं किया जा सकता."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"सूचनाओं के इस समूह को यहां कॉन्फ़िगर नहीं किया जा सकता"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"स्प्लिट स्क्रीन पर, बाईं ओर या ऊपर के ऐप पर स्विच करने के लिए"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"स्प्लिट स्क्रीन के दौरान: एक ऐप्लिकेशन को दूसरे ऐप्लिकेशन से बदलें"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ऐक्टिव विंडो को एक से दूसरे डिसप्ले पर स्विच करें"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"इनपुट"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"अगली भाषा पर स्विच करने के लिए"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"पिछली भाषा पर स्विच करने के लिए"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> वर्ण से कम इस्तेमाल करें"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नंबर"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"बिल्ड नंबर को क्लिपबोर्ड पर कॉपी किया गया."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"क्लिपबोर्ड पर कॉपी करें."</string>
     <string name="basic_status" msgid="2315371112182658176">"ऐसी बातचीत जिसमें इंटरैक्शन डेटा मौजूद नहीं है"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"बातचीत वाला विजेट"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"किसी बातचीत को होम स्क्रीन पर जोड़ने के लिए, उस बातचीत पर टैप करें"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"बेहतर रिज़ॉल्यूशन वाली फ़ोटो खींचने के लिए, फ़ोन को फ़्लिप करें"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फ़ोल्ड किया जा सकने वाला डिवाइस अनफ़ोल्ड किया जा रहा है"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फ़ोल्ड किया जा सकने वाला डिवाइस पलटा जा रहा है"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"फ़्रंट स्क्रीन चालू है"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"डिवाइस फ़ोल्ड किया गया"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"डिवाइस अनफ़ोल्ड किया गया"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"सुलभता"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"कीबोर्ड शॉर्टकट"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"कीबोर्ड शॉर्टकट को पसंद के मुताबिक बनाएं"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"शॉर्टकट असाइन करने के लिए बटन दबाएं"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"क्या आपको शॉर्टकट हटाना है?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"शॉर्टकट असाइन करने के लिए बटन दबाएं"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ऐसा करने से, आपका कस्टम शॉर्टकट हमेशा के लिए मिट जाएगा."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"शॉर्टकट खोजें"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"खोज का कोई नतीजा नहीं मिला"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"छोटा करने का आइकॉन"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"हो गया"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"बड़ा करने का आइकॉन"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"या"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"खींचकर छोड़ने वाला हैंडल"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"कीबोर्ड सेटिंग"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"शॉर्टकट सेट करें"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"हटाएं"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"रद्द करें"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"बटन दबाएं"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"बटन का यह कॉम्बिनेशन पहले से इस्तेमाल किया जा रहा है. कोई दूसरा कॉम्बिनेशन आज़माएं."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"बटन का यह कॉम्बिनेशन पहले से इस्तेमाल किया जा रहा है. कोई दूसरा कॉम्बिनेशन आज़माएं."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"शॉर्टकट सेट नहीं किया जा सकता."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"कीबोर्ड का इस्तेमाल करके नेविगेट करें"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"कीबोर्ड शॉर्टकट के बारे में जानें"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"टचपैड का इस्तेमाल करके नेविगेट करें"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index a17964992..d1be021 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Zajedničko slušanje"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Dodirnite za prebacivanje ili dijeljenje zvuka"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Podržava zajedničko slušanje"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Spremljeno"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekini vezu"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviraj"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgeti na zaključanom zaslonu"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Da biste otvorili aplikaciju pomoću widgeta, trebate potvrditi da ste to vi. Također napominjemo da ih svatko može vidjeti, čak i ako je vaš tablet zaključan. Neki widgeti možda nisu namijenjeni za zaključani zaslon, pa ih možda nije sigurno dodati ovdje."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Shvaćam"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgeti"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Da biste dodali widgete na zaključani zaslon kao prečac, provjerite jesu li omogućeni u postavkama."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Prikazuje se pri vrhu obavijesti razgovora i kao profilna slika na zaključanom zaslonu, izgleda kao oblačić, prekida Ne uznemiravaj"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritetno"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> ne podržava značajke razgovora"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Pošaljite povratne informacije o paketu"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Te se obavijesti ne mogu izmijeniti."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Obavijesti o pozivima ne mogu se izmijeniti."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Ta se grupa obavijesti ne može konfigurirati ovdje"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Prelazak na aplikaciju slijeva ili iznad uz podijeljeni zaslon"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Tijekom podijeljenog zaslona: zamijeni aplikaciju drugom"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Premještanje aktivnog prozora između zaslona"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Unos"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Prelazak na sljedeći jezik"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Prelazak na prethodni jezik"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Upotrijebite manje od ovoliko znakova: <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj međuverzije"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Broj međuverzije kopiran je u međuspremnik."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopiranje u međuspremnik."</string>
     <string name="basic_status" msgid="2315371112182658176">"Otvoreni razgovor"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widgeti razgovora"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Dodirnite razgovor da biste ga dodali na početni zaslon"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za višu razlučivost okrenite telefon"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rasklopljen sklopivi uređaj"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Okretanje sklopivog uređaja sa svih strana"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Prednji zaslon je uključen"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zatvoreno"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"otvoreno"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pristupačnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tipkovni prečaci"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Prilagodba tipkovnih prečaca"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pritisnite tipku da biste dodijelili prečac"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Želite li ukloniti prečac?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pritisnite tipku da biste dodijelili prečac"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Time će se vaš prilagođeni prečac trajno izbrisati."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Prečaci za pretraživanje"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nema rezultata pretraživanja"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona za sažimanje"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Gotovo"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona za proširivanje"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Marker za povlačenje"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Postavke tipkovnice"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Postavite prečac"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Ukloni"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Odustani"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pritisnite tipku"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinacija tipki već se upotrebljava. Pokušajte s drugom tipkom."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinacija tipki već se upotrebljava. Pokušajte s drugom tipkom."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Prečac se ne može postaviti."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Krećite se pomoću tipkovnice"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Saznajte više o tipkovnim prečacima"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Krećite se pomoću dodirne podloge"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 4b5b916..4c5c364 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth használata"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Csatlakozva"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Hang megosztása"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Koppintással átkapcsolhatja vagy megoszthatja a hangot"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Támogatja a hang megosztását"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Mentve"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"leválasztás"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiválás"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"A lezárási képernyő moduljai"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Ha modul használatával szeretne megnyitni egy alkalmazást, igazolnia kell a személyazonosságát. Ne felejtse továbbá, hogy bárki megtekintheti a modulokat, még akkor is, amikor zárolva van a táblagép. Előfordulhat, hogy bizonyos modulokat nem a lezárási képernyőn való használatra terveztek, ezért nem biztonságos a hozzáadásuk."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Értem"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Modulok"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Ha szeretné gyorsparancsként hozzáadni a Modulok a lezárási képernyőn funkciót, győződjön meg arról, hogy a funkció engedélyezve van a beállításokban."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"A beszélgetésekre vonatkozó értesítések tetején, lebegő buborékként látható, megjeleníti a profilképet a lezárási képernyőn, és megszakítja a Ne zavarjanak funkciót"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritás"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> nem támogatja a beszélgetési funkciókat"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Visszajelzés küldése a csomagról"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Ezeket az értesítéseket nem lehet módosítani."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"A hívásértesítéseket nem lehet módosítani."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Az értesítések jelen csoportját itt nem lehet beállítani"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Váltás a bal oldalt, illetve fent lévő appra osztott képernyő esetén"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Osztott képernyőn: az egyik alkalmazás lecserélése egy másikra"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Aktív ablak áthelyezése egyik kijelzőről a másikra"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Bevitel"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Váltás a következő nyelvre"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Váltás az előző nyelvre"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Legfeljebb <xliff:g id="LENGTH">%1$d</xliff:g> karaktert használhat"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Buildszám"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Buildszám a vágólapra másolva."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"Másolás a vágólapra."</string>
     <string name="basic_status" msgid="2315371112182658176">"Beszélgetés megnyitása"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Beszélgetési modulok"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Koppintson a kívánt beszélgetésre a kezdőképernyőre való felvételhez"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"A nagyobb felbontás érdekében fordítsa meg a telefont"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Összehajtható eszköz kihajtása"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Összehajtható eszköz körbeforgatása"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Előlapi képernyő bekapcsolva"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"összehajtva"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"kihajtva"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Kisegítő lehetőségek"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Billentyűparancsok"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"A billentyűparancsok személyre szabása"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Nyomja meg a billentyűt a parancsikon hozzárendeléséhez"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Eltávolítja a billentyűparancsot?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Nyomja meg a billentyűt a billentyűparancs hozzárendeléséhez"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ezzel véglegesen törli az egyéni billentyűparancsot."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Billentyűparancsok keresése"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nincsenek keresési találatok"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Összecsukás ikon"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Kész"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Kibontás ikon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"vagy"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Fogópont"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Billentyűzetbeállítások"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Billentyűparancs beállítása"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Eltávolítás"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Mégse"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Nyomja le a billentyűt"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"A billentyűkombináció már használatban van. Próbálkozzon másik kulccsal."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"A billentyűkombináció már használatban van. Próbálkozzon másik billentyűvel."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Nem lehet beállítani a billentyűparancsot."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigáció a billentyűzet segítségével"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Billentyűparancsok megismerése"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigálás az érintőpaddal"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index a062098..0656700 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"Հպեք՝ աուդիոն փոխանջատելու կամ դրանով կիսվելու համար"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Աջակցում է աուդիոյի փոխանցում"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Կողպէկրանի վիջեթներ"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Վիջեթի միջոցով հավելված բացելու համար դուք պետք է հաստատեք ձեր ինքնությունը։ Նաև նկատի ունեցեք, որ ցանկացած ոք կարող է դիտել վիջեթները, նույնիսկ երբ ձեր պլանշետը կողպված է։ Որոշ վիջեթներ կարող են նախատեսված չլինել ձեր կողպէկրանի համար, և այստեղ դրանց ավելացնելը կարող է վտանգավոր լինել։"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Եղավ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Վիջեթներ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Վիջեթները կողպէկրանին որպես դյուրանցում ավելացնելու համար համոզվեք, որ գործառույթը միացված է կարգավորումներում։"</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>
@@ -695,7 +697,7 @@
     <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s: Հպեք՝ ձայնն անջատելու համար: Մատչելիության ծառայությունների ձայնը կարող է անջատվել:"</string>
     <string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s։ Հպեք՝ թրթռոցը միացնելու համար։"</string>
     <string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s։ Հպեք՝ ձայնը անջատելու համար։"</string>
-    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Աղմուկի կառավարում"</string>
+    <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Աղմուկի վերահսկում"</string>
     <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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Ցուցադրվում է զրույցների ծանուցումների վերևում, ինչպես նաև կողպէկրանին որպես պրոֆիլի նկար, հայտնվում է ամպիկի տեսքով, ընդհատում է «Չանհանգստացնել» ռեժիմը"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Կարևոր"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը զրույցի գործառույթներ չի աջակցում"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Հավաքածուի մասին կարծիք հայտնել"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Այս ծանուցումները չեն կարող փոփոխվել:"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Զանգերի մասին ծանուցումները հնարավոր չէ փոփոխել։"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Ծանուցումների տվյալ խումբը հնարավոր չէ կարգավորել այստեղ"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Անցեք աջ կողմի կամ վերևի հավելվածին տրոհված էկրանի միջոցով"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Տրոհված էկրանի ռեժիմում մեկ հավելվածը փոխարինել մյուսով"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Տեղափոխել ակտիվ պատուհանը էկրանների միջև"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Ներածում"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Անցնել հաջորդ լեզվին"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Անցնել նախորդ լեզվին"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Օգտագործեք մինչև <xliff:g id="LENGTH">%1$d</xliff:g> նիշ"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Կառուցման համարը"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Կառուցման համարը պատճենվեց սեղմատախտակին։"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"պատճենել սեղմատախտակին։"</string>
     <string name="basic_status" msgid="2315371112182658176">"Բաց զրույց"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Զրույցի վիջեթներ"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Հպեք զրույցին՝ այն հիմնական էկրանին ավելացնելու համար"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Ավելի մեծ լուծաչափի համար շրջեք հեռախոսը"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ծալովի սարք՝ բացված վիճակում"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Ծալովի սարք՝ շրջված վիճակում"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Առջևի էկրանը միացված է"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ծալված"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"բացված"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Հատուկ գործառույթներ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Ստեղնային դյուրանցումներ"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Կարգավորեք ստեղնային դյուրանցումներ"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Սեղմեք որևէ ստեղն՝ դյուրանցում նշանակելու համար"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Հեռացնե՞լ դյուրանցումը"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Սեղմեք որևէ ստեղն՝ դյուրանցում նշանակելու համար"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ձեր հատուկ դյուրանցումն ընդմիշտ կջնջվի։"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Դյուրանցումների որոնում"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Որոնման արդյունքներ չկան"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ծալել պատկերակը"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Պատրաստ է"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ծավալել պատկերակը"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"կամ"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Տեղափոխման նշիչ"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Ստեղնաշարի կարգավորումներ"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Ստեղծել դյուրանցում"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Հեռացնել"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Չեղարկել"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Սեղմեք որևէ ստեղն"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Ստեղների համակցությունն արդեն օգտագործվում է։ Ընտրեք այլ ստեղն։"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ստեղների համակցությունն արդեն օգտագործվում է։ Ընտրեք այլ ստեղն։"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Դյուրանցումը հնարավոր չէ ստեղծել։"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Կողմնորոշվեք ձեր ստեղնաշարի օգնությամբ"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Սովորեք օգտագործել ստեղնային դյուրանցումները"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Կողմնորոշվեք ձեր հպահարթակի օգնությամբ"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index cf9b093..da12167 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gunakan Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Terhubung"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Berbagi Audio"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Ketuk untuk beralih atau berbagi audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Mendukung berbagi audio"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Disimpan"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"berhenti hubungkan"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widget layar kunci"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Untuk membuka aplikasi menggunakan widget, Anda perlu memverifikasi diri Anda. Selain itu, harap ingat bahwa siapa saja dapat melihatnya, bahkan saat tablet Anda terkunci. Beberapa widget mungkin tidak dirancang untuk layar kunci Anda dan mungkin tidak aman untuk ditambahkan di sini."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Oke"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widget"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Untuk menambahkan Widget di layar kunci sebagai pintasan, pastikan Widget diaktifkan di setelan."</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Beralih pengguna"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pulldown"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua aplikasi dan data dalam sesi ini akan dihapus."</string>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Muncul teratas di notifikasi percakapan dan sebagai foto profil di layar kunci, ditampilkan sebagai balon, menimpa mode Jangan Ganggu"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritas"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak mendukung fitur percakapan"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Berikan Masukan Gabungan"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Notifikasi ini tidak dapat diubah."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Notifikasi panggilan tidak dapat diubah."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Grup notifikasi ini tidak dapat dikonfigurasi di sini"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Beralih ke aplikasi di bagian kiri atau atas saat menggunakan layar terpisah"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Dalam layar terpisah: ganti salah satu aplikasi dengan yang lain"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Memindahkan jendela aktif dari satu layar ke layar lainnya"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Beralih ke bahasa berikutnya"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Beralih ke bahasa sebelumnya"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Gunakan kurang dari <xliff:g id="LENGTH">%1$d</xliff:g> karakter"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Nomor build"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Nomor versi disalin ke papan klip."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"salin ke papan klip."</string>
     <string name="basic_status" msgid="2315371112182658176">"Membuka percakapan"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widget Percakapan"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Ketuk percakapan untuk menambahkannya ke Layar utama"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Untuk resolusi lebih tinggi, balik ponsel"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Perangkat foldable sedang dibentangkan"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Perangkat foldable sedang dibalik"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Layar depan diaktifkan"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ditutup"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"dibuka"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Aksesibilitas"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Pintasan keyboard"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Menyesuaikan pintasan keyboard"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Tekan tombol untuk menetapkan pintasan"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Hapus pintasan?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Tekan tombol untuk menetapkan pintasan"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Tindakan ini akan menghapus pintasan kustom Anda secara permanen."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Telusuri pintasan"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Tidak ada hasil penelusuran"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikon ciutkan"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Selesai"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikon luaskan"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"atau"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handel geser"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Setelan Keyboard"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Setel pintasan"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Hapus"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Batal"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Tekan tombol"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinasi tombol sudah digunakan. Coba tombol lain."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinasi tombol sudah digunakan. Coba tombol lain."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Pintasan tidak dapat disetel."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Menggunakan keyboard untuk navigasi"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Pelajari pintasan keyboard"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Menavigasi menggunakan touchpad"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index fe32aba..cee3ef4 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Nota Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Tengt"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Hljóði deilt"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Ýttu til að skipta um eða deila hljóði"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Styður hljóðdeilingu"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Vistað"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"aftengja"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"virkja"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Græjur fyrir lásskjá"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Þú þarft að staðfesta að þetta sért þú til að geta opnað forrit með græju. Hafðu einnig í huga að hver sem er getur skoðað þær, jafnvel þótt spjaldtölvan sé læst. Sumar græjur eru hugsanlega ekki ætlaðar fyrir lásskjá og því gæti verið óöruggt að bæta þeim við hér."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Ég skil"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Græjur"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Til að bæta „Græjur á lásskjá“ við sem flýtileið skaltu ganga úr skugga um að kveikt sé á eiginleikanum í stillingunum."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Birtist efst í samtalstilkynningum og sem prófílmynd á lásskjánum. Birtist sem blaðra sem truflar „Ónáðið ekki“"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Forgangur"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> styður ekki samtalseiginleika"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Senda inn ábendingu um pakka"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Ekki er hægt að breyta þessum tilkynningum."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Ekki er hægt að breyta tilkynningum um símtöl."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Ekki er hægt að stilla þessar tilkynningar hér"</string>
@@ -872,6 +875,10 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Skiptu í forrit til vinstri eða fyrir ofan þegar skjáskipting er notuð"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Í skjáskiptingu: Skipta forriti út fyrir annað forrit"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Færa virkan glugga á milli skjáa"</string>
+    <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Færa glugga til vinstri"</string>
+    <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Færa glugga til hægri"</string>
+    <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Stækka glugga"</string>
+    <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minnka glugga"</string>
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Innsláttur"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Skipta yfir í næsta tungumál"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Skipta yfir í fyrra tungumál"</string>
@@ -1220,8 +1227,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Notaðu færri en <xliff:g id="LENGTH">%1$d</xliff:g> stafi"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Útgáfunúmer smíðar"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Útgáfunúmer smíðar afritað á klippiborð."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"afrita á klippiborð."</string>
     <string name="basic_status" msgid="2315371112182658176">"Opna samtal"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Samtalsgræjur"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Ýttu á samtal til að bæta því á heimaskjáinn"</string>
@@ -1357,8 +1363,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Snúðu símanum til að fá betri upplausn"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Samanbrjótanlegt tæki opnað"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Samanbrjótanlegu tæki snúið við"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Kveikt á fremri skjá"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"samanbrotið"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"opið"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1423,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Aðgengi"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Flýtilyklar"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Sérsníddu flýtilykla"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Ýttu á lykil til að stilla flýtileið"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Fjarlægja flýtileið?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Ýttu á lykil til að stilla flýtileið"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Þetta eyðir sérsniðnu flýtileiðinni varanlega."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Leita að flýtileiðum"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Engar leitarniðurstöður"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Minnka tákn"</string>
@@ -1428,12 +1435,17 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Lokið"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Stækka tákn"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eða"</string>
+    <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plús"</string>
+    <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"rétt skástrik"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Dragkló"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Stillingar lyklaborðs"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Stilltu flýtileið"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Fjarlægja"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Hætta við"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Ýttu á lykil"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Lyklasamsetning er þegar í notkun. Prófaðu annan lykil."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Lyklasamsetningin er þegar í notkun. Prófaðu annan lykil."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Ekki er hægt að stilla flýtileið."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Flettu með því að nota lyklaborðið"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Kynntu þér flýtilykla"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Flettu með því að nota snertiflötinn"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index a66ea91..bc6f262 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -167,7 +167,7 @@
     <string name="issuerecord_start_error" msgid="3402782952722871190">"Impossibile avviare la registrazione del problema"</string>
     <string name="immersive_cling_title" msgid="8372056499315585941">"Visualizzazione a schermo intero"</string>
     <string name="immersive_cling_description" msgid="2717426731830851921">"Per uscire, scorri verso il basso dalla parte superiore dello schermo"</string>
-    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Ok"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Indietro"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usa il Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Dispositivo connesso"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Condivisione audio"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tocca per cambiare o condividere l\'audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Supporta la condivisione audio"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Dispositivo salvato"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnetti"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"attiva"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widget della schermata di blocco"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Per aprire un\'app utilizzando un widget, dovrai verificare la tua identità. Inoltre tieni presente che chiunque può vederlo, anche quando il tablet è bloccato. Alcuni widget potrebbero non essere stati progettati per la schermata di blocco e potrebbe non essere sicuro aggiungerli qui."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Ok"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widget"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Per aggiungere Widget alla schermata di blocco come scorciatoia, assicurati che sia abilitata nelle impostazioni."</string>
     <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>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tutte le app e i dati di questa sessione verranno eliminati."</string>
@@ -668,7 +670,7 @@
     <string name="screen_pinning_toast" msgid="8177286912533744328">"Per sbloccare questa app, tocca e tieni premuti i pulsanti Indietro e Panoramica"</string>
     <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"Per sbloccare questa app, tocca e tieni premuti i pulsanti Indietro e Home"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"Per sbloccare questa app, scorri verso l\'alto e tieni premuto"</string>
-    <string name="screen_pinning_positive" msgid="3285785989665266984">"OK"</string>
+    <string name="screen_pinning_positive" msgid="3285785989665266984">"Ok"</string>
     <string name="screen_pinning_negative" msgid="6882816864569211666">"No, grazie"</string>
     <string name="screen_pinning_start" msgid="7483998671383371313">"App bloccata"</string>
     <string name="screen_pinning_exit" msgid="4553787518387346893">"App sbloccata"</string>
@@ -751,7 +753,7 @@
     <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>
     <string name="tuner_persistent_warning" msgid="230466285569307806">"Queste funzioni sperimentali potrebbero cambiare, interrompersi o scomparire nelle versioni successive. Procedi con cautela."</string>
-    <string name="got_it" msgid="477119182261892069">"OK"</string>
+    <string name="got_it" msgid="477119182261892069">"Ok"</string>
     <string name="tuner_toast" msgid="3812684836514766951">"Complimenti! L\'Ottimizzatore UI di sistema è stato aggiunto alle impostazioni."</string>
     <string name="remove_from_settings" msgid="633775561782209994">"Rimuovi dalle impostazioni"</string>
     <string name="remove_from_settings_prompt" msgid="551565437265615426">"Vuoi rimuovere l\'Ottimizzatore UI di sistema dalle impostazioni e smettere di utilizzare tutte le sue funzioni?"</string>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Appare in cima alle notifiche delle conversazioni, come immagine del profilo nella schermata di blocco e sotto forma di bolla, inoltre interrompe la modalità Non disturbare"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Priorità"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> non supporta le funzionalità delle conversazioni"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Fornisci feedback sul bundle"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Impossibile modificare queste notifiche."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Impossibile modificare gli avvisi di chiamata."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Qui non è possibile configurare questo gruppo di notifiche"</string>
@@ -872,6 +875,10 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Passa all\'app a sinistra o sopra mentre usi lo schermo diviso"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Con lo schermo diviso: sostituisci un\'app con un\'altra"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Sposta la finestra attiva tra gli schermi"</string>
+    <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Sposta la finestra a sinistra"</string>
+    <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Sposta la finestra a destra"</string>
+    <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Ingrandisci la finestra"</string>
+    <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Riduci a icona la finestra"</string>
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Inserimento"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Passa alla lingua successiva"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Passa alla lingua precedente"</string>
@@ -1220,8 +1227,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Usa meno di <xliff:g id="LENGTH">%1$d</xliff:g> caratteri"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numero build"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Numero build copiato negli appunti."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copia negli appunti."</string>
     <string name="basic_status" msgid="2315371112182658176">"Apri conversazione"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widget di conversazione"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Tocca una conversazione per aggiungerla alla schermata Home"</string>
@@ -1357,8 +1363,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Gira il telefono per una maggiore risoluzione"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo pieghevole che viene aperto"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo pieghevole che viene capovolto"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Schermo frontale attivato"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"Piegato"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"Non piegato"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1423,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilità"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Scorciatoie da tastiera"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalizza scorciatoie da tastiera"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Premi un tasto per assegnare una scorciatoia"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Rimuovere scorciatoia?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Premi un tasto per assegnare una scorciatoia"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"La scorciatoia personalizzata verrà eliminata definitivamente."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Scorciatoie per la ricerca"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nessun risultato di ricerca"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icona Comprimi"</string>
@@ -1428,18 +1435,23 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Fine"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icona Espandi"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"oppure"</string>
+    <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"più"</string>
+    <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"barra"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Punto di trascinamento"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Impostazioni tastiera"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Imposta scorciatoia"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Rimuovi"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Annulla"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Premi un tasto"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Combinazione di tasti già in uso. Prova con un altro tasto."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Combinazione di tasti già in uso. Prova con un altro tasto."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Impossibile impostare la scorciatoia."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviga usando la tastiera"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Informazioni sulle scorciatoie da tastiera"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviga usando il touchpad"</string>
     <string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"Impara i gesti con il touchpad"</string>
     <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Naviga usando la tastiera e il touchpad"</string>
-    <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Scopri gesti con il touchpad, scorciatoie da tastiera e altro ancora"</string>
+    <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Impara i gesti con il touchpad, le scorciatoie da tastiera e altro ancora"</string>
     <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Indietro"</string>
     <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Vai alla schermata Home"</string>
     <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Visualizza app recenti"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 7b4245e..31f3e4e 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -306,11 +306,11 @@
     <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_audio_sharing_or_switch_active" msgid="3227408556754456024">"צריך להקיש כדי להחליף מצב או לשתף אודיו"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"תמיכה בשיתוף אודיו"</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="3345758139235739006">"הפעלה אוטומטית מחר"</string>
+    <string name="turn_on_bluetooth_auto_tomorrow" msgid="3345758139235739006">"הפעלה אוטומטית ביום הבא"</string>
     <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"‏תכונות כמו \'שיתוף מהיר\' ו\'איפה המכשיר שלי\' משתמשות ב-Bluetooth"</string>
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"‏חיבור ה-Bluetooth יופעל מחר בבוקר"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"שיתוף האודיו"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ווידג\'טים במסך הנעילה"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"כדי לפתוח אפליקציה באמצעות ווידג\'ט, עליך לאמת את זהותך. בנוסף, כדאי לזכור שכל אחד יכול לראות את הווידג\'טים גם כשהטאבלט שלך נעול. יכול להיות שחלק מהווידג\'טים לא נועדו למסך הנעילה ושלא בטוח להוסיף אותם לכאן."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"הבנתי"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ווידג\'טים"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"כדי להוסיף את קיצור דרך \"ווידג\'טים במסך הנעילה\", צריך לוודא שהתכונה הזו מופעלת בהגדרות."</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>
@@ -699,7 +701,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_mode" msgid="6867838048430807128">"מצב תוכנת הצלצול"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"השתקה"</string>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"מוצגת בחלק העליון של קטע התראות השיחה וכתמונת פרופיל במסך הנעילה, מופיעה בבועה צפה ומפריעה במצב \'נא לא להפריע\'"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"בעדיפות גבוהה"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> לא תומכת בתכונות השיחה"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"שליחת משוב על החבילה"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"לא ניתן לשנות את ההתראות האלה."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"לא ניתן לשנות את התראות השיחה."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"לא ניתן להגדיר כאן את קבוצת ההתראות הזו"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"מעבר לאפליקציה מימין או למעלה בזמן שימוש במסך מפוצל"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"כשהמסך מפוצל: החלפה בין אפליקציה אחת לאחרת"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"העברת החלון הפעיל בין מסכים"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"קלט"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"מעבר לשפה הבאה"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"מעבר לשפה הקודמת"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"אפשר להזין עד <xliff:g id="LENGTH">%1$d</xliff:g> תווים"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"‏מספר Build"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"‏מספר ה-Build הועתק ללוח."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"העתקה ללוח."</string>
     <string name="basic_status" msgid="2315371112182658176">"פתיחת שיחה"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"ווידג\'טים של שיחות"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"יש להקיש על שיחה כדי להוסיף אותה למסך הבית"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"כדי לצלם תמונה ברזולוציה גבוהה יותר, כדאי להפוך את הטלפון"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"מכשיר מתקפל עובר למצב לא מקופל"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"מכשיר מתקפל עובר למצב מהופך"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"המסך הקדמי מופעל"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"מצב מקופל"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"מצב לא מקופל"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"‏%1$s‏ / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"נגישות"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"מקשי קיצור"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"התאמה אישית של מקשי הקיצור"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"יש ללחוץ על מקש כדי להקצות מקש קיצור"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"להסיר את קיצור הדרך?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"צריך להקיש על מקש כדי להקצות מקש קיצור"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"קיצור הדרך יימחק באופן סופי."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"קיצורי דרך לחיפוש"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"אין תוצאות חיפוש"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"סמל הכיווץ"</string>
@@ -1428,18 +1439,25 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"סיום"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"סמל ההרחבה"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"או"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"נקודת האחיזה לגרירה"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"הגדרות המקלדת"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"הגדרה של מקש קיצור"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"הסרה"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ביטול"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"יש ללחוץ על מקש"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"שילוב המקשים הזה כבר בשימוש. אפשר לנסות מקש אחר."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"שילוב המקשים הזה כבר בשימוש. אפשר לנסות מקש אחר."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"לא ניתן להגדיר את קיצור הדרך."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ניווט באמצעות המקלדת"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"מידע על מקשי קיצור"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ניווט באמצעות לוח המגע"</string>
     <string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"מידע על התנועות בלוח המגע"</string>
-    <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"ניווט באמצעות המקלדת ולוח המגע"</string>
-    <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"מידע על התנועות בלוח המגע, מקשי קיצור ועוד"</string>
+    <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"ניווט עם המקלדת ולוח המגע"</string>
+    <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"כאן אפשר לקרוא איך מפעילים תנועות בלוח המגע, מקשי קיצור ועוד"</string>
     <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"חזרה"</string>
     <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"חזרה לדף הבית"</string>
     <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"הצגת האפליקציות האחרונות"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index c4fd62b..d5fb1a7 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -306,11 +306,11 @@
     <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_audio_sharing_or_switch_active" msgid="3227408556754456024">"タップして音声の切り替えや共有を行えます"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"音声の共有に対応しています"</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="3345758139235739006">"明日自動的に ON にする"</string>
+    <string name="turn_on_bluetooth_auto_tomorrow" msgid="3345758139235739006">"日付が変わったら自動的に ON にする"</string>
     <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Quick Share や「デバイスを探す」などの機能は Bluetooth を使用します"</string>
     <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"明日の朝に Bluetooth が ON になります"</string>
     <string name="quick_settings_bluetooth_audio_sharing_button" msgid="7545274861795853838">"音声を共有"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ロック画面ウィジェット"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ウィジェットを使用してアプリを起動するには、本人確認が必要です。タブレットがロックされた状態でも他のユーザーにウィジェットが表示されますので、注意してください。一部のウィジェットについてはロック画面での使用を想定していないため、ロック画面への追加は危険な場合があります。"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ウィジェット"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"ロック画面にウィジェットをショートカットとして追加するには、設定でウィジェットが有効になっていることをご確認ください。"</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"会話通知の一番上に表示されると同時に、ロック画面にプロフィール写真として表示されるほか、バブルとして表示され、サイレント モードが中断されます"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"優先"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g>は会話機能に対応していません"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"バンドルに関するフィードバックを送信"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"これらの通知は変更できません。"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"着信通知は変更できません。"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"このグループの通知はここでは設定できません"</string>
@@ -872,6 +875,10 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"分割画面の使用時に左側または上部のアプリに切り替える"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"分割画面中: アプリを順に置換する"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"アクティブなウィンドウをディスプレイ間で移動する"</string>
+    <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ウィンドウを左に移動する"</string>
+    <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ウィンドウを右に移動する"</string>
+    <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ウィンドウを最大化する"</string>
+    <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"ウィンドウを最小化する"</string>
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"入力"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"次の言語に切り替える"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"前の言語に切り替える"</string>
@@ -1220,8 +1227,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"使用できる文字数は <xliff:g id="LENGTH">%1$d</xliff:g> 文字未満です"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"ビルド番号"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"ビルド番号をクリップボードにコピーしました。"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"クリップボードにコピー。"</string>
     <string name="basic_status" msgid="2315371112182658176">"空の会話"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"会話ウィジェット"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"会話をタップするとホーム画面に追加されます"</string>
@@ -1357,8 +1363,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"高解像度で撮るにはスマートフォンを裏返してください"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"折りたたみ式デバイスが広げられている"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"折りたたみ式デバイスがひっくり返されている"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"フロント画面が ON になりました"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"折りたたんだ状態"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"広げた状態"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1423,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ユーザー補助"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"キーボード ショートカット"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"キーボード ショートカットをカスタマイズする"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ショートカットを割り当てるキーを押してください"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ショートカットを削除しますか?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ショートカットを割り当てるキーを押してください"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"この操作を行うと、カスタム ショートカットが完全に削除されます。"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"検索ショートカット"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"検索結果がありません"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"閉じるアイコン"</string>
@@ -1428,12 +1435,17 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"完了"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"開くアイコン"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"または"</string>
+    <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"プラス"</string>
+    <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"スラッシュ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ドラッグ ハンドル"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"キーボードの設定"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ショートカットの設定"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"削除"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"キャンセル"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"キーを押してください"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"このキーの組み合わせはすでに使用されています。別のキーを試してください。"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"このキーの組み合わせはすでに使用されています。別のキーを試してください。"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ショートカットを設定できません。"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"キーボードを使用して移動する"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"キーボード ショートカットの詳細"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"タッチパッドを使用して移動する"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 7cb3c8a..8bbb181 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"შეეხეთ აუდიოს გადასართავად ან გასაზიარებლად"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"აუდიოს გაზიარება მხარდაჭერილია"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"დაბლოკილი ეკრანის ვიჯეტები"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"უნდა დაადასტუროთ თქვენი ვინაობა, რათა გახსნათ აპი ვიჯეტის გამოყენებით. გაითვალისწინეთ, რომ ნებისმიერს შეუძლია მათი ნახვა, მაშინაც კი, როცა ტაბლეტი დაბლოკილია. ზოგი ვიჯეტი შეიძლება არ იყოს გათვლილი თქვენი დაბლოკილი ეკრანისთვის და მათი აქ დამატება შეიძლება სახიფათო იყოს."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"გასაგებია"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ვიჯეტები"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"ჩაკეტილ ეკრანზე ვიჯეტის მალსახმობის სახით დასამატებლად დარწმუნდით, რომ ის ჩართულია პარამეტრებიდან."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"გამოჩნდება საუბრის შეტყობინებების თავში და პროფილის სურათის სახით ჩაკეტილ ეკრანზე, ჩნდება ბუშტის სახით, წყვეტს ფუნქციას „არ შემაწუხოთ“"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"პრიორიტეტი"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ს არ აქვს მიმოწერის ფუნქციების მხარდაჭერა"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ნაკრებზე გამოხმაურების წარმოდგენა"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"ამ შეტყობინებების შეცვლა შეუძლებელია."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"ზარის შეტყობინებების შეცვლა შეუძლებელია."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"შეტყობინებების ამ ჯგუფის კონფიგურირება აქ შეუძლებელია"</string>
@@ -872,6 +875,10 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ეკრანის გაყოფის გამოყენებისას აპზე მარცხნივ ან ზემოთ გადართვა"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"ეკრანის გაყოფის დროს: ერთი აპის მეორით ჩანაცვლება"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"აქტიური ფანჯრის გადატანა ეკრანებს შორის"</string>
+    <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ფანჯრის მარცხნივ გადაადგილება"</string>
+    <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ფანჯრის მარჯვნივ გადაადგილება"</string>
+    <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ფანჯრის გაშლა"</string>
+    <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"ფანჯრის ჩაკეცვა"</string>
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"შეყვანა"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"შემდეგ ენაზე გადართვა"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"წინა ენაზე გადართვა"</string>
@@ -1220,8 +1227,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"გამოიყენეთ <xliff:g id="LENGTH">%1$d</xliff:g>-ზე ნაკლები სიმბოლო"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"ანაწყობის ნომერი"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"ანაწყობის ნომერი დაკოპირებულია გაცვლის ბუფერში."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"კოპირება გაცვლის ბუფერში."</string>
     <string name="basic_status" msgid="2315371112182658176">"მიმოწერის გახსნა"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"საუბრის ვიჯეტები"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"შეეხეთ საუბარს მის თქვენს მთავარ ეკრანზე დასამატებლად"</string>
@@ -1357,8 +1363,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"მაღალი გარჩევადობისთვის ამოაბრუნეთ ტელეფონი"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"დასაკეცი მოწყობილობა იხსნება"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"დასაკეცი მოწყობილობა ტრიალებს"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"წინა ეკრანი ჩართულია"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"დაკეცილი"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"გაშლილი"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1423,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"მისაწვდომობა"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"კლავიატურის მალსახმობები"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"კლავიატურის მალსახმობების მორგება"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"მალსახმობის მინიჭებისთვის დააჭირეთ კლავიშს"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"გსურთ მალსახმობის წაშლა?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"მალსახმობის მინიჭებისთვის დააჭირეთ კლავიშს"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ეს თქვენს მორგებულ მალსახმობებს სამუდამოდ წაშლის."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ძიების მალსახმობები"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ძიების შედეგები არ არის"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ხატულის ჩაკეცვა"</string>
@@ -1428,12 +1435,17 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"მზადაა"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ხატულის გაფართოება"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ან"</string>
+    <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"პლუსი"</string>
+    <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"წინ გადახრილი წილადის ხაზი"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"სახელური ჩავლებისთვის"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"კლავიატურის პარამეტრები"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"მალსახმობის დაყენება"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ამოშლა"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"გაუქმება"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"დააჭირეთ კლავიშს"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"კლავიშების კომბინაცია უკვე გამოიყენება. ცადეთ სხვა კლავიში."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"კლავიშების კომბინაცია უკვე გამოიყენება. ცადეთ სხვა კლავიში."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"მალსახმობის დაყენება ვერ ხერხდება."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ნავიგაცია კლავიატურის გამოყენებით"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"კლავიატურის მალსახმობების სწავლა"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ნავიგაცია სენსორული პანელის გამოყენებით"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 4122eee..4f11293 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"Аудионы бөлісу немесе ауыстыру үшін түртіңіз."</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Аудио бөлісуге мүмкіндік береді."</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Құлып экранының виджеттері"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Қолданбаны виджет көмегімен ашу үшін жеке басыңызды растауыңыз керек. Сондай-ақ басқалар оларды планшетіңіз құлыптаулы кезде де көре алатынын ескеріңіз. Кейбір виджеттер құлып экранына арналмаған болады, сондықтан оларды мұнда қосу қауіпсіз болмауы мүмкін."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Түсінікті"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Виджеттер"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"To add Widgets on the lock screen as a shortcut, make sure it is enabled in settings."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Әңгіме туралы хабарландырулардың жоғарғы жағында тұрады және құлыптаулы экранда профиль суреті болып көрсетіледі, қалқыма хабар түрінде шығады, Мазаламау режимін тоқтатады."</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Маңызды"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> әңгіме функцияларын қолдамайды."</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Пакет туралы пікір жіберу"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Бұл хабарландыруларды өзгерту мүмкін емес."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Қоңырау туралы хабарландыруларды өзгерту мүмкін емес."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Мұндай хабарландырулар бұл жерде конфигурацияланбайды."</string>
@@ -853,7 +856,7 @@
     <string name="keyboard_shortcut_a11y_filter_current_app" msgid="7944592357493737911">"Қазіргі қолданбаға арналған жылдам пәрмендер көрсетіледі."</string>
     <string name="group_system_access_notification_shade" msgid="1619028907006553677">"Хабарландыруларды көру"</string>
     <string name="group_system_full_screenshot" msgid="5742204844232667785">"Скриншот жасау"</string>
-    <string name="group_system_access_system_app_shortcuts" msgid="8562482996626694026">"Жылдам пәрмендерді көрсету"</string>
+    <string name="group_system_access_system_app_shortcuts" msgid="8562482996626694026">"Пернелер тіркесімдерін көрсету"</string>
     <string name="group_system_go_back" msgid="2730322046244918816">"Артқа"</string>
     <string name="group_system_access_home_screen" msgid="4130366993484706483">"Негізгі экранға өту"</string>
     <string name="group_system_overview_open_apps" msgid="5659958952937994104">"Соңғы қолданбаларды көру"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Бөлінген экранда сол не жоғары жақтағы қолданбаға ауысу"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Экранды бөлу кезінде: бір қолданбаны басқасымен алмастыру"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Дисплейлер арасында қосулы терезені ауыстыру"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Енгізу"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Келесі тілге ауысу"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Алдыңғы тілге ауысу"</string>
@@ -1201,7 +1212,7 @@
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Динамиктер мен дисплейлер"</string>
     <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Ұсынылған құрылғылар"</string>
-    <string name="media_input_group_title" msgid="2057057473860783021">"Кіріс"</string>
+    <string name="media_input_group_title" msgid="2057057473860783021">"Енгізу"</string>
     <string name="media_output_group_title" msgid="6789001895863332576">"Шығыс"</string>
     <string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Мультимедиа файлын басқа құрылғыға жылжыту үшін ортақ сеансты тоқтатыңыз."</string>
     <string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Тоқтату"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Ең көбі <xliff:g id="LENGTH">%1$d</xliff:g> таңба пайдаланыңыз."</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Құрама нөмірі"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Құрама нөмірі буферге көшірілді."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"буферге көшіріңіз."</string>
     <string name="basic_status" msgid="2315371112182658176">"Ашық әңгіме"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Әңгіме виджеттері"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Негізгі экранға қосқыңыз келетін әңгімені түртіңіз."</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Жоғары ажыратымдылық үшін телефонды айналдырыңыз."</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Бүктемелі құрылғы ашылып жатыр."</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Бүктемелі құрылғы аударылып жатыр."</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Алдыңғы экран қосылды."</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"жабық"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ашық"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1412,13 +1421,15 @@
     <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Мультитаскинг"</string>
     <string name="shortcutHelper_category_recent_apps" msgid="7918731953612377145">"Соңғы қолданбалар"</string>
     <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Экранды бөлу"</string>
-    <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Кіріс"</string>
+    <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Енгізу"</string>
     <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Қолданба таңбашалары"</string>
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Қолданыстағы қолданба"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Арнайы мүмкіндіктер"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Перне тіркесімдері"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Пернелер тіркесімін бейімдеу"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Жылдам пәрменді тағайындау үшін пернені басыңыз."</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Жылдам пәрменді өшіру керек пе?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Жылдам пәрменді тағайындау үшін пернені басыңыз."</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Арнаулы жылдам пәрменіңіз біржола жойылады."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Іздеу жылдам пәрмендері"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Іздеу нәтижелері жоқ."</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Жию белгішесі"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Дайын"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Жаю белгішесі"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"немесе"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Сүйрейтін тетік"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Пернетақта параметрлері"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Жылдам пәрменді орнату"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Өшіру"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Бас тарту"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Пернені басыңыз"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Бұл пернелер тіркесімі қазір қолданыста. Басқа пернені таңдаңыз."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Бұл пернелер тіркесімі қазір қолданыста. Басқа перне таңдаңыз."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Перне тіркесімін орнату мүмкін емес."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Пернетақтамен жұмыс істеңіз"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Перне тіркесімдерін үйреніңіз."</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Сенсорлық тақтамен жұмыс істеңіз"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index f9ec58f..33aeb70 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"ចុចដើម្បីប្ដូរ ឬចែករំលែកសំឡេង"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"អាចប្រើការស្ដាប់សំឡេងរួមគ្នា"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ធាតុ​ក្រាហ្វិកលើអេក្រង់ចាក់សោ"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ដើម្បីបើកកម្មវិធីដោយប្រើធាតុ​ក្រាហ្វិក អ្នកនឹងត្រូវផ្ទៀងផ្ទាត់ថាជាអ្នក។ ទន្ទឹមនឹងនេះ សូមចងចាំថា នរណាក៏អាចមើលធាតុក្រាហ្វិកបាន សូម្បីពេលថេប្លេតរបស់អ្នកជាប់សោក៏ដោយ។ ធាតុ​ក្រាហ្វិកមួយចំនួនប្រហែលមិនត្រូវបានរចនាឡើងសម្រាប់អេក្រង់ចាក់សោរបស់អ្នកទេ និងមិនមានសុវត្ថិភាពឡើយ បើបញ្ចូលទៅទីនេះ។"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"យល់ហើយ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ធាតុ​ក្រាហ្វិក"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"ដើម្បីបញ្ចូលធាតុក្រាហ្វិកនៅលើអេក្រង់ចាក់សោជាផ្លូវកាត់ សូមប្រាកដថាវាត្រូវបានបើកនៅក្នុងការកំណត់។"</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"បង្ហាញនៅខាងលើ​ការជូនដំណឹងអំពីការសន្ទនា និងជារូបភាព​កម្រង​ព័ត៌មាននៅលើអេក្រង់ចាក់សោ បង្ហាញជាពពុះ បង្អាក់មុខងារកុំ​រំខាន"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"អាទិភាព"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> មិនអាចប្រើ​មុខងារ​សន្ទនា​បានទេ"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ផ្ដល់មតិកែលម្អជាកញ្ចប់"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"មិនអាច​កែប្រែ​ការជូនដំណឹង​ទាំងនេះ​បានទេ។"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"មិនអាច​កែប្រែ​ការជូនដំណឹងអំពីការហៅទូរសព្ទបានទេ។"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"មិនអាច​កំណត់​រចនាសម្ព័ន្ធ​ក្រុមការជូនដំណឹងនេះ​នៅទីនេះ​បានទេ"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ប្ដូរទៅកម្មវិធីនៅខាងឆ្វេង ឬខាងលើ ពេលកំពុងប្រើមុខងារ​បំបែកអេក្រង់"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"ក្នុងអំឡុងពេលប្រើមុខងារបំបែកអេក្រង់៖ ជំនួសកម្មវិធីពីមួយទៅមួយទៀត"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ផ្លាស់ទីវិនដូដែលសកម្មរវាងផ្ទាំងអេក្រង់"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"បញ្ចូល"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"ប្ដូរទៅភាសាបន្ទាប់"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"ប្ដូរទៅភាសាមុន"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"ប្រើតិចជាង <xliff:g id="LENGTH">%1$d</xliff:g> តួអក្សរ"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"លេខ​កំណែបង្កើត"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"បានចម្លងលេខ​កំណែបង្កើតទៅឃ្លីបបត។"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ចម្លង​ទៅ​ឃ្លីបបត។"</string>
     <string name="basic_status" msgid="2315371112182658176">"បើកការសន្ទនា"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"ធាតុ​ក្រាហ្វិកនៃការសន្ទនា"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"ចុចការសន្ទនា ដើម្បីបញ្ចូលវាទៅក្នុងអេក្រង់ដើមរបស់អ្នក"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"សម្រាប់កម្រិតគុណភាពកាន់តែខ្ពស់ សូមត្រឡប់ទូរសព្ទ"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ឧបករណ៍អាច​បត់បានកំពុងត្រូវបានលា"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ឧបករណ៍អាច​បត់បានកំពុងត្រូវបានលា"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"បានបើកអេក្រង់ខាងមុខ"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"បត់"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"លា"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ភាពងាយស្រួល"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"ផ្លូវកាត់​ក្ដារ​ចុច"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"ប្ដូរ​ផ្លូវកាត់​ក្ដារ​ចុចតាម​បំណង"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ចុចគ្រាប់ចុច ដើម្បីកំណត់ផ្លូវ​កាត់"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ដក​ផ្លូវកាត់​ចេញឬ?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ចុចគ្រាប់ចុច ដើម្បីកំណត់ផ្លូវ​កាត់"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ការធ្វើបែបនេះនឹងលុបផ្លូវ​កាត់ផ្ទាល់ខ្លួនរបស់អ្នកជាអចិន្ត្រៃយ៍។"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ស្វែងរកផ្លូវ​កាត់"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"គ្មាន​លទ្ធផល​ស្វែងរក​ទេ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"រូបតំណាង \"បង្រួម\""</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"រួចរាល់"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"រូបតំណាង \"ពង្រីក\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ឬ"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ដង​អូស"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"ការកំណត់​ក្ដារចុច"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"កំណត់ផ្លូវ​កាត់"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ដកចេញ"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"បោះបង់"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"ចុចគ្រាប់ចុច"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"កំពុងប្រើបន្សំគ្រាប់ចុចស្រាប់ហើយ។ សាកល្បងប្រើគ្រាប់ចុចផ្សេង។"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"កំពុងប្រើបន្សំគ្រាប់ចុចស្រាប់ហើយ។ សាកល្បងប្រើគ្រាប់ចុចផ្សេង។"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"មិនអាចកំណត់ផ្លូវកាត់បានទេ។"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"រុករកដោយប្រើក្ដារចុចរបស់អ្នក"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"ស្វែងយល់អំពីផ្លូវកាត់​ក្ដារ​ចុច"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"រុករកដោយប្រើផ្ទាំងប៉ះរបស់អ្នក"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index f315570..ee06f23 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"ಆಡಿಯೊವನ್ನು ಬದಲಾಯಿಸಲು ಅಥವಾ ಹಂಚಿಕೊಳ್ಳಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ಆಡಿಯೋ ಹಂಚಿಕೊಳ್ಳುವಿಕೆಯನ್ನು ಬೆಂಬಲಿಸುತ್ತದೆ"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ಲಾಕ್ ಸ್ಕ್ರೀನ್ ವಿಜೆಟ್‌ಗಳು"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ವಿಜೆಟ್ ಅನ್ನು ಬಳಸಿಕೊಂಡು ಆ್ಯಪ್ ತೆರೆಯಲು, ಇದು ನೀವೇ ಎಂದು ನೀವು ದೃಢೀಕರಿಸಬೇಕಾಗುತ್ತದೆ. ಅಲ್ಲದೆ, ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ ಲಾಕ್ ಆಗಿದ್ದರೂ ಸಹ ಯಾರಾದರೂ ಅವುಗಳನ್ನು ವೀಕ್ಷಿಸಬಹುದು ಎಂಬುದನ್ನು ನೆನಪಿನಲ್ಲಿಡಿ. ಕೆಲವು ವಿಜೆಟ್‌ಗಳು ನಿಮ್ಮ ಲಾಕ್ ಸ್ಕ್ರೀನ್‌ಗಾಗಿ ಉದ್ದೇಶಿಸದೇ ಇರಬಹುದು ಮತ್ತು ಇಲ್ಲಿ ಸೇರಿಸುವುದು ಸುರಕ್ಷಿತವಲ್ಲದಿರಬಹುದು."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ಅರ್ಥವಾಯಿತು"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ವಿಜೆಟ್‌ಗಳು"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"ಶಾರ್ಟ್‌ಕಟ್‌ನಂತೆ ಲಾಕ್‌ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ವಿಜೆಟ್‌ಗಳನ್ನು ಸೇರಿಸಲು, ಅದನ್ನು ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆಯೇ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"ಸಂಭಾಷಣೆ ಅಧಿಸೂಚನೆಗಳ ಮೇಲ್ಭಾಗದಲ್ಲಿ ಹಾಗೂ ಲಾಕ್ ಸ್ಕ್ರೀನ್‌ನ ಮೇಲೆ ಪ್ರೊಫೈಲ್ ಚಿತ್ರವಾಗಿ ತೋರಿಸುತ್ತದೆ, ಬಬಲ್‌ನಂತೆ ಗೋಚರಿಸುತ್ತದೆ, ಅಡಚಣೆ ಮಾಡಬೇಡ ಮೋಡ್‌ಗೆ ಅಡ್ಡಿಯುಂಟುಮಾಡುತ್ತದೆ"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"ಆದ್ಯತೆ"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"ಸಂವಾದ ಫೀಚರ್‌ಗಳನ್ನು <xliff:g id="APP_NAME">%1$s</xliff:g> ಬೆಂಬಲಿಸುವುದಿಲ್ಲ"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ಬಂಡಲ್‌ ಫೀಡ್‌ಬ್ಯಾಕ್‌ ಅನ್ನು ಒದಗಿಸಿ"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"ಈ ಅಧಿಸೂಚನೆಗಳನ್ನು ಮಾರ್ಪಡಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"ಕರೆ ಅಧಿಸೂಚನೆಗಳನ್ನು ಮಾರ್ಪಡಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"ಈ ಗುಂಪಿನ ಅಧಿಸೂಚನೆಗಳನ್ನು ಇಲ್ಲಿ ಕಾನ್ಫಿಗರ್‌ ಮಾಡಲಾಗಿರುವುದಿಲ್ಲ"</string>
@@ -872,6 +875,10 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ಸ್ಕ್ರೀನ್ ಬೇರ್ಪಡಿಸಿ ಮೋಡ್ ಬಳಸುವಾಗ ಎಡಭಾಗ ಅಥವಾ ಮೇಲ್ಭಾಗದಲ್ಲಿರುವ ಆ್ಯಪ್‌ಗೆ ಬದಲಿಸಿ"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"ಸ್ಕ್ರೀನ್ ಬೇರ್ಪಡಿಸುವ ಸಮಯದಲ್ಲಿ: ಒಂದು ಆ್ಯಪ್‌ನಿಂದ ಮತ್ತೊಂದು ಆ್ಯಪ್‌ಗೆ ಬದಲಿಸಿ"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ಸಕ್ರಿಯ ವಿಂಡೋವನ್ನು ಡಿಸ್‌ಪ್ಲೇಗಳ ನಡುವೆ ಸರಿಸಿ"</string>
+    <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ವಿಂಡೋವನ್ನು ಎಡಕ್ಕೆ ಸರಿಸಿ"</string>
+    <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ವಿಂಡೋವನ್ನು ಬಲಕ್ಕೆ ಸರಿಸಿ"</string>
+    <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ವಿಂಡೋವನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string>
+    <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"ವಿಂಡೋವನ್ನು ಮಿನಿಮೈಸ್ ಮಾಡಿ"</string>
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ಇನ್‌ಪುಟ್"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"ಮುಂದಿನ ಭಾಷೆಗೆ ಬದಲಿಸಿ"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"ಹಿಂದಿನ ಭಾಷೆಗೆ ಬದಲಿಸಿ"</string>
@@ -1220,8 +1227,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> ಕ್ಕಿಂತ ಕಡಿಮೆ ಅಕ್ಷರಗಳನ್ನು ಬಳಸಿ"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"ಬಿಲ್ಡ್ ಸಂಖ್ಯೆ"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"ಬಿಲ್ಡ್ ಸಂಖ್ಯೆಯನ್ನು ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ನಲ್ಲಿ ನಕಲಿಸಲಾಗಿದೆ."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ಕಾಪಿ ಮಾಡಿ."</string>
     <string name="basic_status" msgid="2315371112182658176">"ಸಂಭಾಷಣೆಯನ್ನು ತೆರೆಯಿರಿ"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"ಸಂಭಾಷಣೆ ವಿಜೆಟ್‌ಗಳು"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"ಸಂಭಾಷಣೆಯನ್ನು ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಸೇರಿಸಲು ಅದನ್ನು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
@@ -1357,8 +1363,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ಅಧಿಕ ರೆಸಲ್ಯೂಷನ್‌ಗಾಗಿ, ಫೋನ್ ಅನ್ನು ಫ್ಲಿಪ್ ಮಾಡಿ"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ಫೋಲ್ಡ್ ಮಾಡಬಹುದಾದ ಸಾಧನವನ್ನು ಅನ್‌ಫೋಲ್ಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ಫೋಲ್ಡ್ ಮಾಡಬಹುದಾದ ಸಾಧನವನ್ನು ಸುತ್ತಲೂ ತಿರುಗಿಸಲಾಗುತ್ತಿದೆ"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ಫ್ರಂಟ್ ಸ್ಕ್ರೀನ್ ಆನ್ ಆಗಿದೆ"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ಫೋಲ್ಡ್ ಮಾಡಿರುವುದು"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ಅನ್‌ಫೋಲ್ಡ್ ಮಾಡಿರುವುದು"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1423,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"ಕೀಬೋರ್ಡ್ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"ಕೀಬೋರ್ಡ್ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ನಿಯೋಜಿಸಲು ಕೀಯನ್ನು ಒತ್ತಿರಿ"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ತೆಗೆದುಹಾಕಬೇಕೇ?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ನಿಯೋಜಿಸಲು ಕೀಯನ್ನು ಒತ್ತಿರಿ"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ಇದು ನಿಮ್ಮ ಕಸ್ಟಮ್ ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ಶಾಶ್ವತವಾಗಿ ಅಳಿಸುತ್ತದೆ."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ಹುಡುಕಾಟದ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ಯಾವುದೇ ಹುಡುಕಾಟ ಫಲಿತಾಂಶಗಳಿಲ್ಲ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ಕುಗ್ಗಿಸುವ ಐಕಾನ್"</string>
@@ -1428,12 +1435,17 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"ಮುಗಿದಿದೆ"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ವಿಸ್ತೃತಗೊಳಿಸುವ ಐಕಾನ್"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ಅಥವಾ"</string>
+    <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"ಪ್ಲಸ್"</string>
+    <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ಫಾರ್ವರ್ಡ್ ಸ್ಲ್ಯಾಷ್"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ಡ್ರ್ಯಾಗ್‌ ಹ್ಯಾಂಡಲ್‌"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"ಕೀಬೋರ್ಡ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ಶಾರ್ಟ್‌ಕಟ್ ಸೆಟ್ ಮಾಡಿ"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ತೆಗೆದುಹಾಕಿ"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ರದ್ದುಮಾಡಿ"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"ಕೀ ಅನ್ನು ಒತ್ತಿರಿ"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"ಕೀ ಸಂಯೋಜನೆಯು ಈಗಾಗಲೇ ಬಳಕೆಯಲ್ಲಿದೆ. ಮತ್ತೊಂದು ಕೀ ಬಳಸಿ ನೋಡಿ."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ಕೀ ಸಂಯೋಜನೆಯು ಈಗಾಗಲೇ ಬಳಕೆಯಲ್ಲಿದೆ. ಮತ್ತೊಂದು ಕೀ ಬಳಸಿ ನೋಡಿ."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ಶಾರ್ಟ್‌ಕಟ್‌ ಅನ್ನು ಸೆಟ್‌ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ನಿಮ್ಮ ಕೀಬೋರ್ಡ್ ಬಳಸಿ ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"ಕೀಬೋರ್ಡ್ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಕಲಿಯಿರಿ"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ನಿಮ್ಮ ಟಚ್‌ಪ್ಯಾಡ್ ಬಳಸಿ ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 931066b..6dcf92c 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"오디오를 전환하거나 공유하려면 탭하세요"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"오디오 공유 지원"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"잠금 화면 위젯"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"위젯을 사용하여 앱을 열려면 본인 인증을 해야 합니다. 또한 태블릿이 잠겨 있더라도 누구나 볼 수 있다는 점을 유의해야 합니다. 일부 위젯은 잠금 화면에 적합하지 않고 여기에 추가하기에 안전하지 않을 수 있습니다."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"확인"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"위젯"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"잠금 화면에 위젯을 바로가기로 추가하려면 설정에서 위젯을 사용 설정하세요."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"대화 알림 상단에 표시, 잠금 화면에 프로필 사진으로 표시, 대화창으로 표시, 방해 금지 모드를 무시함"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"우선순위"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱은 대화 기능을 지원하지 않습니다."</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"번들 관련 의견 보내기"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"이 알림은 수정할 수 없습니다."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"전화 알림은 수정할 수 없습니다."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"이 알림 그룹은 여기에서 설정할 수 없습니다."</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"화면 분할을 사용하는 중에 왼쪽 또는 위쪽에 있는 앱으로 전환하기"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"화면 분할 중: 다른 앱으로 바꾸기"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"디스플레이 간 활성 창 이동"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"입력"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"다음 언어로 전환"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"이전 언어로 전환"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g>자 미만이어야 합니다."</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"빌드 번호"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"빌드 번호가 클립보드에 복사되었습니다."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"클립보드에 복사"</string>
     <string name="basic_status" msgid="2315371112182658176">"대화 열기"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"대화 위젯"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"대화를 탭하여 홈 화면에 추가하세요."</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"해상도를 높이려면 후면 카메라를 사용하세요."</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"폴더블 기기를 펼치는 모습"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"폴더블 기기를 뒤집는 모습"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"전면 화면 켜짐"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"접은 상태"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"펼친 상태"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"접근성"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"단축키"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"단축키 맞춤설정"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"키를 눌러 단축키 지정"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"바로가기를 제거하시겠습니까?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"키를 눌러 단축키 지정"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"맞춤 단축어가 영구적으로 삭제됩니다."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"검색 바로가기"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"검색 결과 없음"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"접기 아이콘"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"완료"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"확장 아이콘"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"또는"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"드래그 핸들"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"키보드 설정"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"단축키 설정"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"삭제"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"취소"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"키를 누르세요."</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"이미 사용 중인 키 조합입니다. 다른 키를 사용해 보세요."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"이미 사용 중인 키 조합입니다. 다른 키를 사용해 보세요."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"단축키를 설정할 수 없습니다."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"키보드를 사용하여 이동"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"단축키에 관해 알아보세요."</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"터치패드를 사용하여 이동"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index fc16164..5a58507 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"Аудиону которуштуруу же бөлүшүү үчүн таптаңыз"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Чогуу угууга болот"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Кулпуланган экрандагы виджеттер"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Колдонмону виджет аркылуу ачуу үчүн өзүңүздү ырасташыңыз керек. Алар кулпуланган планшетиңизде да көрүнүп турат. Кээ бир виджеттерди кулпуланган экранда колдоно албайсыз, андыктан аларды ал жерге кошпой эле койгонуңуз оң."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Түшүндүм"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Виджеттер"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"\"Кулпуланган экрандагы виджеттер\" функциясын ыкчам баскыч катары кошуу үчүн параметрлерге өтүп, анын иштетилгенин текшериңиз."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Cүйлөшүүлөр тууралуу билдирмелердин жогору жагында жана кулпуланган экранда профилдин сүрөтү, ошондой эле калкып чыкма билдирме түрүндө көрүнүп, \"Тынчымды алба\" режимин токтотот"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Маанилүүлүгү"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда оозеки сүйлөшкөнгө болбойт"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Топтом тууралуу пикир билдирүү"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Бул билдирмелерди өзгөртүүгө болбойт."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Чалуу билдирмелерин өзгөртүүгө болбойт."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Бул билдирмелердин тобун бул жерде конфигурациялоого болбойт"</string>
@@ -872,6 +875,10 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Бөлүнгөн экранды колдонуп жатканда сол же жогору жактагы колдонмого которулуңуз"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Экранды бөлүү режиминде бир колдонмону экинчисине алмаштыруу"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Активдүү терезени экрандардын ортосунда жылдыруу"</string>
+    <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Терезени солго жылдыруу"</string>
+    <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Терезени оңго жылдыруу"</string>
+    <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Терезени чоңойтуу"</string>
+    <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Терезени кичирейтүү"</string>
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Киргизүү"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Кийинки тилге которулуу"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Мурунку тилге которулуу"</string>
@@ -1220,8 +1227,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> символдон ашпашы керек"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Курама номери"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Курама номери алмашуу буферине көчүрүлдү."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"алмашуу буферине көчүрүңүз."</string>
     <string name="basic_status" msgid="2315371112182658176">"Ачык сүйлөшүү"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Сүйлөшүүлөр виджеттери"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Сүйлөшүүнү башкы экранга кошуу үчүн таптап коюңуз"</string>
@@ -1357,8 +1363,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Жогорку дааналык үчүн телефондун арткы камерасын колдонуңуз"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ачылып турган бүктөлмө түзмөк"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Оодарылып жаткан бүктөлмө түзмөк"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Маңдайкы экран күйгүзүлдү"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"бүктөлгөн"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ачылган"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1423,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Атайын мүмкүнчүлүктөр"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Ыкчам баскычтар"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Ыкчам баскычтарды ыңгайлаштыруу"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Ыкчам баскычты дайындоо үчүн баскычты басыңыз"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Ыкчам баскыч өчүрүлсүнбү?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Ыкчам баскычты дайындоо үчүн баскычты басыңыз"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ушуну менен жеке ыкчам баскычыңыз биротоло өчүрүлөт."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Ыкчам баскычтарды издөө"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Эч нерсе табылган жок"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Жыйыштыруу сүрөтчөсү"</string>
@@ -1428,12 +1435,17 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Бүттү"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Жайып көрсөтүү сүрөтчөсү"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"же"</string>
+    <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"кошуу"</string>
+    <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"жантык сызык"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Cүйрөө маркери"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Баскычтоп параметрлери"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Ыкчам баскычты тууралоо"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Өчүрүү"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancel"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Баскычты басыңыз"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Ачкыч айкалышы колдонулууда. Башка ачкычты байкап көрүңүз."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ачкычтардын айкалышы колдонулууда. Башка ачкычты байкап көрүңүз."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Ыкчам баскычты коюу мүмкүн эмес."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Керектүү нерселерге баскычтоп аркылуу өтүү"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Ыкчам баскычтар тууралуу билип алыңыз"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Керектүү жерге сенсордук такта аркылуу өтөсүз"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 3a8c682..1cfd68e 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"ແຕະເພື່ອສະຫຼັບ ຫຼື ແບ່ງປັນສຽງ"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ຮອງຮັບການແບ່ງປັນສຽງ"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ວິດເຈັດໃນໜ້າຈໍລັອກ"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ເພື່ອເປີດແອັບໂດຍໃຊ້ວິດເຈັດ, ທ່ານຈະຕ້ອງຢັ້ງຢືນວ່າແມ່ນທ່ານ. ນອກຈາກນັ້ນ, ກະລຸນາຮັບຊາບວ່າທຸກຄົນສາມາດເບິ່ງຂໍ້ມູນດັ່ງກ່າວໄດ້, ເຖິງແມ່ນວ່າແທັບເລັດຂອງທ່ານຈະລັອກຢູ່ກໍຕາມ. ວິດເຈັດບາງຢ່າງອາດບໍ່ໄດ້ມີໄວ້ສຳລັບໜ້າຈໍລັອກຂອງທ່ານ ແລະ ອາດບໍ່ປອດໄພທີ່ຈະເພີ່ມໃສ່ບ່ອນນີ້."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ເຂົ້າໃຈແລ້ວ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ວິດເຈັດ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"ເພື່ອເພີ່ມວິດເຈັດຢູ່ໜ້າຈໍລັອກເປັນທາງລັດ, ໃຫ້ແນ່ໃຈວ່າມັນຖືກເປີດການນຳໃຊ້ໃນການຕັ້ງຄ່າ."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"ສະແດງຢູ່ເທິງສຸດຂອງການແຈ້ງເຕືອນການສົນທະນາ ແລະ ເປັນຮູບໂປຣໄຟລ໌ຢູ່ໜ້າຈໍລັອກ, ປາກົດເປັນຟອງ, ສະແດງໃນໂໝດຫ້າມລົບກວນໄດ້"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"ສຳຄັນ"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ່ຮອງຮັບຄຸນສົມບັດການສົນທະນາ"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ໃຫ້ຄຳຕິຊົມເປັນຊຸດ"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"ບໍ່ສາມາດແກ້ໄຂການແຈ້ງເຕືອນເຫຼົ່ານີ້ໄດ້."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"ບໍ່ສາມາດແກ້ໄຂການແຈ້ງເຕືອນການໂທໄດ້."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"ບໍ່ສາມາດຕັ້ງຄ່າກຸ່ມການແຈ້ງເຕືອນນີ້ຢູ່ບ່ອນນີ້ໄດ້"</string>
@@ -872,6 +875,10 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ສະຫຼັບໄປໃຊ້ແອັບຢູ່ຊ້າຍ ຫຼື ທາງເທິງໃນຂະນະທີ່ໃຊ້ແບ່ງໜ້າຈໍ"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"ໃນລະຫວ່າງແບ່ງໜ້າຈໍ: ໃຫ້ປ່ຽນຈາກແອັບໜຶ່ງເປັນອີກແອັບໜຶ່ງ"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ຍ້າຍໜ້າຈໍທີ່ເປີດຢູ່ໄປມາລະຫວ່າງຈໍສະແດງຜົນຕ່າງໆ"</string>
+    <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"ຍ້າຍໜ້າຈໍໄປທາງຊ້າຍ"</string>
+    <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"ຍ້າຍໜ້າຈໍໄປທາງຂວາ"</string>
+    <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"ຂະຫຍາຍໜ້າຈໍຂຶ້ນ"</string>
+    <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"ຫຍໍ້ໜ້າຈໍລົງ"</string>
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ການປ້ອນຂໍ້ມູນ"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"ສະຫຼັບເປັນພາສາຖັດໄປ"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"ສະຫຼັບເປັນພາສາກ່ອນໜ້າ"</string>
@@ -1220,8 +1227,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"ໃຊ້ໜ້ອຍກວ່າ <xliff:g id="LENGTH">%1$d</xliff:g> ຕົວອັກສອນ"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"ໝາຍເລກສ້າງ"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"ສຳເນົາໝາຍເລກສ້າງໄປໃສ່ຄລິບບອດແລ້ວ."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ສຳເນົາໄປໃສ່ຄລິບບອດ."</string>
     <string name="basic_status" msgid="2315371112182658176">"ເປີດການສົນທະນາ"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"ວິດເຈັດການສົນທະນາ"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"ແຕະໃສ່ການສົນທະນາໃດໜຶ່ງເພື່ອເພີ່ມມັນໃສ່ໂຮມສະກຣີນຂອງທ່ານ"</string>
@@ -1357,8 +1363,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ເພື່ອຄວາມລະອຽດທີ່ສູງຂຶ້ນ, ໃຫ້ປີ້ນໂທລະສັບ"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ອຸປະກອນທີ່ພັບໄດ້ກຳລັງກາງອອກ"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ອຸປະກອນທີ່ພັກໄດ້ກຳລັງປີ້ນໄປມາ"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ເປີດໜ້າຈໍດ້ານໜ້າແລ້ວ"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ພັບແລ້ວ"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ກາງອອກແລ້ວ"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1423,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ການຊ່ວຍເຂົ້າເຖິງ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"ຄີລັດ"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"ປັບແຕ່ງຄີລັດ"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ກົດປຸ່ມເພື່ອກໍານົດທາງລັດ"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ລຶບທາງລັດອອກບໍ?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ກົດປຸ່ມເພື່ອກຳນົດທາງລັດ"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ການດຳເນີນການນີ້ຈະລຶບທາງລັດທີ່ກຳນົດເອງຂອງທ່ານຢ່າງຖາວອນ."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ທາງລັດການຊອກຫາ"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ບໍ່ມີຜົນການຊອກຫາ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ໄອຄອນຫຍໍ້ລົງ"</string>
@@ -1428,12 +1435,17 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"ແລ້ວໆ"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ໄອຄອນຂະຫຍາຍ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ຫຼື"</string>
+    <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"ບວກ"</string>
+    <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"ເຄື່ອງໝາຍທັບອຽງໄປໜ້າ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ບ່ອນຈັບລາກ"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"ການຕັ້ງຄ່າແປ້ນພິມ"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ຕັ້ງທາງລັດ"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ລຶບອອກ"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ຍົກເລີກ"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"ກົດປຸ່ມ"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"ນໍາໃຊ້ປຸ່ມປະສົມຢູ່ແລ້ວ. ໃຫ້ລອງປຸ່ມອື່ນ."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ນໍາໃຊ້ປຸ່ມປະສົມຢູ່ແລ້ວ. ໃຫ້ລອງປຸ່ມອື່ນ."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ຕັ້ງທາງລັດບໍ່ໄດ້."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ນຳທາງໂດຍໃຊ້ແປ້ນພິມຂອງທ່ານ"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"ສຶກສາຄີລັດ"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ນຳທາງໂດຍໃຊ້ແຜ່ນສຳຜັດຂອງທ່ານ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index ae85ed2..3c81e37 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"„Bluetooth“ naudojimas"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Prisijungta"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Garso įrašų bendrinimas"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Palieskite, jei norite perjungti arba bendrinti garsą"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Palaikomas garso įrašų bendrinimas"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Išsaugota"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"atjungti"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"suaktyvinti"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Užrakinimo ekrano valdikliai"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Kad galėtumėte atidaryti programą naudodami valdiklį, turėsite patvirtinti savo tapatybę. Be to, atminkite, kad bet kas gali peržiūrėti valdiklius net tada, kai planšetinis kompiuteris užrakintas. Kai kurie valdikliai gali būti neskirti jūsų užrakinimo ekranui ir gali būti nesaugu juos čia pridėti."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Supratau"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Valdikliai"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Jei norite pridėti valdiklių šaukinį užrakinimo ekrane, įsitikinkite, kad tai įgalinta nustatymuose."</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>
@@ -674,7 +676,7 @@
     <string name="screen_pinning_exit" msgid="4553787518387346893">"Programa atsegta"</string>
     <string name="stream_voice_call" msgid="7468348170702375660">"Skambutis"</string>
     <string name="stream_system" msgid="7663148785370565134">"Sistema"</string>
-    <string name="stream_ring" msgid="7550670036738697526">"Skambutis"</string>
+    <string name="stream_ring" msgid="7550670036738697526">"Skambėjimas"</string>
     <string name="stream_music" msgid="2188224742361847580">"Medija"</string>
     <string name="stream_alarm" msgid="16058075093011694">"Signalas"</string>
     <string name="stream_notification" msgid="7930294049046243939">"Pranešimas"</string>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Rodoma pokalbių pranešimų viršuje ir kaip profilio nuotrauka užrakinimo ekrane, burbule, pertraukia netrukdymo režimą"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritetiniai"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ nepalaiko pokalbių funkcijų"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Pateikti atsiliepimą apie rinkinį"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Šių pranešimų keisti negalima."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Skambučių pranešimų keisti negalima."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Šios grupės pranešimai čia nekonfigūruojami"</string>
@@ -872,6 +875,10 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Perjunkite į programą kairėje arba viršuje išskaidyto ekrano režimu"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Išskaidyto ekrano režimu: pakeisti iš vienos programos į kitą"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Aktyvaus lango perkėlimas iš vieno ekrano į kitą"</string>
+    <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Perkelti langą į kairę"</string>
+    <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Perkelti langą į dešinę"</string>
+    <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Padidinti langą"</string>
+    <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Sumažinti langą"</string>
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Įvestis"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Perjungti į kitą kalbą"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Perjungti į ankstesnę kalbą"</string>
@@ -1220,8 +1227,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Naudokite daugiausia <xliff:g id="LENGTH">%1$d</xliff:g> simb."</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Versijos numeris"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Versijos numeris nukopijuotas į iškarpinę."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopijuoti į iškarpinę"</string>
     <string name="basic_status" msgid="2315371112182658176">"Atidaryti pokalbį"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Pokalbio valdikliai"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Palieskite pokalbį, kad pridėtumėte jį prie pagrindinio ekrano"</string>
@@ -1357,8 +1363,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Kad raiška būtų geresnė, apverskite telefoną"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Lankstomasis įrenginys išlankstomas"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Lankstomasis įrenginys apverčiamas"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Priekinis ekranas įjungtas"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"sulenkta"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"nesulenkta"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1423,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pritaikomumas"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Spartieji klavišai"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Sparčiųjų klavišų tinkinimas"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Paspauskite klavišą, kad priskirtumėte spartųjį klavišą"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Pašalinti spartųjį klavišą?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Paspauskite klavišą, kad priskirtumėte spartųjį klavišą"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Bus visam laikui ištrintas tinkintas spartusis klavišas."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Ieškoti sparčiųjų klavišų"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nėra jokių paieškos rezultatų"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Sutraukimo piktograma"</string>
@@ -1428,12 +1435,17 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Atlikta"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Išskleidimo piktograma"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"arba"</string>
+    <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"pliusas"</string>
+    <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"dešininis pasvirasis brūkšnys"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vilkimo rankenėlė"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Klaviatūros nustatymai"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Nustatyti spartųjį klavišą"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Pašalinti"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Atšaukti"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Paspauskite klavišą"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Klavišų derinys jau naudojamas. Bandykite naudoti kitą klavišą."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Klavišų derinys jau naudojamas. Bandykite naudoti kitą klavišą."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Sparčiojo klavišo nustatyti negalima."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naršykite naudodamiesi klaviatūra"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Sužinokite apie sparčiuosius klavišus"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naršykite naudodamiesi jutikline dalimi"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index b20ab36..5b553d9 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Izmantot Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Savienojums izveidots"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio kopīgošana"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Pieskarieties, lai pārslēgtu vai kopīgotu audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Atbalsta audio kopīgošanu"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saglabāta"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"atvienot"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizēt"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Bloķēšanas ekrāna logrīki"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Lai atvērtu lietotni, izmantojot logrīku, jums būs jāapstiprina sava identitāte. Turklāt ņemiet vērā, ka ikviens var skatīt logrīkus, pat ja planšetdators ir bloķēts. Iespējams, daži logrīki nav paredzēti izmantošanai bloķēšanas ekrānā, un var nebūt droši tos šeit pievienot."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Labi"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Logrīki"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Lai varētu pievienot funkciju “Logrīki bloķēšanas ekrānā” kā saīsni, iestatījumos noteikti iespējojiet šo funkciju."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Parādās sarunu paziņojumu augšdaļā un kā profila attēls bloķēšanas ekrānā, arī kā burbulis, pārtrauc režīmu “Netraucēt”."</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritārs"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"Lietotnē <xliff:g id="APP_NAME">%1$s</xliff:g> netiek atbalstītas sarunu funkcijas."</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Sniegt atsauksmes par paziņojumu grupu"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Šos paziņojumus nevar modificēt."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Paziņojumus par zvaniem nevar modificēt."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Šeit nevar konfigurēt šo paziņojumu grupu."</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Pāriet uz lietotni pa kreisi/augšā, kamēr izmantojat sadalīto ekrānu."</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Ekrāna sadalīšanas režīmā: pārvietot lietotni no viena ekrāna uz otru"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Pārvietot aktīvo logu starp displejiem"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Ievade"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Pārslēgt uz nākamo valodu"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Pārslēgt uz iepriekšējo valodu"</string>
@@ -1218,10 +1229,9 @@
     <string name="media_output_broadcast_last_update_error" msgid="5484328807296895491">"Nevar saglabāt."</string>
     <string name="media_output_broadcast_code_hint_no_less_than_min" msgid="4663836092607696185">"Izmantojiet vismaz 4 rakstzīmes"</string>
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Izmantojiet mazāk nekā <xliff:g id="LENGTH">%1$d</xliff:g> rakstzīmes."</string>
-    <string name="build_number_clip_data_label" msgid="3623176728412560914">"Versijas numurs"</string>
+    <string name="build_number_clip_data_label" msgid="3623176728412560914">"Būvējuma numurs"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Versijas numurs ir kopēts starpliktuvē."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopēt starpliktuvē."</string>
     <string name="basic_status" msgid="2315371112182658176">"Atvērt sarunu"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Sarunu logrīki"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Pieskarieties kādai sarunai, lai pievienotu to savam sākuma ekrānam."</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Lai izmantotu augstāku izšķirtspēju, apvērsiet tālruni"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Salokāma ierīce tiek atlocīta"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Salokāma ierīce tiek apgriezta"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Priekšējais ekrāns ir ieslēgts"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"aizvērta"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"atvērta"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pieejamība"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Īsinājumtaustiņi"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Īsinājumtaustiņu pielāgošana"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Lai piešķirtu īsinājumtaustiņu, nospiediet taustiņu"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Vai noņemt saīsni?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Lai piešķirtu īsinājumtaustiņu, nospiediet taustiņu"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Veicot šo darbību, jūsu pielāgotā saīsne tiks neatgriezeniski izdzēsta."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Meklēt saīsnes"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nav meklēšanas rezultātu"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Sakļaušanas ikona"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Gatavs"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Izvēršanas ikona"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"vai"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vilkšanas turis"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Tastatūras iestatījumi"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Iestatīt īsinājumtaustiņu"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Noņemt"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Atcelt"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Nospiediet taustiņu"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Taustiņu kombinācija jau tiek izmantota. Izmēģiniet citu taustiņu."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Taustiņu kombinācija jau tiek izmantota. Izmēģiniet citu taustiņu."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Nevar iestatīt saīsni."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Pārvietošanās, izmantojot tastatūru"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Uzziniet par īsinājumtaustiņiem."</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Pārvietošanās, izmantojot skārienpaliktni"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index f8bfcf9..32ee791 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"Допрете за да префрлите или споделите аудио"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Поддржува споделување аудио"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Виџети на заклучен екран"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"За да отворите апликација со помош на виџет, ќе треба да потврдите дека сте вие. Покрај тоа, имајте предвид дека секој може да ги гледа виџетите, дури и кога вашиот таблет е заклучен. Некои виџети можеби не се наменети за вашиот заклучен екран, па можеби не е безбедно да се додадат овде."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Сфатив"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Виџети"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"За да ја додадете „Виџети на заклучен екран“ како кратенка, проверете дали е овозможена во „Поставки“."</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>
@@ -698,7 +700,7 @@
     <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Контрола на шум"</string>
     <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_fixed" msgid="3136080137827746046">"Фиксирано"</string>
     <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Следење на главата"</string>
     <string name="volume_ringer_change" msgid="3574969197796055532">"Допрете за да го промените режимот на ѕвончето"</string>
     <string name="volume_ringer_mode" msgid="6867838048430807128">"режим на ѕвонче"</string>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Се прикажува најгоре во известувањата за разговор и како профилна слика на заклучен екран, се појавува како балонче, го прекинува „Не вознемирувај“"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Приоритетно"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> не поддржува функции за разговор"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Испрати повратни информации за пакет"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Овие известувања не може да се изменат"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Известувањата за повици не може да се изменат."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Оваа група известувања не може да се конфигурира тука"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Префрлете се на апликацијата лево или горе при користењето поделен екран"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"При поделен екран: префрлете ги аплик. од едната на другата страна"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Движете го активниот прозорец меѓу екраните"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Внесување"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Префрлете на следниот јазик"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Префрлете на претходниот јазик"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Употребете помалку од <xliff:g id="LENGTH">%1$d</xliff:g> знаци"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Број на верзија"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Бројот на верзијата е копиран во привремената меморија."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"копирање во привремената меморија."</string>
     <string name="basic_status" msgid="2315371112182658176">"Започни разговор"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Виџети за разговор"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Допрете разговор за да го додадете на почетниот екран"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Отворете го телефонот за да добиете повисока резолуција"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Преклопувачки уред се отклопува"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Преклопувачки уред се врти"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Предниот екран е вклучен"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"затворен"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"отворен"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Пристапност"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Кратенки од тастатура"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Приспособете ги кратенките од тастатурата"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Притиснете го копчето за да доделите кратенка"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Да се отстрани кратенката?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Притиснете го копчето за да доделите кратенка"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ова ќе ја избрише вашата приспособена кратенка трајно."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Пребарувајте кратенки"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Нема резултати од пребарување"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за собирање"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Готово"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за проширување"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Рачка за влечење"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Поставки за тастатурата"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Поставете кратенка"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Отстрани"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Откажи"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Притиснете го копчето"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Комбинацијата на копчиња веќе се користи. Обидете се со друго копче."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Комбинацијата на копчиња веќе се користи. Обидете се со друго копче."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Кратенката не може да се постави."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Движете се со користење на тастатурата"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Научете ги кратенките од тастатурата"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Движете се со користење на допирната подлога"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 54364c8..ed66531 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"ഓഡിയോ മാറാനോ പങ്കിടാനോ ടാപ്പ് ചെയ്യുക"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ഓഡിയോ പങ്കിടൽ പിന്തുണയ്ക്കുന്നു"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ലോക്ക് സ്‌ക്രീൻ വിജറ്റുകൾ"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"വിജറ്റ് ഉപയോഗിച്ച് ഒരു ആപ്പ് തുറക്കാൻ, ഇത് നിങ്ങൾ തന്നെയാണെന്ന് പരിശോധിച്ചുറപ്പിക്കേണ്ടതുണ്ട്. നിങ്ങളുടെ ടാബ്‌ലെറ്റ് ലോക്കായിരിക്കുമ്പോഴും എല്ലാവർക്കും അത് കാണാനാകുമെന്നതും ഓർക്കുക. ചില വിജറ്റുകൾ നിങ്ങളുടെ ലോക്ക് സ്‌ക്രീനിന് ഉള്ളതായിരിക്കില്ല, അവ ഇവിടെ ചേർക്കുന്നത് സുരക്ഷിതവുമായിരിക്കില്ല."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"മനസ്സിലായി"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"വിജറ്റുകൾ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"കുറുക്കുവഴിയായി ലോക്ക് സ്ക്രീനിൽ വിജറ്റുകൾ ചേർക്കാൻ, ക്രമീകരണത്തിൽ അത് പ്രവർത്തനക്ഷമമാക്കിയിട്ടുണ്ടെന്ന് ഉറപ്പാക്കുക."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"സംഭാഷണ അറിയിപ്പുകളുടെ മുകളിലും സ്ക്രീൻ ലോക്കായിരിക്കുമ്പോൾ ഒരു പ്രൊഫൈൽ ചിത്രമായും ബബിൾ രൂപത്തിൽ ദൃശ്യമാകുന്നു, ശല്യപ്പെടുത്തരുത് മോഡ് തടസ്സപ്പെടുത്തുന്നു"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"മുൻഗണന"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"സംഭാഷണ ഫീച്ചറുകളെ <xliff:g id="APP_NAME">%1$s</xliff:g> പിന്തുണയ്‌ക്കുന്നില്ല"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ബണ്ടിൽ ഫീഡ്ബാക്ക് നൽകുക"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"ഈ അറിയിപ്പുകൾ പരിഷ്ക്കരിക്കാനാവില്ല."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"കോൾ അറിയിപ്പുകൾ പരിഷ്‌കരിക്കാനാകുന്നില്ല."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"അറിയിപ്പുകളുടെ ഈ ഗ്രൂപ്പ് ഇവിടെ കോണ്‍ഫിഗര്‍ ചെയ്യാൻ കഴിയില്ല"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"സ്ക്രീൻ വിഭജന മോഡ് ഉപയോഗിക്കുമ്പോൾ ഇടതുവശത്തെ/മുകളിലെ ആപ്പിലേക്ക് മാറൂ"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"സ്‌ക്രീൻ വിഭജന മോഡിൽ: ഒരു ആപ്പിൽ നിന്ന് മറ്റൊന്നിലേക്ക് മാറുക"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"സജീവ വിൻഡോകൾ ഡിസ്‌പ്ലേകൾക്ക് ഇടയിൽ നീക്കുക"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ഇൻപുട്ട്"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"അടുത്ത ഭാഷയിലേക്ക് മാറുക"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"മുമ്പത്തെ ഭാഷയിലേക്ക് മാറുക"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g>-ൽ കുറവ് പ്രതീകങ്ങൾ ഉപയോഗിക്കുക"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"ബിൽഡ് നമ്പർ"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"ക്ലിപ്പ്ബോർഡിലേക്ക് ബിൽഡ് നമ്പർ പകർത്തി."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തുക."</string>
     <string name="basic_status" msgid="2315371112182658176">"സംഭാഷണം തുറക്കുക"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"സംഭാഷണ വിജറ്റുകൾ"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"നിങ്ങളുടെ ഹോം സ്‌ക്രീനിൽ ചേർക്കാൻ സംഭാഷണത്തിൽ ടാപ്പ് ചെയ്യുക"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ഉയർന്ന റെസല്യൂഷന്, ഫോൺ ഫ്ലിപ്പ് ചെയ്യുക"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ഫോൾഡ് ചെയ്യാവുന്ന ഉപകരണം അൺഫോൾഡ് ആകുന്നു"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ഫോൾഡ് ചെയ്യാവുന്ന ഉപകരണം, കറങ്ങുന്ന വിധത്തിൽ ഫ്ലിപ്പ് ആകുന്നു"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ഫ്രണ്ട് സ്ക്രീൻ ഓണാക്കി"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ഫോൾഡ് ചെയ്തത്"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"അൺഫോൾഡ് ചെയ്തത്"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ഉപയോഗസഹായി"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"കീബോഡ് കുറുക്കുവഴികൾ"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"കീബോർഡ് കുറുക്കുവഴികൾ ഇഷ്ടാനുസൃതമാക്കുക"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"കുറുക്കുവഴി അസൈൻ ചെയ്യാൻ കീ അമർത്തുക"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"കുറുക്കുവഴി നീക്കം ചെയ്യണോ?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"കുറുക്കുവഴി അസൈൻ ചെയ്യാൻ കീ അമർത്തുക"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ഇത് നിങ്ങളുടെ ഇഷ്‌ടാനുസൃത കുറുക്കുവഴി ശാശ്വതമായി ഇല്ലാതാക്കും."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"തിരയൽ കുറുക്കുവഴികൾ"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"തിരയൽ ഫലങ്ങളൊന്നുമില്ല"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ചുരുക്കൽ ഐക്കൺ"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"പൂർത്തിയായി"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"വികസിപ്പിക്കൽ ഐക്കൺ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"അല്ലെങ്കിൽ"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"വലിച്ചിടുന്നതിനുള്ള ഹാൻഡിൽ"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"കീബോർഡ് ക്രമീകരണം"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"കുറുക്കുവഴി സജ്ജീകരിക്കുക"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"നീക്കം ചെയ്യുക"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"റദ്ദാക്കുക"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"കീ അമർത്തുക"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"കീ കോമ്പിനേഷൻ ഇതിനകം ഉപയോഗത്തിലുണ്ട്. മറ്റൊരു കീ പരീക്ഷിക്കുക."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"കീ കോമ്പിനേഷൻ ഇതിനകം ഉപയോഗത്തിലുണ്ട്. മറ്റൊരു കീ പരീക്ഷിക്കുക."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"കുറുക്കുവഴി സജ്ജീകരിക്കാനാകില്ല."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"നിങ്ങളുടെ കീബോർഡ് ഉപയോഗിച്ച് നാവിഗേറ്റ് ചെയ്യുക"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"കീബോർഡ് കുറുക്കുവഴികൾ മനസ്സിലാക്കുക"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"നിങ്ങളുടെ ടച്ച്‌പാഡ് ഉപയോഗിച്ച് നാവിഗേറ്റ് ചെയ്യുക"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 0e2dc23..f2863d0 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"Аудиог сэлгэх эсвэл хуваалцахын тулд товшино уу"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Аудио хуваалцахыг дэмждэг"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Түгжээтэй дэлгэцийн виджет"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Виджет ашиглан аппыг нээхийн тулд та өөрийгөө мөн болохыг баталгаажуулах шаардлагатай болно. Мөн таны таблет түгжээтэй байсан ч тэдгээрийг дурын хүн үзэж болохыг санаарай. Зарим виджет таны түгжээтэй дэлгэцэд зориулагдаагүй байж магадгүй ба энд нэмэхэд аюултай байж болзошгүй."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Ойлголоо"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Виджет"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Түгжээтэй дэлгэц дээр товчлол байдлаар виджет нэмэхийн тулд тохиргоонд виджетийг идэвхжүүлсэн эсэхийг нягтална уу."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Харилцан ярианы мэдэгдлийн дээд талд болон түгжигдсэн дэлгэц дээр профайл зураг байдлаар харуулах бөгөөд бөмбөлөг хэлбэрээр харагдана. Бүү саад бол горимыг тасалдуулна"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Чухал"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> нь харилцан ярианы онцлогуудыг дэмждэггүй"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Багц санал хүсэлт өгөх"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Эдгээр мэдэгдлийг өөрчлөх боломжгүй."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Дуудлагын мэдэгдлийг өөрчлөх боломжгүй."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Энэ бүлэг мэдэгдлийг энд тохируулах боломжгүй байна"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Дэлгэц хуваахыг ашиглаж байхдаа зүүн талд эсвэл дээр байх апп руу сэлгэ"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Дэлгэц хуваах үеэр: аппыг нэгээс нөгөөгөөр солих"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Идэвхтэй цонхыг дэлгэц хооронд зөөх"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Оролт"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Дараагийн хэл рүү сэлгэх"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Өмнөх хэл рүү сэлгэх"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g>-с цөөн тэмдэгт ашиглана уу"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Хийцийн дугаар"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Хийцийн дугаарыг түр санах ойд хуулсан."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"түр санах ойд хуулна уу."</string>
     <string name="basic_status" msgid="2315371112182658176">"Харилцан яриаг нээх"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Харилцан ярианы виджетүүд"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Үндсэн нүүрэндээ нэмэх харилцан яриаг товшино уу"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Илүү өндөр нягтрал авах бол утсыг хөнтөрнө үү"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Эвхэгддэг төхөөрөмжийг дэлгэж байна"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Эвхэгддэг төхөөрөмжийг хөнтөрч байна"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Нүүрэн талын дэлгэцийг асаасан"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"эвхсэн"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"дэлгэсэн"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Хандалт"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Товчлуурын шууд холбоос"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Товчлуурын шууд холбоосыг өөрчлөх"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Товчлол оноохын тулд товч дарна уу"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Товчлолыг хасах уу?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Товчлол оноохын тулд товч дарна уу"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Энэ нь таны захиалгат товчлолыг бүрмөсөн устгана."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Товчлолууд хайх"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ямар ч хайлтын илэрц байхгүй"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Хураах дүрс тэмдэг"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Болсон"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Дэлгэх дүрс тэмдэг"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"эсвэл"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Чирэх бариул"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Гарын тохиргоо"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Товчлол тохируулах"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Хасах"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Цуцлах"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Товч дарна уу"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Товчийн хослолыг аль хэдийн ашиглаж байна. Өөр товч туршиж үзнэ үү."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Товчийн хослолыг аль хэдийн ашиглаж байна. Өөр товч туршиж үзнэ үү."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Товчлол тохируулах боломжгүй."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Гараа ашиглан шилжих"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Товчлуурын шууд холбоосыг мэдэж аваарай"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Мэдрэгч самбараа ашиглан шилжээрэй"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index cd90622..35ee42f 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"व्हिडिओवर स्विच करण्यासाठी टॅप करा किंवा शेअर करा"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ऑडिओ शेअरिंगला सपोर्ट करते"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"लॉक स्‍क्रीन विजेट"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"विजेट वापरून अ‍ॅप उघडण्यासाठी, तुम्हाला हे तुम्हीच असल्याची पडताळणी करावी लागेल. तसेच, लक्षात ठेवा, तुमचा टॅबलेट लॉक असतानादेखील कोणीही ती पाहू शकते. काही विजेट कदाचित तुमच्या लॉक स्‍क्रीनसाठी नाहीत आणि ती इथे जोडणे असुरक्षित असू शकते."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"समजले"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"विजेट"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"विजेट शॉर्टकट म्हणून लॉक स्‍क्रीनवर जोडण्यासाठी, सेटिंग्जमध्ये ती सुरू असल्याची खात्री करा."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"संभाषण सूचनांच्या वरती आणि लॉक स्क्रीनवरील प्रोफाइल फोटो म्हणून दिसते, बबल म्हणून दिसते, व्यत्यय आणू नका यामध्ये अडथळा आणते"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"प्राधान्य"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> हे संभाषण वैशिष्ट्यांना सपोर्ट करत नाही"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"बंडलसंबंधित फीडबॅक द्या"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"या सूचनांमध्ये सुधारणा केली जाऊ शकत नाही."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"कॉलशी संबंधित सूचनांमध्ये फेरबदल केला जाऊ शकत नाही."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"या सूचनांचा संच येथे कॉन्फिगर केला जाऊ शकत नाही"</string>
@@ -872,6 +875,10 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"स्प्लिट स्क्रीन वापरताना डावीकडील किंवा वरील अ‍ॅपवर स्विच करा"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"स्प्लिट स्क्रीनदरम्यान: एक अ‍ॅप दुसऱ्या अ‍ॅपने बदला"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ॲक्टिव्ह विंडो डिस्प्लेदरम्यान हलवा"</string>
+    <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"विंडो डावीकडे हलवा"</string>
+    <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"विंडो उजवीकडे हलवा"</string>
+    <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"विंडो मोठी करा"</string>
+    <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"विंडो लहान करा"</string>
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"इनपुट"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"पुढील भाषेवर स्विच करा"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"मागील भाषेवर स्विच करा"</string>
@@ -1220,8 +1227,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> वर्णांपेक्षा कमी वर्ण वापरा"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नंबर"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"बिल्ड नंबर क्लिपबोर्डवर कॉपी केला."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"क्लिपबोर्डवर कॉपी करा."</string>
     <string name="basic_status" msgid="2315371112182658176">"संभाषण उघडा"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"संभाषण विजेट"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"तुमच्या होम स्क्रीन वर संभाषण जोडण्यासाठी त्यावर टॅप करा"</string>
@@ -1357,8 +1363,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"उच्च रेझोल्यूशनसाठी, फोन फ्लिप करा"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फोल्ड करता येण्यासारखे डिव्हाइस अनफोल्ड केले जात आहे"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फोल्ड करता येण्यासारखे डिव्हाइस आजूबाजूला फ्लिप केले जात आहे"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"पुढील स्क्रीन सुरू केलेली आहे"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"फोल्ड केलेले"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"फोल्ड न केलेले"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1423,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"अ‍ॅक्सेसिबिलिटी"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"कीबोर्ड शॉर्टकट"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"कीबोर्ड शॉर्टकट कस्टमाइझ करा"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"शॉर्टकट असाइन करण्यासाठी की प्रेस करा"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"शॉर्टकट काढून टाकायचा आहे का?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"शॉर्टकट असाइन करण्यासाठी की प्रेस करा"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"यामुळे तुमचा कस्टम शॉर्टकट कायमचा हटवला जाईल."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"शोधण्यासाठी शॉर्टकट"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"कोणतेही शोध परिणाम नाहीत"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"कोलॅप्स करा आयकन"</string>
@@ -1428,12 +1435,17 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"पूर्ण झाले"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"विस्तार करा आयकन"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"किंवा"</string>
+    <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"अधिक"</string>
+    <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"फॉरवर्ड स्लॅश"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ड्रॅग हॅंडल"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"कीबोर्ड सेटिंग्ज"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"शॉर्टकट सेट करा"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"काढून टाका"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"रद्द करा"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"की प्रेस करा"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"की कॉम्बिनेशन आधीपासून वापरले जात आहे. दुसरी की वापरून पहा."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"की कॉम्बिनेशन आधीपासून वापरले जात आहे. दुसरी की वापरून पहा."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"शॉर्टकट सेट करू शकत नाही."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"तुमचा कीबोर्ड वापरून नेव्हिगेट करा"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"कीबोर्ड शॉर्टकट जाणून घ्या"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"तुमचा टचपॅड वापरून नेव्हिगेट करा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index c108fc4..cfb95a2 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gunakan Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Disambungkan"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Perkongsian Audio"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Ketik untuk menukar atau berkongsi audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Menyokong perkongsian audio"</string>
     <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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widget skrin kunci"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Untuk membuka apl menggunakan widget, anda perlu mengesahkan identiti anda. Selain itu, perlu diingat bahawa sesiapa sahaja boleh melihat widget tersebut, walaupun semasa tablet anda dikunci. Sesetengah widget mungkin tidak sesuai untuk skrin kunci anda dan mungkin tidak selamat untuk ditambahkan di sini."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widget"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Untuk menambahkan Widget pada skrin kunci sebagai pintasan, pastikan skrin kunci itu didayakan dalam tetapan."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Ditunjukkan di bahagian atas pemberitahuan perbualan dan sebagai gambar profil pada skrin kunci, muncul sebagai gelembung, mengganggu Jangan Ganggu"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Keutamaan"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak menyokong ciri perbualan"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Berikan Maklum Balas Himpunan"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Pemberitahuan ini tidak boleh diubah suai."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Pemberitahuan panggilan tidak boleh diubah suai."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Kumpulan pemberitahuan ini tidak boleh dikonfigurasikan di sini"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Tukar kepada apl di sebelah kiri/atas semasa menggunakan skrin pisah"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Semasa skrin pisah: gantikan apl daripada satu apl kepada apl lain"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Alihkan tetingkap aktif antara paparan"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Beralih kepada bahasa seterusnya"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Beralih kepada bahasa sebelumnya"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Gunakan kurang daripada <xliff:g id="LENGTH">%1$d</xliff:g> aksara"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Nombor binaan"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Nombor binaan disalin ke papan keratan."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"salin kepada papan keratan."</string>
     <string name="basic_status" msgid="2315371112182658176">"Buka perbualan"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widget perbualan"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Ketik perbualan untuk menambahkan perbualan itu pada skrin Utama anda"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Untuk peleraian lebih tinggi, balikkan telefon"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Peranti boleh lipat dibuka"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Peranti boleh lipat diterbalikkan"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Skrin hadapan dihidupkan"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"terlipat"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"tidak terlipat"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Kebolehaksesan"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Pintasan papan kekunci"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Sesuaikan pintasan papan kekunci"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Tekan kekunci untuk menetapkan pintasan"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Alih keluar pintasan?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Tekan kekunci untuk menetapkan pintasan"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Tindakan ini akan memadamkan pintasan tersuai anda secara kekal."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pintasan carian"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Tiada hasil carian"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Kuncupkan ikon"</string>
@@ -1428,17 +1439,24 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Selesai"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Kembangkan ikon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"atau"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Pemegang seret"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Tetapan Papan Kekunci"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Tetapkan pintasan"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Alih keluar"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Batal"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Tekan kekunci"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Gabungan kekunci sudah digunakan. Cuba kekunci lain."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Gabungan kekunci sudah digunakan. Cuba kekunci lain."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Pintasan tidak boleh ditetapkan."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigasi menggunakan papan kekunci"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Ketahui pintasan papan kekunci"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigasi menggunakan pad sentuh anda"</string>
     <string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"Ketahui gerak isyarat pad sentuh"</string>
-    <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Navigasi menggunakan papan kekunci dan pad sentuh anda"</string>
+    <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Navigasi menggunakan papan kekunci dan pad sentuh"</string>
     <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Ketahui gerak isyarat pad sentuh, pintasan papan kekunci dan pelbagai lagi"</string>
     <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Kembali"</string>
     <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Akses laman utama"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index ce52f4e..8cce371 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"အသံ ပြောင်းရန်/မျှဝေရန် တို့ပါ"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"အော်ဒီယို မျှဝေခြင်း ပံ့ပိုးသည်"</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>
@@ -454,7 +454,7 @@
     <string name="zen_mode_off" msgid="1736604456618147306">"ပိတ်"</string>
     <string name="zen_mode_set_up" msgid="8231201163894922821">"သတ်မှတ်မထားပါ"</string>
     <string name="zen_mode_no_manual_invocation" msgid="1769975741344633672">"ဆက်တင်များတွင် စီမံရန်"</string>
-    <string name="zen_mode_active_modes" msgid="1625850411578488856">"{count,plural, =0{သုံးနေသော မုဒ်မရှိပါ}=1{{mode} ကို သုံးနေသည်}other{မုဒ် # ခု သုံးနေသည်}}"</string>
+    <string name="zen_mode_active_modes" msgid="1625850411578488856">"{count,plural, =0{မုဒ် သုံးမနေပါ}=1{{mode} ကို သုံးနေသည်}other{မုဒ် # ခု သုံးနေသည်}}"</string>
     <string name="zen_priority_introduction" msgid="3159291973383796646">"နှိုးစက်သံ၊ သတိပေးချက်အသံများ၊ ပွဲစဉ်သတိပေးသံများနှင့် သင်ခွင့်ပြုထားသူများထံမှ ဖုန်းခေါ်မှုများမှလွဲ၍ အခြားအသံများနှင့် တုန်ခါမှုများက သင့်ကို အနှောင့်အယှက်ပြုမည် မဟုတ်ပါ။ သို့သော်လည်း သီချင်း၊ ဗီဒီယိုနှင့် ဂိမ်းများအပါအဝင် သင်ကရွေးချယ်ဖွင့်ထားသည့် အရာတိုင်း၏ အသံကိုမူ ကြားနေရဆဲဖြစ်ပါလိမ့်မည်။"</string>
     <string name="zen_alarms_introduction" msgid="3987266042682300470">"နှိုးစက်သံမှလွဲ၍ အခြားအသံများနှင့် တုန်ခါမှုများက သင့်ကို အနှောင့်အယှက်ပြုမည် မဟုတ်ပါ။ သို့သော်လည်း သီချင်း၊ ဗီဒီယိုနှင့် ဂိမ်းများအပါအဝင် သင်ကရွေးချယ်ဖွင့်ထားသည့် အရာတိုင်း၏ အသံကိုမူ ကြားနေရဆဲဖြစ်ပါလိမ့်မည်။"</string>
     <string name="zen_priority_customize_button" msgid="4119213187257195047">"စိတ်ကြိုက် ပြုလုပ်ရန်"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"လော့ခ်မျက်နှာပြင် ဝိဂျက်များ"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ဝိဂျက်သုံး၍ အက်ပ်ဖွင့်ရန်အတွက် သင်ဖြစ်ကြောင်း အတည်ပြုရန်လိုသည်။ ထို့ပြင် သင့်တက်ဘလက် လော့ခ်ချထားချိန်၌ပင် မည်သူမဆို ၎င်းတို့ကို ကြည့်နိုင်ကြောင်း သတိပြုပါ။ ဝိဂျက်အချို့ကို လော့ခ်မျက်နှာပြင်အတွက် ရည်ရွယ်ထားခြင်း မရှိသဖြင့် ဤနေရာတွင် ထည့်ပါက မလုံခြုံနိုင်ပါ။"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"နားလည်ပြီ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ဝိဂျက်များ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"လော့ခ်မျက်နှာပြင်တွင် ဖြတ်လမ်းလင့်ခ်အဖြစ် ‘ဝိဂျက်များ’ ထည့်ရန် ၎င်းကို ဆက်တင်များတွင်ဖွင့်ထားကြောင်း သေချာပါစေ။"</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"စကားဝိုင်း အကြောင်းကြားချက်များ၏ ထိပ်ပိုင်းနှင့် ပရိုဖိုင်ပုံအဖြစ် လော့ခ်မျက်နှာပြင်တွင် ပြသည်။ ပူဖောင်းကွက်အဖြစ် မြင်ရပြီး ‘မနှောင့်ယှက်ရ’ ကို ကြားဖြတ်သည်"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"ဦးစားပေး"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> က စကားဝိုင်းဝန်ဆောင်မှုများကို မပံ့ပိုးပါ"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"အတွဲလိုက် အကြံပြုချက်ပေးရန်"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"ဤအကြောင်းကြားချက်များကို ပြုပြင်၍ မရပါ။"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"ခေါ်ဆိုမှုအကြောင်းကြားချက်များကို ပြင်၍မရပါ။"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"ဤအကြောင်းကြားချက်အုပ်စုကို ဤနေရာတွင် စီစဉ်သတ်မှတ်၍ မရပါ"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းသုံးစဉ် ဘယ် (သို့) အထက်ရှိအက်ပ်သို့ ပြောင်းရန်"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"မျက်နှာပြင် ခွဲ၍ပြသစဉ်- အက်ပ်တစ်ခုကို နောက်တစ်ခုနှင့် အစားထိုးရန်"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"လက်ရှိဝင်းဒိုးကို ပြကွက်များအကြား ရွှေ့ခြင်း"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"စာရိုက်ခြင်း"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"နောက်ဘာသာစကားသို့ ပြောင်းရန်"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"ယခင်ဘာသာစကားသို့ ပြောင်းရန်"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"အက္ခရာ <xliff:g id="LENGTH">%1$d</xliff:g> လုံးအောက် သုံးရန်"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"တည်ဆောက်ပုံအမှတ်"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"တည်ဆောက်မှုနံပါတ်ကို ကလစ်ဘုတ်သို့ မိတ္တူကူးပြီးပါပြီ။"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ကလစ်ဘုတ်သို့ မိတ္တူကူးရန်။"</string>
     <string name="basic_status" msgid="2315371112182658176">"စကားဝိုင်းကို ဖွင့်ရန်"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"စကားဝိုင်း ဝိဂျက်များ"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"စကားဝိုင်းကို သင်၏ ‘ပင်မစာမျက်နှာ’ သို့ထည့်ရန် တို့ပါ"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ပုံရိပ် ပိုမိုပြတ်သားစေရန် ဖုန်းကို တစ်ဖက်သို့ လှန်လိုက်ပါ"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ခေါက်နိုင်သောစက်ကို ဖြန့်လိုက်သည်"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ခေါက်နိုင်သောစက်ကို တစ်ဘက်သို့ လှန်လိုက်သည်"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ရှေ့စခရင် ဖွင့်ထားသည်"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ခေါက်ထားသည်"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ဖြန့်ထားသည်"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"အများသုံးနိုင်မှု"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"လက်ကွက်ဖြတ်လမ်းများ"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"လက်ကွက်ဖြတ်လမ်းများကို စိတ်ကြိုက်လုပ်ခြင်း"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ဖြတ်လမ်းသတ်မှတ်ရန် ကီးကို နှိပ်ပါ"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ဖြတ်လမ်းလင့်ခ် ဖယ်ရှားမလား။"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ဖြတ်လမ်းလင့်ခ်သတ်မှတ်ရန် ကီးကို နှိပ်ပါ"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"၎င်းသည် သင့်စိတ်ကြိုက် ဖြတ်လမ်းလင့်ခ်ကို အပြီးဖျက်ပါမည်။"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ဖြတ်လမ်းများ ရှာရန်"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ရှာဖွေမှုရလဒ် မရှိပါ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"လျှော့ပြရန် သင်္ကေတ"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"ပြီးပြီ"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ပိုပြရန် သင်္ကေတ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"သို့မဟုတ်"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ဖိဆွဲအထိန်း"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"ကီးဘုတ်ဆက်တင်များ"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ဖြတ်လမ်း သတ်မှတ်ရန်"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ဖယ်ရှားရန်"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"မလုပ်တော့"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"ကီးကို နှိပ်ပါ"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"ကီးပေါင်းစပ်ခြင်းကို သုံးနေပြီးဖြစ်သည်။ အခြားကီးကို စမ်းကြည့်ပါ။"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ကီးပေါင်းစပ်ခြင်းကို သုံးနေပြီးဖြစ်သည်။ အခြားကီးကို စမ်းကြည့်ပါ။"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ဖြတ်လမ်းလင့်ခ် သတ်မှတ်၍မရပါ။"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"သင့်ကီးဘုတ်ကိုသုံး၍ လမ်းညွှန်ခြင်း"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"လက်ကွက်ဖြတ်လမ်းများကို လေ့လာပါ"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"သင့်တာ့ချ်ပက်ကိုသုံး၍ လမ်းညွှန်ခြင်း"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index db352a5..c863d19 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bruk Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Tilkoblet"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Lyddeling"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Trykk for å bytte eller dele lyd"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Støtter lyddeling"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Lagret"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"koble fra"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiver"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Låseskjermmoduler"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"For å åpne en app ved hjelp av en modul må du bekrefte at det er deg. Husk også at hvem som helst kan se dem, selv om nettbrettet er låst. Noen moduler er kanskje ikke laget for å være på låseskjermen og kan være utrygge å legge til der."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Greit"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Moduler"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"For å legge til moduler på låseskjermen som en snarvei, sørg for at de er slått på i innstillingene."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Vises øverst på samtalevarsler og som et profilbilde på låseskjermen, vises som en boble, avbryter «Ikke forstyrr»"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritet"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> støtter ikke samtalefunksjoner"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Gi tilbakemelding om pakken"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Disse varslene kan ikke endres."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Anropsvarsler kan ikke endres."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Denne varselgruppen kan ikke konfigureres her"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Bytt til appen til venstre eller over mens du bruker delt skjerm"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"I delt skjerm: Bytt ut en app"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Flytt det aktive vinduet mellom skjermer"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Skrivespråk"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Bytt til neste språk"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Bytt til forrige språk"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Bruk færre enn <xliff:g id="LENGTH">%1$d</xliff:g> tegn"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Delversjonsnummer"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Delversjonsnummeret er kopiert til utklippstavlen."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"Kopier til utklippstavlen."</string>
     <string name="basic_status" msgid="2315371112182658176">"Åpen samtale"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Samtalemoduler"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Trykk på en samtale for å legge den til på startskjermen"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Brett ut telefonen for å få høyere oppløsning"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"En foldbar enhet blir brettet ut"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"En foldbar enhet blir snudd"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Frontskjermen er slått på"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"lagt sammen"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"åpen"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Tilgjengelighet"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Hurtigtaster"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Tilpass hurtigtastene"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Trykk på tasten for å tilordne hurtigtasten"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Vil du fjerne hurtigtasten?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Trykk på en tast for å tilordne hurtigtasten"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Dette fører til at den egendefinerte hurtigtasten slettes permanent."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Snarveier til søk"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ingen søkeresultater"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Skjul-ikon"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Ferdig"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Vis-ikon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Håndtak"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Tastaturinnstillinger"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Angi hurtigtast"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Fjern"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Avbryt"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Trykk på tasten"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Tastekombinasjonen brukes allerede. Prøv en annen tast."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tastekombinasjonen brukes allerede. Prøv en annen tast."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Kan ikke angi snarveien."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviger med tastaturet"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Lær deg hurtigtaster"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviger med styreflaten"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 2c1d727..681e328 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"अडियो बदल्न वा सेयर गर्न ट्याप गर्नुहोस्"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"अडियो सेयर गर्न मिल्छ"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"लक स्क्रिन विजेटहरू"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"विजेट प्रयोग गरी एप खोल्न तपाईंले आफ्नो पहिचान पुष्टि गर्नु पर्ने हुन्छ। साथै, तपाईंको ट्याब्लेट लक भएका बेला पनि सबै जनाले तिनलाई देख्न सक्छन् भन्ने कुरा ख्याल गर्नुहोस्। केही विजेटहरू लक स्क्रिनमा प्रयोग गर्ने उद्देश्यले नबनाइएका हुन सक्छन् र तिनलाई यहाँ हाल्नु सुरक्षित नहुन सक्छ।"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"बुझेँ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"विजेटहरू"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"विजेटहरू लक स्क्रिनमा सर्टकटका रूपमा हाल्न सेटिङमा गई यो सुविधा अन गरिएको छ भन्ने सुनिश्चित गर्नुहोस्।"</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"यो वार्तालापका सूचनाहरूको सिरानमा, बबलका रूपमा र लक स्क्रिनमा प्रोफाइल फोटोका रूपमा देखिन्छ। साथै, यसले गर्दा \'बाधा नपुऱ्याउनुहोस्\' नामक सुविधामा अवरोध आउँछ"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"प्राथमिकता"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> मा वार्तालापसम्बन्धी सुविधा प्रयोग गर्न मिल्दैन"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"बन्डलका बारेमा प्रतिक्रिया दिनुहोस्"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"यी सूचनाहरू परिमार्जन गर्न मिल्दैन।"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"कलसम्बन्धी सूचनाहरू परिमार्जन गर्न मिल्दैन।"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"यहाँबाट सूचनाहरूको यो समूह कन्फिगर गर्न सकिँदैन"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"स्प्लिट स्क्रिन प्रयोग गर्दै गर्दा बायाँ वा माथिको एप चलाउनुहोस्"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"स्प्लिट स्क्रिन प्रयोग गरिएका बेला: एउटा स्क्रिनमा भएको एप अर्कोमा लैजानुहोस्"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"सक्रिय विन्डोलाई एउटा डिस्प्लेबाट सारेर अर्को डिस्प्लेमा लैजानुहोस्"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"इनपुट"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"अर्को भाषा प्रयोग गर्नुहोस्"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"अघिल्लो भाषा प्रयोग गर्नुहोस्"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> वटा भन्दा कम वर्ण प्रयोग गर्नुहोस्"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नम्बर"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"बिल्ड नम्बर कपी गरी क्लिपबोर्डमा सारियो।"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"कपी गरेर क्लिपबोर्डमा पेस्ट गर्नुहोस्।"</string>
     <string name="basic_status" msgid="2315371112182658176">"वार्तालाप खोल्नुहोस्"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"वार्तालापसम्बन्धी विजेटहरू"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"कुनै वार्तालाप होम स्क्रिनमा हाल्न उक्त वार्तालापमा ट्याप गर्नुहोस्"</string>
@@ -1355,10 +1365,9 @@
     <string name="rear_display_unfolded_bottom_sheet_title" msgid="6291111173057304055">"स्क्रिनहरू बदल्ने हो?"</string>
     <string name="rear_display_folded_bottom_sheet_description" msgid="6842767125783222695">"उच्च रिजोल्युसनको सेल्फी खिच्न पछाडिको क्यामेरा प्रयोग गर्नुहोस्"</string>
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"उच्च रिजोल्युसनको सेल्फी खिच्न फोन फ्लिप गर्नुहोस्"</string>
-    <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फोल्ड गर्न मिल्ने डिभाइस अनफोल्ड गरेको देखाइएको एनिमेसन"</string>
-    <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फोल्ड गर्न मिल्ने डिभाइस यताउता पल्टाएर देखाइएको एनिमेसन"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फोल्डेबल डिभाइस अनफोल्ड गरेको देखाइएको एनिमेसन"</string>
+    <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फोल्डेबल डिभाइस यताउता पल्टाएर देखाइएको एनिमेसन"</string>
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"अगाडिको स्क्रिन अन गरिएको छ"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"फोल्ड गरिएको"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"अनफोल्ड गरिएको"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"सर्वसुलभता"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"किबोर्डका सर्टकटहरू"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"किबोर्डका सर्टकटहरू कस्टमाइज गर्नुहोस्"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"सर्टकट असाइन गर्न की थिच्नुहोस्"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"सर्टकट हटाउने हो?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"सर्टकट असाइन गर्न की थिच्नुहोस्"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"यसो गर्नुभयो भने तपाईंको कस्टम सर्टकट सदाका लागि मेटिने छ।"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"खोजका सर्टकटहरू"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"कुनै पनि खोज परिणाम भेटिएन"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"\"कोल्याप्स गर्नुहोस्\" आइकन"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"पूरा भयो"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"\"एक्स्पान्ड गर्नुहोस्\" आइकन"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"वा"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ड्र्याग ह्यान्डल"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"किबोर्डसम्बन्धी सेटिङ"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"सर्टकट सेट गर्नुहोस्"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"हटाउनुहोस्"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"रद्द गर्नुहोस्"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"की थिच्नुहोस्"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"यो की कम्बिनेसन प्रयोग गरिसकिएको छ। अर्कै की प्रयोग गरी हेर्नुहोस्।"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"यो की कम्बिनेसन प्रयोग गरिसकिएको छ। अर्कै की प्रयोग गरी हेर्नुहोस्।"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"सर्टकट सेट गर्न सकिएन।"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"किबोर्ड प्रयोग गरी नेभिगेट गर्नुहोस्"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"किबोर्डका सर्टकटहरू प्रयोग गर्न सिक्नुहोस्"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"टचप्याड प्रयोग गरी नेभिगेट गर्नुहोस्"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 0d195a3..c5c08b1 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth gebruiken"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Verbonden"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio delen"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tik om audio te schakelen of te delen"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Ondersteunt audio delen"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Opgeslagen"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"loskoppelen"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activeren"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets op het vergrendelscherm"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Als je een app wilt openen met een widget, moet je verifiëren dat jij het bent. Houd er ook rekening mee dat iedereen ze kan bekijken, ook als je tablet vergrendeld is. Bepaalde widgets zijn misschien niet bedoeld voor je vergrendelscherm en kunnen hier niet veilig worden toegevoegd."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Als je Widgets als sneltoets wilt toevoegen aan het vergrendelscherm, zorg je dat deze is aangezet in de instellingen."</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Gebruiker wijzigen"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pull-downmenu"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps en gegevens in deze sessie worden verwijderd."</string>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Wordt getoond bovenaan gespreksmeldingen en als profielfoto op het vergrendelscherm, verschijnt als bubbel, onderbreekt Niet storen"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioriteit"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ondersteunt geen gespreksfuncties"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Feedback over bundel geven"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Deze meldingen kunnen niet worden aangepast."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Gespreksmeldingen kunnen niet worden aangepast."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Deze groep meldingen kan hier niet worden ingesteld"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Naar de app links of bovenaan gaan als je een gesplitst scherm gebruikt"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Tijdens gesplitst scherm: een app vervangen door een andere"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Actief venster verplaatsen tussen schermen"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Invoer"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Overschakelen naar volgende taal"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Overschakelen naar vorige taal"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Gebruik minder dan <xliff:g id="LENGTH">%1$d</xliff:g> tekens"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Buildnummer"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Buildnummer naar klembord gekopieerd."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopiëren naar klembord."</string>
     <string name="basic_status" msgid="2315371112182658176">"Gesprek openen"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Gesprekswidgets"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Tik op een gesprek om het toe te voegen aan je startscherm"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Draai de telefoon om voor een hogere resolutie"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Opvouwbaar apparaat wordt uitgevouwen"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Opvouwbaar apparaat wordt gedraaid"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Scherm aan voorzijde aangezet"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"dichtgevouwen"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"opengevouwen"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Toegankelijkheid"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Sneltoetsen"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Sneltoetsen aanpassen"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Druk op de toets om de sneltoets toe te wijzen"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Sneltoets verwijderen?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Druk op de toets om de sneltoets toe te wijzen"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Hiermee wordt je aangepaste sneltoets definitief verwijderd."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Sneltoetsen zoeken"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Geen zoekresultaten"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icoon voor samenvouwen"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Klaar"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icoon voor uitvouwen"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"of"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handgreep voor slepen"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Toetsenbordinstellingen"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Sneltoets instellen"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Verwijderen"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Annuleren"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Druk op een toets"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Toetsencombinatie is al in gebruik. Probeer een andere toets."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Toetsencombinatie is al in gebruik. Probeer een andere toets."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Sneltoets kan niet worden ingesteld."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigeren met je toetsenbord"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Leer sneltoetsen die je kunt gebruiken"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigeren met je touchpad"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 181315b..b7d840a 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"ଅଡିଓ ସୁଇଚ କିମ୍ବା ସେୟାର କରିବାକୁ ଟାପ କରନ୍ତୁ"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ଅଡିଓ ସେୟାରିଂକୁ ସପୋର୍ଟ କରେ"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ଲକ ସ୍କ୍ରିନ ୱିଜେଟ"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ଏକ ୱିଜେଟ ବ୍ୟବହାର କରି ଗୋଟିଏ ଆପ ଖୋଲିବା ପାଇଁ ଏହା ଆପଣ ଅଟନ୍ତି ବୋଲି ଆପଣଙ୍କୁ ଯାଞ୍ଚ କରିବାକୁ ହେବ। ଆହୁରି ମଧ୍ୟ, ଆପଣଙ୍କ ଟାବଲେଟ ଲକ ଥିଲେ ମଧ୍ୟ ଯେ କୌଣସି ବ୍ୟକ୍ତି ଏହାକୁ ଭ୍ୟୁ କରିପାରିବେ ବୋଲି ମନେ ରଖନ୍ତୁ। କିଛି ୱିଜେଟ ଆପଣଙ୍କ ଲକ ସ୍କ୍ରିନ ପାଇଁ ଉଦ୍ଦିଷ୍ଟ ହୋଇନଥାଇପାରେ ଏବଂ ଏଠାରେ ଯୋଗ କରିବା ଅସୁରକ୍ଷିତ ହୋଇପାରେ।"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ବୁଝିଗଲି"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ୱିଜେଟ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"ଏକ ସର୍ଟକଟ ଭାବେ ଲକ ସ୍କ୍ରିନରେ ୱିଜାଟ ଯୋଗ କରିବାକୁ, ସୁନିଶ୍ଚିତ କରନ୍ତୁ ଯେ ଏହା ସେଟିଂସରେ ସକ୍ଷମ ହୋଇଛି।"</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"ବାର୍ତ୍ତାଳାପ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକର ଶୀର୍ଷରେ ଏବଂ ଲକ୍ ସ୍କ୍ରିନରେ ଏକ ପ୍ରୋଫାଇଲ୍ ଛବି ଭାବେ ଦେଖାଏ, ଏକ ବବଲ୍ ଭାବେ ଦେଖାଯାଏ, \'ବିରକ୍ତ କରନ୍ତୁ ନାହିଁ\'କୁ ବାଧା ଦିଏ"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"ପ୍ରାଥମିକତା"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବାର୍ତ୍ତାଳାପ ଫିଚରଗୁଡ଼ିକୁ ସମର୍ଥନ କରେ ନାହିଁ"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ବଣ୍ଡଲ ମତାମତ ପ୍ରଦାନ କରନ୍ତୁ"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"ଏହି ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ପରିବର୍ତ୍ତନ କରିହେବ ନାହିଁ।"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"କଲ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକୁ ପରିବର୍ତ୍ତନ କରାଯାଇପାରିବ ନାହିଁ।"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"ଏଠାରେ ଏହି ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକର ଗ୍ରୁପ୍ କନଫ୍ୟୁଗର୍ କରାଯାଇପାରିବ ନାହିଁ"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ବ୍ୟବହାର କରିବା ସମୟରେ ବାମପଟର ବା ଉପରର ଆପକୁ ସୁଇଚ କରନ୍ତୁ"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ସମୟରେ: କୌଣସି ଆପକୁ ଗୋଟିଏରୁ ଅନ୍ୟ ଏକ ଆପରେ ବଦଳାନ୍ତୁ"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ସକ୍ରିୟ ୱିଣ୍ଡୋକୁ ଡିସପ୍ଲେଗୁଡ଼ିକ ମଧ୍ୟରେ ମୁଭ କରନ୍ତୁ"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ଇନପୁଟ"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"ପରବର୍ତ୍ତୀ ଭାଷାକୁ ସୁଇଚ କରନ୍ତୁ"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"ପୂର୍ବବର୍ତ୍ତୀ ଭାଷାକୁ ସୁଇଚ କରନ୍ତୁ"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g>ଟିରୁ କମ କେରେକ୍ଟର ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"ବିଲ୍ଡ ନମ୍ୱର"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"କ୍ଲିପବୋର୍ଡକୁ କପି କରାଯାଇଥିବା ବିଲ୍ଡ ନମ୍ୱର।"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"କ୍ଲିପବୋର୍ଡକୁ କପି କରନ୍ତୁ।"</string>
     <string name="basic_status" msgid="2315371112182658176">"ବାର୍ତ୍ତାଳାପ ଖୋଲନ୍ତୁ"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"ବାର୍ତ୍ତାଳାପ ୱିଜେଟଗୁଡ଼ିକ"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"ଏକ ବାର୍ତ୍ତାଳାପକୁ ଆପଣଙ୍କ ହୋମ ସ୍କ୍ରିନରେ ଯୋଗ କରିବା ପାଇଁ ସେଥିରେ ଟାପ କରନ୍ତୁ"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ଉଚ୍ଚ ରିଜୋଲ୍ୟୁସନ ପାଇଁ ଫୋନକୁ ଫ୍ଲିପ କରନ୍ତୁ"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ଫୋଲ୍ଡ କରାଯାଇପାରୁଥିବା ଡିଭାଇସକୁ ଅନଫୋଲ୍ଡ କରାଯାଉଛି"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ଫୋଲ୍ଡ କରାଯାଇପାରୁଥିବା ଡିଭାଇସକୁ ଫ୍ଲିପ କରାଯାଉଛି"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ସାମ୍ନା ସ୍କ୍ରିନ ଚାଲୁ ଅଛି"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ଫୋଲ୍ଡେଡ"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ଅନଫୋଲ୍ଡେଡ"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ଆକ୍ସେସିବିଲିଟୀ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"କୀବୋର୍ଡ ସର୍ଟକଟ"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"କୀବୋର୍ଡ ସର୍ଟକଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରନ୍ତୁ"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ସର୍ଟକଟ ଆସାଇନ କରିବା ପାଇଁ କୀ\'କୁ ଦବାନ୍ତୁ"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ସର୍ଟକଟକୁ କାଢ଼ି ଦେବେ?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ସର୍ଟକଟ ଆସାଇନ କରିବା ପାଇଁ କୀ\'କୁ ଦବାନ୍ତୁ"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ଏହା ଆପଣଙ୍କ କଷ୍ଟମ ସର୍ଟକଟକୁ ସ୍ଥାୟୀ ଭାବେ ଡିଲିଟ କରିଦେବ।"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ସର୍ଚ୍ଚ ସର୍ଟକଟ"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"କୌଣସି ସର୍ଚ୍ଚ ଫଳାଫଳ ନାହିଁ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ଆଇକନକୁ ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"ହୋଇଗଲା"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ଆଇକନକୁ ବିସ୍ତାର କରନ୍ତୁ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"କିମ୍ବା"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ଡ୍ରାଗ ହେଣ୍ଡେଲ"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"କୀବୋର୍ଡ ସେଟିଂ"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ସର୍ଟକଟ ସେଟ କରନ୍ତୁ"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ବାତିଲ କରନ୍ତୁ"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"କୀ ଦବାନ୍ତୁ"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"କୀ କମ୍ବିନେସନ ପୂର୍ବରୁ ବ୍ୟବହାର କରାଯାଉଛି। ଅନ୍ୟ ଏକ କୀ ବ୍ୟବହାର କରି ଦେଖନ୍ତୁ।"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"କୀ କମ୍ବିନେସନ ପୂର୍ବରୁ ବ୍ୟବହାର କରାଯାଉଛି। ଅନ୍ୟ ଏକ କୀ ବ୍ୟବହାର କରି ଦେଖନ୍ତୁ।"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ସର୍ଟକଟ ସେଟ କରାଯାଇପାରିବ ନାହିଁ।"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ଆପଣଙ୍କ କୀବୋର୍ଡ ବ୍ୟବହାର କରି ନାଭିଗେଟ କରନ୍ତୁ"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"କୀବୋର୍ଡ ସର୍ଟକଟଗୁଡ଼ିକ ବିଷୟରେ ଜାଣନ୍ତୁ"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ଆପଣଙ୍କ ଟଚପେଡ ବ୍ୟବହାର କରି ନାଭିଗେଟ କରନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 5b9919e..1dd4079 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"ਆਡੀਓ ਨੂੰ ਸਵਿੱਚ ਜਾਂ ਸਾਂਝਾ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ਆਡੀਓ ਸਾਂਝਾਕਰਨ ਦਾ ਸਮਰਥਨ ਕਰਦਾ ਹੈ"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"ਲਾਕ ਸਕ੍ਰੀਨ ਵਿਜੇਟ"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ਵਿਜੇਟ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਐਪ ਖੋਲ੍ਹਣ ਲਈ, ਤੁਹਾਨੂੰ ਇਹ ਪੁਸ਼ਟੀ ਕਰਨ ਦੀ ਲੋੜ ਪਵੇਗੀ ਕਿ ਇਹ ਤੁਸੀਂ ਹੀ ਹੋ। ਨਾਲ ਹੀ, ਇਹ ਵੀ ਧਿਆਨ ਵਿੱਚ ਰੱਖੋ ਕਿ ਕੋਈ ਵੀ ਉਨ੍ਹਾਂ ਨੂੰ ਦੇਖ ਸਕਦਾ ਹੈ, ਭਾਵੇਂ ਤੁਹਾਡਾ ਟੈਬਲੈੱਟ ਲਾਕ ਹੋਵੇ। ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਕੁਝ ਵਿਜੇਟ ਤੁਹਾਡੀ ਲਾਕ ਸਕ੍ਰੀਨ ਲਈ ਨਾ ਬਣੇ ਹੋਣ ਅਤੇ ਉਨ੍ਹਾਂ ਨੂੰ ਇੱਥੇ ਸ਼ਾਮਲ ਕਰਨਾ ਅਸੁਰੱਖਿਅਤ ਹੋਵੇ।"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ਸਮਝ ਲਿਆ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ਵਿਜੇਟ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"ਲਾਕ ਸਕ੍ਰੀਨ \'ਤੇ ਵਿਜੇਟ ਨੂੰ ਸ਼ਾਰਟਕੱਟ ਵਜੋਂ ਸ਼ਾਮਲ ਕਰਨ ਲਈ, ਪੱਕਾ ਕਰੋ ਕਿ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਇਹ ਸੁਵਿਧਾ ਚਾਲੂ ਹੈ।"</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"ਗੱਲਬਾਤ ਸੂਚਨਾਵਾਂ ਦੇ ਸਿਖਰ \'ਤੇ ਅਤੇ ਲਾਕ ਸਕ੍ਰੀਨ \'ਤੇ ਪ੍ਰੋਫਾਈਲ ਤਸਵੀਰ ਵਜੋਂ ਦਿਖਾਈਆਂ ਜਾਂਦੀਆਂ ਹਨ, ਜੋ ਕਿ ਬਬਲ ਵਜੋਂ ਦਿਸਦੀਆਂ ਹਨ ਅਤੇ \'ਪਰੇਸ਼ਾਨ ਨਾ ਕਰੋ\' ਸੁਵਿਧਾ ਵਿੱਚ ਵਿਘਨ ਵੀ ਪਾ ਸਕਦੀਆਂ ਹਨ"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"ਤਰਜੀਹ"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਐਪ ਗੱਲਬਾਤ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"ਬੰਡਲ ਬਾਰੇ ਵਿਚਾਰ ਮੁਹੱਈਆ ਕਰਵਾਓ"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"ਇਹਨਾਂ ਸੂਚਨਾਵਾਂ ਨੂੰ ਸੋਧਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ।"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"ਕਾਲ ਸੰਬੰਧੀ ਸੂਚਨਾਵਾਂ ਨੂੰ ਸੋਧਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ।"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"ਇਹ ਸੂਚਨਾਵਾਂ ਦਾ ਗਰੁੱਪ ਇੱਥੇ ਸੰਰੂਪਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੀ ਵਰਤੋਂ ਕਰਨ ਵੇਲੇ ਖੱਬੇ ਜਾਂ ਉੱਪਰ ਮੌਜੂਦ ਐਪ \'ਤੇ ਸਵਿੱਚ ਕਰੋ"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੌਰਾਨ: ਇੱਕ ਐਪ ਨਾਲ ਦੂਜੀ ਐਪ ਨੂੰ ਬਦਲੋ"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ਕਿਰਿਆਸ਼ੀਲ ਵਿੰਡੋ ਨੂੰ ਇੱਕ ਤੋਂ ਦੂਜੇ ਡਿਸਪਲੇ \'ਤੇ ਲਿਜਾਓ"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ਇਨਪੁੱਟ"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"ਅਗਲੀ ਭਾਸ਼ਾ \'ਤੇ ਸਵਿੱਚ ਕਰੋ"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"ਪਿਛਲੀ ਭਾਸ਼ਾ \'ਤੇ ਸਵਿੱਚ ਕਰੋ"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> ਤੋਂ ਘੱਟ ਅੱਖਰ-ਚਿੰਨ੍ਹ ਵਰਤੋ"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"ਬਿਲਡ ਨੰਬਰ"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"ਬਿਲਡ ਨੰਬਰ ਨੂੰ ਕਲਿੱਪਬੋਰਡ \'ਤੇ ਕਾਪੀ ਕੀਤਾ ਗਿਆ।"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"ਕਲਿੱਪਬੋਰਡ \'ਤੇ ਕਾਪੀ ਕਰੋ।"</string>
     <string name="basic_status" msgid="2315371112182658176">"ਗੱਲਬਾਤ ਖੋਲ੍ਹੋ"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"ਗੱਲਬਾਤ ਵਿਜੇਟ"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"ਆਪਣੀ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰਨ ਲਈ ਕਿਸੇ ਗੱਲਬਾਤ \'ਤੇ ਟੈਪ ਕਰੋ"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ਉੱਚ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਲਈ, ਫ਼ੋਨ ਨੂੰ ਫਲਿੱਪ ਕਰੋ"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ਮੋੜਨਯੋਗ ਡੀਵਾਈਸ ਨੂੰ ਖੋਲ੍ਹਿਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ਮੋੜਨਯੋਗ ਡੀਵਾਈਸ ਨੂੰ ਆਲੇ-ਦੁਆਲੇ ਫਲਿੱਪ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ਅਗਲੀ ਸਕ੍ਰੀਨ ਚਾਲੂ ਕੀਤੀ ਗਈ"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ਫੋਲਡਯੋਗ ਡੀਵਾਈਸ"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ਅਣਫੋਲਡਯੋਗ ਡੀਵਾਈਸ"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ਪਹੁੰਚਯੋਗਤਾ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"ਕੀ-ਬੋਰਡ ਸ਼ਾਰਟਕੱਟ"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"ਕੀ-ਬੋਰਡ ਸ਼ਾਰਟਕੱਟ ਵਿਉਂਤਬੱਧ ਕਰੋ"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ਸ਼ਾਰਟਕੱਟ ਨਿਰਧਾਰਿਤ ਕਰਨ ਲਈ ਕੁੰਜੀ ਦਬਾਓ"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ਕੀ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਹਟਾਉਣਾ ਹੈ?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ਸ਼ਾਰਟਕੱਟ ਨਿਰਧਾਰਿਤ ਕਰਨ ਲਈ ਕੁੰਜੀ ਦਬਾਓ"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ਇਸ ਨਾਲ ਤੁਹਾਡੇ ਵਿਉਂਤੇ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਪੱਕੇ ਤੌਰ \'ਤੇ ਮਿਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ।"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ਸ਼ਾਰਟਕੱਟ ਖੋਜੋ"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ਕੋਈ ਖੋਜ ਨਤੀਜਾ ਨਹੀਂ ਮਿਲਿਆ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ਪ੍ਰਤੀਕ ਨੂੰ ਸਮੇਟੋ"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"ਹੋ ਗਿਆ"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ਪ੍ਰਤੀਕ ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ਜਾਂ"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ਘਸੀਟਣ ਵਾਲਾ ਹੈਂਡਲ"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"ਕੀ-ਬੋਰਡ ਸੈਟਿੰਗਾਂ"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ਸ਼ਾਰਟਕੱਟ ਸੈੱਟ ਕਰੋ"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ਹਟਾਓ"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ਰੱਦ ਕਰੋ"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"ਕੁੰਜੀ ਦਬਾਓ"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"ਕੁੰਜੀ ਸੁਮੇਲ ਪਹਿਲਾਂ ਹੀ ਵਰਤੋਂ ਵਿੱਚ ਹੈ। ਕੋਈ ਹੋਰ ਕੁੰਜੀ ਨੂੰ ਵਰਤ ਕੇ ਦੇਖੋ।"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ਕੁੰਜੀ ਸੁਮੇਲ ਪਹਿਲਾਂ ਹੀ ਵਰਤੋਂ ਵਿੱਚ ਹੈ। ਕੋਈ ਹੋਰ ਕੁੰਜੀ ਵਰਤ ਕੇ ਦੇਖੋ।"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਸੈੱਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ਆਪਣੇ ਕੀ-ਬੋਰਡ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਨੈਵੀਗੇਟ ਕਰੋ"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"ਕੀ-ਬੋਰਡ ਦੇ ਸ਼ਾਰਟਕੱਟਾਂ ਬਾਰੇ ਜਾਣੋ"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ਆਪਣੇ ਟੱਚਪੈਡ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਨੈਵੀਗੇਟ ਕਰੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index e41d16f..82fe861 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Używaj Bluetootha"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Połączone"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Udostępnianie dźwięku"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Kliknij, aby przełączyć lub udostępnić dźwięk"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Obsługa udostępniania dźwięku"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Zapisane"</string>
     <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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widżety na ekranie blokady"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Aby otworzyć aplikację za pomocą widżetu, musisz potwierdzić swoją tożsamość. Pamiętaj też, że każdy będzie mógł wyświetlić widżety nawet wtedy, gdy tablet będzie zablokowany. Niektóre widżety mogą nie być przeznaczone do umieszczenia na ekranie blokady i ich dodanie w tym miejscu może być niebezpieczne."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widżety"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Aby dodać Widżety na ekranie blokady jako skrót, upewnij się, że ta opcja jest włączona w ustawieniach."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Wyświetla się u góry powiadomień w rozmowach oraz jako zdjęcie profilowe na ekranie blokady, jako dymek, przerywa działanie trybu Nie przeszkadzać"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Priorytetowe"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> nie obsługuje funkcji rozmów"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Prześlij opinię o pakiecie"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Tych powiadomień nie można zmodyfikować."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Powiadomień o połączeniach nie można modyfikować."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Tej grupy powiadomień nie można tu skonfigurować"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Przełącz się na aplikację po lewej lub powyżej na podzielonym ekranie"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Podczas podzielonego ekranu: zastępowanie aplikacji"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Przenieś aktywne okno na inny ekran"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Wprowadzanie"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Przełącz na następny język"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Przełącz na poprzedni język"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Wpisz mniej znaków niż <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numer kompilacji"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Numer kompilacji został skopiowany do schowka."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"Kopiuj do schowka"</string>
     <string name="basic_status" msgid="2315371112182658176">"Otwarta rozmowa"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widżety rozmów"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Kliknij rozmowę, aby dodać ją do ekranu głównego"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Odwróć telefon, aby uzyskać wyższą rozdzielczość"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Składane urządzenie jest rozkładane"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Składane urządzenie jest obracane"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Ekran przedni jest włączony"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"po zamknięciu"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"po otwarciu"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Ułatwienia dostępu"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Skróty klawiszowe"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Dostosuj skróty klawiszowe"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Naciśnij klawisz, aby przypisać skrót"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Usunąć skrót?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Naciśnij klawisz, aby przypisać skrót"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Spowoduje to trwałe usunięcie skrótu niestandardowego."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Skróty do wyszukiwania"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Brak wyników wyszukiwania"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona zwijania"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Gotowe"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozwijania"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"lub"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Uchwyt do przeciągania"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Ustawienia klawiatury"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Ustaw skrót"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Usuń"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Anuluj"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Naciśnij klawisz"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinacja klawiszy jest już używana. Użyj innego klawisza."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinacja klawiszy jest już używana. Użyj innego klawisza."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Nie można ustawić skrótu."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Nawiguj za pomocą klawiatury"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Dowiedz się więcej o skrótach klawiszowych"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Nawiguj za pomocą touchpada"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 72e593c..e65dccb 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Compartilhamento de áudio"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Toque para mudar ou compartilhar o áudio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Compatível com compartilhamento de áudio"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvo"</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">"ativar"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets da tela de bloqueio"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para abrir um app usando um widget, você precisa confirmar sua identidade. E não se esqueça que qualquer pessoa pode ver os widgets, mesmo com o tablet bloqueado. Além disso, alguns apps não foram criados para a tela de bloqueio, é melhor manter a segurança."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Entendi"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Para adicionar o recurso \"Widgets na tela de bloqueio\" como um atalho, verifique se os atalhos estão ativados nas configurações."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Aparecem na parte superior das notificações de conversa, como uma foto do perfil na tela de bloqueio e como um balão. Interrompem o Não perturbe."</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritárias"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não é compatível com recursos de conversa"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Enviar feedback sobre o pacote"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Não é possível modificar essas notificações."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Não é possível modificar as notificações de chamada."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Não é possível configurar esse grupo de notificações aqui"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Mudar para o app à esquerda ou acima ao usar a tela dividida"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Com a tela dividida: substituir um app por outro"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mover janela ativa entre telas"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrada"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Mudar para o próximo idioma"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Mudar para o idioma anterior"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Use menos de <xliff:g id="LENGTH">%1$d</xliff:g> caracteres"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da versão"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Número da versão copiado para a área de transferência."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiar para a área de transferência."</string>
     <string name="basic_status" msgid="2315371112182658176">"Conversa aberta"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversa"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Toque em uma conversa para adicioná-la à tela inicial"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para uma resolução maior, vire o smartphone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável sendo aberto"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável sendo virado"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Tela frontal ativada"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"fechado"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"aberto"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Acessibilidade"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Atalhos do teclado"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalizar atalhos de teclado"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pressione a tecla para atribuir o atalho"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remover atalho?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pressione a tecla para atribuir o atalho"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Essa ação vai excluir permanentemente seu atalho personalizado."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pesquisar atalhos"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nenhum resultado de pesquisa"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícone \"Fechar\""</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Concluir"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícone \"Abrir\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Alça de arrastar"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Configurações do teclado"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Definir atalho"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Remover"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancelar"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pressione a tecla"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Essa combinação de teclas já está em uso. Tente outra tecla."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Essa combinação de teclas já está em uso. Tente outra tecla."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Não é possível definir o atalho."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navegue usando o teclado"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprenda atalhos do teclado"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navegue usando o touchpad"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 460d553..3dcba30 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ligado"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Partilha de áudio"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Toque para mudar ou partilhar o áudio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Compatível com partilha de áudio"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desassociar"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets do ecrã de bloqueio"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para abrir uma app através de um widget, vai ter de validar a sua identidade. Além disso, tenha em atenção que qualquer pessoa pode ver os widgets, mesmo quando o tablet estiver bloqueado. Alguns widgets podem não se destinar ao ecrã de bloqueio e pode ser inseguro adicioná-los aqui."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Para adicionar widgets ao ecrã de bloqueio como um atalho, certifique-se de que estão ativados nas definições."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Aparece na parte superior das notificações de conversas e como uma imagem do perfil no ecrã de bloqueio, surge como um balão, interrompe o modo Não incomodar"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioridade"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> não suporta funcionalidades de conversa."</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Enviar feedback sobre o pacote"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Não é possível modificar estas notificações."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Não é possível modificar as notificações de chamadas."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Não é possível configurar este grupo de notificações aqui."</string>
@@ -872,6 +875,10 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Mude para a app à esquerda ou acima enquanto usa o ecrã dividido"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Durante o ecrã dividido: substituir uma app por outra"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mova a janela ativa entre ecrãs"</string>
+    <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Mover janela para a esquerda"</string>
+    <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Mover janela para a direita"</string>
+    <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Maximizar janela"</string>
+    <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Minimizar janela"</string>
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrada"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Mudar para idioma seguinte"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Mudar para idioma anterior"</string>
@@ -1220,8 +1227,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Use menos de <xliff:g id="LENGTH">%1$d</xliff:g> carateres"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da compilação"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Número da compilação copiado para a área de transferência."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiar para a área de transferência."</string>
     <string name="basic_status" msgid="2315371112182658176">"Abrir conversa"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversa"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Toque numa conversa para a adicionar ao ecrã principal"</string>
@@ -1357,8 +1363,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para uma resolução superior, inverta o telemóvel"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável a ser desdobrado"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável a ser virado ao contrário"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Ecrã frontal ativado"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"fechado"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"aberto"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1423,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Acessibilidade"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Atalhos de teclado"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalize os atalhos de teclado"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Prima a tecla para atribuir o atalho"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remover atalho?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Prima a tecla para atribuir o atalho"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Esta ação elimina o atalho personalizado permanentemente."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pesquisar atalhos"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nenhum resultado da pesquisa"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícone de reduzir"</string>
@@ -1428,12 +1435,17 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Concluir"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícone de expandir"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
+    <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"mais"</string>
+    <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"barra"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Indicador para arrastar"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Definições do teclado"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Configurar atalho"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Remover"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancelar"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Prima a tecla"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"A combinação de teclas já está a ser usada. Experimente outra tecla."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"A combinação de teclas já está a ser usada. Experimente outra tecla."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Não é possível definir o atalho."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navegue com o teclado"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprenda atalhos de teclado"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navegue com o touchpad"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 72e593c..e65dccb 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Compartilhamento de áudio"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Toque para mudar ou compartilhar o áudio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Compatível com compartilhamento de áudio"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvo"</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">"ativar"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets da tela de bloqueio"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para abrir um app usando um widget, você precisa confirmar sua identidade. E não se esqueça que qualquer pessoa pode ver os widgets, mesmo com o tablet bloqueado. Além disso, alguns apps não foram criados para a tela de bloqueio, é melhor manter a segurança."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Entendi"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgets"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Para adicionar o recurso \"Widgets na tela de bloqueio\" como um atalho, verifique se os atalhos estão ativados nas configurações."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Aparecem na parte superior das notificações de conversa, como uma foto do perfil na tela de bloqueio e como um balão. Interrompem o Não perturbe."</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritárias"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não é compatível com recursos de conversa"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Enviar feedback sobre o pacote"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Não é possível modificar essas notificações."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Não é possível modificar as notificações de chamada."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Não é possível configurar esse grupo de notificações aqui"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Mudar para o app à esquerda ou acima ao usar a tela dividida"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Com a tela dividida: substituir um app por outro"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mover janela ativa entre telas"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Entrada"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Mudar para o próximo idioma"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Mudar para o idioma anterior"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Use menos de <xliff:g id="LENGTH">%1$d</xliff:g> caracteres"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da versão"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Número da versão copiado para a área de transferência."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiar para a área de transferência."</string>
     <string name="basic_status" msgid="2315371112182658176">"Conversa aberta"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widgets de conversa"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Toque em uma conversa para adicioná-la à tela inicial"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para uma resolução maior, vire o smartphone"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável sendo aberto"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável sendo virado"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Tela frontal ativada"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"fechado"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"aberto"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Acessibilidade"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Atalhos do teclado"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalizar atalhos de teclado"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pressione a tecla para atribuir o atalho"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remover atalho?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pressione a tecla para atribuir o atalho"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Essa ação vai excluir permanentemente seu atalho personalizado."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pesquisar atalhos"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nenhum resultado de pesquisa"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícone \"Fechar\""</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Concluir"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícone \"Abrir\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Alça de arrastar"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Configurações do teclado"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Definir atalho"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Remover"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Cancelar"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pressione a tecla"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Essa combinação de teclas já está em uso. Tente outra tecla."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Essa combinação de teclas já está em uso. Tente outra tecla."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Não é possível definir o atalho."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navegue usando o teclado"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprenda atalhos do teclado"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navegue usando o touchpad"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 42fd14b..93333c0 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Folosește Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectat"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Permiterea accesului la audio"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Atinge pentru a comuta sau a permite accesul la conținutul audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Acceptă permiterea accesului la audio"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvat"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"deconectează"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activează"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgeturi pe ecranul de blocare"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Pentru a deschide o aplicație folosind un widget, va trebui să-ți confirmi identitatea. În plus, reține că oricine poate să vadă widgeturile, chiar dacă tableta este blocată. Este posibil ca unele widgeturi să nu fi fost create pentru ecranul de blocare și poate fi nesigur să le adaugi aici."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgeturi"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Pentru a adăuga widgeturi pe ecranul de blocare drept comandă rapidă, verifică dacă sunt activate în setări."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Se afișează în partea de sus a notificărilor pentru conversații și ca fotografie de profil pe ecranul de blocare, apare ca un balon, întrerupe funcția Nu deranja"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritate"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> nu acceptă funcții pentru conversații"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Trimite feedback despre pachet"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Aceste notificări nu pot fi modificate."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Notificările pentru apeluri nu pot fi modificate."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Acest grup de notificări nu poate fi configurat aici"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Treci la aplicația din stânga sau de mai sus cu ecranul împărțit"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"În modul ecran împărțit: înlocuiește o aplicație cu alta"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Mută fereastra activă de pe un ecran pe altul"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Introducere"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Comută la următoarea limbă"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Comută la limba anterioară"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Folosește maximum <xliff:g id="LENGTH">%1$d</xliff:g> caractere"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numărul versiunii"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Numărul versiunii s-a copiat în clipboard."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"copiază în clipboard"</string>
     <string name="basic_status" msgid="2315371112182658176">"Deschide conversația"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Widgeturi pentru conversație"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Atinge o conversație ca să o adaugi pe ecranul de pornire"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Pentru o rezoluție mai mare, deschide telefonul"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispozitiv pliabil care este desfăcut"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispozitiv pliabil care este întors"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Ecranul frontal este activat"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"închis"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"deschis"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilitate"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Comenzi rapide de la tastatură"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalizează comenzile rapide de la tastatură"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Apasă tasta pentru a atribui comanda rapidă"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Elimini comanda rapidă?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Apasă tasta pentru a atribui comanda rapidă"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Astfel, se va șterge definitiv comanda rapidă personalizată."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Comenzi directe de căutare"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Niciun rezultat al căutării"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Pictograma de restrângere"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Gata"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Pictograma de extindere"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"sau"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ghidaj de tragere"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Setările tastaturii"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Setează o comandă rapidă"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Elimină"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Anulează"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Apasă tasta"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Combinația de taste este deja folosită. Încearcă altă tastă."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Combinația de taste este deja folosită. Încearcă altă tastă."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Comanda rapidă nu poate fi setată."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navighează folosind tastatura"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Învață comenzile rapide de la tastatură"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navighează folosind touchpadul"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 866cb5e..4641e01 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"Нажмите, чтобы переключить аудио или поделиться им"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Поддерживает передачу аудио"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Виджеты на заблокированном экране"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Чтобы открыть приложение, используя виджет, вам нужно будет подтвердить свою личность. Обратите внимание, что виджеты видны всем, даже если планшет заблокирован. Некоторые виджеты не предназначены для использования на заблокированном экране. Добавлять их туда может быть небезопасно."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"ОК"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Виджеты"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Чтобы добавить виджеты на заблокированный экран, включите в настройках параметр \"Виджеты на заблокированном экране\"."</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>
@@ -698,8 +700,8 @@
     <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Контроль шума"</string>
     <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_fixed" msgid="3136080137827746046">"Статичное"</string>
+    <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Динамичное"</string>
     <string name="volume_ringer_change" msgid="3574969197796055532">"Нажмите, чтобы изменить режим звонка."</string>
     <string name="volume_ringer_mode" msgid="6867838048430807128">"режим звонка"</string>
     <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"отключить звук"</string>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Появляется в верхней части уведомлений о сообщениях, в виде всплывающего чата, а также в качестве фото профиля на заблокированном экране, прерывает режим \"Не беспокоить\"."</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Приоритет"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" не поддерживает функции разговоров."</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Отправить отзыв о сгруппированных уведомлениях"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Эти уведомления нельзя изменить."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Уведомления о звонках нельзя изменить."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Эту группу уведомлений нельзя настроить здесь."</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Перейти к приложению слева или вверху на разделенном экране"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"В режиме разделения экрана заменить одно приложение другим"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Переместить активное окно между экранами"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Ввод"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Выбрать следующий язык"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Выбрать предыдущий язык"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Максимальное количество символов – <xliff:g id="LENGTH">%1$d</xliff:g>."</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер сборки"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Номер сборки скопирован в буфер обмена."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"скопировать в буфер обмена."</string>
     <string name="basic_status" msgid="2315371112182658176">"Открытый чат"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Виджеты разговоров"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Нажмите на разговор, чтобы добавить его на главный экран"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Переверните телефон и используйте основную камеру, чтобы делать снимки с более высоким разрешением."</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Складное устройство в разложенном виде"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Перевернутое складное устройство"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Передний экран включен"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"устройство сложено"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"устройство разложено"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Специальные возможности"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Быстрые клавиши"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Как настроить быстрые клавиши"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Нажмите клавишу, чтобы назначить быструю команду."</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Удалить сочетание клавиш?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Нажмите клавишу, чтобы назначить сочетание клавиш."</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Настроенное сочетание будет безвозвратно удалено."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Найти быстрые клавиши"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ничего не найдено"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок \"Свернуть\""</string>
@@ -1428,18 +1439,25 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Готово"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок \"Развернуть\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер перемещения"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Настройки клавиатуры"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Задать сочетание клавиш"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Удалить"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Отмена"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Нажмите клавишу"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Это сочетание клавиш уже используется. Попробуйте другое."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Это сочетание клавиш уже используется. Попробуйте другое."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Это сочетание клавиш выбрать нельзя."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Навигация с помощью клавиатуры"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Узнайте о сочетаниях клавиш."</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Навигация с помощью сенсорной панели"</string>
     <string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"Узнайте о жестах на сенсорной панели."</string>
     <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Навигация с помощью клавиатуры и сенсорной панели"</string>
-    <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Узнайте о жестах на сенсорной панели, сочетаниях клавиш и многом другом."</string>
+    <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Выучите жесты на сенсорной панели, сочетания клавиш и другие варианты навигации."</string>
     <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Назад"</string>
     <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"На главный экран"</string>
     <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Просмотр недавних приложений"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 0ccd61c..7f85b6f 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"ශ්‍රව්‍ය මාරු කිරීමට හෝ බෙදා ගැනීමට තට්ටු කරන්න"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ශ්‍රව්‍ය බෙදා ගැනීම සහය දක්වයි"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"අගුළු තිර විජට්"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"විජට් එකක් භාවිතයෙන් යෙදුමක් විවෘත කිරීමට, ඔබට ඒ ඔබ බව සත්‍යාපනය කිරීමට අවශ්‍ය වනු ඇත. එසේම, ඔබේ ටැබ්ලටය අගුළු දමා ඇති විට පවා ඕනෑම කෙනෙකුට ඒවා බැලිය හැකි බව මතක තබා ගන්න. සමහර විජට් ඔබේ අගුළු තිරය සඳහා අදහස් කර නොතිබිය හැකි අතර මෙහි එක් කිරීමට අනාරක්ෂිත විය හැක."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"තේරුණා"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"විජට්"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"කෙටිමඟක් ලෙස අගුළු තිරය මත Widgets එක් කිරීමට, එය සැකසීම් තුළ සබල කර ඇති බවට වග බලා ගන්න."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"සංවාද දැනුම්දීම්වල ඉහළින්ම සහ අගුලු තිරයේ ඇති පැතිකඩ පින්තූරයක් ලෙස පෙන්වයි, බුබුළක් ලෙස දිස් වේ, බාධා නොකරන්න සඳහා බාධා කරයි"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"ප්‍රමුඛතාව"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> සංවාද විශේෂාංගවලට සහාය නොදක්වයි"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"බණ්ඩල් ප්‍රතිපෝෂණ ලබා දෙන්න"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"මෙම දැනුම්දීම් වෙනස් කළ නොහැක."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"ඇමතුම් දැනුම්දීම් වෙනස් කළ නොහැකිය."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"මෙම දැනුම්දීම් සමූහය මෙහි වින්‍යාස කළ නොහැක"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"බෙදුම් තිරය භාවිත කරන අතරතුර වමේ හෝ ඉහළ ඇති යෙදුමට මාරු වන්න"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"බෙදුම් තිරය අතරතුර: යෙදුමක් එකකින් තවත් එකක් ප්‍රතිස්ථාපනය කරන්න"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"සක්‍රිය කවුළුව සංදර්ශක අතර ගෙන යන්න"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ආදානය"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"මීළඟ භාෂාවට මාරු වන්න"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"පෙර භාෂාවට මාරු වන්න"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"අනුලකුණු <xliff:g id="LENGTH">%1$d</xliff:g>කට වඩා අඩුවෙන් භාවිතා කරන්න"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"නිමැවුම් අංකය"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"නිමැවුම් අංකය පසුරු පුවරුවට පිටපත් කරන ලදි."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"පසුරු පුවරුවට පිටපත් කරන්න."</string>
     <string name="basic_status" msgid="2315371112182658176">"සංවාදය විවෘත කරන්න"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"සංවාද විජට්"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"ඔබගේ මුල් තිරයට එය එක් කිරීමට සංවාදයක් තට්ටු කරන්න"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ඉහළ විභේදනය සඳහා, දුරකථනය හරවන්න"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"දිග හැරෙමින් පවතින නැමිය හැකි උපාංගය"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"වටා පෙරළෙමින් තිබෙන නැමිය හැකි උපාංගය"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ඉදිරිපස තිරය ක්‍රියාත්මකයි"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"නැවූ"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"නොනැවූ"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ප්‍රවේශ්‍යතාව"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"යතුරු පුවරු කෙටි මං"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"යතුරුපුවරු කෙටිමං අභිරුචිකරණය කරන්න"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"කෙටි මග පැවරීමට යතුර ඔබන්න"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"කෙටිමඟ ඉවත් කරන්න ද?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"කෙටිමඟ පැවරීමට යතුර ඔබන්න"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"මෙය ඔබේ අභිරුචි කෙටිමඟ ස්ථිරවම මකනු ඇත."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"කෙටි මං සොයන්න"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"සෙවීම් ප්‍රතිඵල නැත"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"හැකුළුම් නිරූපකය"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"නිමයි"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"දිගහැරීම් නිරූපකය"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"හෝ"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ඇදීම් හැඬලය"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"යතුරු පුවරු සැකසීම්"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"කෙටිමඟ සකසන්න"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ඉවත් කරන්න"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"අවලංගු කරන්න"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"යතුර ඔබන්න"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"යතුරු සංයෝජනය දැනටමත් භාවිත වේ. වෙනත් යතුරක් උත්සාහ කරන්න."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"යතුරු සංයෝජනය දැනටමත් භාවිත වේ. වෙනත් යතුරක් උත්සාහ කරන්න."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"කෙටිමඟ සැකසිය නොහැක."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ඔබේ යතුරු පුවරුව භාවිතයෙන් සංචාලනය කරන්න"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"යතුරුපුවරු කෙටිමං ඉගෙන ගන්න"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ඔබේ ස්පර්ශ පෑඩ් භාවිතයෙන් සංචාලනය කරන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index cc59bea..6dc7a66 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -306,7 +306,7 @@
     <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_audio_sharing_or_switch_active" msgid="3227408556754456024">"Klepnutím prepnete alebo budete zdieľať zvuk"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Podporuje zdieľanie zvuku"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uložené"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojiť"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovať"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Miniaplikácie na uzamknutej obrazovke"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Ak chcete otvoriť aplikáciu pomocou miniaplikácie, budete musieť overiť svoju totožnosť. Pamätajte, že si miniaplikáciu môže pozrieť ktokoľvek, aj keď máte tablet uzamknutý. Niektoré miniaplikácie možno nie sú určené pre uzamknutú obrazovku a ich pridanie tu môže byť nebezpečné."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Dobre"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Miniaplikácie"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Ak chcete na uzamknutú obrazovku pridať miniaplikácie ako odkaz, uistite sa, že sú v nastaveniach povolené."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Zobrazuje sa ako bublina v hornej časti upozornení konverzácie a profilová fotka na uzamknutej obrazovke, preruší režim bez vyrušení"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritné"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> nepodporuje funkcie konverzácie"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Poskytnúť spätnú väzbu k balíku"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Tieto upozornenia sa nedajú upraviť."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Upozornenia na hovory sa nedajú upraviť."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Túto skupinu upozornení nejde na tomto mieste konfigurovať"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Prechod na aplikáciu vľavo alebo hore pri rozdelenej obrazovke"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Počas rozdelenej obrazovky: nahradenie aplikácie inou"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Presun aktívneho okna medzi obrazovkami"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Vstup"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Prepnutie na ďalší jazyk"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Prepnutie na predchádzajúci jazyk"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Použite menej znakov než <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Číslo zostavy"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Číslo zostavy bolo skopírované do schránky."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"skopírovať do schránky."</string>
     <string name="basic_status" msgid="2315371112182658176">"Otvorená konverzácia"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Miniaplikácie konverzácií"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Klepnite na konverzáciu a pridajte ju tak na plochu"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Ak chcete vyššie rozlíšenie, prevráťte telefón"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rozloženie skladacieho zariadenia"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Prevrátenie skladacieho zariadenia"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Predná obrazovka je zapnutá"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zložené"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"rozložené"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Dostupnosť"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klávesové skratky"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Prispôsobenie klávesových skratiek"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Stlačením klávesa priraďte skratku"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Chcete skratku odstrániť?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Stlačením klávesa priraďte skratku"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Týmto natrvalo odstránite vlastnú skratku."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Prehľadávať skratky"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Žiadne výsledky vyhľadávania"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona zbalenia"</string>
@@ -1428,17 +1439,24 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Hotovo"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozbalenia"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"alebo"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Presúvadlo"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Nastavenia klávesnice"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Nastaviť skratku"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Odstrániť"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Zrušiť"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Stlačte kláves"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinácia klávesov sa už používa. Skúste iný kláves."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinácia klávesov sa už používa. Skúste iný kláves."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Skratku nie je možné nastaviť."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Pohybujte sa v systéme pomocou klávesnice"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Naučte sa klávesové skratky"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Pohybujte sa v systéme pomocou touchpadu"</string>
     <string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"Naučte sa gestá touchpadu"</string>
-    <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Prechádzajte pomocou klávesnice a touchpadu"</string>
+    <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Pohybujte sa v systéme pomocou klávesnice a touchpadu"</string>
     <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Naučte sa gestá touchpadu, klávesové skratky a ďalšie funkcie"</string>
     <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Prechod späť"</string>
     <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Prejsť na plochu"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 38f1e5a..74fbd06 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Uporabi Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Deljenje zvoka"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Dotaknite se za preklop ali deljenje zvoka"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Podpira deljenje zvoka"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Shranjeno"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekinitev povezave"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviranje"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Pripomočki na zaklenjenem zaslonu"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Če želite aplikacijo odpreti s pripomočkom, morate potrditi, da ste to vi. Upoštevajte tudi, da si jih lahko ogledajo vsi, tudi ko je tablični računalnik zaklenjen. Nekateri pripomočki morda niso predvideni za uporabo na zaklenjenem zaslonu, zato jih tukaj morda ni varno dodati."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Razumem"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Pripomočki"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Če želite na zaklenjen zaslon dodati pripomočke kot bližnjico, morate to omogočiti v nastavitvah."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Prikaz v obliki oblačka na vrhu razdelka z obvestili za pogovor in kot profilna slika na zaklenjenem zaslonu, preglasitev načina Ne moti."</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prednostno"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> ne podpira pogovornih funkcij."</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Pošiljanje povratnih informacij v paketu"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Za ta obvestila ni mogoče spremeniti nastavitev."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Obvestil o klicih ni mogoče spreminjati."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Te skupine obvestil ni mogoče konfigurirati tukaj"</string>
@@ -872,6 +875,10 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Preklop na aplikacijo levo ali zgoraj med uporabo razdeljenega zaslona"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Pri razdeljenem zaslonu: medsebojna zamenjava aplikacij"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Premikanje aktivnega okna med zasloni"</string>
+    <string name="system_desktop_mode_snap_left_window" msgid="8636204689945162298">"Premik okna na levo"</string>
+    <string name="system_desktop_mode_snap_right_window" msgid="2162560187639411929">"Premik okna na desno"</string>
+    <string name="system_desktop_mode_toggle_maximize_window" msgid="4084100093691768239">"Povečanje okna"</string>
+    <string name="system_desktop_mode_minimize_window" msgid="1248714536732927092">"Pomanjšanje okna"</string>
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Vnos"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Preklop na naslednji jezik"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Preklop na prejšnji jezik"</string>
@@ -1220,8 +1227,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Uporabite manj kot <xliff:g id="LENGTH">%1$d</xliff:g> znakov."</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Delovna različica"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Delovna različica je bila kopirana v odložišče."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopiranje v odložišče."</string>
     <string name="basic_status" msgid="2315371112182658176">"Odprt pogovor"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Pripomočki za pogovore"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Dotaknite se pogovora, da ga dodate na začetni zaslon."</string>
@@ -1357,8 +1363,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za višjo ločljivost obrnite telefon"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Razpiranje zložljive naprave"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Obračanje zložljive naprave"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Sprednji zaslon je vklopljen"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zaprto"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"razprto"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1423,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Dostopnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Bližnjične tipke"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Prilagajanje bližnjičnih tipk"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pritisnite tipko za dodelitev bližnjice"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Želite odstraniti bližnjico?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pritisnite tipko za dodelitev bližnjice"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"S tem boste trajno izbrisali bližnjico po meri."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Iskanje po bližnjicah"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ni rezultatov iskanja"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona za strnitev"</string>
@@ -1428,12 +1435,17 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Končano"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona za razširitev"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ali"</string>
+    <string name="shortcut_helper_key_combinations_and_conjunction" msgid="6138186504075880224">"plus"</string>
+    <string name="shortcut_helper_key_combinations_forward_slash" msgid="1238652537199346970">"poševnica naprej"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ročica za vlečenje"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Nastavitve tipkovnice"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Nastavite bližnjico"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Odstrani"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Prekliči"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pritisnite tipko"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinacija tipk je že v uporabi. Poskusite z drugo tipko."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinacija tipk je že v uporabi. Poskusite z drugo tipko."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Bližnjice ni mogoče nastaviti."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Krmarjenje s tipkovnico"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Učenje bližnjičnih tipk"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Krmarjenje s sledilno ploščico"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 9ed8bf2..bd68061 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -306,7 +306,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Përdor Bluetooth-in"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Lidhur"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Ndarja e audios"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Trokit për të ndërruar ose ndarë audion"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (8680997711431098238) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Ruajtur"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"shkëput"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizo"</string>
@@ -528,6 +529,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Miniaplikacionet në ekranin e kyçjes"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Për të hapur një aplikacion duke përdorur një miniaplikacion, do të duhet të verifikosh që je ti. Ki parasysh gjithashtu që çdo person mund t\'i shikojë, edhe kur tableti yt është i kyçur. Disa miniaplikacione mund të mos jenë planifikuar për ekranin tënd të kyçjes dhe mund të mos jetë e sigurt t\'i shtosh këtu."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"E kuptova"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Miniaplikacionet"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Për të shtuar \"Miniaplikacionet në ekranin e kyçjes\" si shkurtore, sigurohu që kjo veçori të jetë aktivizuar te cilësimet."</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>
@@ -780,6 +783,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Shfaqet në krye të njoftimeve të bisedës dhe si fotografia e profilit në ekranin e kyçjes, shfaqet si flluskë dhe ndërpret modalitetin \"Mos shqetëso\""</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Me përparësi"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> nuk mbështet veçoritë e bisedës"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Jep komente për paketën"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Këto njoftime nuk mund të modifikohen."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Njoftimet e telefonatave nuk mund të modifikohen."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Ky grup njoftimesh nuk mund të konfigurohet këtu"</string>
@@ -872,6 +876,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Kalo tek aplikacioni në të majtë ose sipër kur përdor ekranin e ndarë"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Gjatë ekranit të ndarë: zëvendëso një aplikacion me një tjetër"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Zhvendose dritaren aktive mes ekraneve"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Hyrja"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Kalo te gjuha tjetër"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Kalo te gjuha e mëparshme"</string>
@@ -1220,8 +1232,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Përdor më pak se <xliff:g id="LENGTH">%1$d</xliff:g> karaktere"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numri i ndërtimit"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Numri i ndërtimit u kopjua te kujtesa e fragmenteve"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopjo në kujtesën e fragmenteve."</string>
     <string name="basic_status" msgid="2315371112182658176">"Hap bisedën"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Miniaplikacionet e bisedave"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Trokit te një bisedë dhe shtoje në ekranin bazë"</string>
@@ -1357,8 +1368,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Për rezolucion më të lartë, përmbys telefonin"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Pajisja e palosshme duke u hapur"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Pajisja e palosshme duke u rrotulluar"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Ekrani i përparmë është aktivizuar"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"palosur"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"shpalosur"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1428,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Qasshmëria"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Shkurtoret e tastierës"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Personalizo shkurtoret e tastierës"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Shtyp tastin për të caktuar shkurtoren"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Të hiqet shkurtorja?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Shtyp tastin për të caktuar shkurtoren"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Kjo do ta fshijë përgjithmonë shkurtoren tënde të personalizuar."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Kërko për shkurtoret"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Asnjë rezultat kërkimi"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona e palosjes"</string>
@@ -1428,12 +1440,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"U krye"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona e zgjerimit"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ose"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Doreza e zvarritjes"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Cilësimet e tastierës"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Cakto shkurtoren"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Hiq"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Anulo"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Shtyp tastin"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Kombinimi i tasteve është tashmë në përdorim. Provo një tast tjetër."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinimi i tasteve është tashmë në përdorim. Provo një tast tjetër."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Shkurtorja nuk mund të caktohet."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigo duke përdorur tastierën tënde"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Mëso shkurtoret e tastierës"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigo duke përdorur bllokun me prekje"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 102c452..cc18b66 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"Додирните да бисте пребацили или делили звук"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Подржава дељење звука"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Виџети за закључани екран"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Да бисте отворили апликацију која користи виџет, треба да потврдите да сте то ви. Имајте у виду да свако може да га види, чак и када је таблет закључан. Неки виџети можда нису намењени за закључани екран и можда није безбедно да их тамо додате."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Важи"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Виџети"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Да бисте додали виџете на закључани екран као пречицу, уверите се да је то омогућено у подешавањима."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Приказује се у врху обавештења о конверзацијама и као слика профила на закључаном екрану, појављује се као облачић, прекида режим Не узнемиравај"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Приоритетно"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> не подржава функције конверзације"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Пружите повратне информације о скупу"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Ова обавештења не могу да се мењају."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Обавештења о позивима не могу да се мењају."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Ова група обавештења не може да се конфигурише овде"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Пређите у апликацију слева или изнад док користите подељени екран"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"У режиму подељеног екрана: замена једне апликације другом"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Премести активан прозор на следећи екран"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Унос"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Пређи на следећи језик"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Пређи на претходни језик"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Користите мањи број знакова од <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Број верзије"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Број верзије је копиран у привремену меморију."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"копирајте у привремену меморију."</string>
     <string name="basic_status" msgid="2315371112182658176">"Отворите конверзацију"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Виџети за конверзацију"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Додирните конверзацију да бисте је додали на почетни екран"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"За већу резолуцију обрните телефон"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Уређај на преклоп се отвара"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Уређај на преклоп се обрће"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Предњи екран је укључен"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"затворено"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"отворено"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Приступачност"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Тастерске пречице"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Прилагодите тастерске пречице"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Притисните тастер да бисте доделили пречицу"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Желите да уклоните пречицу?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Притисните тастер да бисте доделили пречицу"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Овим ћете трајно избрисати прилагођену пречицу."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Претражите пречице"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Нема резултата претраге"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за скупљање"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Готово"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за проширивање"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер за превлачење"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Подешавања тастатуре"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Подеси пречицу"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Уклони"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Откажи"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Притисните тастер"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Комбинација тастера се већ користи. Пробајте са другим тастером."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Комбинација тастера се већ користи. Пробајте са другим тастером."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Подешавање пречице није успело."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Крећите се помоћу тастатуре"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Сазнајте више о тастерским пречицама"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Крећите се помоћу тачпеда"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index c2ac216..fae3c11 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Använd Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ansluten"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Ljuddelning"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Tryck för att byta eller dela ljud"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Ljuddelning stöds"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sparad"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"koppla från"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivera"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgetar för låsskärm"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Du måste verifiera din identitet innan du öppnar en app med en widget. Tänk också på att alla kan se dem, även när surfplattan är låst. Vissa widgetar kanske inte är avsedda för låsskärmen och det kan vara osäkert att lägga till dem här."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widgetar"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Om du vill lägga till widgetar på låsskärmen som en genväg måste du se till att de är aktiverade i inställningarna."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Visas högst upp i konversationsaviseringarna och som profilbild på låsskärmen, visas som bubbla, åsidosätter Stör ej"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Prioritet"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> har inte stöd för konversationsfunktioner"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Ge feedback om paket"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Det går inte att ändra de här aviseringarna."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Det går inte att ändra samtalsaviseringarna."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Den här aviseringsgruppen kan inte konfigureras här"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Byt till appen till vänster eller ovanför när du använder delad skärm"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Med delad skärm: ersätt en app med en annan"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Flytta det aktiva fönstret mellan skärmar"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Inmatning"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Byt till nästa språk"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Byt till föregående språk"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Använd färre än <xliff:g id="LENGTH">%1$d</xliff:g> tecken"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Versionsnummer"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Versionsnumret har kopierats till urklipp."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopiera till urklipp."</string>
     <string name="basic_status" msgid="2315371112182658176">"Öppen konversation"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Konversationswidgetar"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Tryck på en konversation för att lägga till den på startskärmen"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Vänd telefonen för högre upplösning"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"En vikbar enhet viks upp"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"En vikbar enhet vänds"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Den främre skärmen har aktiverats"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"hopvikt"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"uppvikt"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Tillgänglighet"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Kortkommandon"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Anpassa kortkommandon"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Tryck på tangenten för att ange kortkommando"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Vill du ta bort kortkommandot?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Tryck på tangenten för att tilldela ett kortkommando"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Det anpassade kortkommandot raderas permanent."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Sökgenvägar"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Inga sökresultat"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikonen Komprimera"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Klar"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikonen Utöka"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handtag"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Tangentbordsinställningar"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Ange kortkommando"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Ta bort"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Avbryt"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Tryck på tangenten"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Tangentkombinationen används redan. Testa en annan tangent."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tangentkombinationen används redan. Testa en annan tangent."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Det går inte att ställa in kortkommandot."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigera med tangentbordet"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Lär dig kortkommandon"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigera med styrplattan"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 3418907..d8581e8 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Tumia Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Imeunganishwa"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Kusikiliza Pamoja"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Gusa ili ubadilishe sauti au usikilize pamoja na wengine"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Inatumia kipengele cha kusikiliza pamoja"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Imehifadhiwa"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ondoa"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"anza kutumia"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Wijeti zinazoonekana kwenye skrini iliyofungwa"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Utahitaji kuthibitisha kuwa ni wewe ili ufungue programu ukitumia wijeti. Pia, kumbuka kuwa mtu yeyote anaweza kuziona, hata kishikwambi chako kikiwa kimefungwa. Huenda baadhi ya wijeti hazikukusudiwa kutumika kwenye skrini yako iliyofungwa na huenda si salama kuziweka hapa."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Nimeelewa"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Wijeti"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Ili kuweka kipengele cha Wijeti kwenye skrini iliyofungwa kiwe njia ya mkato, hakikisha kimewashwa katika mipangilio."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Huonyeshwa kwenye sehemu ya juu ya arifa za mazungumzo na kama picha ya wasifu kwenye skrini iliyofungwa. Huonekana kama kiputo na hukatiza kipengele cha Usinisumbue"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Kipaumbele"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> haitumii vipengele vya mazungumzo"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Toa Maoni kuhusu Kifurushi"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Arifa hizi haziwezi kubadilishwa."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Arifa za simu haziwezi kubadilishwa."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Kikundi hiki cha arifa hakiwezi kuwekewa mipangilio hapa"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Badilisha uende kwenye programu iliyo kushoto au juu unapotumia hali ya kugawa skrini"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Ukigawanya skrini: badilisha kutoka programu moja hadi nyingine"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Hamisha dirisha linalotumika kati ya skrini moja na nyingine"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Vifaa vya kuingiza data"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Badilisha utumie lugha inayofuata"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Badilisha utumie lugha iliyotangulia"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Hupaswi kuzidi herufi <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Nambari ya muundo"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Nambari ya muundo imewekwa kwenye ubao wa kunakili."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"Nakili kwenye ubao wa kunakili"</string>
     <string name="basic_status" msgid="2315371112182658176">"Fungua mazungumzo"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Wijeti za mazungumzo"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Gusa mazungumzo ili uyaweke kwenye Skrini yako ya kwanza"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Kwa ubora wa juu, geuza simu"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Kifaa kinachokunjwa kikikunjuliwa"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Kifaa kinachokunjwa kikigeuzwa"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Umewasha skrini ya mbele"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"kimekunjwa"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"kimefunguliwa"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Ufikivu"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Mikato ya kibodi"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Weka mapendeleo ya mikato ya kibodi"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Bonyeza kitufe ukabidhi njia ya mkato"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Ungependa kuondoa njia ya mkato?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Bonyeza kitufe ukabidhi njia ya mkato"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Hatua hii itaondoa kabisa njia yako maalum ya mkato."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Njia mkato za kutafutia"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Hamna matokeo ya utafutaji"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Kunja aikoni"</string>
@@ -1428,18 +1439,25 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Nimemaliza"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Panua aikoni"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"au"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Aikoni ya buruta"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Mipangilio ya Kibodi"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Weka njia ya mkato"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Ondoa"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Acha"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Bonyeza kitufe"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Tayari unatumia mchanganyiko huu wa vitufe. Jatibu kitufe kingine."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tayari unatumia mchanganyiko wa vitufe. Jaribu kitufe kingine."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Haiwezi kuweka njia ya mkato."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Kusogeza kwa kutumia kibodi yako"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Fahamu kuhusu mikato ya kibodi"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Kusogeza kwa kutumia padi yako ya kugusa"</string>
     <string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"Fahamu miguso ya padi ya kugusa"</string>
     <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Kusogeza kwa kutumia kibodi na padi yako ya kugusa"</string>
-    <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Jifunze kuhusu miguso ya padi ya kugusa, mikato ya kibodi na mengineyo"</string>
+    <string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Fahamu kuhusu miguso ya padi ya kugusa, mikato ya kibodi na mengineyo"</string>
     <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Rudi nyuma"</string>
     <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Nenda kwenye ukurasa wa mwanzo"</string>
     <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Angalia programu za hivi majuzi"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 99c912c..f2145ff 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"ஆடியோவை மாற்ற அல்லது பகிர, தட்டவும்"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ஆடியோ பகிர்வை ஆதரிக்கிறது"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"பூட்டுத் திரை விட்ஜெட்கள்"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"விட்ஜெட்டைப் பயன்படுத்தி ஆப்ஸைத் திறக்க, அது நீங்கள்தான் என்பதை உறுதிசெய்ய வேண்டும். அத்துடன், உங்கள் டேப்லெட் பூட்டப்பட்டிருந்தாலும்கூட அவற்றை யார் வேண்டுமானாலும் பார்க்கலாம் என்பதை நினைவில்கொள்ளுங்கள். சில விட்ஜெட்கள் உங்கள் பூட்டுத் திரைக்காக உருவாக்கப்பட்டவை அல்ல என்பதையும் அவற்றை இங்கே சேர்ப்பது பாதுகாப்பற்றதாக இருக்கக்கூடும் என்பதையும் நினைவில்கொள்ளுங்கள்."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"சரி"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"விட்ஜெட்கள்"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"\'பூட்டுத் திரையில் விட்ஜெட்கள்\' அம்சத்தை ஷார்ட்கட்டாகச் சேர்க்க, அமைப்புகளில் அது இயக்கப்பட்டுள்ளதை உறுதிப்படுத்தவும்."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"உரையாடல் அறிவிப்புகளின் மேற்பகுதியில் காட்டப்படும், திரை பூட்டப்பட்டிருக்கும்போது சுயவிவரப் படமாகக் காட்டப்படும், குமிழாகத் தோன்றும், தொந்தரவு செய்ய வேண்டாம் அம்சம் இயக்கப்பட்டிருக்கும்போதும் காட்டப்படும்"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"முன்னுரிமை"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"உரையாடல் அம்சங்களை <xliff:g id="APP_NAME">%1$s</xliff:g> ஆதரிக்காது"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"மொத்தமாகக் கருத்தை வழங்கு"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"இந்த அறிவிப்புகளை மாற்ற இயலாது."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"அழைப்பு அறிவிப்புகளை மாற்ற முடியாது."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"இந்த அறிவுப்புக் குழுக்களை இங்கே உள்ளமைக்க இயலாது"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"திரைப் பிரிப்பைப் பயன்படுத்தும்போது இடது/மேலே உள்ள ஆப்ஸுக்கு மாறுதல்"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"திரைப் பிரிப்பின்போது: ஓர் ஆப்ஸுக்குப் பதிலாக மற்றொன்றை மாற்றுதல்"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"காட்சிகளுக்கு இடையே செயலில் உள்ள சாளரத்தை நகர்த்துதல்"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"உள்ளீடு"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"அடுத்த மொழிக்கு மாற்றுதல்"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"முந்தைய மொழிக்கு மாற்றுதல்"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> எழுத்துகளுக்குக் குறைவாகப் பயன்படுத்துங்கள்"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"பதிப்பு எண்"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"பதிப்பு எண் கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"கிளிப்போர்டுக்கு நகலெடுக்கும்."</string>
     <string name="basic_status" msgid="2315371112182658176">"திறந்தநிலை உரையாடல்"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"உரையாடல் விட்ஜெட்டுகள்"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"ஓர் உரையாடலை உங்கள் முகப்புத் திரையில் சேர்க்க அந்த உரையாடலைத் தட்டுங்கள்"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"உயர் தெளிவுத்திறனுக்கு, மொபைலை ஃபிளிப் செய்யுங்கள்"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"மடக்கத்தக்க சாதனம் திறக்கப்படுகிறது"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"மடக்கத்தக்க சாதனம் ஃபிளிப் செய்யப்பட்டு திருப்பப்படுகிறது"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"முன்பக்கத் திரை இயக்கப்பட்டுள்ளது"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"மடக்கப்பட்டது"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"விரிக்கப்பட்டது"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"மாற்றுத்திறன் வசதி"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"கீபோர்டு ஷார்ட்கட்கள்"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"கீபோர்டு ஷார்ட்கட்களைப் பிரத்தியேகப்படுத்துதல்"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"ஷார்ட்கட்டை அமைக்க பட்டனை அழுத்துங்கள்"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ஷார்ட்கட்டை அகற்றவா?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ஷார்ட்கட்டை அமைக்க பட்டனை அழுத்துங்கள்"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"இது உங்கள் பிரத்தியேக ஷார்ட்கட்டை நிரந்தரமாக நீக்கும்."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ஷார்ட்கட்களைத் தேடுக"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"தேடல் முடிவுகள் இல்லை"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"சுருக்குவதற்கான ஐகான்"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"முடிந்தது"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"விரிவாக்குவதற்கான ஐகான்"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"அல்லது"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"இழுப்பதற்கான ஹேண்டில்"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"கீபோர்டு அமைப்புகள்"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ஷார்ட்கட்டை அமையுங்கள்"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"அகற்று"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ரத்துசெய்"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"பட்டனை அழுத்துங்கள்"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"பட்டன் சேர்க்கை ஏற்கெனவே பயன்பாட்டில் உள்ளது. வேறொரு பட்டனைப் பயன்படுத்திப் பார்க்கவும்."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"பட்டன் சேர்க்கை ஏற்கெனவே பயன்பாட்டில் உள்ளது. வேறொரு பட்டனைப் பயன்படுத்திப் பார்க்கவும்."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ஷார்ட்கட்டை அமைக்க முடியாது."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"கீபோர்டைப் பயன்படுத்திச் செல்லுதல்"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"கீபோர்டு ஷார்ட்கட்கள் குறித்துத் தெரிந்துகொள்ளுங்கள்"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"டச்பேடைப் பயன்படுத்திச் செல்லுதல்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 5a08823..2c72796 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"ఆడియోను మార్చడానికి లేదా షేర్ చేయడానికి ట్యాప్ చేయండి"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"ఆడియో షేరింగ్‌కు సపోర్ట్ చేస్తుంది"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"లాక్ స్క్రీన్ విడ్జెట్‌లు"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"విడ్జెట్‌ను ఉపయోగించి యాప్‌ను తెరవడానికి, ఇది మీరేనని వెరిఫై చేయాల్సి ఉంటుంది. అలాగే, మీ టాబ్లెట్ లాక్ చేసి ఉన్నప్పటికీ, ఎవరైనా వాటిని చూడగలరని గుర్తుంచుకోండి. కొన్ని విడ్జెట్‌లు మీ లాక్ స్క్రీన్‌కు తగినవి కాకపోవచ్చు, వాటిని ఇక్కడ జోడించడం సురక్షితం కాకపోవచ్చు."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"అర్థమైంది"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"విడ్జెట్‌లు"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"లాక్ స్క్రీన్‌లో విడ్జెట్‌లను షార్ట్‌కట్‌గా జోడించడానికి, ఇది సెట్టింగ్‌లలో ఎనేబుల్ చేసి ఉందని నిర్ధారించుకోండి."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"సంభాషణ నోటిఫికేషన్‌ల ఎగువున, లాక్ స్క్రీన్‌లో ప్రొఫైల్ ఫోటో‌గా చూపిస్తుంది, బబుల్‌గా కనిపిస్తుంది, \'అంతరాయం కలిగించవద్దు\'ను అంతరాయం కలిగిస్తుంది"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"ప్రాధాన్యత"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> సంభాషణ ఫీచర్‌లను సపోర్ట్ చేయదు"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"బండిల్ ఫీడ్‌బ్యాక్‌ను అందించండి"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"ఈ నోటిఫికేషన్‌లను ఎడిట్ చేయడం వీలుపడదు."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"కాల్ నోటిఫికేషన్‌లను ఎడిట్ చేయడం సాధ్యం కాదు."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"ఈ నోటిఫికేషన్‌ల గ్రూప్‌ను ఇక్కడ కాన్ఫిగర్ చేయలేము"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"స్ప్లిట్ స్క్రీన్ ఉపయోగిస్తున్నప్పుడు ఎడమ లేదా పైన యాప్‌నకు మారండి"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"స్ప్లిట్ స్క్రీన్ సమయంలో: ఒక దాన్నుండి మరో దానికి యాప్ రీప్లేస్ చేయండి"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"యాక్టివ్ విండోను డిస్‌ప్లేల మధ్య తరలించండి"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ఇన్‌పుట్"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"తర్వాత భాషకు స్విచ్ అవ్వండి"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"మునుపటి భాషకు స్విచ్ అవ్వండి"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> కంటే తక్కువ అక్షరాలను ఉపయోగించండి"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"బిల్డ్ నంబర్"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"బిల్డ్ నంబర్, క్లిప్‌బోర్డ్‌కు కాపీ చేయబడింది."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"క్లిప్‌బోర్డ్‌కు కాపీ చేయండి."</string>
     <string name="basic_status" msgid="2315371112182658176">"సంభాషణను తెరవండి"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"సంభాషణ విడ్జెట్‌లు"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"ఏదైనా సంభాషణను మీ మొదటి స్క్రీన్‌కు జోడించడానికి దానిని ట్యాప్ చేయండి"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"అధిక రిజల్యూషన్ కోసం, ఫోన్‌ను తిప్పండి"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"మడవగల పరికరం విప్పబడుతోంది"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"మడవగల పరికరం చుట్టూ తిప్పబడుతోంది"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"ముందు వైపు స్క్రీన్ ఆన్ అయింది"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"మడిచే సదుపాయం గల పరికరం"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"మడిచే సదుపాయం లేని పరికరం"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"యాక్సెసిబిలిటీ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"కీబోర్డ్ షార్ట్‌కట్‌లు"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"కీబోర్డ్ షార్ట్‌కట్‌లను అనుకూలంగా మార్చండి"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"షార్ట్‌కట్‌ను కేటాయించడానికి కీని నొక్కండి"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"షార్ట్‌కట్‌ను తీసివేయాలా?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"షార్ట్‌కట్‌ను కేటాయించడానికి కీని నొక్కండి"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ఇది మీ అనుకూల షార్ట్‌కట్‌ను శాశ్వతంగా తొలగిస్తుంది."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"షార్ట్‌కట్‌లను వెతకండి"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"సెర్చ్ ఫలితాలు ఏవీ లేవు"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"కుదించండి చిహ్నం"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"పూర్తయింది"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"విస్తరించండి చిహ్నం"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"లేదా"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"లాగే హ్యాండిల్"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"కీబోర్డ్ సెట్టింగ్‌లు"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"షార్ట్‌కట్‌ను సెట్ చేయండి"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"తీసివేయండి"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"రద్దు చేయండి"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"కీని నొక్కండి"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"కీ కాంబినేషన్ ఇప్పటికే వినియోగంలో ఉంది. వేరొక కీని ట్రై చేయండి."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"కీ కాంబినేషన్ ఇప్పటికే వినియోగంలో ఉంది. వేరొక కీని ట్రై చేయండి."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"షార్ట్‌కట్‌ను సెట్ చేయడం సాధ్యం కాదు."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"మీ కీబోర్డ్ ఉపయోగించి నావిగేట్ చేయండి"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"కీబోర్డ్ షార్ట్‌కట్‌ల గురించి తెలుసుకోండి"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"మీ టచ్‌ప్యాడ్‌ను ఉపయోగించి నావిగేట్ చేయండి"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 816514a..6ca6968 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"แตะเพื่อสลับหรือแชร์เสียง"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"รองรับการแชร์เสียง"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"วิดเจ็ตในหน้าจอล็อก"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"หากต้องการเปิดแอปโดยใช้วิดเจ็ต คุณจะต้องยืนยันตัวตนของคุณ นอกจากนี้ โปรดทราบว่าผู้อื่นจะดูวิดเจ็ตเหล่านี้ได้แม้ว่าแท็บเล็ตจะล็อกอยู่ก็ตาม วิดเจ็ตบางอย่างอาจไม่ได้มีไว้สำหรับหน้าจอล็อกของคุณ และอาจไม่ปลอดภัยที่จะเพิ่มที่นี่"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"รับทราบ"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"วิดเจ็ต"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"หากต้องการเพิ่มวิดเจ็ตในหน้าจอล็อกเป็นทางลัด โปรดตรวจสอบว่าได้เปิดใช้วิดเจ็ตแล้วในการตั้งค่า"</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"แสดงที่ด้านบนของการแจ้งเตือนการสนทนาและเป็นรูปโปรไฟล์บนหน้าจอล็อก ปรากฏเป็นบับเบิล แสดงในโหมดห้ามรบกวน"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"สำคัญ"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ไม่รองรับฟีเจอร์การสนทนา"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"แสดงความคิดเห็นเกี่ยวกับแพ็กเกจ"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"แก้ไขการแจ้งเตือนเหล่านี้ไม่ได้"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"แก้ไขการแจ้งเตือนสายเรียกเข้าไม่ได้"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"การแจ้งเตือนกลุ่มนี้กำหนดค่าที่นี่ไม่ได้"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"เปลี่ยนไปใช้แอปทางด้านซ้ายหรือด้านบนขณะใช้โหมดแยกหน้าจอ"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"ระหว่างใช้โหมดแยกหน้าจอ: เปลี่ยนแอปหนึ่งเป็นอีกแอปหนึ่ง"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"ย้ายหน้าต่างที่ใช้งานไปยังหน้าจอต่างๆ"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"อินพุต"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"เปลี่ยนเป็นภาษาถัดไป"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"เปลี่ยนเป็นภาษาก่อนหน้า"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"ใช้อักขระไม่เกิน <xliff:g id="LENGTH">%1$d</xliff:g> ตัว"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"หมายเลขบิลด์"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"คัดลอกหมายเลขบิลด์ไปยังคลิปบอร์ดแล้ว"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"คัดลอกไปยังคลิปบอร์ด"</string>
     <string name="basic_status" msgid="2315371112182658176">"เปิดการสนทนา"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"วิดเจ็ตการสนทนา"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"แตะการสนทนาเพื่อเพิ่มไปยังหน้าจอหลัก"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"พลิกด้านโทรศัพท์เพื่อให้ได้ภาพที่มีความละเอียดมากขึ้น"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"อุปกรณ์ที่พับได้กำลังกางออก"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"อุปกรณ์ที่พับได้กำลังพลิกไปมา"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"เปิดหน้าจอด้านหน้าแล้ว"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"พับ"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"กางออก"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"การช่วยเหลือพิเศษ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"แป้นพิมพ์ลัด"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"ปรับแต่งแป้นพิมพ์ลัด"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"กดแป้นเพื่อกำหนดแป้นพิมพ์ลัด"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"นำแป้นพิมพ์ลัดออกใช่ไหม"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"กดแป้นเพื่อกำหนดแป้นพิมพ์ลัด"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"การดำเนินการนี้จะลบแป้นพิมพ์ลัดที่กำหนดเองอย่างถาวร"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ค้นหาแป้นพิมพ์ลัด"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ไม่พบผลการค้นหา"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ไอคอนยุบ"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"เสร็จสิ้น"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ไอคอนขยาย"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"หรือ"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"แฮนเดิลการลาก"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"การตั้งค่าแป้นพิมพ์"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"ตั้งค่าแป้นพิมพ์ลัด"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"นำออก"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"ยกเลิก"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"กดแป้น"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"มีการใช้แป้นที่กดร่วมกันนี้แล้ว โปรดลองใช้แป้นอื่น"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"มีการใช้แป้นที่กดร่วมกันนี้แล้ว โปรดลองใช้แป้นอื่น"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ตั้งค่าแป้นพิมพ์ลัดไม่ได้"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ไปยังส่วนต่างๆ โดยใช้แป้นพิมพ์"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"ดูข้อมูลเกี่ยวกับแป้นพิมพ์ลัด"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ไปยังส่วนต่างๆ โดยใช้ทัชแพด"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 3845fcd..2e1d286 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gumamit ng Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Nakakonekta"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Pag-share ng Audio"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"I-tap para lumipat o magbahagi ng audio"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Sinusuportahan ang pag-share ng audio"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Na-save"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"idiskonekta"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"i-activate"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Mga widget ng lock screen"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para magbukas ng app gamit ang isang widget, kakailanganin mong i-verify na ikaw iyan. Bukod pa rito, tandaang puwedeng tingnan ng kahit na sino ang mga ito, kahit na naka-lock ang iyong tablet. Posibleng hindi para sa iyong lock screen ang ilang widget at posibleng hindi ligtas ang mga ito na idagdag dito."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Mga Widget"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Para magdagdag ng Mga Widget sa lock screen bilang shortcut, tiyaking naka-enable ito sa mga setting."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Makikita sa itaas ng mga notification ng pag-uusap at bilang larawan sa profile sa lock screen, lumalabas bilang bubble, naaabala ang Huwag Istorbohin"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Priyoridad"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"Hindi sinusuportahan ng <xliff:g id="APP_NAME">%1$s</xliff:g> ang mga feature ng pag-uusap"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Magbigay ng Feedback sa Bundle"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Hindi puwedeng baguhin ang mga notification na ito."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Hindi mabago ang mga notification ng tawag."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Hindi mako-configure dito ang pangkat na ito ng mga notification"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Lumipat sa app sa kaliwa o itaas habang ginagamit ang split screen"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Habang nasa split screen: magpalit-palit ng app"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Ilipat ang aktibong window sa pagitan ng mga display"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Input"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Lumipat sa susunod na wika"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Lumipat sa dating wika"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Gumamit ng mas kaunti sa <xliff:g id="LENGTH">%1$d</xliff:g> (na) character"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numero ng build"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Nakopya sa clipboard ang numero ng build."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopyahin sa clipboard."</string>
     <string name="basic_status" msgid="2315371112182658176">"Buksan ang pag-uusap"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Mga widget ng pag-uusap"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Mag-tap sa isang pag-uusap para idagdag ito sa iyong Home screen"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para sa mas mataas na resolution, i-flip ang telepono"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ina-unfold na foldable na device"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Fini-flip na foldable na device"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Na-on ang screen sa harap"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"naka-fold"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"hindi naka-fold"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Mga keyboard shortcut"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"I-customize ang mga keyboard shortcut"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Pindutin ang key para magtalaga ng shortcut"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Alisin ang shortcut?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pindutin ang key para magtalaga ng shortcut"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Permanente nitong ide-delete ang iyong custom na shortcut."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Mga shortcut ng paghahanap"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Walang resulta ng paghahanap"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"I-collapse ang icon"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Tapos na"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"I-expand ang icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handle sa pag-drag"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Mga Setting ng Keyboard"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Magtakda ng shortcut"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Alisin"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Kanselahin"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Pindutin ang key"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Ginagamit na ang kumbinasyon ng key. Sumubok ng ibang key."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ginagamit na ang kumbinasyon ng key. Sumubok ng ibang key."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Hindi maitakda ang shortcut."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Mag-navigate gamit ang iyong keyboard"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Matuto ng mga keyboard shortcut"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Mag-navigate gamit ang iyong touchpad"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 45d8b46..6628d23 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -113,7 +113,7 @@
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Bir uygulamayı kaydet"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Tüm ekranı kaydet"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen_for_display" msgid="3754611651558838691">"Tüm ekranı kaydet: %s"</string>
-    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Tüm ekranınızı kaydettiğinizde ekranınızda gösterilen her şey kaydedilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
+    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Ekranın tamamını kaydederken ekranınızda gösterilen her şey kaydedilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Bir uygulamayı kaydettiğinizde o uygulamada gösterilen veya oynatılan her şey kaydedilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ekranı kaydet"</string>
     <string name="screenrecord_app_selector_title" msgid="3854492366333954736">"Kaydedilecek uygulamayı seçin"</string>
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth\'u kullan"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Bağlandı"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Ses Paylaşımı"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Geçiş yapmak veya ses paylaşmak için dokunun"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Ses paylaşımını destekler"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Kaydedildi"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"bağlantıyı kes"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"etkinleştir"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Kilit ekranı widget\'ları"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Widget kullanarak bir uygulamayı açmak için kimliğinizi doğrulamanız gerekir. Ayrıca, tabletiniz kilitliyken bile widget\'ların herkes tarafından görüntülenebileceğini unutmayın. Bazı widget\'lar kilit ekranınız için tasarlanmamış olabileceğinden buraya eklenmeleri güvenli olmayabilir."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Anladım"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Widget\'lar"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Kilit ekranında widget\'lar özelliğini kısayol olarak eklemek için ayarlarda bu özelliğin etkinleştirildiğinden emin olun."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Görüşme bildirimlerinin üstünde ve kilit ekranında profil resmi olarak gösterilir, baloncuk olarak görünür, Rahatsız Etmeyin\'i kesintiye uğratır"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Öncelikli"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g>, sohbet özelliklerini desteklemiyor"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Paketle İlgili Geri Bildirim Verin"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Bu bildirimler değiştirilemez."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Arama bildirimleri değiştirilemez."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Bu bildirim grubu burada yapılandırılamaz"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Bölünmüş ekran kullanırken soldaki veya üstteki uygulamaya geçiş yapın"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Bölünmüş ekran etkinken: Bir uygulamayı başkasıyla değiştir"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Etkin pencereyi ekranlar arasında taşıma"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Giriş"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Sonraki dile geç"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Önceki dile geç"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"En fazla <xliff:g id="LENGTH">%1$d</xliff:g> karakter kullanın"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Derleme numarası"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Derleme numarası panoya kopyalandı."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"panoya kopyalayın."</string>
     <string name="basic_status" msgid="2315371112182658176">"Görüşmeyi aç"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Görüşme widget\'ları"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Ana ekranınıza eklemek için bir görüşmeye dokunun"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Daha yüksek çözünürlük için telefonu çevirin"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Katlanabilir cihaz açılıyor"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Katlanabilir cihaz döndürülüyor"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Ön ekran açıldı"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"katlanmış"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"katlanmamış"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Erişilebilirlik"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klavye kısayolları"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Klavye kısayollarını özelleştirin"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Kısayol atamak için tuşa basın"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Kısayol kaldırılsın mı?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Kısayol atamak için tuşa basın"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Bu işlem, özel kısayolunuzu kalıcı olarak siler."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Arama kısayolları"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Arama sonucu yok"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Daralt simgesi"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Bitti"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Genişlet simgesi"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"veya"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Sürükleme tutamacı"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Klavye Ayarları"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Kısayol ayarla"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Kaldır"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"İptal"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Tuşa basın"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Tuş kombinasyonu zaten kullanılıyor. Başka bir tuş deneyin."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tuş kombinasyonu zaten kullanılıyor. Başka bir tuş deneyin."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Kısayol ayarlanamıyor."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Klavyenizi kullanarak gezinin"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Klavye kısayollarını öğrenin"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Dokunmatik alanınızı kullanarak gezinin"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 4c4c80e..71845a350 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"Натисніть, щоб змінити режим або надіслати аудіо"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Підтримує надсилання аудіо"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Віджети для заблокованого екрана"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Щоб відкрити додаток за допомогою віджета, вам потрібно буде підтвердити особу. Пам’ятайте також, що бачити віджети можуть усі, навіть коли планшет заблоковано. Можливо, деякі віджети не призначені для заблокованого екрана, і додавати їх на нього може бути небезпечно."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Віджети"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Щоб додати ярлик для опції \"Показувати віджети на заблокованому екрані\", переконайтеся, що її ввімкнено в налаштуваннях."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"З’являється вгорі сповіщень про розмови і як зображення профілю на заблокованому екрані, відображається як спливаючий чат, перериває режим \"Не турбувати\""</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Пріоритет"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> не підтримує функції розмов"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Надіслати груповий відгук"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Ці сповіщення не можна змінити."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Сповіщення про виклик не можна змінити."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Цю групу сповіщень не можна налаштувати тут"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Під час розділення екрана перемикатися на додаток ліворуч або вгорі"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Під час розділення екрана: замінити додаток іншим"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Перемістити активне вікно між дисплеями"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Метод введення"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Вибрати наступну мову"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Вибрати попередню мову"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Кількість символів має бути менше ніж <xliff:g id="LENGTH">%1$d</xliff:g>"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер складання"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Номер складання скопійовано в буфер обміну."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"копіювати в буфер обміну"</string>
     <string name="basic_status" msgid="2315371112182658176">"Відкрита розмова"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Віджети розмов"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Натисніть розмову, щоб додати її на головний екран"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Для вищої роздільної здатності переверніть телефон"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Розкладний пристрій у розкладеному стані"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Розкладний пристрій обертається"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Передній екран увімкнено"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"складений"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"розкладений"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Доступність"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Комбінації клавіш"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Налаштуйте комбінації клавіш"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Натисніть клавішу, щоб призначити комбінацію клавіш"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Видалити комбінацію клавіш?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Натисніть клавішу, щоб призначити комбінацію клавіш"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Вашу власну комбінацію клавіш буде видалено назавжди."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Комбінації клавіш для пошуку"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Нічого не знайдено"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок згортання"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Готово"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок розгортання"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"або"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер переміщення"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Налаштування клавіатури"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Налаштувати комбінацію клавіш"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Видалити"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Скасувати"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Натисніть клавішу"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Комбінація клавіш уже використовується. Спробуйте іншу клавішу."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Комбінація клавіш уже використовується. Спробуйте іншу клавішу."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Не вдалося встановити комбінацію клавіш."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Навігація за допомогою клавіатури"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Дізнайтеся більше про комбінації клавіш"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Навігація за допомогою сенсорної панелі"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 109f43e..514244f 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"آڈیو پر سوئچ کرنے یا اس کا اشتراک کرنے کے لیے تھپتھپائیں"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"آڈیو کے اشتراک کو سپورٹ کرتا ہے"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"مقفل اسکرین کے ویجیٹس"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"ویجیٹ کے ذریعے ایپ کھولنے کے لیے آپ کو تصدیق کرنی ہوگی کہ یہ آپ ہی ہیں۔ نیز، ذہن میں رکھیں کہ کوئی بھی انہیں دیکھ سکتا ہے، یہاں تک کہ جب آپ کا ٹیبلیٹ مقفل ہو۔ ہو سکتا ہے کچھ ویجٹس آپ کی لاک اسکرین کے لیے نہ بنائے گئے ہوں اور یہاں شامل کرنا غیر محفوظ ہو سکتا ہے۔"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"سمجھ آ گئی"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ویجیٹس"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"مقفل اسکرین پر ویجیٹس کو شارٹ کٹ کے بطور شامل کرنے کے لیے یقینی بنائیں کہ یہ ترتیبات میں فعال ہے۔"</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"یہ گفتگو کی اطلاعات کے اوپری حصّے پر اور مقفل اسکرین پر پروفائل کی تصویر کے بطور دکھائی دیتا ہے، بلبلے کے بطور ظاہر ہوتا ہے، \'ڈسٹرب نہ کریں\' میں مداخلت کرتا ہے"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"ترجیح"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ایپ گفتگو کی خصوصیات کو سپورٹ نہیں کرتی ہے"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"بنڈل کے تاثرات فراہم کریں"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"ان اطلاعات کی ترمیم نہیں کی جا سکتی۔"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"کال کی اطلاعات میں ترمیم نہیں کی جا سکتی۔"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"اطلاعات کے اس گروپ کو یہاں کنفیگر نہیں کیا جا سکتا"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"اسپلٹ اسکرین کا استعمال کرتے ہوئے بائیں یا اوپر ایپ پر سوئچ کریں"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"اسپلٹ اسکرین کے دوران: ایک ایپ کو دوسرے سے تبدیل کریں"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"فعال ونڈو کو ڈسپلیز کے مابین منتقل کریں"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"ان پٹ"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"اگلی زبان پر سوئچ کریں"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"پچھلی زبان پر سوئچ کریں"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"<xliff:g id="LENGTH">%1$d</xliff:g> حروف سے کم استعمال کریں"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"بلڈ نمبر"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"بلڈ نمبر کلپ بورڈ میں کاپی ہو گیا۔"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"کلپ بورڈ میں کاپی کریں۔"</string>
     <string name="basic_status" msgid="2315371112182658176">"گفتگو کھولیں"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"گفتگو ویجیٹس"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"اسے اپنی ہوم اسکرین پر شامل کرنے کے لیے گفتگو پر تھپتھپائیں"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"زیادہ ریزولوشن کے لیے، فون پلٹائیں"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"فولڈ ہونے والے آلے کو کھولا جا رہا ہے"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"فولڈ ہونے والے آلے کو گھمایا جا رہا ہے"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"فرنٹ اسکرین آن ہے"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"فولڈ کردہ"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"اَن فولڈ کردہ"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ایکسیسبیلٹی"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"کی بورڈ شارٹ کٹس"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"کی بورڈ شارٹ کٹس کو حسب ضرورت بنائیں"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"شارٹ کٹ تفویض کرنے کے لیے کلید کو دبائیں"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"شارٹ کٹ ہٹائیں؟"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"شارٹ کٹ تفویض کرنے کے لیے کلید کو دبائیں"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"یہ آپ کا حسب ضرورت شارٹ کٹ مستقل طور پر حذف کر دے گا۔"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"تلاش کے شارٹ کٹس"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"تلاش کا کوئی نتیجہ نہیں ہے"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"آئیکن سکیڑیں"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"ہو گیا"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"آئیکن پھیلائیں"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"یا"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"گھسیٹنے کا ہینڈل"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"کی بورڈ کی ترتیبات"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"شارٹ کٹ سیٹ کریں"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"ہٹائیں"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"منسوخ کریں"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"کلید کو دبائیں"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"کلیدی مجموعہ پہلے سے استعمال میں ہے۔ دوسری کلید آزمائیں۔"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"کلیدی مجموعہ پہلے سے استعمال میں ہے۔ دوسری کلید آزمائیں۔"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"شارٹ کٹ سیٹ نہیں کیا جا سکتا۔"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"اپنے کی بورڈ کا استعمال کر کے نیویگیٹ کریں"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"کی بورڈ شارٹ کٹس جانیں"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"اپنے ٹچ پیڈ کا استعمال کر کے نیویگیٹ کریں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 1c27a00..777be07 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth ishlatish"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ulangan"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio ulashuvi"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Audioni almashtirish yoki ulashish uchun bosing"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Audio ulashishi mumkin"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saqlangan"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"uzish"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"faollashtirish"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Ekran qulfi vidjetlari"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Ilovani vidjet orqali ochish uchun shaxsingizni tasdiqlashingiz kerak. Shuningdek, planshet qulflanganda ham bu axborotlar hammaga koʻrinishini unutmang. Ayrim vidjetlar ekran qulfiga moslanmagan va ularni bu yerda chiqarish xavfli boʻlishi mumkin."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Vidjetlar"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Ekran qulfiga yorliq sifatida vidjetlar kiritish uchun uning sozlamalarda yoqilganini tekshiring."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Suhbat bildirishnomalari tepasida va ekran qulfida profil rasmi sifatida chiqariladi, bulutcha sifatida chiqadi, Bezovta qilinmasin rejimini bekor qiladi"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Muhim"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasida suhbat funksiyalari ishlamaydi"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Jamlanma fikr-mulohaza bildirish"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Bu bildirishnomalarni tahrirlash imkonsiz."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Chaqiruv bildirishnomalarini tahrirlash imkonsiz."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Ushbu bildirishnomalar guruhi bu yerda sozlanmaydi"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Ajratilgan ekranda chapdagi yoki yuqoridagi ilovaga almashish"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Ajratilgan rejimda ilovalarni oʻzaro almashtirish"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Faol oynani ekranlararo koʻchirish"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Kiritish"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Keyingi tilga almashtirish"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Avvalgi tilga almashtirish"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Kiritiladigan belgilar <xliff:g id="LENGTH">%1$d</xliff:g> tadan oshmasin"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Nashr raqami"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Nashr raqami vaqtinchalik xotiraga nusxalandi."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"vaqtincha xotiraga nusxalash"</string>
     <string name="basic_status" msgid="2315371112182658176">"Suhbatni ochish"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Suhbat vidjetlari"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Bosh ekranga chiqariladigan suhbat ustiga bosing"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Yuqori aniqlik uchun telefonni aylantiring"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Buklanadigan qurilma ochilmoqda"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Buklanadigan qurilma aylantirilmoqda"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Old ekran yoqildi"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"buklangan"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"buklanmagan"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Qulayliklar"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tezkor tugmalar"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Tezkor tugmalarni moslash"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Tezkor tugma sozlash uchun tugmani bosing"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Tezkor tugma olib tashlansinmi?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Tezkor tugma sozlash uchun tugmani bosing"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Bunda maxsus tezkor tugma butunlay oʻchirib tashlanadi."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Tezkor tugmalar qidiruvi"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Hech narsa topilmadi"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Yigʻish belgisi"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Tayyor"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Yoyish belgisi"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"yoki"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Surish dastagi"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Klaviatura sozlamalari"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Tezkor tugma sozlash"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Olib tashlash"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Bekor qilish"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Tugmani bosing"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Bu tugmalar birikmasi band. Boshqasini ishlating."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Bu tugmalar birikmasi band. Boshqasini ishlating."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Buyruq sozlanmadi."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Klaviatura yordamida kezing"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Tezkor tugmalar haqida"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Sensorli panel yordamida kezing"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index d8e3bc0..6ea683a 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bật Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Đã kết nối"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Chia sẻ âm thanh"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Nhấn để chuyển hoặc chia sẻ âm thanh"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Hỗ trợ tính năng chia sẻ âm thanh"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Đã lưu"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ngắt kết nối"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"kích hoạt"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Tiện ích trên màn hình khoá"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Để dùng tiện ích mở một ứng dụng, bạn cần xác minh danh tính của mình. Ngoài ra, hãy lưu ý rằng bất kỳ ai cũng có thể xem các tiện ích này, ngay cả khi máy tính bảng của bạn được khoá. Một số tiện ích có thể không dành cho màn hình khoá và không an toàn khi thêm vào đây."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Tôi hiểu"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Tiện ích"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Để thêm Tiện ích dưới dạng lối tắt trên màn hình khoá, hãy đảm bảo bạn đã bật tính năng này trong phần cài đặt."</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>
@@ -649,7 +651,7 @@
     <string name="volume_odi_captions_content_description" msgid="4172765742046013630">"Lớp phủ phụ đề"</string>
     <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"bật"</string>
     <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"tắt"</string>
-    <string name="sound_settings" msgid="8874581353127418308">"Âm thanh và chế độ rung"</string>
+    <string name="sound_settings" msgid="8874581353127418308">"Âm thanh và rung"</string>
     <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"Cài đặt"</string>
     <string name="volume_panel_captioning_title" msgid="5984936949147684357">"Phụ đề trực tiếp"</string>
     <string name="csd_lowered_title" product="default" msgid="2464112924151691129">"Âm lượng đã giảm xuống mức an toàn hơn"</string>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Hiện ở đầu phần thông báo cuộc trò chuyện và ở dạng ảnh hồ sơ trên màn hình khóa, xuất hiện ở dạng bong bóng, làm gián đoạn chế độ Không làm phiền"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Mức độ ưu tiên"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> không hỗ trợ các tính năng trò chuyện"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Phản hồi về gói"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Không thể sửa đổi các thông báo này."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Không thể sửa đổi các thông báo cuộc gọi."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Không thể định cấu hình nhóm thông báo này tại đây"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Chuyển sang ứng dụng bên trái hoặc ở trên khi đang chia đôi màn hình"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Trong chế độ chia đôi màn hình: thay một ứng dụng bằng ứng dụng khác"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Di chuyển cửa sổ đang hoạt động giữa các màn hình"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Đầu vào"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Chuyển sang ngôn ngữ tiếp theo"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Chuyển về ngôn ngữ trước"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Hãy dùng ít hơn <xliff:g id="LENGTH">%1$d</xliff:g> ký tự"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Số bản dựng"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Đã sao chép số bản dựng vào bảng nhớ tạm."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"sao chép vào bảng nhớ tạm."</string>
     <string name="basic_status" msgid="2315371112182658176">"Mở cuộc trò chuyện"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Tiện ích trò chuyện"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Nhấn vào một cuộc trò chuyện để thêm cuộc trò chuyện đó vào Màn hình chính"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Để có độ phân giải cao hơn, hãy lật điện thoại"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Thiết bị có thể gập lại đang được mở ra"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Thiết bị có thể gập lại đang được lật ngược"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Đã bật màn hình trước"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"gập"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"mở"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Hỗ trợ tiếp cận"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Phím tắt"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Tuỳ chỉnh phím tắt"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Nhấn phím để chỉ định phím tắt"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Xoá lối tắt?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Nhấn phím để chỉ định lối tắt"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Thao tác này sẽ xoá vĩnh viễn lối tắt tuỳ chỉnh của bạn."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Tìm lối tắt"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Không có kết quả tìm kiếm nào"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Biểu tượng Thu gọn"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Xong"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Biểu tượng Mở rộng"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"hoặc"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Nút kéo"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Cài đặt bàn phím"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Đặt phím tắt"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Xoá"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Huỷ"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Nhấn phím"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Tổ hợp phím đã được sử dụng. Hãy thử một phím khác."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tổ hợp phím đã được sử dụng. Hãy thử một phím khác."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Không đặt được lối tắt."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Di chuyển bằng bàn phím"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Tìm hiểu về phím tắt"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Di chuyển bằng bàn di chuột"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index d9a974a..bc9c318 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -113,7 +113,7 @@
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"录制单个应用"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"录制整个屏幕"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen_for_display" msgid="3754611651558838691">"全屏录制:%s"</string>
-    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"录制整个屏幕时,屏幕上显示的所有内容均会被录制。因此,请务必小心操作,谨防泄露密码、付款信息、消息、照片、音频、视频等。"</string>
+    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"录制整个屏幕时,屏幕上显示的所有内容均会被录制。因此,请务必小心操作,谨防泄露密码、付款详情、消息、照片、音频、视频等敏感信息。"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"录制单个应用时,该应用中显示或播放的所有内容均会被录制。因此,请务必小心操作,谨防泄露密码、付款信息、消息、照片、音频、视频等。"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"录制屏幕"</string>
     <string name="screenrecord_app_selector_title" msgid="3854492366333954736">"选择要录制的应用"</string>
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"点按即可切换或分享音频"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"支持音频分享"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"锁屏微件"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"若要使用微件打开应用,您需要验证是您本人在操作。另外请注意,任何人都可以查看此类微件,即使您的平板电脑已锁定。有些微件可能不适合显示在锁定的屏幕中,因此添加到这里可能不安全。"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"知道了"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"微件"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"如要将微件作为快捷方式添加到锁屏界面,请确保已在设置中启用该功能。"</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"以气泡形式显示在对话通知顶部(屏幕锁定时显示为个人资料照片),并且会中断勿扰模式"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"优先"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g>不支持对话功能"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"提供有关套装的反馈"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"无法修改这些通知。"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"无法修改来电通知。"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"您无法在此处配置这组通知"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"使用分屏模式时,切换到左侧或上方的应用"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"在分屏期间:将一个应用替换为另一个应用"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"在各个显示屏之间移动活动窗口"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"输入"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"切换到下一种语言"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"切换到上一种语言"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"必须少于 <xliff:g id="LENGTH">%1$d</xliff:g> 个字符"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build 号"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"已将 Build 号复制到剪贴板。"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"复制到剪贴板。"</string>
     <string name="basic_status" msgid="2315371112182658176">"开放式对话"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"对话微件"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"点按对话即可将其添加到主屏幕"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"若要获得更高的分辨率,请翻转手机"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展开可折叠设备"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻转可折叠设备"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"前屏已开启"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"折叠状态"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"展开状态"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"无障碍功能"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"键盘快捷键"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"自定义键盘快捷键"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"按下按键即可指定快捷键"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"要移除快捷键吗?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"按下按键即可指定快捷键"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"此操作会永久删除您的自定义快捷键。"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜索快捷键"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"无搜索结果"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收起图标"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"完成"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展开图标"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖动手柄"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"键盘设置"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"设置快捷键"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"移除"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"取消"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"按下按键"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"按键组合已被使用,请尝试使用其他按键。"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"按键组合已被使用,请尝试使用其他按键。"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"无法设置快捷方式。"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"使用键盘导航"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"了解键盘快捷键"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"使用触控板导航"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 443b855..2147743 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"輕按即可切換或分享音訊"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"支援音訊分享功能"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"上鎖畫面小工具"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"如要使用小工具開啟應用程式,系統會要求你驗證身分。請注意,所有人都能查看小工具,即使平板電腦已鎖定亦然。部分小工具可能不適用於上鎖畫面,新增至這裡可能會有安全疑慮。"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"知道了"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"小工具"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"如要將小工具新增為上鎖畫面上的捷徑,請確認已在設定中啟用此功能。"</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"以對話氣泡形式顯示在對話通知頂部 (在上鎖畫面會顯示為個人檔案相片),並會中斷「請勿打擾」模式"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"優先"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」不支援對話功能"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"提供套裝意見"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"無法修改這些通知。"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"無法修改通話通知。"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"無法在此設定這組通知"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"使用分割螢幕時,切換至左邊或上方的應用程式"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"使用分割螢幕期間:更換應用程式"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"在不同畫面間移動使用中的視窗"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"輸入"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"切換至下一個語言"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"切換至上一個語言"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"請使用少於 <xliff:g id="LENGTH">%1$d</xliff:g> 個字元"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"版本號碼"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"版本號碼已複製到剪貼簿。"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"複製去剪貼簿"</string>
     <string name="basic_status" msgid="2315371112182658176">"開啟對話"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"對話小工具"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"輕按對話即可新增至主畫面"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"如要提高解像度,請切換至手機後置鏡頭"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展開折疊式裝置"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻轉折疊式裝置"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"正面螢幕已開啟"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"已摺疊"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"已打開"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"無障礙功能"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"鍵盤快速鍵"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"自訂鍵盤快速鍵"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"按鍵即可指派快速鍵"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"要移除快速鍵嗎?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"按鍵即可指派快速鍵"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"這將永久刪除你的自訂快速鍵。"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜尋快速鍵"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"沒有相符的搜尋結果"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收合圖示"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"完成"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展開圖示"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖曳控點"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"鍵盤設定"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"設定快速鍵"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"移除"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"取消"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"按鍵"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"此按鍵組合已在使用,請改用其他按鍵。"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"此按鍵組合已在使用,請改用其他按鍵。"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"無法設定快速鍵。"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"使用鍵盤導覽"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"瞭解鍵盤快速鍵"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"使用觸控板導覽"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 4b84f25..13fb81c 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -306,7 +306,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_audio_sharing_or_switch_active" msgid="3227408556754456024">"輕觸即可切換或分享音訊"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"支援音訊分享"</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>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"螢幕鎖定小工具"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"如要使用小工具開啟應用程式,需先驗證身分。請留意,即使平板電腦已鎖定,所有人都還是能查看小工具。某些小工具可能不適用於螢幕鎖定畫面,新增到此可能會有安全疑慮。"</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"我知道了"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"小工具"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"如要將小工具新增為螢幕鎖定畫面上的捷徑,請確認已在設定中啟用這項功能。"</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"以對話框的形式顯示在對話通知頂端 (螢幕鎖定時會顯示為個人資料相片),並會中斷「零打擾」模式"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"優先"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」不支援對話功能"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"提供套裝組合意見"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"無法修改這些通知。"</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"無法修改來電通知。"</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"無法在這裡設定這個通知群組"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"使用分割畫面時,切換到左邊或上方的應用程式"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"使用分割畫面期間:更換應用程式"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"在不同畫面間移動使用中的視窗"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"輸入"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"切換到下一個語言"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"切換到上一個語言"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"不得超過 <xliff:g id="LENGTH">%1$d</xliff:g> 個半形字元"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"版本號碼"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"已將版本號碼複製到剪貼簿。"</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"複製到剪貼簿。"</string>
     <string name="basic_status" msgid="2315371112182658176">"開放式對話"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"對話小工具"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"輕觸對話即可新增至主畫面"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"如要提高解析度,請切換至手機後置鏡頭"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展開的折疊式裝置"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻轉折疊式裝置"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"正面螢幕已開啟"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"已摺疊"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"已展開"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"無障礙"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"鍵盤快速鍵"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"自訂鍵盤快速鍵"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"按下按鍵即可指派快速鍵"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"要移除快速鍵嗎?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"按下按鍵即可指派快速鍵"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"這項操作會永久刪除自訂快速鍵。"</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜尋快速鍵"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"找不到相符的搜尋結果"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收合圖示"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"完成"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展開圖示"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖曳控點"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"鍵盤設定"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"設定快速鍵"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"移除"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"取消"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"按下按鍵"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"這個按鍵組合已在使用中,請改用其他按鍵。"</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"這個按鍵組合已在使用中,請改用其他按鍵。"</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"無法設定捷徑。"</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"使用鍵盤操作"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"學習鍵盤快速鍵"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"使用觸控板操作"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 4afb67d..f323021 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -306,7 +306,7 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Sebenzisa iBluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ixhunyiwe"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Ukwabelana Ngokuqoshiwe"</string>
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="3227408556754456024">"Thepha ukuze ushintshe noma wabelane ngokulalelwayo"</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active" msgid="8680997711431098238">"Isekela ukwabelana ngokuqoshiwe"</string>
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Ilondoloziwe"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"nqamula"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"yenza kusebenze"</string>
@@ -528,6 +528,8 @@
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Amawijethi wesikrini esikhiyiwe"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Ukuze uvule i-app usebenzisa iwijethi, uzodinga ukuqinisekisa ukuthi nguwe. Futhi, khumbula ukuthi noma ubani angakwazi ukuzibuka, nanoma ithebhulethi yakho ikhiyiwe. Amanye amawijethi kungenzeka abengahloselwe ukukhiya isikrini sakho futhi kungenzeka awaphephile ukuthi angafakwa lapha."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Ngiyezwa"</string>
+    <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"Amawijethi"</string>
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="511359420883794513">"Ukufaka Amawijethi esikrinini sokukhiya njengesinqamuleli, qinisekisa ukuthi inikwe amandla kumasethingi."</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>
@@ -780,6 +782,7 @@
     <string name="notification_channel_summary_priority_all" msgid="7151752959650048285">"Ivela phezu kwezaziso zengxoxo futhi njengesithombe sephrofayela esikrinini sokukhiya, ivela njengebhamuza, ukuphazamisa okuthi Ungaphazamisi"</string>
     <string name="notification_priority_title" msgid="2079708866333537093">"Okubalulekile"</string>
     <string name="no_shortcut" msgid="8257177117568230126">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> ayisekeli izici zengxoxo"</string>
+    <string name="notification_guts_bundle_feedback" msgid="5393570876655201459">"Nikeza Impendulo Yenqwaba"</string>
     <string name="notification_unblockable_desc" msgid="2073030886006190804">"Lezi zaziso azikwazi ukushintshwa."</string>
     <string name="notification_unblockable_call_desc" msgid="5907328164696532169">"Izaziso zekholi azikwazi ukushintshwa."</string>
     <string name="notification_multichannel_desc" msgid="7414593090056236179">"Leli qembu lezaziso alikwazi ukulungiselelwa lapha"</string>
@@ -872,6 +875,14 @@
     <string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"Shintshela ku-app ngakwesokunxele noma ngaphezulu ngenkathi usebenzisa ukuhlukanisa isikrini"</string>
     <string name="system_multitasking_replace" msgid="7410071959803642125">"Ngesikhathi sokuhlukaniswa kwesikrini: shintsha i-app ngenye"</string>
     <string name="system_multitasking_move_to_next_display" msgid="6169737557526976997">"Hambisa iwindi elisebenzayo phakathi kwezibonisi"</string>
+    <!-- no translation found for system_desktop_mode_snap_left_window (8636204689945162298) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_snap_right_window (2162560187639411929) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_toggle_maximize_window (4084100093691768239) -->
+    <skip />
+    <!-- no translation found for system_desktop_mode_minimize_window (1248714536732927092) -->
+    <skip />
     <string name="keyboard_shortcut_group_input" msgid="6888282716546625610">"Okokufaka"</string>
     <string name="input_switch_input_language_next" msgid="3782155659868227855">"Shintshela olimini olulandelayo"</string>
     <string name="input_switch_input_language_previous" msgid="6043341362202336623">"Shintshela olimini lwangaphambili"</string>
@@ -1220,8 +1231,7 @@
     <string name="media_output_broadcast_edit_hint_no_more_than_max" msgid="3923625800037673922">"Sebenzisa isinhlamvu ezimbalwa kuneziyi-<xliff:g id="LENGTH">%1$d</xliff:g>"</string>
     <string name="build_number_clip_data_label" msgid="3623176728412560914">"Yakha inombolo"</string>
     <string name="build_number_copy_toast" msgid="877720921605503046">"Yakha inombolo ekopishelwe kubhodi yokunamathisela."</string>
-    <!-- no translation found for copy_to_clipboard_a11y_action (4312789069718446749) -->
-    <skip />
+    <string name="copy_to_clipboard_a11y_action" msgid="4312789069718446749">"kopishela ebhodini lokunamathisela."</string>
     <string name="basic_status" msgid="2315371112182658176">"Vula ingxoxo"</string>
     <string name="select_conversation_title" msgid="6716364118095089519">"Amawijethi wengxoxo"</string>
     <string name="select_conversation_text" msgid="3376048251434956013">"Thepha ingxoxo ukuyengeza Kusikrini sakho sasekhaya"</string>
@@ -1357,8 +1367,7 @@
     <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Ukuze uthole ukulungiswa okuphezulu, phendula ifoni"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Idivayisi egoqekayo iyembulwa"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Idivayisi egoqekayo iphendulwa nxazonke"</string>
-    <!-- no translation found for rear_display_unfolded_front_screen_on (5946436677205643170) -->
-    <skip />
+    <string name="rear_display_unfolded_front_screen_on" msgid="5946436677205643170">"Isikrini sangaphambili sivuliwe"</string>
     <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"kugoqiwe"</string>
     <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"kuvuliwe"</string>
     <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
@@ -1418,7 +1427,9 @@
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Ukufinyeleleka"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Izinqamuleli zekhibhodi"</string>
     <string name="shortcut_helper_customize_mode_title" msgid="1467657117101096033">"Hlela izinqamuleli zekhibhodi ngendlela oyifisayo"</string>
-    <string name="shortcut_helper_customize_mode_sub_title" msgid="2479732335876820286">"Cindezela ukhiye ukuze unikeze isinqamuleli"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Susa isinqamuleli?"</string>
+    <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Cindezela ukhiye ukuze unikeze isinqamuleli"</string>
+    <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Lokhu kuzosula isinqamuleli sakho somuntu ngamunye unomphela."</string>
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Sesha izinqamuleli"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ayikho imiphumela yosesho"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Goqa isithonjana"</string>
@@ -1428,12 +1439,19 @@
     <string name="shortcut_helper_done_button_text" msgid="7249905942125386191">"Kwenziwe"</string>
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Nweba isithonjana"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"noma"</string>
+    <!-- no translation found for shortcut_helper_key_combinations_and_conjunction (6138186504075880224) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_key_combinations_forward_slash (1238652537199346970) -->
+    <skip />
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Hudula isibambi"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Amasethingi Ekhibhodi"</string>
     <string name="shortcut_helper_customize_dialog_set_shortcut_button_label" msgid="4754492225010429382">"Setha isinqamuleli"</string>
+    <string name="shortcut_helper_customize_dialog_remove_button_label" msgid="6546386970440176552">"Susa"</string>
     <string name="shortcut_helper_customize_dialog_cancel_button_label" msgid="5595546460431741178">"Khansela"</string>
     <string name="shortcut_helper_add_shortcut_dialog_placeholder" msgid="9154297849458741995">"Cindezela ukhiye"</string>
-    <string name="shortcut_helper_customize_dialog_error_message" msgid="5954264095841845768">"Inhlanganisela yokhiye isiyasetshenziswa kakade. Zama omunye ukhiye."</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Inhlanganisela yokhiye isiyasetshenziswa kakade. Zama omunye ukhiye."</string>
+    <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Isinqamuleli asikwazi ukusethwa."</string>
+    <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Funa usebenzisa ikhibhodi yakho"</string>
     <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Funda izinqamuleli zamakhibhodi"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Funa usebenzisa iphedi yokuthinta"</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 42e9092..113f3d2 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -172,6 +172,9 @@
     <!-- Minimum display time for a heads up notification, in milliseconds. -->
     <integer name="heads_up_notification_minimum_time">2000</integer>
 
+    <!-- Minimum display time for a heads up notification if throttling is enabled, in milliseconds. -->
+    <integer name="heads_up_notification_minimum_time_with_throttling">500</integer>
+
     <!-- Display time for a sticky heads up notification, in milliseconds. -->
     <integer name="sticky_heads_up_notification_time">60000</integer>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 411f36b..478050b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -236,6 +236,9 @@
     <!-- The size of a bluetooth indicator icon that displays next to the RSSI status icon. -->
     <dimen name="status_bar_connected_device_bt_indicator_size">17dp</dimen>
 
+    <!-- Height of a small notification in the status bar (2025 redesign version) -->
+    <dimen name="notification_2025_min_height">@*android:dimen/notification_2025_min_height</dimen>
+
     <!-- Height of a small notification in the status bar-->
     <dimen name="notification_min_height">@*android:dimen/notification_min_height</dimen>
 
@@ -1995,12 +1998,16 @@
     <dimen name="backlight_indicator_step_large_radius">28dp</dimen>
 
     <!-- Touchpad gestures tutorial-->
-    <!-- This value is in unit of dp/ms
+    <!-- This value is in dp/ms.
         TriggerSwipeUpTouchTracker (which is base for gesture tutorial implementation) uses value
         of 0.5dp but from manual testing it's too high and doesn't really feel like it's forcing
         slowing down. Also for tutorial it should be fine to lean to the side of being more strict
         rather than not strict enough and not teaching user the proper gesture as a result.-->
     <dimen name="touchpad_recent_apps_gesture_velocity_threshold">0.05dp</dimen>
+    <!-- This value is in dp/ms.
+        As above, it's not tied to system-wide value (defined in launcher's
+        quickstep_fling_threshold_speed) because for tutorial it's fine to be more strict. -->
+    <dimen name="touchpad_home_gesture_velocity_threshold">0.5dp</dimen>
     <!-- Normal gesture threshold is system_gestures_distance_threshold but for tutorial we can
          exaggerate gesture, which also works much better with live tracking -->
     <dimen name="touchpad_tutorial_gestures_distance_threshold">48dp</dimen>
@@ -2086,7 +2093,7 @@
     <dimen name="volume_dialog_floating_sliders_vertical_padding">10dp</dimen>
     <dimen name="volume_dialog_floating_sliders_vertical_padding_negative">-10dp</dimen>
     <dimen name="volume_dialog_floating_sliders_horizontal_padding">4dp</dimen>
-    <dimen name="volume_dialog_button_size">48dp</dimen>
+    <dimen name="volume_dialog_button_size">40dp</dimen>
     <dimen name="volume_dialog_slider_width">52dp</dimen>
     <dimen name="volume_dialog_slider_height">254dp</dimen>
 
@@ -2094,7 +2101,7 @@
 
     <dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
 
-    <dimen name="volume_dialog_ringer_drawer_button_size">40dp</dimen>
+    <dimen name="volume_dialog_ringer_drawer_button_size">@dimen/volume_dialog_button_size</dimen>
     <dimen name="volume_dialog_ringer_drawer_button_icon_radius">10dp</dimen>
     <dimen name="volume_dialog_ringer_selected_button_background_radius">20dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 713aa47..e417da4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -753,7 +753,7 @@
     <!-- QuickSettings: Bluetooth dialog device in audio sharing default summary [CHAR LIMIT=50]-->
     <string name="quick_settings_bluetooth_device_audio_sharing">Audio Sharing</string>
     <!-- QuickSettings: Bluetooth dialog device summary for devices that are capable of audio sharing and switching to active[CHAR LIMIT=NONE]-->
-    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active">Tap to switch or share audio</string>
+    <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active">Supports audio sharing</string>
     <!-- QuickSettings: Bluetooth dialog device saved default summary [CHAR LIMIT=NONE]-->
     <string name="quick_settings_bluetooth_device_saved">Saved</string>
     <!-- QuickSettings: Accessibility label to disconnect a device [CHAR LIMIT=NONE]-->
@@ -1336,10 +1336,12 @@
     <string name="communal_widgets_disclaimer_text">To open an app using a widget, you\u2019ll need to verify it\u2019s you. Also, keep in mind that anyone can view them, even when your tablet\u2019s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here.</string>
     <!-- Button for user to verify they understand the information presented. [CHAR LIMIT=50] -->
     <string name="communal_widgets_disclaimer_button">Got it</string>
-    <!-- Lockscreen affordance to open glanceable hub. [CHAR LIMIT=20] -->
+    <!-- Label for a lock screen affordance to show widgets on the lock screen. [CHAR LIMIT=20] -->
     <string name="glanceable_hub_lockscreen_affordance_label">Widgets</string>
-    <!-- Text explaining that the glanceable hub affordance is disabled. [CHAR LIMIT=NONE] -->
-    <string name="glanceable_hub_lockscreen_affordance_disabled_text">To add Widgets on the lock screen as a shortcut, make sure it is enabled in settings.</string>
+    <!-- Text explaining why the lock screen affordance to show widgets on the lockscreen is disabled and how to enable the affordance in settings. [CHAR LIMIT=NONE] -->
+    <string name="glanceable_hub_lockscreen_affordance_disabled_text">To add the \"Widgets\" shortcut, make sure \"Show widgets on lock screen\" is enabled in settings.</string>
+    <!-- Label for a button used to open Settings in order to enable showing widgets on the lock screen. [CHAR LIMIT=NONE] -->
+    <string name="glanceable_hub_lockscreen_affordance_action_button_label">Settings</string>
 
     <!-- Related to user switcher --><skip/>
 
@@ -2287,6 +2289,14 @@
     <string name="system_multitasking_replace">During split screen: replace an app from one to another</string>
     <!-- User visible title for the keyboard shortcut that moves a focused task to a next display [CHAR LIMIT=70] -->
     <string name="system_multitasking_move_to_next_display">Move active window between displays</string>
+    <!-- User visible title for the keyboard shortcut that snaps a task to the left in desktop mode [CHAR LIMIT=70] -->
+    <string name="system_desktop_mode_snap_left_window">Move window to the left</string>
+    <!-- User visible title for the keyboard shortcut that snaps a task to the right in desktop mode [CHAR LIMIT=70] -->
+    <string name="system_desktop_mode_snap_right_window">Move window to the right</string>
+    <!-- User visible title for the keyboard shortcut that toggles between maximizing and restoring a task's previous bounds in desktop mode [CHAR LIMIT=70] -->
+    <string name="system_desktop_mode_toggle_maximize_window">Maximize window</string>
+    <!-- User visible title for the keyboard shortcut that minimizes a task in desktop mode [CHAR LIMIT=70] -->
+    <string name="system_desktop_mode_minimize_window">Minimize window</string>
 
     <!-- User visible title for the input keyboard shortcuts list. [CHAR LIMIT=70] -->
     <string name="keyboard_shortcut_group_input">Input</string>
@@ -3728,10 +3738,6 @@
          that shows the user which keyboard shortcuts they can use. The "Multitasking" shortcuts are
          for example "Enter split screen". [CHAR LIMIT=NONE] -->
     <string name="shortcut_helper_category_multitasking">Multitasking</string>
-    <!-- Title of the keyboard shortcut helper category "Recent apps". The helper is a component
-         that shows the user which keyboard shortcuts they can use. The "Recent apps" shortcuts are
-         for example "Cycle through recent apps". [CHAR LIMIT=NONE] -->
-    <string name="shortcutHelper_category_recent_apps">Recent apps</string>
     <!-- Title of the keyboard shortcut helper category "Split screen". The helper is a component
          that shows the user which keyboard shortcuts they can use. The "Split screen" shortcuts are
          for example "Move current app to left split". [CHAR LIMIT=NONE] -->
@@ -3764,6 +3770,11 @@
          The helper is a component that shows the user which keyboard shortcuts they can use. Also
          allows the user to add/remove custom shortcuts.[CHAR LIMIT=NONE] -->
     <string name="shortcut_customize_mode_remove_shortcut_dialog_title">Remove shortcut?</string>
+    <!-- Title at the top of the keyboard shortcut helper reset shortcut dialog. This dialog allows
+         the user to remove all custom shortcuts the user has set, resetting to default shortcuts only.
+         Shortcut helper is a component that shows the user which keyboard shortcuts they can use. Also
+         allows the user to add/remove custom shortcuts.[CHAR LIMIT=NONE] -->
+    <string name="shortcut_customize_mode_reset_shortcut_dialog_title">Reset back to default?</string>
     <!-- Sub title at the top of the keyboard shortcut helper customization dialog. Explains to the
          user what action they need to take in the customization dialog to assign a new custom shortcut.
          The shortcut customize dialog allows users to add/remove custom shortcuts
@@ -3774,6 +3785,10 @@
          users to add/remove custom shortcuts
          [CHAR LIMIT=NONE] -->
     <string name="shortcut_customize_mode_remove_shortcut_description">This will delete your custom shortcut permanently.</string>
+    <!-- Sub title at the top of the reset custom shortcut dialog. Explains to the user that the action
+         they're about to take will remove all custom shortcuts they have set, resetting to default shortcuts only.
+         The shortcut customize dialog allows users to add/remove custom shortcuts [CHAR LIMIT=NONE] -->
+    <string name="shortcut_customize_mode_reset_shortcut_description">This will delete all your custom shortcuts permanently.</string>
     <!-- Placeholder text shown in the search box of the keyboard shortcut helper, when the user
          hasn't typed in anything in the search box yet. The helper is a  component that shows the
          user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
@@ -3799,6 +3814,9 @@
          shortcut helper The helper is a  component that shows the  user which keyboard shortcuts
          they can use. [CHAR LIMIT=NONE] -->
     <string name="shortcut_helper_customize_button_text">Customize</string>
+    <!-- Description text of the button that allows user to resets all custom shortcuts in keyboard
+         shortcut helper when in customization mode. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_reset_button_text">Reset</string>
     <!-- Description text of the button that allows user to exit shortcut customization mode in
          keyboard shortcut helper The helper is a  component that shows the  user which keyboard
          shortcuts they can use. [CHAR LIMIT=NONE] -->
@@ -3816,6 +3834,14 @@
          [CHAR LIMIT=NONE]
           -->
     <string name="shortcut_helper_key_combinations_or_separator">or</string>
+    <!-- Word that combines different possible key combinations of a shortcut. For example the
+         "Go to home screen" shortcut could be triggered using "ctrl" plus "h".
+         This is only used for accessibility content descriptions of the key combinations.
+         [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_key_combinations_and_conjunction">plus</string>
+    <!-- Word that represents the forward slash character "/". To be used only in accessibility
+         content descriptions. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_key_combinations_forward_slash">forward slash</string>
     <!-- Content description of the drag handle that allows to swipe to dismiss the shortcut helper.
          The helper is a  component that shows the  user which keyboard shortcuts they can
          use. The helper shows shortcuts in categories, which can be collapsed or expanded.
@@ -3833,6 +3859,10 @@
          confirm and remove previously added custom shortcut. The helper is a  component that
          shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
     <string name="shortcut_helper_customize_dialog_remove_button_label">Remove</string>
+    <!-- Label on the reset shortcut button in keyboard shortcut helper customize dialog, that allows user to
+         confirm and reset all added custom shortcut. The helper is a  component that
+         shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_customize_dialog_reset_button_label">Yes, reset</string>
     <!-- Label on the cancel button in keyboard shortcut helper customize dialog, that allows user to
          cancel and exit shortcut customization dialog, returning to the main shortcut helper page.
          The helper is a  component that shows the user which keyboard shortcuts they can use.
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index e14008a..9aa7137 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -831,6 +831,11 @@
         <item name="android:textColor">?attr/onSurfaceVariant</item>
     </style>
 
+    <style name="TextAppearance.QSEditTitle" >
+        <item name="android:fontFamily">"gsf-title-medium-emphasized"</item>
+        <item name="android:textColor">?attr/onSurfaceVariant</item>
+    </style>
+
     <style name="QSCustomizeToolbar" parent="@*android:style/Widget.DeviceDefault.Toolbar">
         <item name="android:textColor">?attr/onSurface</item>
         <item name="android:elevation">10dp</item>
@@ -1079,11 +1084,6 @@
         <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen -->
         <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
         <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
-
-        <!--
-            TODO(b/309578419): Make the activity handle insets properly and then remove this.
-        -->
-        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
     </style>
 
     <style name="Widget.SliceView.VolumePanel">
diff --git a/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml b/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml
index 877637e..1607121 100644
--- a/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml
+++ b/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml
@@ -17,10 +17,10 @@
 <MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto">
     <Transition
-        android:id="@+id/transition"
+        android:id="@+id/close_to_open_transition"
         app:constraintSetEnd="@+id/volume_dialog_ringer_drawer_open"
         app:constraintSetStart="@+id/volume_dialog_ringer_drawer_close"
-        app:transitionEasing="path(0.05f, 0.7f, 0.1f, 1f)"
+        app:transitionEasing="cubic(0.05, 0.7, 0.1, 1.0)"
         app:duration="400">
     </Transition>
 
diff --git a/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/5.json b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/5.json
new file mode 100644
index 0000000..c5a83c4
--- /dev/null
+++ b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/5.json
@@ -0,0 +1,95 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 5,
+    "identityHash": "a83f96ef4babe730b3a00e8acb777a25",
+    "entities": [
+      {
+        "tableName": "communal_widget_table",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL, `user_serial_number` INTEGER NOT NULL DEFAULT -1, `span_y` INTEGER NOT NULL DEFAULT 3, `span_y_new` INTEGER NOT NULL DEFAULT 1)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "widgetId",
+            "columnName": "widget_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "componentName",
+            "columnName": "component_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "itemId",
+            "columnName": "item_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "userSerialNumber",
+            "columnName": "user_serial_number",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "-1"
+          },
+          {
+            "fieldPath": "spanY",
+            "columnName": "span_y",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "3"
+          },
+          {
+            "fieldPath": "spanYNew",
+            "columnName": "span_y_new",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "uid"
+          ]
+        }
+      },
+      {
+        "tableName": "communal_item_rank_table",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "rank",
+            "columnName": "rank",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "uid"
+          ]
+        }
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a83f96ef4babe730b3a00e8acb777a25')"
+    ]
+  }
+}
\ No newline at end of file
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 ab61190..fc536bd 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
@@ -25,9 +25,17 @@
 import android.annotation.LongDef;
 import android.content.Context;
 import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+import android.util.Log;
 import android.view.ViewConfiguration;
 import android.view.WindowManagerPolicyConstants;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.policy.ScreenDecorationsUtils;
 
 import java.lang.annotation.Retention;
@@ -39,10 +47,7 @@
  */
 public class QuickStepContract {
 
-    public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
-    public static final String KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER = "extra_unfold_animation";
-    // See ISysuiUnlockAnimationController.aidl
-    public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
+    private static final String TAG = "QuickStepContract";
 
     public static final String NAV_BAR_MODE_3BUTTON_OVERLAY =
             WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
@@ -412,4 +417,20 @@
     public static boolean supportsRoundedCornersOnWindows(Resources resources) {
         return ScreenDecorationsUtils.supportsRoundedCornersOnWindows(resources);
     }
+
+    /**
+     * Adds the provided interface to the bundle using the interface descriptor as the key
+     */
+    public static void addInterface(@Nullable IInterface iInterface, @NonNull Bundle out) {
+        if (iInterface != null) {
+            IBinder binder = iInterface.asBinder();
+            if (binder != null) {
+                try {
+                    out.putIBinder(binder.getInterfaceDescriptor(), binder);
+                } catch (RemoteException e) {
+                    Log.d(TAG, "Invalid interface description " + binder, e);
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index b116e29..dcbacec 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static com.android.systemui.Flags.simPinUseSlotId;
 import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ACTIVE_DATA_SUB_CHANGED;
 import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_TELEPHONY_CAPABLE;
 import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_REFRESH_CARRIER_INFO;
@@ -368,10 +369,12 @@
 
         for (int i = 0; i < numSubs; i++) {
             int subId = subs.get(i).getSubscriptionId();
+            int slotId = subs.get(i).getSimSlotIndex();
             carrierNames[i] = "";
             subsIds[i] = subId;
-            subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
-            int simState = mKeyguardUpdateMonitor.getSimState(subId);
+            subOrderBySlot[slotId] = i;
+            int simState = simPinUseSlotId() ? mKeyguardUpdateMonitor.getSimStateForSlotId(slotId)
+                    :  mKeyguardUpdateMonitor.getSimState(subId);
             CharSequence carrierName = subs.get(i).getCarrierName();
             CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
             mLogger.logUpdateLoopStart(subId, simState, String.valueOf(carrierName));
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index df9f705..5af80cb 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -63,6 +63,7 @@
 import com.android.systemui.plugins.clocks.ZenData
 import com.android.systemui.plugins.clocks.ZenData.ZenMode
 import com.android.systemui.res.R as SysuiR
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.regionsampling.RegionSampler
 import com.android.systemui.statusbar.policy.BatteryController
@@ -95,9 +96,10 @@
     private val broadcastDispatcher: BroadcastDispatcher,
     private val batteryController: BatteryController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    // TODO b/362719719 - We should use the configuration controller associated with the display.
     private val configurationController: ConfigurationController,
     @DisplaySpecific private val resources: Resources,
-    private val context: Context,
+    @DisplaySpecific val context: Context,
     @Main private val mainExecutor: DelayableExecutor,
     @Background private val bgExecutor: Executor,
     private val clockBuffers: ClockMessageBuffers,
@@ -465,6 +467,15 @@
         batteryController.addCallback(batteryCallback)
         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
         zenModeController.addCallback(zenModeCallback)
+        if (SceneContainerFlag.isEnabled) {
+            handleDoze(
+                when (AOD) {
+                    keyguardTransitionInteractor.getCurrentState() -> 1f
+                    keyguardTransitionInteractor.getStartedState() -> 1f
+                    else -> 0f
+                }
+            )
+        }
         disposableHandle =
             parent.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 8ed675c..6ef7de4 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -16,7 +16,10 @@
 
 package com.android.keyguard;
 
+import static com.android.systemui.Flags.gsfBouncer;
+
 import android.content.Context;
+import android.graphics.Typeface;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
@@ -122,6 +125,9 @@
                 textId = com.android.internal.R.string.lockscreen_emergency_call;
             }
             setText(textId);
+            if (gsfBouncer()) {
+                setTypeface(Typeface.create("gsf-title-medium", Typeface.NORMAL));
+            }
         } else {
             setVisibility(View.GONE);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index add459b..1083136 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -44,6 +44,7 @@
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.views.NavigationBarView;
 import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.shade.data.repository.ShadeDisplaysRepository;
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -103,7 +104,8 @@
             };
 
     @Inject
-    public KeyguardDisplayManager(Context context,
+    public KeyguardDisplayManager(
+            @ShadeDisplayAware Context context,
             Lazy<NavigationBarController> navigationBarControllerLazy,
             DisplayTracker displayTracker,
             @Main Executor mainExecutor,
@@ -331,7 +333,8 @@
         private boolean mIsInConcurrentDisplayState;
 
         @Inject
-        DeviceStateHelper(Context context,
+        DeviceStateHelper(
+                @ShadeDisplayAware Context context,
                 DeviceStateManager deviceStateManager,
                 @Main Executor mainExecutor) {
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 6bcacd0..fcaccd2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -32,6 +32,7 @@
 import static androidx.constraintlayout.widget.ConstraintSet.TOP;
 import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
 
+import static com.android.systemui.Flags.gsfBouncer;
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
 
 import static java.lang.Integer.max;
@@ -51,6 +52,7 @@
 import android.graphics.BlendMode;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.graphics.Typeface;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
@@ -1335,6 +1337,9 @@
                     true);
             mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher);
             mUserSwitcher = mView.findViewById(R.id.user_switcher_header);
+            if (gsfBouncer()) {
+                mUserSwitcher.setTypeface(Typeface.create("gsf-label-medium", Typeface.NORMAL));
+            }
         }
 
         interface UserSwitcherCallback {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index 5a02486..07bd813 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.shared.R as sharedR
 import com.android.systemui.shade.NotificationShadeWindowView
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
@@ -43,7 +44,7 @@
 class KeyguardUnfoldTransition
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val keyguardRootView: KeyguardRootView,
     private val shadeWindowView: NotificationShadeWindowView,
     statusBarStateController: StatusBarStateController,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 8ca0e80..a703b02 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -44,6 +44,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.systemui.Flags.simPinBouncerReset;
+import static com.android.systemui.Flags.simPinUseSlotId;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
 
 import android.annotation.AnyThread;
@@ -100,6 +101,7 @@
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
@@ -150,6 +152,7 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.StatusBarState;
@@ -172,7 +175,6 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -318,6 +320,7 @@
 
     private final Object mSimDataLockObject = new Object();
     HashMap<Integer, SimData> mSimDatas = new HashMap<>();
+    HashMap<Integer, SimData> mSimDatasBySlotId = new HashMap<>();
     HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
 
     private int mPhoneState;
@@ -616,16 +619,17 @@
                 // It is possible for active subscriptions to become invalid (-1), and these will
                 // not be present in the subscriptionInfo list
                 synchronized (mSimDataLockObject) {
-                    Iterator<Map.Entry<Integer, SimData>> iter = mSimDatas.entrySet().iterator();
+                    var iter = simPinUseSlotId() ? mSimDatasBySlotId.entrySet().iterator()
+                            : mSimDatas.entrySet().iterator();
+
                     while (iter.hasNext()) {
-                        Map.Entry<Integer, SimData> simData = iter.next();
-                        if (!activeSubIds.contains(simData.getKey())) {
-                            mSimLogger.logInvalidSubId(simData.getKey());
+                        SimData data = iter.next().getValue();
+                        if (!activeSubIds.contains(data.subId)) {
+                            mSimLogger.logInvalidSubId(data.subId, data.slotId);
                             iter.remove();
 
-                            SimData data = simData.getValue();
                             for (int j = 0; j < mCallbacks.size(); j++) {
-                                KeyguardUpdateMonitorCallback cb = mCallbacks.get(j).get();
+                                var cb = mCallbacks.get(j).get();
                                 if (cb != null) {
                                     cb.onSimStateChanged(data.subId, data.slotId, data.simState);
                                 }
@@ -634,10 +638,20 @@
                     }
 
                     for (int i = 0; i < changedSubscriptions.size(); i++) {
-                        SimData data = mSimDatas.get(
-                                changedSubscriptions.get(i).getSubscriptionId());
+                        SimData data;
+                        if (simPinUseSlotId()) {
+                            data = mSimDatasBySlotId.get(changedSubscriptions.get(i)
+                                .getSimSlotIndex());
+                        } else {
+                            data = mSimDatas.get(changedSubscriptions.get(i).getSubscriptionId());
+                        }
+                        if (data == null) {
+                            Log.w(TAG, "Null SimData for subscription: "
+                                    + changedSubscriptions.get(i));
+                            continue;
+                        }
                         for (int j = 0; j < mCallbacks.size(); j++) {
-                            KeyguardUpdateMonitorCallback cb = mCallbacks.get(j).get();
+                            var cb = mCallbacks.get(j).get();
                             if (cb != null) {
                                 cb.onSimStateChanged(data.subId, data.slotId, data.simState);
                             }
@@ -2150,7 +2164,7 @@
     @VisibleForTesting
     @Inject
     protected KeyguardUpdateMonitor(
-            Context context,
+            @ShadeDisplayAware Context context,
             UserTracker userTracker,
             @Main Looper mainLooper,
             BroadcastDispatcher broadcastDispatcher,
@@ -3408,13 +3422,17 @@
      * Removes all valid subscription info from the map for the given slotId.
      */
     private void invalidateSlot(int slotId) {
+        if (simPinUseSlotId()) {
+            return;
+        }
         synchronized (mSimDataLockObject) {
-            Iterator<Map.Entry<Integer, SimData>> iter = mSimDatas.entrySet().iterator();
+            var iter = simPinUseSlotId() ? mSimDatasBySlotId.entrySet().iterator()
+                    : mSimDatas.entrySet().iterator();
             while (iter.hasNext()) {
                 SimData data = iter.next().getValue();
                 if (data.slotId == slotId
                         && SubscriptionManager.isValidSubscriptionId(data.subId)) {
-                    mSimLogger.logInvalidSubId(data.subId);
+                    mSimLogger.logInvalidSubId(data.subId, data.slotId);
                     iter.remove();
                 }
             }
@@ -3427,7 +3445,7 @@
     @VisibleForTesting
     void handleSimStateChange(int subId, int slotId, int state) {
         Assert.isMainThread();
-        mSimLogger.logSimState(subId, slotId, state);
+        mSimLogger.logSimState(subId, slotId, TelephonyManager.simStateToString(state));
 
         boolean becameAbsent = ABSENT_SIM_STATE_LIST.contains(state);
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -3438,17 +3456,20 @@
                     || state == TelephonyManager.SIM_STATE_CARD_IO_ERROR) {
                 updateTelephonyCapable(true);
             }
-
             invalidateSlot(slotId);
         }
 
         // TODO(b/327476182): Preserve SIM_STATE_CARD_IO_ERROR sims in a separate data source.
         synchronized (mSimDataLockObject) {
-            SimData data = mSimDatas.get(subId);
+            SimData data = simPinUseSlotId() ? mSimDatasBySlotId.get(slotId) : mSimDatas.get(subId);
             final boolean changed;
             if (data == null) {
                 data = new SimData(state, slotId, subId);
-                mSimDatas.put(subId, data);
+                if (simPinUseSlotId()) {
+                    mSimDatasBySlotId.put(slotId, data);
+                } else {
+                    mSimDatas.put(subId, data);
+                }
                 changed = true; // no data yet; force update
             } else {
                 changed = (data.simState != state || data.subId != subId || data.slotId != slotId);
@@ -3727,7 +3748,8 @@
         callback.onTelephonyCapable(mTelephonyCapable);
 
         synchronized (mSimDataLockObject) {
-            for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
+            var simDatas = simPinUseSlotId() ? mSimDatasBySlotId : mSimDatas;
+            for (Entry<Integer, SimData> data : simDatas.entrySet()) {
                 final SimData state = data.getValue();
                 callback.onSimStateChanged(state.subId, state.slotId, state.simState);
             }
@@ -3860,7 +3882,8 @@
      */
     public boolean isSimPinSecure() {
         synchronized (mSimDataLockObject) {
-            for (SimData data : mSimDatas.values()) {
+            var simDatas = simPinUseSlotId() ? mSimDatasBySlotId : mSimDatas;
+            for (SimData data : simDatas.values()) {
                 if (isSimPinSecure(data.simState)) {
                     return true;
                 }
@@ -3870,6 +3893,10 @@
     }
 
     public int getSimState(int subId) {
+        if (simPinUseSlotId()) {
+            throw new UnsupportedOperationException("Method not supported with flag "
+                    + "simPinUseSlotId");
+        }
         synchronized (mSimDataLockObject) {
             if (mSimDatas.containsKey(subId)) {
                 return mSimDatas.get(subId).simState;
@@ -3879,12 +3906,32 @@
         }
     }
 
+    /**
+     * Find the sim state for a slot id, or SIM_STATE_UNKNOWN if not found.
+     */
+    public int getSimStateForSlotId(int slotId) {
+        if (!simPinUseSlotId()) {
+            throw new UnsupportedOperationException("Method not supported without flag "
+                    + "simPinUseSlotId");
+        }
+        synchronized (mSimDataLockObject) {
+            if (mSimDatasBySlotId.containsKey(slotId)) {
+                return mSimDatasBySlotId.get(slotId).simState;
+            } else {
+                return TelephonyManager.SIM_STATE_UNKNOWN;
+            }
+        }
+    }
+
     private int getSlotId(int subId) {
         synchronized (mSimDataLockObject) {
-            if (!mSimDatas.containsKey(subId)) {
-                refreshSimState(subId, SubscriptionManager.getSlotIndex(subId));
+            var simDatas = simPinUseSlotId() ? mSimDatasBySlotId : mSimDatas;
+            int slotId = SubscriptionManager.getSlotIndex(subId);
+            int index = simPinUseSlotId() ? slotId : subId;
+            if (!simDatas.containsKey(index)) {
+                refreshSimState(subId, slotId);
             }
-            SimData simData = mSimDatas.get(subId);
+            SimData simData = simDatas.get(index);
             return simData != null ? simData.slotId : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         }
     }
@@ -3928,16 +3975,19 @@
     private boolean refreshSimState(int subId, int slotId) {
         int state = mTelephonyManager.getSimState(slotId);
         synchronized (mSimDataLockObject) {
-            SimData data = mSimDatas.get(subId);
-
             if (!SubscriptionManager.isValidSubscriptionId(subId)) {
                 invalidateSlot(slotId);
             }
+            SimData data = simPinUseSlotId() ? mSimDatasBySlotId.get(slotId) : mSimDatas.get(subId);
 
             final boolean changed;
             if (data == null) {
                 data = new SimData(state, slotId, subId);
-                mSimDatas.put(subId, data);
+                if (simPinUseSlotId()) {
+                    mSimDatasBySlotId.put(slotId, data);
+                } else {
+                    mSimDatas.put(subId, data);
+                }
                 changed = true; // no data yet; force update
             } else {
                 changed = data.simState != state;
@@ -4033,10 +4083,17 @@
         for (int i = 0; i < list.size(); i++) {
             final SubscriptionInfo info = list.get(i);
             final int id = info.getSubscriptionId();
-            int slotId = getSlotId(id);
-            if (state == getSimState(id) && bestSlotId > slotId) {
-                resultId = id;
-                bestSlotId = slotId;
+            final int slotId = info.getSimSlotIndex();
+            if (simPinUseSlotId()) {
+                if (state == getSimStateForSlotId(slotId) && bestSlotId > slotId) {
+                    resultId = id;
+                    bestSlotId = slotId;
+                }
+            } else {
+                if (state == getSimState(id) && bestSlotId > slotId) {
+                    resultId = id;
+                    bestSlotId = slotId;
+                }
             }
         }
         return resultId;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 9513c8e..5a9cbce 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -174,9 +174,20 @@
 
     /**
      * Stop showing the alternate bouncer, if showing.
+     *
+     * <p>Should be like calling {@link #hideAlternateBouncer(boolean, boolean)} with a {@code true}
+     * {@code clearDismissAction} parameter.
      */
     void hideAlternateBouncer(boolean updateScrim);
 
+    /**
+     * Stop showing the alternate bouncer, if showing.
+     *
+     * @param updateScrim Whether to update the scrim
+     * @param clearDismissAction Whether the pending dismiss action should be cleared
+     */
+    void hideAlternateBouncer(boolean updateScrim, boolean clearDismissAction);
+
     // TODO: Deprecate registerStatusBar in KeyguardViewController interface. It is currently
     //  only used for testing purposes in StatusBarKeyguardViewManager, and it prevents us from
     //  achieving complete abstraction away from where the Keyguard View is mounted.
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 7fe4ec8..ebde8a3 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -15,11 +15,13 @@
  */
 package com.android.keyguard;
 
+import static com.android.systemui.Flags.gsfBouncer;
 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
 
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
+import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.os.PowerManager;
@@ -158,6 +160,9 @@
         int klondikeColor = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary)
                 .getDefaultColor();
         mDigitText.setTextColor(textColor);
+        if (gsfBouncer()) {
+            mDigitText.setTypeface(Typeface.create("gsf-label-large-emphasized", Typeface.NORMAL));
+        }
         mKlondikeText.setTextColor(klondikeColor);
 
         if (mAnimator != null) mAnimator.reloadColors(getContext());
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index fc42045..0305b5e 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -30,6 +30,7 @@
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.clocks.ClockMessageBuffers;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.shared.clocks.ClockRegistry;
 import com.android.systemui.shared.clocks.DefaultClockProvider;
 import com.android.systemui.util.ThreadAssert;
@@ -47,7 +48,7 @@
     @Provides
     @SysUISingleton
     public static ClockRegistry getClockRegistry(
-            @Application Context context,
+            @ShadeDisplayAware Context context,
             PluginManager pluginManager,
             @Application CoroutineScope scope,
             @Main CoroutineDispatcher mainDispatcher,
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/SimLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/SimLogger.kt
index a81698b..fb26c6a 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/SimLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/SimLogger.kt
@@ -47,12 +47,15 @@
 
     fun log(@CompileTimeConstant msg: String, level: LogLevel) = logBuffer.log(TAG, level, msg)
 
-    fun logInvalidSubId(subId: Int) {
+    fun logInvalidSubId(subId: Int, slotId: Int) {
         logBuffer.log(
             TAG,
             INFO,
-            { int1 = subId },
-            { "Previously active sub id $int1 is now invalid, will remove" },
+            {
+                int1 = subId
+                int2 = slotId
+            },
+            { "Previously active subId: $int1, slotId: $int2 is now invalid, will remove" },
         )
     }
 
@@ -94,16 +97,16 @@
         )
     }
 
-    fun logSimState(subId: Int, slotId: Int, state: Int) {
+    fun logSimState(subId: Int, slotId: Int, state: String) {
         logBuffer.log(
             TAG,
             DEBUG,
             {
                 int1 = subId
                 int2 = slotId
-                long1 = state.toLong()
+                str1 = state
             },
-            { "handleSimStateChange(subId=$int1, slotId=$int2, state=$long1)" },
+            { "handleSimStateChange(subId=$int1, slotId=$int2, state=$str1)" },
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index 3cf400a..5b43346 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -18,7 +18,6 @@
 
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
-import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 
 import static com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent;
@@ -48,7 +47,6 @@
 import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.Flags;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.model.SysUiState;
@@ -118,15 +116,13 @@
         @Override
         protected WindowMagnificationController createInstance(Display display) {
             final Context windowContext = mContext.createWindowContext(display,
-                    Flags.createWindowlessWindowMagnifier()
-                            ? TYPE_ACCESSIBILITY_OVERLAY
-                            : TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
-                    /* options */ null);
+                        TYPE_ACCESSIBILITY_OVERLAY,
+                        /* options */ null);
             windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
 
             Supplier<SurfaceControlViewHost> scvhSupplier = () ->
-                    Flags.createWindowlessWindowMagnifier() ? new SurfaceControlViewHost(mContext,
-                            mContext.getDisplay(), new InputTransferToken(), TAG) : null;
+                    new SurfaceControlViewHost(mContext,
+                            mContext.getDisplay(), new InputTransferToken(), TAG);
 
             return new WindowMagnificationController(
                     windowContext,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 7d5cf23..08d3e17 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -283,7 +283,7 @@
                 com.android.internal.R.integer.config_shortAnimTime));
         updateDimensions();
 
-        final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible();
+        final Size windowFrameSize = restoreMagnificationWindowFrameIndexAndSizeIfPossible();
         setMagnificationFrame(windowFrameSize.getWidth(), windowFrameSize.getHeight(),
                 mWindowBounds.width() / 2, mWindowBounds.height() / 2);
         computeBounceAnimationScale();
@@ -541,7 +541,7 @@
             return false;
         }
         mWindowBounds.set(currentWindowBounds);
-        final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible();
+        final Size windowFrameSize = restoreMagnificationWindowFrameIndexAndSizeIfPossible();
         final float newCenterX = (getCenterX()) * mWindowBounds.width() / oldWindowBounds.width();
         final float newCenterY = (getCenterY()) * mWindowBounds.height() / oldWindowBounds.height();
 
@@ -787,18 +787,6 @@
         mMagnificationFrame.set(initX, initY, initX + width, initY + height);
     }
 
-    private Size restoreMagnificationWindowFrameSizeIfPossible() {
-        if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
-            return restoreMagnificationWindowFrameIndexAndSizeIfPossible();
-        }
-
-        if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) {
-            return getDefaultMagnificationWindowFrameSize();
-        }
-
-        return mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity();
-    }
-
     private Size restoreMagnificationWindowFrameIndexAndSizeIfPossible() {
         if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) {
             notifyWindowSizeRestored(MagnificationSize.DEFAULT);
@@ -845,9 +833,7 @@
         }
         // Set the surface of the SurfaceView to black to avoid users seeing the contents below the
         // magnifier when the mirrored surface has an alpha less than 1.
-        if (Flags.addBlackBackgroundForWindowMagnifier()) {
-            mTransaction.setColor(mMirrorSurfaceView.getSurfaceControl(), COLOR_BLACK_ARRAY);
-        }
+        mTransaction.setColor(mMirrorSurfaceView.getSurfaceControl(), COLOR_BLACK_ARRAY);
         mTransaction.show(mMirrorSurface)
                 .reparent(mMirrorSurface, mMirrorSurfaceView.getSurfaceControl());
         modifyWindowMagnification(false);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
index ee36c6e..558c87c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
@@ -22,8 +22,6 @@
 import android.content.SharedPreferences;
 import android.util.Size;
 
-import com.android.systemui.Flags;
-
 /**
  * Class to handle SharedPreference for window magnification size.
  */
@@ -52,14 +50,10 @@
      * Saves the window frame size for current screen density.
      */
     public void saveIndexAndSizeForCurrentDensity(int index, Size size) {
-        if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
-            mWindowMagnificationSizePreferences.edit()
-                    .putString(getKey(),
-                            WindowMagnificationFrameSpec.serialize(index, size)).apply();
-        } else {
-            mWindowMagnificationSizePreferences.edit()
-                    .putString(getKey(), size.toString()).apply();
-        }
+        mWindowMagnificationSizePreferences
+                .edit()
+                .putString(getKey(), WindowMagnificationFrameSpec.serialize(index, size))
+                .apply();
     }
 
     /**
@@ -91,13 +85,9 @@
      * Gets the size preference for current screen density.
      */
     public Size getSizeForCurrentDensity() {
-        if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
-            return WindowMagnificationFrameSpec
-                    .deserialize(mWindowMagnificationSizePreferences.getString(getKey(), null))
-                    .getSize();
-        } else {
-            return Size.parseSize(mWindowMagnificationSizePreferences.getString(getKey(), null));
-        }
+        return WindowMagnificationFrameSpec.deserialize(
+                        mWindowMagnificationSizePreferences.getString(getKey(), null))
+                .getSize();
     }
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 2f0ca6e..9d9f569 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -59,7 +59,6 @@
 import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.Flags;
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
 import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
@@ -460,12 +459,8 @@
                 mAllowDiagonalScrollingView.setVisibility(View.VISIBLE);
                 mFullScreenButton.setVisibility(View.GONE);
                 if (selectedButtonIndex == MagnificationSize.FULLSCREEN) {
-                    if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
-                        selectedButtonIndex =
-                                windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
-                    } else {
-                        selectedButtonIndex = MagnificationSize.CUSTOM;
-                    }
+                    selectedButtonIndex =
+                            windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
                 }
                 break;
 
@@ -482,10 +477,8 @@
                 } else { // mode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
                     mEditButton.setVisibility(View.VISIBLE);
                     mAllowDiagonalScrollingView.setVisibility(View.VISIBLE);
-                    if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
-                        selectedButtonIndex =
-                                windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
-                    }
+                    selectedButtonIndex =
+                            windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
                 }
                 break;
 
@@ -581,13 +574,15 @@
                 || (configDiff & ActivityInfo.CONFIG_ASSETS_PATHS) != 0
                 || (configDiff & ActivityInfo.CONFIG_FONT_SCALE) != 0
                 || (configDiff & ActivityInfo.CONFIG_LOCALE) != 0
-                || (configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
+                || (configDiff & ActivityInfo.CONFIG_DENSITY) != 0
+                || (configDiff & ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT) != 0) {
             // We listen to following config changes to trigger layout inflation:
             // CONFIG_UI_MODE: theme change
             // CONFIG_ASSETS_PATHS: wallpaper change
             // CONFIG_FONT_SCALE: font size change
             // CONFIG_LOCALE: language change
             // CONFIG_DENSITY: display size change
+            // CONFIG_FONT_WEIGHT_ADJUSTMENT: bold text setting change
             mParams.width = getPanelWidth(mContext);
             mParams.accessibilityTitle = getAccessibilityWindowTitle(mContext);
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index ffb5f3d..559e6f7 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -20,7 +20,6 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT;
 import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY;
 import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE;
-import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
 
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
@@ -43,7 +42,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.NonNull;
 
@@ -77,9 +75,6 @@
 
     private final Context mContext;
     private final Configuration mConfiguration;
-    private final AccessibilityManager mAccessibilityManager;
-    private final AccessibilityManager.AccessibilityServicesStateChangeListener
-            mA11yServicesStateChangeListener = manager -> onTargetFeaturesChanged();
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final OnSettingsContentsChanged mSettingsContentsCallback;
     private final SecureSettings mSecureSettings;
@@ -147,10 +142,9 @@
         }
     };
 
-    MenuInfoRepository(Context context, AccessibilityManager accessibilityManager,
+    MenuInfoRepository(Context context,
             OnSettingsContentsChanged settingsContentsChanged, SecureSettings secureSettings) {
         mContext = context;
-        mAccessibilityManager = accessibilityManager;
         mConfiguration = new Configuration(context.getResources().getConfiguration());
         mSettingsContentsCallback = settingsContentsChanged;
         mSecureSettings = secureSettings;
@@ -244,13 +238,6 @@
                 mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
                 /* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver,
                 UserHandle.USER_CURRENT);
-        if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
-            mSecureSettings.registerContentObserverForUserSync(
-                    mSecureSettings.getUriFor(ENABLED_ACCESSIBILITY_SERVICES),
-                    /* notifyForDescendants */ false,
-                    mMenuTargetFeaturesContentObserver,
-                    UserHandle.USER_CURRENT);
-        }
         mSecureSettings.registerContentObserverForUserSync(
                 mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
                 /* notifyForDescendants */ false, mMenuSizeContentObserver,
@@ -264,11 +251,6 @@
                 /* notifyForDescendants */ false, mMenuFadeOutContentObserver,
                 UserHandle.USER_CURRENT);
         mContext.registerComponentCallbacks(mComponentCallbacks);
-
-        if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
-            mAccessibilityManager.addAccessibilityServicesStateChangeListener(
-                    mA11yServicesStateChangeListener);
-        }
     }
 
     void unregisterObserversAndCallbacks() {
@@ -276,11 +258,6 @@
         mContext.getContentResolver().unregisterContentObserver(mMenuSizeContentObserver);
         mContext.getContentResolver().unregisterContentObserver(mMenuFadeOutContentObserver);
         mContext.unregisterComponentCallbacks(mComponentCallbacks);
-
-        if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
-            mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
-                    mA11yServicesStateChangeListener);
-        }
     }
 
     interface OnSettingsContentsChanged {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index cb96e78..cfcaa4f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -42,8 +42,7 @@
             NavigationModeController navigationModeController) {
         mWindowManager = viewCaptureAwareWindowManager;
 
-        MenuViewModel menuViewModel = new MenuViewModel(
-                context, accessibilityManager, secureSettings);
+        MenuViewModel menuViewModel = new MenuViewModel(context, secureSettings);
         MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context, windowManager);
 
         mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
index f924784..46c407e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
@@ -17,7 +17,6 @@
 package com.android.systemui.accessibility.floatingmenu;
 
 import android.content.Context;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.MutableLiveData;
@@ -43,10 +42,9 @@
     private final MutableLiveData<Position> mPercentagePositionData = new MutableLiveData<>();
     private final MenuInfoRepository mInfoRepository;
 
-    MenuViewModel(Context context, AccessibilityManager accessibilityManager,
-            SecureSettings secureSettings) {
-        mInfoRepository = new MenuInfoRepository(context,
-                accessibilityManager, /* settingsContentsChanged= */ this, secureSettings);
+    MenuViewModel(Context context, SecureSettings secureSettings) {
+        mInfoRepository = new MenuInfoRepository(context, /* settingsContentsChanged= */ this,
+                secureSettings);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/activity/ActivityManagerModule.kt b/packages/SystemUI/src/com/android/systemui/activity/ActivityManagerModule.kt
new file mode 100644
index 0000000..db315e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/activity/ActivityManagerModule.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.activity
+
+import com.android.systemui.activity.data.repository.ActivityManagerRepository
+import com.android.systemui.activity.data.repository.ActivityManagerRepositoryImpl
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface ActivityManagerModule {
+    @Binds
+    @SysUISingleton
+    fun activityManagerRepository(impl: ActivityManagerRepositoryImpl): ActivityManagerRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt
new file mode 100644
index 0000000..94614b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.activity.data.repository
+
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.core.Logger
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+
+/** Repository for interfacing with [ActivityManager]. */
+interface ActivityManagerRepository {
+    /**
+     * Given a UID, creates a flow that emits true when the process with the given UID is visible to
+     * the user and false otherwise.
+     *
+     * @param identifyingLogTag a tag identifying who created this flow, used for logging.
+     */
+    fun createIsAppVisibleFlow(
+        creationUid: Int,
+        logger: Logger,
+        identifyingLogTag: String,
+    ): Flow<Boolean>
+}
+
+@SysUISingleton
+class ActivityManagerRepositoryImpl
+@Inject
+constructor(
+    @Background private val backgroundContext: CoroutineContext,
+    private val activityManager: ActivityManager,
+) : ActivityManagerRepository {
+    override fun createIsAppVisibleFlow(
+        creationUid: Int,
+        logger: Logger,
+        identifyingLogTag: String,
+    ): Flow<Boolean> {
+        return conflatedCallbackFlow {
+                val listener =
+                    object : ActivityManager.OnUidImportanceListener {
+                        override fun onUidImportance(uid: Int, importance: Int) {
+                            if (uid != creationUid) {
+                                return
+                            }
+                            val isAppVisible = isAppVisibleToUser(importance)
+                            logger.d({
+                                "$str1: #onUidImportance. importance=$int1, isAppVisible=$bool1"
+                            }) {
+                                str1 = identifyingLogTag
+                                int1 = importance
+                                bool1 = isAppVisible
+                            }
+                            trySend(isAppVisible)
+                        }
+                    }
+                try {
+                    // TODO(b/286258140): Replace this with the #addOnUidImportanceListener
+                    //  overload that filters to certain UIDs.
+                    activityManager.addOnUidImportanceListener(listener, IMPORTANCE_CUTPOINT)
+                } catch (e: SecurityException) {
+                    logger.e({ "$str1: Security exception on #addOnUidImportanceListener" }, e) {
+                        str1 = identifyingLogTag
+                    }
+                }
+
+                awaitClose { activityManager.removeOnUidImportanceListener(listener) }
+            }
+            .distinctUntilChanged()
+            .onStart {
+                try {
+                    val isVisibleOnStart =
+                        isAppVisibleToUser(activityManager.getUidImportance(creationUid))
+                    logger.d({ "$str1: Starting UID observation. isAppVisible=$bool1" }) {
+                        str1 = identifyingLogTag
+                        bool1 = isVisibleOnStart
+                    }
+                    emit(isVisibleOnStart)
+                } catch (e: SecurityException) {
+                    logger.e({ "$str1: Security exception on #getUidImportance" }, e) {
+                        str1 = identifyingLogTag
+                    }
+                    emit(false)
+                }
+            }
+            .flowOn(backgroundContext)
+    }
+
+    /** Returns true if the given [importance] represents an app that's visible to the user. */
+    private fun isAppVisibleToUser(importance: Int): Boolean {
+        return importance <= IMPORTANCE_CUTPOINT
+    }
+
+    companion object {
+        private const val IMPORTANCE_CUTPOINT = IMPORTANCE_FOREGROUND
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index e634726..1176cb0 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -19,6 +19,7 @@
 
 import static com.android.settingslib.flags.Flags.newStatusBarIcons;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.Flags.gsfQuickSettings;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -33,6 +34,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
+import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -384,6 +386,9 @@
         }
         float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null);
         mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
+        if (gsfQuickSettings()) {
+            mBatteryPercentView.setTypeface(Typeface.create("gsf-label-large", Typeface.NORMAL));
+        }
         if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
         addView(mBatteryPercentView, new LayoutParams(
                 LayoutParams.WRAP_CONTENT,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index b491c94..f6b6655 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -64,6 +64,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
+import com.android.systemui.biometrics.plugins.AuthContextPlugins;
 import com.android.systemui.biometrics.shared.model.BiometricModalities;
 import com.android.systemui.biometrics.shared.model.PromptKind;
 import com.android.systemui.biometrics.ui.CredentialView;
@@ -132,6 +133,7 @@
     private final int mEffectiveUserId;
     private final IBinder mWindowToken = new Binder();
     private final ViewCaptureAwareWindowManager mWindowManager;
+    @Nullable private final AuthContextPlugins mAuthContextPlugins;
     private final Interpolator mLinearOutSlowIn;
     private final LockPatternUtils mLockPatternUtils;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -289,6 +291,7 @@
             @Nullable List<FaceSensorPropertiesInternal> faceProps,
             @NonNull WakefulnessLifecycle wakefulnessLifecycle,
             @NonNull UserManager userManager,
+            @Nullable AuthContextPlugins authContextPlugins,
             @NonNull LockPatternUtils lockPatternUtils,
             @NonNull InteractionJankMonitor jankMonitor,
             @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider,
@@ -306,6 +309,7 @@
         WindowManager wm = getContext().getSystemService(WindowManager.class);
         mWindowManager = new ViewCaptureAwareWindowManager(wm, lazyViewCapture,
                 enableViewCaptureTracing());
+        mAuthContextPlugins = authContextPlugins;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mApplicationCoroutineScope = applicationCoroutineScope;
 
@@ -446,7 +450,7 @@
         final CredentialViewModel vm = mCredentialViewModelProvider.get();
         vm.setAnimateContents(animateContents);
         ((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel,
-                mBiometricCallback);
+                mBiometricCallback, mAuthContextPlugins);
 
         mLayout.addView(mCredentialView);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a5bd559..4faf6ff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -21,6 +21,7 @@
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.systemui.Flags.contAuthPlugin;
 import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
 
 import android.annotation.NonNull;
@@ -74,6 +75,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
+import com.android.systemui.biometrics.plugins.AuthContextPlugins;
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
@@ -108,6 +110,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -139,6 +142,7 @@
     private final ActivityTaskManager mActivityTaskManager;
     @Nullable private final FingerprintManager mFingerprintManager;
     @Nullable private final FaceManager mFaceManager;
+    @Nullable private final AuthContextPlugins mContextPlugins;
     private final Provider<UdfpsController> mUdfpsControllerFactory;
     private final CoroutineScope mApplicationCoroutineScope;
     private Job mBiometricContextListenerJob = null;
@@ -717,6 +721,7 @@
             @NonNull WindowManager windowManager,
             @Nullable FingerprintManager fingerprintManager,
             @Nullable FaceManager faceManager,
+            Optional<AuthContextPlugins> contextPlugins,
             Provider<UdfpsController> udfpsControllerFactory,
             @NonNull DisplayManager displayManager,
             @NonNull WakefulnessLifecycle wakefulnessLifecycle,
@@ -744,6 +749,7 @@
         mActivityTaskManager = activityTaskManager;
         mFingerprintManager = fingerprintManager;
         mFaceManager = faceManager;
+        mContextPlugins = contAuthPlugin() ? contextPlugins.orElse(null) : null;
         mUdfpsControllerFactory = udfpsControllerFactory;
         mUdfpsLogger = udfpsLogger;
         mDisplayManager = displayManager;
@@ -858,6 +864,10 @@
         mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
         mOrientationListener.enable();
         updateSensorLocations();
+
+        if (mContextPlugins != null) {
+            mContextPlugins.activate();
+        }
     }
 
     @Override
@@ -1350,7 +1360,7 @@
         config.mSensorIds = sensorIds;
         config.mScaleProvider = this::getScaleFactor;
         return new AuthContainerView(config, mApplicationCoroutineScope, mFpProps, mFaceProps,
-                wakefulnessLifecycle, userManager, lockPatternUtils,
+                wakefulnessLifecycle, userManager, mContextPlugins, lockPatternUtils,
                 mInteractionJankMonitor, mPromptSelectorInteractor, viewModel,
                 mCredentialViewModelProvider, bgExecutor, mVibratorHelper,
                 mLazyViewCapture, mMSDLPlayer);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 2863e29..a9133e4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -814,6 +814,11 @@
     private void showUdfpsOverlay(@NonNull UdfpsControllerOverlay overlay) {
         mExecution.assertIsMainThread();
 
+        if (mOverlay != null) {
+            Log.d(TAG, "showUdfpsOverlay | the overlay is already showing");
+            return;
+        }
+
         mOverlay = overlay;
         final int requestReason = overlay.getRequestReason();
         if (requestReason == REASON_AUTH_KEYGUARD
@@ -823,7 +828,7 @@
             return;
         }
         if (overlay.show(this, mOverlayParams)) {
-            Log.v(TAG, "showUdfpsOverlay | adding window reason=" + requestReason);
+            Log.d(TAG, "showUdfpsOverlay | adding window reason=" + requestReason);
             mOnFingerDown = false;
             mAttemptedToDismissKeyguard = false;
             mOrientationListener.enable();
@@ -832,7 +837,7 @@
                         overlay.getRequestId(), mSensorProps.sensorId);
             }
         } else {
-            Log.v(TAG, "showUdfpsOverlay | the overlay is already showing");
+            Log.d(TAG, "showUdfpsOverlay | the overlay is already showing");
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 2593ceb..51eb139 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -41,6 +41,7 @@
 import android.view.accessibility.AccessibilityManager
 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
 import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.app.viewcapture.ViewCaptureAwareWindowManager
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.animation.ActivityTransitionAnimator
@@ -73,7 +74,6 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 private const val TAG = "UdfpsControllerOverlay"
 
@@ -245,7 +245,7 @@
             return true
         }
 
-        Log.v(TAG, "showUdfpsOverlay | the overlay is already showing")
+        Log.d(TAG, "showUdfpsOverlay | the overlay is already showing")
         return false
     }
 
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 e2a8a69..60ce177 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
 import com.android.systemui.biometrics.data.repository.PromptRepository
 import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl
+import com.android.systemui.biometrics.plugins.AuthContextPlugins
 import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector
 import com.android.systemui.biometrics.udfps.EllipseOverlapDetector
 import com.android.systemui.biometrics.udfps.OverlapDetector
@@ -58,7 +59,7 @@
 /** Dagger module for all things biometric. */
 @Module
 interface BiometricsModule {
-    /** Starts AuthController.  */
+    /** Starts AuthController. */
     @Binds
     @IntoMap
     @ClassKey(AuthController::class)
@@ -103,8 +104,9 @@
     @SysUISingleton
     fun displayStateRepository(impl: DisplayStateRepositoryImpl): DisplayStateRepository
 
-    @BindsOptionalOf
-    fun deviceEntryUnlockTrackerViewBinder(): DeviceEntryUnlockTrackerViewBinder
+    @BindsOptionalOf fun authContextPlugins(): AuthContextPlugins
+
+    @BindsOptionalOf fun deviceEntryUnlockTrackerViewBinder(): DeviceEntryUnlockTrackerViewBinder
 
     companion object {
         /** Background [Executor] for HAL related operations. */
@@ -117,8 +119,7 @@
 
         @Provides fun providesUdfpsUtils(): UdfpsUtils = UdfpsUtils()
 
-        @Provides
-        fun provideIconProvider(context: Context): IconProvider = IconProvider(context)
+        @Provides fun provideIconProvider(context: Context): IconProvider = IconProvider(context)
 
         @Provides
         @SysUISingleton
@@ -136,7 +137,7 @@
                     EllipseOverlapDetectorParams(
                         minOverlap = values[3],
                         targetSize = values[2],
-                        stepSize = values[4].toInt()
+                        stepSize = values[4].toInt(),
                     )
                 )
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/plugins/AuthContextPlugins.kt b/packages/SystemUI/src/com/android/systemui/biometrics/plugins/AuthContextPlugins.kt
new file mode 100644
index 0000000..ca38e98
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/plugins/AuthContextPlugins.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.biometrics.plugins
+
+import com.android.systemui.plugins.AuthContextPlugin
+import com.android.systemui.plugins.PluginManager
+
+/** Wrapper interface for registering & forwarding events to all available [AuthContextPlugin]s. */
+interface AuthContextPlugins {
+    /** Finds and actives all plugins via SysUI's [PluginManager] (should be called at startup). */
+    fun activate()
+
+    /**
+     * Interact with all registered plugins.
+     *
+     * The provided [block] will be repeated for each available plugin.
+     */
+    suspend fun use(block: (AuthContextPlugin) -> Unit)
+
+    /**
+     * Like [use] but when no existing coroutine context is available.
+     *
+     * The [block] will be run on SysUI's general background context and can, optionally, be
+     * confined to [runOnMain] (defaults to a background thread).
+     */
+    fun useInBackground(runOnMain: Boolean = false, block: (AuthContextPlugin) -> Unit)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
index b28733f..dad140f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
@@ -11,6 +11,7 @@
 import android.widget.LinearLayout
 import android.widget.TextView
 import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.plugins.AuthContextPlugins
 import com.android.systemui.biometrics.ui.binder.CredentialViewBinder
 import com.android.systemui.biometrics.ui.binder.Spaghetti
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
@@ -33,6 +34,7 @@
         panelViewController: AuthPanelController,
         animatePanel: Boolean,
         legacyCallback: Spaghetti.Callback,
+        plugins: AuthContextPlugins?,
     ) {
         CredentialViewBinder.bind(
             this,
@@ -40,7 +42,8 @@
             viewModel,
             panelViewController,
             animatePanel,
-            legacyCallback
+            legacyCallback,
+            plugins,
         )
     }
 
@@ -78,7 +81,7 @@
             0,
             statusBarInsets.top,
             0,
-            if (keyboardInsets.bottom == 0) navigationInsets.bottom else keyboardInsets.bottom
+            if (keyboardInsets.bottom == 0) navigationInsets.bottom else keyboardInsets.bottom,
         )
         return WindowInsets.CONSUMED
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
index d9d286f..e80a79b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
@@ -8,6 +8,7 @@
 import android.view.WindowInsets.Type
 import android.widget.LinearLayout
 import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.plugins.AuthContextPlugins
 import com.android.systemui.biometrics.ui.binder.CredentialViewBinder
 import com.android.systemui.biometrics.ui.binder.Spaghetti
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
@@ -23,6 +24,7 @@
         panelViewController: AuthPanelController,
         animatePanel: Boolean,
         legacyCallback: Spaghetti.Callback,
+        plugins: AuthContextPlugins?,
     ) {
         CredentialViewBinder.bind(
             this,
@@ -30,7 +32,8 @@
             viewModel,
             panelViewController,
             animatePanel,
-            legacyCallback
+            legacyCallback,
+            plugins,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt
index e2f9895..f3e4917 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt
@@ -1,6 +1,7 @@
 package com.android.systemui.biometrics.ui
 
 import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.plugins.AuthContextPlugins
 import com.android.systemui.biometrics.ui.binder.Spaghetti
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 
@@ -29,5 +30,6 @@
         panelViewController: AuthPanelController,
         animatePanel: Boolean,
         legacyCallback: Spaghetti.Callback,
+        plugins: AuthContextPlugins?,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index 39543e7..10b1211 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -9,18 +9,21 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.plugins.AuthContextPlugins
 import com.android.systemui.biometrics.ui.CredentialPasswordView
 import com.android.systemui.biometrics.ui.CredentialPatternView
 import com.android.systemui.biometrics.ui.CredentialView
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.AuthContextPlugin
 import com.android.systemui.res.R
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.onEach
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 private const val ANIMATE_CREDENTIAL_INITIAL_DURATION_MS = 150
 
@@ -42,6 +45,7 @@
         panelViewController: AuthPanelController,
         animatePanel: Boolean,
         legacyCallback: Spaghetti.Callback,
+        plugins: AuthContextPlugins?,
         maxErrorDuration: Long = 3_000L,
         requestFocusForInput: Boolean = true,
     ) {
@@ -72,6 +76,10 @@
             }
 
             repeatOnLifecycle(Lifecycle.State.STARTED) {
+                if (plugins != null) {
+                    launch { plugins.notifyShowingBPCredential(view) }
+                }
+
                 // show prompt metadata
                 launch {
                     viewModel.header.collect { header ->
@@ -136,6 +144,12 @@
                             host.onCredentialAttemptsRemaining(info.remaining!!, info.message)
                         }
                 }
+
+                try {
+                    awaitCancellation()
+                } catch (_: Throwable) {
+                    plugins?.notifyHidingBPCredential()
+                }
             }
         }
 
@@ -172,3 +186,15 @@
         text = if (gone) "" else value
     }
     get() = text?.toString()
+
+private suspend fun AuthContextPlugins.notifyShowingBPCredential(view: View) = use { plugin ->
+    plugin.onShowingSensitiveSurface(
+        AuthContextPlugin.SensitiveSurface.BiometricPrompt(view = view, isCredential = true)
+    )
+}
+
+private fun AuthContextPlugins.notifyHidingBPCredential() = useInBackground { plugin ->
+    plugin.onHidingSensitiveSurface(
+        AuthContextPlugin.SensitiveSurface.BiometricPrompt(isCredential = true)
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
index 13c7209..4dc2a13 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
@@ -27,7 +27,6 @@
 import com.android.settingslib.bluetooth.LeAudioProfile
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
 import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.settingslib.flags.Flags.audioSharingQsDialogImprovement
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -69,7 +68,7 @@
                 }
                 deviceItem.type ==
                     DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
-                    if (audioSharingQsDialogImprovement()) {
+                    if (audioSharingInteractor.qsDialogImprovementAvailable()) {
                         withContext(mainDispatcher) {
                             delegateFactory
                                 .create(deviceItem.cachedBluetoothDevice)
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
index 65f1105..c4f26cd 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
@@ -16,10 +16,13 @@
 
 package com.android.systemui.bluetooth.qsdialog
 
+import android.content.Context
+import androidx.annotation.WorkerThread
 import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.settingslib.bluetooth.onPlaybackStarted
+import com.android.settingslib.bluetooth.onBroadcastMetadataChanged
+import com.android.settingslib.flags.Flags.audioSharingQsDialogImprovement
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import javax.inject.Inject
@@ -52,6 +55,8 @@
     suspend fun startAudioSharing()
 
     suspend fun audioSharingAvailable(): Boolean
+
+    suspend fun qsDialogImprovementAvailable(): Boolean
 }
 
 @SysUISingleton
@@ -59,11 +64,14 @@
 class AudioSharingInteractorImpl
 @Inject
 constructor(
+    private val context: Context,
     private val localBluetoothManager: LocalBluetoothManager?,
     private val audioSharingRepository: AudioSharingRepository,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) : AudioSharingInteractor {
 
+    private var previewEnabled: Boolean? = null
+
     override val isAudioSharingOn: Flow<Boolean> =
         flow { emit(audioSharingAvailable()) }
             .flatMapLatest { isEnabled ->
@@ -93,10 +101,10 @@
                     isAudioSharingOn
                         .mapNotNull { audioSharingOn ->
                             if (audioSharingOn) {
-                                // onPlaybackStarted could emit multiple times during one
-                                // audio sharing session, we only perform add source on the
-                                // first time
-                                profile.onPlaybackStarted.firstOrNull()
+                                // onBroadcastMetadataChanged could emit multiple times during one
+                                // audio sharing session, we only perform add source on the first
+                                // time
+                                profile.onBroadcastMetadataChanged.firstOrNull()
                             } else {
                                 null
                             }
@@ -141,6 +149,20 @@
     override suspend fun audioSharingAvailable(): Boolean {
         return audioSharingRepository.audioSharingAvailable()
     }
+
+    override suspend fun qsDialogImprovementAvailable(): Boolean {
+        return withContext(backgroundDispatcher) {
+            audioSharingQsDialogImprovement() || isAudioSharingPreviewEnabled()
+        }
+    }
+
+    @WorkerThread
+    private fun isAudioSharingPreviewEnabled(): Boolean {
+        if (previewEnabled == null) {
+            previewEnabled = BluetoothUtils.isAudioSharingPreviewEnabled(context.contentResolver)
+        }
+        return previewEnabled ?: false
+    }
 }
 
 @SysUISingleton
@@ -160,4 +182,6 @@
     override suspend fun startAudioSharing() {}
 
     override suspend fun audioSharingAvailable(): Boolean = false
+
+    override suspend fun qsDialogImprovementAvailable(): Boolean = false
 }
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 b255a4f..497d8cf 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -26,10 +26,10 @@
 import androidx.annotation.DimenRes
 import androidx.annotation.StringRes
 import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
-import com.android.settingslib.flags.Flags.audioSharingQsDialogImprovement
 import com.android.systemui.Prefs
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
@@ -56,7 +56,6 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 /** ViewModel for Bluetooth Dialog after clicking on the Bluetooth QS tile. */
@@ -145,7 +144,7 @@
                         bluetoothDeviceMetadataInteractor.metadataUpdate,
                         if (
                             audioSharingInteractor.audioSharingAvailable() &&
-                                audioSharingQsDialogImprovement()
+                                audioSharingInteractor.qsDialogImprovementAvailable()
                         ) {
                             audioSharingInteractor.audioSourceStateUpdate
                         } else {
@@ -165,7 +164,7 @@
                     .launchIn(this)
 
                 if (audioSharingInteractor.audioSharingAvailable()) {
-                    if (audioSharingQsDialogImprovement()) {
+                    if (audioSharingInteractor.qsDialogImprovementAvailable()) {
                         launch { audioSharingInteractor.handleAudioSourceWhenReady() }
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
index 8b5fde3..afe9a1e 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
@@ -55,7 +55,10 @@
             settingsLibAudioSharingRepository: SettingsLibAudioSharingRepository,
             @Background backgroundDispatcher: CoroutineDispatcher,
         ): AudioSharingRepository =
-            if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
+            if (
+                (Flags.enableLeAudioSharing() || Flags.audioSharingDeveloperOption()) &&
+                    localBluetoothManager != null
+            ) {
                 AudioSharingRepositoryImpl(
                     localBluetoothManager,
                     settingsLibAudioSharingRepository,
@@ -72,7 +75,10 @@
             impl: Lazy<AudioSharingInteractorImpl>,
             emptyImpl: Lazy<AudioSharingInteractorEmptyImpl>,
         ): AudioSharingInteractor =
-            if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
+            if (
+                (Flags.enableLeAudioSharing() || Flags.audioSharingDeveloperOption()) &&
+                    localBluetoothManager != null
+            ) {
                 impl.get()
             } else {
                 emptyImpl.get()
@@ -85,7 +91,10 @@
             audioSharingImpl: Lazy<AudioSharingDeviceItemActionInteractorImpl>,
             impl: Lazy<DeviceItemActionInteractorImpl>,
         ): DeviceItemActionInteractor =
-            if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
+            if (
+                (Flags.enableLeAudioSharing() || Flags.audioSharingDeveloperOption()) &&
+                    localBluetoothManager != null
+            ) {
                 audioSharingImpl.get()
             } else {
                 impl.get()
@@ -97,7 +106,10 @@
             localBluetoothManager: LocalBluetoothManager?
         ): List<DeviceItemFactory> = buildList {
             add(ActiveMediaDeviceItemFactory())
-            if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
+            if (
+                (Flags.enableLeAudioSharing() || Flags.audioSharingDeveloperOption()) &&
+                    localBluetoothManager != null
+            ) {
                 add(AudioSharingMediaDeviceItemFactory(localBluetoothManager))
                 add(AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager))
             }
@@ -112,7 +124,10 @@
             localBluetoothManager: LocalBluetoothManager?
         ): List<DeviceItemType> = buildList {
             add(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
-            if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
+            if (
+                (Flags.enableLeAudioSharing() || Flags.audioSharingDeveloperOption()) &&
+                    localBluetoothManager != null
+            ) {
                 add(DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE)
                 add(DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
index bba0050..a42ae03 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt
@@ -18,6 +18,7 @@
 
 import android.content.res.Resources
 import com.android.internal.R
+import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -36,7 +37,7 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     @Main private val resources: Resources,
-    configurationRepository: ConfigurationRepository,
+    @GlobalConfig configurationRepository: ConfigurationRepository,
 ) {
     /**
      * Whether to enable emergency services calls while the SIM card is locked. This is disabled in
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 61cd7c7..641400a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -71,7 +72,7 @@
     private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
     private val falsingCollector: FalsingCollector,
     private val dismissCallbackRegistry: DismissCallbackRegistry,
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val trustRepository: TrustRepository,
     @Application private val applicationScope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
index 1aaf4fb..ec9ee91 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.util.icuMessageFormat
 import javax.inject.Inject
@@ -62,7 +63,7 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val repository: SimBouncerRepository,
     private val telephonyManager: TelephonyManager,
-    @Main private val resources: Resources,
+    @ShadeDisplayAware private val resources: Resources,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val euiccManager: EuiccManager?,
     // TODO(b/307977401): Replace this with `MobileConnectionsInteractor` when available.
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerMessageView.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerMessageView.kt
index 79a11ee..ad49fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerMessageView.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerMessageView.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.bouncer.ui
 
 import android.content.Context
+import android.graphics.Typeface
 import android.util.AttributeSet
 import android.widget.LinearLayout
 import com.android.keyguard.BouncerKeyguardMessageArea
 import com.android.keyguard.KeyguardMessageArea
 import com.android.keyguard.KeyguardMessageAreaController
+import com.android.systemui.Flags
 import com.android.systemui.res.R
 
 class BouncerMessageView : LinearLayout {
@@ -37,10 +39,20 @@
     var secondaryMessageView: BouncerKeyguardMessageArea? = null
     var primaryMessage: KeyguardMessageAreaController<KeyguardMessageArea>? = null
     var secondaryMessage: KeyguardMessageAreaController<KeyguardMessageArea>? = null
+
     override fun onFinishInflate() {
         super.onFinishInflate()
         primaryMessageView = findViewById(R.id.bouncer_primary_message_area)
         secondaryMessageView = findViewById(R.id.bouncer_secondary_message_area)
+
+        if (Flags.gsfBouncer()) {
+            primaryMessageView?.apply {
+                typeface = Typeface.create("gsf-title-large-emphasized", Typeface.NORMAL)
+            }
+            secondaryMessageView?.apply {
+                typeface = Typeface.create("gsf-title-medium-emphasized", Typeface.NORMAL)
+            }
+        }
     }
 
     fun init(factory: KeyguardMessageAreaController.Factory) {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
index 12f06bb..8a4cc63 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -3,6 +3,8 @@
 import android.view.ViewGroup
 import com.android.keyguard.KeyguardMessageAreaController
 import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.Flags.contAuthPlugin
+import com.android.systemui.biometrics.plugins.AuthContextPlugins
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
@@ -17,6 +19,7 @@
 import com.android.systemui.log.BouncerLogger
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import dagger.Lazy
+import java.util.Optional
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,6 +63,7 @@
 constructor(
     private val legacyBouncerDependencies: Lazy<LegacyBouncerDependencies>,
     private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>,
+    private val contextPlugins: Optional<AuthContextPlugins>,
 ) {
     fun bind(view: ViewGroup) {
         if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) {
@@ -85,6 +89,7 @@
                 deps.bouncerMessageInteractor,
                 deps.bouncerLogger,
                 deps.selectedUserInteractor,
+                if (contAuthPlugin()) contextPlugins.orElse(null) else null,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index 71eda0c..434a9ce 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -22,11 +22,13 @@
 import android.window.OnBackAnimationCallback
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.keyguard.KeyguardMessageAreaController
 import com.android.keyguard.KeyguardSecurityContainerController
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardSecurityView
 import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.biometrics.plugins.AuthContextPlugins
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
 import com.android.systemui.bouncer.ui.BouncerViewDelegate
@@ -35,10 +37,10 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.log.BouncerLogger
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.AuthContextPlugin
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.filter
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Binds the bouncer container to its view model. */
 object KeyguardBouncerViewBinder {
@@ -52,6 +54,7 @@
         bouncerMessageInteractor: BouncerMessageInteractor,
         bouncerLogger: BouncerLogger,
         selectedUserInteractor: SelectedUserInteractor,
+        plugins: AuthContextPlugins?,
     ) {
         // Builds the KeyguardSecurityContainerController from bouncer view group.
         val securityContainerController: KeyguardSecurityContainerController =
@@ -94,7 +97,7 @@
 
                 override fun setDismissAction(
                     onDismissAction: ActivityStarter.OnDismissAction?,
-                    cancelAction: Runnable?
+                    cancelAction: Runnable?,
                 ) {
                     securityContainerController.setOnDismissAction(onDismissAction, cancelAction)
                 }
@@ -138,7 +141,7 @@
                                     it.bindMessageView(
                                         bouncerMessageInteractor,
                                         messageAreaControllerFactory,
-                                        bouncerLogger
+                                        bouncerLogger,
                                     )
                                 }
                             } else {
@@ -149,6 +152,13 @@
                                 securityContainerController.reset()
                                 securityContainerController.onPause()
                             }
+                            plugins?.apply {
+                                if (isShowing) {
+                                    notifyBouncerShowing(view)
+                                } else {
+                                    notifyBouncerGone()
+                                }
+                            }
                         }
                     }
 
@@ -209,7 +219,7 @@
                             securityContainerController.showMessage(
                                 it.message,
                                 it.colorStateList,
-                                /* animated= */ true
+                                /* animated= */ true,
                             )
                             viewModel.onMessageShown()
                         }
@@ -233,8 +243,19 @@
                     awaitCancellation()
                 } finally {
                     viewModel.setBouncerViewDelegate(null)
+                    plugins?.notifyBouncerGone()
                 }
             }
         }
     }
 }
+
+private suspend fun AuthContextPlugins.notifyBouncerShowing(view: View) = use { plugin ->
+    plugin.onShowingSensitiveSurface(
+        AuthContextPlugin.SensitiveSurface.LockscreenBouncer(view = view)
+    )
+}
+
+private fun AuthContextPlugins.notifyBouncerGone() = useInBackground { plugin ->
+    plugin.onHidingSensitiveSurface(AuthContextPlugin.SensitiveSurface.LockscreenBouncer())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
index 47d91374..5deb751 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -23,6 +23,7 @@
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.type
 import androidx.core.graphics.drawable.toBitmap
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.app.tracing.coroutines.traceCoroutine
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -35,6 +36,7 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardMediaKeyInteractor
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
@@ -48,7 +50,6 @@
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Models UI state for the content of the bouncer scene. */
 class BouncerSceneContentViewModel
@@ -67,6 +68,7 @@
     private val bouncerHapticPlayer: BouncerHapticPlayer,
     private val keyguardMediaKeyInteractor: KeyguardMediaKeyInteractor,
     private val bouncerActionButtonInteractor: BouncerActionButtonInteractor,
+    private val keyguardDismissActionInteractor: KeyguardDismissActionInteractor,
 ) : ExclusiveActivatable() {
     private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
     val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
@@ -421,6 +423,13 @@
         }
     }
 
+    /**
+     * Notifies that the bouncer UI has been destroyed (e.g. the composable left the composition).
+     */
+    fun onUiDestroyed() {
+        keyguardDismissActionInteractor.clearDismissAction()
+    }
+
     data class DialogViewModel(
         val text: String,
 
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 ccd953d..6f2a2c4 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.brightness.ui.compose
 
+import android.view.MotionEvent
 import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.gestures.Orientation
@@ -32,6 +33,7 @@
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
@@ -41,6 +43,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInteropFilter
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.colorResource
 import androidx.compose.ui.res.stringResource
@@ -103,11 +106,11 @@
             null
         }
 
-    val overriddenByAppState =
+    val overriddenByAppState by
         if (Flags.showToastWhenAppControlBrightness()) {
-            viewModel.brightnessOverriddenByWindow.collectAsStateWithLifecycle().value
+            viewModel.brightnessOverriddenByWindow.collectAsStateWithLifecycle()
         } else {
-            false
+            remember { mutableStateOf(false) }
         }
 
     PlatformSlider(
@@ -221,7 +224,16 @@
                     )
                     .then(if (viewModel.showMirror) Modifier.drawInOverlay() else Modifier)
                     .sliderBackground(containerColor)
-                    .fillMaxWidth(),
+                    .fillMaxWidth()
+                    .pointerInteropFilter {
+                        if (
+                            it.actionMasked == MotionEvent.ACTION_UP ||
+                                it.actionMasked == MotionEvent.ACTION_CANCEL
+                        ) {
+                            viewModel.emitBrightnessTouchForFalsing()
+                        }
+                        false
+                    },
             formatter = viewModel::formatValue,
             hapticsViewModelFactory = viewModel.hapticsViewModelFactory,
         )
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
index 1630ee5..7df7155 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
@@ -16,12 +16,14 @@
 
 package com.android.systemui.brightness.ui.viewmodel
 
-import androidx.compose.runtime.getValue
 import android.content.Context
 import androidx.annotation.StringRes
+import androidx.compose.runtime.getValue
 import com.android.systemui.brightness.domain.interactor.BrightnessPolicyEnforcementInteractor
 import com.android.systemui.brightness.domain.interactor.ScreenBrightnessInteractor
 import com.android.systemui.brightness.shared.model.GammaBrightness
+import com.android.systemui.classifier.Classifier
+import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
@@ -52,6 +54,7 @@
     private val brightnessPolicyEnforcementInteractor: BrightnessPolicyEnforcementInteractor,
     val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
     private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor,
+    private val falsingInteractor: FalsingInteractor,
     @Assisted private val supportsMirroring: Boolean,
     private val brightnessWarningToast: BrightnessWarningToast,
 ) : ExclusiveActivatable() {
@@ -87,6 +90,10 @@
         brightnessWarningToast.show(viewContext, resId)
     }
 
+    fun emitBrightnessTouchForFalsing() {
+        falsingInteractor.isFalseTouch(Classifier.BRIGHTNESS_SLIDER)
+    }
+
     /**
      * As a brightness slider is dragged, the corresponding events should be sent using this method.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
index 57e9ade..074b64e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.classifier.FalsingCollectorActual
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.Penalty
 import javax.inject.Inject
 
 /**
@@ -63,12 +64,13 @@
      * Inserts the given [result] into the falsing system, affecting future runs of the classifier
      * as if this was a result that had organically happened before.
      */
-    fun updateFalseConfidence(
-        result: FalsingClassifier.Result,
-    ) = collector.updateFalseConfidence(result)
+    fun updateFalseConfidence(result: FalsingClassifier.Result) =
+        collector.updateFalseConfidence(result)
 
     /** Returns `true` if the gesture should be rejected. */
-    fun isFalseTouch(
-        @Classifier.InteractionType interactionType: Int,
-    ): Boolean = manager.isFalseTouch(interactionType)
+    fun isFalseTouch(@Classifier.InteractionType interactionType: Int): Boolean =
+        manager.isFalseTouch(interactionType)
+
+    /** Returns `true` if the tap gesture should be rejected */
+    fun isFalseTap(@Penalty penalty: Int): Boolean = manager.isFalseTap(penalty)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
index 7fe0032..82bce0b 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -179,6 +179,14 @@
     }
 
     /**
+     * Only for testing. Get mSeekBarListener to the seekbar.
+     */
+    @VisibleForTesting
+    public SeekBarChangeListener getSeekBarChangeListener() {
+        return mSeekBarListener;
+    }
+
+    /**
      * Only for testing. Get {@link #mSeekbar} in the layout.
      */
     @VisibleForTesting
@@ -289,8 +297,10 @@
         void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control);
     }
 
-    private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
+    @VisibleForTesting
+    public class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
         private OnSeekBarWithIconButtonsChangeListener mOnSeekBarChangeListener = null;
+        private boolean mSeekByTouch = false;
 
         @Override
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
@@ -308,6 +318,14 @@
                             seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON);
                 } else {
                     mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
+                    if (!mSeekByTouch) {
+                        // Accessibility users could change the progress of the seekbar without
+                        // touching the seekbar or clicking the buttons. We will consider the
+                        // interaction has finished in this case.
+                        mOnSeekBarChangeListener.onUserInteractionFinalized(
+                                seekBar,
+                                OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER);
+                    }
                 }
             }
             updateIconViewIfNeeded(progress);
@@ -315,6 +333,7 @@
 
         @Override
         public void onStartTrackingTouch(SeekBar seekBar) {
+            mSeekByTouch = true;
             if (mOnSeekBarChangeListener != null) {
                 mOnSeekBarChangeListener.onStartTrackingTouch(seekBar);
             }
@@ -322,6 +341,7 @@
 
         @Override
         public void onStopTrackingTouch(SeekBar seekBar) {
+            mSeekByTouch = false;
             if (mOnSeekBarChangeListener != null) {
                 mOnSeekBarChangeListener.onStopTrackingTouch(seekBar);
                 mOnSeekBarChangeListener.onUserInteractionFinalized(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index db9a7f5..d648b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -23,6 +23,7 @@
 import com.android.compose.animation.scene.TransitionKey
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.communalHubOnMobile
 import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -216,6 +217,9 @@
                         communalSceneInteractor.changeScene(
                             newScene = CommunalScenes.Blank,
                             loggingReason = "hub timeout",
+                            transitionKey =
+                                if (communalHubOnMobile()) CommunalTransitionKeys.SimpleFade
+                                else null,
                         )
                         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT)
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 44dd34a..8ddd1ed 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelperImpl
+import com.android.systemui.communal.ui.compose.sceneTransitions
 import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.communal.util.CommunalColorsImpl
 import com.android.systemui.communal.widgets.CommunalWidgetModule
@@ -113,6 +114,7 @@
                 SceneContainerConfig(
                     sceneKeys = listOf(CommunalScenes.Blank, CommunalScenes.Communal),
                     initialSceneKey = CommunalScenes.Blank,
+                    transitions = sceneTransitions,
                     navigationDistances =
                         mapOf(CommunalScenes.Blank to 0, CommunalScenes.Communal to 1),
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt b/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
index c3d2683..41ea7b6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
@@ -29,9 +29,7 @@
 import kotlinx.coroutines.runBlocking
 
 /** Utilities for communal backup and restore. */
-class CommunalBackupUtils(
-    private val context: Context,
-) {
+class CommunalBackupUtils(private val context: Context) {
 
     /**
      * Retrieves a communal hub state protobuf that represents the current state of the communal
@@ -50,6 +48,8 @@
                     widgetId = widget.widgetId
                     componentName = widget.componentName
                     userSerialNumber = widget.userSerialNumber
+                    spanY = widget.spanY
+                    spanYNew = widget.spanYNew
                 }
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
index e72088f..679d071 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
@@ -26,9 +26,11 @@
 import androidx.room.migration.Migration
 import androidx.sqlite.db.SupportSQLiteDatabase
 import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelperImpl
+import com.android.systemui.communal.shared.model.SpanValue
+import com.android.systemui.communal.shared.model.toResponsive
 import com.android.systemui.res.R
 
-@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 4)
+@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 5)
 abstract class CommunalDatabase : RoomDatabase() {
     abstract fun communalWidgetDao(): CommunalWidgetDao
 
@@ -59,7 +61,12 @@
                             context.resources.getString(R.string.config_communalDatabase),
                         )
                         .also { builder ->
-                            builder.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
+                            builder.addMigrations(
+                                MIGRATION_1_2,
+                                MIGRATION_2_3,
+                                MIGRATION_3_4,
+                                MIGRATION_4_5,
+                            )
                             builder.fallbackToDestructiveMigration(dropAllTables = true)
                             callback?.let { callback -> builder.addCallback(callback) }
                         }
@@ -123,5 +130,30 @@
                     )
                 }
             }
+
+        /** This migration adds a new spanY column for responsive grid sizing. */
+        @VisibleForTesting
+        val MIGRATION_4_5 =
+            object : Migration(4, 5) {
+                override fun migrate(db: SupportSQLiteDatabase) {
+                    Log.i(TAG, "Migrating from version 4 to 5")
+                    db.execSQL(
+                        "ALTER TABLE communal_widget_table " +
+                            "ADD COLUMN span_y_new INTEGER NOT NULL DEFAULT 1"
+                    )
+                    db.query("SELECT item_id, span_y FROM communal_widget_table").use { cursor ->
+                        while (cursor.moveToNext()) {
+                            val id = cursor.getInt(cursor.getColumnIndex("item_id"))
+                            val spanYFixed =
+                                SpanValue.Fixed(cursor.getInt(cursor.getColumnIndex("span_y")))
+                            val spanYResponsive = spanYFixed.toResponsive()
+                            db.execSQL(
+                                "UPDATE communal_widget_table SET span_y_new = " +
+                                    "${spanYResponsive.value} WHERE item_id = $id"
+                            )
+                        }
+                    }
+                }
+            }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
index f9d2a84..6ef4bb8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
@@ -45,7 +45,12 @@
      * The vertical span of the widget. Span_Y default value corresponds to
      * CommunalContentSize.HALF.span
      */
-    @ColumnInfo(name = "span_y", defaultValue = "3") val spanY: Int,
+    @Deprecated("Use spanYNew instead")
+    @ColumnInfo(name = "span_y", defaultValue = "3")
+    val spanY: Int,
+
+    /** The vertical span of the widget in grid cell units. */
+    @ColumnInfo(name = "span_y_new", defaultValue = "1") val spanYNew: Int,
 ) {
     companion object {
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 3d40aa7..3907a37 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -27,7 +27,9 @@
 import androidx.sqlite.db.SupportSQLiteDatabase
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.communal.nano.CommunalHubState
-import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.SpanValue
+import com.android.systemui.communal.shared.model.toFixed
+import com.android.systemui.communal.shared.model.toResponsive
 import com.android.systemui.communal.widgets.CommunalWidgetHost
 import com.android.systemui.communal.widgets.CommunalWidgetModule.Companion.DEFAULT_WIDGETS
 import com.android.systemui.dagger.SysUISingleton
@@ -101,6 +103,7 @@
                                 componentName = name,
                                 rank = index,
                                 userSerialNumber = userSerialNumber,
+                                spanY = SpanValue.Fixed(3),
                             )
                     }
                 }
@@ -155,15 +158,16 @@
 
     @Query(
         "INSERT INTO communal_widget_table" +
-            "(widget_id, component_name, item_id, user_serial_number, span_y) " +
-            "VALUES(:widgetId, :componentName, :itemId, :userSerialNumber, :spanY)"
+            "(widget_id, component_name, item_id, user_serial_number, span_y, span_y_new) " +
+            "VALUES(:widgetId, :componentName, :itemId, :userSerialNumber, :spanY, :spanYNew)"
     )
     fun insertWidget(
         widgetId: Int,
         componentName: String,
         itemId: Long,
         userSerialNumber: Int,
-        spanY: Int = 3,
+        spanY: Int,
+        spanYNew: Int,
     ): Long
 
     @Query("INSERT INTO communal_item_rank_table(rank) VALUES(:rank)")
@@ -189,10 +193,12 @@
     }
 
     @Transaction
-    fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {
+    fun resizeWidget(appWidgetId: Int, spanY: SpanValue, widgetIdToRankMap: Map<Int, Int>) {
         val widget = getWidgetByIdNow(appWidgetId)
         if (widget != null) {
-            updateWidget(widget.copy(spanY = spanY))
+            updateWidget(
+                widget.copy(spanY = spanY.toFixed().value, spanYNew = spanY.toResponsive().value)
+            )
         }
         updateWidgetOrder(widgetIdToRankMap)
     }
@@ -203,7 +209,7 @@
         provider: ComponentName,
         rank: Int? = null,
         userSerialNumber: Int,
-        spanY: Int = CommunalContentSize.HALF.span,
+        spanY: SpanValue,
     ): Long {
         return addWidget(
             widgetId = widgetId,
@@ -220,7 +226,7 @@
         componentName: String,
         rank: Int? = null,
         userSerialNumber: Int,
-        spanY: Int = 3,
+        spanY: SpanValue,
     ): Long {
         val widgets = getWidgetsNow()
 
@@ -241,7 +247,8 @@
             componentName = componentName,
             itemId = insertItemRank(newRank),
             userSerialNumber = userSerialNumber,
-            spanY = spanY,
+            spanY = spanY.toFixed().value,
+            spanYNew = spanY.toResponsive().value,
         )
     }
 
@@ -264,7 +271,11 @@
         clearCommunalItemRankTable()
 
         state.widgets.forEach {
-            val spanY = if (it.spanY != 0) it.spanY else CommunalContentSize.HALF.span
+            // Check if there is a new value to restore. If so, restore that new value.
+            val spanYResponsive = if (it.spanYNew != 0) SpanValue.Responsive(it.spanYNew) else null
+            // If no new value, restore any existing old values.
+            val spanY = spanYResponsive ?: SpanValue.Fixed(it.spanY.coerceIn(3, 6))
+
             addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber, spanY)
         }
     }
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 7b54815..26abb48 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
@@ -20,9 +20,11 @@
 import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
 import android.content.IntentFilter
 import android.content.pm.UserInfo
+import android.content.res.Resources
 import android.os.UserHandle
 import android.provider.Settings
 import com.android.systemui.Flags.communalHub
+import com.android.systemui.Flags.glanceableHubV2
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.communal.data.model.CommunalEnabledState
 import com.android.systemui.communal.data.model.DisabledReason
@@ -33,6 +35,7 @@
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.util.kotlin.emitOnStart
@@ -53,13 +56,30 @@
     fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState>
 
     /**
-     * Returns true if both the communal trunk-stable flag and resource flag are enabled.
+     * Returns true if any glanceable hub functionality should be enabled via configs and flags.
      *
-     * The trunk-stable flag is controlled by server rollout and is on all devices. The resource
-     * flag is enabled via resource overlay only on products we want the hub to be present on.
+     * This should be used for preventing basic glanceable hub functionality from running on devices
+     * that don't need it.
+     *
+     * If the glanceable_hub_v2 flag is enabled, checks the config_glanceableHubEnabled Android
+     * config boolean. Otherwise, checks the old config_communalServiceEnabled config and
+     * communal_hub flag.
      */
     fun getFlagEnabled(): Boolean
 
+    /**
+     * Returns true if the Android config config_glanceableHubEnabled and the glanceable_hub_v2 flag
+     * are enabled.
+     *
+     * This should be used to flag off new glanceable hub or dream behavior that should launch
+     * together with the new hub experience that brings the hub to mobile.
+     *
+     * The trunk-stable flag is controlled by server rollout and is on all devices. The Android
+     * config flag is enabled via resource overlay only on products we want the hub to be present
+     * on.
+     */
+    fun getV2FlagEnabled(): Boolean
+
     /** Keyguard widgets enabled state by Device Policy Manager for the specified user. */
     fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean>
 
@@ -72,6 +92,7 @@
 @Inject
 constructor(
     @Background private val bgDispatcher: CoroutineDispatcher,
+    @Main private val resources: Resources,
     private val featureFlagsClassic: FeatureFlagsClassic,
     private val secureSettings: SecureSettings,
     private val broadcastDispatcher: BroadcastDispatcher,
@@ -79,7 +100,18 @@
 ) : CommunalSettingsRepository {
 
     override fun getFlagEnabled(): Boolean {
-        return featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
+        return if (getV2FlagEnabled()) {
+            true
+        } else {
+            // This config (exposed as a classic feature flag) is targeted only to tablet.
+            // TODO(b/379181581): clean up usages of communal_hub flag
+            featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
+        }
+    }
+
+    override fun getV2FlagEnabled(): Boolean {
+        return resources.getBoolean(com.android.internal.R.bool.config_glanceableHubEnabled) &&
+            glanceableHubV2()
     }
 
     override fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState> {
@@ -128,7 +160,7 @@
                     secureSettings.getIntForUser(
                         GLANCEABLE_HUB_BACKGROUND_SETTING,
                         CommunalBackgroundType.ANIMATED.value,
-                        user.id
+                        user.id,
                     )
                 CommunalBackgroundType.entries.find { type -> type.value == intType }
                     ?: CommunalBackgroundType.ANIMATED
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 29569f8..e44d78b 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
@@ -22,6 +22,7 @@
 import android.os.UserHandle
 import android.os.UserManager
 import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.Flags.communalResponsiveGrid
 import com.android.systemui.Flags.communalWidgetResizing
 import com.android.systemui.common.data.repository.PackageChangeRepository
 import com.android.systemui.common.shared.model.PackageInstallSession
@@ -33,6 +34,7 @@
 import com.android.systemui.communal.nano.CommunalHubState
 import com.android.systemui.communal.proto.toCommunalHubState
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.SpanValue
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.CommunalWidgetHost
 import com.android.systemui.communal.widgets.WidgetConfigurator
@@ -143,15 +145,21 @@
                     componentName = widget.componentName,
                     rank = rank.rank,
                     providerInfo = providers[widget.widgetId],
-                    spanY = widget.spanY,
+                    spanY = if (communalResponsiveGrid()) widget.spanYNew else widget.spanY,
                 )
             }
         }
 
     override fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {
         if (!communalWidgetResizing()) return
+        val spanValue =
+            if (communalResponsiveGrid()) {
+                SpanValue.Responsive(spanY)
+            } else {
+                SpanValue.Fixed(spanY)
+            }
         bgScope.launch {
-            communalWidgetDao.resizeWidget(appWidgetId, spanY, widgetIdToRankMap)
+            communalWidgetDao.resizeWidget(appWidgetId, spanValue, widgetIdToRankMap)
             logger.i({ "Updated spanY of widget $int1 to $int2." }) {
                 int1 = appWidgetId
                 int2 = spanY
@@ -225,7 +233,7 @@
                     provider = provider,
                     rank = rank,
                     userSerialNumber = userManager.getUserSerialNumber(user.identifier),
-                    spanY = 3,
+                    spanY = SpanValue.Fixed(3),
                 )
                 backupManager.dataChanged()
             } else {
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 602fe30..f9b30c6 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
@@ -34,9 +34,9 @@
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent
 import com.android.systemui.communal.shared.model.CommunalContentSize
-import com.android.systemui.communal.shared.model.CommunalContentSize.FULL
-import com.android.systemui.communal.shared.model.CommunalContentSize.HALF
-import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
+import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.FULL
+import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.HALF
+import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.THIRD
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.shared.model.EditModeState
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 30f580e..da613f5 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
@@ -22,6 +22,7 @@
 import android.content.pm.ApplicationInfo
 import android.graphics.Bitmap
 import android.widget.RemoteViews
+import com.android.systemui.Flags.communalResponsiveGrid
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import java.util.UUID
 
@@ -35,7 +36,7 @@
 
     /** The minimum size content can be resized to. */
     val minSize: CommunalContentSize
-        get() = CommunalContentSize.HALF
+        get() = fixedHalfOrResponsiveSize()
 
     /**
      * A type of communal content is ongoing / live / ephemeral, and can be sized and ordered
@@ -44,7 +45,12 @@
     sealed interface Ongoing : CommunalContentModel {
         override var size: CommunalContentSize
         override val minSize
-            get() = CommunalContentSize.THIRD
+            get() =
+                if (communalResponsiveGrid()) {
+                    CommunalContentSize.Responsive(1)
+                } else {
+                    CommunalContentSize.FixedSize.THIRD
+                }
 
         /** Timestamp in milliseconds of when the content was created. */
         val createdTimestampMillis: Long
@@ -100,14 +106,16 @@
     class WidgetPlaceholder : CommunalContentModel {
         override val key: String = KEY.widgetPlaceholder()
         // Same as widget size.
-        override val size = CommunalContentSize.HALF
+        override val size: CommunalContentSize
+            get() = fixedHalfOrResponsiveSize()
     }
 
     /** A CTA tile in the glanceable hub view mode which can be dismissed. */
     class CtaTileInViewMode : CommunalContentModel {
         override val key: String = KEY.CTA_TILE_IN_VIEW_MODE_KEY
         // Same as widget size.
-        override val size = CommunalContentSize.HALF
+        override val size: CommunalContentSize
+            get() = fixedHalfOrResponsiveSize()
     }
 
     class Tutorial(id: Int, override var size: CommunalContentSize) : CommunalContentModel {
@@ -118,15 +126,15 @@
         smartspaceTargetId: String,
         val remoteViews: RemoteViews,
         override val createdTimestampMillis: Long,
-        override var size: CommunalContentSize = CommunalContentSize.HALF,
+        override var size: CommunalContentSize = fixedHalfOrResponsiveSize(),
     ) : Ongoing {
         override val key = KEY.smartspace(smartspaceTargetId)
     }
 
     class Umo(
         override val createdTimestampMillis: Long,
-        override var size: CommunalContentSize = CommunalContentSize.HALF,
-        override var minSize: CommunalContentSize = CommunalContentSize.HALF,
+        override var size: CommunalContentSize = fixedHalfOrResponsiveSize(),
+        override var minSize: CommunalContentSize = fixedHalfOrResponsiveSize(),
     ) : Ongoing {
         override val key = KEY.umo()
     }
@@ -170,3 +178,10 @@
 
     fun isLiveContent() = this is Smartspace || this is Umo
 }
+
+private fun fixedHalfOrResponsiveSize() =
+    if (communalResponsiveGrid()) {
+        CommunalContentSize.Responsive(1)
+    } else {
+        CommunalContentSize.FixedSize.HALF
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
index 7602a7a..04717d0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
+++ b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
@@ -39,7 +39,10 @@
         // Serial number of the user associated with the widget.
         int32 user_serial_number = 4;
 
-        // The vertical span of the widget
+        // The vertical span of the widget, replaced by span_y_new.
         int32 span_y = 5;
+
+        // The vertical span of the widget.
+        int32 span_y_new = 6;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
index cf80b7d..df30716 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
@@ -16,27 +16,39 @@
 
 package com.android.systemui.communal.shared.model
 
+import com.android.systemui.Flags.communalResponsiveGrid
+
 /**
  * Supported sizes for communal content in the layout grid.
  *
- * @param span The span of the content in a column. For example, if FULL is 6, then 3 represents
- *   HALF, 2 represents THIRD, and 1 represents SIXTH.
+ * @property span The span of the content in a column.
  */
-enum class CommunalContentSize(val span: Int) {
-    /** Content takes the full height of the column. */
-    FULL(6),
+sealed interface CommunalContentSize {
+    val span: Int
 
-    /** Content takes half of the height of the column. */
-    HALF(3),
+    @Deprecated("Use Responsive size instead")
+    enum class FixedSize(override val span: Int) : CommunalContentSize {
+        /** Content takes the full height of the column. */
+        FULL(6),
 
-    /** Content takes a third of the height of the column. */
-    THIRD(2);
+        /** Content takes half of the height of the column. */
+        HALF(3),
+
+        /** Content takes a third of the height of the column. */
+        THIRD(2),
+    }
+
+    @JvmInline value class Responsive(override val span: Int) : CommunalContentSize
 
     companion object {
         /** Converts from span to communal content size. */
         fun toSize(span: Int): CommunalContentSize {
-            return entries.find { it.span == span }
-                ?: throw IllegalArgumentException("$span is not a valid span size")
+            return if (communalResponsiveGrid()) {
+                Responsive(span)
+            } else {
+                FixedSize.entries.find { it.span == span }
+                    ?: throw IllegalArgumentException("$span is not a valid span size")
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/SpanValue.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/SpanValue.kt
new file mode 100644
index 0000000..15cc6b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/SpanValue.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.communal.shared.model
+
+/** Models possible span values for different grid formats. */
+sealed interface SpanValue {
+    val value: Int
+
+    @Deprecated("Use Responsive sizes instead")
+    @JvmInline
+    value class Fixed(override val value: Int) : SpanValue
+
+    @JvmInline value class Responsive(override val value: Int) : SpanValue
+}
+
+fun SpanValue.toResponsive(): SpanValue.Responsive =
+    when (this) {
+        is SpanValue.Responsive -> this
+        is SpanValue.Fixed -> SpanValue.Responsive((this.value / 3).coerceAtMost(1))
+    }
+
+fun SpanValue.toFixed(): SpanValue.Fixed =
+    when (this) {
+        is SpanValue.Fixed -> this
+        is SpanValue.Responsive -> SpanValue.Fixed((this.value * 3).coerceIn(3, 6))
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 5ecf2e6b..a339af3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -195,7 +195,7 @@
     open fun onDismissCtaTile() {}
 
     /** Called as the user starts dragging a widget to reorder. */
-    open fun onReorderWidgetStart() {}
+    open fun onReorderWidgetStart(draggingItemKey: String) {}
 
     /** Called as the user finishes dragging a widget to reorder. */
     open fun onReorderWidgetEnd() {}
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 736ed5c..52bf000 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
@@ -164,9 +164,8 @@
         )
     }
 
-    override fun onReorderWidgetStart() {
-        // Clear selection status
-        setSelectedKey(null)
+    override fun onReorderWidgetStart(draggingItemKey: String) {
+        setSelectedKey(draggingItemKey)
         _reorderingWidgets.value = true
         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
index cc6007b..50d86a2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
@@ -20,6 +20,7 @@
 import android.os.Bundle
 import android.util.SizeF
 import com.android.app.tracing.coroutines.withContextTraced as withContext
+import com.android.systemui.Flags
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate
@@ -30,6 +31,9 @@
 import com.android.systemui.dagger.qualifiers.UiBackground
 import dagger.Lazy
 import java.util.concurrent.Executor
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.ThreadPoolExecutor
+import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
 
@@ -53,7 +57,11 @@
         withContext("$TAG#createWidget", uiBgContext) {
             val view =
                 CommunalAppWidgetHostView(context, interactionHandler).apply {
-                    setExecutor(uiBgExecutor)
+                    if (Flags.communalHubUseThreadPoolForWidgets()) {
+                        setExecutor(widgetExecutor)
+                    } else {
+                        setExecutor(uiBgExecutor)
+                    }
                     setAppWidget(model.appWidgetId, model.providerInfo)
                 }
 
@@ -90,5 +98,20 @@
 
     private companion object {
         const val TAG = "WidgetViewFactory"
+
+        val poolSize = Runtime.getRuntime().availableProcessors().coerceAtLeast(2)
+
+        /**
+         * This executor is used for widget inflation. Parameters match what launcher uses. See
+         * [com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR].
+         */
+        val widgetExecutor =
+            ThreadPoolExecutor(
+                /*corePoolSize*/ poolSize,
+                /*maxPoolSize*/ poolSize,
+                /*keepAlive*/ 1,
+                /*unit*/ TimeUnit.SECONDS,
+                /*workQueue*/ LinkedBlockingQueue(),
+            )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index 41edb4a..740e011 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.controls.management
 
+import android.app.Activity
 import android.app.ActivityOptions
 import android.content.ComponentName
 import android.content.Context
@@ -34,25 +35,25 @@
 import androidx.recyclerview.widget.GridLayoutManager
 import androidx.recyclerview.widget.ItemTouchHelper
 import androidx.recyclerview.widget.RecyclerView
-import com.android.systemui.res.R
 import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
-/**
- * Activity for rearranging and removing controls for a given structure
- */
-open class ControlsEditingActivity @Inject constructor(
+/** Activity for rearranging and removing controls for a given structure */
+open class ControlsEditingActivity
+@Inject
+constructor(
     @Main private val mainExecutor: Executor,
     private val controller: ControlsControllerImpl,
     private val userTracker: UserTracker,
     private val customIconCache: CustomIconCache,
-) : ComponentActivity() {
+) : ComponentActivity(), ControlsManagementActivity {
 
     companion object {
         private const val DEBUG = false
@@ -64,6 +65,9 @@
         private val EMPTY_TEXT_ID = R.string.controls_favorite_removed
     }
 
+    override val activity: Activity
+        get() = this
+
     private lateinit var component: ComponentName
     private lateinit var structure: CharSequence
     private lateinit var model: FavoritesModel
@@ -73,16 +77,17 @@
 
     private var isFromFavoriting: Boolean = false
 
-    private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
-        private val startingUser = controller.currentUserId
+    private val userTrackerCallback: UserTracker.Callback =
+        object : UserTracker.Callback {
+            private val startingUser = controller.currentUserId
 
-        override fun onUserChanged(newUser: Int, userContext: Context) {
-            if (newUser != startingUser) {
-                userTracker.removeCallback(this)
-                finish()
+            override fun onUserChanged(newUser: Int, userContext: Context) {
+                if (newUser != startingUser) {
+                    userTracker.removeCallback(this)
+                    finish()
+                }
             }
         }
-    }
 
     private val mOnBackInvokedCallback = OnBackInvokedCallback {
         if (DEBUG) {
@@ -98,9 +103,7 @@
             component = it
         } ?: run(this::finish)
         isFromFavoriting = intent.getBooleanExtra(EXTRA_FROM_FAVORITING, false)
-        intent.getCharSequenceExtra(EXTRA_STRUCTURE)?.let {
-            structure = it
-        } ?: run(this::finish)
+        intent.getCharSequenceExtra(EXTRA_STRUCTURE)?.let { structure = it } ?: run(this::finish)
 
         bindViews()
 
@@ -117,7 +120,9 @@
             Log.d(TAG, "Registered onBackInvokedCallback")
         }
         onBackInvokedDispatcher.registerOnBackInvokedCallback(
-                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
+            OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+            mOnBackInvokedCallback,
+        )
     }
 
     override fun onStop() {
@@ -142,18 +147,21 @@
                     override fun run() {
                         finish()
                     }
-                }
-        ).start()
+                },
+            )
+            .start()
     }
 
     private fun bindViews() {
         setContentView(R.layout.controls_management)
 
+        applyInsets(R.id.controls_management_root)
+
         lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById<ViewGroup>(R.id.controls_management_root),
                 window,
-                intent
+                intent,
             )
         )
 
@@ -163,126 +171,142 @@
         }
         requireViewById<TextView>(R.id.title).text = structure
         setTitle(structure)
-        subtitle = requireViewById<TextView>(R.id.subtitle).apply {
-            setText(SUBTITLE_ID)
-        }
+        subtitle = requireViewById<TextView>(R.id.subtitle).apply { setText(SUBTITLE_ID) }
     }
 
     private fun bindButtons() {
-        addControls = requireViewById<Button>(R.id.addControls).apply {
-            isEnabled = true
-            visibility = View.VISIBLE
-            setOnClickListener {
-                if (saveButton.isEnabled) {
-                    // The user has made changes
-                    Toast.makeText(
-                        applicationContext,
-                        R.string.controls_favorite_toast_no_changes,
-                        Toast.LENGTH_SHORT
-                    ).show()
+        addControls =
+            requireViewById<Button>(R.id.addControls).apply {
+                isEnabled = true
+                visibility = View.VISIBLE
+                setOnClickListener {
+                    if (saveButton.isEnabled) {
+                        // The user has made changes
+                        Toast.makeText(
+                                applicationContext,
+                                R.string.controls_favorite_toast_no_changes,
+                                Toast.LENGTH_SHORT,
+                            )
+                            .show()
+                    }
+                    if (isFromFavoriting) {
+                        animateExitAndFinish()
+                    } else {
+                        startActivity(
+                            Intent(context, ControlsFavoritingActivity::class.java).also {
+                                it.putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, structure)
+                                it.putExtra(Intent.EXTRA_COMPONENT_NAME, component)
+                                it.putExtra(
+                                    ControlsFavoritingActivity.EXTRA_APP,
+                                    intent.getCharSequenceExtra(EXTRA_APP),
+                                )
+                                it.putExtra(
+                                    ControlsFavoritingActivity.EXTRA_SOURCE,
+                                    ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_EDITING,
+                                )
+                            },
+                            ActivityOptions.makeSceneTransitionAnimation(
+                                    this@ControlsEditingActivity
+                                )
+                                .toBundle(),
+                        )
+                    }
                 }
-                if (isFromFavoriting) {
-                    animateExitAndFinish()
-                } else {
-                    startActivity(Intent(context, ControlsFavoritingActivity::class.java).also {
-                        it.putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, structure)
-                        it.putExtra(Intent.EXTRA_COMPONENT_NAME, component)
-                        it.putExtra(
-                            ControlsFavoritingActivity.EXTRA_APP,
-                            intent.getCharSequenceExtra(EXTRA_APP),
-                        )
-                        it.putExtra(
-                            ControlsFavoritingActivity.EXTRA_SOURCE,
-                            ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_EDITING,
-                        )
-                    },
-                                  ActivityOptions.makeSceneTransitionAnimation(
-                                      this@ControlsEditingActivity
-                                  ).toBundle(),
+            }
+        saveButton =
+            requireViewById<Button>(R.id.done).apply {
+                isEnabled = isFromFavoriting
+                setText(R.string.save)
+                setOnClickListener {
+                    saveFavorites()
+                    startActivity(
+                        Intent(applicationContext, ControlsActivity::class.java),
+                        ActivityOptions.makeSceneTransitionAnimation(this@ControlsEditingActivity)
+                            .toBundle(),
                     )
+                    animateExitAndFinish()
                 }
             }
-        }
-        saveButton = requireViewById<Button>(R.id.done).apply {
-            isEnabled = isFromFavoriting
-            setText(R.string.save)
-            setOnClickListener {
-                saveFavorites()
-                startActivity(
-                    Intent(applicationContext, ControlsActivity::class.java),
-                    ActivityOptions
-                        .makeSceneTransitionAnimation(this@ControlsEditingActivity).toBundle()
-                )
-                animateExitAndFinish()
-            }
-        }
     }
 
     private fun saveFavorites() {
         controller.replaceFavoritesForStructure(
-                StructureInfo(component, structure, model.favorites))
+            StructureInfo(component, structure, model.favorites)
+        )
     }
 
-    private val favoritesModelCallback = object : FavoritesModel.FavoritesModelCallback {
-        override fun onNoneChanged(showNoFavorites: Boolean) {
-            if (showNoFavorites) {
-                subtitle.setText(EMPTY_TEXT_ID)
-            } else {
-                subtitle.setText(SUBTITLE_ID)
+    private val favoritesModelCallback =
+        object : FavoritesModel.FavoritesModelCallback {
+            override fun onNoneChanged(showNoFavorites: Boolean) {
+                if (showNoFavorites) {
+                    subtitle.setText(EMPTY_TEXT_ID)
+                } else {
+                    subtitle.setText(SUBTITLE_ID)
+                }
+            }
+
+            override fun onChange() = Unit
+
+            override fun onFirstChange() {
+                saveButton.isEnabled = true
             }
         }
 
-        override fun onChange() = Unit
-
-        override fun onFirstChange() {
-            saveButton.isEnabled = true
-        }
-    }
-
     private fun setUpList() {
         val controls = controller.getFavoritesForStructure(component, structure)
         model = FavoritesModel(customIconCache, component, controls, favoritesModelCallback)
         val elevation = resources.getFloat(R.dimen.control_card_elevation)
         val recyclerView = requireViewById<RecyclerView>(R.id.list)
         recyclerView.alpha = 0.0f
-        val adapter = ControlAdapter(elevation, userTracker.userId).apply {
-            registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
-                var hasAnimated = false
-                override fun onChanged() {
-                    if (!hasAnimated) {
-                        hasAnimated = true
-                        ControlsAnimations.enterAnimation(recyclerView).start()
-                    }
-                }
-            })
-        }
+        val adapter =
+            ControlAdapter(elevation, userTracker.userId).apply {
+                registerAdapterDataObserver(
+                    object : RecyclerView.AdapterDataObserver() {
+                        var hasAnimated = false
 
-        val margin = resources
-                .getDimensionPixelSize(R.dimen.controls_card_margin)
+                        override fun onChanged() {
+                            if (!hasAnimated) {
+                                hasAnimated = true
+                                ControlsAnimations.enterAnimation(recyclerView).start()
+                            }
+                        }
+                    }
+                )
+            }
+
+        val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin)
         val itemDecorator = MarginItemDecorator(margin, margin)
         val spanCount = ControlAdapter.findMaxColumns(resources)
 
         recyclerView.apply {
             this.adapter = adapter
-            layoutManager = object : GridLayoutManager(recyclerView.context, spanCount) {
+            layoutManager =
+                object : GridLayoutManager(recyclerView.context, spanCount) {
 
-                // This will remove from the announcement the row corresponding to the divider,
-                // as it's not something that should be announced.
-                override fun getRowCountForAccessibility(
-                    recycler: RecyclerView.Recycler,
-                    state: RecyclerView.State
-                ): Int {
-                    val initial = super.getRowCountForAccessibility(recycler, state)
-                    return if (initial > 0) initial - 1 else initial
-                }
-            }.apply {
-                spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
-                    override fun getSpanSize(position: Int): Int {
-                        return if (adapter?.getItemViewType(position)
-                                != ControlAdapter.TYPE_CONTROL) spanCount else 1
+                        // This will remove from the announcement the row corresponding to the
+                        // divider,
+                        // as it's not something that should be announced.
+                        override fun getRowCountForAccessibility(
+                            recycler: RecyclerView.Recycler,
+                            state: RecyclerView.State,
+                        ): Int {
+                            val initial = super.getRowCountForAccessibility(recycler, state)
+                            return if (initial > 0) initial - 1 else initial
+                        }
                     }
-                }
-            }
+                    .apply {
+                        spanSizeLookup =
+                            object : GridLayoutManager.SpanSizeLookup() {
+                                override fun getSpanSize(position: Int): Int {
+                                    return if (
+                                        adapter?.getItemViewType(position) !=
+                                            ControlAdapter.TYPE_CONTROL
+                                    )
+                                        spanCount
+                                    else 1
+                                }
+                            }
+                    }
             addItemDecoration(itemDecorator)
         }
         adapter.changeModel(model)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 2ea4303..ab55c53 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -18,6 +18,7 @@
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
+import android.app.Activity
 import android.app.ActivityOptions
 import android.content.ComponentName
 import android.content.Context
@@ -39,22 +40,24 @@
 import androidx.annotation.VisibleForTesting
 import androidx.viewpager2.widget.ViewPager2
 import com.android.systemui.Prefs
-import com.android.systemui.res.R
 import com.android.systemui.controls.TooltipManager
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import java.text.Collator
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
-open class ControlsFavoritingActivity @Inject constructor(
+open class ControlsFavoritingActivity
+@Inject
+constructor(
     @Main private val executor: Executor,
     private val controller: ControlsControllerImpl,
     private val userTracker: UserTracker,
-) : ComponentActivity() {
+) : ComponentActivity(), ControlsManagementActivity {
 
     companion object {
         private const val DEBUG = false
@@ -74,6 +77,9 @@
         private const val TOOLTIP_MAX_SHOWN = 2
     }
 
+    override val activity: Activity
+        get() = this
+
     private var component: ComponentName? = null
     private var appName: CharSequence? = null
     private var structureExtra: CharSequence? = null
@@ -95,18 +101,21 @@
 
     private val fromProviderSelector: Boolean
         get() = openSource == EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR
+
     private val fromEditing: Boolean
         get() = openSource == EXTRA_SOURCE_VALUE_FROM_EDITING
-    private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
-        private val startingUser = controller.currentUserId
 
-        override fun onUserChanged(newUser: Int, userContext: Context) {
-            if (newUser != startingUser) {
-                userTracker.removeCallback(this)
-                finish()
+    private val userTrackerCallback: UserTracker.Callback =
+        object : UserTracker.Callback {
+            private val startingUser = controller.currentUserId
+
+            override fun onUserChanged(newUser: Int, userContext: Context) {
+                if (newUser != startingUser) {
+                    userTracker.removeCallback(this)
+                    finish()
+                }
             }
         }
-    }
 
     private val mOnBackInvokedCallback = OnBackInvokedCallback {
         if (DEBUG) {
@@ -138,81 +147,113 @@
         bindViews()
     }
 
-    private val controlsModelCallback = object : ControlsModel.ControlsModelCallback {
-        override fun onFirstChange() {
-            doneButton.isEnabled = true
-        }
+    private val controlsModelCallback =
+        object : ControlsModel.ControlsModelCallback {
+            override fun onFirstChange() {
+                doneButton.isEnabled = true
+            }
 
-        override fun onChange() {
-            val structure: StructureContainer = listOfStructures[structurePager.currentItem]
-            rearrangeButton.isEnabled = structure.model.favorites.isNotEmpty()
+            override fun onChange() {
+                val structure: StructureContainer = listOfStructures[structurePager.currentItem]
+                rearrangeButton.isEnabled = structure.model.favorites.isNotEmpty()
+            }
         }
-    }
 
     private fun loadControls() {
         component?.let { componentName ->
             statusText.text = resources.getText(com.android.internal.R.string.loading)
-            val emptyZoneString = resources.getText(
-                    R.string.controls_favorite_other_zone_header)
-            controller.loadForComponent(componentName, { data ->
-                val allControls = data.allControls
-                val favoriteKeys = data.favoritesIds
-                val error = data.errorOnLoad
-                val controlsByStructure = allControls.groupBy { it.control.structure ?: "" }
-                listOfStructures = controlsByStructure.map {
-                    StructureContainer(it.key, AllModel(
-                            it.value, favoriteKeys, emptyZoneString, controlsModelCallback))
-                }.sortedWith(comparator)
+            val emptyZoneString = resources.getText(R.string.controls_favorite_other_zone_header)
+            controller.loadForComponent(
+                componentName,
+                { data ->
+                    val allControls = data.allControls
+                    val favoriteKeys = data.favoritesIds
+                    val error = data.errorOnLoad
+                    val controlsByStructure = allControls.groupBy { it.control.structure ?: "" }
+                    listOfStructures =
+                        controlsByStructure
+                            .map {
+                                StructureContainer(
+                                    it.key,
+                                    AllModel(
+                                        it.value,
+                                        favoriteKeys,
+                                        emptyZoneString,
+                                        controlsModelCallback,
+                                    ),
+                                )
+                            }
+                            .sortedWith(comparator)
 
-                val structureIndex = listOfStructures.indexOfFirst {
-                    sc -> sc.structureName == structureExtra
-                }.let { if (it == -1) 0 else it }
+                    val structureIndex =
+                        listOfStructures
+                            .indexOfFirst { sc -> sc.structureName == structureExtra }
+                            .let { if (it == -1) 0 else it }
 
-                // If we were requested to show a single structure, set the list to just that one
-                if (intent.getBooleanExtra(EXTRA_SINGLE_STRUCTURE, false)) {
-                    listOfStructures = listOf(listOfStructures[structureIndex])
-                }
-
-                executor.execute {
-                    structurePager.adapter = StructureAdapter(listOfStructures, userTracker.userId)
-                    structurePager.setCurrentItem(structureIndex)
-                    if (error) {
-                        statusText.text = resources.getString(R.string.controls_favorite_load_error,
-                                appName ?: "")
-                        subtitleView.visibility = View.GONE
-                    } else if (listOfStructures.isEmpty()) {
-                        statusText.text = resources.getString(R.string.controls_favorite_load_none)
-                        subtitleView.visibility = View.GONE
-                    } else {
-                        statusText.visibility = View.GONE
-
-                        pageIndicator.setNumPages(listOfStructures.size)
-                        pageIndicator.setLocation(0f)
-                        pageIndicator.visibility =
-                            if (listOfStructures.size > 1) View.VISIBLE else View.INVISIBLE
-
-                        ControlsAnimations.enterAnimation(pageIndicator).apply {
-                            addListener(object : AnimatorListenerAdapter() {
-                                override fun onAnimationEnd(animation: Animator) {
-                                    // Position the tooltip if necessary after animations are complete
-                                    // so we can get the position on screen. The tooltip is not
-                                    // rooted in the layout root.
-                                    if (pageIndicator.visibility == View.VISIBLE &&
-                                        mTooltipManager != null) {
-                                        val p = IntArray(2)
-                                        pageIndicator.getLocationOnScreen(p)
-                                        val x = p[0] + pageIndicator.width / 2
-                                        val y = p[1] + pageIndicator.height
-                                        mTooltipManager?.show(
-                                            R.string.controls_structure_tooltip, x, y)
-                                    }
-                                }
-                            })
-                        }.start()
-                        ControlsAnimations.enterAnimation(structurePager).start()
+                    // If we were requested to show a single structure, set the list to just that
+                    // one
+                    if (intent.getBooleanExtra(EXTRA_SINGLE_STRUCTURE, false)) {
+                        listOfStructures = listOf(listOfStructures[structureIndex])
                     }
-                }
-            }, { runnable -> cancelLoadRunnable = runnable })
+
+                    executor.execute {
+                        structurePager.adapter =
+                            StructureAdapter(listOfStructures, userTracker.userId)
+                        structurePager.setCurrentItem(structureIndex)
+                        if (error) {
+                            statusText.text =
+                                resources.getString(
+                                    R.string.controls_favorite_load_error,
+                                    appName ?: "",
+                                )
+                            subtitleView.visibility = View.GONE
+                        } else if (listOfStructures.isEmpty()) {
+                            statusText.text =
+                                resources.getString(R.string.controls_favorite_load_none)
+                            subtitleView.visibility = View.GONE
+                        } else {
+                            statusText.visibility = View.GONE
+
+                            pageIndicator.setNumPages(listOfStructures.size)
+                            pageIndicator.setLocation(0f)
+                            pageIndicator.visibility =
+                                if (listOfStructures.size > 1) View.VISIBLE else View.INVISIBLE
+
+                            ControlsAnimations.enterAnimation(pageIndicator)
+                                .apply {
+                                    addListener(
+                                        object : AnimatorListenerAdapter() {
+                                            override fun onAnimationEnd(animation: Animator) {
+                                                // Position the tooltip if necessary after
+                                                // animations are complete
+                                                // so we can get the position on screen. The tooltip
+                                                // is not
+                                                // rooted in the layout root.
+                                                if (
+                                                    pageIndicator.visibility == View.VISIBLE &&
+                                                        mTooltipManager != null
+                                                ) {
+                                                    val p = IntArray(2)
+                                                    pageIndicator.getLocationOnScreen(p)
+                                                    val x = p[0] + pageIndicator.width / 2
+                                                    val y = p[1] + pageIndicator.height
+                                                    mTooltipManager?.show(
+                                                        R.string.controls_structure_tooltip,
+                                                        x,
+                                                        y,
+                                                    )
+                                                }
+                                            }
+                                        }
+                                    )
+                                }
+                                .start()
+                            ControlsAnimations.enterAnimation(structurePager).start()
+                        }
+                    }
+                },
+                { runnable -> cancelLoadRunnable = runnable },
+            )
         }
     }
 
@@ -221,35 +262,39 @@
         pageIndicator.alpha = 0.0f
         structurePager.apply {
             adapter = StructureAdapter(emptyList(), userTracker.userId)
-            registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
-                override fun onPageSelected(position: Int) {
-                    super.onPageSelected(position)
-                    val name = listOfStructures[position].structureName
-                    val title = if (!TextUtils.isEmpty(name)) name else appName
-                    titleView.text = title
-                    titleView.requestFocus()
-                }
+            registerOnPageChangeCallback(
+                object : ViewPager2.OnPageChangeCallback() {
+                    override fun onPageSelected(position: Int) {
+                        super.onPageSelected(position)
+                        val name = listOfStructures[position].structureName
+                        val title = if (!TextUtils.isEmpty(name)) name else appName
+                        titleView.text = title
+                        titleView.requestFocus()
+                    }
 
-                override fun onPageScrolled(
-                    position: Int,
-                    positionOffset: Float,
-                    positionOffsetPixels: Int
-                ) {
-                    super.onPageScrolled(position, positionOffset, positionOffsetPixels)
-                    pageIndicator.setLocation(position + positionOffset)
+                    override fun onPageScrolled(
+                        position: Int,
+                        positionOffset: Float,
+                        positionOffsetPixels: Int,
+                    ) {
+                        super.onPageScrolled(position, positionOffset, positionOffsetPixels)
+                        pageIndicator.setLocation(position + positionOffset)
+                    }
                 }
-            })
+            )
         }
     }
 
     private fun bindViews() {
         setContentView(R.layout.controls_management)
 
+        applyInsets(R.id.controls_management_root)
+
         lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById<ViewGroup>(R.id.controls_management_root),
                 window,
-                intent
+                intent,
             )
         )
 
@@ -260,41 +305,43 @@
 
         statusText = requireViewById(R.id.status_message)
         if (shouldShowTooltip()) {
-            mTooltipManager = TooltipManager(statusText.context,
-                TOOLTIP_PREFS_KEY, TOOLTIP_MAX_SHOWN)
+            mTooltipManager =
+                TooltipManager(statusText.context, TOOLTIP_PREFS_KEY, TOOLTIP_MAX_SHOWN)
             addContentView(
                 mTooltipManager?.layout,
                 FrameLayout.LayoutParams(
                     ViewGroup.LayoutParams.WRAP_CONTENT,
                     ViewGroup.LayoutParams.WRAP_CONTENT,
-                    Gravity.TOP or Gravity.LEFT
-                )
+                    Gravity.TOP or Gravity.LEFT,
+                ),
             )
         }
-        pageIndicator = requireViewById<ManagementPageIndicator>(
-            R.id.structure_page_indicator).apply {
-            visibilityListener = {
-                if (it != View.VISIBLE) {
+        pageIndicator =
+            requireViewById<ManagementPageIndicator>(R.id.structure_page_indicator).apply {
+                visibilityListener = {
+                    if (it != View.VISIBLE) {
+                        mTooltipManager?.hide(true)
+                    }
+                }
+            }
+
+        val title =
+            structureExtra
+                ?: (appName ?: resources.getText(R.string.controls_favorite_default_title))
+        titleView = requireViewById<TextView>(R.id.title).apply { text = title }
+        subtitleView =
+            requireViewById<TextView>(R.id.subtitle).apply {
+                text = resources.getText(R.string.controls_favorite_subtitle)
+            }
+        structurePager = requireViewById<ViewPager2>(R.id.structure_pager)
+        structurePager.registerOnPageChangeCallback(
+            object : ViewPager2.OnPageChangeCallback() {
+                override fun onPageSelected(position: Int) {
+                    super.onPageSelected(position)
                     mTooltipManager?.hide(true)
                 }
             }
-        }
-
-        val title = structureExtra
-            ?: (appName ?: resources.getText(R.string.controls_favorite_default_title))
-        titleView = requireViewById<TextView>(R.id.title).apply {
-            text = title
-        }
-        subtitleView = requireViewById<TextView>(R.id.subtitle).apply {
-            text = resources.getText(R.string.controls_favorite_subtitle)
-        }
-        structurePager = requireViewById<ViewPager2>(R.id.structure_pager)
-        structurePager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
-            override fun onPageSelected(position: Int) {
-                super.onPageSelected(position)
-                mTooltipManager?.hide(true)
-            }
-        })
+        )
         bindButtons()
     }
 
@@ -307,47 +354,53 @@
                     override fun run() {
                         finish()
                     }
-                }
-        ).start()
+                },
+            )
+            .start()
     }
 
     private fun bindButtons() {
-        rearrangeButton = requireViewById<Button>(R.id.rearrange).apply {
-            text = if (fromEditing) {
-                getString(R.string.controls_favorite_back_to_editing)
-            } else {
-                getString(R.string.controls_favorite_rearrange_button)
+        rearrangeButton =
+            requireViewById<Button>(R.id.rearrange).apply {
+                text =
+                    if (fromEditing) {
+                        getString(R.string.controls_favorite_back_to_editing)
+                    } else {
+                        getString(R.string.controls_favorite_rearrange_button)
+                    }
+                isEnabled = false
+                visibility = View.VISIBLE
+                setOnClickListener {
+                    if (component == null) return@setOnClickListener
+                    saveFavorites()
+                    startActivity(
+                        Intent(context, ControlsEditingActivity::class.java).also {
+                            it.putExtra(Intent.EXTRA_COMPONENT_NAME, component)
+                            it.putExtra(ControlsEditingActivity.EXTRA_APP, appName)
+                            it.putExtra(ControlsEditingActivity.EXTRA_FROM_FAVORITING, true)
+                            it.putExtra(
+                                ControlsEditingActivity.EXTRA_STRUCTURE,
+                                listOfStructures[structurePager.currentItem].structureName,
+                            )
+                        },
+                        ActivityOptions.makeSceneTransitionAnimation(
+                                this@ControlsFavoritingActivity
+                            )
+                            .toBundle(),
+                    )
+                }
             }
-            isEnabled = false
-            visibility = View.VISIBLE
-            setOnClickListener {
-                if (component == null) return@setOnClickListener
-                saveFavorites()
-                startActivity(
-                    Intent(context, ControlsEditingActivity::class.java).also {
-                        it.putExtra(Intent.EXTRA_COMPONENT_NAME, component)
-                        it.putExtra(ControlsEditingActivity.EXTRA_APP, appName)
-                        it.putExtra(ControlsEditingActivity.EXTRA_FROM_FAVORITING, true)
-                        it.putExtra(
-                            ControlsEditingActivity.EXTRA_STRUCTURE,
-                            listOfStructures[structurePager.currentItem].structureName,
-                        )
-                    },
-                    ActivityOptions
-                        .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle()
-                )
-            }
-        }
 
-        doneButton = requireViewById<Button>(R.id.done).apply {
-            isEnabled = false
-            setOnClickListener {
-                if (component == null) return@setOnClickListener
-                saveFavorites()
-                animateExitAndFinish()
-                openControlsOrigin()
+        doneButton =
+            requireViewById<Button>(R.id.done).apply {
+                isEnabled = false
+                setOnClickListener {
+                    if (component == null) return@setOnClickListener
+                    saveFavorites()
+                    animateExitAndFinish()
+                    openControlsOrigin()
+                }
             }
-        }
     }
 
     private fun saveFavorites() {
@@ -362,14 +415,14 @@
     private fun openControlsOrigin() {
         startActivity(
             Intent(applicationContext, ControlsActivity::class.java),
-            ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
+            ActivityOptions.makeSceneTransitionAnimation(this).toBundle(),
         )
     }
 
     override fun onPause() {
         super.onPause()
         mTooltipManager?.hide(false)
-   }
+    }
 
     override fun onStart() {
         super.onStart()
@@ -380,7 +433,9 @@
             Log.d(TAG, "Registered onBackInvokedCallback")
         }
         onBackInvokedDispatcher.registerOnBackInvokedCallback(
-                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
+            OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+            mOnBackInvokedCallback,
+        )
     }
 
     override fun onResume() {
@@ -393,7 +448,7 @@
             loadControls()
             isPagerLoaded = true
         }
-   }
+    }
 
     override fun onStop() {
         super.onStop()
@@ -403,8 +458,7 @@
         if (DEBUG) {
             Log.d(TAG, "Unregistered onBackInvokedCallback")
         }
-        onBackInvokedDispatcher.unregisterOnBackInvokedCallback(
-                mOnBackInvokedCallback)
+        onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback)
     }
 
     override fun onConfigurationChanged(newConfig: Configuration) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsManagementActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsManagementActivity.kt
new file mode 100644
index 0000000..c3b8cff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsManagementActivity.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.controls.management
+
+import android.app.Activity
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowInsets.Type
+
+interface ControlsManagementActivity {
+    val activity: Activity
+}
+
+fun ControlsManagementActivity.applyInsets(viewId: Int) {
+    activity.requireViewById<ViewGroup>(viewId).apply {
+        setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
+            v.apply {
+                val paddings = insets.getInsets(Type.systemBars() or Type.displayCutout())
+                setPadding(paddings.left, paddings.top, paddings.right, paddings.bottom)
+            }
+
+            WindowInsets.CONSUMED
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 10a0117..f56cd00 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.controls.management
 
+import android.app.Activity
 import android.app.ActivityOptions
 import android.app.Dialog
 import android.content.ComponentName
@@ -35,7 +36,6 @@
 import androidx.annotation.VisibleForTesting
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
-import com.android.systemui.res.R
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
@@ -43,40 +43,46 @@
 import com.android.systemui.controls.ui.SelectedItem
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
-/**
- * Activity to select an application to favorite the [Control] provided by them.
- */
-open class ControlsProviderSelectorActivity @Inject constructor(
+/** Activity to select an application to favorite the [Control] provided by them. */
+open class ControlsProviderSelectorActivity
+@Inject
+constructor(
     @Main private val executor: Executor,
     @Background private val backExecutor: Executor,
     private val listingController: ControlsListingController,
     private val controlsController: ControlsController,
     private val userTracker: UserTracker,
     private val authorizedPanelsRepository: AuthorizedPanelsRepository,
-    private val panelConfirmationDialogFactory: PanelConfirmationDialogFactory
-) : ComponentActivity() {
+    private val panelConfirmationDialogFactory: PanelConfirmationDialogFactory,
+) : ComponentActivity(), ControlsManagementActivity {
 
     companion object {
         private const val DEBUG = false
         private const val TAG = "ControlsProviderSelectorActivity"
         const val BACK_SHOULD_EXIT = "back_should_exit"
     }
+
+    override val activity: Activity
+        get() = this
+
     private var backShouldExit = false
     private lateinit var recyclerView: RecyclerView
-    private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
-        private val startingUser = listingController.currentUserId
+    private val userTrackerCallback: UserTracker.Callback =
+        object : UserTracker.Callback {
+            private val startingUser = listingController.currentUserId
 
-        override fun onUserChanged(newUser: Int, userContext: Context) {
-            if (newUser != startingUser) {
-                userTracker.removeCallback(this)
-                finish()
+            override fun onUserChanged(newUser: Int, userContext: Context) {
+                if (newUser != startingUser) {
+                    userTracker.removeCallback(this)
+                    finish()
+                }
             }
         }
-    }
     private var dialog: Dialog? = null
 
     private val mOnBackInvokedCallback = OnBackInvokedCallback {
@@ -95,10 +101,12 @@
             ControlsAnimations.observerForAnimations(
                 requireViewById<ViewGroup>(R.id.controls_management_root),
                 window,
-                intent
+                intent,
             )
         )
 
+        applyInsets(R.id.controls_management_root)
+
         requireViewById<ViewStub>(R.id.stub).apply {
             layoutResource = R.layout.controls_management_apps
             inflate()
@@ -114,9 +122,7 @@
         requireViewById<Button>(R.id.other_apps).apply {
             visibility = View.VISIBLE
             setText(com.android.internal.R.string.cancel)
-            setOnClickListener {
-                onBackPressed()
-            }
+            setOnClickListener { onBackPressed() }
         }
         requireViewById<View>(R.id.done).visibility = View.GONE
 
@@ -125,9 +131,10 @@
 
     override fun onBackPressed() {
         if (!backShouldExit) {
-            val i = Intent().apply {
-                component = ComponentName(applicationContext, ControlsActivity::class.java)
-            }
+            val i =
+                Intent().apply {
+                    component = ComponentName(applicationContext, ControlsActivity::class.java)
+                }
             startActivity(i, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
         }
         animateExitAndFinish()
@@ -138,33 +145,40 @@
         userTracker.addCallback(userTrackerCallback, executor)
 
         recyclerView.alpha = 0.0f
-        recyclerView.adapter = AppAdapter(
-                backExecutor,
-                executor,
-                lifecycle,
-                listingController,
-                LayoutInflater.from(this),
-                ::onAppSelected,
-                FavoritesRenderer(resources, controlsController::countFavoritesForComponent),
-                resources,
-                authorizedPanelsRepository.getAuthorizedPanels()
-        ).apply {
-            registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
-                var hasAnimated = false
-                override fun onChanged() {
-                    if (!hasAnimated) {
-                        hasAnimated = true
-                        ControlsAnimations.enterAnimation(recyclerView).start()
-                    }
+        recyclerView.adapter =
+            AppAdapter(
+                    backExecutor,
+                    executor,
+                    lifecycle,
+                    listingController,
+                    LayoutInflater.from(this),
+                    ::onAppSelected,
+                    FavoritesRenderer(resources, controlsController::countFavoritesForComponent),
+                    resources,
+                    authorizedPanelsRepository.getAuthorizedPanels(),
+                )
+                .apply {
+                    registerAdapterDataObserver(
+                        object : RecyclerView.AdapterDataObserver() {
+                            var hasAnimated = false
+
+                            override fun onChanged() {
+                                if (!hasAnimated) {
+                                    hasAnimated = true
+                                    ControlsAnimations.enterAnimation(recyclerView).start()
+                                }
+                            }
+                        }
+                    )
                 }
-            })
-        }
 
         if (DEBUG) {
             Log.d(TAG, "Registered onBackInvokedCallback")
         }
         onBackInvokedDispatcher.registerOnBackInvokedCallback(
-                OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
+            OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+            mOnBackInvokedCallback,
+        )
     }
 
     override fun onStop() {
@@ -184,38 +198,45 @@
             launchFavoritingActivity(serviceInfo.componentName)
         } else {
             val appName = serviceInfo.loadLabel() ?: ""
-            dialog = panelConfirmationDialogFactory.createConfirmationDialog(this, appName) { ok ->
-                if (ok) {
-                    authorizedPanelsRepository.addAuthorizedPanels(
-                            setOf(serviceInfo.componentName.packageName)
-                    )
-                    val selected = SelectedItem.PanelItem(appName, serviceInfo.componentName)
-                    controlsController.setPreferredSelection(selected)
-                    animateExitAndFinish()
-                    openControlsOrigin()
-                }
-                dialog = null
-            }.also { it.show() }
+            dialog =
+                panelConfirmationDialogFactory
+                    .createConfirmationDialog(this, appName) { ok ->
+                        if (ok) {
+                            authorizedPanelsRepository.addAuthorizedPanels(
+                                setOf(serviceInfo.componentName.packageName)
+                            )
+                            val selected =
+                                SelectedItem.PanelItem(appName, serviceInfo.componentName)
+                            controlsController.setPreferredSelection(selected)
+                            animateExitAndFinish()
+                            openControlsOrigin()
+                        }
+                        dialog = null
+                    }
+                    .also { it.show() }
         }
     }
 
     /**
      * Launch the [ControlsFavoritingActivity] for the specified component.
+     *
      * @param component a component name for a [ControlsProviderService]
      */
     private fun launchFavoritingActivity(component: ComponentName?) {
         executor.execute {
             component?.let {
-                val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java)
-                        .apply {
-                    putExtra(ControlsFavoritingActivity.EXTRA_APP,
-                            listingController.getAppLabel(it))
-                    putExtra(Intent.EXTRA_COMPONENT_NAME, it)
-                    putExtra(
-                        ControlsFavoritingActivity.EXTRA_SOURCE,
-                        ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR,
-                    )
-                }
+                val intent =
+                    Intent(applicationContext, ControlsFavoritingActivity::class.java).apply {
+                        putExtra(
+                            ControlsFavoritingActivity.EXTRA_APP,
+                            listingController.getAppLabel(it),
+                        )
+                        putExtra(Intent.EXTRA_COMPONENT_NAME, it)
+                        putExtra(
+                            ControlsFavoritingActivity.EXTRA_SOURCE,
+                            ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR,
+                        )
+                    }
                 startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
             }
         }
@@ -228,8 +249,8 @@
 
     private fun openControlsOrigin() {
         startActivity(
-                Intent(applicationContext, ControlsActivity::class.java),
-                ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
+            Intent(applicationContext, ControlsActivity::class.java),
+            ActivityOptions.makeSceneTransitionAnimation(this).toBundle(),
         )
     }
 
@@ -242,7 +263,8 @@
                     override fun run() {
                         finish()
                     }
-                }
-        ).start()
+                },
+            )
+            .start()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 955a5a3..8296ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.controls.ui
 
+import android.app.Activity
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
@@ -25,36 +26,40 @@
 import android.os.Bundle
 import android.os.RemoteException
 import android.service.dreams.IDreamManager
-import android.view.View
 import android.view.ViewGroup
-import android.view.WindowInsets
-import android.view.WindowInsets.Type
 import android.view.WindowManager
 import androidx.activity.ComponentActivity
-import com.android.systemui.res.R
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.management.ControlsAnimations
+import com.android.systemui.controls.management.ControlsManagementActivity
+import com.android.systemui.controls.management.applyInsets
 import com.android.systemui.controls.settings.ControlsSettingsDialogManager
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
 
 /**
  * Displays Device Controls inside an activity. This activity is available to be displayed over the
  * lockscreen if the user has allowed it via
- * [android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS]. This activity will be
- * destroyed on SCREEN_OFF events, due to issues with occluded activities over lockscreen as well as
- * user expectations for the activity to not continue running.
+ * [android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS]. This activity will be destroyed on
+ * SCREEN_OFF events, due to issues with occluded activities over lockscreen as well as user
+ * expectations for the activity to not continue running.
  */
 // Open for testing
-open class ControlsActivity @Inject constructor(
+open class ControlsActivity
+@Inject
+constructor(
     private val uiController: ControlsUiController,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val dreamManager: IDreamManager,
     private val featureFlags: FeatureFlags,
     private val controlsSettingsDialogManager: ControlsSettingsDialogManager,
-    private val keyguardStateController: KeyguardStateController
-) : ComponentActivity() {
+    private val keyguardStateController: KeyguardStateController,
+) : ComponentActivity(), ControlsManagementActivity {
+
+    override val activity: Activity
+        get() = this
 
     private val lastConfiguration = Configuration()
 
@@ -69,38 +74,27 @@
 
         setContentView(R.layout.controls_fullscreen)
 
+        applyInsets(R.id.control_detail_root)
+
         lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById(R.id.control_detail_root),
                 window,
                 intent,
-                false
+                false,
             )
         )
 
-        requireViewById<ViewGroup>(R.id.control_detail_root).apply {
-            setOnApplyWindowInsetsListener {
-                v: View, insets: WindowInsets ->
-                    v.apply {
-                        val l = getPaddingLeft()
-                        val t = getPaddingTop()
-                        val r = getPaddingRight()
-                        setPadding(l, t, r, insets.getInsets(Type.systemBars()).bottom)
-                    }
-
-                WindowInsets.CONSUMED
-            }
-        }
-
         initBroadcastReceiver()
     }
 
     override fun onConfigurationChanged(newConfig: Configuration) {
         super.onConfigurationChanged(newConfig)
-        val interestingFlags = ActivityInfo.CONFIG_ORIENTATION or
+        val interestingFlags =
+            ActivityInfo.CONFIG_ORIENTATION or
                 ActivityInfo.CONFIG_SCREEN_SIZE or
                 ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE
-        if (lastConfiguration.diff(newConfig) and interestingFlags != 0 ) {
+        if (lastConfiguration.diff(newConfig) and interestingFlags != 0) {
             uiController.onSizeChange()
         }
         lastConfiguration.setTo(newConfig)
@@ -164,15 +158,18 @@
     }
 
     private fun initBroadcastReceiver() {
-        broadcastReceiver = object : BroadcastReceiver() {
-            override fun onReceive(context: Context, intent: Intent) {
-                val action = intent.getAction()
-                if (action == Intent.ACTION_SCREEN_OFF ||
-                    action == Intent.ACTION_DREAMING_STARTED) {
-                    finish()
+        broadcastReceiver =
+            object : BroadcastReceiver() {
+                override fun onReceive(context: Context, intent: Intent) {
+                    val action = intent.getAction()
+                    if (
+                        action == Intent.ACTION_SCREEN_OFF ||
+                            action == Intent.ACTION_DREAMING_STARTED
+                    ) {
+                        finish()
+                    }
                 }
             }
-        }
 
         val filter = IntentFilter()
         filter.addAction(Intent.ACTION_SCREEN_OFF)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 2e323d4..1fc5494 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -75,7 +75,7 @@
 import com.android.systemui.statusbar.notification.dagger.ReferenceNotificationsModule;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.DozeServiceHost;
-import com.android.systemui.statusbar.phone.HeadsUpModule;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpModule;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentStartableModule;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 38cfb9b..fcc3ea9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -54,7 +54,7 @@
 import com.android.systemui.statusbar.ImmersiveModeConfirmation
 import com.android.systemui.statusbar.gesture.GesturePointerEventListener
 import com.android.systemui.statusbar.notification.InstantAppNotifier
-import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
+import com.android.systemui.statusbar.notification.headsup.StatusBarHeadsUpChangeListener
 import com.android.systemui.statusbar.policy.BatteryControllerStartable
 import com.android.systemui.stylus.StylusUsiPowerStartable
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index b7d3c92..d6f8957 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -32,6 +32,7 @@
 import com.android.systemui.CameraProtectionModule;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.SystemUISecondaryUserService;
+import com.android.systemui.activity.ActivityManagerModule;
 import com.android.systemui.ambient.dagger.AmbientModule;
 import com.android.systemui.appops.dagger.AppOpsModule;
 import com.android.systemui.assist.AssistModule;
@@ -140,7 +141,7 @@
 import com.android.systemui.statusbar.phone.ConfigurationControllerModule;
 import com.android.systemui.statusbar.phone.LetterboxModule;
 import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.PolicyModule;
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
@@ -198,6 +199,7 @@
  * may not appreciate that.
  */
 @Module(includes = {
+        ActivityManagerModule.class,
         AmbientModule.class,
         AppOpsModule.class,
         AssistModule.class,
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 400d097..2e1096f 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
@@ -84,30 +84,53 @@
             )
 
     /**
+     * Emits `true` when the current scene switches to [Scenes.Gone] for the first time after having
+     * been on [Scenes.Lockscreen].
+     *
+     * Different from [isDeviceEntered] such that the current scene must actually go through
+     * [Scenes.Gone] to produce a `true`. [isDeviceEntered] also takes into account the navigation
+     * back stack and will produce a `true` value even when the current scene is still not
+     * [Scenes.Gone] but the bottommost entry of the navigation back stack switched from
+     * [Scenes.Lockscreen] to [Scenes.Gone] while the user is staring at another scene.
+     */
+    val isDeviceEnteredDirectly: StateFlow<Boolean> =
+        sceneInteractor.currentScene
+            .filter { currentScene ->
+                currentScene == Scenes.Gone || currentScene == Scenes.Lockscreen
+            }
+            .mapLatestConflated { scene ->
+                if (scene == Scenes.Gone) {
+                    // Make sure device unlock status is definitely unlocked before we
+                    // consider the device "entered".
+                    deviceUnlockedInteractor.deviceUnlockStatus.first { it.isUnlocked }
+                    true
+                } else {
+                    false
+                }
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
+            )
+
+    /**
      * Whether the device has been entered (i.e. the lockscreen has been dismissed, by any method).
      * This can be `false` when the device is unlocked, e.g. when the user still needs to swipe away
      * the non-secure lockscreen, even though they've already authenticated.
      *
      * Note: This does not imply that the lockscreen is visible or not.
+     *
+     * Different from [isDeviceEnteredDirectly] such that the current scene doesn't actually have to
+     * go through [Scenes.Gone] to produce a `true`. [isDeviceEnteredDirectly] doesn't take the
+     * navigation back stack into account and will only produce a `true` value even when the current
+     * scene is actually [Scenes.Gone].
      */
     val isDeviceEntered: StateFlow<Boolean> =
         combine(
                 // This flow emits true when the currentScene switches to Gone for the first time
                 // after having been on Lockscreen.
-                sceneInteractor.currentScene
-                    .filter { currentScene ->
-                        currentScene == Scenes.Gone || currentScene == Scenes.Lockscreen
-                    }
-                    .mapLatestConflated { scene ->
-                        if (scene == Scenes.Gone) {
-                            // Make sure device unlock status is definitely unlocked before we
-                            // consider the device "entered".
-                            deviceUnlockedInteractor.deviceUnlockStatus.first { it.isUnlocked }
-                            true
-                        } else {
-                            false
-                        }
-                    },
+                isDeviceEnteredDirectly,
                 // This flow emits true only if the bottom of the navigation back stack has been
                 // switched from Lockscreen to Gone. In other words, only if the device was unlocked
                 // while visiting at least one scene "above" the Lockscreen scene.
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
index e862525..9b181be 100644
--- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
+import com.android.systemui.display.domain.interactor.DisplayWindowPropertiesInteractorModule
 import com.android.systemui.display.domain.interactor.RearDisplayStateInteractor
 import com.android.systemui.display.domain.interactor.RearDisplayStateInteractorImpl
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
@@ -41,7 +42,7 @@
 import dagger.multibindings.IntoMap
 
 /** Module binding display related classes. */
-@Module
+@Module(includes = [DisplayWindowPropertiesInteractorModule::class])
 interface DisplayModule {
     @Binds
     fun bindConnectedDisplayInteractor(
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 1fa829a..e5acb82 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -66,10 +66,22 @@
     /** Display removal event indicating a display has been removed. */
     val displayRemovalEvent: Flow<Int>
 
-    /** Provides the current set of displays. */
+    /**
+     * Provides the current set of displays.
+     *
+     * Consider using [displayIds] if only the [Display.getDisplayId] is needed.
+     */
     val displays: StateFlow<Set<Display>>
 
     /**
+     * Provides the current set of display ids.
+     *
+     * Note that it is preferred to use this instead of [displays] if only the
+     * [Display.getDisplayId] is needed.
+     */
+    val displayIds: StateFlow<Set<Int>>
+
+    /**
      * Pending display id that can be enabled/disabled.
      *
      * When `null`, it means there is no pending display waiting to be enabled.
@@ -159,7 +171,7 @@
     private val initialDisplayIds = initialDisplays.map { display -> display.displayId }.toSet()
 
     /** Propagate to the listeners only enabled displays */
-    private val enabledDisplayIds: Flow<Set<Int>> =
+    private val enabledDisplayIds: StateFlow<Set<Int>> =
         allDisplayEvents
             .scan(initial = initialDisplayIds) { previousIds: Set<Int>, event: DisplayEvent ->
                 val id = event.displayId
@@ -170,8 +182,8 @@
                 }
             }
             .distinctUntilChanged()
-            .stateIn(bgApplicationScope, SharingStarted.WhileSubscribed(), initialDisplayIds)
             .debugLog("enabledDisplayIds")
+            .stateIn(bgApplicationScope, SharingStarted.WhileSubscribed(), initialDisplayIds)
 
     private val defaultDisplay by lazy {
         getDisplayFromDisplayManager(Display.DEFAULT_DISPLAY)
@@ -209,6 +221,8 @@
      */
     override val displays: StateFlow<Set<Display>> = enabledDisplays
 
+    override val displayIds: StateFlow<Set<Int>> = enabledDisplayIds
+
     /**
      * Implementation that maps from [displays], instead of [allDisplayEvents] for 2 reasons:
      * 1. Guarantee that it emits __after__ [displays] emitted. This way it is guaranteed that
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractor.kt
new file mode 100644
index 0000000..22e467b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractor.kt
@@ -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.systemui.display.domain.interactor
+
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.display.shared.model.DisplayWindowProperties
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+
+/** Provides per display instances of [DisplayWindowProperties]. */
+interface DisplayWindowPropertiesInteractor {
+
+    /**
+     * Returns a [DisplayWindowProperties] instance for a given display id, to be used for the
+     * status bar.
+     *
+     * @throws IllegalArgumentException if no display with the given display id exists.
+     */
+    fun getForStatusBar(displayId: Int): DisplayWindowProperties
+}
+
+@SysUISingleton
+class DisplayWindowPropertiesInteractorImpl
+@Inject
+constructor(private val repo: DisplayWindowPropertiesRepository) :
+    DisplayWindowPropertiesInteractor {
+
+    override fun getForStatusBar(displayId: Int): DisplayWindowProperties {
+        return repo.get(displayId, TYPE_STATUS_BAR)
+    }
+}
+
+@Module
+interface DisplayWindowPropertiesInteractorModule {
+
+    @Binds
+    fun interactor(impl: DisplayWindowPropertiesInteractorImpl): DisplayWindowPropertiesInteractor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt
index 12ceedd..b990e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt
@@ -31,15 +31,17 @@
     override fun transitionTo(oldState: DozeMachine.State, newState: DozeMachine.State) {
         this.oldState = oldState
         this.newState = newState
-        callbacks.forEach { it.onDozeTransition(oldState, newState) }
+
+        val cbs = synchronized(this) { callbacks.toSet() }
+        cbs.forEach { it.onDozeTransition(oldState, newState) }
     }
 
     override fun addCallback(callback: DozeTransitionCallback) {
-        callbacks.add(callback)
+        synchronized(this) { callbacks.add(callback) }
     }
 
     override fun removeCallback(callback: DozeTransitionCallback) {
-        callbacks.remove(callback)
+        synchronized(this) { callbacks.remove(callback) }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 408fe83..aee3a45 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -18,6 +18,7 @@
 
 import static android.service.dreams.Flags.dreamWakeRedirect;
 
+import static com.android.systemui.Flags.communalHubOnMobile;
 import static com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming;
 import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_WINDOW_TITLE;
 import static com.android.systemui.dreams.dagger.DreamModule.DREAM_TOUCH_INSET_MANAGER;
@@ -54,14 +55,17 @@
 import com.android.internal.policy.PhoneWindow;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.ambient.touch.TouchHandler;
 import com.android.systemui.ambient.touch.TouchMonitor;
 import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
 import com.android.systemui.ambient.touch.scrim.ScrimManager;
 import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.communal.shared.log.CommunalUiEvent;
 import com.android.systemui.communal.shared.model.CommunalScenes;
+import com.android.systemui.communal.shared.model.CommunalTransitionKeys;
 import com.android.systemui.complication.dagger.ComplicationComponent;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.dagger.DreamComplicationComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
@@ -76,8 +80,8 @@
 import kotlinx.coroutines.Job;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.concurrent.CancellationException;
 import java.util.function.Consumer;
 
@@ -138,8 +142,7 @@
      */
     private boolean mBouncerShowing = false;
 
-    private final com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
-            mDreamComplicationComponentFactory;
+    private final DreamComplicationComponent.Factory mDreamComplicationComponentFactory;
     private final ComplicationComponent.Factory mComplicationComponentFactory;
     private final DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
     private final AmbientTouchComponent.Factory mAmbientTouchComponentFactory;
@@ -373,8 +376,7 @@
             @Main DelayableExecutor executor,
             ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
             ComplicationComponent.Factory complicationComponentFactory,
-            com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
-                    dreamComplicationComponentFactory,
+            DreamComplicationComponent.Factory dreamComplicationComponentFactory,
             DreamOverlayComponent.Factory dreamOverlayComponentFactory,
             AmbientTouchComponent.Factory ambientTouchComponentFactory,
             DreamOverlayStateController stateController,
@@ -476,18 +478,23 @@
                 mLifecycleOwner,
                 () -> mExecutor.execute(DreamOverlayService.this::requestExit),
                 new ViewModelStore(), mTouchInsetManager);
-        final com.android.systemui.dreams.complication.dagger.ComplicationComponent
-                dreamComplicationComponent = mDreamComplicationComponentFactory.create(
-                complicationComponent.getVisibilityController(), mTouchInsetManager);
+        final DreamComplicationComponent dreamComplicationComponent =
+                mDreamComplicationComponentFactory.create(
+                        complicationComponent.getVisibilityController(), mTouchInsetManager);
 
         final DreamOverlayComponent dreamOverlayComponent = mDreamOverlayComponentFactory.create(
                 mLifecycleOwner, complicationComponent.getComplicationHostViewController(),
                 mTouchInsetManager);
+
+        final ArrayList<TouchHandler> touchHandlers = new ArrayList<>(
+                List.of(dreamComplicationComponent.getHideComplicationTouchHandler()));
+        if (!communalHubOnMobile()) {
+            // Do not add the communal touch handler for glanceable hub v2 since there is no dream
+            // to hub swipe gesture.
+            touchHandlers.add(dreamOverlayComponent.getCommunalTouchHandler());
+        }
         final AmbientTouchComponent ambientTouchComponent = mAmbientTouchComponentFactory.create(
-                mLifecycleOwner,
-                new HashSet<>(Arrays.asList(
-                        dreamComplicationComponent.getHideComplicationTouchHandler(),
-                        dreamOverlayComponent.getCommunalTouchHandler())), TAG);
+                mLifecycleOwner, new HashSet<>(touchHandlers), TAG);
 
         setLifecycleStateLocked(Lifecycle.State.STARTED);
 
@@ -568,7 +575,7 @@
         } else {
             mCommunalInteractor.changeScene(CommunalScenes.Communal,
                     "dream wake requested",
-                    null);
+                    communalHubOnMobile() ? CommunalTransitionKeys.INSTANCE.getSimpleFade() : null);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
index f8ae5c2..ea5fbc6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
@@ -17,8 +17,8 @@
 package com.android.systemui.dreams.complication;
 
 import static com.android.systemui.Flags.removeDreamOverlayHideOnTouch;
-import static com.android.systemui.dreams.complication.dagger.ComplicationModule.COMPLICATIONS_FADE_OUT_DELAY;
-import static com.android.systemui.dreams.complication.dagger.ComplicationModule.COMPLICATIONS_RESTORE_TIMEOUT;
+import static com.android.systemui.dreams.complication.dagger.DreamComplicationModule.COMPLICATIONS_FADE_OUT_DELAY;
+import static com.android.systemui.dreams.complication.dagger.DreamComplicationModule.COMPLICATIONS_RESTORE_TIMEOUT;
 
 import android.util.Log;
 import android.view.MotionEvent;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt
deleted file mode 100644
index 492c502..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.android.systemui.dreams.complication.dagger
-
-import com.android.systemui.complication.Complication
-import com.android.systemui.dreams.complication.HideComplicationTouchHandler
-import com.android.systemui.touch.TouchInsetManager
-import dagger.BindsInstance
-import dagger.Subcomponent
-
-@Subcomponent(modules = [ComplicationModule::class])
-interface ComplicationComponent {
-    /** Factory for generating [ComplicationComponent]. */
-    @Subcomponent.Factory
-    interface Factory {
-        fun create(
-            @BindsInstance visibilityController: Complication.VisibilityController,
-            @BindsInstance touchInsetManager: TouchInsetManager
-        ): ComplicationComponent
-    }
-
-    fun getHideComplicationTouchHandler(): HideComplicationTouchHandler
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.kt
deleted file mode 100644
index 6fd6f4e..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.android.systemui.dreams.complication.dagger
-
-import android.content.res.Resources
-import com.android.systemui.res.R
-import com.android.systemui.dagger.qualifiers.Main
-import dagger.Module
-import dagger.Provides
-import javax.inject.Named
-
-@Module
-object ComplicationModule {
-    const val COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout"
-    const val COMPLICATIONS_FADE_OUT_DELAY = "complication_fade_out_delay"
-
-    /** Provides the delay to wait for before fading out complications. */
-    @Provides
-    @Named(COMPLICATIONS_FADE_OUT_DELAY)
-    fun providesComplicationsFadeOutDelay(@Main resources: Resources): Int {
-        return resources.getInteger(R.integer.complicationFadeOutDelayMs)
-    }
-
-    /** Provides the timeout for restoring complication visibility. */
-    @Provides
-    @Named(COMPLICATIONS_RESTORE_TIMEOUT)
-    fun providesComplicationsRestoreTimeout(@Main resources: Resources): Int {
-        return resources.getInteger(R.integer.complicationRestoreMs)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationComponent.kt
new file mode 100644
index 0000000..17d3acd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationComponent.kt
@@ -0,0 +1,21 @@
+package com.android.systemui.dreams.complication.dagger
+
+import com.android.systemui.complication.Complication
+import com.android.systemui.dreams.complication.HideComplicationTouchHandler
+import com.android.systemui.touch.TouchInsetManager
+import dagger.BindsInstance
+import dagger.Subcomponent
+
+@Subcomponent(modules = [DreamComplicationModule::class])
+interface DreamComplicationComponent {
+    /** Factory for generating [DreamComplicationComponent]. */
+    @Subcomponent.Factory
+    interface Factory {
+        fun create(
+            @BindsInstance visibilityController: Complication.VisibilityController,
+            @BindsInstance touchInsetManager: TouchInsetManager,
+        ): DreamComplicationComponent
+    }
+
+    fun getHideComplicationTouchHandler(): HideComplicationTouchHandler
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationModule.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationModule.kt
new file mode 100644
index 0000000..59af22a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamComplicationModule.kt
@@ -0,0 +1,28 @@
+package com.android.systemui.dreams.complication.dagger
+
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+@Module
+object DreamComplicationModule {
+    const val COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout"
+    const val COMPLICATIONS_FADE_OUT_DELAY = "complication_fade_out_delay"
+
+    /** Provides the delay to wait for before fading out complications. */
+    @Provides
+    @Named(COMPLICATIONS_FADE_OUT_DELAY)
+    fun providesComplicationsFadeOutDelay(@Main resources: Resources): Int {
+        return resources.getInteger(R.integer.complicationFadeOutDelayMs)
+    }
+
+    /** Provides the timeout for restoring complication visibility. */
+    @Provides
+    @Named(COMPLICATIONS_RESTORE_TIMEOUT)
+    fun providesComplicationsRestoreTimeout(@Main resources: Resources): Int {
+        return resources.getInteger(R.integer.complicationRestoreMs)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 3171bbc..216cb86 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -32,7 +32,7 @@
 import com.android.systemui.dreams.DreamOverlayNotificationCountProvider;
 import com.android.systemui.dreams.DreamOverlayService;
 import com.android.systemui.dreams.SystemDialogsCloser;
-import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
+import com.android.systemui.dreams.complication.dagger.DreamComplicationComponent;
 import com.android.systemui.dreams.homecontrols.HomeControlsDreamService;
 import com.android.systemui.dreams.homecontrols.dagger.HomeControlsDataSourceModule;
 import com.android.systemui.dreams.homecontrols.dagger.HomeControlsRemoteServiceComponent;
@@ -68,7 +68,7 @@
         HomeControlsDataSourceModule.class,
 },
         subcomponents = {
-                ComplicationComponent.class,
+                DreamComplicationComponent.class,
                 DreamOverlayComponent.class,
                 HomeControlsRemoteServiceComponent.class,
         })
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
index b37206a..160574fa 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
@@ -19,6 +19,7 @@
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
@@ -38,6 +39,7 @@
 class DreamUserActionsViewModel
 @AssistedInject
 constructor(
+    private val communalInteractor: CommunalInteractor,
     private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
     private val shadeInteractor: ShadeInteractor,
 ) : UserActionsViewModel() {
@@ -50,10 +52,13 @@
                 } else {
                     combine(
                         deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked },
+                        communalInteractor.isCommunalAvailable,
                         shadeInteractor.shadeMode,
-                    ) { isDeviceUnlocked, shadeMode ->
+                    ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
                         buildList {
-                                add(Swipe.Start to Scenes.Communal)
+                                if (isCommunalAvailable) {
+                                    add(Swipe.Start to Scenes.Communal)
+                                }
 
                                 val bouncerOrGone =
                                     if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
index 2a3729b..a36e45f 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
@@ -27,7 +27,13 @@
 import android.os.UserHandle
 import android.view.accessibility.AccessibilityManager
 import androidx.core.app.NotificationCompat
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.CoreStartable
+import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
+import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
+import com.android.systemui.contextualeducation.GestureType.OVERVIEW
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.education.ui.viewmodel.ContextualEduNotificationViewModel
@@ -36,10 +42,13 @@
 import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity
 import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_ENTRY_POINT_CONTEXTUAL_EDU
 import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_ENTRY_POINT_KEY
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_KEY
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_TOUCHPAD_BACK
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_TOUCHPAD_HOME
 import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * A class to show contextual education on UI based on the edu produced from
@@ -59,6 +68,8 @@
         private const val CHANNEL_ID = "ContextualEduNotificationChannel"
         private const val TAG = "ContextualEduUiCoordinator"
         private const val NOTIFICATION_ID = 1000
+        private const val TUTORIAL_ACTION: String = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
+        private const val SYSTEMUI_PACKAGE_NAME: String = "com.android.systemui"
     }
 
     @Inject
@@ -107,6 +118,7 @@
     }
 
     private fun showDialog(model: ContextualEduToastViewModel) {
+        dialog?.dismiss()
         dialog = createDialog(model)
         dialog?.show()
     }
@@ -124,7 +136,7 @@
                 .setSmallIcon(R.drawable.ic_settings)
                 .setContentTitle(model.title)
                 .setContentText(model.message)
-                .setContentIntent(createPendingIntent())
+                .setContentIntent(createPendingIntent(model.gestureType))
                 .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                 .setAutoCancel(true)
                 .addExtras(extras)
@@ -137,21 +149,37 @@
         )
     }
 
-    private fun createPendingIntent(): PendingIntent {
+    private fun createPendingIntent(gestureType: GestureType): PendingIntent {
         val intent =
-            Intent(context, KeyboardTouchpadTutorialActivity::class.java).apply {
-                addCategory(Intent.CATEGORY_DEFAULT)
-                flags = Intent.FLAG_ACTIVITY_NEW_TASK
-                putExtra(
-                    INTENT_TUTORIAL_ENTRY_POINT_KEY,
-                    INTENT_TUTORIAL_ENTRY_POINT_CONTEXTUAL_EDU,
-                )
+            when (gestureType) {
+                BACK -> createKeyboardTouchpadTutorialIntent(INTENT_TUTORIAL_SCOPE_TOUCHPAD_BACK)
+                HOME -> createKeyboardTouchpadTutorialIntent(INTENT_TUTORIAL_SCOPE_TOUCHPAD_HOME)
+                ALL_APPS -> createKeyboardTouchpadTutorialIntent(INTENT_TUTORIAL_SCOPE_KEYBOARD)
+                OVERVIEW -> createTouchpadTutorialIntent()
             }
+
         return PendingIntent.getActivity(
             context,
             /* requestCode= */ 0,
             intent,
-            PendingIntent.FLAG_IMMUTABLE,
+            // FLAG_UPDATE_CURRENT to avoid caching of intent extras and always use latest values
+            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
         )
     }
+
+    private fun createKeyboardTouchpadTutorialIntent(tutorialType: String): Intent {
+        return Intent(context, KeyboardTouchpadTutorialActivity::class.java).apply {
+            addCategory(Intent.CATEGORY_DEFAULT)
+            flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            putExtra(INTENT_TUTORIAL_SCOPE_KEY, tutorialType)
+            putExtra(INTENT_TUTORIAL_ENTRY_POINT_KEY, INTENT_TUTORIAL_ENTRY_POINT_CONTEXTUAL_EDU)
+        }
+    }
+
+    private fun createTouchpadTutorialIntent(): Intent {
+        return Intent(TUTORIAL_ACTION).apply {
+            flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            setPackage(SYSTEMUI_PACKAGE_NAME)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt
index 5a02cda..06c0d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt
@@ -16,11 +16,14 @@
 
 package com.android.systemui.education.ui.viewmodel
 
+import com.android.systemui.contextualeducation.GestureType
+
 sealed class ContextualEduContentViewModel(open val userId: Int)
 
 data class ContextualEduNotificationViewModel(
     val title: String,
     val message: String,
+    val gestureType: GestureType,
     override val userId: Int,
 ) : ContextualEduContentViewModel(userId)
 
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
index 7417a70..443ad020 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
@@ -68,6 +68,7 @@
                     ContextualEduNotificationViewModel(
                         getEduTitle(it),
                         getEduContent(it),
+                        it.gestureType,
                         it.userId,
                     )
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
index 215ceac..0ed4007 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
@@ -78,9 +78,16 @@
             cookie: ActivityTransitionAnimator.TransitionCookie?,
             component: ComponentName?,
             returnCujType: Int?,
+            isEphemeral: Boolean,
         ): ActivityTransitionAnimator.Controller? =
             delegate
-                .activityTransitionController(launchCujType, cookie, component, returnCujType)
+                .activityTransitionController(
+                    launchCujType,
+                    cookie,
+                    component,
+                    returnCujType,
+                    isEphemeral,
+                )
                 ?.withStateAwareness(onActivityLaunchTransitionStart, onActivityLaunchTransitionEnd)
 
         override fun dialogTransitionController(
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index b82aa81..1504402 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -284,6 +284,7 @@
                     cookie: ActivityTransitionAnimator.TransitionCookie?,
                     component: ComponentName?,
                     returnCujType: Int?,
+                    isEphemeral: Boolean,
                 ): ActivityTransitionAnimator.Controller? {
                     val delegatedController =
                         ActivityTransitionAnimator.Controller.fromView(
@@ -292,6 +293,7 @@
                             cookie,
                             component,
                             returnCujType,
+                            isEphemeral,
                         )
                     return delegatedController?.let { createTransitionControllerDelegate(it) }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
index 092a25a..7bd09b9 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.os.Build
 import android.os.UserHandle
 import com.android.systemui.CoreStartable
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -52,13 +53,13 @@
             receiver =
                 object : BroadcastReceiver() {
                     override fun onReceive(context: Context, intent: Intent) {
-                        applicationContext.startActivityAsUser(
-                            Intent(
-                                applicationContext,
-                                KeyboardTouchpadTutorialActivity::class.java
-                            ),
-                            UserHandle.SYSTEM
-                        )
+                        val activityIntent =
+                            Intent(applicationContext, KeyboardTouchpadTutorialActivity::class.java)
+                        if (Build.IS_DEBUGGABLE) {
+                            // helpful for testing different cases but pointless for public builds
+                            intent.extras?.let { activityIntent.putExtras(it) }
+                        }
+                        applicationContext.startActivityAsUser(activityIntent, UserHandle.SYSTEM)
                     }
                 },
             filter = IntentFilter("com.android.systemui.action.KEYBOARD_TOUCHPAD_TUTORIAL"),
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialMetricsLogger.kt
index 144c5ead..ae6cbbc 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialMetricsLogger.kt
@@ -18,8 +18,8 @@
 
 import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_ENTRY_POINT_CONTEXTUAL_EDU
 import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_ENTRY_POINT_SCHEDULER
-import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
-import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_TOUCHPAD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_TOUCHPAD
 import com.android.systemui.shared.system.SysUiStatsLog
 import javax.inject.Inject
 
@@ -37,9 +37,9 @@
 
         val tutorialType =
             when (tutorialTypeExtra) {
-                INTENT_TUTORIAL_TYPE_KEYBOARD ->
+                INTENT_TUTORIAL_SCOPE_KEYBOARD ->
                     SysUiStatsLog.PERIPHERAL_TUTORIAL_LAUNCHED__TUTORIAL_TYPE__KEYBOARD
-                INTENT_TUTORIAL_TYPE_TOUCHPAD ->
+                INTENT_TUTORIAL_SCOPE_TOUCHPAD ->
                     SysUiStatsLog.PERIPHERAL_TUTORIAL_LAUNCHED__TUTORIAL_TYPE__TOUCHPAD
                 else -> SysUiStatsLog.PERIPHERAL_TUTORIAL_LAUNCHED__TUTORIAL_TYPE__BOTH
             }
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
index 9dae649..3cba70e 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
@@ -34,10 +34,10 @@
 import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity
 import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_ENTRY_POINT_KEY
 import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_ENTRY_POINT_SCHEDULER
-import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_BOTH
-import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
-import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
-import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_TOUCHPAD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_ALL
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_KEY
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_TOUCHPAD
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import javax.inject.Inject
@@ -108,7 +108,7 @@
     private fun createPendingIntent(tutorialType: String): PendingIntent {
         val intent =
             Intent(context, KeyboardTouchpadTutorialActivity::class.java).apply {
-                putExtra(INTENT_TUTORIAL_TYPE_KEY, tutorialType)
+                putExtra(INTENT_TUTORIAL_SCOPE_KEY, tutorialType)
                 putExtra(INTENT_TUTORIAL_ENTRY_POINT_KEY, INTENT_TUTORIAL_ENTRY_POINT_SCHEDULER)
                 flags = Intent.FLAG_ACTIVITY_NEW_TASK
             }
@@ -128,13 +128,13 @@
                 NotificationInfo(
                     context.getString(R.string.launch_keyboard_tutorial_notification_title),
                     context.getString(R.string.launch_keyboard_tutorial_notification_content),
-                    INTENT_TUTORIAL_TYPE_KEYBOARD,
+                    INTENT_TUTORIAL_SCOPE_KEYBOARD,
                 )
             TutorialType.TOUCHPAD ->
                 NotificationInfo(
                     context.getString(R.string.launch_touchpad_tutorial_notification_title),
                     context.getString(R.string.launch_touchpad_tutorial_notification_content),
-                    INTENT_TUTORIAL_TYPE_TOUCHPAD,
+                    INTENT_TUTORIAL_SCOPE_TOUCHPAD,
                 )
             TutorialType.BOTH ->
                 NotificationInfo(
@@ -144,7 +144,7 @@
                     context.getString(
                         R.string.launch_keyboard_touchpad_tutorial_notification_content
                     ),
-                    INTENT_TUTORIAL_TYPE_BOTH,
+                    INTENT_TUTORIAL_SCOPE_ALL,
                 )
             TutorialType.NONE -> null
         }
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
index 2be619b..abd39cc 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
@@ -23,13 +23,16 @@
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.node.Ref
@@ -93,13 +96,19 @@
     animationProperties: LottieDynamicProperties,
 ) {
     val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(educationAnimationId))
+    var isPlaying by remember { mutableStateOf(true) }
     val progress by
-        animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
+        animateLottieCompositionAsState(
+            composition,
+            iterations = LottieConstants.IterateForever,
+            isPlaying = isPlaying,
+            restartOnPlay = false,
+        )
     LottieAnimation(
         composition = composition,
         progress = { progress },
         dynamicProperties = animationProperties,
-        modifier = Modifier.fillMaxSize(),
+        modifier = Modifier.fillMaxSize().clickable { isPlaying = !isPlaying },
     )
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
index fee08b3..67b307f 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
@@ -56,10 +56,13 @@
 ) : ComponentActivity() {
 
     companion object {
-        const val INTENT_TUTORIAL_TYPE_KEY = "tutorial_type"
-        const val INTENT_TUTORIAL_TYPE_TOUCHPAD = "touchpad"
-        const val INTENT_TUTORIAL_TYPE_KEYBOARD = "keyboard"
-        const val INTENT_TUTORIAL_TYPE_BOTH = "both"
+        const val INTENT_TUTORIAL_SCOPE_KEY = "tutorial_scope"
+        const val INTENT_TUTORIAL_SCOPE_TOUCHPAD = "touchpad"
+        const val INTENT_TUTORIAL_SCOPE_TOUCHPAD_BACK = "touchpad_back"
+        const val INTENT_TUTORIAL_SCOPE_TOUCHPAD_HOME = "touchpad_home"
+        const val INTENT_TUTORIAL_SCOPE_KEYBOARD = "keyboard"
+        const val INTENT_TUTORIAL_SCOPE_ALL = "all"
+
         const val INTENT_TUTORIAL_ENTRY_POINT_KEY = "entry_point"
         const val INTENT_TUTORIAL_ENTRY_POINT_SCHEDULER = "scheduler"
         const val INTENT_TUTORIAL_ENTRY_POINT_CONTEXTUAL_EDU = "contextual_edu"
@@ -94,7 +97,7 @@
         if (savedInstanceState == null) {
             metricsLogger.logPeripheralTutorialLaunched(
                 intent.getStringExtra(INTENT_TUTORIAL_ENTRY_POINT_KEY),
-                intent.getStringExtra(INTENT_TUTORIAL_TYPE_KEY),
+                intent.getStringExtra(INTENT_TUTORIAL_SCOPE_KEY),
             )
             logger.logOpenTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
index 896bdc0..eeb4b69 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
@@ -22,15 +22,21 @@
 import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
 import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState
 import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor
-import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
-import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_ALL
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_KEY
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_TOUCHPAD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_TOUCHPAD_BACK
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_SCOPE_TOUCHPAD_HOME
 import com.android.systemui.inputdevice.tutorial.ui.viewmodel.RequiredHardware.KEYBOARD
 import com.android.systemui.inputdevice.tutorial.ui.viewmodel.RequiredHardware.TOUCHPAD
 import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.ACTION_KEY
 import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.BACK_GESTURE
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.HOME_GESTURE
 import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -42,27 +48,23 @@
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNot
 import kotlinx.coroutines.flow.runningFold
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 class KeyboardTouchpadTutorialViewModel(
     private val gesturesInteractor: Optional<TouchpadGesturesInteractor>,
     private val keyboardTouchpadConnectionInteractor: KeyboardTouchpadConnectionInteractor,
     private val hasTouchpadTutorialScreens: Boolean,
     private val logger: InputDeviceTutorialLogger,
-    handle: SavedStateHandle
+    handle: SavedStateHandle,
 ) : ViewModel(), DefaultLifecycleObserver {
 
-    private fun startingScreen(handle: SavedStateHandle): Screen {
-        val tutorialType: String? = handle[INTENT_TUTORIAL_TYPE_KEY]
-        return if (tutorialType == INTENT_TUTORIAL_TYPE_KEYBOARD) ACTION_KEY else BACK_GESTURE
-    }
-
     private val _screen = MutableStateFlow(startingScreen(handle))
     val screen: Flow<Screen> = _screen.filter { it.canBeShown() }
 
     private val _closeActivity: MutableStateFlow<Boolean> = MutableStateFlow(false)
     val closeActivity: StateFlow<Boolean> = _closeActivity
 
+    private val screenSequence: ScreenSequence = chooseScreenSequence(handle)
+
     private val screensBackStack = ArrayDeque(listOf(_screen.value))
 
     private var connectionState: ConnectionState =
@@ -105,6 +107,33 @@
         }
     }
 
+    private fun startingScreen(handle: SavedStateHandle): Screen {
+        val scope: String? = handle[INTENT_TUTORIAL_SCOPE_KEY]
+        return when (scope) {
+            INTENT_TUTORIAL_SCOPE_KEYBOARD -> ACTION_KEY
+            INTENT_TUTORIAL_SCOPE_TOUCHPAD_HOME -> HOME_GESTURE
+            INTENT_TUTORIAL_SCOPE_TOUCHPAD,
+            INTENT_TUTORIAL_SCOPE_ALL,
+            INTENT_TUTORIAL_SCOPE_TOUCHPAD_BACK -> BACK_GESTURE
+            else -> {
+                logger.w("Intent didn't specify tutorial scope, starting with default")
+                BACK_GESTURE
+            }
+        }
+    }
+
+    private fun chooseScreenSequence(handle: SavedStateHandle): ScreenSequence {
+        val scope: String? = handle[INTENT_TUTORIAL_SCOPE_KEY]
+        return if (
+            scope == INTENT_TUTORIAL_SCOPE_TOUCHPAD_HOME ||
+                scope == INTENT_TUTORIAL_SCOPE_TOUCHPAD_BACK
+        ) {
+            SingleScreenOnly
+        } else {
+            AllSupportedScreens
+        }
+    }
+
     override fun onCleared() {
         // this shouldn't be needed as onTutorialInvisible should already clear device state but
         // it'd be really bad if we'd block gestures/shortcuts after leaving tutorial so just to be
@@ -121,13 +150,13 @@
     }
 
     fun onDoneButtonClicked() {
-        var nextScreen = _screen.value.next()
+        var nextScreen = screenSequence.nextScreen(_screen.value)
         while (nextScreen != null) {
             if (requiredHardwarePresent(nextScreen)) {
                 break
             }
             logger.logNextScreenMissingHardware(nextScreen)
-            nextScreen = nextScreen.next()
+            nextScreen = screenSequence.nextScreen(nextScreen)
         }
         if (nextScreen == null) {
             logger.d("Final screen reached, closing tutorial")
@@ -192,33 +221,44 @@
         override fun <T : ViewModel> create(
             key: String,
             modelClass: Class<T>,
-            handle: SavedStateHandle
+            handle: SavedStateHandle,
         ): T =
             KeyboardTouchpadTutorialViewModel(
                 gesturesInteractor,
                 keyboardTouchpadConnected,
                 hasTouchpadTutorialScreens,
                 logger,
-                handle
+                handle,
             )
                 as T
     }
+
+    private interface ScreenSequence {
+        fun nextScreen(current: Screen): Screen?
+    }
+
+    private object AllSupportedScreens : ScreenSequence {
+        override fun nextScreen(current: Screen): Screen? {
+            return when (current) {
+                BACK_GESTURE -> HOME_GESTURE
+                HOME_GESTURE -> ACTION_KEY
+                ACTION_KEY -> null
+            }
+        }
+    }
+
+    private object SingleScreenOnly : ScreenSequence {
+        override fun nextScreen(current: Screen): Screen? = null
+    }
 }
 
 enum class RequiredHardware {
     TOUCHPAD,
-    KEYBOARD
+    KEYBOARD,
 }
 
 enum class Screen(val requiredHardware: RequiredHardware) {
     BACK_GESTURE(requiredHardware = TOUCHPAD),
     HOME_GESTURE(requiredHardware = TOUCHPAD),
-    ACTION_KEY(requiredHardware = KEYBOARD);
-
-    fun next(): Screen? =
-        when (this) {
-            BACK_GESTURE -> HOME_GESTURE
-            HOME_GESTURE -> ACTION_KEY
-            ACTION_KEY -> null
-        }
+    ACTION_KEY(requiredHardware = KEYBOARD),
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
index 38fc2a8..84a423e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt
@@ -21,11 +21,12 @@
 import android.view.WindowManager
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.settingslib.Utils
+import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyboard.docking.domain.interactor.KeyboardDockingIndicationInteractor
-import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.surfaceeffects.glowboxeffect.GlowBoxConfig
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -37,9 +38,9 @@
 @Inject
 constructor(
     private val windowManager: WindowManager,
-    private val context: Context,
+    @Application private val context: Context,
     keyboardDockingIndicationInteractor: KeyboardDockingIndicationInteractor,
-    @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
+    @GlobalConfig configurationInteractor: ConfigurationInteractor,
     @Background private val backgroundScope: CoroutineScope,
 ) {
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
new file mode 100644
index 0000000..36cd400
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.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.keyboard.shortcut.data.repository
+
+import android.content.Context
+import android.content.Context.INPUT_SERVICE
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+import android.hardware.input.InputSettings
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult
+import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.ERROR_OTHER
+import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.ERROR_RESERVED_COMBINATION
+import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.SUCCESS
+import com.android.systemui.settings.UserTracker
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+
+class CustomInputGesturesRepository
+@Inject
+constructor(private val userTracker: UserTracker,
+    @Background private val bgCoroutineContext: CoroutineContext)
+{
+
+    private val userContext: Context
+        get() = userTracker.createCurrentUserContext(userTracker.userContext)
+
+    // Input manager created with user context to provide correct user id when requesting custom
+    // shortcut
+    private val inputManager: InputManager
+        get() = userContext.getSystemService(INPUT_SERVICE) as InputManager
+
+    private val _customInputGesture = MutableStateFlow<List<InputGestureData>>(emptyList())
+
+    val customInputGestures =
+        _customInputGesture.onStart { refreshCustomInputGestures() }
+
+    private fun refreshCustomInputGestures() {
+        setCustomInputGestures(inputGestures = retrieveCustomInputGestures())
+    }
+
+    private fun setCustomInputGestures(inputGestures: List<InputGestureData>) {
+        _customInputGesture.value = inputGestures
+    }
+
+    fun retrieveCustomInputGestures(): List<InputGestureData> {
+        return if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) {
+            inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+        } else emptyList()
+    }
+
+    suspend fun addCustomInputGesture(inputGesture: InputGestureData): ShortcutCustomizationRequestResult {
+        return withContext(bgCoroutineContext) {
+            when (val result = inputManager.addCustomInputGesture(inputGesture)) {
+                CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> {
+                    refreshCustomInputGestures()
+                    SUCCESS
+                }
+                CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS ->
+                    ERROR_RESERVED_COMBINATION
+
+                CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE ->
+                    ERROR_RESERVED_COMBINATION
+
+                else -> {
+                    Log.w(
+                        TAG,
+                        "Attempted to add inputGesture: $inputGesture " +
+                                "but ran into an error with code: $result",
+                    )
+                    ERROR_OTHER
+                }
+            }
+        }
+    }
+
+    suspend fun deleteCustomInputGesture(inputGesture: InputGestureData): ShortcutCustomizationRequestResult {
+        return withContext(bgCoroutineContext){
+            when (
+                val result = inputManager.removeCustomInputGesture(inputGesture)
+            ) {
+                CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> {
+                    refreshCustomInputGestures()
+                    SUCCESS
+                }
+                else -> {
+                    Log.w(
+                        TAG,
+                        "Attempted to delete inputGesture: $inputGesture " +
+                                "but ran into an error with code: $result",
+                    )
+                    ERROR_OTHER
+                }
+            }
+        }
+    }
+
+    suspend fun resetAllCustomInputGestures(): ShortcutCustomizationRequestResult {
+        return withContext(bgCoroutineContext) {
+            try {
+                inputManager.removeAllCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+                setCustomInputGestures(emptyList())
+                SUCCESS
+            } catch (e: Exception) {
+                Log.w(
+                    TAG,
+                    "Attempted to remove all custom shortcut but ran into a remote error: $e",
+                )
+                ERROR_OTHER
+            }
+        }
+    }
+
+    private companion object {
+        private const val TAG = "CustomInputGesturesRepository"
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
index 99cafd3..4af3786 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
@@ -17,17 +17,12 @@
 package com.android.systemui.keyboard.shortcut.data.repository
 
 import android.content.Context
-import android.content.Context.INPUT_SERVICE
 import android.hardware.input.InputGestureData
 import android.hardware.input.InputGestureData.Builder
 import android.hardware.input.InputGestureData.KeyTrigger
 import android.hardware.input.InputGestureData.createKeyTrigger
 import android.hardware.input.InputManager
-import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS
-import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE
-import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
-import android.hardware.input.InputSettings
-import android.hardware.input.KeyGestureEvent
+import android.hardware.input.KeyGestureEvent.KeyGestureType
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import androidx.compose.runtime.mutableStateOf
@@ -43,7 +38,6 @@
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
-import com.android.systemui.settings.UserTracker
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -60,21 +54,14 @@
 @Inject
 constructor(
     stateRepository: ShortcutHelperStateRepository,
-    private val userTracker: UserTracker,
     @Background private val backgroundScope: CoroutineScope,
     @Background private val bgCoroutineContext: CoroutineContext,
     private val shortcutCategoriesUtils: ShortcutCategoriesUtils,
     private val context: Context,
-    private val inputGestureMaps: InputGestureMaps
-) : ShortcutCategoriesRepository {
-
-    private val userContext: Context
-        get() = userTracker.createCurrentUserContext(userTracker.userContext)
-
-    // Input manager created with user context to provide correct user id when requesting custom
-    // shortcut
+    private val inputGestureMaps: InputGestureMaps,
+    private val customInputGesturesRepository: CustomInputGesturesRepository,
     private val inputManager: InputManager
-        get() = userContext.getSystemService(INPUT_SERVICE) as InputManager
+) : ShortcutCategoriesRepository {
 
     private val _selectedKeyCombination = MutableStateFlow<KeyCombination?>(null)
     private val _shortcutBeingCustomized = mutableStateOf<ShortcutCustomizationRequestInfo?>(null)
@@ -124,16 +111,12 @@
             )
 
     override val categories: Flow<List<ShortcutCategory>> =
-        activeInputDevice
-            .map { inputDevice ->
+        combine(activeInputDevice, customInputGesturesRepository.customInputGestures)
+        { inputDevice, inputGestures ->
                 if (inputDevice == null) {
                     emptyList()
                 } else {
-                    val customInputGesturesForUser: List<InputGestureData> =
-                        if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) {
-                            inputManager.getCustomInputGestures(/* filter= */ null)
-                        } else emptyList()
-                    val sources = toInternalGroupSources(customInputGesturesForUser)
+                    val sources = toInternalGroupSources(inputGestures)
                     val supportedKeyCodes =
                         shortcutCategoriesUtils.fetchSupportedKeyCodes(
                             inputDevice.id,
@@ -173,61 +156,92 @@
                 .addKeyGestureTypeFromShortcutLabel()
                 .addTriggerFromSelectedKeyCombination()
                 .build()
-            // TODO(b/379648200) add app launch data for application categories shortcut after
-            // dynamic
-            // label/icon mapping implementation
+            // TODO(b/379648200) add app launch data after dynamic label/icon mapping implementation
         } catch (e: IllegalArgumentException) {
             Log.w(TAG, "could not add custom shortcut: $e")
             return null
         }
     }
 
-    suspend fun confirmAndSetShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
-        return withContext(bgCoroutineContext) {
-            val inputGestureData =
-                buildInputGestureDataForShortcutBeingCustomized()
-                    ?: return@withContext ShortcutCustomizationRequestResult.ERROR_OTHER
+    private fun retrieveInputGestureDataForShortcutBeingDeleted(): InputGestureData? {
+        val keyGestureType = getKeyGestureTypeFromShortcutBeingDeletedLabel()
+        return customInputGesturesRepository.retrieveCustomInputGestures()
+            .firstOrNull { it.action.keyGestureType() == keyGestureType }
+    }
 
-            return@withContext when (inputManager.addCustomInputGesture(inputGestureData)) {
-                CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> ShortcutCustomizationRequestResult.SUCCESS
-                CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS ->
-                    ShortcutCustomizationRequestResult.ERROR_RESERVED_COMBINATION
+    suspend fun confirmAndSetShortcutCurrentlyBeingCustomized():
+        ShortcutCustomizationRequestResult {
+        val inputGestureData =
+            buildInputGestureDataForShortcutBeingCustomized()
+                ?: return ShortcutCustomizationRequestResult.ERROR_OTHER
 
-                CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE ->
-                    ShortcutCustomizationRequestResult.ERROR_RESERVED_COMBINATION
+        return customInputGesturesRepository.addCustomInputGesture(inputGestureData)
+    }
 
-                else -> ShortcutCustomizationRequestResult.ERROR_OTHER
-            }
-        }
+    suspend fun deleteShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
+        val inputGestureData =
+            retrieveInputGestureDataForShortcutBeingDeleted()
+                ?: return ShortcutCustomizationRequestResult.ERROR_OTHER
+        return customInputGesturesRepository.deleteCustomInputGesture(inputGestureData)
+    }
+
+    suspend fun resetAllCustomShortcuts(): ShortcutCustomizationRequestResult {
+        return customInputGesturesRepository.resetAllCustomInputGestures()
     }
 
     private fun Builder.addKeyGestureTypeFromShortcutLabel(): Builder {
-        val shortcutBeingCustomized =
-            getShortcutBeingCustomized() as? ShortcutCustomizationRequestInfo.Add
-
-        if (shortcutBeingCustomized == null) {
-            Log.w(TAG, "User requested to set shortcut but shortcut being customized is null")
-            return this
-        }
-
-        val keyGestureType =
-            inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label]
+        val keyGestureType = getKeyGestureTypeFromShortcutBeingCustomizedLabel()
 
         if (keyGestureType == null) {
-            Log.w(TAG, "Could not find KeyGestureType for shortcut $shortcutBeingCustomized")
+            Log.w(
+                TAG,
+                "Could not find KeyGestureType for shortcut ${_shortcutBeingCustomized.value}",
+            )
             return this
         }
 
         return setKeyGestureType(keyGestureType)
     }
 
+    @KeyGestureType
+    private fun getKeyGestureTypeFromShortcutBeingCustomizedLabel(): Int? {
+        val shortcutBeingCustomized =
+            getShortcutBeingCustomized() as? ShortcutCustomizationRequestInfo.Add
+
+        if (shortcutBeingCustomized == null) {
+            Log.w(
+                TAG,
+                "Requested key gesture type from label but shortcut being customized is null",
+            )
+            return null
+        }
+
+        return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label]
+    }
+
+    @KeyGestureType
+    private fun getKeyGestureTypeFromShortcutBeingDeletedLabel(): Int? {
+        val shortcutBeingCustomized =
+            getShortcutBeingCustomized() as? ShortcutCustomizationRequestInfo.Delete
+
+        if (shortcutBeingCustomized == null) {
+            Log.w(
+                TAG,
+                "Requested key gesture type from label but shortcut being customized is null",
+            )
+            return null
+        }
+
+        return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label]
+    }
+
     private fun Builder.addTriggerFromSelectedKeyCombination(): Builder {
         val selectedKeyCombination = _selectedKeyCombination.value
         if (selectedKeyCombination?.keyCode == null) {
             Log.w(
                 TAG,
                 "User requested to set shortcut but selected key combination is " +
-                        "$selectedKeyCombination",
+                    "$selectedKeyCombination",
             )
             return this
         }
@@ -235,8 +249,7 @@
         return setTrigger(
             createKeyTrigger(
                 /* keycode = */ selectedKeyCombination.keyCode,
-                /* modifierState = */
-                shortcutCategoriesUtils.removeUnsupportedModifiers(
+                /* modifierState = */ shortcutCategoriesUtils.removeUnsupportedModifiers(
                     selectedKeyCombination.modifiers
                 ),
             )
@@ -256,10 +269,8 @@
                 val keyTrigger = gestureData.trigger as KeyTrigger
                 val keyGestureType = gestureData.action.keyGestureType()
                 fetchGroupLabelByGestureType(keyGestureType)?.let { groupLabel ->
-                    toInternalKeyboardShortcutInfo(
-                        keyGestureType,
-                        keyTrigger
-                    )?.let { internalKeyboardShortcutInfo ->
+                    toInternalKeyboardShortcutInfo(keyGestureType, keyTrigger)?.let {
+                        internalKeyboardShortcutInfo ->
                         val group =
                             InternalKeyboardShortcutGroup(
                                 label = groupLabel,
@@ -291,24 +302,20 @@
         return null
     }
 
-    private fun fetchGroupLabelByGestureType(
-        @KeyGestureEvent.KeyGestureType keyGestureType: Int
-    ): String? {
+    private fun fetchGroupLabelByGestureType(@KeyGestureType keyGestureType: Int): String? {
         inputGestureMaps.gestureToInternalKeyboardShortcutGroupLabelResIdMap[keyGestureType]?.let {
             return context.getString(it)
         } ?: return null
     }
 
-    private fun fetchShortcutInfoLabelByGestureType(
-        @KeyGestureEvent.KeyGestureType keyGestureType: Int
-    ): String? {
+    private fun fetchShortcutInfoLabelByGestureType(@KeyGestureType keyGestureType: Int): String? {
         inputGestureMaps.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let {
             return context.getString(it)
         } ?: return null
     }
 
     private fun fetchShortcutCategoryTypeByGestureType(
-        @KeyGestureEvent.KeyGestureType keyGestureType: Int
+        @KeyGestureType keyGestureType: Int
     ): ShortcutCategoryType? {
         return inputGestureMaps.gestureToShortcutCategoryTypeMap[keyGestureType]
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
index d228a15..1c380c2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
@@ -48,9 +48,7 @@
 import com.android.systemui.res.R
 import javax.inject.Inject
 
-class InputGestureMaps
-@Inject
-constructor(private val context: Context) {
+class InputGestureMaps @Inject constructor(private val context: Context) {
     val gestureToShortcutCategoryTypeMap =
         mapOf(
             // System Category
@@ -106,7 +104,6 @@
                 R.string.shortcut_helper_category_system_apps,
 
             // Multitasking Category
-            KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to R.string.shortcutHelper_category_recent_apps,
             KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to
                 R.string.shortcutHelper_category_split_screen,
             KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to
@@ -180,9 +177,10 @@
         )
 
     val shortcutLabelToKeyGestureTypeMap: Map<String, Int>
-        get() = gestureToInternalKeyboardShortcutInfoLabelResIdMap.entries.associateBy({
-            context.getString(it.value)
-        }) {
-            it.key
-        }
+        get() =
+            gestureToInternalKeyboardShortcutInfoLabelResIdMap.entries.associateBy({
+                context.getString(it.value)
+            }) {
+                it.key
+            }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
index 95bc9f6..27d1a30 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
@@ -36,9 +36,9 @@
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
-import kotlinx.coroutines.withContext
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
 
 class ShortcutCategoriesUtils
 @Inject
@@ -116,7 +116,10 @@
             .filter {
                 // Allow KEYCODE_UNKNOWN (0) because shortcuts can have just modifiers and no
                 // keycode, or they could have a baseCharacter instead of a keycode.
-                it.keycode == KeyEvent.KEYCODE_UNKNOWN || supportedKeyCodes.contains(it.keycode)
+                it.keycode == KeyEvent.KEYCODE_UNKNOWN ||
+                    supportedKeyCodes.contains(it.keycode) ||
+                    // Support keyboard function row key codes
+                    keyGlyphMap?.functionRowKeys?.contains(it.keycode) ?: false
             }
             .mapNotNull { toShortcut(keyGlyphMap, keyCharacterMap, it, keepIcons) }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
index e47b33f..d91922d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
@@ -99,6 +99,7 @@
 import android.view.KeyEvent.KEYCODE_PAGE_UP
 import android.view.KeyEvent.KEYCODE_PERIOD
 import android.view.KeyEvent.KEYCODE_RECENT_APPS
+import android.view.KeyEvent.KEYCODE_SCREENSHOT
 import android.view.KeyEvent.KEYCODE_SCROLL_LOCK
 import android.view.KeyEvent.KEYCODE_SHIFT_LEFT
 import android.view.KeyEvent.KEYCODE_SHIFT_RIGHT
@@ -125,6 +126,14 @@
             KEYCODE_RECENT_APPS to R.drawable.ic_check_box_outline_blank,
         )
 
+    val keyLabelResIds =
+        mapOf(
+            KEYCODE_BACK to R.string.group_system_go_back,
+            KEYCODE_HOME to R.string.group_system_access_home_screen,
+            KEYCODE_RECENT_APPS to R.string.group_system_overview_open_apps,
+            KEYCODE_SCREENSHOT to R.string.group_system_full_screenshot,
+        )
+
     val modifierLabels =
         mapOf<Int, (Context) -> String>(
             // Modifiers
@@ -140,6 +149,7 @@
         mapOf<Int, (Context) -> String>(
             KEYCODE_HOME to { context -> context.getString(R.string.keyboard_key_home) },
             KEYCODE_BACK to { context -> context.getString(R.string.keyboard_key_back) },
+            KEYCODE_RECENT_APPS to { context -> context.getString(R.string.accessibility_recent) },
             KEYCODE_DPAD_UP to { context -> context.getString(R.string.keyboard_key_dpad_up) },
             KEYCODE_DPAD_DOWN to { context -> context.getString(R.string.keyboard_key_dpad_down) },
             KEYCODE_DPAD_LEFT to { context -> context.getString(R.string.keyboard_key_dpad_left) },
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt
index 1b20986..4787507 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSource.kt
@@ -17,12 +17,16 @@
 package com.android.systemui.keyboard.shortcut.data.source
 
 import android.content.res.Resources
+import android.hardware.input.InputManager
+import android.view.KeyEvent.KEYCODE_EMOJI_PICKER
 import android.view.KeyEvent.KEYCODE_SPACE
 import android.view.KeyEvent.META_CTRL_ON
 import android.view.KeyEvent.META_SHIFT_ON
 import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
 import android.view.WindowManager
 import android.view.WindowManager.KeyboardShortcutsReceiver
+import com.android.systemui.Flags.shortcutHelperKeyGlyph
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo
 import com.android.systemui.res.R
@@ -31,16 +35,19 @@
 
 class InputShortcutsSource
 @Inject
-constructor(@Main private val resources: Resources, private val windowManager: WindowManager) :
-    KeyboardShortcutGroupsSource {
+constructor(
+    @Main private val resources: Resources,
+    private val windowManager: WindowManager,
+    private val inputManager: InputManager,
+) : KeyboardShortcutGroupsSource {
     override suspend fun shortcutGroups(deviceId: Int): List<KeyboardShortcutGroup> =
-        getInputLanguageShortcutGroup() + getImeShortcutGroup(deviceId)
+        getInputLanguageShortcutGroup(deviceId) + getImeShortcutGroup(deviceId)
 
-    private fun getInputLanguageShortcutGroup() =
+    private fun getInputLanguageShortcutGroup(deviceId: Int) =
         listOf(
             KeyboardShortcutGroup(
                 resources.getString(R.string.shortcut_helper_category_input),
-                inputLanguageShortcuts()
+                inputLanguageShortcuts() + hardwareShortcuts(deviceId),
             )
         )
 
@@ -53,9 +60,23 @@
             /* Switch previous language (next language): Ctrl + Shift + Space */
             shortcutInfo(resources.getString(R.string.input_switch_input_language_previous)) {
                 command(META_CTRL_ON or META_SHIFT_ON, KEYCODE_SPACE)
-            }
+            },
         )
 
+    private fun hardwareShortcuts(deviceId: Int): List<KeyboardShortcutInfo> {
+        if (shortcutHelperKeyGlyph()) {
+            val keyGlyphMap = inputManager.getKeyGlyphMap(deviceId)
+            if (keyGlyphMap != null && keyGlyphMap.functionRowKeys.contains(KEYCODE_EMOJI_PICKER)) {
+                return listOf(
+                    shortcutInfo(resources.getString(R.string.input_access_emoji)) {
+                        command(modifiers = 0, KEYCODE_EMOJI_PICKER)
+                    }
+                )
+            }
+        }
+        return emptyList()
+    }
+
     private suspend fun getImeShortcutGroup(deviceId: Int): List<KeyboardShortcutGroup> =
         suspendCancellableCoroutine { continuation ->
             val shortcutsReceiver = KeyboardShortcutsReceiver {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
index 0201f40..df6b04e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
@@ -16,36 +16,40 @@
 
 package com.android.systemui.keyboard.shortcut.data.source
 
+import android.content.Context
 import android.content.res.Resources
 import android.view.KeyEvent.KEYCODE_D
 import android.view.KeyEvent.KEYCODE_DPAD_LEFT
 import android.view.KeyEvent.KEYCODE_DPAD_RIGHT
 import android.view.KeyEvent.KEYCODE_DPAD_UP
-import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.KEYCODE_EQUALS
+import android.view.KeyEvent.KEYCODE_LEFT_BRACKET
+import android.view.KeyEvent.KEYCODE_MINUS
+import android.view.KeyEvent.KEYCODE_RIGHT_BRACKET
 import android.view.KeyEvent.META_ALT_ON
 import android.view.KeyEvent.META_CTRL_ON
 import android.view.KeyEvent.META_META_ON
-import android.view.KeyEvent.META_SHIFT_ON
 import android.view.KeyboardShortcutGroup
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo
 import com.android.systemui.res.R
 import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
+import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import javax.inject.Inject
 
-class MultitaskingShortcutsSource @Inject constructor(@Main private val resources: Resources) :
+class MultitaskingShortcutsSource
+@Inject
+constructor(@Main private val resources: Resources, @Application private val context: Context) :
     KeyboardShortcutGroupsSource {
 
     override suspend fun shortcutGroups(deviceId: Int) =
         listOf(
             KeyboardShortcutGroup(
-                resources.getString(R.string.shortcutHelper_category_recent_apps),
-                recentsShortcuts(),
-            ),
-            KeyboardShortcutGroup(
                 resources.getString(R.string.shortcutHelper_category_split_screen),
                 splitScreenShortcuts(),
-            ),
+            )
         )
 
     private fun splitScreenShortcuts() = buildList {
@@ -95,19 +99,39 @@
                 }
             )
         }
+        if (
+            DesktopModeStatus.canEnterDesktopMode(context) && enableTaskResizingKeyboardShortcuts()
+        ) {
+            // Snap a freeform window to the left
+            //  - Meta + Left bracket
+            add(
+                shortcutInfo(resources.getString(R.string.system_desktop_mode_snap_left_window)) {
+                    command(META_META_ON, KEYCODE_LEFT_BRACKET)
+                }
+            )
+            // Snap a freeform window to the right
+            //  - Meta + Right bracket
+            add(
+                shortcutInfo(resources.getString(R.string.system_desktop_mode_snap_right_window)) {
+                    command(META_META_ON, KEYCODE_RIGHT_BRACKET)
+                }
+            )
+            // Toggle maximize a freeform window
+            //  - Meta + Equals
+            add(
+                shortcutInfo(
+                    resources.getString(R.string.system_desktop_mode_toggle_maximize_window)
+                ) {
+                    command(META_META_ON, KEYCODE_EQUALS)
+                }
+            )
+            // Minimize a freeform window
+            //  - Meta + Minus
+            add(
+                shortcutInfo(resources.getString(R.string.system_desktop_mode_minimize_window)) {
+                    command(META_META_ON, KEYCODE_MINUS)
+                }
+            )
+        }
     }
-
-    private fun recentsShortcuts() =
-        listOf(
-            // Cycle through recent apps (forward):
-            //  - Alt + Tab
-            shortcutInfo(resources.getString(R.string.group_system_cycle_forward)) {
-                command(META_ALT_ON, KEYCODE_TAB)
-            },
-            // Cycle through recent apps (back):
-            //  - Shift + Alt + Tab
-            shortcutInfo(resources.getString(R.string.group_system_cycle_back)) {
-                command(META_SHIFT_ON or META_ALT_ON, KEYCODE_TAB)
-            },
-        )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
index 7c0c75e..5060abd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
@@ -17,11 +17,11 @@
 package com.android.systemui.keyboard.shortcut.data.source
 
 import android.content.res.Resources
+import android.hardware.input.InputManager
+import android.hardware.input.KeyGlyphMap
 import android.view.KeyEvent.KEYCODE_A
 import android.view.KeyEvent.KEYCODE_BACK
-import android.view.KeyEvent.KEYCODE_DEL
 import android.view.KeyEvent.KEYCODE_DPAD_LEFT
-import android.view.KeyEvent.KEYCODE_ENTER
 import android.view.KeyEvent.KEYCODE_ESCAPE
 import android.view.KeyEvent.KEYCODE_H
 import android.view.KeyEvent.KEYCODE_HOME
@@ -32,29 +32,92 @@
 import android.view.KeyEvent.KEYCODE_S
 import android.view.KeyEvent.KEYCODE_SLASH
 import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.META_ALT_ON
 import android.view.KeyEvent.META_CTRL_ON
 import android.view.KeyEvent.META_META_ON
+import android.view.KeyEvent.META_SHIFT_ON
 import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
+import com.android.systemui.Flags.shortcutHelperKeyGlyph
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys
 import com.android.systemui.res.R
 import javax.inject.Inject
 
-class SystemShortcutsSource @Inject constructor(@Main private val resources: Resources) :
+class SystemShortcutsSource
+@Inject
+constructor(@Main private val resources: Resources, private val inputManager: InputManager) :
     KeyboardShortcutGroupsSource {
 
     override suspend fun shortcutGroups(deviceId: Int) =
         listOf(
             KeyboardShortcutGroup(
                 resources.getString(R.string.shortcut_helper_category_system_controls),
-                systemControlsShortcuts()
+                systemControlsShortcuts() + hardwareShortcuts(deviceId),
             ),
             KeyboardShortcutGroup(
                 resources.getString(R.string.shortcut_helper_category_system_apps),
-                systemAppsShortcuts()
-            )
+                systemAppsShortcuts(),
+            ),
         )
 
+    private fun hardwareShortcuts(deviceId: Int): List<KeyboardShortcutInfo> =
+        if (shortcutHelperKeyGlyph()) {
+            val keyGlyphMap = inputManager.getKeyGlyphMap(deviceId)
+            if (keyGlyphMap != null) {
+                functionRowKeys(keyGlyphMap) + keyCombinationShortcuts(keyGlyphMap)
+            } else {
+                // Not add function row keys if it is not supported by keyboard
+                emptyList()
+            }
+        } else {
+            defaultFunctionRowKeys()
+        }
+
+    private fun defaultFunctionRowKeys(): List<KeyboardShortcutInfo> =
+        listOf(
+            shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
+                command(modifiers = 0, KEYCODE_HOME)
+            },
+            shortcutInfo(resources.getString(R.string.group_system_go_back)) {
+                command(modifiers = 0, KEYCODE_BACK)
+            },
+            shortcutInfo(resources.getString(R.string.group_system_overview_open_apps)) {
+                command(modifiers = 0, KEYCODE_RECENT_APPS)
+            },
+        )
+
+    private fun functionRowKeys(keyGlyphMap: KeyGlyphMap): List<KeyboardShortcutInfo> {
+        val functionRowKeys = mutableListOf<KeyboardShortcutInfo>()
+        keyGlyphMap.functionRowKeys.forEach { keyCode ->
+            val labelResId = ShortcutHelperKeys.keyLabelResIds[keyCode]
+            if (labelResId != null) {
+                functionRowKeys.add(
+                    shortcutInfo(resources.getString(labelResId)) {
+                        command(modifiers = 0, keyCode)
+                    }
+                )
+            }
+        }
+        return functionRowKeys
+    }
+
+    private fun keyCombinationShortcuts(keyGlyphMap: KeyGlyphMap): List<KeyboardShortcutInfo> {
+        val shortcuts = mutableListOf<KeyboardShortcutInfo>()
+        keyGlyphMap.hardwareShortcuts.forEach { (keyCombination, keyCode) ->
+            val labelResId = ShortcutHelperKeys.keyLabelResIds[keyCode]
+            if (labelResId != null) {
+                val info =
+                    shortcutInfo(resources.getString(labelResId)) {
+                        command(keyCombination.modifierState, keyCombination.keycode)
+                    }
+                shortcuts.add(info)
+            }
+        }
+        return shortcuts
+    }
+
     private fun systemControlsShortcuts() =
         listOf(
             // Access list of all apps and search (i.e. Search/Launcher):
@@ -63,42 +126,32 @@
                 command(META_META_ON)
             },
             // Access home screen:
-            //  - Home button
             //  - Meta + H
-            //  - Meta + Enter
-            shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
-                command(modifiers = 0, KEYCODE_HOME)
-            },
             shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
                 command(META_META_ON, KEYCODE_H)
             },
-            shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
-                command(META_META_ON, KEYCODE_ENTER)
-            },
             // Overview of open apps:
-            //  - Recent apps button
             //  - Meta + Tab
             shortcutInfo(resources.getString(R.string.group_system_overview_open_apps)) {
-                command(modifiers = 0, KEYCODE_RECENT_APPS)
-            },
-            shortcutInfo(resources.getString(R.string.group_system_overview_open_apps)) {
                 command(META_META_ON, KEYCODE_TAB)
             },
+            // Cycle through recent apps (forward):
+            //  - Alt + Tab
+            shortcutInfo(resources.getString(R.string.group_system_cycle_forward)) {
+                command(META_ALT_ON, KEYCODE_TAB)
+            },
+            // Cycle through recent apps (back):
+            //  - Shift + Alt + Tab
+            shortcutInfo(resources.getString(R.string.group_system_cycle_back)) {
+                command(META_SHIFT_ON or META_ALT_ON, KEYCODE_TAB)
+            },
             // Back: go back to previous state (back button)
-            //  - Back button
             //  - Meta + Escape OR
-            //  - Meta + Backspace OR
             //  - Meta + Left arrow
             shortcutInfo(resources.getString(R.string.group_system_go_back)) {
-                command(modifiers = 0, KEYCODE_BACK)
-            },
-            shortcutInfo(resources.getString(R.string.group_system_go_back)) {
                 command(META_META_ON, KEYCODE_ESCAPE)
             },
             shortcutInfo(resources.getString(R.string.group_system_go_back)) {
-                command(META_META_ON, KEYCODE_DEL)
-            },
-            shortcutInfo(resources.getString(R.string.group_system_go_back)) {
                 command(META_META_ON, KEYCODE_DPAD_LEFT)
             },
             // Take a full screenshot:
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
index f4e2f05..ef24267 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
@@ -41,7 +41,16 @@
         customShortcutRepository.onCustomizationRequested(requestInfo)
     }
 
-    suspend fun confirmAndSetShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
+    suspend fun confirmAndSetShortcutCurrentlyBeingCustomized():
+        ShortcutCustomizationRequestResult {
         return customShortcutRepository.confirmAndSetShortcutCurrentlyBeingCustomized()
     }
+
+    suspend fun deleteShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
+        return customShortcutRepository.deleteShortcutCurrentlyBeingCustomized()
+    }
+
+    suspend fun resetAllCustomShortcuts(): ShortcutCustomizationRequestResult {
+        return customShortcutRepository.resetAllCustomShortcuts()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
index 0381eae..d625846 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
@@ -16,14 +16,22 @@
 
 package com.android.systemui.keyboard.shortcut.domain.interactor
 
+import android.content.Context
+import android.view.KeyEvent.META_META_ON
 import com.android.systemui.Flags.keyboardShortcutHelperShortcutCustomizer
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys.metaModifierIconResId
 import com.android.systemui.keyboard.shortcut.qualifiers.CustomShortcutCategories
 import com.android.systemui.keyboard.shortcut.qualifiers.DefaultShortcutCategories
 import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.res.R
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -34,6 +42,7 @@
 class ShortcutHelperCategoriesInteractor
 @Inject
 constructor(
+    @Application private val context: Context,
     @DefaultShortcutCategories defaultCategoriesRepository: ShortcutCategoriesRepository,
     @CustomShortcutCategories customCategoriesRepositoryLazy: Lazy<ShortcutCategoriesRepository>,
 ) {
@@ -86,7 +95,55 @@
                 Shortcut(
                     label = commonLabel,
                     icon = groupedShortcuts.firstOrNull()?.icon,
-                    commands = groupedShortcuts.flatMap { it.commands },
+                    commands = groupedShortcuts.flatMap { it.commands }.sortedBy { it.keys.size },
+                    contentDescription =
+                        toContentDescription(commonLabel, groupedShortcuts.flatMap { it.commands }),
                 )
             }
+
+    private fun toContentDescription(label: String, commands: List<ShortcutCommand>): String {
+        val pressKey = context.getString(R.string.shortcut_helper_add_shortcut_dialog_placeholder)
+        val andConjunction =
+            context.getString(R.string.shortcut_helper_key_combinations_and_conjunction)
+        val orConjunction =
+            context.getString(R.string.shortcut_helper_key_combinations_or_separator)
+        val forwardSlash =
+            context.getString(R.string.shortcut_helper_key_combinations_forward_slash)
+        return buildString {
+            append("$label, $pressKey")
+            commands.forEachIndexed { i, shortcutCommand ->
+                if (i > 0) {
+                    append(", $orConjunction")
+                }
+                shortcutCommand.keys.forEachIndexed { j, shortcutKey ->
+                    if (j > 0) {
+                        append(" $andConjunction")
+                    }
+                    if (shortcutKey is ShortcutKey.Text) {
+                        // Special handling for "/" as TalkBack will not read punctuation by
+                        // default.
+                        if (shortcutKey.value.equals("/")) {
+                            append(" $forwardSlash")
+                        } else {
+                            append(" ${shortcutKey.value}")
+                        }
+                    } else if (shortcutKey is ShortcutKey.Icon.ResIdIcon) {
+                        val keyLabel =
+                            if (shortcutKey.drawableResId == metaModifierIconResId) {
+                                ShortcutHelperKeys.modifierLabels[META_META_ON]
+                            } else {
+                                val keyCode =
+                                    ShortcutHelperKeys.keyIcons.entries
+                                        .firstOrNull { it.value == shortcutKey.drawableResId }
+                                        ?.key
+                                ShortcutHelperKeys.specialKeyLabels[keyCode]
+                            }
+                        if (keyLabel != null) {
+                            append(" ${keyLabel.invoke(context)}")
+                        }
+                    } // No-Op when shortcutKey is ShortcutKey.Icon.DrawableIcon
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
index bf7df7e..9cc15ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
@@ -20,18 +20,24 @@
     val label: String,
     val commands: List<ShortcutCommand>,
     val icon: ShortcutIcon? = null,
+    val contentDescription: String = "",
 ) {
     val containsCustomShortcutCommands: Boolean = commands.any { it.isCustom }
 }
 
 class ShortcutBuilder(private val label: String) {
     val commands = mutableListOf<ShortcutCommand>()
+    var contentDescription = ""
 
     fun command(builder: ShortcutCommandBuilder.() -> Unit) {
         commands += ShortcutCommandBuilder().apply(builder).build()
     }
 
-    fun build() = Shortcut(label, commands)
+    fun contentDescription(string: () -> String) {
+        contentDescription = string.invoke()
+    }
+
+    fun build() = Shortcut(label, commands, contentDescription = contentDescription)
 }
 
 fun shortcut(label: String, block: ShortcutBuilder.() -> Unit): Shortcut =
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
index 813a1fca..4648053 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
@@ -18,25 +18,31 @@
 
 sealed interface ShortcutCategoryType {
     val isTrusted: Boolean
+    val includeInCustomization: Boolean
 
     data object System : ShortcutCategoryType {
         override val isTrusted: Boolean = true
+        override val includeInCustomization: Boolean = true
     }
 
     data object MultiTasking : ShortcutCategoryType {
         override val isTrusted: Boolean = true
+        override val includeInCustomization: Boolean = true
     }
 
     data object InputMethodEditor : ShortcutCategoryType {
         override val isTrusted: Boolean = false
+        override val includeInCustomization: Boolean = false
     }
 
     data object AppCategories : ShortcutCategoryType {
         override val isTrusted: Boolean = true
+        override val includeInCustomization: Boolean = true
     }
 
     data class CurrentApp(val packageName: String) : ShortcutCategoryType {
         override val isTrusted: Boolean = false
+        override val includeInCustomization: Boolean = false
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
index 2d3e7f6..095de41 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
@@ -28,4 +28,6 @@
         val categoryType: ShortcutCategoryType = ShortcutCategoryType.System,
         val subCategoryLabel: String = "",
     ) : ShortcutCustomizationRequestInfo
+
+    data object Reset : ShortcutCustomizationRequestInfo
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt
index 1401678..37433ca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutSubCategory.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.keyboard.shortcut.shared.model
 
-data class ShortcutSubCategory(val label: String, val shortcuts: List<Shortcut>)
+data class ShortcutSubCategory(val label: String, val shortcuts: List<Shortcut>) {
+    val containsCustomShortcuts: Boolean = shortcuts.any { it.containsCustomShortcutCommands }
+}
 
 class ShortcutSubCategoryBuilder(val label: String) {
     private val shortcuts = mutableListOf<Shortcut>()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
index 67caadb..274fa59 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog
 import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.statusbar.phone.SystemUIDialogFactory
@@ -52,14 +53,18 @@
 
     override suspend fun onActivated(): Nothing {
         viewModel.shortcutCustomizationUiState.collect { uiState ->
-            val shouldShowAddDialog = uiState is AddShortcutDialog && !uiState.isDialogShowing
-            val shouldShowDeleteDialog = uiState is DeleteShortcutDialog && !uiState.isDialogShowing
-            if (shouldShowDeleteDialog || shouldShowAddDialog) {
-                dialog = createDialog().also { it.show() }
-                viewModel.onDialogShown()
-            } else if (uiState is ShortcutCustomizationUiState.Inactive) {
-                dialog?.dismiss()
-                dialog = null
+            when(uiState){
+                is AddShortcutDialog,
+                is DeleteShortcutDialog,
+                is ResetShortcutDialog -> {
+                    if (dialog == null){
+                        dialog = createDialog().also { it.show() }
+                    }
+                }
+                is ShortcutCustomizationUiState.Inactive -> {
+                    dialog?.dismiss()
+                    dialog = null
+                }
             }
         }
         awaitCancellation()
@@ -73,22 +78,22 @@
         return dialogFactory.create(dialogDelegate = ShortcutCustomizationDialogDelegate()) { dialog
             ->
             val uiState by
-            viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle(
-                initialValue = ShortcutCustomizationUiState.Inactive
-            )
+                viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle(
+                    initialValue = ShortcutCustomizationUiState.Inactive
+                )
             val coroutineScope = rememberCoroutineScope()
             ShortcutCustomizationDialog(
                 uiState = uiState,
-                modifier = Modifier
-                    .width(364.dp)
-                    .wrapContentHeight()
-                    .padding(vertical = 24.dp),
+                modifier = Modifier.width(364.dp).wrapContentHeight().padding(vertical = 24.dp),
                 onKeyPress = { viewModel.onKeyPressed(it) },
                 onCancel = { dialog.dismiss() },
-                onConfirmSetShortcut = {
-                    coroutineScope.launch { viewModel.onSetShortcut() }
+                onConfirmSetShortcut = { coroutineScope.launch { viewModel.onSetShortcut() } },
+                onConfirmDeleteShortcut = {
+                    coroutineScope.launch { viewModel.deleteShortcutCurrentlyBeingCustomized() }
                 },
-                onConfirmDeleteShortcut = { viewModel.onDeleteShortcut() },
+                onConfirmResetShortcut = {
+                    coroutineScope.launch { viewModel.resetAllCustomShortcuts() }
+                },
             )
             dialog.setOnDismissListener { viewModel.onDialogDismissed() }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
index 20040c6..ac6708a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
@@ -68,73 +68,133 @@
     onCancel: () -> Unit,
     onConfirmSetShortcut: () -> Unit,
     onConfirmDeleteShortcut: () -> Unit,
+    onConfirmResetShortcut: () -> Unit,
 ) {
     when (uiState) {
         is ShortcutCustomizationUiState.AddShortcutDialog -> {
-            Column(modifier = modifier) {
-                Title(uiState.shortcutLabel)
-                Description(
-                    text =
-                        stringResource(
-                            id = R.string.shortcut_customize_mode_add_shortcut_description
-                        )
-                )
-                PromptShortcutModifier(
-                    modifier =
-                        Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp)
-                            .width(131.dp)
-                            .height(48.dp),
-                    defaultModifierKey = uiState.defaultCustomShortcutModifierKey,
-                )
-                SelectedKeyCombinationContainer(
-                    shouldShowError = uiState.errorMessage.isNotEmpty(),
-                    onKeyPress = onKeyPress,
-                    pressedKeys = uiState.pressedKeys,
-                )
-                ErrorMessageContainer(uiState.errorMessage)
-                DialogButtons(
-                    onCancel,
-                    isConfirmButtonEnabled = uiState.pressedKeys.isNotEmpty(),
-                    onConfirm = onConfirmSetShortcut,
-                    confirmButtonText =
-                        stringResource(
-                            R.string.shortcut_helper_customize_dialog_set_shortcut_button_label
-                        ),
-                )
-            }
+            AddShortcutDialog(modifier, uiState, onKeyPress, onCancel, onConfirmSetShortcut)
         }
         is ShortcutCustomizationUiState.DeleteShortcutDialog -> {
-            Column(modifier) {
-                Title(
-                    title =
-                        stringResource(
-                            id = R.string.shortcut_customize_mode_remove_shortcut_dialog_title
-                        )
-                )
-                Description(
-                    text =
-                        stringResource(
-                            id = R.string.shortcut_customize_mode_remove_shortcut_description
-                        )
-                )
-                DialogButtons(
-                    onCancel = onCancel,
-                    onConfirm = onConfirmDeleteShortcut,
-                    confirmButtonText =
-                        stringResource(
-                            R.string.shortcut_helper_customize_dialog_remove_button_label
-                        ),
-                )
-            }
+            DeleteShortcutDialog(modifier, onCancel, onConfirmDeleteShortcut)
+        }
+        is ShortcutCustomizationUiState.ResetShortcutDialog -> {
+            ResetShortcutDialog(modifier, onCancel, onConfirmResetShortcut)
         }
         else -> {
-            /* No-Op */
+            /* No-op */
         }
     }
 }
 
 @Composable
-fun DialogButtons(
+private fun AddShortcutDialog(
+    modifier: Modifier,
+    uiState: ShortcutCustomizationUiState.AddShortcutDialog,
+    onKeyPress: (KeyEvent) -> Boolean,
+    onCancel: () -> Unit,
+    onConfirmSetShortcut: () -> Unit
+){
+    Column(modifier = modifier) {
+        Title(uiState.shortcutLabel)
+        Description(
+            text =
+            stringResource(
+                id = R.string.shortcut_customize_mode_add_shortcut_description
+            )
+        )
+        PromptShortcutModifier(
+            modifier =
+            Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp)
+                .width(131.dp)
+                .height(48.dp),
+            defaultModifierKey = uiState.defaultCustomShortcutModifierKey,
+        )
+        SelectedKeyCombinationContainer(
+            shouldShowError = uiState.errorMessage.isNotEmpty(),
+            onKeyPress = onKeyPress,
+            pressedKeys = uiState.pressedKeys,
+        )
+        ErrorMessageContainer(uiState.errorMessage)
+        DialogButtons(
+            onCancel,
+            isConfirmButtonEnabled = uiState.pressedKeys.isNotEmpty(),
+            onConfirm = onConfirmSetShortcut,
+            confirmButtonText =
+            stringResource(
+                R.string.shortcut_helper_customize_dialog_set_shortcut_button_label
+            ),
+        )
+    }
+}
+
+@Composable
+private fun DeleteShortcutDialog(
+    modifier: Modifier,
+    onCancel: () -> Unit,
+    onConfirmDeleteShortcut: () -> Unit
+){
+    ConfirmationDialog(
+        modifier = modifier,
+        title =
+        stringResource(
+            id = R.string.shortcut_customize_mode_remove_shortcut_dialog_title
+        ),
+        description =
+        stringResource(
+            id = R.string.shortcut_customize_mode_remove_shortcut_description
+        ),
+        confirmButtonText =
+        stringResource(R.string.shortcut_helper_customize_dialog_remove_button_label),
+        onCancel = onCancel,
+        onConfirm = onConfirmDeleteShortcut,
+    )
+}
+
+@Composable
+private fun ResetShortcutDialog(
+    modifier: Modifier,
+    onCancel: () -> Unit,
+    onConfirmResetShortcut: () -> Unit
+){
+    ConfirmationDialog(
+        modifier = modifier,
+        title =
+        stringResource(
+            id = R.string.shortcut_customize_mode_reset_shortcut_dialog_title
+        ),
+        description =
+        stringResource(
+            id = R.string.shortcut_customize_mode_reset_shortcut_description
+        ),
+        confirmButtonText =
+        stringResource(R.string.shortcut_helper_customize_dialog_reset_button_label),
+        onCancel = onCancel,
+        onConfirm = onConfirmResetShortcut,
+    )
+}
+
+@Composable
+private fun ConfirmationDialog(
+    modifier: Modifier,
+    title: String,
+    description: String,
+    confirmButtonText: String,
+    onConfirm: () -> Unit,
+    onCancel: () -> Unit,
+) {
+    Column(modifier) {
+        Title(title = title)
+        Description(text = description)
+        DialogButtons(
+            onCancel = onCancel,
+            onConfirm = onConfirm,
+            confirmButtonText = confirmButtonText,
+        )
+    }
+}
+
+@Composable
+private fun DialogButtons(
     onCancel: () -> Unit,
     isConfirmButtonEnabled: Boolean = true,
     onConfirm: () -> Unit,
@@ -168,7 +228,7 @@
 }
 
 @Composable
-fun ErrorMessageContainer(errorMessage: String) {
+private fun ErrorMessageContainer(errorMessage: String) {
     if (errorMessage.isNotEmpty()) {
         Box(modifier = Modifier.padding(horizontal = 16.dp).width(332.dp).height(40.dp)) {
             Text(
@@ -185,7 +245,7 @@
 }
 
 @Composable
-fun SelectedKeyCombinationContainer(
+private fun SelectedKeyCombinationContainer(
     shouldShowError: Boolean,
     onKeyPress: (KeyEvent) -> Boolean,
     pressedKeys: List<ShortcutKey>,
@@ -352,7 +412,7 @@
 }
 
 @Composable
-fun ActionKeyText() {
+private fun ActionKeyText() {
     Text(
         text = "Action",
         style = MaterialTheme.typography.titleMedium,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index cd13e87..2fdcf87 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -19,6 +19,7 @@
 import android.graphics.drawable.Icon
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
@@ -56,6 +57,7 @@
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.DeleteOutline
 import androidx.compose.material.icons.filled.ExpandMore
+import androidx.compose.material.icons.filled.Refresh
 import androidx.compose.material.icons.filled.Search
 import androidx.compose.material.icons.filled.Tune
 import androidx.compose.material3.CenterAlignedTopAppBar
@@ -96,6 +98,8 @@
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.hideFromAccessibility
 import androidx.compose.ui.semantics.isTraversalGroup
 import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
@@ -121,8 +125,8 @@
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
 import com.android.systemui.res.R
-import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
 import kotlinx.coroutines.delay
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
 
 @Composable
 fun ShortcutHelper(
@@ -185,6 +189,7 @@
             onKeyboardSettingsClicked,
             shortcutsUiState.isShortcutCustomizerFlagEnabled,
             onCustomizationRequested,
+            shortcutsUiState.shouldShowResetButton
         )
     }
 }
@@ -375,6 +380,7 @@
     onKeyboardSettingsClicked: () -> Unit,
     isShortcutCustomizerFlagEnabled: Boolean,
     onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
+    shouldShowResetButton: Boolean
 ) {
     val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType }
     var isCustomizing by remember { mutableStateOf(false) }
@@ -387,11 +393,14 @@
                     TitleBar(isCustomizing)
                 }
                 if (isShortcutCustomizerFlagEnabled) {
-                    if (isCustomizing) {
-                        DoneButton(onClick = { isCustomizing = false })
-                    } else {
-                        CustomizeButton(onClick = { isCustomizing = true })
-                    }
+                    CustomizationButtonsContainer(
+                        isCustomizing = isCustomizing,
+                        onToggleCustomizationMode = { isCustomizing = !isCustomizing },
+                        onReset = {
+                            onCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+                        },
+                        shouldShowResetButton = shouldShowResetButton,
+                    )
                 } else {
                     Spacer(modifier = Modifier.width(if (isCustomizing) 69.dp else 133.dp))
                 }
@@ -420,6 +429,38 @@
 }
 
 @Composable
+private fun CustomizationButtonsContainer(
+    isCustomizing: Boolean,
+    shouldShowResetButton: Boolean,
+    onToggleCustomizationMode: () -> Unit,
+    onReset: () -> Unit,
+) {
+    Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+        if (isCustomizing) {
+            if (shouldShowResetButton) {
+                ResetButton(onClick = onReset)
+            }
+            DoneButton(onClick = onToggleCustomizationMode)
+        } else {
+            CustomizeButton(onClick = onToggleCustomizationMode)
+        }
+    }
+}
+
+@Composable
+private fun ResetButton(onClick: () -> Unit) {
+    ShortcutHelperButton(
+        onClick = onClick,
+        color = Color.Transparent,
+        width = 99.dp,
+        iconSource = IconSource(imageVector = Icons.Default.Refresh),
+        text = stringResource(id = R.string.shortcut_helper_reset_button_text),
+        contentColor = MaterialTheme.colorScheme.primary,
+        border = BorderStroke(color = MaterialTheme.colorScheme.outlineVariant, width = 1.dp),
+    )
+}
+
+@Composable
 private fun CustomizeButton(onClick: () -> Unit) {
     ShortcutHelperButton(
         onClick = onClick,
@@ -461,18 +502,17 @@
             SubCategoryContainerDualPane(
                 searchQuery = searchQuery,
                 subCategory = subcategory,
-                isCustomizing = isCustomizing,
+                isCustomizing = isCustomizing and category.type.includeInCustomization,
                 onCustomizationRequested = { requestInfo ->
                     when (requestInfo) {
                         is ShortcutCustomizationRequestInfo.Add ->
-                            onCustomizationRequested(
-                                requestInfo.copy(categoryType = category.type)
-                            )
+                            onCustomizationRequested(requestInfo.copy(categoryType = category.type))
 
                         is ShortcutCustomizationRequestInfo.Delete ->
-                            onCustomizationRequested(
-                                requestInfo.copy(categoryType = category.type)
-                            )
+                            onCustomizationRequested(requestInfo.copy(categoryType = category.type))
+
+                        ShortcutCustomizationRequestInfo.Reset ->
+                            onCustomizationRequested(requestInfo)
                     }
                 },
             )
@@ -537,6 +577,9 @@
                                 onCustomizationRequested(
                                     requestInfo.copy(subCategoryLabel = subCategory.label)
                                 )
+
+                            ShortcutCustomizationRequestInfo.Reset ->
+                                onCustomizationRequested(requestInfo)
                         }
                     },
                 )
@@ -572,20 +615,31 @@
             }
             .focusable(interactionSource = interactionSource)
             .padding(8.dp)
+            .semantics { contentDescription = shortcut.contentDescription }
     ) {
         Row(
-            modifier = Modifier.width(128.dp).align(Alignment.CenterVertically).weight(0.333f),
+            modifier =
+                Modifier.width(128.dp).align(Alignment.CenterVertically).weight(0.333f).semantics {
+                    hideFromAccessibility()
+                },
             horizontalArrangement = Arrangement.spacedBy(16.dp),
             verticalAlignment = Alignment.CenterVertically,
         ) {
             if (shortcut.icon != null) {
-                ShortcutIcon(shortcut.icon, modifier = Modifier.size(24.dp))
+                ShortcutIcon(
+                    shortcut.icon,
+                    modifier = Modifier.size(24.dp).semantics { hideFromAccessibility() },
+                )
             }
-            ShortcutDescriptionText(searchQuery = searchQuery, shortcut = shortcut)
+            ShortcutDescriptionText(
+                searchQuery = searchQuery,
+                shortcut = shortcut,
+                modifier = Modifier.semantics { hideFromAccessibility() },
+            )
         }
-        Spacer(modifier = Modifier.width(24.dp))
+        Spacer(modifier = Modifier.width(24.dp).semantics { hideFromAccessibility() })
         ShortcutKeyCombinations(
-            modifier = Modifier.weight(.666f),
+            modifier = Modifier.weight(.666f).semantics { hideFromAccessibility() },
             shortcut = shortcut,
             isCustomizing = isCustomizing,
             onAddShortcutRequested = {
@@ -786,7 +840,7 @@
     Text(
         modifier = modifier,
         text = textWithHighlightedSearchQuery(shortcut.label, searchQuery),
-        style = MaterialTheme.typography.bodyMedium,
+        style = MaterialTheme.typography.titleSmall,
         color = MaterialTheme.colorScheme.onSurface,
     )
 }
@@ -904,7 +958,7 @@
                 Text(
                     fontSize = 18.sp,
                     color = colors.textColor(selected).value,
-                    style = MaterialTheme.typography.headlineSmall,
+                    style = MaterialTheme.typography.titleSmall,
                     text = label,
                 )
             }
@@ -1000,9 +1054,11 @@
     ) {
         Row(verticalAlignment = Alignment.CenterVertically) {
             Text(
-                stringResource(id = R.string.shortcut_helper_keyboard_settings_buttons_label),
+                text =
+                    stringResource(id = R.string.shortcut_helper_keyboard_settings_buttons_label),
                 color = MaterialTheme.colorScheme.onSurfaceVariant,
                 fontSize = 16.sp,
+                style = MaterialTheme.typography.titleSmall,
             )
             Spacer(modifier = Modifier.weight(1f))
             Icon(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
index 58ce194..55c0fe2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
@@ -228,74 +228,8 @@
     contentColor: Color,
     contentPaddingHorizontal: Dp = 16.dp,
     contentPaddingVertical: Dp = 10.dp,
-) {
-    ClickableShortcutSurface(
-        onClick = onClick,
-        shape = shape,
-        color = color,
-        modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
-        interactionsConfig =
-            InteractionsConfig(
-                hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
-                hoverOverlayAlpha = 0.11f,
-                pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
-                pressedOverlayAlpha = 0.15f,
-                focusOutlineColor = MaterialTheme.colorScheme.secondary,
-                focusOutlineStrokeWidth = 3.dp,
-                focusOutlinePadding = 2.dp,
-                surfaceCornerRadius = 28.dp,
-                focusOutlineCornerRadius = 33.dp,
-            ),
-    ) {
-        Row(
-            modifier =
-                Modifier.padding(
-                    horizontal = contentPaddingHorizontal,
-                    vertical = contentPaddingVertical,
-                ),
-            verticalAlignment = Alignment.CenterVertically,
-            horizontalArrangement = Arrangement.Center,
-        ) {
-            if (iconSource.imageVector != null) {
-                Icon(
-                    tint = contentColor,
-                    imageVector = iconSource.imageVector,
-                    contentDescription = null,
-                    modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
-                )
-            }
-
-            if (iconSource.imageVector != null && text != null) {
-                Spacer(modifier = Modifier.weight(1f))
-            }
-
-            if (text != null) {
-                Text(
-                    text,
-                    color = contentColor,
-                    fontSize = 14.sp,
-                    style = MaterialTheme.typography.labelLarge,
-                    modifier = Modifier.wrapContentSize(Alignment.Center),
-                )
-            }
-        }
-    }
-}
-
-@Composable
-fun ShortcutHelperButton(
-    modifier: Modifier = Modifier,
-    onClick: () -> Unit,
-    shape: Shape = RoundedCornerShape(360.dp),
-    color: Color,
-    width: Dp,
-    height: Dp = 40.dp,
-    iconSource: IconSource = IconSource(),
-    text: String? = null,
-    contentColor: Color,
-    contentPaddingHorizontal: Dp = 16.dp,
-    contentPaddingVertical: Dp = 10.dp,
     enabled: Boolean = true,
+    border: BorderStroke? = null,
 ) {
     ShortcutHelperButtonSurface(
         onClick = onClick,
@@ -305,6 +239,7 @@
         enabled = enabled,
         width = width,
         height = height,
+        border = border,
     ) {
         Row(
             modifier =
@@ -342,7 +277,7 @@
 }
 
 @Composable
-fun ShortcutHelperButtonSurface(
+private fun ShortcutHelperButtonSurface(
     onClick: () -> Unit,
     shape: Shape,
     color: Color,
@@ -350,6 +285,7 @@
     enabled: Boolean,
     width: Dp,
     height: Dp,
+    border: BorderStroke?,
     content: @Composable () -> Unit,
 ) {
     if (enabled) {
@@ -357,6 +293,7 @@
             onClick = onClick,
             shape = shape,
             color = color,
+            border = border,
             modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
             interactionsConfig =
                 InteractionsConfig(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt
index f5d478b..2728772 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCategoryUi.kt
@@ -31,4 +31,6 @@
         iconSource: IconSource,
         shortcutCategory: ShortcutCategory,
     ) : this(label, iconSource, shortcutCategory.type, shortcutCategory.subCategories)
+
+    val containsCustomShortcuts: Boolean = subCategories.any { it.containsCustomShortcuts }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
index 990257d..36c5ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
@@ -23,11 +23,12 @@
         val shortcutLabel: String,
         val errorMessage: String = "",
         val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon,
-        val isDialogShowing: Boolean,
         val pressedKeys: List<ShortcutKey> = emptyList(),
     ) : ShortcutCustomizationUiState
 
-    data class DeleteShortcutDialog(val isDialogShowing: Boolean) : ShortcutCustomizationUiState
+    data object DeleteShortcutDialog : ShortcutCustomizationUiState
+
+    data object ResetShortcutDialog : ShortcutCustomizationUiState
 
     data object Inactive : ShortcutCustomizationUiState
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
index 02b0b43..52ab157 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
@@ -25,6 +25,7 @@
         val shortcutCategories: List<ShortcutCategoryUi>,
         val defaultSelectedCategory: ShortcutCategoryType?,
         val isShortcutCustomizerFlagEnabled: Boolean = false,
+        val shouldShowResetButton: Boolean = false,
     ) : ShortcutsUiState
 
     data object Inactive : ShortcutsUiState
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
index 8696827..92e2592 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog
 import com.android.systemui.res.R
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -72,25 +73,19 @@
                         shortcutLabel = requestInfo.label,
                         defaultCustomShortcutModifierKey =
                             shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(),
-                        isDialogShowing = false,
                         pressedKeys = emptyList(),
                     )
                 shortcutCustomizationInteractor.onCustomizationRequested(requestInfo)
             }
 
             is ShortcutCustomizationRequestInfo.Delete -> {
-                _shortcutCustomizationUiState.value =
-                    DeleteShortcutDialog(isDialogShowing = false)
+                _shortcutCustomizationUiState.value = DeleteShortcutDialog
                 shortcutCustomizationInteractor.onCustomizationRequested(requestInfo)
             }
-        }
-    }
 
-    fun onDialogShown() {
-        _shortcutCustomizationUiState.update { uiState ->
-            (uiState as? AddShortcutDialog)?.copy(isDialogShowing = true)
-                ?: (uiState as? DeleteShortcutDialog)?.copy(isDialogShowing = true)
-                ?: uiState
+            ShortcutCustomizationRequestInfo.Reset -> {
+                _shortcutCustomizationUiState.value = ResetShortcutDialog
+            }
         }
     }
 
@@ -134,17 +129,33 @@
         }
     }
 
-    fun onDeleteShortcut() {
-        // TODO(b/373631984) not yet implemented
+    suspend fun deleteShortcutCurrentlyBeingCustomized() {
+        val result = shortcutCustomizationInteractor.deleteShortcutCurrentlyBeingCustomized()
+
+        _shortcutCustomizationUiState.update { uiState ->
+            when (result) {
+                ShortcutCustomizationRequestResult.SUCCESS -> ShortcutCustomizationUiState.Inactive
+                else -> uiState
+            }
+        }
+    }
+
+    suspend fun resetAllCustomShortcuts() {
+        val result = shortcutCustomizationInteractor.resetAllCustomShortcuts()
+
+        _shortcutCustomizationUiState.update { uiState ->
+            when (result) {
+                ShortcutCustomizationRequestResult.SUCCESS -> ShortcutCustomizationUiState.Inactive
+                else -> uiState
+            }
+        }
     }
 
     private fun getUiStateWithErrorMessage(
         uiState: ShortcutCustomizationUiState,
         errorMessage: String,
     ): ShortcutCustomizationUiState {
-        return (uiState as? AddShortcutDialog)?.copy(
-            errorMessage = errorMessage
-        ) ?: uiState
+        return (uiState as? AddShortcutDialog)?.copy(errorMessage = errorMessage) ?: uiState
     }
 
     private fun updatePressedKeys(keyEvent: KeyEvent) {
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
index 08fd0af8..0f5f073 100644
--- 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
@@ -17,6 +17,7 @@
 package com.android.systemui.keyboard.shortcut.ui.viewmodel
 
 import android.app.role.RoleManager
+import android.content.Context
 import android.content.pm.PackageManager.NameNotFoundException
 import android.util.Log
 import androidx.compose.material.icons.Icons
@@ -40,7 +41,6 @@
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
-import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -51,10 +51,12 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
+import javax.inject.Inject
 
 class ShortcutHelperViewModel
 @Inject
 constructor(
+    private val context: Context,
     private val roleManager: RoleManager,
     private val userTracker: UserTracker,
     @Background private val backgroundScope: CoroutineScope,
@@ -88,6 +90,7 @@
                         shortcutCategories = shortcutCategoriesUi,
                         defaultSelectedCategory = getDefaultSelectedCategory(filteredCategories),
                         isShortcutCustomizerFlagEnabled = keyboardShortcutHelperShortcutCustomizer(),
+                        shouldShowResetButton = shouldShowResetButton(shortcutCategoriesUi)
                     )
                 }
             }
@@ -97,6 +100,10 @@
                 initialValue = ShortcutsUiState.Inactive,
             )
 
+    private fun shouldShowResetButton(categoriesUi: List<ShortcutCategoryUi>): Boolean {
+        return categoriesUi.any { it.containsCustomShortcuts }
+    }
+
     private fun convertCategoriesModelToUiModel(
         categories: List<ShortcutCategory>
     ): List<ShortcutCategoryUi> {
@@ -136,13 +143,13 @@
     private fun getShortcutCategoryLabel(type: ShortcutCategoryType): String =
         when (type) {
             ShortcutCategoryType.System ->
-                userContext.getString(R.string.shortcut_helper_category_system)
+                context.getString(R.string.shortcut_helper_category_system)
             ShortcutCategoryType.MultiTasking ->
-                userContext.getString(R.string.shortcut_helper_category_multitasking)
+                context.getString(R.string.shortcut_helper_category_multitasking)
             ShortcutCategoryType.InputMethodEditor ->
-                userContext.getString(R.string.shortcut_helper_category_input)
+                context.getString(R.string.shortcut_helper_category_input)
             ShortcutCategoryType.AppCategories ->
-                userContext.getString(R.string.shortcut_helper_category_app_shortcuts)
+                context.getString(R.string.shortcut_helper_category_app_shortcuts)
             is CurrentApp -> getApplicationLabelForCurrentApp(type)
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index a94df09..7a72732 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -44,6 +45,7 @@
     ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
 
     @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
+    @Inject lateinit var shadeModeInteractor: ShadeModeInteractor
     @Inject lateinit var previewManager: KeyguardRemotePreviewManager
     @Inject @Main lateinit var mainDispatcher: CoroutineDispatcher
 
@@ -73,6 +75,11 @@
                 MATCH_CODE_ALL_SELECTIONS,
             )
             addURI(Contract.AUTHORITY, Contract.FlagsTable.TABLE_NAME, MATCH_CODE_ALL_FLAGS)
+            addURI(
+                Contract.AUTHORITY,
+                Contract.RuntimeValuesTable.TABLE_NAME,
+                MATCH_CODE_ALL_RUNTIME_VALUES,
+            )
         }
 
     override fun onCreate(): Boolean {
@@ -94,7 +101,8 @@
                 MATCH_CODE_ALL_SLOTS,
                 MATCH_CODE_ALL_AFFORDANCES,
                 MATCH_CODE_ALL_FLAGS,
-                MATCH_CODE_ALL_SELECTIONS -> "vnd.android.cursor.dir/vnd."
+                MATCH_CODE_ALL_SELECTIONS,
+                MATCH_CODE_ALL_RUNTIME_VALUES -> "vnd.android.cursor.dir/vnd."
                 else -> null
             }
 
@@ -113,6 +121,7 @@
                         Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME
                     )
                 MATCH_CODE_ALL_FLAGS -> Contract.FlagsTable.TABLE_NAME
+                MATCH_CODE_ALL_RUNTIME_VALUES -> Contract.RuntimeValuesTable.TABLE_NAME
                 else -> null
             }
 
@@ -146,6 +155,7 @@
                 MATCH_CODE_ALL_SLOTS -> querySlots()
                 MATCH_CODE_ALL_SELECTIONS -> querySelections()
                 MATCH_CODE_ALL_FLAGS -> queryFlags()
+                MATCH_CODE_ALL_RUNTIME_VALUES -> queryRuntimeValues()
                 else -> null
             }
         }
@@ -334,6 +344,23 @@
             }
     }
 
+    private fun queryRuntimeValues(): Cursor {
+        return MatrixCursor(
+                arrayOf(
+                    Contract.RuntimeValuesTable.Columns.NAME,
+                    Contract.RuntimeValuesTable.Columns.VALUE,
+                )
+            )
+            .apply {
+                addRow(
+                    arrayOf(
+                        Contract.RuntimeValuesTable.KEY_IS_SHADE_LAYOUT_WIDE,
+                        if (shadeModeInteractor.isShadeLayoutWide.value) 1 else 0,
+                    )
+                )
+            }
+    }
+
     private suspend fun deleteSelection(uri: Uri, selectionArgs: Array<out String>?): Int {
         if (selectionArgs == null) {
             throw IllegalArgumentException(
@@ -370,5 +397,6 @@
         private const val MATCH_CODE_ALL_AFFORDANCES = 2
         private const val MATCH_CODE_ALL_SELECTIONS = 3
         private const val MATCH_CODE_ALL_FLAGS = 4
+        private const val MATCH_CODE_ALL_RUNTIME_VALUES = 5
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 7097c1d..d40fe46 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -81,7 +81,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardLockWhileAwakeInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardServiceLockNowInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardStateCallbackInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardWakeDirectlyToGoneInteractor;
 import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
@@ -330,8 +330,7 @@
             return new FoldGracePeriodProvider();
         }
     };
-    private final KeyguardLockWhileAwakeInteractor
-            mKeyguardLockWhileAwakeInteractor;
+    private final KeyguardServiceLockNowInteractor mKeyguardServiceLockNowInteractor;
 
     @Inject
     public KeyguardService(
@@ -357,7 +356,7 @@
             KeyguardDismissInteractor keyguardDismissInteractor,
             Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy,
             KeyguardStateCallbackInteractor keyguardStateCallbackInteractor,
-            KeyguardLockWhileAwakeInteractor keyguardLockWhileAwakeInteractor) {
+            KeyguardServiceLockNowInteractor keyguardServiceLockNowInteractor) {
         super();
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -389,7 +388,7 @@
         mKeyguardEnabledInteractor = keyguardEnabledInteractor;
         mKeyguardWakeDirectlyToGoneInteractor = keyguardWakeDirectlyToGoneInteractor;
         mKeyguardDismissInteractor = keyguardDismissInteractor;
-        mKeyguardLockWhileAwakeInteractor = keyguardLockWhileAwakeInteractor;
+        mKeyguardServiceLockNowInteractor = keyguardServiceLockNowInteractor;
     }
 
     @Override
@@ -665,7 +664,7 @@
             if (SceneContainerFlag.isEnabled()) {
                 mDeviceEntryInteractorLazy.get().lockNow();
             } else if (KeyguardWmStateRefactor.isEnabled()) {
-                mKeyguardLockWhileAwakeInteractor.onKeyguardServiceDoKeyguardTimeout(options);
+                mKeyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(options);
             }
 
             mKeyguardViewMediator.doKeyguardTimeout(options);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 01ec4d0..9f13160 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3943,7 +3943,7 @@
     }
 
     private void notifyDefaultDisplayCallbacks(boolean showing) {
-        if (SceneContainerFlag.isEnabled()) {
+        if (SceneContainerFlag.isEnabled() || KeyguardWmStateRefactor.isEnabled()) {
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
index 0b78be1..71f29c0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.keyguard.data.quickaffordance
 
 import android.content.Context
+import android.content.Intent
+import android.provider.Settings
 import android.util.Log
 import com.android.systemui.Flags.glanceableHubShortcutButton
 import com.android.systemui.animation.Expandable
@@ -25,6 +27,7 @@
 import com.android.systemui.communal.data.repository.CommunalSceneRepository
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.res.R
@@ -85,7 +88,13 @@
         } else if (!communalInteractor.isCommunalEnabled.value) {
             Log.i(TAG, "Button disabled in picker: hub not enabled in settings.")
             KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
-                context.getString(R.string.glanceable_hub_lockscreen_affordance_disabled_text)
+                explanation =
+                    context.getString(R.string.glanceable_hub_lockscreen_affordance_disabled_text),
+                actionText =
+                    context.getString(
+                        R.string.glanceable_hub_lockscreen_affordance_action_button_label
+                    ),
+                actionIntent = Intent(Settings.ACTION_LOCKSCREEN_SETTINGS),
             )
         } else {
             KeyguardQuickAffordanceConfig.PickerScreenState.Default()
@@ -98,7 +107,10 @@
         if (SceneContainerFlag.isEnabled) {
             sceneInteractor.changeScene(Scenes.Communal, "lockscreen to communal from shortcut")
         } else {
-            communalSceneRepository.changeScene(CommunalScenes.Communal, null)
+            communalSceneRepository.changeScene(
+                CommunalScenes.Communal,
+                transitionKey = CommunalTransitionKeys.SimpleFade,
+            )
         }
         return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
     }
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 1e672e2..deef2a6 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
@@ -21,11 +21,13 @@
 import android.app.DreamManager
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.Flags.communalHubOnMobile
 import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -48,7 +50,6 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.flow.filter
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -176,6 +177,9 @@
                         communalSceneInteractor.changeScene(
                             newScene = CommunalScenes.Communal,
                             loggingReason = "FromDreamingTransitionInteractor",
+                            transitionKey =
+                                if (communalHubOnMobile()) CommunalTransitionKeys.SimpleFade
+                                else null,
                         )
                     } else {
                         startTransitionTo(
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 21090c1..cc8652c 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
@@ -18,7 +18,6 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.keyguard.logging.KeyguardLogger
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.dagger.SysUISingleton
@@ -28,15 +27,12 @@
 import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
-import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.log.core.LogLevel
-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.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -66,8 +62,6 @@
     val dismissInteractor: KeyguardDismissInteractor,
     @Application private val applicationScope: CoroutineScope,
     deviceUnlockedInteractor: Lazy<DeviceUnlockedInteractor>,
-    powerInteractor: PowerInteractor,
-    alternateBouncerInteractor: AlternateBouncerInteractor,
     shadeInteractor: Lazy<ShadeInteractor>,
     keyguardInteractor: Lazy<KeyguardInteractor>,
     sceneInteractor: Lazy<SceneInteractor>,
@@ -144,42 +138,6 @@
             }
         }
 
-    /** Flow that emits whenever we need to reset the dismiss action */
-    private val resetDismissAction: Flow<Unit> =
-        combine(
-                if (SceneContainerFlag.isEnabled) {
-                    // Using currentScene instead of isFinishedIn because of a race condition that
-                    // forms between isFinishedIn(Gone) and isOnShadeWhileUnlocked where the latter
-                    // emits false before the former emits true, causing the evaluation of the
-                    // combine to come up with true, temporarily, before settling on false, which is
-                    // a valid final state. That causes an incorrect reset of the dismiss action to
-                    // occur before it gets executed.
-                    sceneInteractor
-                        .get()
-                        .currentScene
-                        .map { it == Scenes.Gone }
-                        .distinctUntilChanged()
-                } else {
-                    transitionInteractor.isFinishedIn(
-                        scene = Scenes.Gone,
-                        stateWithoutSceneContainer = GONE,
-                    )
-                },
-                transitionInteractor.isFinishedIn(
-                    scene = Scenes.Bouncer,
-                    stateWithoutSceneContainer = PRIMARY_BOUNCER,
-                ),
-                alternateBouncerInteractor.isVisible,
-                isOnShadeWhileUnlocked,
-                powerInteractor.isAsleep,
-            ) { isOnGone, isOnBouncer, isOnAltBouncer, isOnShadeWhileUnlocked, isAsleep ->
-                (!isOnGone && !isOnBouncer && !isOnAltBouncer && !isOnShadeWhileUnlocked) ||
-                    isAsleep
-            }
-            .filter { it }
-            .sampleFilter(dismissAction) { it !is DismissAction.None }
-            .map {}
-
     fun runDismissAnimationOnKeyguard(): Boolean {
         return willAnimateDismissActionOnLockscreen.value
     }
@@ -220,19 +178,15 @@
                 }
             }
 
-            launch {
-                resetDismissAction.collect {
-                    log("resetDismissAction")
-                    repository.dismissAction.value.onCancelAction.run()
-                    clearDismissAction()
-                }
-            }
-
             launch { repository.dismissAction.collect { log("updatedDismissAction=$it") } }
             awaitCancellation()
         }
     }
 
+    fun clearDismissAction() {
+        repository.setDismissAction(DismissAction.None)
+    }
+
     /** Run the dismiss action and starts the dismiss keyguard transition. */
     private suspend fun runDismissAction() {
         val dismissAction = repository.dismissAction.value
@@ -249,10 +203,6 @@
         }
     }
 
-    private fun clearDismissAction() {
-        repository.setDismissAction(DismissAction.None)
-    }
-
     private fun log(message: String) {
         keyguardLogger.log(TAG, LogLevel.DEBUG, message)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
index 2d7da38..0a4022a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.policy.IKeyguardDismissCallback
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
@@ -39,7 +40,6 @@
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 /** Encapsulates business logic for requesting the keyguard to dismiss/finish/done. */
@@ -53,8 +53,8 @@
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
     private val selectedUserInteractor: SelectedUserInteractor,
     private val dismissCallbackRegistry: DismissCallbackRegistry,
+    private val alternateBouncerInteractor: AlternateBouncerInteractor,
     trustRepository: TrustRepository,
-    alternateBouncerInteractor: AlternateBouncerInteractor,
     powerInteractor: PowerInteractor,
 ) {
     /*
@@ -151,13 +151,16 @@
                     dismissCallbackRegistry.addCallback(callback)
                 }
 
-                // This will either show the bouncer, or dismiss the keyguard if insecure.
-                // We currently need to request showing the primary bouncer in order to start a
-                // transition to PRIMARY_BOUNCER. Once we refactor that so that starting the
-                // transition is what causes the bouncer to show, we can remove this entire method,
-                // and simply ask KeyguardTransitionInteractor to transition to a bouncer state or
-                // dismiss keyguard.
-                primaryBouncerInteractor.show(true)
+                // This will either show the bouncer, or dismiss the keyguard if insecure. We
+                // currently need to request showing the bouncer in order to start a transition to
+                // *_BOUNCER. Once we refactor that so that starting the transition is what causes
+                // the bouncer to show, we can remove this entire method, and simply call
+                // KeyguardDismissTransitionInteractor#startDismissKeyguardTransition.
+                if (alternateBouncerInteractor.canShowAlternateBouncer.value) {
+                    alternateBouncerInteractor.forceShow()
+                } else {
+                    primaryBouncerInteractor.show(true)
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 631e44a..42cbd7d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -16,39 +16,52 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.withContext
 
 /**
- * Logic around the keyguard being enabled/disabled, per [KeyguardService]. If the keyguard is not
- * enabled, the lockscreen cannot be shown and the device will go from AOD/DOZING directly to GONE.
+ * Logic around the keyguard being enabled, disabled, or suppressed via adb. If the keyguard is
+ * disabled or suppressed, the lockscreen cannot be shown and the device will go from AOD/DOZING
+ * directly to GONE.
  *
  * Keyguard can be disabled by selecting Security: "None" in settings, or by apps that hold
  * permission to do so (such as Phone). Some CTS tests also disable keyguard in onCreate or onStart
  * rather than simply dismissing the keyguard or setting up the device to have Security: None, for
  * reasons unknown.
+ *
+ * Keyguard can be suppressed by calling "adb shell locksettings set-disabled true", which is
+ * frequently done in tests. If keyguard is suppressed, it won't show even if the keyguard is
+ * enabled. If keyguard is not suppressed, then we defer to whether keyguard is enabled or disabled.
  */
 @SysUISingleton
 class KeyguardEnabledInteractor
 @Inject
 constructor(
-    @Application scope: CoroutineScope,
+    @Application val scope: CoroutineScope,
+    @Background val backgroundDispatcher: CoroutineDispatcher,
     val repository: KeyguardRepository,
     val biometricSettingsRepository: BiometricSettingsRepository,
-    keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor,
+    private val selectedUserInteractor: SelectedUserInteractor,
+    private val lockPatternUtils: LockPatternUtils,
+    keyguardDismissTransitionInteractor: dagger.Lazy<KeyguardDismissTransitionInteractor>,
     internalTransitionInteractor: InternalKeyguardTransitionInteractor,
 ) {
 
@@ -62,6 +75,10 @@
      * If the keyguard is disabled while we're locked, we will transition to GONE unless we're in
      * lockdown mode. If the keyguard is re-enabled, we'll transition back to LOCKSCREEN if we were
      * locked when it was disabled.
+     *
+     * Even if the keyguard is enabled, it's possible for it to be suppressed temporarily via adb.
+     * If you need to respect that adb command, you will need to use
+     * [isKeyguardEnabledAndNotSuppressed] instead of using this flow.
      */
     val isKeyguardEnabled: StateFlow<Boolean> = repository.isKeyguardEnabled
 
@@ -96,9 +113,9 @@
                         val currentTransitionInfo =
                             internalTransitionInteractor.currentTransitionInfoInternal()
                         if (currentTransitionInfo.to != KeyguardState.GONE && !inLockdown) {
-                            keyguardDismissTransitionInteractor.startDismissKeyguardTransition(
-                                "keyguard disabled"
-                            )
+                            keyguardDismissTransitionInteractor
+                                .get()
+                                .startDismissKeyguardTransition("keyguard disabled")
                         }
                     }
             }
@@ -116,4 +133,37 @@
     fun isShowKeyguardWhenReenabled(): Boolean {
         return repository.isShowKeyguardWhenReenabled()
     }
+
+    /**
+     * Whether the keyguard is enabled, and has not been suppressed via adb.
+     *
+     * There is unfortunately no callback for [isKeyguardSuppressed], which means this can't be a
+     * flow, since it's ambiguous when we would query the latest suppression value.
+     */
+    suspend fun isKeyguardEnabledAndNotSuppressed(): Boolean {
+        return isKeyguardEnabled.value && !isKeyguardSuppressed()
+    }
+
+    /**
+     * Returns whether the lockscreen has been disabled ("suppressed") via "adb shell locksettings
+     * set-disabled". If suppressed, we'll ignore all signals that would typically result in showing
+     * the keyguard, regardless of the value of [isKeyguardEnabled].
+     *
+     * It's extremely confusing to have [isKeyguardEnabled] not be the inverse of "is lockscreen
+     * disabled", so this method intentionally re-terms it as "suppressed".
+     *
+     * Note that if the lockscreen is currently showing when it's suppressed, it will remain visible
+     * until it's unlocked, at which point it will never re-appear until suppression is removed.
+     */
+    suspend fun isKeyguardSuppressed(
+        userId: Int = selectedUserInteractor.getSelectedUserId()
+    ): Boolean {
+        // isLockScreenDisabled returns true whenever keyguard is not enabled, even if the adb
+        // command was not used to disable/suppress the lockscreen. To make these booleans as clear
+        // as possible, only return true if keyguard is suppressed when it otherwise would have
+        // been enabled.
+        return withContext(backgroundDispatcher) {
+            isKeyguardEnabled.value && lockPatternUtils.isLockScreenDisabled(userId)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt
index 0ab3e5c..ce84e71 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt
@@ -16,27 +16,16 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import android.os.Bundle
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 
-/**
- * Emitted when we receive a [KeyguardLockWhileAwakeInteractor.onKeyguardServiceDoKeyguardTimeout]
- * call.
- *
- * Includes a timestamp so it's not conflated by the StateFlow.
- */
-data class KeyguardTimeoutWhileAwakeEvent(val timestamp: Long, val options: Bundle?)
-
 /** The reason we're locking while awake, used for logging. */
 enum class LockWhileAwakeReason(private val logReason: String) {
     LOCKDOWN("Lockdown initiated."),
@@ -71,10 +60,8 @@
 constructor(
     biometricSettingsRepository: BiometricSettingsRepository,
     keyguardEnabledInteractor: KeyguardEnabledInteractor,
+    keyguardServiceLockNowInteractor: KeyguardServiceLockNowInteractor,
 ) {
-    /** Emits whenever a timeout event is received by [KeyguardService]. */
-    private val timeoutEvents: MutableStateFlow<KeyguardTimeoutWhileAwakeEvent?> =
-        MutableStateFlow(null)
 
     /** Emits whenever the current user is in lockdown mode. */
     private val inLockdown: Flow<LockWhileAwakeReason> =
@@ -97,25 +84,19 @@
     /** Emits whenever we should lock while the screen is on, for any reason. */
     val lockWhileAwakeEvents: Flow<LockWhileAwakeReason> =
         merge(
-            inLockdown,
-            keyguardReenabled,
-            timeoutEvents.filterNotNull().map {
-                LockWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON
-            },
+            // We're in lockdown, and the keyguard is enabled. If the keyguard is disabled, the
+            // lockdown button is hidden in the UI, but it's still possible to trigger lockdown in
+            // tests.
+            inLockdown
+                .filter { keyguardEnabledInteractor.isKeyguardEnabledAndNotSuppressed() }
+                .map { LockWhileAwakeReason.LOCKDOWN },
+            // The keyguard was re-enabled, and it was showing when it was originally disabled.
+            // Tests currently expect that if the keyguard is re-enabled, it will show even if it's
+            // suppressed, so we don't check for isKeyguardEnabledAndNotSuppressed() on this flow.
+            keyguardReenabled.map { LockWhileAwakeReason.KEYGUARD_REENABLED },
+            // KeyguardService says we need to lock now, and the lockscreen is enabled.
+            keyguardServiceLockNowInteractor.lockNowEvents
+                .filter { keyguardEnabledInteractor.isKeyguardEnabledAndNotSuppressed() }
+                .map { LockWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON },
         )
-
-    /**
-     * Called by [KeyguardService] when it receives a doKeyguardTimeout() call. This indicates that
-     * the device locked while the screen was on.
-     *
-     * [options] appears to be no longer used, but we'll keep it in this interactor in case that
-     * turns out not to be true.
-     */
-    fun onKeyguardServiceDoKeyguardTimeout(options: Bundle?) {
-        timeoutEvents.value =
-            KeyguardTimeoutWhileAwakeEvent(
-                timestamp = System.currentTimeMillis(),
-                options = options,
-            )
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt
new file mode 100644
index 0000000..9ed53ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.annotation.SuppressLint
+import android.os.Bundle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.launch
+
+/**
+ * Emitted when we receive a [KeyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout]
+ * call.
+ */
+data class KeyguardLockNowEvent(val options: Bundle?)
+
+/**
+ * Logic around requests by [KeyguardService] to lock the device right now, even though the device
+ * is awake and not going to sleep.
+ *
+ * This can happen if WM#lockNow() is called, or if the screen is forced to stay awake but the lock
+ * timeout elapses.
+ *
+ * This is not the only way for the device to lock while the screen is on. The other cases, which do
+ * not directly involve [KeyguardService], are handled in [KeyguardLockWhileAwakeInteractor].
+ */
+@SysUISingleton
+class KeyguardServiceLockNowInteractor
+@Inject
+constructor(@Background val backgroundScope: CoroutineScope) {
+
+    /**
+     * Emits whenever [KeyguardService] receives a call that indicates we should lock the device
+     * right now, even though the device is awake and not going to sleep.
+     *
+     * WARNING: This is only one of multiple reasons the device might need to lock while not going
+     * to sleep. Unless you're dealing with keyguard internals that specifically need to know that
+     * we're locking due to a call to doKeyguardTimeout, use
+     * [KeyguardLockWhileAwakeInteractor.lockWhileAwakeEvents].
+     *
+     * This is fundamentally an event flow, hence the SharedFlow.
+     */
+    @SuppressLint("SharedFlowCreation")
+    val lockNowEvents: MutableSharedFlow<KeyguardLockNowEvent> = MutableSharedFlow()
+
+    /**
+     * Called by [KeyguardService] when it receives a doKeyguardTimeout() call. This indicates that
+     * the device locked while the screen was on.
+     *
+     * [options] appears to be no longer used, but we'll keep it in this interactor in case that
+     * turns out not to be true.
+     */
+    fun onKeyguardServiceDoKeyguardTimeout(options: Bundle?) {
+        backgroundScope.launch { lockNowEvents.emit(KeyguardLockNowEvent(options = options)) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
index fbc7e2a..8641dfa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
@@ -25,6 +25,7 @@
 import android.content.IntentFilter
 import android.provider.Settings
 import android.provider.Settings.Secure
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -48,20 +49,21 @@
 import kotlin.math.max
 import kotlin.math.min
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.distinctUntilChangedBy
 import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
 
 /**
  * Logic related to the ability to wake directly to GONE from asleep (AOD/DOZING), without going
  * through LOCKSCREEN or a BOUNCER state.
  *
  * This is possible in the following scenarios:
- * - The lockscreen is disabled, either from an app request (SUW does this), or by the security
+ * - The keyguard is not enabled, either from an app request (SUW does this), or by the security
  *   "None" setting.
+ * - The keyguard was suppressed via adb.
  * - A biometric authentication event occurred while we were asleep (fingerprint auth, etc). This
  *   specifically is referred to throughout the codebase as "wake and unlock".
  * - The screen timed out, but the "lock after screen timeout" duration has not elapsed.
@@ -86,43 +88,44 @@
     private val lockPatternUtils: LockPatternUtils,
     private val systemSettings: SystemSettings,
     private val selectedUserInteractor: SelectedUserInteractor,
+    keyguardEnabledInteractor: KeyguardEnabledInteractor,
+    keyguardServiceLockNowInteractor: KeyguardServiceLockNowInteractor,
 ) {
 
     /**
-     * Whether the lockscreen was disabled as of the last wake/sleep event, according to
-     * LockPatternUtils.
-     *
-     * This will always be true if [repository.isKeyguardServiceEnabled]=false, but it can also be
-     * true when the keyguard service is enabled if the lockscreen has been disabled via adb using
-     * the `adb shell locksettings set-disabled true` command, which is often done in tests.
-     *
-     * Unlike keyguardServiceEnabled, changes to this value should *not* immediately show or hide
-     * the keyguard. If the lockscreen is disabled in this way, it will just not show on the next
-     * sleep/wake.
+     * Whether the keyguard was suppressed as of the most recent wakefulness event or lockNow
+     * command. Keyguard suppression can only be queried (there is no callback available), and
+     * legacy code only queried the value in onStartedGoingToSleep and doKeyguardTimeout. Tests now
+     * depend on that behavior, so for now, we'll replicate it here.
      */
-    private val isLockscreenDisabled: Flow<Boolean> =
-        powerInteractor.isAwake.map { isLockscreenDisabled() }
+    private val shouldSuppressKeyguard =
+        merge(powerInteractor.isAwake, keyguardServiceLockNowInteractor.lockNowEvents)
+            .map { keyguardEnabledInteractor.isKeyguardSuppressed() }
+            // Default to false, so that flows that combine this one emit prior to the first
+            // wakefulness emission.
+            .onStart { emit(false) }
 
     /**
      * Whether we can wake from AOD/DOZING directly to GONE, bypassing LOCKSCREEN/BOUNCER states.
      *
      * This is possible in the following cases:
      * - Keyguard is disabled, either from an app request or from security being set to "None".
+     * - Keyguard is suppressed, via adb locksettings.
      * - We're wake and unlocking (fingerprint auth occurred while asleep).
      * - We're allowed to ignore auth and return to GONE, due to timeouts not elapsing.
      */
     val canWakeDirectlyToGone =
         combine(
                 repository.isKeyguardEnabled,
-                isLockscreenDisabled,
+                shouldSuppressKeyguard,
                 repository.biometricUnlockState,
                 repository.canIgnoreAuthAndReturnToGone,
             ) {
                 keyguardEnabled,
-                isLockscreenDisabled,
+                shouldSuppressKeyguard,
                 biometricUnlockState,
                 canIgnoreAuthAndReturnToGone ->
-                (!keyguardEnabled || isLockscreenDisabled) ||
+                (!keyguardEnabled || shouldSuppressKeyguard) ||
                     BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode) ||
                     canIgnoreAuthAndReturnToGone
             }
@@ -186,9 +189,9 @@
                 .sample(
                     transitionInteractor.isCurrentlyIn(
                         Scenes.Gone,
-                        stateWithoutSceneContainer = KeyguardState.GONE
+                        stateWithoutSceneContainer = KeyguardState.GONE,
                     ),
-                    ::Pair
+                    ::Pair,
                 )
                 .collect { (wakefulness, finishedInGone) ->
                     // Save isAwake for use in onDreamingStarted/onDreamingStopped.
@@ -260,7 +263,7 @@
             delayedActionFilter,
             SYSTEMUI_PERMISSION,
             null /* scheduler */,
-            Context.RECEIVER_EXPORTED_UNAUDITED
+            Context.RECEIVER_EXPORTED_UNAUDITED,
         )
     }
 
@@ -282,7 +285,7 @@
                 context,
                 0,
                 intent,
-                PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+                PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE,
             )
 
         val time = systemClock.elapsedRealtime() + getCanIgnoreAuthAndReturnToGoneDuration()
@@ -311,16 +314,6 @@
     }
 
     /**
-     * Returns whether the lockscreen is disabled, either because the keyguard service is disabled
-     * or because an adb command has disabled the lockscreen.
-     */
-    private fun isLockscreenDisabled(
-        userId: Int = selectedUserInteractor.getSelectedUserId()
-    ): Boolean {
-        return lockPatternUtils.isLockScreenDisabled(userId)
-    }
-
-    /**
      * Returns the duration within which we can return to GONE without auth after a screen timeout
      * (or power button press, if lock instantly is disabled).
      *
@@ -336,7 +329,7 @@
                 .getIntForUser(
                     Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
                     KEYGUARD_CAN_IGNORE_AUTH_DURATION,
-                    userId
+                    userId,
                 )
                 .toLong()
 
@@ -352,7 +345,7 @@
                     .getIntForUser(
                         Settings.System.SCREEN_OFF_TIMEOUT,
                         KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT,
-                        userId
+                        userId,
                     )
                     .toLong()
 
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 364b1a9..9df293b 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
@@ -112,8 +112,10 @@
             }
             .distinctUntilChanged()
 
-    private val isDeviceEntered by lazy { deviceEntryInteractor.get().isDeviceEntered }
-    private val isDeviceNotEntered by lazy { isDeviceEntered.map { !it } }
+    private val isDeviceEnteredDirectly by lazy {
+        deviceEntryInteractor.get().isDeviceEnteredDirectly
+    }
+    private val isDeviceNotEnteredDirectly by lazy { isDeviceEnteredDirectly.map { !it } }
 
     /**
      * Surface visibility, which is either determined by the default visibility when not
@@ -126,7 +128,7 @@
                 sceneInteractor.get().transitionState.flatMapLatestConflated { state ->
                     when {
                         state.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Gone) ->
-                            isDeviceEntered
+                            isDeviceEnteredDirectly
                         state.isTransitioning(from = Scenes.Bouncer, to = Scenes.Gone) ->
                             (state as Transition).progress.map { progress ->
                                 progress >
@@ -210,7 +212,7 @@
                             when (it.currentScene) {
                                 in keyguardScenes -> flowOf(true)
                                 in nonKeyguardScenes -> flowOf(false)
-                                in keyguardAgnosticScenes -> isDeviceNotEntered
+                                in keyguardAgnosticScenes -> isDeviceNotEnteredDirectly
                                 else ->
                                     throw IllegalStateException("Unknown scene: ${it.currentScene}")
                             }
@@ -220,7 +222,7 @@
                                 it.isTransitioningSets(from = keyguardScenes) -> flowOf(true)
                                 it.isTransitioningSets(from = nonKeyguardScenes) -> flowOf(false)
                                 it.isTransitioningSets(from = keyguardAgnosticScenes) ->
-                                    isDeviceNotEntered
+                                    isDeviceNotEnteredDirectly
                                 else ->
                                     throw IllegalStateException("Unknown scene: ${it.fromContent}")
                             }
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 6985615..3d6cf2f 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
@@ -187,10 +187,6 @@
                     launch("$TAG#fpIconView.viewModel") {
                         fgViewModel.viewModel.collect { viewModel ->
                             Log.d(TAG, "Updating device entry icon image state $viewModel")
-                            fgIconView.setImageState(
-                                view.getIconState(viewModel.type, viewModel.useAodVariant),
-                                /* merge */ false,
-                            )
                             if (viewModel.type.contentDescriptionResId != -1) {
                                 fgIconView.contentDescription =
                                     fgIconView.resources.getString(
@@ -205,6 +201,14 @@
                                 viewModel.padding,
                                 viewModel.padding,
                             )
+                            // Set image state at the end after updating other view state. This
+                            // method forces the ImageView to recompute the bounds of the drawable.
+                            fgIconView.setImageState(
+                                view.getIconState(viewModel.type, viewModel.useAodVariant),
+                                /* merge */ false,
+                            )
+                            // Invalidate, just in case the padding changes just after icon changes
+                            fgIconView.invalidate()
                         }
                     }
                 }
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 b30e1e9..8cae777 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
@@ -48,8 +48,6 @@
 
 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(
@@ -72,19 +70,33 @@
         disposables +=
             keyguardRootView.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    // When changing to new clock, we need to remove old views from burnInLayer
+                    var lastClock: ClockController? = null
                     launch {
-                        if (!MigrateClocksToBlueprint.isEnabled) return@launch
-                        viewModel.currentClock.collect { currentClock ->
-                            cleanupClockViews(currentClock, keyguardRootView, viewModel.burnInLayer)
-                            addClockViews(currentClock, keyguardRootView)
-                            updateBurnInLayer(
-                                keyguardRootView,
-                                viewModel,
-                                viewModel.clockSize.value,
-                            )
-                            applyConstraints(clockSection, keyguardRootView, true)
+                            if (!MigrateClocksToBlueprint.isEnabled) return@launch
+                            viewModel.currentClock.collect { currentClock ->
+                                if (lastClock != currentClock) {
+                                    cleanupClockViews(
+                                        lastClock,
+                                        keyguardRootView,
+                                        viewModel.burnInLayer,
+                                    )
+                                    lastClock = currentClock
+                                }
+
+                                addClockViews(currentClock, keyguardRootView)
+                                updateBurnInLayer(
+                                    keyguardRootView,
+                                    viewModel,
+                                    viewModel.clockSize.value,
+                                )
+                                applyConstraints(clockSection, keyguardRootView, true)
+                            }
                         }
-                    }
+                        .invokeOnCompletion {
+                            cleanupClockViews(lastClock, keyguardRootView, viewModel.burnInLayer)
+                            lastClock = null
+                        }
 
                     launch {
                         if (!MigrateClocksToBlueprint.isEnabled) return@launch
@@ -185,23 +197,18 @@
         viewModel.burnInLayer?.updatePostLayout(keyguardRootView)
     }
 
-    private fun cleanupClockViews(
-        currentClock: ClockController?,
+    fun cleanupClockViews(
+        lastClock: ClockController?,
         rootView: ConstraintLayout,
         burnInLayer: Layer?,
     ) {
-        if (lastClock == currentClock) {
-            return
-        }
-
-        lastClock?.let { clock ->
-            clock.smallClock.layout.views.forEach {
+        lastClock?.run {
+            smallClock.layout.views.forEach {
                 burnInLayer?.removeView(it)
                 rootView.removeView(it)
             }
-            clock.largeClock.layout.views.forEach { rootView.removeView(it) }
+            largeClock.layout.views.forEach { rootView.removeView(it) }
         }
-        lastClock = currentClock
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 6c03b24..c0b3d83 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -17,7 +17,7 @@
 
 package com.android.systemui.keyguard.ui.binder
 
-import android.content.Context
+import android.content.res.Resources
 import android.view.View
 import android.view.View.INVISIBLE
 import android.view.View.VISIBLE
@@ -34,8 +34,8 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockPreviewConfig
 import com.android.systemui.shared.clocks.ClockRegistry
-import kotlin.reflect.KSuspendFunction1
 
 /** Binder for the small clock view, large clock view. */
 object KeyguardPreviewClockViewBinder {
@@ -66,11 +66,11 @@
 
     @JvmStatic
     fun bind(
-        context: Context,
         rootView: ConstraintLayout,
         viewModel: KeyguardPreviewClockViewModel,
         clockRegistry: ClockRegistry,
-        updateClockAppearance: KSuspendFunction1<ClockController, Unit>,
+        updateClockAppearance: suspend (ClockController, Resources) -> Unit,
+        clockPreviewConfig: ClockPreviewConfig,
     ) {
         rootView.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -82,7 +82,10 @@
                                     .forEach { rootView.removeView(it) }
                             }
                             lastClock = currentClock
-                            updateClockAppearance(currentClock)
+                            updateClockAppearance(
+                                currentClock,
+                                clockPreviewConfig.previewContext.resources,
+                            )
 
                             if (viewModel.shouldHighlightSelectedAffordance) {
                                 (currentClock.largeClock.layout.views +
@@ -98,7 +101,12 @@
                                 (it.parent as? ViewGroup)?.removeView(it)
                                 rootView.addView(it)
                             }
-                            applyPreviewConstraints(context, rootView, currentClock, viewModel)
+                            applyPreviewConstraints(
+                                clockPreviewConfig,
+                                rootView,
+                                currentClock,
+                                viewModel,
+                            )
                         }
                     }
                     .invokeOnCompletion {
@@ -115,20 +123,21 @@
                                 )
                             }
                         }
+                        lastClock = null
                     }
             }
         }
     }
 
     private fun applyPreviewConstraints(
-        context: Context,
+        clockPreviewConfig: ClockPreviewConfig,
         rootView: ConstraintLayout,
         previewClock: ClockController,
         viewModel: KeyguardPreviewClockViewModel,
     ) {
         val cs = ConstraintSet().apply { clone(rootView) }
-        previewClock.largeClock.layout.applyPreviewConstraints(context, cs)
-        previewClock.smallClock.layout.applyPreviewConstraints(context, cs)
+        previewClock.largeClock.layout.applyPreviewConstraints(clockPreviewConfig, cs)
+        previewClock.smallClock.layout.applyPreviewConstraints(clockPreviewConfig, cs)
 
         // When selectedClockSize is the initial value, make both clocks invisible to avoid
         // flickering
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
index baa6812..741b149 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
@@ -17,7 +17,6 @@
 
 package com.android.systemui.keyguard.ui.binder
 
-import android.content.Context
 import android.view.View
 import androidx.core.view.isInvisible
 import androidx.lifecycle.Lifecycle
@@ -26,33 +25,27 @@
 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.clocks.ClockPreviewConfig
 
 /** Binder for the small clock view, large clock view and smartspace. */
 object KeyguardPreviewSmartspaceViewBinder {
 
     @JvmStatic
     fun bind(
-        previewContext: Context,
         smartspace: View,
-        splitShadePreview: Boolean,
         viewModel: KeyguardPreviewSmartspaceViewModel,
+        clockPreviewConfig: ClockPreviewConfig,
     ) {
         smartspace.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch("$TAG#viewModel.selectedClockSize") {
-                    viewModel.selectedClockSize.collect {
+                    viewModel.previewingClockSize.collect {
                         val topPadding =
                             when (it) {
                                 ClockSizeSetting.DYNAMIC ->
-                                    viewModel.getLargeClockSmartspaceTopPadding(
-                                        splitShadePreview,
-                                        previewContext,
-                                    )
+                                    viewModel.getLargeClockSmartspaceTopPadding(clockPreviewConfig)
                                 ClockSizeSetting.SMALL ->
-                                    viewModel.getSmallClockSmartspaceTopPadding(
-                                        splitShadePreview,
-                                        previewContext,
-                                    )
+                                    viewModel.getSmallClockSmartspaceTopPadding(clockPreviewConfig)
                             }
                         smartspace.setTopPadding(topPadding)
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index ab9cffc..7d77e71 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -22,6 +22,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.content.res.Resources
 import android.graphics.Rect
 import android.hardware.display.DisplayManager
 import android.os.Bundle
@@ -47,6 +48,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import androidx.core.view.isInvisible
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.policy.SystemBarUtils
 import com.android.keyguard.ClockEventController
 import com.android.keyguard.KeyguardClockSwitch
@@ -57,12 +59,14 @@
 import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder
 import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel
 import com.android.systemui.coroutines.newTracingContext
+import com.android.systemui.customization.R as customR
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.shared.model.ClockSizeSetting
 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -80,9 +84,11 @@
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.monet.Style
 import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockPreviewConfig
 import com.android.systemui.plugins.clocks.ThemeConfig
 import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.shared.clocks.DefaultClockController
@@ -105,7 +111,6 @@
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.flowOf
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 import org.json.JSONException
@@ -183,7 +188,8 @@
     private val shortcutsBindings = mutableSetOf<KeyguardQuickAffordanceViewBinder.Binding>()
 
     private val coroutineScope: CoroutineScope
-    private var themeStyle: Style? = null
+
+    @Style.Type private var themeStyle: Int? = null
 
     init {
         coroutineScope =
@@ -308,6 +314,10 @@
         )
     }
 
+    fun onClockSizeSelected(clockSize: ClockSizeSetting) {
+        smartspaceViewModel.setOverrideClockSize(clockSize)
+    }
+
     fun destroy() {
         isDestroyed = true
         lockscreenSmartspaceController.disconnect()
@@ -352,8 +362,11 @@
 
         val topPadding: Int =
             smartspaceViewModel.getLargeClockSmartspaceTopPadding(
-                previewInSplitShade(),
-                previewContext,
+                ClockPreviewConfig(
+                    previewContext,
+                    getPreviewShadeLayoutWide(display!!),
+                    SceneContainerFlag.isEnabled,
+                )
             )
         val startPadding: Int = smartspaceViewModel.getSmartspaceStartPadding(previewContext)
         val endPadding: Int = smartspaceViewModel.getSmartspaceEndPadding(previewContext)
@@ -436,11 +449,15 @@
             setUpClock(previewContext, rootView)
             if (MigrateClocksToBlueprint.isEnabled) {
                 KeyguardPreviewClockViewBinder.bind(
-                    previewContext,
                     keyguardRootView,
                     clockViewModel,
                     clockRegistry,
                     ::updateClockAppearance,
+                    ClockPreviewConfig(
+                        previewContext,
+                        getPreviewShadeLayoutWide(display!!),
+                        SceneContainerFlag.isEnabled,
+                    ),
                 )
             } else {
                 KeyguardPreviewClockViewBinder.bind(
@@ -455,10 +472,14 @@
 
         smartSpaceView?.let {
             KeyguardPreviewSmartspaceViewBinder.bind(
-                previewContext,
                 it,
-                previewInSplitShade(),
                 smartspaceViewModel,
+                clockPreviewConfig =
+                    ClockPreviewConfig(
+                        previewContext,
+                        getPreviewShadeLayoutWide(display!!),
+                        SceneContainerFlag.isEnabled,
+                    ),
             )
         }
         setupCommunalTutorialIndicator(keyguardRootView)
@@ -552,20 +573,14 @@
             val layoutParams =
                 FrameLayout.LayoutParams(
                     FrameLayout.LayoutParams.WRAP_CONTENT,
-                    resources.getDimensionPixelSize(
-                        com.android.systemui.customization.R.dimen.small_clock_height
-                    ),
+                    resources.getDimensionPixelSize(customR.dimen.small_clock_height),
                 )
             layoutParams.topMargin =
                 SystemBarUtils.getStatusBarHeight(previewContext) +
-                    resources.getDimensionPixelSize(
-                        com.android.systemui.customization.R.dimen.small_clock_padding_top
-                    )
+                    resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top)
             smallClockHostView.layoutParams = layoutParams
             smallClockHostView.setPaddingRelative(
-                /* start = */ resources.getDimensionPixelSize(
-                    com.android.systemui.customization.R.dimen.clock_padding_start
-                ),
+                /* start = */ resources.getDimensionPixelSize(customR.dimen.clock_padding_start),
                 /* top = */ 0,
                 /* end = */ 0,
                 /* bottom = */ 0,
@@ -637,7 +652,7 @@
         onClockChanged()
     }
 
-    private suspend fun updateClockAppearance(clock: ClockController) {
+    private suspend fun updateClockAppearance(clock: ClockController, resources: Resources) {
         if (!MigrateClocksToBlueprint.isEnabled) {
             clockController.clock = clock
         }
@@ -646,6 +661,7 @@
             // Seed color null means users do not override any color on the clock. The default
             // color will need to use wallpaper's extracted color and consider if the
             // wallpaper's color is dark or light.
+            @Style.Type
             val style = themeStyle ?: fetchThemeStyleFromSetting().also { themeStyle = it }
             val wallpaperColorScheme = ColorScheme(colors, false, style)
             val lightClockColor = wallpaperColorScheme.accent1.s100
@@ -667,6 +683,11 @@
         if (MigrateClocksToBlueprint.isEnabled) {
             clockController.clock = clock
         }
+        // When set clock to clockController,it will reset fontsize based on context.resources
+        // We need to override it with overlaid resources
+        clock.largeClock.events.onFontSettingChanged(
+            resources.getDimensionPixelSize(customR.dimen.large_clock_text_size).toFloat()
+        )
     }
 
     private fun onClockChanged() {
@@ -676,7 +697,7 @@
         coroutineScope.launch {
             val clock = clockRegistry.createCurrentClock()
             clockController.clock = clock
-            updateClockAppearance(clock)
+            updateClockAppearance(clock, context.resources)
             updateLargeClock(clock)
             updateSmallClock(clock)
         }
@@ -693,7 +714,8 @@
         }
     }
 
-    private suspend fun fetchThemeStyleFromSetting(): Style {
+    @Style.Type
+    private suspend fun fetchThemeStyleFromSetting(): Int {
         val overlayPackageJson =
             withContext(backgroundDispatcher) {
                 secureSettings.getString(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES)
@@ -742,12 +764,14 @@
         smallClockHostView.addView(clock.smallClock.view)
     }
 
-    /*
-     * When multi_crop_preview_ui_flag is on, we can preview portrait in split shadow direction
-     * or vice versa. So we need to decide preview direction by width and height
-     */
-    private fun previewInSplitShade(): Boolean {
-        return width > height
+    private fun getPreviewShadeLayoutWide(display: Display): Boolean {
+        return if (display.displayId == 0) {
+            shadeInteractor.isShadeLayoutWide.value
+        } else {
+            // For the unfolded preview in a folded screen; it's landscape by default
+            // For the folded preview in an unfolded screen; it's portrait by default
+            display.name == "Inner Display"
+        }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index f228e26f..d51708f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -25,18 +25,24 @@
 import android.util.ArrayMap
 import android.util.Log
 import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.app.tracing.coroutines.runBlockingTraced as runBlocking
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.shared.model.ClockSizeSetting
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.CLOCK_SIZE_DYNAMIC
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.CLOCK_SIZE_SMALL
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_CLOCK_SIZE
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_QUICK_AFFORDANCE_ID
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_SLOT_ID
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_DEFAULT_PREVIEW
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_PREVIEW_CLOCK_SIZE
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES
@@ -44,7 +50,6 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 @SysUISingleton
 class KeyguardRemotePreviewManager
@@ -86,17 +91,8 @@
             renderer.render()
             renderer.hostToken?.linkToDeath(observer, 0)
             val result = Bundle()
-            result.putParcelable(
-                KEY_PREVIEW_SURFACE_PACKAGE,
-                renderer.surfacePackage,
-            )
-            val messenger =
-                Messenger(
-                    Handler(
-                        backgroundHandler.looper,
-                        observer,
-                    )
-                )
+            result.putParcelable(KEY_PREVIEW_SURFACE_PACKAGE, renderer.surfacePackage)
+            val messenger = Messenger(Handler(backgroundHandler.looper, observer))
             // NOTE: The process on the other side can retain messenger indefinitely.
             // (e.g. GC might not trigger and cleanup the reference)
             val msg = Message.obtain()
@@ -191,6 +187,18 @@
             MESSAGE_ID_HIDE_SMART_SPACE -> {
                 checkNotNull(renderer).hideSmartspace(message.data.getBoolean(KEY_HIDE_SMART_SPACE))
             }
+            MESSAGE_ID_PREVIEW_CLOCK_SIZE -> {
+                message.data
+                    .getString(KEY_CLOCK_SIZE)
+                    ?.let {
+                        when (it) {
+                            CLOCK_SIZE_DYNAMIC -> ClockSizeSetting.DYNAMIC
+                            CLOCK_SIZE_SMALL -> ClockSizeSetting.SMALL
+                            else -> null
+                        }
+                    }
+                    ?.let { checkNotNull(renderer).onClockSizeSelected(it) }
+            }
             else -> checkNotNull(onDestroy).invoke(this)
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 8622ffc..160380b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -19,6 +19,7 @@
 
 import android.content.Context
 import android.view.View
+import android.view.ViewGroup
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -47,11 +48,15 @@
             visibility = View.GONE
         }
     }
+
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!MigrateClocksToBlueprint.isEnabled) {
             return
         }
-
+        if (emptyView.parent != null) {
+            // As emptyView is lazy, it might be already attached.
+            (emptyView.parent as? ViewGroup)?.removeView(emptyView)
+        }
         constraintLayout.addView(emptyView)
         burnInLayer =
             AodBurnInLayer(context).apply {
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 6c98d5b..70bf8bc 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
@@ -235,9 +235,7 @@
 
             val smallClockBottom =
                 keyguardClockViewModel.getSmallClockTopMargin() +
-                    context.resources.getDimensionPixelSize(
-                        com.android.systemui.customization.R.dimen.small_clock_height
-                    )
+                    context.resources.getDimensionPixelSize(customR.dimen.small_clock_height)
             val dateWeatherSmartspaceHeight = getDimen(context, DATE_WEATHER_VIEW_HEIGHT).toFloat()
             val marginBetweenSmartspaceAndNotification =
                 context.resources.getDimensionPixelSize(
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 f0bccac..85ce5cd 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
@@ -23,7 +23,9 @@
 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.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.transitions.TO_BOUNCER_FADE_FRACTION
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -36,9 +38,7 @@
 @SysUISingleton
 class AlternateBouncerToPrimaryBouncerTransitionViewModel
 @Inject
-constructor(
-    animationFlow: KeyguardTransitionAnimationFlow,
-) : DeviceEntryIconTransition {
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
     private val transitionAnimation =
         animationFlow
             .setup(
@@ -46,9 +46,23 @@
                 edge = Edge.create(from = ALTERNATE_BOUNCER, to = Scenes.Bouncer),
             )
             .setupWithoutSceneContainer(
-                edge = Edge.create(from = ALTERNATE_BOUNCER, to = PRIMARY_BOUNCER),
+                edge = Edge.create(from = ALTERNATE_BOUNCER, to = PRIMARY_BOUNCER)
             )
 
+    private val alphaForAnimationStep: (Float) -> Float =
+        when {
+            SceneContainerFlag.isEnabled -> { step ->
+                    1f - Math.min((step / TO_BOUNCER_FADE_FRACTION), 1f)
+                }
+            else -> { step -> 1f - step }
+        }
+
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.sharedFlow(
+            duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+            onStep = alphaForAnimationStep,
+        )
+
     override val deviceEntryParentViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 3a7a640..6e30e48 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.content.Context
 import android.content.res.Resources
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.helper.widget.Layer
@@ -27,7 +28,8 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.ClockSize
 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
-import com.android.systemui.res.R
+import com.android.systemui.plugins.clocks.ClockPreviewConfig
+import com.android.systemui.plugins.clocks.DefaultClockFaceLayout.Companion.getSmallClockTopPadding
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -46,6 +48,7 @@
 class KeyguardClockViewModel
 @Inject
 constructor(
+    val context: Context,
     keyguardClockInteractor: KeyguardClockInteractor,
     @Application private val applicationScope: CoroutineScope,
     aodNotificationIconViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
@@ -158,16 +161,15 @@
             )
 
     /** Calculates the top margin for the small clock. */
-    fun getSmallClockTopMargin(): Int {
-        val statusBarHeight = systemBarUtils.getStatusBarHeaderHeightKeyguard()
-        return if (shadeInteractor.isShadeLayoutWide.value) {
-            resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin) -
-                if (SceneContainerFlag.isEnabled) statusBarHeight else 0
-        } else {
-            resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
-                if (!SceneContainerFlag.isEnabled) statusBarHeight else 0
-        }
-    }
+    fun getSmallClockTopMargin(): Int =
+        getSmallClockTopPadding(
+            ClockPreviewConfig(
+                context,
+                shadeInteractor.isShadeLayoutWide.value,
+                SceneContainerFlag.isEnabled,
+            ),
+            systemBarUtils.getStatusBarHeaderHeightKeyguard(),
+        )
 
     val smallClockTopMargin =
         combine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
index 65c0f57..0280d17 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
@@ -17,14 +17,15 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.content.Context
-import com.android.internal.policy.SystemBarUtils
 import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
-import com.android.systemui.res.R
+import com.android.systemui.plugins.clocks.ClockPreviewConfig
+import com.android.systemui.plugins.clocks.DefaultClockFaceLayout.Companion.getSmallClockTopPadding
+import com.android.systemui.statusbar.ui.SystemBarUtilsProxy
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
 
@@ -35,22 +36,32 @@
     interactor: KeyguardClockInteractor,
     val smartspaceViewModel: KeyguardSmartspaceViewModel,
     val clockViewModel: KeyguardClockViewModel,
+    private val systemBarUtils: SystemBarUtilsProxy,
 ) {
-
-    val selectedClockSize: StateFlow<ClockSizeSetting> = interactor.selectedClockSize
+    // overrideClockSize will override the clock size that is currently set to the system.
+    private val overrideClockSize: MutableStateFlow<ClockSizeSetting?> = MutableStateFlow(null)
+    val previewingClockSize =
+        combine(overrideClockSize, interactor.selectedClockSize) {
+            overrideClockSize,
+            selectedClockSize ->
+            overrideClockSize ?: selectedClockSize
+        }
 
     val shouldHideSmartspace: Flow<Boolean> =
-        combine(interactor.selectedClockSize, interactor.currentClockId, ::Pair).map {
-            (size, currentClockId) ->
+        combine(previewingClockSize, interactor.currentClockId, ::Pair).map { (size, clockId) ->
             when (size) {
                 // TODO (b/284122375) This is temporary. We should use clockController
                 //      .largeClock.config.hasCustomWeatherDataDisplay instead, but
                 //      ClockRegistry.createCurrentClock is not reliable.
-                ClockSizeSetting.DYNAMIC -> currentClockId == "DIGITAL_CLOCK_WEATHER"
+                ClockSizeSetting.DYNAMIC -> clockId == "DIGITAL_CLOCK_WEATHER"
                 ClockSizeSetting.SMALL -> false
             }
         }
 
+    fun setOverrideClockSize(clockSize: ClockSizeSetting) {
+        overrideClockSize.value = clockSize
+    }
+
     fun getSmartspaceStartPadding(context: Context): Int {
         return KeyguardSmartspaceViewModel.getSmartspaceStartMargin(context)
     }
@@ -59,29 +70,18 @@
         return KeyguardSmartspaceViewModel.getSmartspaceEndMargin(context)
     }
 
-    fun getSmallClockSmartspaceTopPadding(splitShadePreview: Boolean, context: Context): Int {
-        return getSmallClockTopPadding(splitShadePreview, context) +
-            context.resources.getDimensionPixelSize(
-                com.android.systemui.customization.R.dimen.small_clock_height
-            )
-    }
-
-    fun getLargeClockSmartspaceTopPadding(splitShadePreview: Boolean, context: Context): Int {
-        return getSmallClockTopPadding(splitShadePreview, context)
-    }
-
     /*
      * SmallClockTopPadding decides the top position of smartspace
      */
-    private fun getSmallClockTopPadding(splitShadePreview: Boolean, context: Context): Int {
-        return with(context.resources) {
-            if (splitShadePreview) {
-                getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
-            } else {
-                getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
-                    SystemBarUtils.getStatusBarHeight(context) +
-                    getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset)
-            }
-        }
+    fun getSmallClockSmartspaceTopPadding(config: ClockPreviewConfig): Int {
+        return getSmallClockTopPadding(config, systemBarUtils.getStatusBarHeaderHeightKeyguard()) +
+            config.previewContext.resources.getDimensionPixelSize(customR.dimen.small_clock_height)
+    }
+
+    fun getLargeClockSmartspaceTopPadding(clockPreviewConfig: ClockPreviewConfig): Int {
+        return getSmallClockTopPadding(
+            clockPreviewConfig,
+            systemBarUtils.getStatusBarHeaderHeightKeyguard(),
+        )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt
index 6f29004..618b047 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -44,6 +45,7 @@
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val communalInteractor: CommunalInteractor,
     private val shadeInteractor: ShadeInteractor,
+    private val occlusionInteractor: SceneContainerOcclusionInteractor,
 ) : UserActionsViewModel() {
 
     override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
@@ -57,7 +59,8 @@
                     deviceEntryInteractor.isUnlocked,
                     communalInteractor.isCommunalAvailable,
                     shadeInteractor.shadeMode,
-                ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+                    occlusionInteractor.isOccludingActivityShown,
+                ) { isDeviceUnlocked, isCommunalAvailable, shadeMode, isOccluded ->
                     buildList {
                             if (isCommunalAvailable) {
                                 add(Swipe.Start to Scenes.Communal)
@@ -67,7 +70,8 @@
 
                             addAll(
                                 when (shadeMode) {
-                                    ShadeMode.Single -> singleShadeActions()
+                                    ShadeMode.Single ->
+                                        singleShadeActions(isDownFromTopEdgeEnabled = !isOccluded)
                                     ShadeMode.Split -> splitShadeActions()
                                     ShadeMode.Dual -> dualShadeActions()
                                 }
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
index 881228d..93ecae3 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
@@ -21,11 +21,11 @@
 import androidx.compose.runtime.snapshots.StateFactoryMarker
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.app.tracing.coroutines.traceCoroutine
+import com.android.systemui.log.table.TableLogBuffer
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Keeps snapshot/Compose [State]s up-to-date.
@@ -47,6 +47,12 @@
      * concatenation or templating.
      */
     private val traceName: String,
+    /**
+     * An optional [TableLogBuffer] to log emissions to the states. [traceName] will be used as the
+     * prefix for the columns logged by this [Hydrator], allowing to aggregate multiple hydrators in
+     * the same table.
+     */
+    private val tableLogBuffer: TableLogBuffer? = null,
 ) : ExclusiveActivatable() {
 
     private val children = mutableListOf<NamedActivatable>()
@@ -62,15 +68,8 @@
      *   automatically set on the returned [State].
      */
     @StateFactoryMarker
-    fun <T> hydratedStateOf(
-        traceName: String,
-        source: StateFlow<T>,
-    ): State<T> {
-        return hydratedStateOf(
-            traceName = traceName,
-            initialValue = source.value,
-            source = source,
-        )
+    fun <T> hydratedStateOf(traceName: String, source: StateFlow<T>): State<T> {
+        return hydratedStateOf(traceName = traceName, initialValue = source.value, source = source)
     }
 
     /**
@@ -81,26 +80,44 @@
      *   performance findings with actual code. One recommendation: prefer whole string literals
      *   instead of some complex concatenation or templating scheme. Use `null` to disable
      *   performance tracing for this state.
+     *
+     *   If a [TableLogBuffer] was provided, every emission to the flow will be logged using the
+     *   [traceName] as the column name. For this to work correctly, all the states in the same
+     *   hydrator should have different [traceName]. Use `null` to disable logging for this state.
+     *
      * @param initialValue The first value to place on the [State]
      * @param source The upstream [Flow] to collect from; values emitted to it will be automatically
      *   set on the returned [State].
      */
     @StateFactoryMarker
-    fun <T> hydratedStateOf(
-        traceName: String?,
-        initialValue: T,
-        source: Flow<T>,
-    ): State<T> {
+    fun <T> hydratedStateOf(traceName: String?, initialValue: T, source: Flow<T>): State<T> {
         check(!isActive) { "Cannot call hydratedStateOf after Hydrator is already active." }
 
         val mutableState = mutableStateOf(initialValue)
+        traceName?.let { name ->
+            tableLogBuffer?.logChange(
+                prefix = this.traceName,
+                columnName = name,
+                value = initialValue?.toString(),
+                isInitial = true,
+            )
+        }
         children.add(
             NamedActivatable(
                 traceName = traceName,
                 activatable =
                     object : ExclusiveActivatable() {
                         override suspend fun onActivated(): Nothing {
-                            source.collect { mutableState.value = it }
+                            source.collect {
+                                traceName?.let { name ->
+                                    tableLogBuffer?.logChange(
+                                        prefix = this@Hydrator.traceName,
+                                        columnName = name,
+                                        value = it?.toString(),
+                                    )
+                                }
+                                mutableState.value = it
+                            }
                             awaitCancellation()
                         }
                     },
@@ -122,8 +139,5 @@
         }
     }
 
-    private data class NamedActivatable(
-        val traceName: String?,
-        val activatable: Activatable,
-    )
+    private data class NamedActivatable(val traceName: String?, val activatable: Activatable)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt
index c21513b..14a4e26 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt
@@ -98,7 +98,11 @@
     }
 
     /** Returns [ColorScheme] of media app given its [icon]. */
-    fun getColorScheme(icon: Drawable, tag: String, style: Style = Style.TONAL_SPOT): ColorScheme? {
+    fun getColorScheme(
+        icon: Drawable,
+        tag: String,
+        @Style.Type style: Int = Style.TONAL_SPOT,
+    ): ColorScheme? {
         return try {
             ColorScheme(WallpaperColors.fromDrawable(icon), true, style)
         } catch (e: PackageManager.NameNotFoundException) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
index b69b25d..8fbbb8b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
@@ -813,8 +813,15 @@
     }
 
     private void attachConnectNewDeviceItemIfNeeded(List<MediaItem> mediaItems) {
+        boolean isSelectedDeviceNotAGroup = getSelectedMediaDevice().size() == 1;
+        if (enableInputRouting()) {
+            // When input routing is enabled, there are expected to be at least 2 total selected
+            // devices: one output device and one input device.
+            isSelectedDeviceNotAGroup = getSelectedMediaDevice().size() <= 2;
+        }
+
         // Attach "Connect a device" item only when current output is not remote and not a group
-        if (!isCurrentConnectedDeviceRemote() && getSelectedMediaDevice().size() == 1) {
+        if (!isCurrentConnectedDeviceRemote() && isSelectedDeviceNotAGroup) {
             mediaItems.add(MediaItem.createPairNewDeviceMediaItem());
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
index 35efd75..7d30948 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
@@ -20,6 +20,7 @@
 import android.hardware.display.DisplayManager
 import android.media.projection.MediaProjectionInfo
 import android.media.projection.MediaProjectionManager
+import android.media.projection.StopReason
 import android.os.Handler
 import android.view.ContentRecordingSession
 import android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY
@@ -72,7 +73,7 @@
         }
     }
 
-    override suspend fun stopProjecting() {
+    override suspend fun stopProjecting(@StopReason stopReason: Int) {
         withContext(backgroundDispatcher) {
             logger.log(
                 TAG,
@@ -80,7 +81,7 @@
                 {},
                 { "Requesting MediaProjectionManager#stopActiveProjection" },
             )
-            mediaProjectionManager.stopActiveProjection()
+            mediaProjectionManager.stopActiveProjection(stopReason)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
index 50182d7..a01d8c2 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.mediaprojection.data.repository
 
 import android.app.ActivityManager.RunningTaskInfo
+import android.media.projection.StopReason
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import kotlinx.coroutines.flow.Flow
 
@@ -27,7 +28,7 @@
     suspend fun switchProjectedTask(task: RunningTaskInfo)
 
     /** Stops the currently active projection. */
-    suspend fun stopProjecting()
+    suspend fun stopProjecting(@StopReason stopReason: Int)
 
     /** Represents the current [MediaProjectionState]. */
     val mediaProjectionState: Flow<MediaProjectionState>
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
index 32de56f..6f5d262 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
@@ -50,22 +50,14 @@
     @ScreenShareMode val defaultSelectedMode: Int = screenShareOptions.first().mode,
 ) : DialogDelegate<T>, AdapterView.OnItemSelectedListener {
     private lateinit var dialogTitle: TextView
-    private lateinit var startButton: TextView
     private lateinit var cancelButton: TextView
-    private lateinit var warning: TextView
     private lateinit var screenShareModeSpinner: Spinner
     protected lateinit var dialog: AlertDialog
-    private var shouldLogCancel: Boolean = true
-    var selectedScreenShareOption: ScreenShareOption =
-        screenShareOptions.first { it.mode == defaultSelectedMode }
+    private lateinit var viewBinder: BaseMediaProjectionPermissionViewBinder
 
     @CallSuper
     override fun onStop(dialog: T) {
-        // onStop can be called multiple times and we only want to log once.
-        if (shouldLogCancel) {
-            mediaProjectionMetricsLogger.notifyProjectionRequestCancelled(hostUid)
-            shouldLogCancel = false
-        }
+        viewBinder.unbind()
     }
 
     @CallSuper
@@ -75,12 +67,22 @@
         dialog.window?.setGravity(Gravity.CENTER)
         dialog.setContentView(R.layout.screen_share_dialog)
         dialogTitle = dialog.requireViewById(R.id.screen_share_dialog_title)
-        warning = dialog.requireViewById(R.id.text_warning)
-        startButton = dialog.requireViewById(android.R.id.button1)
         cancelButton = dialog.requireViewById(android.R.id.button2)
         updateIcon()
-        initScreenShareOptions()
         createOptionsView(getOptionsViewLayoutId())
+        if (!::viewBinder.isInitialized) {
+            viewBinder =
+                BaseMediaProjectionPermissionViewBinder(
+                    screenShareOptions,
+                    appName,
+                    hostUid,
+                    mediaProjectionMetricsLogger,
+                    defaultSelectedMode,
+                    dialog,
+                )
+        }
+        viewBinder.bind()
+        initScreenShareSpinner()
     }
 
     private fun updateIcon() {
@@ -93,18 +95,6 @@
         }
     }
 
-    private fun initScreenShareOptions() {
-        selectedScreenShareOption = screenShareOptions.first { it.mode == defaultSelectedMode }
-        setOptionSpecificFields()
-        initScreenShareSpinner()
-    }
-
-    private val warningText: String
-        get() = dialog.context.getString(selectedScreenShareOption.warningText, appName)
-
-    private val startButtonText: String
-        get() = dialog.context.getString(selectedScreenShareOption.startButtonText)
-
     private fun initScreenShareSpinner() {
         val adapter = OptionsAdapter(dialog.context.applicationContext, screenShareOptions)
         screenShareModeSpinner = dialog.requireViewById(R.id.screen_share_mode_options)
@@ -128,18 +118,15 @@
     }
 
     override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) {
-        selectedScreenShareOption = screenShareOptions[pos]
-        setOptionSpecificFields()
-    }
-
-    /** Sets fields on the dialog that change based on which option is selected. */
-    private fun setOptionSpecificFields() {
-        warning.text = warningText
-        startButton.text = startButtonText
+        viewBinder.onItemSelected(pos)
     }
 
     override fun onNothingSelected(parent: AdapterView<*>?) {}
 
+    fun getSelectedScreenShareOption(): ScreenShareOption {
+        return viewBinder.selectedScreenShareOption
+    }
+
     /** Protected methods for the text updates & functionality */
     protected fun setDialogTitle(@StringRes stringId: Int) {
         val title = dialog.context.getString(stringId, appName)
@@ -147,10 +134,7 @@
     }
 
     protected fun setStartButtonOnClickListener(listener: View.OnClickListener?) {
-        startButton.setOnClickListener { view ->
-            shouldLogCancel = false
-            listener?.onClick(view)
-        }
+        viewBinder.setStartButtonOnClickListener(listener)
     }
 
     protected fun setCancelButtonOnClickListener(listener: View.OnClickListener?) {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt
new file mode 100644
index 0000000..70b25f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.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.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.view.View
+import android.widget.TextView
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.res.R
+
+open class BaseMediaProjectionPermissionViewBinder(
+    private val screenShareOptions: List<ScreenShareOption>,
+    private val appName: String?,
+    private val hostUid: Int,
+    private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+    @ScreenShareMode val defaultSelectedMode: Int = screenShareOptions.first().mode,
+    private val dialog: AlertDialog,
+) {
+    private lateinit var warning: TextView
+    private lateinit var startButton: TextView
+    var selectedScreenShareOption: ScreenShareOption =
+        screenShareOptions.first { it.mode == defaultSelectedMode }
+    private var shouldLogCancel: Boolean = true
+
+    fun unbind() {
+        // unbind can be called multiple times and we only want to log once.
+        if (shouldLogCancel) {
+            mediaProjectionMetricsLogger.notifyProjectionRequestCancelled(hostUid)
+            shouldLogCancel = false
+        }
+    }
+
+    open fun bind() {
+        warning = dialog.requireViewById(R.id.text_warning)
+        startButton = dialog.requireViewById(android.R.id.button1)
+        initScreenShareOptions()
+    }
+
+    private fun initScreenShareOptions() {
+        selectedScreenShareOption = screenShareOptions.first { it.mode == defaultSelectedMode }
+        setOptionSpecificFields()
+    }
+
+    /** Sets fields on the dialog that change based on which option is selected. */
+    private fun setOptionSpecificFields() {
+        warning.text = warningText
+        startButton.text = startButtonText
+    }
+
+    fun onItemSelected(pos: Int) {
+        selectedScreenShareOption = screenShareOptions[pos]
+        setOptionSpecificFields()
+    }
+
+    private val warningText: String
+        get() = dialog.context.getString(selectedScreenShareOption.warningText, appName)
+
+    private val startButtonText: String
+        get() = dialog.context.getString(selectedScreenShareOption.startButtonText)
+
+    fun setStartButtonOnClickListener(listener: View.OnClickListener?) {
+        startButton.setOnClickListener { view ->
+            shouldLogCancel = false
+            listener?.onClick(view)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
index debb667..a19c9b3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.mediarouter.data.repository
 
+import android.media.projection.StopReason
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
@@ -40,7 +41,7 @@
     val castDevices: StateFlow<List<CastDevice>>
 
     /** Stops the cast to the given device. */
-    fun stopCasting(device: CastDevice)
+    fun stopCasting(device: CastDevice, @StopReason stopReason: Int)
 }
 
 @SysUISingleton
@@ -67,8 +68,8 @@
             .map { it.filter { device -> device.origin == CastDevice.CastOrigin.MediaRouter } }
             .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
 
-    override fun stopCasting(device: CastDevice) {
-        castController.stopCasting(device)
+    override fun stopCasting(device: CastDevice, @StopReason stopReason: Int) {
+        castController.stopCasting(device, stopReason)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index a3b7590..d2b1d54 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -56,7 +56,7 @@
 import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.AutoHideControllerStore;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.Utils;
@@ -124,7 +124,7 @@
             TaskbarDelegate taskbarDelegate,
             NavigationBarComponent.Factory navigationBarComponentFactory,
             DumpManager dumpManager,
-            AutoHideController autoHideController,
+            AutoHideControllerStore autoHideControllerStore,
             LightBarController lightBarController,
             TaskStackChangeListeners taskStackChangeListeners,
             Optional<Pip> pipOptional,
@@ -146,8 +146,9 @@
         mTaskbarDelegate = taskbarDelegate;
         mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
                 navBarHelper, navigationModeController, sysUiFlagsContainer,
-                dumpManager, autoHideController, lightBarController, pipOptional,
-                backAnimation.orElse(null), taskStackChangeListeners);
+                dumpManager, autoHideControllerStore.forDisplay(mContext.getDisplayId()),
+                lightBarController, pipOptional, backAnimation.orElse(null),
+                taskStackChangeListeners);
         mIsLargeScreen = isLargeScreen(mContext);
         mIsPhone = determineIfPhone(mContext, deviceStateManager);
         dumpManager.registerDumpable(this);
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 80ac2fc..b1719107 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -18,6 +18,7 @@
 import static android.content.pm.ActivityInfo.CONFIG_FONT_SCALE;
 import static android.view.InputDevice.SOURCE_MOUSE;
 import static android.view.InputDevice.SOURCE_TOUCHPAD;
+import static android.view.MotionEvent.TOOL_TYPE_FINGER;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
 
 import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground;
@@ -67,6 +68,7 @@
 import android.view.ViewConfiguration;
 import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
 import android.window.BackEvent;
 
 import androidx.annotation.DimenRes;
@@ -216,6 +218,7 @@
     private final int mDisplayId;
 
     private final UiThreadContext mUiThreadContext;
+    private final Handler mBgHandler;
     private final Executor mBackgroundExecutor;
 
     private final Rect mPipExcludedBounds = new Rect();
@@ -378,11 +381,14 @@
         @Override
         public void onInputDeviceAdded(int deviceId) {
             if (isTrackpadDevice(deviceId)) {
-                boolean wasEmpty = mTrackpadsConnected.isEmpty();
-                mTrackpadsConnected.add(deviceId);
-                if (wasEmpty) {
-                    update();
-                }
+                // This updates the gesture handler state and should be running on the main thread.
+                mUiThreadContext.getHandler().post(() -> {
+                    boolean wasEmpty = mTrackpadsConnected.isEmpty();
+                    mTrackpadsConnected.add(deviceId);
+                    if (wasEmpty) {
+                        update();
+                    }
+                });
             }
         }
 
@@ -391,10 +397,13 @@
 
         @Override
         public void onInputDeviceRemoved(int deviceId) {
-            mTrackpadsConnected.remove(deviceId);
-            if (mTrackpadsConnected.isEmpty()) {
-                update();
-            }
+            // This updates the gesture handler state and should be running on the main thread.
+            mUiThreadContext.getHandler().post(() -> {
+                mTrackpadsConnected.remove(deviceId);
+                if (mTrackpadsConnected.isEmpty()) {
+                    update();
+                }
+            });
         }
 
         private void update() {
@@ -408,12 +417,12 @@
         }
 
         private boolean isTrackpadDevice(int deviceId) {
+            // This is a blocking binder call that should run on a bg thread.
             InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
             if (inputDevice == null) {
                 return false;
             }
-            return inputDevice.getSources() == (InputDevice.SOURCE_MOUSE
-                    | InputDevice.SOURCE_TOUCHPAD);
+            return inputDevice.getSources() == (SOURCE_MOUSE | SOURCE_TOUCHPAD);
         }
     };
 
@@ -457,6 +466,7 @@
         mDisplayId = context.getDisplayId();
         mUiThreadContext = uiThreadContext;
         mBackgroundExecutor = backgroundExecutor;
+        mBgHandler = bgHandler;
         mUserTracker = userTracker;
         mOverviewProxyService = overviewProxyService;
         mSysUiState = sysUiState;
@@ -576,6 +586,7 @@
         mNonLinearFactor = getDimenFloat(res,
                 com.android.internal.R.dimen.back_progress_non_linear_factor);
         updateBackAnimationThresholds();
+        mBackgroundExecutor.execute(this::disableNavBarVirtualKeyHapticFeedback);
     }
 
     private float getDimenFloat(Resources res, @DimenRes int resId) {
@@ -611,9 +622,7 @@
         mIsAttached = true;
         mOverviewProxyService.addCallback(mQuickSwitchListener);
         mSysUiState.addCallback(mSysUiStateCallback);
-        mInputManager.registerInputDeviceListener(
-                mInputDeviceListener,
-                mUiThreadContext.getHandler());
+        mInputManager.registerInputDeviceListener(mInputDeviceListener, mBgHandler);
         int[] inputDevices = mInputManager.getInputDeviceIds();
         for (int inputDeviceId : inputDevices) {
             mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
@@ -1089,8 +1098,8 @@
                         && isValidTrackpadBackGesture(true /* isTrackpadEvent */);
             } else {
                 mAllowGesture = isBackAllowedCommon && !mUsingThreeButtonNav && isWithinInsets
-                    && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
-                    && !isButtonPressFromTrackpad(ev);
+                        && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
+                        && !isButtonPressFromTrackpad(ev);
             }
             if (mAllowGesture) {
                 mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
@@ -1202,10 +1211,8 @@
     }
 
     private boolean isButtonPressFromTrackpad(MotionEvent ev) {
-        // We don't allow back for button press from the trackpad, and yet we do with a mouse.
-        int sources = InputManager.getInstance().getInputDevice(ev.getDeviceId()).getSources();
-        int sourceTrackpad = (SOURCE_MOUSE | SOURCE_TOUCHPAD);
-        return (sources & sourceTrackpad) == sourceTrackpad && ev.getButtonState() != 0;
+        return ev.getSource() == (SOURCE_MOUSE | SOURCE_TOUCHPAD)
+                && ev.getToolType(ev.getActionIndex()) == TOOL_TYPE_FINGER;
     }
 
     private void dispatchToBackAnimation(MotionEvent event) {
@@ -1282,6 +1289,15 @@
         }
     }
 
+    private void disableNavBarVirtualKeyHapticFeedback() {
+        try {
+            WindowManagerGlobal.getWindowManagerService()
+                    .setNavBarVirtualKeyHapticFeedbackEnabled(false);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to disable navigation bar button haptics: ", e);
+        }
+    }
+
     public void dump(PrintWriter pw) {
         pw.println("EdgeBackGestureHandler:");
         pw.println("  mIsEnabled=" + mIsEnabled);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index 40613c0..c895732 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -149,9 +149,9 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.AutoHideControllerStore;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -259,10 +259,10 @@
     private boolean mTransientShownFromGestureOnSystemBar;
     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
     private LightBarController mLightBarController;
-    private final LightBarControllerStore mLightBarControllerStore;
+    private final LightBarController mMainLightBarController;
+    private final LightBarController.Factory mLightBarControllerFactory;
     private AutoHideController mAutoHideController;
-    private final AutoHideController mMainAutoHideController;
-    private final AutoHideController.Factory mAutoHideControllerFactory;
+    private final AutoHideControllerStore mAutoHideControllerStore;
     private final Optional<TelecomManager> mTelecomManagerOptional;
     private final InputMethodManager mInputMethodManager;
     private final TaskStackChangeListeners mTaskStackChangeListeners;
@@ -580,9 +580,9 @@
             @Background Executor bgExecutor,
             UiEventLogger uiEventLogger,
             NavBarHelper navBarHelper,
-            LightBarControllerStore lightBarControllerStore,
-            AutoHideController mainAutoHideController,
-            AutoHideController.Factory autoHideControllerFactory,
+            LightBarController mainLightBarController,
+            LightBarController.Factory lightBarControllerFactory,
+            AutoHideControllerStore autoHideControllerStore,
             Optional<TelecomManager> telecomManagerOptional,
             InputMethodManager inputMethodManager,
             DeadZone deadZone,
@@ -627,9 +627,9 @@
         mUiEventLogger = uiEventLogger;
         mNavBarHelper = navBarHelper;
         mNotificationShadeDepthController = notificationShadeDepthController;
-        mLightBarControllerStore = lightBarControllerStore;
-        mMainAutoHideController = mainAutoHideController;
-        mAutoHideControllerFactory = autoHideControllerFactory;
+        mMainLightBarController = mainLightBarController;
+        mLightBarControllerFactory = lightBarControllerFactory;
+        mAutoHideControllerStore = autoHideControllerStore;
         mTelecomManagerOptional = telecomManagerOptional;
         mInputMethodManager = inputMethodManager;
         mUserContextProvider = userContextProvider;
@@ -840,16 +840,11 @@
         // Unfortunately, we still need it because status bar needs LightBarController
         // before notifications creation. We cannot directly use getLightBarController()
         // from NavigationBarFragment directly.
-        LightBarController lightBarController = mLightBarControllerStore.forDisplay(mDisplayId);
+        LightBarController lightBarController = mIsOnDefaultDisplay
+                ? mMainLightBarController : mLightBarControllerFactory.create(mContext);
         setLightBarController(lightBarController);
 
-        // TODO(b/118592525): to support multi-display, we start to add something which is
-        //                    per-display, while others may be global. I think it's time to
-        //                    add a new class maybe named DisplayDependency to solve
-        //                    per-display Dependency problem.
-        // Alternative: this is a good case for a Dagger subcomponent. Same with LightBarController.
-        AutoHideController autoHideController = mIsOnDefaultDisplay
-                ? mMainAutoHideController : mAutoHideControllerFactory.create(mContext);
+        AutoHideController autoHideController = mAutoHideControllerStore.forDisplay(mDisplayId);
         setAutoHideController(autoHideController);
         restoreAppearanceAndTransientState();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index eff5fc0..d8fc52b 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -216,20 +216,13 @@
             "handleKeyGestureEvent: Received OPEN_NOTES gesture event from keycodes: " +
                 event.keycodes.contentToString()
         }
-        if (
-            event.keycodes.contains(KEYCODE_N) &&
-                event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_META_ON)
-        ) {
-            debugLog { "Note task triggered by keyboard shortcut" }
-            backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) }
-            return true
-        }
         if (event.keycodes.size == 1 && event.keycodes[0] == KEYCODE_STYLUS_BUTTON_TAIL) {
             debugLog { "Note task triggered by stylus tail button" }
             backgroundExecutor.execute { controller.showNoteTask(TAIL_BUTTON) }
             return true
         }
-        return false
+        backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) }
+        return true
     }
 
     private fun isKeyGestureSupported(gestureType: Int): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 2a5ffc6..5c9baa0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -55,8 +55,7 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.Dumpable
-import com.android.systemui.Flags;
-import com.android.systemui.res.R
+import com.android.systemui.Flags
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
@@ -65,7 +64,9 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.DeviceConfigProxy
@@ -106,8 +107,8 @@
     fun init()
 
     /**
-     * Show the foreground services dialog. The dialog will be expanded from [expandable] if
-     * it's not `null`.
+     * Show the foreground services dialog. The dialog will be expanded from [expandable] if it's
+     * not `null`.
      */
     fun showDialog(expandable: Expandable?)
 
@@ -123,8 +124,7 @@
     /** Remove a [OnDialogDismissedListener]. */
     fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener)
 
-    @VisibleForTesting
-    fun visibleButtonsCount(): Int
+    @VisibleForTesting fun visibleButtonsCount(): Int
 
     interface OnNumberOfPackagesChangedListener {
         /** Called when [numRunningPackages] changed. */
@@ -138,8 +138,10 @@
 }
 
 @SysUISingleton
-class FgsManagerControllerImpl @Inject constructor(
-    @Main private val resources: Resources,
+class FgsManagerControllerImpl
+@Inject
+constructor(
+    @ShadeDisplayAware private val resources: Resources,
     @Main private val mainExecutor: Executor,
     @Background private val backgroundExecutor: Executor,
     private val systemClock: SystemClock,
@@ -187,50 +189,46 @@
 
     private val lock = Any()
 
-    @GuardedBy("lock")
-    var initialized = false
+    @GuardedBy("lock") var initialized = false
 
-    @GuardedBy("lock")
-    private var lastNumberOfVisiblePackages = 0
+    @GuardedBy("lock") private var lastNumberOfVisiblePackages = 0
 
-    @GuardedBy("lock")
-    private var currentProfileIds = mutableSetOf<Int>()
+    @GuardedBy("lock") private var currentProfileIds = mutableSetOf<Int>()
 
     @GuardedBy("lock")
     private val runningTaskIdentifiers = mutableMapOf<UserPackage, StartTimeAndIdentifiers>()
 
-    @GuardedBy("lock")
-    private var dialog: SystemUIDialog? = null
+    @GuardedBy("lock") private var dialog: SystemUIDialog? = null
 
-    @GuardedBy("lock")
-    private val appListAdapter: AppListAdapter = AppListAdapter()
+    @GuardedBy("lock") private val appListAdapter: AppListAdapter = AppListAdapter()
 
     /* Only mutate on the background thread */
     private var runningApps: ArrayMap<UserPackage, RunningApp> = ArrayMap()
 
-    private val userTrackerCallback = object : UserTracker.Callback {
-        override fun onUserChanged(newUser: Int, userContext: Context) {}
+    private val userTrackerCallback =
+        object : UserTracker.Callback {
+            override fun onUserChanged(newUser: Int, userContext: Context) {}
 
-        override fun onProfilesChanged(profiles: List<UserInfo>) {
-            synchronized(lock) {
-                currentProfileIds.clear()
-                currentProfileIds.addAll(profiles.map { it.id })
-                lastNumberOfVisiblePackages = 0
-                updateNumberOfVisibleRunningPackagesLocked()
+            override fun onProfilesChanged(profiles: List<UserInfo>) {
+                synchronized(lock) {
+                    currentProfileIds.clear()
+                    currentProfileIds.addAll(profiles.map { it.id })
+                    lastNumberOfVisiblePackages = 0
+                    updateNumberOfVisibleRunningPackagesLocked()
+                }
             }
         }
-    }
 
     private val foregroundServiceObserver = ForegroundServiceObserver()
 
     private val userVisibleJobObserver = UserVisibleJobObserver()
 
-    private val stoppableApps by lazy { resources
-        .getStringArray(com.android.internal.R.array.stoppable_fgs_system_apps)
+    private val stoppableApps by lazy {
+        resources.getStringArray(com.android.internal.R.array.stoppable_fgs_system_apps)
     }
 
-    private val vendorStoppableApps by lazy { resources
-        .getStringArray(com.android.internal.R.array.vendor_stoppable_fgs_system_apps)
+    private val vendorStoppableApps by lazy {
+        resources.getStringArray(com.android.internal.R.array.vendor_stoppable_fgs_system_apps)
     }
 
     override fun init() {
@@ -239,14 +237,19 @@
                 return
             }
 
-            showUserVisibleJobs = deviceConfigProxy.getBoolean(
-                NAMESPACE_SYSTEMUI,
-                TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS)
+            showUserVisibleJobs =
+                deviceConfigProxy.getBoolean(
+                    NAMESPACE_SYSTEMUI,
+                    TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
+                    DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
+                )
 
-            informJobSchedulerOfPendingAppStop = deviceConfigProxy.getBoolean(
-                NAMESPACE_SYSTEMUI,
-                TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP,
-                DEFAULT_TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP)
+            informJobSchedulerOfPendingAppStop =
+                deviceConfigProxy.getBoolean(
+                    NAMESPACE_SYSTEMUI,
+                    TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP,
+                    DEFAULT_TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP,
+                )
 
             try {
                 activityManager.registerForegroundServiceObserver(foregroundServiceObserver)
@@ -267,31 +270,39 @@
 
             deviceConfigProxy.addOnPropertiesChangedListener(
                 NAMESPACE_SYSTEMUI,
-                backgroundExecutor
+                backgroundExecutor,
             ) {
                 _showFooterDot.value =
                     it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, _showFooterDot.value)
-                showStopBtnForUserAllowlistedApps = it.getBoolean(
-                    TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
-                    showStopBtnForUserAllowlistedApps)
+                showStopBtnForUserAllowlistedApps =
+                    it.getBoolean(
+                        TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+                        showStopBtnForUserAllowlistedApps,
+                    )
                 var wasShowingUserVisibleJobs = showUserVisibleJobs
-                showUserVisibleJobs = it.getBoolean(
-                    TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, showUserVisibleJobs)
+                showUserVisibleJobs =
+                    it.getBoolean(TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, showUserVisibleJobs)
                 if (showUserVisibleJobs != wasShowingUserVisibleJobs) {
                     onShowUserVisibleJobsFlagChanged()
                 }
-                informJobSchedulerOfPendingAppStop = it.getBoolean(
-                    TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
-                    informJobSchedulerOfPendingAppStop)
+                informJobSchedulerOfPendingAppStop =
+                    it.getBoolean(
+                        TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+                        informJobSchedulerOfPendingAppStop,
+                    )
             }
-            _showFooterDot.value = deviceConfigProxy.getBoolean(
-                NAMESPACE_SYSTEMUI,
-                TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT
-            )
-            showStopBtnForUserAllowlistedApps = deviceConfigProxy.getBoolean(
-                NAMESPACE_SYSTEMUI,
-                TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
-                DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS)
+            _showFooterDot.value =
+                deviceConfigProxy.getBoolean(
+                    NAMESPACE_SYSTEMUI,
+                    TASK_MANAGER_SHOW_FOOTER_DOT,
+                    DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT,
+                )
+            showStopBtnForUserAllowlistedApps =
+                deviceConfigProxy.getBoolean(
+                    NAMESPACE_SYSTEMUI,
+                    TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+                    DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+                )
 
             dumpManager.registerDumpable(this)
 
@@ -305,7 +316,7 @@
                 },
                 IntentFilter(Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER),
                 executor = mainExecutor,
-                flags = Context.RECEIVER_NOT_EXPORTED
+                flags = Context.RECEIVER_NOT_EXPORTED,
             )
 
             initialized = true
@@ -323,33 +334,25 @@
     override fun addOnNumberOfPackagesChangedListener(
         listener: FgsManagerController.OnNumberOfPackagesChangedListener
     ) {
-        synchronized(lock) {
-            onNumberOfPackagesChangedListeners.add(listener)
-        }
+        synchronized(lock) { onNumberOfPackagesChangedListeners.add(listener) }
     }
 
     override fun removeOnNumberOfPackagesChangedListener(
         listener: FgsManagerController.OnNumberOfPackagesChangedListener
     ) {
-        synchronized(lock) {
-            onNumberOfPackagesChangedListeners.remove(listener)
-        }
+        synchronized(lock) { onNumberOfPackagesChangedListeners.remove(listener) }
     }
 
     override fun addOnDialogDismissedListener(
         listener: FgsManagerController.OnDialogDismissedListener
     ) {
-        synchronized(lock) {
-            onDialogDismissedListeners.add(listener)
-        }
+        synchronized(lock) { onDialogDismissedListeners.add(listener) }
     }
 
     override fun removeOnDialogDismissedListener(
         listener: FgsManagerController.OnDialogDismissedListener
     ) {
-        synchronized(lock) {
-            onDialogDismissedListeners.remove(listener)
-        }
+        synchronized(lock) { onDialogDismissedListeners.remove(listener) }
     }
 
     private fun getNumVisiblePackagesLocked(): Int {
@@ -364,9 +367,7 @@
             lastNumberOfVisiblePackages = num
             newChangesSinceDialogWasDismissed = true
             onNumberOfPackagesChangedListeners.forEach {
-                backgroundExecutor.execute {
-                    it.onNumberOfPackagesChanged(num)
-                }
+                backgroundExecutor.execute { it.onNumberOfPackagesChanged(num) }
             }
         }
     }
@@ -396,8 +397,10 @@
                 recyclerView.layoutManager = LinearLayoutManager(dialogContext)
                 recyclerView.adapter = appListAdapter
 
-                val topSpacing = dialogContext.resources
-                    .getDimensionPixelSize(R.dimen.fgs_manager_list_top_spacing)
+                val topSpacing =
+                    dialogContext.resources.getDimensionPixelSize(
+                        R.dimen.fgs_manager_list_top_spacing
+                    )
                 dialog.setView(recyclerView, 0, topSpacing, 0, 0)
 
                 this.dialog = dialog
@@ -436,9 +439,7 @@
     @GuardedBy("lock")
     private fun updateAppItemsLocked(refreshUiControls: Boolean = false) {
         if (dialog == null) {
-            backgroundExecutor.execute {
-                clearRunningApps()
-            }
+            backgroundExecutor.execute { clearRunningApps() }
             return
         }
 
@@ -449,59 +450,61 @@
         }
     }
 
-    /**
-     * Must be called on the background thread.
-     */
+    /** Must be called on the background thread. */
     @WorkerThread
     private fun updateAppItems(
         packages: Map<UserPackage, Long>,
         profileIds: Set<Int>,
-        refreshUiControls: Boolean = true
+        refreshUiControls: Boolean = true,
     ) {
         if (refreshUiControls) {
-            packages.forEach { (pkg, _) ->
-                pkg.updateUiControl()
-            }
+            packages.forEach { (pkg, _) -> pkg.updateUiControl() }
         }
 
-        val addedPackages = packages.keys.filter {
-            profileIds.contains(it.userId) &&
-                    it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true
-        }
+        val addedPackages =
+            packages.keys.filter {
+                profileIds.contains(it.userId) &&
+                    it.uiControl != UIControl.HIDE_ENTRY &&
+                    runningApps[it]?.stopped != true
+            }
         val removedPackages = runningApps.keys.filter { it !in packages }
 
         addedPackages.forEach {
             val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId)
-            runningApps[it] = RunningApp(
-                it.userId, it.packageName,
-                packages[it]!!, it.uiControl,
-                packageManager.getApplicationLabel(ai),
-                packageManager.getUserBadgedIcon(
-                    packageManager.getApplicationIcon(ai), UserHandle.of(it.userId)
+            runningApps[it] =
+                RunningApp(
+                    it.userId,
+                    it.packageName,
+                    packages[it]!!,
+                    it.uiControl,
+                    packageManager.getApplicationLabel(ai),
+                    packageManager.getUserBadgedIcon(
+                        packageManager.getApplicationIcon(ai),
+                        UserHandle.of(it.userId),
+                    ),
                 )
-            )
             logEvent(stopped = false, it.packageName, it.userId, runningApps[it]!!.timeStarted)
         }
 
         removedPackages.forEach { pkg ->
             val ra = runningApps[pkg]!!
-            val ra2 = ra.copy().also {
-                it.stopped = true
-                it.appLabel = ra.appLabel
-                it.icon = ra.icon
-            }
+            val ra2 =
+                ra.copy().also {
+                    it.stopped = true
+                    it.appLabel = ra.appLabel
+                    it.icon = ra.icon
+                }
             runningApps[pkg] = ra2
         }
 
         mainExecutor.execute {
-            appListAdapter
-                .setData(runningApps.values.toList().sortedByDescending { it.timeStarted })
+            appListAdapter.setData(
+                runningApps.values.toList().sortedByDescending { it.timeStarted }
+            )
         }
     }
 
-    /**
-     * Must be called on the background thread.
-     */
+    /** Must be called on the background thread. */
     @WorkerThread
     private fun clearRunningApps() {
         runningApps.clear()
@@ -545,16 +548,19 @@
 
     private fun logEvent(stopped: Boolean, packageName: String, userId: Int, timeStarted: Long) {
         val timeLogged = systemClock.elapsedRealtime()
-        val event = if (stopped) {
-            SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED__EVENT__STOPPED
-        } else {
-            SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED__EVENT__VIEWED
-        }
+        val event =
+            if (stopped) {
+                SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED__EVENT__STOPPED
+            } else {
+                SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED__EVENT__VIEWED
+            }
         backgroundExecutor.execute {
             val uid = packageManager.getPackageUidAsUser(packageName, userId)
             SysUiStatsLog.write(
-                SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED, uid, event,
-                timeLogged - timeStarted
+                SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED,
+                uid,
+                event,
+                timeLogged - timeStarted,
             )
         }
     }
@@ -562,8 +568,7 @@
     private inner class AppListAdapter : RecyclerView.Adapter<AppItemViewHolder>() {
         private val lock = Any()
 
-        @GuardedBy("lock")
-        private var data: List<RunningApp> = listOf()
+        @GuardedBy("lock") private var data: List<RunningApp> = listOf()
 
         override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppItemViewHolder {
             return AppItemViewHolder(
@@ -574,16 +579,15 @@
 
         override fun onBindViewHolder(holder: AppItemViewHolder, position: Int) {
             var runningApp: RunningApp
-            synchronized(lock) {
-                runningApp = data[position]
-            }
+            synchronized(lock) { runningApp = data[position] }
             with(holder) {
                 iconView.setImageDrawable(runningApp.icon)
                 appLabelView.text = runningApp.appLabel
-                durationView.text = DateUtils.formatDuration(
-                    max(systemClock.elapsedRealtime() - runningApp.timeStarted, 60000),
-                    DateUtils.LENGTH_MEDIUM
-                )
+                durationView.text =
+                    DateUtils.formatDuration(
+                        max(systemClock.elapsedRealtime() - runningApp.timeStarted, 60000),
+                        DateUtils.LENGTH_MEDIUM,
+                    )
                 stopButton.setOnClickListener {
                     stopButton.setText(R.string.fgs_manager_app_item_stop_button_stopped_label)
                     stopPackage(runningApp.userId, runningApp.packageName, runningApp.timeStarted)
@@ -611,46 +615,54 @@
             var oldData = data
             data = newData
 
-            DiffUtil.calculateDiff(object : DiffUtil.Callback() {
-                override fun getOldListSize(): Int {
-                    return oldData.size
-                }
+            DiffUtil.calculateDiff(
+                    object : DiffUtil.Callback() {
+                        override fun getOldListSize(): Int {
+                            return oldData.size
+                        }
 
-                override fun getNewListSize(): Int {
-                    return newData.size
-                }
+                        override fun getNewListSize(): Int {
+                            return newData.size
+                        }
 
-                override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
-                    return oldData[oldItemPosition] == newData[newItemPosition]
-                }
+                        override fun areItemsTheSame(
+                            oldItemPosition: Int,
+                            newItemPosition: Int,
+                        ): Boolean {
+                            return oldData[oldItemPosition] == newData[newItemPosition]
+                        }
 
-                override fun areContentsTheSame(
-                    oldItemPosition: Int,
-                    newItemPosition: Int
-                ): Boolean {
-                    return oldData[oldItemPosition].stopped == newData[newItemPosition].stopped
-                }
-            }).dispatchUpdatesTo(this)
+                        override fun areContentsTheSame(
+                            oldItemPosition: Int,
+                            newItemPosition: Int,
+                        ): Boolean {
+                            return oldData[oldItemPosition].stopped ==
+                                newData[newItemPosition].stopped
+                        }
+                    }
+                )
+                .dispatchUpdatesTo(this)
         }
     }
 
     private inner class ForegroundServiceObserver : IForegroundServiceObserver.Stub() {
         override fun onForegroundStateChanged(
-                token: IBinder,
-                packageName: String,
-                userId: Int,
-                isForeground: Boolean
+            token: IBinder,
+            packageName: String,
+            userId: Int,
+            isForeground: Boolean,
         ) {
             synchronized(lock) {
                 val userPackageKey = UserPackage(userId, packageName)
                 if (isForeground) {
                     runningTaskIdentifiers
-                            .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) }
-                            .addFgsToken(token)
+                        .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) }
+                        .addFgsToken(token)
                 } else {
-                    if (runningTaskIdentifiers[userPackageKey]?.also {
-                                it.removeFgsToken(token)
-                            }?.isEmpty() == true
+                    if (
+                        runningTaskIdentifiers[userPackageKey]
+                            ?.also { it.removeFgsToken(token) }
+                            ?.isEmpty() == true
                     ) {
                         runningTaskIdentifiers.remove(userPackageKey)
                     }
@@ -665,20 +677,24 @@
 
     private inner class UserVisibleJobObserver : IUserVisibleJobObserver.Stub() {
         override fun onUserVisibleJobStateChanged(
-                summary: UserVisibleJobSummary,
-                isRunning: Boolean
+            summary: UserVisibleJobSummary,
+            isRunning: Boolean,
         ) {
             synchronized(lock) {
-                val userPackageKey = UserPackage(
-                        UserHandle.getUserId(summary.callingUid), summary.callingPackageName)
+                val userPackageKey =
+                    UserPackage(
+                        UserHandle.getUserId(summary.callingUid),
+                        summary.callingPackageName,
+                    )
                 if (isRunning) {
                     runningTaskIdentifiers
-                            .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) }
-                            .addJobSummary(summary)
+                        .getOrPut(userPackageKey) { StartTimeAndIdentifiers(systemClock) }
+                        .addJobSummary(summary)
                 } else {
-                    if (runningTaskIdentifiers[userPackageKey]?.also {
-                                it.removeJobSummary(summary)
-                            }?.isEmpty() == true
+                    if (
+                        runningTaskIdentifiers[userPackageKey]
+                            ?.also { it.removeJobSummary(summary) }
+                            ?.isEmpty() == true
                     ) {
                         runningTaskIdentifiers.remove(userPackageKey)
                     }
@@ -691,10 +707,7 @@
         }
     }
 
-    private inner class UserPackage(
-        val userId: Int,
-        val packageName: String
-    ) {
+    private inner class UserPackage(val userId: Int, val packageName: String) {
         val uid by lazy { packageManager.getPackageUidAsUser(packageName, userId) }
         var backgroundRestrictionExemptionReason = PowerExemptionManager.REASON_DENIED
 
@@ -711,30 +724,31 @@
         fun updateUiControl() {
             backgroundRestrictionExemptionReason =
                 activityManager.getBackgroundRestrictionExemptionReason(uid)
-            uiControl = when (backgroundRestrictionExemptionReason) {
-                PowerExemptionManager.REASON_SYSTEM_UID,
-                PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY
+            uiControl =
+                when (backgroundRestrictionExemptionReason) {
+                    PowerExemptionManager.REASON_SYSTEM_UID,
+                    PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY
 
-                PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED,
-                PowerExemptionManager.REASON_DEVICE_OWNER,
-                PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL,
-                PowerExemptionManager.REASON_DPO_PROTECTED_APP,
-                PowerExemptionManager.REASON_PROFILE_OWNER,
-                PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN,
-                PowerExemptionManager.REASON_PROC_STATE_PERSISTENT,
-                PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI,
-                PowerExemptionManager.REASON_ROLE_DIALER,
-                PowerExemptionManager.REASON_SYSTEM_MODULE,
-                PowerExemptionManager.REASON_SYSTEM_EXEMPT_APP_OP -> UIControl.HIDE_BUTTON
+                    PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED,
+                    PowerExemptionManager.REASON_DEVICE_OWNER,
+                    PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL,
+                    PowerExemptionManager.REASON_DPO_PROTECTED_APP,
+                    PowerExemptionManager.REASON_PROFILE_OWNER,
+                    PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN,
+                    PowerExemptionManager.REASON_PROC_STATE_PERSISTENT,
+                    PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI,
+                    PowerExemptionManager.REASON_ROLE_DIALER,
+                    PowerExemptionManager.REASON_SYSTEM_MODULE,
+                    PowerExemptionManager.REASON_SYSTEM_EXEMPT_APP_OP -> UIControl.HIDE_BUTTON
 
-                PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE ->
-                    if (showStopBtnForUserAllowlistedApps) {
-                        UIControl.NORMAL
-                    } else {
-                        UIControl.HIDE_BUTTON
-                    }
-                else -> UIControl.NORMAL
-            }
+                    PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE ->
+                        if (showStopBtnForUserAllowlistedApps) {
+                            UIControl.NORMAL
+                        } else {
+                            UIControl.HIDE_BUTTON
+                        }
+                    else -> UIControl.NORMAL
+                }
             // If the app wants to be a good citizen by being stoppable, even if the category it
             // belongs to is exempted for background restriction, let it be stoppable by user.
             if (Flags.stoppableFgsSystemApp()) {
@@ -747,8 +761,7 @@
         }
 
         fun isStoppableApp(packageName: String): Boolean {
-            return stoppableApps.contains(packageName) ||
-                vendorStoppableApps.contains(packageName)
+            return stoppableApps.contains(packageName) || vendorStoppableApps.contains(packageName)
         }
 
         override fun equals(other: Any?): Boolean {
@@ -771,9 +784,7 @@
         }
     }
 
-    private data class StartTimeAndIdentifiers(
-        val systemClock: SystemClock
-    ) {
+    private data class StartTimeAndIdentifiers(val systemClock: SystemClock) {
         val startTime = systemClock.elapsedRealtime()
         val fgsTokens = mutableSetOf<IBinder>()
         val jobSummaries = mutableSetOf<UserVisibleJobSummary>()
@@ -846,7 +857,7 @@
         val userId: Int,
         val packageName: String,
         val timeStarted: Long,
-        val uiControl: UIControl
+        val uiControl: UIControl,
     ) {
         constructor(
             userId: Int,
@@ -854,7 +865,7 @@
             timeStarted: Long,
             uiControl: UIControl,
             appLabel: CharSequence,
-            icon: Drawable
+            icon: Drawable,
         ) : this(userId, packageName, timeStarted, uiControl) {
             this.appLabel = appLabel
             this.icon = icon
@@ -884,7 +895,9 @@
     }
 
     private enum class UIControl {
-        NORMAL, HIDE_BUTTON, HIDE_ENTRY
+        NORMAL,
+        HIDE_BUTTON,
+        HIDE_ENTRY,
     }
 
     override fun dump(printwriter: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 7ccdf0a..946eccd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -25,6 +25,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.ViewController;
 
@@ -65,7 +66,7 @@
             QSContainerImpl view,
             QSPanelController qsPanelController,
             QuickStatusBarHeaderController quickStatusBarHeaderController,
-            ConfigurationController configurationController,
+            @ShadeDisplayAware ConfigurationController configurationController,
             FalsingManager falsingManager) {
         super(view);
         mQsPanelController = qsPanelController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index d715f42..dc188c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -16,8 +16,11 @@
 
 package com.android.systemui.qs;
 
+import static com.android.systemui.Flags.gsfQuickSettings;
+
 import android.content.ClipData;
 import android.content.ClipboardManager;
+import android.graphics.Typeface;
 import android.text.TextUtils;
 import android.view.View;
 import android.widget.TextView;
@@ -64,6 +67,9 @@
         mRetailModeInteractor = retailModeInteractor;
 
         mBuildText = mView.findViewById(R.id.build);
+        if (gsfQuickSettings()) {
+            mBuildText.setTypeface(Typeface.create("gsf-body-medium", Typeface.NORMAL));
+        }
         mPageIndicator = mView.findViewById(R.id.footer_page_indicator);
         mEditButton = mView.findViewById(android.R.id.edit);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
index 5da4809..cb3fc07 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
@@ -10,6 +10,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.shade.ShadeDisplayAware;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -96,7 +97,7 @@
         private final QSCustomizerController mQsCustomizerController;
 
         @Inject
-        Factory(Context context, QSCustomizerController qsCustomizerController) {
+        Factory(@ShadeDisplayAware Context context, QSCustomizerController qsCustomizerController) {
             mContext = context;
             mQsCustomizerController = qsCustomizerController;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 21c45c5..9dc21fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -233,6 +233,7 @@
                 // Only allow scrolling when we are fully expanded. That way, we don't intercept
                 // swipes in lockscreen (when somehow QS is receiving touches).
                 { (scrollState.canScrollForward && viewModel.isQsFullyExpanded) || isCustomizing },
+                viewModel::emitMotionEventForFalsingSwipeNested,
             )
         frame.addView(
             composeView,
@@ -628,11 +629,7 @@
                                         id = R.string.accessibility_quick_settings_expand
                                     )
                                 )
-                                .padding(
-                                    horizontal = {
-                                        QuickSettingsShade.Dimensions.Padding.roundToPx()
-                                    }
-                                )
+                                .padding(horizontal = qsHorizontalMargin())
                     ) {
                         QuickQuickSettingsLayout(
                             tiles = Tiles,
@@ -718,7 +715,6 @@
                                                     max =
                                                         QuickSettingsShade.Dimensions.GridMaxHeight
                                                 ),
-                                        containerViewModel.editModeViewModel::startEditing,
                                     )
                                 }
                             }
@@ -737,8 +733,8 @@
                                     .sysuiResTag(ResIdTags.quickSettingsPanel)
                                     .padding(
                                         top = QuickSettingsShade.Dimensions.Padding,
-                                        start = QuickSettingsShade.Dimensions.Padding,
-                                        end = QuickSettingsShade.Dimensions.Padding,
+                                        start = qsHorizontalMargin(),
+                                        end = qsHorizontalMargin(),
                                     )
                         ) {
                             QuickSettingsLayout(
@@ -951,6 +947,7 @@
     private val clippingEnabledProvider: () -> Boolean,
     private val clippingTopProvider: () -> Int,
     private val canScrollForwardQs: () -> Boolean,
+    private val emitMotionEventForFalsing: () -> Unit,
 ) : FrameLayout(context) {
     override fun isTransformedTouchPointInView(
         x: Float,
@@ -967,6 +964,32 @@
 
     val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
     var downY = 0f
+    var preventingIntercept = false
+
+    override fun onTouchEvent(event: MotionEvent): Boolean {
+        val action = event.actionMasked
+        when (action) {
+            MotionEvent.ACTION_DOWN -> {
+                preventingIntercept = false
+                if (canScrollVertically(1)) {
+                    // If we can scroll down, make sure we're not intercepted by the parent
+                    preventingIntercept = true
+                    parent?.requestDisallowInterceptTouchEvent(true)
+                } else if (!canScrollVertically(-1)) {
+                    // Don't pass on the touch to the view, because scrolling will unconditionally
+                    // disallow interception even if we can't scroll.
+                    // if a user can't scroll at all, we should never listen to the touch.
+                    return false
+                }
+            }
+            MotionEvent.ACTION_UP -> {
+                if (preventingIntercept) {
+                    emitMotionEventForFalsing()
+                }
+            }
+        }
+        return super.onTouchEvent(event)
+    }
 
     override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
         // If there's a touch on this view and we can scroll down, we don't want to be intercepted
@@ -974,8 +997,10 @@
 
         when (action) {
             MotionEvent.ACTION_DOWN -> {
+                preventingIntercept = false
                 // If we can scroll down, make sure none of our parents intercepts us.
                 if (canScrollForwardQs()) {
+                    preventingIntercept = true
                     parent?.requestDisallowInterceptTouchEvent(true)
                 }
                 downY = ev.y
@@ -1098,6 +1123,8 @@
     const val qsFooterActions = "qs_footer_actions"
 }
 
+@Composable private fun qsHorizontalMargin() = dimensionResource(id = R.dimen.qs_horizontal_margin)
+
 @Composable
 private fun interactionsConfig() =
     InteractionsConfig(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeLog.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeLog.kt
new file mode 100644
index 0000000..5f151eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeLog.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.dagger
+
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class QSFragmentComposeLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
index 2ec7292..4c9df11 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
@@ -18,7 +18,8 @@
 
 import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.qs.flags.QSComposeFragment
 import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.Utils
@@ -38,5 +39,14 @@
         fun providesUsingMedia(@ShadeDisplayAware context: Context): Boolean {
             return QSComposeFragment.isEnabled && Utils.useQsMediaPlayer(context)
         }
+
+        @Provides
+        @SysUISingleton
+        @QSFragmentComposeLog
+        fun providesQSFragmentComposeViewModelTableLog(
+            factory: TableLogBufferFactory
+        ): TableLogBuffer {
+            return factory.create("QSFragmentComposeViewModel", 200)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index e3de6d5..02498d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -31,6 +31,8 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator
 import com.android.systemui.Dumpable
 import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.classifier.Classifier
+import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -39,6 +41,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
@@ -48,6 +51,7 @@
 import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeLog
 import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
@@ -100,7 +104,9 @@
     @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
     private val squishinessInteractor: TileSquishinessInteractor,
+    private val falsingInteractor: FalsingInteractor,
     private val inFirstPageViewModel: InFirstPageViewModel,
+    @QSFragmentComposeLog private val tableLogBuffer: TableLogBuffer,
     mediaInRowInLandscapeViewModelFactory: MediaInRowInLandscapeViewModel.Factory,
     @Named(QUICK_QS_PANEL) val qqsMediaHost: MediaHost,
     @Named(QS_PANEL) val qsMediaHost: MediaHost,
@@ -112,7 +118,7 @@
     private val qqsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QQS)
     private val qsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QS)
 
-    private val hydrator = Hydrator("QSFragmentComposeViewModel.hydrator")
+    private val hydrator = Hydrator("QSFragmentComposeViewModel.hydrator", tableLogBuffer)
 
     val footerActionsViewModel =
         footerActionsViewModelFactory.create(lifecycleScope).also {
@@ -434,6 +440,10 @@
         }
     }
 
+    fun emitMotionEventForFalsingSwipeNested() {
+        falsingInteractor.isFalseTouch(Classifier.QS_SWIPE_NESTED)
+    }
+
     override suspend fun onActivated(): Nothing {
         initMediaHosts() // init regardless of using media (same as current QS).
         coroutineScope {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index f8d4080..0e64820 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.qs.customize;
 
+import static com.android.systemui.Flags.gsfQuickSettings;
+
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
@@ -81,6 +83,10 @@
         mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
         mToolbar.setTitle(R.string.qs_edit);
+        if (gsfQuickSettings()) {
+            mToolbar.setTitleTextAppearance(context, R.style.TextAppearance_QSEditTitle);
+        }
+
         mRecyclerView = findViewById(android.R.id.list);
         mTransparentView = findViewById(R.id.customizer_transparent_view);
         DefaultItemAnimator animator = new DefaultItemAnimator();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index a222b3c..c606ce4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -43,6 +43,7 @@
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -107,7 +108,8 @@
     protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper,
             QSHost qsHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle,
             KeyguardStateController keyguardStateController, LightBarController lightBarController,
-            ConfigurationController configurationController, UiEventLogger uiEventLogger) {
+            @ShadeDisplayAware ConfigurationController configurationController,
+            UiEventLogger uiEventLogger) {
         super(view);
         mTileQueryHelper = tileQueryHelper;
         mQsHost = qsHost;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 829c419..db778a2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -14,12 +14,15 @@
 
 package com.android.systemui.qs.customize;
 
+import static com.android.systemui.Flags.gsfQuickSettings;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.view.LayoutInflater;
@@ -309,6 +312,10 @@
         if (viewType == TYPE_HEADER) {
             View v = inflater.inflate(R.layout.qs_customize_header, parent, false);
             v.setMinimumHeight(calculateHeaderMinHeight(context));
+            if (gsfQuickSettings()) {
+                ((TextView) v.findViewById(android.R.id.title)).setTypeface(
+                        Typeface.create("gsf-label-large", Typeface.NORMAL));
+            }
             return new Holder(v);
         }
         if (viewType == TYPE_DIVIDER) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt
index 6f5dea3..379b606 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt
@@ -22,12 +22,14 @@
 import android.service.quicksettings.Tile
 import android.util.Log
 import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
 import javax.inject.Inject
 import org.json.JSONException
 import org.json.JSONObject
 
 data class TileServiceKey(val componentName: ComponentName, val user: Int) {
     private val string = "${componentName.flattenToString()}:$user"
+
     override fun toString() = string
 }
 
@@ -56,12 +58,14 @@
      * Any fields that have not been saved will be set to `null`
      */
     fun readState(key: TileServiceKey): Tile?
+
     /**
      * Persists the state into [SharedPreferences].
      *
      * The implementation does not store fields that are `null` or icons.
      */
     fun persistState(key: TileServiceKey, tile: Tile)
+
     /**
      * Removes the state for a given tile, user pair.
      *
@@ -71,7 +75,7 @@
 }
 
 // TODO(b/299909989) Merge this class into into CustomTileRepository
-class CustomTileStatePersisterImpl @Inject constructor(context: Context) :
+class CustomTileStatePersisterImpl @Inject constructor(@Application context: Context) :
     CustomTileStatePersister {
     companion object {
         private const val FILE_NAME = "custom_tiles_state"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
index 28e4fd0..6fb4455 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
@@ -28,6 +28,8 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.systemui.dagger.qualifiers.Application;
+
 import javax.inject.Inject;
 
 // Adapter that wraps calls to PackageManager or IPackageManager for {@link TileLifecycleManager}.
@@ -42,7 +44,7 @@
     // Uses the PackageManager for the provided context.
     // When necessary, uses the IPackagemanger in AppGlobals.
     @Inject
-    public PackageManagerAdapter(Context context) {
+    public PackageManagerAdapter(@Application Context context) {
         mPackageManager = context.getPackageManager();
         mIPackageManager = AppGlobals.getPackageManager();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileData.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileData.kt
new file mode 100644
index 0000000..de75968
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileData.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.graphics.drawable.Icon
+import androidx.compose.runtime.Immutable
+
+/**
+ * Data bundle of information to show the user when requesting to add a TileService
+ *
+ * @property appName Name of the app requesting their [TileService] to be added.
+ * @property label Label of the tile.
+ * @property icon Icon for the tile.
+ */
+@Immutable
+data class TileData(
+    val callingUid: Int,
+    val appName: CharSequence,
+    val label: CharSequence,
+    val icon: Icon?,
+    val packageName: String,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
index c3c587d..5597f28 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
@@ -18,73 +18,73 @@
 
 import android.app.IUriGrantsManager
 import android.content.Context
-import android.graphics.drawable.Icon
 import android.view.ContextThemeWrapper
 import android.view.LayoutInflater
 import android.view.ViewGroup
 import android.widget.TextView
-import com.android.systemui.res.R
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.qs.QSTileView
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
 import com.android.systemui.qs.tileimpl.QSTileViewImpl
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialog
 
-/**
- * Dialog to present to the user to ask for authorization to add a [TileService].
- */
-class TileRequestDialog(
-    context: Context,
-) : SystemUIDialog(context) {
+/** Dialog to present to the user to ask for authorization to add a [TileService]. */
+class TileRequestDialog(context: Context) : SystemUIDialog(context) {
 
     companion object {
         internal val CONTENT_ID = R.id.content
     }
 
-    /**
-     * Set the data of the tile to add, to show the user.
-     */
+    /** Set the data of the tile to add, to show the user. */
     fun setTileData(tileData: TileData, iUriGrantsManager: IUriGrantsManager) {
-        val ll = (LayoutInflater
-                        .from(context)
-                        .inflate(R.layout.tile_service_request_dialog, null)
-                        as ViewGroup).apply {
+        val ll =
+            (LayoutInflater.from(context).inflate(R.layout.tile_service_request_dialog, null)
+                    as ViewGroup)
+                .apply {
                     requireViewById<TextView>(R.id.text).apply {
-                        text = context
-                                .getString(R.string.qs_tile_request_dialog_text, tileData.appName)
+                        text =
+                            context.getString(
+                                R.string.qs_tile_request_dialog_text,
+                                tileData.appName,
+                            )
                     }
                     addView(
-                            createTileView(tileData, iUriGrantsManager),
-                            context.resources.getDimensionPixelSize(
-                                    R.dimen.qs_tile_service_request_tile_width),
-                            context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size)
+                        createTileView(tileData, iUriGrantsManager),
+                        context.resources.getDimensionPixelSize(
+                            R.dimen.qs_tile_service_request_tile_width
+                        ),
+                        context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size),
                     )
                     isSelected = true
-        }
+                }
         val spacing = 0
         setView(ll, spacing, spacing, spacing, spacing / 2)
     }
 
     private fun createTileView(
-            tileData: TileData,
-            iUriGrantsManager: IUriGrantsManager,
+        tileData: TileData,
+        iUriGrantsManager: IUriGrantsManager,
     ): QSTileView {
         val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
         val tile = QSTileViewImpl(themedContext, true)
-        val state = QSTile.BooleanState().apply {
-            label = tileData.label
-            handlesLongClick = false
-            icon = tileData.icon?.loadDrawableCheckingUriGrant(
-                    context,
-                    iUriGrantsManager,
-                    tileData.callingUid,
-                    tileData.packageName,
-            )?.let {
-                QSTileImpl.DrawableIcon(it)
-            } ?: ResourceIcon.get(R.drawable.android)
-            contentDescription = label
-        }
+        val state =
+            QSTile.BooleanState().apply {
+                label = tileData.label
+                handlesLongClick = false
+                icon =
+                    tileData.icon
+                        ?.loadDrawableCheckingUriGrant(
+                            context,
+                            iUriGrantsManager,
+                            tileData.callingUid,
+                            tileData.packageName,
+                        )
+                        ?.let { QSTileImpl.DrawableIcon(it) }
+                        ?: ResourceIcon.get(R.drawable.android)
+                contentDescription = label
+            }
         tile.onStateChanged(state)
         tile.post {
             tile.stateDescription = ""
@@ -93,19 +93,4 @@
         }
         return tile
     }
-
-    /**
-     * Data bundle of information to show the user.
-     *
-     * @property appName Name of the app requesting their [TileService] to be added.
-     * @property label Label of the tile.
-     * @property icon Icon for the tile.
-     */
-    data class TileData(
-        val callingUid: Int,
-        val appName: CharSequence,
-        val label: CharSequence,
-        val icon: Icon?,
-        val packageName: String,
-    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
index 08567af..33e0590 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt
@@ -26,9 +26,11 @@
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import com.android.internal.statusbar.IAddTileResultCallback
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.external.ui.dialog.TileRequestDialogComposeDelegate
+import com.android.systemui.qs.flags.QsInCompose
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -40,50 +42,50 @@
 
 private const val TAG = "TileServiceRequestController"
 
-/**
- * Controller to interface between [TileRequestDialog] and [QSHost].
- */
+/** Controller to interface between [TileRequestDialog] and [QSHost]. */
 class TileServiceRequestController(
-        private val qsHost: QSHost,
-        private val commandQueue: CommandQueue,
-        private val commandRegistry: CommandRegistry,
-        private val eventLogger: TileRequestDialogEventLogger,
-        private val iUriGrantsManager: IUriGrantsManager,
-        private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) }
+    private val qsHost: QSHost,
+    private val commandQueue: CommandQueue,
+    private val commandRegistry: CommandRegistry,
+    private val eventLogger: TileRequestDialogEventLogger,
+    private val iUriGrantsManager: IUriGrantsManager,
+    private val tileRequestDialogComposeDelegateFactory: TileRequestDialogComposeDelegate.Factory,
+    private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) },
 ) {
 
     companion object {
-        internal const val ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED
-        internal const val DONT_ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED
-        internal const val TILE_ALREADY_ADDED =
-                StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED
-        internal const val DISMISSED = StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED
+        const val ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED
+        const val DONT_ADD_TILE = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED
+        const val TILE_ALREADY_ADDED =
+            StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED
+        const val DISMISSED = StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED
     }
 
     private var dialogCanceller: ((String) -> Unit)? = null
 
-    private val commandQueueCallback = object : CommandQueue.Callbacks {
-        override fun requestAddTile(
-            callingUid: Int,
-            componentName: ComponentName,
-            appName: CharSequence,
-            label: CharSequence,
-            icon: Icon,
-            callback: IAddTileResultCallback
-        ) {
-            requestTileAdd(callingUid, componentName, appName, label, icon) {
-                try {
-                    callback.onTileRequest(it)
-                } catch (e: RemoteException) {
-                    Log.e(TAG, "Couldn't respond to request", e)
+    private val commandQueueCallback =
+        object : CommandQueue.Callbacks {
+            override fun requestAddTile(
+                callingUid: Int,
+                componentName: ComponentName,
+                appName: CharSequence,
+                label: CharSequence,
+                icon: Icon,
+                callback: IAddTileResultCallback,
+            ) {
+                requestTileAdd(callingUid, componentName, appName, label, icon) {
+                    try {
+                        callback.onTileRequest(it)
+                    } catch (e: RemoteException) {
+                        Log.e(TAG, "Couldn't respond to request", e)
+                    }
                 }
             }
-        }
 
-        override fun cancelRequestAddTile(packageName: String) {
-            dialogCanceller?.invoke(packageName)
+            override fun cancelRequestAddTile(packageName: String) {
+                dialogCanceller?.invoke(packageName)
+            }
         }
-    }
 
     fun init() {
         commandRegistry.registerCommand("tile-service-add") { TileServiceRequestCommand() }
@@ -100,58 +102,87 @@
     }
 
     @VisibleForTesting
-    internal fun requestTileAdd(
+    fun requestTileAdd(
         callingUid: Int,
         componentName: ComponentName,
         appName: CharSequence,
         label: CharSequence,
         icon: Icon?,
-        callback: Consumer<Int>
-    ) {
+        callback: Consumer<Int>,
+    ): SystemUIDialog? {
         val instanceId = eventLogger.newInstanceId()
         val packageName = componentName.packageName
         if (isTileAlreadyAdded(componentName)) {
             callback.accept(TILE_ALREADY_ADDED)
             eventLogger.logTileAlreadyAdded(packageName, instanceId)
-            return
+            return null
         }
-        val dialogResponse = SingleShotConsumer<Int> { response ->
-            if (response == ADD_TILE) {
-                addTile(componentName)
-            }
-            dialogCanceller = null
-            eventLogger.logUserResponse(response, packageName, instanceId)
-            callback.accept(response)
-        }
-        val tileData = TileRequestDialog.TileData(
-                callingUid,
-                appName,
-                label,
-                icon,
-                componentName.packageName,
-        )
-        createDialog(tileData, dialogResponse).also { dialog ->
-            dialogCanceller = {
-                if (packageName == it) {
-                    dialog.cancel()
+        val dialogResponse =
+            SingleShotConsumer<Int> { response ->
+                if (response == ADD_TILE) {
+                    addTile(componentName)
                 }
                 dialogCanceller = null
+                eventLogger.logUserResponse(response, packageName, instanceId)
+                callback.accept(response)
             }
-        }.show()
-        eventLogger.logDialogShown(packageName, instanceId)
+        val tileData = TileData(callingUid, appName, label, icon, componentName.packageName)
+        return if (QsInCompose.isEnabled) {
+                createComposeDialog(tileData, dialogResponse)
+            } else {
+                createDialog(tileData, dialogResponse)
+            }
+            .also { dialog ->
+                dialogCanceller = {
+                    if (packageName == it) {
+                        dialog.cancel()
+                    }
+                    dialogCanceller = null
+                }
+                dialog.show()
+                eventLogger.logDialogShown(packageName, instanceId)
+            }
+    }
+
+    private fun createComposeDialog(
+        tileData: TileData,
+        responseHandler: SingleShotConsumer<Int>,
+    ): SystemUIDialog {
+        val dialogClickListener =
+            DialogInterface.OnClickListener { _, which ->
+                if (which == Dialog.BUTTON_POSITIVE) {
+                    responseHandler.accept(ADD_TILE)
+                } else {
+                    responseHandler.accept(DONT_ADD_TILE)
+                }
+            }
+        return tileRequestDialogComposeDelegateFactory
+            .create(dialogListener = dialogClickListener, tiledata = tileData)
+            .createDialog()
+            .apply {
+                setShowForAllUsers(true)
+                setCanceledOnTouchOutside(true)
+                setOnCancelListener { responseHandler.accept(DISMISSED) }
+                // We want this in case the dialog is dismissed without it being cancelled (for
+                // example
+                // by going home or locking the device). We use a SingleShotConsumer so the response
+                // is only sent once, with the first value.
+                setOnDismissListener { responseHandler.accept(DISMISSED) }
+            }
     }
 
     private fun createDialog(
-        tileData: TileRequestDialog.TileData,
-        responseHandler: SingleShotConsumer<Int>
+        tileData: TileData,
+        responseHandler: SingleShotConsumer<Int>,
     ): SystemUIDialog {
-        val dialogClickListener = DialogInterface.OnClickListener { _, which ->
-            if (which == Dialog.BUTTON_POSITIVE) {
-                responseHandler.accept(ADD_TILE)
-            } else {
-                responseHandler.accept(DONT_ADD_TILE)
+        val dialogClickListener =
+            DialogInterface.OnClickListener { _, which ->
+                if (which == Dialog.BUTTON_POSITIVE) {
+                    responseHandler.accept(ADD_TILE)
+                } else {
+                    responseHandler.accept(DONT_ADD_TILE)
+                }
             }
-        }
         return dialogCreator().apply {
             setTileData(tileData, iUriGrantsManager)
             setShowForAllUsers(true)
@@ -173,19 +204,20 @@
 
     inner class TileServiceRequestCommand : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
-            val componentName: ComponentName = ComponentName.unflattenFromString(args[0])
+            val componentName: ComponentName =
+                ComponentName.unflattenFromString(args[0])
                     ?: run {
                         Log.w(TAG, "Malformed componentName ${args[0]}")
                         return
                     }
-            requestTileAdd(0, componentName, args[1], args[2], null) {
-                Log.d(TAG, "Response: $it")
-            }
+            requestTileAdd(0, componentName, args[1], args[2], null) { Log.d(TAG, "Response: $it") }
         }
 
         override fun help(pw: PrintWriter) {
-            pw.println("Usage: adb shell cmd statusbar tile-service-add " +
-                    "<componentName> <appName> <label>")
+            pw.println(
+                "Usage: adb shell cmd statusbar tile-service-add " +
+                    "<componentName> <appName> <label>"
+            )
         }
     }
 
@@ -200,18 +232,23 @@
     }
 
     @SysUISingleton
-    class Builder @Inject constructor(
+    class Builder
+    @Inject
+    constructor(
         private val commandQueue: CommandQueue,
         private val commandRegistry: CommandRegistry,
         private val iUriGrantsManager: IUriGrantsManager,
+        private val tileRequestDialogComposeDelegateFactory:
+            TileRequestDialogComposeDelegate.Factory,
     ) {
         fun create(qsHost: QSHost): TileServiceRequestController {
             return TileServiceRequestController(
-                    qsHost,
-                    commandQueue,
-                    commandRegistry,
-                    TileRequestDialogEventLogger(),
-                    iUriGrantsManager,
+                qsHost,
+                commandQueue,
+                commandRegistry,
+                TileRequestDialogEventLogger(),
+                iUriGrantsManager,
+                tileRequestDialogComposeDelegateFactory,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt
new file mode 100644
index 0000000..446be9b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.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.external.ui.dialog
+
+import android.content.DialogInterface.BUTTON_NEGATIVE
+import android.content.DialogInterface.BUTTON_POSITIVE
+import android.content.DialogInterface.OnClickListener
+import androidx.compose.foundation.layout.Arrangement.spacedBy
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformButton
+import com.android.compose.PlatformOutlinedButton
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.dialog.ui.composable.AlertDialogContent
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.qs.external.TileData
+import com.android.systemui.qs.external.ui.viewmodel.TileRequestDialogViewModel
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.LargeStaticTile
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.statusbar.phone.create
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class TileRequestDialogComposeDelegate
+@AssistedInject
+constructor(
+    private val sysuiDialogFactory: SystemUIDialogFactory,
+    private val tileRequestDialogViewModelFactory: TileRequestDialogViewModel.Factory,
+    @Assisted private val tileData: TileData,
+    @Assisted private val dialogListener: OnClickListener,
+) : SystemUIDialog.Delegate {
+
+    override fun createDialog(): SystemUIDialog {
+        return sysuiDialogFactory.create { TileRequestDialogContent(it) }
+    }
+
+    @Composable
+    private fun TileRequestDialogContent(dialog: SystemUIDialog) {
+        PlatformTheme {
+            AlertDialogContent(
+                title = {},
+                content = {
+                    Column(
+                        horizontalAlignment = Alignment.CenterHorizontally,
+                        verticalArrangement = spacedBy(16.dp),
+                    ) {
+                        val viewModel =
+                            rememberViewModel(traceName = "TileRequestDialog", key = tileData) {
+                                tileRequestDialogViewModelFactory.create(dialog.context, tileData)
+                            }
+
+                        Text(
+                            text =
+                                stringResource(
+                                    R.string.qs_tile_request_dialog_text,
+                                    tileData.appName,
+                                ),
+                            textAlign = TextAlign.Start,
+                        )
+
+                        LargeStaticTile(
+                            uiState = viewModel.uiState,
+                            modifier =
+                                Modifier.width(
+                                    dimensionResource(
+                                        id = R.dimen.qs_tile_service_request_tile_width
+                                    )
+                                ),
+                        )
+                    }
+                },
+                positiveButton = {
+                    PlatformButton(
+                        onClick = {
+                            dialogListener.onClick(dialog, BUTTON_POSITIVE)
+                            dialog.dismiss()
+                        }
+                    ) {
+                        Text(stringResource(R.string.qs_tile_request_dialog_add))
+                    }
+                },
+                negativeButton = {
+                    PlatformOutlinedButton(
+                        onClick = {
+                            dialogListener.onClick(dialog, BUTTON_NEGATIVE)
+                            dialog.dismiss()
+                        }
+                    ) {
+                        Text(stringResource(R.string.qs_tile_request_dialog_not_add))
+                    }
+                },
+            )
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            tiledata: TileData,
+            dialogListener: OnClickListener,
+        ): TileRequestDialogComposeDelegate
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt
new file mode 100644
index 0000000..c756adc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.ui.viewmodel
+
+import android.app.IUriGrantsManager
+import android.content.Context
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.external.TileData
+import com.android.systemui.qs.panels.ui.viewmodel.toUiState
+import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon
+import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.res.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.withContext
+
+class TileRequestDialogViewModel
+@AssistedInject
+constructor(
+    private val iUriGrantsManager: IUriGrantsManager,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @Assisted private val dialogContext: Context,
+    @Assisted private val tileData: TileData,
+) : ExclusiveActivatable() {
+
+    private var _icon by mutableStateOf(defaultIcon)
+
+    private val state: QSTile.State
+        get() =
+            QSTile.State().apply {
+                label = tileData.label
+                handlesLongClick = false
+                this.icon = _icon
+            }
+
+    val uiState by derivedStateOf { state.toUiState(dialogContext.resources) }
+
+    override suspend fun onActivated(): Nothing {
+        withContext(backgroundDispatcher) {
+            tileData.icon
+                ?.loadDrawableCheckingUriGrant(
+                    dialogContext,
+                    iUriGrantsManager,
+                    tileData.callingUid,
+                    tileData.packageName,
+                )
+                ?.run { _icon = DrawableIcon(this) }
+        }
+        awaitCancellation()
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(dialogContext: Context, tileData: TileData): TileRequestDialogViewModel
+    }
+
+    companion object {
+        private val defaultIcon: QSTile.Icon = ResourceIcon.get(R.drawable.android)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/flags/QsInCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/flags/QsInCompose.kt
new file mode 100644
index 0000000..3067ccb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/flags/QsInCompose.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.qs.flags
+
+import com.android.systemui.flags.RefactorFlagUtils
+import com.android.systemui.shade.shared.flag.DualShade
+
+/**
+ * Object to help check if the new QS ui should be used. This is true if either [QSComposeFragment]
+ * or [DualShade] are enabled.
+ */
+object QsInCompose {
+
+    /**
+     * This is not a real flag name, but a representation of the allowed flag names. Should not be
+     * used with test annotations.
+     */
+    private val flagName = "${QSComposeFragment.FLAG_NAME}|${DualShade.FLAG_NAME}"
+
+    @JvmStatic
+    inline val isEnabled: Boolean
+        get() = QSComposeFragment.isEnabled || DualShade.isEnabled
+
+    @JvmStatic
+    fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, flagName)
+
+    @JvmStatic fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, flagName)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt
index 5883403..eeec9b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt
@@ -20,8 +20,8 @@
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -38,8 +38,8 @@
 @Inject
 constructor(
     @Application scope: CoroutineScope,
-    @Main private val resources: Resources,
-    configurationRepository: ConfigurationRepository,
+    @ShadeDisplayAware private val resources: Resources,
+    @ShadeDisplayAware configurationRepository: ConfigurationRepository,
 ) {
     val span: StateFlow<Int> =
         configurationRepository.onConfigurationChange
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt
index 424be90..6746efa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt
@@ -19,8 +19,8 @@
 import android.content.res.Resources
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
 import kotlinx.coroutines.flow.map
@@ -33,8 +33,8 @@
 class PaginatedGridRepository
 @Inject
 constructor(
-    @Main private val resources: Resources,
-    configurationRepository: ConfigurationRepository,
+    @ShadeDisplayAware private val resources: Resources,
+    @ShadeDisplayAware configurationRepository: ConfigurationRepository,
 ) {
     val rows =
         configurationRepository.onConfigurationChange.emitOnStart().map {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt
index a9205c2..693681d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt
@@ -19,8 +19,8 @@
 import android.content.res.Resources
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -33,8 +33,8 @@
 class QSColumnsRepository
 @Inject
 constructor(
-    @Main private val resources: Resources,
-    configurationRepository: ConfigurationRepository,
+    @ShadeDisplayAware private val resources: Resources,
+    @ShadeDisplayAware configurationRepository: ConfigurationRepository,
 ) {
     val splitShadeColumns: Flow<Int> =
         flowOf(resources.getInteger(R.integer.quick_settings_split_shade_num_columns))
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt
index ee0cfb3..636f703 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt
@@ -19,8 +19,8 @@
 import android.content.res.Resources
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
 import kotlinx.coroutines.flow.map
@@ -29,8 +29,8 @@
 class QuickQuickSettingsRowRepository
 @Inject
 constructor(
-    @Main private val resources: Resources,
-    configurationRepository: ConfigurationRepository,
+    @ShadeDisplayAware private val resources: Resources,
+    @ShadeDisplayAware configurationRepository: ConfigurationRepository,
 ) {
     val rows =
         configurationRepository.onConfigurationChange.emitOnStart().map {
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
index 86a29f9..a2d892c 100644
--- 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
@@ -19,17 +19,15 @@
 import android.content.res.Resources
 import com.android.server.display.feature.flags.Flags
 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 com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 @SysUISingleton
 class StockTilesRepository
 @Inject
-constructor(
-    @Main private val resources: Resources,
-) {
+constructor(@ShadeDisplayAware private val resources: Resources) {
     /**
      * List of stock platform tiles. All of the specs will be of type [TileSpec.PlatformTileSpec].
      */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditModeButton.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditModeButton.kt
new file mode 100644
index 0000000..c2764f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditModeButton.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.compose
+
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.EditModeButtonViewModel
+import com.android.systemui.qs.ui.compose.borderOnFocus
+import com.android.systemui.res.R
+
+@Composable
+fun EditModeButton(
+    viewModelFactory: EditModeButtonViewModel.Factory,
+    modifier: Modifier = Modifier,
+) {
+    val viewModel = rememberViewModel(traceName = "EditModeButton") { viewModelFactory.create() }
+    CompositionLocalProvider(
+        value = LocalContentColor provides MaterialTheme.colorScheme.onSurface
+    ) {
+        IconButton(
+            onClick = viewModel::onButtonClick,
+            shape = RoundedCornerShape(CornerSize(28.dp)),
+            modifier =
+                modifier.borderOnFocus(
+                    color = MaterialTheme.colorScheme.secondary,
+                    cornerSize = CornerSize(24.dp),
+                ),
+        ) {
+            Icon(
+                imageVector = Icons.Default.Edit,
+                contentDescription = stringResource(id = R.string.qs_edit),
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index c729c7c..14abfa2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -68,29 +68,16 @@
         return _tiles.indexOfFirst { it is TileGridCell && it.tile.tileSpec == tileSpec }
     }
 
-    /**
-     * Whether the tile with this [TileSpec] is currently an icon in the [EditTileListState]
-     *
-     * @return true if the tile is an icon, false if it's large, null if the tile isn't in the list
-     */
-    fun isIcon(tileSpec: TileSpec): Boolean? {
-        val index = indexOf(tileSpec)
-        return if (index != -1) {
-            val cell = _tiles[index]
-            cell as TileGridCell
-            return cell.isIcon
-        } else {
-            null
-        }
-    }
-
-    /** Toggle the size of the tile corresponding to the [TileSpec] */
-    fun toggleSize(tileSpec: TileSpec) {
+    /** Resize the tile corresponding to the [TileSpec] to [toIcon] */
+    fun resizeTile(tileSpec: TileSpec, toIcon: Boolean) {
         val fromIndex = indexOf(tileSpec)
         if (fromIndex != -1) {
-            val cell = _tiles.removeAt(fromIndex)
-            cell as TileGridCell
-            _tiles.add(fromIndex, cell.copy(width = if (cell.isIcon) largeTilesSpan else 1))
+            val cell = _tiles[fromIndex] as TileGridCell
+
+            if (cell.isIcon == toIcon) return
+
+            _tiles.removeAt(fromIndex)
+            _tiles.add(fromIndex, cell.copy(width = if (toIcon) 1 else largeTilesSpan))
             regenerateGrid(fromIndex)
         }
     }
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 0d37581..a22eb3a 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
@@ -27,12 +27,7 @@
 
 /** A layout of tiles, indicating how they should be composed when showing in QS or in edit mode. */
 interface GridLayout {
-    @Composable
-    fun SceneScope.TileGrid(
-        tiles: List<TileViewModel>,
-        modifier: Modifier,
-        editModeStart: () -> Unit,
-    )
+    @Composable fun SceneScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier)
 
     @Composable
     fun EditTileGrid(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index 789fdeb..b6dbf4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.panels.ui.compose
 
+import android.view.MotionEvent
 import androidx.compose.foundation.layout.Arrangement.spacedBy
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
@@ -29,22 +30,15 @@
 import androidx.compose.foundation.pager.PagerState
 import androidx.compose.foundation.pager.rememberPagerState
 import androidx.compose.foundation.shape.CornerSize
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Edit
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.input.pointer.pointerInteropFilter
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.padding
@@ -55,10 +49,10 @@
 import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType
 import com.android.systemui.qs.panels.ui.compose.Dimensions.FooterHeight
 import com.android.systemui.qs.panels.ui.compose.Dimensions.InterPageSpacing
+import com.android.systemui.qs.panels.ui.viewmodel.EditModeButtonViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
 import com.android.systemui.qs.ui.compose.borderOnFocus
-import com.android.systemui.res.R
 import javax.inject.Inject
 
 class PaginatedGridLayout
@@ -68,11 +62,7 @@
     @PaginatedBaseLayoutType private val delegateGridLayout: PaginatableGridLayout,
 ) : GridLayout by delegateGridLayout {
     @Composable
-    override fun SceneScope.TileGrid(
-        tiles: List<TileViewModel>,
-        modifier: Modifier,
-        editModeStart: () -> Unit,
-    ) {
+    override fun SceneScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) {
         val viewModel =
             rememberViewModel(traceName = "PaginatedGridLayout-TileGrid") {
                 viewModelFactory.create()
@@ -115,7 +105,13 @@
                 state = pagerState,
                 modifier =
                     Modifier.sysuiResTag("qs_pager")
-                        .padding(horizontal = { -contentPaddingValue.roundToPx() }),
+                        .padding(horizontal = { -contentPaddingValue.roundToPx() })
+                        .pointerInteropFilter { event ->
+                            if (event.actionMasked == MotionEvent.ACTION_UP) {
+                                viewModel.registerSideSwipeGesture()
+                            }
+                            false
+                        },
                 contentPadding = contentPadding,
                 pageSpacing = if (pages.size > 1) InterPageSpacing else 0.dp,
                 beyondViewportPageCount = 1,
@@ -123,14 +119,12 @@
             ) {
                 val page = pages[it]
 
-                with(delegateGridLayout) {
-                    TileGrid(tiles = page, modifier = Modifier, editModeStart = {})
-                }
+                with(delegateGridLayout) { TileGrid(tiles = page, modifier = Modifier) }
             }
             FooterBar(
                 buildNumberViewModelFactory = viewModel.buildNumberViewModelFactory,
                 pagerState = pagerState,
-                editModeStart = editModeStart,
+                editButtonViewModelFactory = viewModel.editModeButtonViewModelFactory,
             )
         }
     }
@@ -145,7 +139,7 @@
 private fun FooterBar(
     buildNumberViewModelFactory: BuildNumberViewModel.Factory,
     pagerState: PagerState,
-    editModeStart: () -> Unit,
+    editButtonViewModelFactory: EditModeButtonViewModel.Factory,
 ) {
     // Use requiredHeight so it won't be squished if the view doesn't quite fit. As this is
     // expected to be inside a scrollable container, this should not be an issue.
@@ -181,24 +175,7 @@
         )
         Row(Modifier.weight(1f)) {
             Spacer(modifier = Modifier.weight(1f))
-            CompositionLocalProvider(
-                value = LocalContentColor provides MaterialTheme.colorScheme.onSurface
-            ) {
-                IconButton(
-                    onClick = editModeStart,
-                    shape = RoundedCornerShape(CornerSize(28.dp)),
-                    modifier =
-                        Modifier.borderOnFocus(
-                            color = MaterialTheme.colorScheme.secondary,
-                            cornerSize = CornerSize(FooterHeight / 2),
-                        ),
-                ) {
-                    Icon(
-                        imageVector = Icons.Default.Edit,
-                        contentDescription = stringResource(id = R.string.qs_edit),
-                    )
-                }
-            }
+            EditModeButton(viewModelFactory = editButtonViewModelFactory)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index ca28ab3..2928ad1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -75,6 +75,8 @@
                 coroutineScope = scope,
                 bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
                 tileHapticsViewModelFactoryProvider = viewModel.tileHapticsViewModelFactoryProvider,
+                // There should be no QuickQuickSettings when the details view is enabled.
+                detailsViewModel = null,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
new file mode 100644
index 0000000..1bfbbe1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.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.qs.panels.ui.compose
+
+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.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.em
+import com.android.systemui.qs.flags.QsDetailedView
+import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
+
+@Composable
+fun TileDetails(detailsViewModel: DetailsViewModel) {
+
+    if (!QsDetailedView.isEnabled) {
+        throw IllegalStateException("QsDetailedView should be enabled")
+    }
+
+    val tileDetailedViewModel = detailsViewModel.activeTileDetails ?: return
+
+    DisposableEffect(Unit) { onDispose { detailsViewModel.closeDetailedView() } }
+
+    Column(
+        modifier = Modifier
+            .fillMaxWidth()
+            // The height of the details view is TBD.
+            .fillMaxHeight()
+    ) {
+        CompositionLocalProvider(
+            value = LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant
+        ) {
+            Row(
+                modifier = Modifier.fillMaxWidth(),
+                horizontalArrangement = Arrangement.SpaceBetween,
+                verticalAlignment = Alignment.CenterVertically
+            ) {
+
+                IconButton(
+                    onClick = { detailsViewModel.closeDetailedView() },
+                    modifier = Modifier
+                        .align(Alignment.CenterVertically)
+                        .height(TileDetailsDefaults.IconHeight)
+                        .padding(start = TileDetailsDefaults.IconPadding),
+                ) {
+                    Icon(
+                        imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+                        // Description is TBD
+                        contentDescription = "Back to QS panel",
+                    )
+                }
+                Text(
+                    text = tileDetailedViewModel.getTitle(),
+                    modifier = Modifier
+                        .align(Alignment.CenterVertically),
+                    textAlign = TextAlign.Center,
+                    style = MaterialTheme.typography.titleLarge
+                )
+                IconButton(
+                    onClick = { tileDetailedViewModel.clickOnSettingsButton() },
+                    modifier = Modifier
+                        .align(Alignment.CenterVertically)
+                        .height(TileDetailsDefaults.IconHeight)
+                        .padding(end = TileDetailsDefaults.IconPadding),
+                ) {
+                    Icon(
+                        imageVector = Icons.Default.Settings,
+                        // Description is TBD
+                        contentDescription = "Go to Settings",
+                    )
+                }
+            }
+            Text(
+                text = tileDetailedViewModel.getSubTitle(),
+                modifier = Modifier
+                    .fillMaxWidth(),
+                textAlign = TextAlign.Center,
+                style = MaterialTheme.typography.titleSmall
+
+            )
+        }
+        tileDetailedViewModel.GetContentView()
+    }
+}
+
+private object TileDetailsDefaults {
+    val IconHeight = 48.dp
+    val IconPadding = 4.dp
+}
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 1a5297b..6c1906b 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
@@ -24,13 +24,9 @@
 import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
 
 @Composable
-fun SceneScope.TileGrid(
-    viewModel: TileGridViewModel,
-    modifier: Modifier = Modifier,
-    editModeStart: () -> Unit,
-) {
+fun SceneScope.TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) {
     val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle()
     val tiles by viewModel.tileViewModels.collectAsStateWithLifecycle(emptyList())
 
-    with(gridLayout) { TileGrid(tiles, modifier, editModeStart) }
+    with(gridLayout) { TileGrid(tiles, modifier) }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index dbad602..d72d5f1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -21,7 +21,6 @@
 import android.graphics.drawable.Drawable
 import android.text.TextUtils
 import androidx.compose.animation.animateColorAsState
-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
@@ -192,7 +191,6 @@
     }
 }
 
-@OptIn(ExperimentalAnimationGraphicsApi::class)
 @Composable
 fun SmallTileContent(
     modifier: Modifier = Modifier,
@@ -229,6 +227,7 @@
                         }
                     }
                 }
+
                 is Icon.Loaded -> {
                     LaunchedEffect(loadedDrawable) {
                         if (loadedDrawable is AnimatedVectorDrawable) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index 31ea60e..c6141a1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -20,7 +20,6 @@
 
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.animation.fadeIn
@@ -75,7 +74,6 @@
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
@@ -124,8 +122,12 @@
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.CurrentTilesGridPadding
 import com.android.systemui.qs.panels.ui.compose.selection.MutableSelectionState
 import com.android.systemui.qs.panels.ui.compose.selection.ResizableTileContainer
-import com.android.systemui.qs.panels.ui.compose.selection.TileWidths
+import com.android.systemui.qs.panels.ui.compose.selection.ResizingState
+import com.android.systemui.qs.panels.ui.compose.selection.ResizingState.ResizeOperation
+import com.android.systemui.qs.panels.ui.compose.selection.ResizingState.ResizeOperation.FinalResizeOperation
+import com.android.systemui.qs.panels.ui.compose.selection.ResizingState.ResizeOperation.TemporaryResizeOperation
 import com.android.systemui.qs.panels.ui.compose.selection.clearSelectionTile
+import com.android.systemui.qs.panels.ui.compose.selection.rememberResizingState
 import com.android.systemui.qs.panels.ui.compose.selection.rememberSelectionState
 import com.android.systemui.qs.panels.ui.compose.selection.selectableTile
 import com.android.systemui.qs.panels.ui.model.GridCell
@@ -136,10 +138,10 @@
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.shared.model.groupAndSort
 import com.android.systemui.res.R
+import kotlin.math.max
 import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collectLatest
 
 object TileType
 
@@ -181,15 +183,7 @@
     onStopEditing: () -> Unit,
     onReset: (() -> Unit)?,
 ) {
-    val currentListState by rememberUpdatedState(listState)
-    val selectionState =
-        rememberSelectionState(
-            onResize = { currentListState.toggleSize(it) },
-            onResizeEnd = { spec ->
-                // Commit the size currently in the list
-                currentListState.isIcon(spec)?.let { onResize(spec, it) }
-            },
-        )
+    val selectionState = rememberSelectionState()
     val reset: (() -> Unit)? =
         if (onReset != null) {
             {
@@ -349,10 +343,21 @@
                 }
                 .testTag(CURRENT_TILES_GRID_TEST_TAG),
     ) {
-        EditTiles(cells, columns, listState, selectionState, coroutineScope, largeTilesSpan) { spec
-            ->
-            // Toggle the current size of the tile
-            currentListState.isIcon(spec)?.let { onResize(spec, !it) }
+        EditTiles(cells, columns, listState, selectionState, coroutineScope, largeTilesSpan) {
+            resizingOperation ->
+            when (resizingOperation) {
+                is TemporaryResizeOperation -> {
+                    currentListState.resizeTile(resizingOperation.spec, resizingOperation.toIcon)
+                }
+                is FinalResizeOperation -> {
+                    // Commit the new size of the tile
+                    onResize(resizingOperation.spec, resizingOperation.toIcon)
+
+                    // Mark the selection as automatic in case the tile ends up moving to a
+                    // different row with its new size.
+                    selectionState.select(resizingOperation.spec, manual = false)
+                }
+            }
         }
     }
 }
@@ -373,7 +378,7 @@
 
     // Available tiles
     Column(
-        verticalArrangement = spacedBy(CommonTileDefaults.TileArrangementPadding),
+        verticalArrangement = spacedBy(TileArrangementPadding),
         horizontalAlignment = Alignment.Start,
         modifier =
             Modifier.fillMaxWidth().wrapContentHeight().testTag(AVAILABLE_TILES_GRID_TEST_TAG),
@@ -387,7 +392,7 @@
             )
             tiles.chunked(columns).forEach { row ->
                 Row(
-                    horizontalArrangement = spacedBy(CommonTileDefaults.TileArrangementPadding),
+                    horizontalArrangement = spacedBy(TileArrangementPadding),
                     modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max),
                 ) {
                     row.forEachIndexed { index, tileGridCell ->
@@ -436,7 +441,7 @@
     selectionState: MutableSelectionState,
     coroutineScope: CoroutineScope,
     largeTilesSpan: Int,
-    onToggleSize: (spec: TileSpec) -> Unit,
+    onResize: (operation: ResizeOperation) -> Unit,
 ) {
     items(
         count = cells.size,
@@ -464,7 +469,7 @@
                         index = index,
                         dragAndDropState = dragAndDropState,
                         selectionState = selectionState,
-                        onToggleSize = onToggleSize,
+                        onResize = onResize,
                         coroutineScope = coroutineScope,
                         bounceableInfo = cells.bounceableInfo(index, columns),
                         largeTilesSpan = largeTilesSpan,
@@ -482,7 +487,7 @@
     index: Int,
     dragAndDropState: DragAndDropState,
     selectionState: MutableSelectionState,
-    onToggleSize: (spec: TileSpec) -> Unit,
+    onResize: (operation: ResizeOperation) -> Unit,
     coroutineScope: CoroutineScope,
     largeTilesSpan: Int,
     bounceableInfo: BounceableInfo,
@@ -511,28 +516,47 @@
         selected = selectionState.selection?.tileSpec == cell.tile.tileSpec
     }
 
-    // Current base, min and max width of this tile
-    var tileWidths: TileWidths? by remember { mutableStateOf(null) }
-    val padding = with(LocalDensity.current) { TileArrangementPadding.roundToPx() }
+    val state = rememberResizingState(cell.tile.tileSpec, cell.isIcon)
+
+    val progress: () -> Float = {
+        if (selected) {
+            // If selected, return the manual progress from the drag
+            state.progress()
+        } else {
+            // Else, return the target progress for the tile format
+            if (cell.isIcon) 0f else 1f
+        }
+    }
+
+    if (!selected) {
+        // Update the draggable anchor state when the tile's size is not manually toggled
+        LaunchedEffect(cell.isIcon) { state.updateCurrentValue(cell.isIcon) }
+    } else {
+        // If the tile is selected, listen to new target values from the draggable anchor to toggle
+        // the tile's size
+        LaunchedEffect(state.temporaryResizeOperation) { onResize(state.temporaryResizeOperation) }
+        LaunchedEffect(state.finalResizeOperation) { onResize(state.finalResizeOperation) }
+    }
+
+    val totalPadding =
+        with(LocalDensity.current) { (largeTilesSpan - 1) * TileArrangementPadding.roundToPx() }
 
     ResizableTileContainer(
         selected = selected,
-        selectionState = selectionState,
+        state = state,
         selectionAlpha = { selectionAlpha },
         selectionColor = selectionColor,
-        tileWidths = { tileWidths },
         modifier =
             modifier
                 .height(TileHeight)
                 .fillMaxWidth()
                 .onSizeChanged {
                     // Grab the size before the bounceable to get the idle width
-                    val totalPadding = (largeTilesSpan - 1) * padding
                     val min =
                         if (cell.isIcon) it.width else (it.width - totalPadding) / largeTilesSpan
                     val max =
                         if (cell.isIcon) (it.width * largeTilesSpan) + totalPadding else it.width
-                    tileWidths = TileWidths(it.width, min, max)
+                    state.updateAnchors(min.toFloat(), max.toFloat())
                 }
                 .bounceable(
                     bounceable = currentBounceableInfo.bounceable,
@@ -552,7 +576,7 @@
                         listOf(
                             // TODO(b/367748260): Add final accessibility actions
                             CustomAccessibilityAction("Toggle size") {
-                                onToggleSize(cell.tile.tileSpec)
+                                onResize(FinalResizeOperation(cell.tile.tileSpec, !cell.isIcon))
                                 true
                             }
                         )
@@ -567,24 +591,7 @@
                 )
                 .tileBackground(colors.background)
         ) {
-            val targetValue = if (cell.isIcon) 0f else 1f
-            val animatedProgress = remember { Animatable(targetValue) }
-
-            val resizingState = selectionState.resizingState?.takeIf { selected }
-            LaunchedEffect(targetValue, resizingState) {
-                if (resizingState == null) {
-                    animatedProgress.animateTo(targetValue)
-                } else {
-                    snapshotFlow { resizingState.progression }
-                        .collectLatest { animatedProgress.snapTo(it) }
-                }
-            }
-
-            EditTile(
-                tile = cell.tile,
-                tileWidths = { tileWidths },
-                progress = { animatedProgress.value },
-            )
+            EditTile(tile = cell.tile, state = state, progress = progress)
         }
     }
 }
@@ -651,7 +658,7 @@
 @Composable
 fun EditTile(
     tile: EditTileViewModel,
-    tileWidths: () -> TileWidths?,
+    state: ResizingState,
     progress: () -> Float,
     colors: TileColors = EditModeTileDefaults.editTileColors(),
 ) {
@@ -661,12 +668,16 @@
         verticalAlignment = Alignment.CenterVertically,
         modifier =
             Modifier.layout { measurable, constraints ->
+                    val (min, max) = state.bounds
+                    val currentProgress = progress()
                     // Always display the tile using the large size and trust the parent composable
                     // to clip the content as needed. This stop the labels from being truncated.
-                    val width = tileWidths()?.max ?: constraints.maxWidth
+                    val width =
+                        max?.roundToInt()?.takeIf { it > constraints.maxWidth }
+                            ?: constraints.maxWidth
                     val placeable =
                         measurable.measure(constraints.copy(minWidth = width, maxWidth = width))
-                    val currentProgress = progress()
+
                     val startPadding =
                         if (currentProgress == 0f) {
                             // Find the center of the max width when the tile is icon only
@@ -675,7 +686,7 @@
                             // Find the center of the minimum width to hold the same position as the
                             // tile is resized.
                             val basePadding =
-                                tileWidths()?.min?.let { iconHorizontalCenter(it) } ?: 0f
+                                min?.let { iconHorizontalCenter(it.roundToInt()) } ?: 0f
                             // Large tiles, represented with a progress of 1f, have a 0.dp padding
                             basePadding * (1f - currentProgress)
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index 29ff171..8fd99a5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.qs.panels.ui.compose.bounceableInfo
 import com.android.systemui.qs.panels.ui.compose.rememberEditListState
 import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridViewModel
@@ -49,17 +50,14 @@
 class InfiniteGridLayout
 @Inject
 constructor(
+    private val detailsViewModel: DetailsViewModel,
     private val iconTilesViewModel: IconTilesViewModel,
     private val viewModelFactory: InfiniteGridViewModel.Factory,
     private val tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
 ) : PaginatableGridLayout {
 
     @Composable
-    override fun SceneScope.TileGrid(
-        tiles: List<TileViewModel>,
-        modifier: Modifier,
-        editModeStart: () -> Unit,
-    ) {
+    override fun SceneScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) {
         DisposableEffect(tiles) {
             val token = Any()
             tiles.forEach { it.startListening(token) }
@@ -104,6 +102,7 @@
                 tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider,
                 coroutineScope = scope,
                 bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
+                detailsViewModel = detailsViewModel,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index 0a80a19..c798e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -24,6 +24,7 @@
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
 import androidx.compose.foundation.combinedClickable
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Arrangement
@@ -77,10 +78,12 @@
 import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.flags.QsDetailedView
 import com.android.systemui.qs.panels.ui.compose.BounceableInfo
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel
+import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
 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
@@ -121,6 +124,7 @@
     bounceableInfo: BounceableInfo,
     tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
     modifier: Modifier = Modifier,
+    detailsViewModel: DetailsViewModel?,
 ) {
     val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
     val currentBounceableInfo by rememberUpdatedState(bounceableInfo)
@@ -163,12 +167,20 @@
                 .takeIf { uiState.handlesLongClick }
         TileContainer(
             onClick = {
-                tile.onClick(expandable)
-                hapticsViewModel?.setTileInteractionState(
-                    TileHapticsViewModel.TileInteractionState.CLICKED
-                )
-                if (uiState.accessibilityUiState.toggleableState != null) {
-                    coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() }
+                var hasDetails = false
+                if (QsDetailedView.isEnabled) {
+                    hasDetails = detailsViewModel?.onTileClicked(tile.spec) == true
+                }
+                if (!hasDetails) {
+                    // For those tile's who doesn't have a detailed view, process with their
+                    // `onClick` behavior.
+                    tile.onClick(expandable)
+                    hapticsViewModel?.setTileInteractionState(
+                        TileHapticsViewModel.TileInteractionState.CLICKED
+                    )
+                    if (uiState.accessibilityUiState.toggleableState != null) {
+                        coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() }
+                    }
                 }
             },
             onLongClick = longClick,
@@ -252,6 +264,28 @@
 }
 
 @Composable
+fun LargeStaticTile(uiState: TileUiState, modifier: Modifier = Modifier) {
+    val colors = TileDefaults.getColorForState(uiState = uiState, iconOnly = false)
+
+    Box(
+        modifier
+            .clip(TileDefaults.animateTileShape(state = uiState.state))
+            .background(colors.background)
+            .height(TileHeight)
+            .tilePadding()
+    ) {
+        LargeTileContent(
+            label = uiState.label,
+            secondaryLabel = "",
+            icon = getTileIcon(icon = uiState.icon),
+            sideDrawable = null,
+            colors = colors,
+            squishiness = { 1f },
+        )
+    }
+}
+
+@Composable
 private fun getTileIcon(icon: Supplier<QSTile.Icon?>): Icon {
     val context = LocalContext.current
     return icon.get()?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
index 1d36aee..c6c6dca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
@@ -27,11 +27,8 @@
 
 /** Creates the state of the current selected tile that is remembered across compositions. */
 @Composable
-fun rememberSelectionState(
-    onResize: (TileSpec) -> Unit,
-    onResizeEnd: (TileSpec) -> Unit,
-): MutableSelectionState {
-    return remember { MutableSelectionState(onResize, onResizeEnd) }
+fun rememberSelectionState(): MutableSelectionState {
+    return remember { MutableSelectionState() }
 }
 
 /**
@@ -41,47 +38,18 @@
 data class Selection(val tileSpec: TileSpec, val manual: Boolean)
 
 /** Holds the state of the current selection. */
-class MutableSelectionState(
-    val onResize: (TileSpec) -> Unit,
-    private val onResizeEnd: (TileSpec) -> Unit,
-) {
+class MutableSelectionState {
     private var _selection = mutableStateOf<Selection?>(null)
-    private var _resizingState = mutableStateOf<ResizingState?>(null)
 
     /** The [Selection] if a tile is selected, null if not. */
     val selection by _selection
 
-    /** The [ResizingState] of the selected tile is currently being resized, null if not. */
-    val resizingState by _resizingState
-
     fun select(tileSpec: TileSpec, manual: Boolean) {
         _selection.value = Selection(tileSpec, manual)
     }
 
     fun unSelect() {
         _selection.value = null
-        onResizingDragEnd()
-    }
-
-    fun onResizingDrag(offset: Float) {
-        _resizingState.value?.onDrag(offset)
-    }
-
-    fun onResizingDragStart(tileWidths: TileWidths) {
-        _selection.value?.let {
-            _resizingState.value = ResizingState(tileWidths) { onResize(it.tileSpec) }
-        }
-    }
-
-    fun onResizingDragEnd() {
-        _resizingState.value = null
-        _selection.value?.let {
-            onResizeEnd(it.tileSpec)
-
-            // Mark the selection as automatic in case the tile ends up moving to a different
-            // row with its new size.
-            _selection.value = it.copy(manual = false)
-        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
index 41c3de5..1113053 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
@@ -16,56 +16,82 @@
 
 package com.android.systemui.qs.panels.ui.compose.selection
 
+import androidx.compose.foundation.gestures.AnchoredDraggableState
+import androidx.compose.foundation.gestures.DraggableAnchors
+import androidx.compose.foundation.gestures.animateTo
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.setValue
-import com.android.systemui.qs.panels.ui.compose.selection.ResizingDefaults.RESIZING_THRESHOLD
+import androidx.compose.runtime.remember
+import com.android.systemui.qs.panels.ui.compose.selection.ResizingState.ResizeOperation.FinalResizeOperation
+import com.android.systemui.qs.panels.ui.compose.selection.ResizingState.ResizeOperation.TemporaryResizeOperation
+import com.android.systemui.qs.pipeline.shared.TileSpec
 
-class ResizingState(val widths: TileWidths, private val onResize: () -> Unit) {
-    /** Total drag offset of this resize operation. */
-    private var totalOffset by mutableFloatStateOf(0f)
+@Composable
+fun rememberResizingState(tileSpec: TileSpec, startsAsIcon: Boolean): ResizingState {
+    return remember(tileSpec) { ResizingState(tileSpec, startsAsIcon) }
+}
 
-    /** Width in pixels of the resizing tile. */
-    var width by mutableIntStateOf(widths.base)
+enum class QSDragAnchor {
+    Icon,
+    Large,
+}
 
-    /** Progression between icon (0) and large (1) sizes. */
-    val progression
-        get() = calculateProgression()
+class ResizingState(tileSpec: TileSpec, startsAsIcon: Boolean) {
+    val anchoredDraggableState =
+        AnchoredDraggableState(if (startsAsIcon) QSDragAnchor.Icon else QSDragAnchor.Large)
 
-    // Whether the tile is currently over the threshold and should be a large tile
-    private var passedThreshold: Boolean = passedThreshold(progression)
+    val bounds by derivedStateOf {
+        anchoredDraggableState.anchors.minPosition().takeIf { !it.isNaN() } to
+            anchoredDraggableState.anchors.maxPosition().takeIf { !it.isNaN() }
+    }
 
-    fun onDrag(offset: Float) {
-        totalOffset += offset
-        width = (widths.base + totalOffset).toInt().coerceIn(widths.min, widths.max)
+    val temporaryResizeOperation by derivedStateOf {
+        TemporaryResizeOperation(
+            tileSpec,
+            toIcon = anchoredDraggableState.currentValue == QSDragAnchor.Icon,
+        )
+    }
 
-        passedThreshold(progression).let {
-            // Resize if we went over the threshold
-            if (passedThreshold != it) {
-                passedThreshold = it
-                onResize()
+    val finalResizeOperation by derivedStateOf {
+        FinalResizeOperation(
+            tileSpec,
+            toIcon = anchoredDraggableState.settledValue == QSDragAnchor.Icon,
+        )
+    }
+
+    fun updateAnchors(min: Float, max: Float) {
+        anchoredDraggableState.updateAnchors(
+            DraggableAnchors {
+                QSDragAnchor.Icon at min
+                QSDragAnchor.Large at max
             }
-        }
+        )
     }
 
-    private fun passedThreshold(progression: Float): Boolean {
-        return progression >= RESIZING_THRESHOLD
+    suspend fun updateCurrentValue(isIcon: Boolean) {
+        anchoredDraggableState.animateTo(if (isIcon) QSDragAnchor.Icon else QSDragAnchor.Large)
     }
 
-    /** The progression of the resizing tile between an icon tile (0f) and a large tile (1f) */
-    private fun calculateProgression(): Float {
-        return ((width - widths.min) / (widths.max - widths.min).toFloat()).coerceIn(0f, 1f)
+    suspend fun toggleCurrentValue() {
+        val isIcon = anchoredDraggableState.currentValue == QSDragAnchor.Icon
+        updateCurrentValue(!isIcon)
     }
-}
 
-/** Holds the width of a tile as well as its min and max widths */
-data class TileWidths(val base: Int, val min: Int, val max: Int) {
-    init {
-        check(max > min) { "The max width needs to be larger than the min width." }
+    fun progress(): Float = anchoredDraggableState.progress(QSDragAnchor.Icon, QSDragAnchor.Large)
+
+    /**
+     * Represents a resizing operation for a tile.
+     *
+     * @property spec The tile's [TileSpec]
+     * @property toIcon The new size for the tile.
+     */
+    sealed class ResizeOperation private constructor(val spec: TileSpec, val toIcon: Boolean) {
+        /** A temporary resizing operation, used while a resizing movement is in motion. */
+        class TemporaryResizeOperation(spec: TileSpec, toIcon: Boolean) :
+            ResizeOperation(spec, toIcon)
+
+        /** A final resizing operation, used while a resizing movement is done. */
+        class FinalResizeOperation(spec: TileSpec, toIcon: Boolean) : ResizeOperation(spec, toIcon)
     }
 }
-
-private object ResizingDefaults {
-    const val RESIZING_THRESHOLD = .25f
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
index 8a345ce..c1545e1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
@@ -19,10 +19,11 @@
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.core.animateIntAsState
 import androidx.compose.animation.core.spring
 import androidx.compose.foundation.Canvas
-import androidx.compose.foundation.gestures.detectHorizontalDragGestures
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.anchoredDraggable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.size
@@ -30,7 +31,9 @@
 import androidx.compose.material3.LocalMinimumInteractiveComponentSize
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawWithContent
@@ -40,16 +43,16 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.graphics.drawscope.Stroke
-import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.layout.layout
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.toSize
 import androidx.compose.ui.zIndex
-import com.android.compose.modifiers.thenIf
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
 import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.ResizingDotSize
 import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.SelectedBorderWidth
+import kotlin.math.roundToInt
+import kotlinx.coroutines.launch
 
 /**
  * Places a dot to handle resizing drag events. Use this on tiles to resize.
@@ -58,31 +61,24 @@
  * selected.
  *
  * @param selected whether resizing drag events should be handled
- * @param selectionState the [MutableSelectionState] on the grid
+ * @param state the [ResizingState] for the tile
  * @param selectionAlpha the animated value for the dot and border alpha
  * @param selectionColor the [Color] of the dot and border
- * @param tileWidths the [TileWidths] of the selected tile
  */
 @Composable
 fun ResizableTileContainer(
     selected: Boolean,
-    selectionState: MutableSelectionState,
+    state: ResizingState,
     selectionAlpha: () -> Float,
     selectionColor: Color,
-    tileWidths: () -> TileWidths?,
     modifier: Modifier = Modifier,
     content: @Composable BoxScope.() -> Unit = {},
 ) {
-    Box(
-        modifier
-            .resizable(selected, selectionState, tileWidths)
-            .selectionBorder(selectionColor, selectionAlpha)
-    ) {
+    Box(modifier.resizable(selected, state).selectionBorder(selectionColor, selectionAlpha)) {
         content()
         ResizingHandle(
             enabled = selected,
-            selectionState = selectionState,
-            tileWidths = tileWidths,
+            state = state,
             modifier =
                 // Higher zIndex to make sure the handle is drawn above the content
                 Modifier.zIndex(2f),
@@ -91,15 +87,11 @@
 }
 
 @Composable
-private fun ResizingHandle(
-    enabled: Boolean,
-    selectionState: MutableSelectionState,
-    tileWidths: () -> TileWidths?,
-    modifier: Modifier = Modifier,
-) {
+private fun ResizingHandle(enabled: Boolean, state: ResizingState, modifier: Modifier = Modifier) {
     // Manually creating the touch target around the resizing dot to ensure that the next tile
     // does not receive the touch input accidentally.
     val minTouchTargetSize = LocalMinimumInteractiveComponentSize.current
+    val scope = rememberCoroutineScope()
     Box(
         modifier
             .layout { measurable, constraints ->
@@ -112,20 +104,14 @@
                     )
                 }
             }
-            .thenIf(enabled) {
-                Modifier.systemGestureExclusion { Rect(Offset.Zero, it.size.toSize()) }
-                    .pointerInput(Unit) {
-                        detectHorizontalDragGestures(
-                            onHorizontalDrag = { _, offset ->
-                                selectionState.onResizingDrag(offset)
-                            },
-                            onDragStart = {
-                                tileWidths()?.let { selectionState.onResizingDragStart(it) }
-                            },
-                            onDragEnd = selectionState::onResizingDragEnd,
-                            onDragCancel = selectionState::onResizingDragEnd,
-                        )
-                    }
+            .systemGestureExclusion { Rect(Offset.Zero, it.size.toSize()) }
+            .anchoredDraggable(
+                enabled = enabled,
+                state = state.anchoredDraggableState,
+                orientation = Orientation.Horizontal,
+            )
+            .clickable(enabled = enabled, interactionSource = null, indication = null) {
+                scope.launch { state.toggleCurrentValue() }
             }
     ) {
         ResizingDot(enabled = enabled, modifier = Modifier.align(Alignment.Center))
@@ -165,23 +151,15 @@
 }
 
 @Composable
-private fun Modifier.resizable(
-    selected: Boolean,
-    selectionState: MutableSelectionState,
-    tileWidths: () -> TileWidths?,
-): Modifier {
+private fun Modifier.resizable(selected: Boolean, state: ResizingState): Modifier {
     if (!selected) return zIndex(1f)
 
-    // Animated diff between the current width and the resized width of the tile. We can't use
-    // animateContentSize here as the tile is sometimes unbounded.
-    val remainingOffset by
-        animateIntAsState(
-            selectionState.resizingState?.let { tileWidths()?.base?.minus(it.width) ?: 0 } ?: 0,
-            label = "QSEditTileWidthOffset",
-        )
     return zIndex(2f).layout { measurable, constraints ->
+        val isIdle by derivedStateOf { state.progress().let { it == 0f || it == 1f } }
         // Grab the width from the resizing state if a resize is in progress
-        val width = selectionState.resizingState?.width ?: (constraints.maxWidth - remainingOffset)
+        val width =
+            state.anchoredDraggableState.requireOffset().roundToInt().takeIf { !isIdle }
+                ?: constraints.maxWidth
         val placeable = measurable.measure(constraints.copy(minWidth = width, maxWidth = width))
         layout(constraints.maxWidth, placeable.height) { placeable.place(0, 0) }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt
new file mode 100644
index 0000000..d8361f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.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.qs.panels.ui.viewmodel
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.qs.TileDetailsViewModel
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+@SysUISingleton
+class DetailsViewModel
+@Inject constructor(val currentTilesInteractor: CurrentTilesInteractor) {
+
+    /**
+     * The current active [TileDetailsViewModel]. If it's `null`, it means the qs overlay is not
+     * showing any details view. It changes when [onTileClicked] and [closeDetailedView].
+     */
+    private val _activeTileDetails = mutableStateOf<TileDetailsViewModel?>(null)
+    val activeTileDetails by _activeTileDetails
+
+    /**
+     * Update the active [TileDetailsViewModel] to `null`.
+     * @see activeTileDetails
+     */
+    fun closeDetailedView() {
+        _activeTileDetails.value = null
+    }
+
+    /**
+     * Update the active [TileDetailsViewModel] to the `spec`'s corresponding view model.
+     * Return if the [TileDetailsViewModel] is successfully found.
+     * @see activeTileDetails
+     */
+    fun onTileClicked(spec: TileSpec?): Boolean {
+        if (spec == null) {
+            _activeTileDetails.value = null
+            return false
+        }
+
+        _activeTileDetails.value = currentTilesInteractor
+            .currentQSTiles
+            .firstOrNull { it.tileSpec == spec.spec }
+            ?.detailsViewModel
+
+        return _activeTileDetails.value != null
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModel.kt
new file mode 100644
index 0000000..b033473
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModel.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.ui.viewmodel
+
+import com.android.systemui.classifier.domain.interactor.FalsingInteractor
+import com.android.systemui.plugins.FalsingManager
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class EditModeButtonViewModel
+@AssistedInject
+constructor(
+    private val editModeViewModel: EditModeViewModel,
+    private val falsingInteractor: FalsingInteractor,
+) {
+
+    fun onButtonClick() {
+        if (!falsingInteractor.isFalseTap(FalsingManager.LOW_PENALTY)) {
+            editModeViewModel.startEditing()
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): EditModeButtonViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
index bff330b..4a18872 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.qs.panels.ui.viewmodel
 
 import androidx.compose.runtime.getValue
+import com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE
+import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.development.ui.viewmodel.BuildNumberViewModel
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.Hydrator
@@ -36,6 +38,8 @@
     paginatedGridInteractor: PaginatedGridInteractor,
     inFirstPageViewModel: InFirstPageViewModel,
     val buildNumberViewModelFactory: BuildNumberViewModel.Factory,
+    val editModeButtonViewModelFactory: EditModeButtonViewModel.Factory,
+    private val falsingInteractor: FalsingInteractor,
 ) : IconTilesViewModel by iconTilesViewModel, ExclusiveActivatable() {
 
     private val hydrator = Hydrator("PaginatedGridViewModel")
@@ -53,6 +57,10 @@
     val columns: Int
         get() = columnsWithMediaViewModel.columns
 
+    fun registerSideSwipeGesture() {
+        falsingInteractor.isFalseTouch(QS_SWIPE_SIDE)
+    }
+
     override suspend fun onActivated(): Nothing {
         coroutineScope {
             launch { hydrator.activate() }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt
index fe0a69b..d4ac901 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt
@@ -2,9 +2,9 @@
 
 import android.content.res.Resources
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 interface DefaultTilesRepository {
@@ -14,9 +14,7 @@
 @SysUISingleton
 class DefaultTilesQSHostRepository
 @Inject
-constructor(
-    @Main private val resources: Resources,
-) : DefaultTilesRepository {
+constructor(@ShadeDisplayAware private val resources: Resources) : DefaultTilesRepository {
     override val defaultTiles: List<TileSpec>
         get() =
             QSHost.getDefaultSpecs(resources).map(TileSpec::create).filter {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt
index 3a005c0..40720a2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt
@@ -18,8 +18,8 @@
 
 import android.content.res.Resources
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /**
@@ -35,7 +35,7 @@
  * creation, as it's not expected to change.
  */
 @SysUISingleton
-class MinimumTilesResourceRepository @Inject constructor(@Main resources: Resources) :
+class MinimumTilesResourceRepository @Inject constructor(@ShadeDisplayAware resources: Resources) :
     MinimumTilesRepository {
     override val minNumberOfTiles: Int =
         resources.getInteger(R.integer.quick_settings_min_num_tiles)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index d94e7cf..c6751b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -20,12 +20,12 @@
 import android.content.res.Resources
 import android.util.SparseArray
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.pipeline.data.model.RestoreData
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import com.android.systemui.res.R
 import com.android.systemui.retail.data.repository.RetailModeRepository
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -92,7 +92,7 @@
 class TileSpecSettingsRepository
 @Inject
 constructor(
-    @Main private val resources: Resources,
+    @ShadeDisplayAware private val resources: Resources,
     private val logger: QSPipelineLogger,
     private val retailModeRepository: RetailModeRepository,
     private val userTileSpecRepositoryFactory: UserTileSpecRepository.Factory,
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 9abc494..464eeda 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -31,6 +31,7 @@
 
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
@@ -68,6 +69,7 @@
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.SideLabelTileLayout;
+import com.android.systemui.qs.flags.QsInCompose;
 import com.android.systemui.qs.logging.QSLogger;
 
 import java.io.PrintWriter;
@@ -535,6 +537,23 @@
         }
     }
 
+    protected Icon maybeLoadResourceIcon(int id) {
+        return maybeLoadResourceIcon(id, mContext);
+    }
+
+    /**
+     * Returns the {@link QSTile.Icon} for the resource ID, optionally loading the drawable if
+     * {@link QsInCompose#isEnabled()} is true.
+     */
+    @SuppressLint("UseCompatLoadingForDrawables")
+    public static Icon maybeLoadResourceIcon(int id, Context context) {
+        if (QsInCompose.isEnabled()) {
+            return new DrawableIconWithRes(context.getDrawable(id), id);
+        } else {
+            return ResourceIcon.get(id);
+        }
+    }
+
     @Override
     public String getMetricsSpec() {
         return mTileSpec;
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 1fb76f1..b7ebce2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -27,6 +27,7 @@
 import android.graphics.Color
 import android.graphics.PorterDuff
 import android.graphics.Rect
+import android.graphics.Typeface
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.GradientDrawable
 import android.graphics.drawable.LayerDrawable
@@ -308,6 +309,14 @@
         }
         setLabelColor(getLabelColorForState(QSTile.State.DEFAULT_STATE))
         setSecondaryLabelColor(getSecondaryLabelColorForState(QSTile.State.DEFAULT_STATE))
+
+        if (Flags.gsfQuickSettings()) {
+            label.apply {
+                typeface = Typeface.create("gsf-title-small-emphasized", Typeface.NORMAL)
+            }
+            secondaryLabel.apply { typeface = Typeface.create("gsf-label-medium", Typeface.NORMAL) }
+        }
+
         addView(labelContainer)
     }
 
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 71b69c9..bb818fa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -160,7 +160,7 @@
         final boolean airplaneMode = value != 0;
         state.value = airplaneMode;
         state.label = mContext.getString(R.string.airplane_mode);
-        state.icon = ResourceIcon.get(state.value
+        state.icon = maybeLoadResourceIcon(state.value
                 ? R.drawable.qs_airplane_icon_on : R.drawable.qs_airplane_icon_off);
         state.state = airplaneMode ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.contentDescription = state.label;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
index 73d991f..9efdd98 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -9,6 +9,7 @@
 import android.service.quicksettings.Tile
 import android.text.TextUtils
 import android.text.format.DateFormat
+import android.widget.Button
 import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.MetricsLogger
@@ -42,26 +43,28 @@
     activityStarter: ActivityStarter,
     qsLogger: QSLogger,
     private val userTracker: UserTracker,
-    nextAlarmController: NextAlarmController
-) : QSTileImpl<QSTile.State>(
-    host,
-    uiEventLogger,
-    backgroundLooper,
-    mainHandler,
-    falsingManager,
-    metricsLogger,
-    statusBarStateController,
-    activityStarter,
-    qsLogger
-) {
+    nextAlarmController: NextAlarmController,
+) :
+    QSTileImpl<QSTile.State>(
+        host,
+        uiEventLogger,
+        backgroundLooper,
+        mainHandler,
+        falsingManager,
+        metricsLogger,
+        statusBarStateController,
+        activityStarter,
+        qsLogger,
+    ) {
 
     private var lastAlarmInfo: AlarmManager.AlarmClockInfo? = null
-    private val icon = ResourceIcon.get(R.drawable.ic_alarm)
+    private var icon: QSTile.Icon? = null
     @VisibleForTesting internal val defaultIntent = Intent(AlarmClock.ACTION_SHOW_ALARMS)
-    private val callback = NextAlarmController.NextAlarmChangeCallback { nextAlarm ->
-        lastAlarmInfo = nextAlarm
-        refreshState()
-    }
+    private val callback =
+        NextAlarmController.NextAlarmChangeCallback { nextAlarm ->
+            lastAlarmInfo = nextAlarm
+            refreshState()
+        }
 
     init {
         nextAlarmController.observe(this, callback)
@@ -70,6 +73,7 @@
     override fun newTileState(): QSTile.State {
         return QSTile.State().apply {
             handlesLongClick = false
+            expandedAccessibilityClassName = Button::class.java.name
         }
     }
 
@@ -82,21 +86,28 @@
         if (pendingIntent != null) {
             mActivityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
         } else {
-            mActivityStarter.postStartActivityDismissingKeyguard(defaultIntent, 0,
-                    animationController)
+            mActivityStarter.postStartActivityDismissingKeyguard(
+                defaultIntent,
+                0,
+                animationController,
+            )
         }
     }
 
     override fun handleUpdateState(state: QSTile.State, arg: Any?) {
+        if (icon == null) {
+            icon = maybeLoadResourceIcon(R.drawable.ic_alarm)
+        }
         state.icon = icon
         state.label = tileLabel
         lastAlarmInfo?.let {
             state.secondaryLabel = formatNextAlarm(it)
             state.state = Tile.STATE_ACTIVE
-        } ?: run {
-            state.secondaryLabel = mContext.getString(R.string.qs_alarm_tile_no_alarm)
-            state.state = Tile.STATE_INACTIVE
         }
+            ?: run {
+                state.secondaryLabel = mContext.getString(R.string.qs_alarm_tile_no_alarm)
+                state.state = Tile.STATE_INACTIVE
+            }
         state.contentDescription = TextUtils.concat(state.label, ", ", state.secondaryLabel)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index 7c0ce4c..9df4e42 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -147,9 +147,8 @@
     protected void handleUpdateState(BooleanState state, Object arg) {
         state.state = mPluggedIn ? Tile.STATE_UNAVAILABLE
                 : mPowerSave ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
-        state.icon = ResourceIcon.get(mPowerSave
-                ? R.drawable.qs_battery_saver_icon_on
-                : R.drawable.qs_battery_saver_icon_off);
+        state.icon = maybeLoadResourceIcon(mPowerSave
+                ? R.drawable.qs_battery_saver_icon_on : R.drawable.qs_battery_saver_icon_off);
         state.label = mContext.getString(R.string.battery_detail_switch_title);
         state.secondaryLabel = "";
         state.contentDescription = state.label;
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 7bff827..7eb0aaa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -59,13 +59,13 @@
 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;
 
 import javax.inject.Inject;
 
-import kotlinx.coroutines.Job;
-
 /** Quick settings tile: Bluetooth **/
 public class BluetoothTile extends QSTileImpl<BooleanState> {
 
@@ -201,7 +201,7 @@
 
         if (enabled) {
             if (connected) {
-                state.icon = ResourceIcon.get(R.drawable.qs_bluetooth_icon_on);
+                state.icon = maybeLoadResourceIcon(R.drawable.qs_bluetooth_icon_on);
                 if (!TextUtils.isEmpty(mController.getConnectedDeviceName())) {
                     state.label = mController.getConnectedDeviceName();
                 }
@@ -209,17 +209,15 @@
                         mContext.getString(R.string.accessibility_bluetooth_name, state.label)
                                 + ", " + state.secondaryLabel;
             } else if (state.isTransient) {
-                state.icon = ResourceIcon.get(
-                        R.drawable.qs_bluetooth_icon_search);
+                state.icon = maybeLoadResourceIcon(R.drawable.qs_bluetooth_icon_search);
                 state.stateDescription = state.secondaryLabel;
             } else {
-                state.icon =
-                        ResourceIcon.get(R.drawable.qs_bluetooth_icon_off);
+                state.icon = maybeLoadResourceIcon(R.drawable.qs_bluetooth_icon_off);
                 state.stateDescription = mContext.getString(R.string.accessibility_not_connected);
             }
             state.state = Tile.STATE_ACTIVE;
         } else {
-            state.icon = ResourceIcon.get(R.drawable.qs_bluetooth_icon_off);
+            state.icon = maybeLoadResourceIcon(R.drawable.qs_bluetooth_icon_off);
             state.state = Tile.STATE_INACTIVE;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 8a72e8d..30c2adf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -24,6 +24,7 @@
 import android.app.Dialog;
 import android.content.Intent;
 import android.media.MediaRouter.RouteInfo;
+import android.media.projection.StopReason;
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings;
@@ -183,7 +184,7 @@
                 });
             }
         } else {
-            mController.stopCasting(activeDevices.get(0));
+            mController.stopCasting(activeDevices.get(0), StopReason.STOP_QS_TILE);
         }
     }
 
@@ -290,8 +291,8 @@
         if (connecting && !state.value) {
             state.secondaryLabel = mContext.getString(R.string.quick_settings_connecting);
         }
-        state.icon = ResourceIcon.get(state.value ? R.drawable.ic_cast_connected
-                : R.drawable.ic_cast);
+        state.icon = maybeLoadResourceIcon(state.value
+                ? R.drawable.ic_cast_connected : R.drawable.ic_cast);
         if (canCastToNetwork() || state.value) {
             state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
             if (!state.value) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
index 871973df..c2e609d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
@@ -50,7 +50,8 @@
 
     public static final String TILE_SPEC = "color_correction";
 
-    private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_color_correction);
+    @Nullable
+    private Icon mIcon = null;
     private final UserSettingObserver mSetting;
 
     @Inject
@@ -122,6 +123,9 @@
     protected void handleUpdateState(BooleanState state, Object arg) {
         final int value = arg instanceof Integer ? (Integer) arg : mSetting.getValue();
         final boolean enabled = value != 0;
+        if (mIcon == null) {
+            mIcon = maybeLoadResourceIcon(R.drawable.ic_qs_color_correction);
+        }
         state.value = enabled;
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.label = mContext.getString(R.string.quick_settings_color_correction_label);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 5896910..ce80133 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -124,7 +124,7 @@
         state.value = enabled;
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.label = mContext.getString(R.string.quick_settings_inversion_label);
-        state.icon = ResourceIcon.get(state.value
+        state.icon = maybeLoadResourceIcon(state.value
                 ? R.drawable.qs_invert_colors_icon_on
                 : R.drawable.qs_invert_colors_icon_off);
         state.expandedAccessibilityClassName = Switch.class.getName();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 7760943..deeef55 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -147,7 +147,7 @@
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.label = mContext.getString(R.string.data_saver);
         state.contentDescription = state.label;
-        state.icon = ResourceIcon.get(state.value ? R.drawable.qs_data_saver_icon_on
+        state.icon = maybeLoadResourceIcon(state.value ? R.drawable.qs_data_saver_icon_on
                 : R.drawable.qs_data_saver_icon_off);
         state.expandedAccessibilityClassName = Switch.class.getName();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index cc8a734..7213f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (C) 2021 The Android Open Source Project
  *
@@ -22,10 +21,9 @@
 import android.os.Handler
 import android.os.Looper
 import android.service.quicksettings.Tile
-import androidx.annotation.VisibleForTesting
+import android.widget.Button
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.dagger.ControlsComponent
@@ -43,10 +41,13 @@
 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 java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
 
-class DeviceControlsTile @Inject constructor(
+class DeviceControlsTile
+@Inject
+constructor(
     host: QSHost,
     uiEventLogger: QsEventLogger,
     @Background backgroundLooper: Looper,
@@ -56,32 +57,34 @@
     statusBarStateController: StatusBarStateController,
     activityStarter: ActivityStarter,
     qsLogger: QSLogger,
-    private val controlsComponent: ControlsComponent
-) : QSTileImpl<QSTile.State>(
-    host,
-    uiEventLogger,
-    backgroundLooper,
-    mainHandler,
-    falsingManager,
-    metricsLogger,
-    statusBarStateController,
-    activityStarter,
-    qsLogger
-) {
+    private val controlsComponent: ControlsComponent,
+) :
+    QSTileImpl<QSTile.State>(
+        host,
+        uiEventLogger,
+        backgroundLooper,
+        mainHandler,
+        falsingManager,
+        metricsLogger,
+        statusBarStateController,
+        activityStarter,
+        qsLogger,
+    ) {
 
     private var hasControlsApps = AtomicBoolean(false)
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    val icon: QSTile.Icon
-        get() = ResourceIcon.get(controlsComponent.getTileImageId())
+    private var icon: QSTile.Icon? = null
 
-    private val listingCallback = object : ControlsListingController.ControlsListingCallback {
-        override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
-            if (hasControlsApps.compareAndSet(serviceInfos.isEmpty(), serviceInfos.isNotEmpty())) {
-                refreshState()
+    private val listingCallback =
+        object : ControlsListingController.ControlsListingCallback {
+            override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+                if (
+                    hasControlsApps.compareAndSet(serviceInfos.isEmpty(), serviceInfos.isNotEmpty())
+                ) {
+                    refreshState()
+                }
             }
         }
-    }
 
     init {
         controlsComponent.getControlsListingController().ifPresent {
@@ -105,15 +108,19 @@
             return
         }
 
-        val intent = Intent().apply {
-            component = ComponentName(mContext, controlsComponent.getControlsUiController().get()
-                    .resolveActivity())
-            addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
-            putExtra(ControlsUiController.EXTRA_ANIMATE, true)
-        }
+        val intent =
+            Intent().apply {
+                component =
+                    ComponentName(
+                        mContext,
+                        controlsComponent.getControlsUiController().get().resolveActivity(),
+                    )
+                addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+                putExtra(ControlsUiController.EXTRA_ANIMATE, true)
+            }
         val animationController =
             expandable?.activityTransitionController(
-                    InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
+                InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
             )
 
         mUiHandler.post {
@@ -130,17 +137,23 @@
     override fun handleUpdateState(state: QSTile.State, arg: Any?) {
         state.label = tileLabel
         state.contentDescription = state.label
+        if (icon == null) {
+            icon = maybeLoadResourceIcon(controlsComponent.getTileImageId())
+        }
         state.icon = icon
         if (controlsComponent.isEnabled() && hasControlsApps.get()) {
             if (controlsComponent.getVisibility() == AVAILABLE) {
-                val selection = controlsComponent
-                        .getControlsController().get().getPreferredSelection()
-                state.state = if (selection is SelectedItem.StructureItem &&
-                        selection.structure.controls.isEmpty()) {
-                    Tile.STATE_INACTIVE
-                } else {
-                    Tile.STATE_ACTIVE
-                }
+                val selection =
+                    controlsComponent.getControlsController().get().getPreferredSelection()
+                state.state =
+                    if (
+                        selection is SelectedItem.StructureItem &&
+                            selection.structure.controls.isEmpty()
+                    ) {
+                        Tile.STATE_INACTIVE
+                    } else {
+                        Tile.STATE_ACTIVE
+                    }
                 val label = selection.name
                 state.secondaryLabel = if (label == tileLabel) null else label
             } else {
@@ -151,6 +164,7 @@
         } else {
             state.state = Tile.STATE_UNAVAILABLE
         }
+        state.expandedAccessibilityClassName = Button::class.java.name
     }
 
     override fun getMetricsCategory(): Int {
@@ -170,4 +184,4 @@
     companion object {
         const val TILE_SPEC = "controls"
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index ad76b4f..04f0b87 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -229,7 +229,7 @@
         state.dualTarget = true;
         state.value = newValue;
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
-        state.icon = ResourceIcon.get(state.value
+        state.icon = maybeLoadResourceIcon(state.value
                 ? R.drawable.qs_dnd_icon_on
                 : R.drawable.qs_dnd_icon_off);
         state.label = getTileLabel();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index 0d3d980..e37ed16 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -32,6 +32,7 @@
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
 import android.util.Log;
+import android.widget.Switch;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
@@ -64,9 +65,6 @@
     public static final String TILE_SPEC = "dream";
 
     private static final String LOG_TAG = "QSDream";
-    // TODO: consider 1 animated icon instead
-    private final Icon mIconDocked = ResourceIcon.get(R.drawable.ic_qs_screen_saver);
-    private final Icon mIconUndocked = ResourceIcon.get(R.drawable.ic_qs_screen_saver_undocked);
     private final IDreamManager mDreamManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final UserSettingObserver mEnabledSettingObserver;
@@ -170,13 +168,16 @@
         state.label = getTileLabel();
         state.secondaryLabel = getActiveDreamName();
         state.contentDescription = getContentDescription(state.secondaryLabel);
-        state.icon = mIsDocked ? mIconDocked : mIconUndocked;
+        // TODO: consider 1 animated icon instead
+        state.icon = maybeLoadResourceIcon(mIsDocked
+                ? R.drawable.ic_qs_screen_saver : R.drawable.ic_qs_screen_saver_undocked);
 
         if (getActiveDream() == null || !isScreensaverEnabled()) {
             state.state = Tile.STATE_UNAVAILABLE;
         } else {
             state.state = isDreaming() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         }
+        state.expandedAccessibilityClassName = Switch.class.getName();
     }
 
     @Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 848ff3c..2b127d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -128,7 +128,7 @@
                     R.string.quick_settings_flashlight_camera_in_use);
             state.stateDescription = state.secondaryLabel;
             state.state = Tile.STATE_UNAVAILABLE;
-            state.icon = ResourceIcon.get(R.drawable.qs_flashlight_icon_off);
+            state.icon = maybeLoadResourceIcon(R.drawable.qs_flashlight_icon_off);
             return;
         }
         if (arg instanceof Boolean) {
@@ -143,7 +143,7 @@
         state.contentDescription = mContext.getString(R.string.quick_settings_flashlight_label);
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
-        state.icon = ResourceIcon.get(state.value
+        state.icon = maybeLoadResourceIcon(state.value
                 ? R.drawable.qs_flashlight_icon_on : R.drawable.qs_flashlight_icon_off);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index 7606293..43e84a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -68,7 +68,7 @@
         activityStarter,
         qsLogger,
     ) {
-    private val icon = ResourceIcon.get(R.drawable.ic_qs_font_scaling)
+    private var icon: QSTile.Icon? = null
 
     override fun newTileState(): QSTile.State {
         return QSTile.State()
@@ -108,6 +108,9 @@
     }
 
     override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
+        if (icon == null) {
+            icon = maybeLoadResourceIcon(R.drawable.ic_qs_font_scaling)
+        }
         state?.label = mContext.getString(R.string.quick_settings_font_scaling_label)
         state?.icon = icon
         state?.contentDescription = state?.label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
index f723ff2..74563ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
@@ -24,6 +24,7 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.service.quicksettings.Tile;
+import android.widget.Button;
 
 import androidx.annotation.Nullable;
 
@@ -106,7 +107,7 @@
         checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_BLUETOOTH);
 
         state.label = mContext.getString(R.string.quick_settings_hearing_devices_label);
-        state.icon = ResourceIcon.get(R.drawable.qs_hearing_devices_icon);
+        state.icon = maybeLoadResourceIcon(R.drawable.qs_hearing_devices_icon);
         state.forceExpandIcon = true;
 
         boolean isBonded = mDevicesChecker.isAnyPairedHearingDevice();
@@ -124,6 +125,7 @@
             state.state = Tile.STATE_INACTIVE;
             state.secondaryLabel = "";
         }
+        state.expandedAccessibilityClassName = Button.class.getName();
     }
 
     @Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index ea3993e..03bbbd7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -151,10 +151,10 @@
         state.label = mContext.getString(R.string.quick_settings_hotspot_label);
         state.isTransient = isTransient;
         if (state.isTransient) {
-            state.icon = ResourceIcon.get(
+            state.icon = maybeLoadResourceIcon(
                     R.drawable.qs_hotspot_icon_search);
         } else {
-            state.icon = ResourceIcon.get(state.value
+            state.icon = maybeLoadResourceIcon(state.value
                     ? R.drawable.qs_hotspot_icon_on : R.drawable.qs_hotspot_icon_off);
         }
         state.expandedAccessibilityClassName = Switch.class.getName();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 02f6f80..0a59529 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -30,7 +30,7 @@
 import android.text.Html;
 import android.text.TextUtils;
 import android.util.Log;
-import android.widget.Switch;
+import android.widget.Button;
 
 import androidx.annotation.Nullable;
 
@@ -136,7 +136,7 @@
     }
 
     @Override
-    public void secondaryClick(@Nullable Expandable expandable) {
+    public void handleSecondaryClick(@Nullable Expandable expandable) {
         // TODO(b/358352265): Figure out the correct action for the secondary click
         // Toggle Wifi
         mWifiStateWorker.setWifiEnabled(!mWifiStateWorker.isWifiEnabled());
@@ -529,10 +529,10 @@
         if (cb.mAirplaneModeEnabled) {
             if (!state.value) {
                 state.state = Tile.STATE_INACTIVE;
-                state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
+                state.icon = maybeLoadResourceIcon(R.drawable.ic_qs_no_internet_unavailable);
                 state.secondaryLabel = r.getString(R.string.status_bar_airplane);
             } else if (!wifiConnected) {
-                state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
+                state.icon = maybeLoadResourceIcon(R.drawable.ic_qs_no_internet_unavailable);
                 if (cb.mNoNetworksAvailable) {
                     state.secondaryLabel =
                             r.getString(R.string.quick_settings_networks_unavailable);
@@ -541,28 +541,28 @@
                             r.getString(R.string.quick_settings_networks_available);
                 }
             } else {
-                state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
+                state.icon = maybeLoadResourceIcon(cb.mWifiSignalIconId);
             }
         } else if (cb.mNoDefaultNetwork) {
             if (cb.mNoNetworksAvailable || !cb.mEnabled) {
-                state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
+                state.icon = maybeLoadResourceIcon(R.drawable.ic_qs_no_internet_unavailable);
                 state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
             } else {
-                state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_available);
+                state.icon = maybeLoadResourceIcon(R.drawable.ic_qs_no_internet_available);
                 state.secondaryLabel = r.getString(R.string.quick_settings_networks_available);
             }
         } else if (cb.mIsTransient) {
-            state.icon = ResourceIcon.get(
+            state.icon = maybeLoadResourceIcon(
                 com.android.internal.R.drawable.ic_signal_wifi_transient_animation);
         } else if (!state.value) {
             state.state = Tile.STATE_INACTIVE;
-            state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_DISABLED);
+            state.icon = maybeLoadResourceIcon(WifiIcons.QS_WIFI_DISABLED);
         } else if (wifiConnected) {
-            state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
+            state.icon = maybeLoadResourceIcon(cb.mWifiSignalIconId);
         } else if (wifiNotConnected) {
-            state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK);
+            state.icon = maybeLoadResourceIcon(WifiIcons.QS_WIFI_NO_NETWORK);
         } else {
-            state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK);
+            state.icon = maybeLoadResourceIcon(WifiIcons.QS_WIFI_NO_NETWORK);
         }
         minimalContentDescription.append(
             mContext.getString(R.string.quick_settings_internet_label)).append(",");
@@ -577,7 +577,7 @@
         state.contentDescription = minimalContentDescription.toString();
         state.dualLabelContentDescription = r.getString(
                 R.string.accessibility_quick_settings_open_settings, getTileLabel());
-        state.expandedAccessibilityClassName = Switch.class.getName();
+        state.expandedAccessibilityClassName = Button.class.getName();
         if (DEBUG) {
             Log.d(TAG, "handleUpdateWifiState: " + "BooleanState = " + state.toString());
         }
@@ -594,18 +594,18 @@
         boolean mobileDataEnabled = mDataController.isMobileDataSupported()
                 && mDataController.isMobileDataEnabled();
         state.value = mobileDataEnabled;
-        state.expandedAccessibilityClassName = Switch.class.getName();
+        state.expandedAccessibilityClassName = Button.class.getName();
 
         if (cb.mAirplaneModeEnabled && cb.mQsTypeIcon != TelephonyIcons.ICON_CWF) {
             state.state = Tile.STATE_INACTIVE;
-            state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
+            state.icon = maybeLoadResourceIcon(R.drawable.ic_qs_no_internet_unavailable);
             state.secondaryLabel = r.getString(R.string.status_bar_airplane);
         } else if (cb.mNoDefaultNetwork) {
             if (cb.mNoNetworksAvailable || !mSignalCallback.mWifiInfo.mEnabled) {
-                state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
+                state.icon = maybeLoadResourceIcon(R.drawable.ic_qs_no_internet_unavailable);
                 state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
             } else {
-                state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_available);
+                state.icon = maybeLoadResourceIcon(R.drawable.ic_qs_no_internet_available);
                 state.secondaryLabel = r.getString(R.string.quick_settings_networks_available);
             }
         } else {
@@ -637,7 +637,7 @@
         final Resources r = mContext.getResources();
         state.label = r.getString(R.string.quick_settings_internet_label);
         state.state = Tile.STATE_ACTIVE;
-        state.icon = ResourceIcon.get(cb.mEthernetSignalIconId);
+        state.icon = maybeLoadResourceIcon(cb.mEthernetSignalIconId);
         state.secondaryLabel = cb.mEthernetContentDescription;
         if (DEBUG) {
             Log.d(TAG, "handleUpdateEthernetState: " + "BooleanState = " + state.toString());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
index 42ef0cd..6d3e5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
@@ -20,7 +20,7 @@
 import android.os.Handler
 import android.os.Looper
 import android.provider.Settings
-import android.widget.Switch
+import android.widget.Button
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.animation.Expandable
 import com.android.systemui.dagger.qualifiers.Background
@@ -28,11 +28,14 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.TileDetailsViewModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsDetailedView
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel
 import com.android.systemui.qs.tiles.dialog.InternetDialogManager
 import com.android.systemui.qs.tiles.dialog.WifiStateWorker
 import com.android.systemui.res.R
@@ -68,7 +71,7 @@
         metricsLogger,
         statusBarStateController,
         activityStarter,
-        qsLogger
+        qsLogger,
     ) {
     private var model: InternetTileModel = viewModel.tileModel.value
 
@@ -90,6 +93,9 @@
     }
 
     override fun handleClick(expandable: Expandable?) {
+        if (QsDetailedView.isEnabled) {
+            return
+        }
         mainHandler.post {
             internetDialogManager.create(
                 aboveStatusBar = true,
@@ -100,7 +106,11 @@
         }
     }
 
-    override fun secondaryClick(expandable: Expandable?) {
+    override fun getDetailsViewModel(): TileDetailsViewModel {
+        return InternetDetailsViewModel { longClick(null) }
+    }
+
+    override fun handleSecondaryClick(expandable: Expandable?) {
         // TODO(b/358352265): Figure out the correct action for the secondary click
         // Toggle wifi
         wifiStateWorker.isWifiEnabled = !wifiStateWorker.isWifiEnabled
@@ -108,7 +118,7 @@
 
     override fun handleUpdateState(state: QSTile.BooleanState, arg: Any?) {
         state.label = mContext.resources.getString(R.string.quick_settings_internet_label)
-        state.expandedAccessibilityClassName = Switch::class.java.name
+        state.expandedAccessibilityClassName = Button::class.java.name
 
         model.applyTo(state, mContext)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index cad5c0d..f35c25f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -122,7 +122,7 @@
         if (state.disabledByPolicy == false) {
             checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_CONFIG_LOCATION);
         }
-        state.icon = ResourceIcon.get(state.value
+        state.icon = maybeLoadResourceIcon(state.value
                 ? R.drawable.qs_location_icon_on : R.drawable.qs_location_icon_off);
         state.label = mContext.getString(R.string.quick_settings_location_label);
         state.contentDescription = state.label;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index fef5a74..9c63456 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -120,7 +120,7 @@
         tileState = tileMapper.map(config, model)
         state?.apply {
             this.state = tileState.activationState.legacyState
-            icon = tileState.icon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
+            icon = tileState.icon?.asQSTileIcon() ?: maybeLoadResourceIcon(ICON_RES_ID)
             label = tileLabel
             secondaryLabel = tileState.secondaryLabel
             contentDescription = tileState.contentDescription
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index 136eea8..683e4e9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -55,7 +55,8 @@
     public static final String TILE_SPEC = "nfc";
 
     private static final String NFC = TILE_SPEC;
-    private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_nfc);
+    @Nullable
+    private Icon mIcon = null;
 
     @Nullable
     private NfcAdapter mAdapter;
@@ -137,6 +138,10 @@
 
     @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
+        if (mIcon == null) {
+            mIcon = maybeLoadResourceIcon(R.drawable.ic_qs_nfc);
+        }
+
         state.value = getAdapter() != null && getAdapter().isEnabled();
         state.state = getAdapter() == null
                 ? Tile.STATE_UNAVAILABLE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index ac762de..2f59087 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -150,7 +150,7 @@
         state.label = mContext.getString(R.string.quick_settings_night_display_label);
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
-        state.icon = ResourceIcon.get(state.value ? R.drawable.qs_nightlight_icon_on
+        state.icon = maybeLoadResourceIcon(state.value ? R.drawable.qs_nightlight_icon_on
                 : R.drawable.qs_nightlight_icon_off);
         state.secondaryLabel = getSecondaryLabel(state.value);
         state.contentDescription = TextUtils.isEmpty(state.secondaryLabel)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt
index 69df096..989fc0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt
@@ -40,14 +40,14 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
 import javax.inject.Inject
-import kotlinx.coroutines.runBlocking
 
 /** Quick settings tile: Notes */
 class NotesTile
-@Inject constructor(
+@Inject
+constructor(
     private val host: QSHost,
     private val uiEventLogger: QsEventLogger,
-    @Background private val  backgroundLooper: Looper,
+    @Background private val backgroundLooper: Looper,
     @Main private val mainHandler: Handler,
     private val falsingManager: FalsingManager,
     private val metricsLogger: MetricsLogger,
@@ -74,8 +74,7 @@
     private lateinit var tileState: QSTileState
     private val config = qsTileConfigProvider.getConfig(TILE_SPEC)
 
-    override fun getTileLabel(): CharSequence =
-        mContext.getString(config.uiConfig.labelRes)
+    override fun getTileLabel(): CharSequence = mContext.getString(config.uiConfig.labelRes)
 
     override fun newTileState(): QSTile.State? {
         return QSTile.State().apply { state = Tile.STATE_INACTIVE }
@@ -88,13 +87,12 @@
     override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent
 
     override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
-        val model =
-            if (arg is NotesTileModel) arg else dataInteractor.getCurrentTileModel()
+        val model = if (arg is NotesTileModel) arg else dataInteractor.getCurrentTileModel()
         tileState = tileMapper.map(config, model)
 
         state?.apply {
             this.state = tileState.activationState.legacyState
-            icon = ResourceIcon.get(tileState.iconRes ?: R.drawable.ic_qs_notes)
+            icon = maybeLoadResourceIcon(tileState.iconRes ?: R.drawable.ic_qs_notes)
             label = tileState.label
             contentDescription = tileState.contentDescription
             expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
index 450c954..c605ac8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
@@ -51,8 +51,8 @@
 
     public static final String TILE_SPEC = "onehanded";
 
-    private final Icon mIcon = ResourceIcon.get(
-            com.android.internal.R.drawable.ic_qs_one_handed_mode);
+    @Nullable
+    private Icon mIcon = null;
     private final UserSettingObserver mSetting;
 
     @Inject
@@ -125,6 +125,10 @@
 
     @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
+        if (mIcon == null) {
+            mIcon = maybeLoadResourceIcon(com.android.internal.R.drawable.ic_qs_one_handed_mode);
+        }
+
         final int value = arg instanceof Integer ? (Integer) arg : mSetting.getValue();
         final boolean enabled = value != 0;
         state.value = enabled;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index 9766fac..467233d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -21,6 +21,7 @@
 import android.os.Looper;
 import android.service.quicksettings.Tile;
 import android.util.Log;
+import android.widget.Button;
 
 import androidx.annotation.Nullable;
 
@@ -119,13 +120,14 @@
     protected void handleUpdateState(State state, Object arg) {
         state.label = mContext.getString(R.string.qr_code_scanner_title);
         state.contentDescription = state.label;
-        state.icon = ResourceIcon.get(R.drawable.ic_qr_code_scanner);
+        state.icon = maybeLoadResourceIcon(R.drawable.ic_qr_code_scanner);
         state.state = mQRCodeScannerController.isAbleToLaunchScannerActivity() ? Tile.STATE_INACTIVE
                 : Tile.STATE_UNAVAILABLE;
         // The assumption is that if the OEM has the QR code scanner module enabled then the scanner
         // would go to "Unavailable" state only when GMS core is updating.
         state.secondaryLabel = state.state == Tile.STATE_UNAVAILABLE
                 ? mContext.getString(R.string.qr_code_scanner_updating_secondary_label) : null;
+        state.expandedAccessibilityClassName = Button.class.getName();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 37d24de..6deb192 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -40,6 +40,7 @@
 import android.service.quickaccesswallet.WalletCard;
 import android.service.quicksettings.Tile;
 import android.util.Log;
+import android.widget.Button;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -142,8 +143,16 @@
                         InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE);
 
         mUiHandler.post(
-                () -> mController.startQuickAccessUiIntent(
-                        mActivityStarter, animationController, mSelectedCard != null));
+                () -> {
+                    if (android.service.quickaccesswallet.Flags.launchSelectedCardFromQsTile()
+                            && mSelectedCard != null) {
+                        mController.startWalletCardPendingIntent(
+                                mSelectedCard, mActivityStarter, animationController);
+                    } else {
+                        mController.startQuickAccessUiIntent(
+                                mActivityStarter, animationController, mSelectedCard != null);
+                    }
+                });
     }
 
     @Override
@@ -154,7 +163,7 @@
         Drawable tileIcon = mController.getWalletClient().getTileIcon();
         state.icon =
                 tileIcon == null
-                        ? ResourceIcon.get(R.drawable.ic_wallet_lockscreen)
+                        ? maybeLoadResourceIcon(R.drawable.ic_wallet_lockscreen)
                         : new DrawableIcon(tileIcon);
         boolean isDeviceLocked = !mKeyguardStateController.isUnlocked();
         if (mController.getWalletClient().isWalletServiceAvailable()
@@ -178,6 +187,7 @@
             state.secondaryLabel = null;
             state.sideViewCustomDrawable = null;
         }
+        state.expandedAccessibilityClassName = Button.class.getName();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index 028ac6f..ca9d96e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -221,13 +221,13 @@
                 state = Tile.STATE_ACTIVE
                 forceExpandIcon = false
                 secondaryLabel = mContext.getString(R.string.qs_record_issue_stop)
-                icon = ResourceIcon.get(R.drawable.qs_record_issue_icon_on)
+                icon = maybeLoadResourceIcon(R.drawable.qs_record_issue_icon_on)
             } else {
                 value = false
                 state = Tile.STATE_INACTIVE
                 forceExpandIcon = true
                 secondaryLabel = mContext.getString(R.string.qs_record_issue_start)
-                icon = ResourceIcon.get(R.drawable.qs_record_issue_icon_off)
+                icon = maybeLoadResourceIcon(R.drawable.qs_record_issue_icon_off)
             }
             label = tileLabel
             contentDescription =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
index d624d98..26d43ee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
@@ -141,7 +141,7 @@
         state.label = mContext.getString(R.string.reduce_bright_colors_feature_name);
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.contentDescription = state.label;
-        state.icon = ResourceIcon.get(state.value
+        state.icon = maybeLoadResourceIcon(state.value
                 ? drawable.qs_extra_dim_icon_on
                 : drawable.qs_extra_dim_icon_off);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 35e43b6..e361bb8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -63,7 +63,8 @@
 
     private static final String EMPTY_SECONDARY_STRING = "";
 
-    private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate);
+    private final Icon mIcon =
+            maybeLoadResourceIcon(com.android.internal.R.drawable.ic_qs_auto_rotate);
     private final RotationLockController mController;
     private final SensorPrivacyManager mPrivacyManager;
     private final BatteryController mBatteryController;
@@ -153,13 +154,13 @@
                         && mController.isCameraRotationEnabled();
         state.value = !rotationLocked;
         state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
-        state.icon = ResourceIcon.get(R.drawable.qs_auto_rotate_icon_off);
+        state.icon = maybeLoadResourceIcon(R.drawable.qs_auto_rotate_icon_off);
         state.contentDescription = getAccessibilityString(rotationLocked);
         if (!rotationLocked) {
             state.secondaryLabel = cameraRotation ? mContext.getResources().getString(
                     R.string.rotation_lock_camera_rotation_on)
                     : EMPTY_SECONDARY_STRING;
-            state.icon = ResourceIcon.get(R.drawable.qs_auto_rotate_icon_on);
+            state.icon = maybeLoadResourceIcon(R.drawable.qs_auto_rotate_icon_on);
         } else {
             state.secondaryLabel = EMPTY_SECONDARY_STRING;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index f3be340..4fb96e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -18,6 +18,7 @@
 
 import android.app.Dialog;
 import android.content.Intent;
+import android.media.projection.StopReason;
 import android.os.Handler;
 import android.os.Looper;
 import android.service.quicksettings.Tile;
@@ -138,9 +139,8 @@
         state.value = isRecording || isStarting;
         state.state = (isRecording || isStarting) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.label = mContext.getString(R.string.quick_settings_screen_record_label);
-        state.icon = ResourceIcon.get(state.value
-                ? R.drawable.qs_screen_record_icon_on
-                : R.drawable.qs_screen_record_icon_off);
+        state.icon = maybeLoadResourceIcon(state.value
+                ? R.drawable.qs_screen_record_icon_on : R.drawable.qs_screen_record_icon_off);
         // Show expand icon when clicking will open a dialog
         state.forceExpandIcon = state.state == Tile.STATE_INACTIVE;
 
@@ -225,7 +225,7 @@
     }
 
     private void stopRecording() {
-        mController.stopRecording();
+        mController.stopRecording(StopReason.STOP_QS_TILE);
     }
 
     private final class Callback implements RecordingController.RecordingStateChangeCallback {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 036ce08..b62e858 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -119,7 +119,7 @@
 
         checkIfRestrictionEnforcedByAdminOnly(state, getRestriction());
 
-        state.icon = ResourceIcon.get(getIconRes(isBlocked));
+        state.icon = maybeLoadResourceIcon(getIconRes(isBlocked));
         state.state = isBlocked ? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE;
         state.value = !isBlocked;
         state.label = getTileLabel();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index bec6581..61beb6c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -42,6 +42,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.LocationController;
@@ -78,7 +79,7 @@
             StatusBarStateController statusBarStateController,
             ActivityStarter activityStarter,
             QSLogger qsLogger,
-            ConfigurationController configurationController,
+            @ShadeDisplayAware ConfigurationController configurationController,
             BatteryController batteryController,
             LocationController locationController
     ) {
@@ -166,7 +167,7 @@
         } else {
             state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         }
-        state.icon = ResourceIcon.get(state.state == Tile.STATE_ACTIVE
+        state.icon = maybeLoadResourceIcon(state.state == Tile.STATE_ACTIVE
                 ? R.drawable.qs_light_dark_theme_icon_on
                 : R.drawable.qs_light_dark_theme_icon_off);
         state.expandedAccessibilityClassName = Switch.class.getName();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 1750347..f6f89f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -53,8 +53,8 @@
 
     public static final String TILE_SPEC = "work";
 
-    private final Icon mIcon = ResourceIcon.get(
-            com.android.internal.R.drawable.stat_sys_managed_profile_status);
+    @Nullable
+    private Icon mIcon = null;
 
     private final ManagedProfileController mProfileController;
 
@@ -129,6 +129,11 @@
             state.value = mProfileController.isWorkModeEnabled();
         }
 
+        if (mIcon == null) {
+            mIcon = maybeLoadResourceIcon(
+                    com.android.internal.R.drawable.stat_sys_managed_profile_status);
+        }
+
         state.icon = mIcon;
         state.label = getTileLabel();
         state.contentDescription = state.label;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
index 87f542e..aeb6cef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.base.viewmodel
 
 import android.os.UserHandle
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Dumpable
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
@@ -58,11 +59,8 @@
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.transformLatest
-import com.android.app.tracing.coroutines.launchTraced as launch
-import kotlinx.coroutines.withContext
 
 /**
  * Provides a hassle-free way to implement new tiles according to current System UI architecture
@@ -90,36 +88,35 @@
 
     private val users: MutableStateFlow<UserHandle> =
         MutableStateFlow(userRepository.getSelectedUserInfo().userHandle)
+
     private val userInputs: MutableSharedFlow<QSTileUserAction> = MutableSharedFlow()
+
     private val forceUpdates: MutableSharedFlow<Unit> = MutableSharedFlow()
     private val spec
         get() = config.tileSpec
 
-    private val tileData: SharedFlow<DATA_TYPE> = createTileDataFlow()
+    private val tileData: SharedFlow<DATA_TYPE?> = createTileDataFlow()
 
     override val state: StateFlow<QSTileState?> =
         tileData
             .map { data ->
-                withContext(uiBackgroundDispatcher) { mapper().map(config, data) }
-                    .also { state -> qsTileLogger.logStateUpdate(spec, state, data) }
+                data?.let {
+                    mapper().map(config, it).also { state ->
+                        qsTileLogger.logStateUpdate(spec, state, it)
+                    }
+                }
             }
-            .stateIn(
-                tileScope,
-                SharingStarted.WhileSubscribed(),
-                null,
-            )
+            .flowOn(uiBackgroundDispatcher)
+            .stateIn(tileScope, SharingStarted.WhileSubscribed(), null)
+
     override val isAvailable: StateFlow<Boolean> =
         users
             .flatMapLatest { tileDataInteractor().availability(it) }
             .flowOn(backgroundDispatcher)
-            .stateIn(
-                tileScope,
-                SharingStarted.WhileSubscribed(),
-                true,
-            )
+            .stateIn(tileScope, SharingStarted.WhileSubscribed(), true)
 
     override fun forceUpdate() {
-        tileScope.launch { forceUpdates.emit(Unit) }
+        tileScope.launch(context = backgroundDispatcher) { forceUpdates.emit(Unit) }
     }
 
     override fun onUserChanged(user: UserHandle) {
@@ -131,9 +128,9 @@
             userAction,
             spec,
             tileData.replayCache.isNotEmpty(),
-            state.replayCache.isNotEmpty()
+            state.replayCache.isNotEmpty(),
         )
-        tileScope.launch { userInputs.emit(userAction) }
+        tileScope.launch(context = backgroundDispatcher) { userInputs.emit(userAction) }
     }
 
     override fun destroy() {
@@ -147,7 +144,7 @@
             println(state.replayCache.lastOrNull().toString())
         }
 
-    private fun createTileDataFlow(): SharedFlow<DATA_TYPE> =
+    private fun createTileDataFlow(): SharedFlow<DATA_TYPE?> =
         users
             .transformLatest { user ->
                 coroutineScope {
@@ -159,6 +156,7 @@
                                     .onEach { qsTileLogger.logForceUpdate(spec) },
                             )
                             .onStart { qsTileLogger.logInitialRequest(spec) }
+                            .flowOn(backgroundDispatcher)
                             .stateIn(this, SharingStarted.Eagerly, DataUpdateTrigger.InitialRequest)
                     tileDataInteractor()
                         .tileData(user, updateTriggers)
@@ -171,11 +169,8 @@
                 }
             }
             .distinctUntilChanged()
-            .shareIn(
-                tileScope,
-                SharingStarted.WhileSubscribed(),
-                replay = 1, // we only care about the most recent value
-            )
+            .flowOn(backgroundDispatcher)
+            .stateIn(tileScope, SharingStarted.WhileSubscribed(), null)
 
     /**
      * Creates a user input flow which:
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailedViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailedViewModel.kt
new file mode 100644
index 0000000..f239a17
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailedViewModel.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.dialog
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.plugins.qs.TileDetailsViewModel
+import com.android.systemui.res.R
+
+class InternetDetailsViewModel(
+    onLongClick: () -> Unit,
+) : TileDetailsViewModel() {
+    private val _onLongClick = onLongClick
+
+    @Composable
+    override fun GetContentView() {
+        AndroidView(
+            modifier = Modifier.fillMaxWidth().fillMaxHeight(),
+            factory = { context ->
+                // Inflate with the existing dialog xml layout
+                LayoutInflater.from(context)
+                    .inflate(R.layout.internet_connectivity_dialog, null)
+                // TODO: b/377388104 - Implement the internet details view
+            },
+        )
+    }
+
+    override fun clickOnSettingsButton() {
+        _onLongClick()
+    }
+
+    override fun getTitle(): String {
+        // TODO: b/377388104 Update the placeholder text
+        return "Internet"
+    }
+
+    override fun getSubTitle(): String {
+        // TODO: b/377388104 Update the placeholder text
+        return "Tab a network to connect"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
index d67057a..34c2ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
@@ -19,18 +19,18 @@
 import android.content.res.Resources
 import android.content.res.Resources.Theme
 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.airplane.domain.model.AirplaneModeTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Maps [AirplaneModeTileModel] to [QSTileState]. */
 class AirplaneModeMapper
 @Inject
-constructor(@Main private val resources: Resources, val theme: Theme) :
+constructor(@ShadeDisplayAware private val resources: Resources, val theme: Theme) :
     QSTileDataToStateMapper<AirplaneModeTileModel> {
 
     override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
index 7322b8d..a72992d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -19,12 +19,12 @@
 import android.content.res.Resources
 import android.content.res.Resources.Theme
 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.alarm.domain.model.AlarmTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.time.SystemClock
 import java.time.Instant
 import java.time.LocalDateTime
@@ -36,7 +36,7 @@
 class AlarmTileMapper
 @Inject
 constructor(
-    @Main private val resources: Resources,
+    @ShadeDisplayAware private val resources: Resources,
     private val theme: Theme,
     private val clock: SystemClock,
 ) : QSTileDataToStateMapper<AlarmTileModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
index 5b30e8d..e116d8c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -18,19 +18,21 @@
 
 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.battery.domain.model.BatterySaverTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Maps [BatterySaverTileModel] to [QSTileState]. */
 open class BatterySaverTileMapper
 @Inject
-constructor(@Main protected val resources: Resources, private val theme: Resources.Theme) :
-    QSTileDataToStateMapper<BatterySaverTileModel> {
+constructor(
+    @ShadeDisplayAware protected val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<BatterySaverTileModel> {
 
     override fun map(config: QSTileConfig, data: BatterySaverTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
index 7c90b3d..21b9f65 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
@@ -18,19 +18,21 @@
 
 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.colorcorrection.domain.model.ColorCorrectionTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Maps [ColorCorrectionTileModel] to [QSTileState]. */
 class ColorCorrectionTileMapper
 @Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
-    QSTileDataToStateMapper<ColorCorrectionTileModel> {
+constructor(
+    @ShadeDisplayAware private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<ColorCorrectionTileModel> {
 
     override fun map(config: QSTileConfig, data: ColorCorrectionTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
index 8f870d4..4806c3f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
@@ -18,7 +18,7 @@
 
 import android.os.UserHandle
 import android.service.quicksettings.Tile
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
@@ -28,6 +28,7 @@
 import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.impl.di.QSTileScope
 import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,7 +45,6 @@
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.shareIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 @QSTileScope
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -64,7 +64,7 @@
     private val bindingFlow =
         mutableUserFlow
             .flatMapLatest { user ->
-                ConflatedCallbackFlow.conflatedCallbackFlow {
+                conflatedCallbackFlow {
                     serviceInteractor.setUser(user)
 
                     // Wait for the CustomTileInteractor to become initialized first, because
@@ -79,7 +79,7 @@
                             defaultsRepository.requestNewDefaults(
                                 user,
                                 tileSpec.componentName,
-                                true
+                                true,
                             )
                         }
                         .launchIn(this)
@@ -99,7 +99,7 @@
 
     override fun tileData(
         user: UserHandle,
-        triggers: Flow<DataUpdateTrigger>
+        triggers: Flow<DataUpdateTrigger>,
     ): Flow<CustomTileDataModel> {
         tileScope.launch { mutableUserFlow.emit(user) }
         return bindingFlow.combine(triggers) { _, _ -> }.flatMapLatest { dataFlow(user) }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index 7e557eb..2dfb1fc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -19,18 +19,18 @@
 import android.content.res.Resources
 import android.content.res.Resources.Theme
 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.flashlight.domain.model.FlashlightTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Maps [FlashlightTileModel] to [QSTileState]. */
 class FlashlightMapper
 @Inject
-constructor(@Main private val resources: Resources, private val theme: Theme) :
+constructor(@ShadeDisplayAware private val resources: Resources, private val theme: Theme) :
     QSTileDataToStateMapper<FlashlightTileModel> {
 
     override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
index 9d44fc6..7f41cbd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
@@ -18,19 +18,21 @@
 
 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.fontscaling.domain.model.FontScalingTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Maps [FontScalingTileModel] to [QSTileState]. */
 class FontScalingTileMapper
 @Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
-    QSTileDataToStateMapper<FontScalingTileModel> {
+constructor(
+    @ShadeDisplayAware private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<FontScalingTileModel> {
 
     override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
index c3ac1f8..4c302b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
@@ -18,19 +18,21 @@
 
 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.hearingdevices.domain.model.HearingDevicesTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Maps [HearingDevicesTileModel] to [QSTileState]. */
 class HearingDevicesTileMapper
 @Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
-    QSTileDataToStateMapper<HearingDevicesTileModel> {
+constructor(
+    @ShadeDisplayAware private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<HearingDevicesTileModel> {
 
     override fun map(config: QSTileConfig, data: HearingDevicesTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
index 3692c35..8d35b24 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
@@ -19,18 +19,18 @@
 import android.content.res.Resources
 import android.content.res.Resources.Theme
 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.inversion.domain.model.ColorInversionTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Maps [ColorInversionTileModel] to [QSTileState]. */
 class ColorInversionTileMapper
 @Inject
-constructor(@Main private val resources: Resources, private val theme: Theme) :
+constructor(@ShadeDisplayAware private val resources: Resources, private val theme: Theme) :
     QSTileDataToStateMapper<ColorInversionTileModel> {
     override fun map(config: QSTileConfig, data: ColorInversionTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
index 3fe2a77..3557c1a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
@@ -19,16 +19,16 @@
 import android.content.res.Resources
 import android.content.res.Resources.Theme
 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.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 class IssueRecordingMapper
 @Inject
-constructor(@Main private val resources: Resources, private val theme: Theme) :
+constructor(@ShadeDisplayAware private val resources: Resources, private val theme: Theme) :
     QSTileDataToStateMapper<IssueRecordingModel> {
     override fun map(config: QSTileConfig, data: IssueRecordingModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
index 08432f6..dfc24a1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
@@ -19,18 +19,18 @@
 import android.content.res.Resources
 import android.content.res.Resources.Theme
 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.location.domain.model.LocationTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Maps [LocationTileModel] to [QSTileState]. */
 class LocationTileMapper
 @Inject
-constructor(@Main private val resources: Resources, private val theme: Theme) :
+constructor(@ShadeDisplayAware private val resources: Resources, private val theme: Theme) :
     QSTileDataToStateMapper<LocationTileModel> {
 
     override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 4a64313..bac048f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -19,18 +19,18 @@
 import android.content.res.Resources
 import android.icu.text.MessageFormat
 import android.widget.Button
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import java.util.Locale
 import javax.inject.Inject
 
 class ModesTileMapper
 @Inject
-constructor(@Main private val resources: Resources, val theme: Resources.Theme) :
+constructor(@ShadeDisplayAware private val resources: Resources, val theme: Resources.Theme) :
     QSTileDataToStateMapper<ModesTileModel> {
     override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
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
index 88bd224..e8e43e8 100644
--- 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
@@ -20,10 +20,10 @@
 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.shade.ShadeDisplayAware
 import com.android.systemui.util.time.DateFormatUtil
 import java.time.LocalTime
 import javax.inject.Inject
@@ -35,14 +35,14 @@
 class NightDisplayTileDataInteractor
 @Inject
 constructor(
-    @Application private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val dateFormatUtil: DateFormatUtil,
     private val nightDisplayRepository: NightDisplayRepository,
 ) : QSTileDataInteractor<NightDisplayTileModel> {
 
     override fun tileData(
         user: UserHandle,
-        triggers: Flow<DataUpdateTrigger>
+        triggers: Flow<DataUpdateTrigger>,
     ): Flow<NightDisplayTileModel> =
         nightDisplayRepository.nightDisplayState(user).map {
             generateModel(
@@ -51,7 +51,7 @@
                 it.startTime,
                 it.endTime,
                 it.shouldForceAutoMode,
-                it.locationEnabled
+                it.locationEnabled,
             )
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
index 081a03c7..3569e4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
@@ -22,7 +22,6 @@
 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
@@ -30,6 +29,7 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import java.time.DateTimeException
 import java.time.LocalTime
 import java.time.format.DateTimeFormatter
@@ -39,7 +39,7 @@
 class NightDisplayTileMapper
 @Inject
 constructor(
-    @Main private val resources: Resources,
+    @ShadeDisplayAware private val resources: Resources,
     private val theme: Resources.Theme,
     private val logger: QSTileLogger,
 ) : QSTileDataToStateMapper<NightDisplayTileModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt
index ee1b9e5..a543619 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt
@@ -19,28 +19,24 @@
 import android.content.res.Resources
 import android.widget.Button
 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.notes.domain.model.NotesTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 class NotesTileMapper
 @Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
-    QSTileDataToStateMapper<NotesTileModel> {
+constructor(
+    @ShadeDisplayAware private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<NotesTileModel> {
     override fun map(config: QSTileConfig, data: NotesTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             iconRes = R.drawable.ic_qs_notes
-            icon =
-                Icon.Loaded(
-                    resources.getDrawable(
-                        iconRes!!,
-                        theme),
-                    contentDescription = null
-                )
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
             contentDescription = label
             activationState = QSTileState.ActivationState.INACTIVE
             sideViewIcon = QSTileState.SideViewIcon.Chevron
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
index 8e5d0d4..76f1e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
@@ -18,19 +18,21 @@
 
 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 com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Maps [OneHandedModeTileModel] to [QSTileState]. */
 class OneHandedModeTileMapper
 @Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
-    QSTileDataToStateMapper<OneHandedModeTileModel> {
+constructor(
+    @ShadeDisplayAware 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) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
index 5c6351e..c546250 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -18,19 +18,21 @@
 
 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 com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Maps [QRCodeScannerTileModel] to [QSTileState]. */
 class QRCodeScannerTileMapper
 @Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
-    QSTileDataToStateMapper<QRCodeScannerTileModel> {
+constructor(
+    @ShadeDisplayAware 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) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
index 15c9901..eff5f8f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
@@ -20,20 +20,20 @@
 import android.content.res.Resources
 import android.provider.Settings
 import com.android.systemui.accessibility.extradim.ExtraDimDialogManager
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.ReduceBrightColorsController
 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.reducebrightness.domain.model.ReduceBrightColorsTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Handles reduce bright colors tile clicks. */
 class ReduceBrightColorsTileUserActionInteractor
 @Inject
 constructor(
-    @Main private val resources: Resources,
+    @ShadeDisplayAware private val resources: Resources,
     private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
     private val reduceBrightColorsController: ReduceBrightColorsController,
     private val extraDimDialogManager: ExtraDimDialogManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
index fe77fe6..66d0f96 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
@@ -19,19 +19,21 @@
 import android.content.res.Resources
 import android.service.quicksettings.Tile
 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.reducebrightness.domain.model.ReduceBrightColorsTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Maps [ReduceBrightColorsTileModel] to [QSTileState]. */
 class ReduceBrightColorsTileMapper
 @Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
-    QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
+constructor(
+    @ShadeDisplayAware private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
 
     override fun map(config: QSTileConfig, data: ReduceBrightColorsTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt
index 57a60c1..7f17a3a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt
@@ -22,10 +22,10 @@
 import android.os.UserHandle
 import com.android.systemui.camera.data.repository.CameraAutoRotateRepository
 import com.android.systemui.camera.data.repository.CameraSensorPrivacyRepository
-import com.android.systemui.dagger.qualifiers.Main
 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.rotation.domain.model.RotationLockTileModel
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.RotationLockController
 import com.android.systemui.util.kotlin.isBatteryPowerSaveEnabled
@@ -44,30 +44,29 @@
     private val cameraAutoRotateRepository: CameraAutoRotateRepository,
     private val cameraSensorPrivacyRepository: CameraSensorPrivacyRepository,
     private val packageManager: PackageManager,
-    @Main private val resources: Resources,
+    @ShadeDisplayAware private val resources: Resources,
 ) : QSTileDataInteractor<RotationLockTileModel> {
 
     override fun tileData(
         user: UserHandle,
-        triggers: Flow<DataUpdateTrigger>
+        triggers: Flow<DataUpdateTrigger>,
     ): Flow<RotationLockTileModel> =
         combine(
             rotationLockController.isRotationLockEnabled(),
             cameraSensorPrivacyRepository.isEnabled(user),
             batteryController.isBatteryPowerSaveEnabled(),
-            cameraAutoRotateRepository.isCameraAutoRotateSettingEnabled(user)
+            cameraAutoRotateRepository.isCameraAutoRotateSettingEnabled(user),
         ) {
             isRotationLockEnabled,
             isCamPrivacySensorEnabled,
             isBatteryPowerSaveEnabled,
-            isCameraAutoRotateEnabled,
-            ->
+            isCameraAutoRotateEnabled ->
             RotationLockTileModel(
                 isRotationLockEnabled,
                 isCameraRotationEnabled(
                     isBatteryPowerSaveEnabled,
                     isCamPrivacySensorEnabled,
-                    isCameraAutoRotateEnabled
+                    isCameraAutoRotateEnabled,
                 ),
             )
         }
@@ -84,7 +83,7 @@
     private fun isCameraRotationEnabled(
         isBatteryPowerSaverModeOn: Boolean,
         isCameraSensorPrivacyEnabled: Boolean,
-        isCameraAutoRotateEnabled: Boolean
+        isCameraAutoRotateEnabled: Boolean,
     ): Boolean =
         resources.getBoolean(com.android.internal.R.bool.config_allowRotationResolver) &&
             !isBatteryPowerSaverModeOn &&
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
index 9a003ff..a014422 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
@@ -19,12 +19,12 @@
 import android.content.res.Resources
 import android.hardware.devicestate.DeviceStateManager
 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.rotation.domain.model.RotationLockTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.util.Utils.isDeviceFoldable
 import javax.inject.Inject
@@ -33,7 +33,7 @@
 class RotationLockTileMapper
 @Inject
 constructor(
-    @Main private val resources: Resources,
+    @ShadeDisplayAware private val resources: Resources,
     private val theme: Resources.Theme,
     private val devicePostureController: DevicePostureController,
     private val deviceStateManager: DeviceStateManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
index 08196bb..aea4967 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
@@ -18,19 +18,21 @@
 
 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.saver.domain.model.DataSaverTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Maps [DataSaverTileModel] to [QSTileState]. */
 class DataSaverTileMapper
 @Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
-    QSTileDataToStateMapper<DataSaverTileModel> {
+constructor(
+    @ShadeDisplayAware private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<DataSaverTileModel> {
     override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             with(data) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
index 85aa674..9453447 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor
 
+import android.media.projection.StopReason
 import android.util.Log
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.DialogCuj
@@ -61,7 +62,9 @@
                             Log.d(TAG, "Cancelling countdown")
                             withContext(backgroundContext) { recordingController.cancelCountdown() }
                         }
-                        is ScreenRecordModel.Recording -> screenRecordRepository.stopRecording()
+                        is ScreenRecordModel.Recording -> {
+                            screenRecordRepository.stopRecording(StopReason.STOP_QS_TILE)
+                        }
                         is ScreenRecordModel.DoingNothing ->
                             withContext(mainContext) {
                                 showPrompt(action.expandable, user.identifier)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
index ba06de9..f3136e0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
@@ -19,19 +19,21 @@
 import android.content.res.Resources
 import android.text.TextUtils
 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.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
 import com.android.systemui.screenrecord.data.model.ScreenRecordModel
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Maps [ScreenRecordModel] to [QSTileState]. */
 class ScreenRecordTileMapper
 @Inject
-constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
-    QSTileDataToStateMapper<ScreenRecordModel> {
+constructor(
+    @ShadeDisplayAware private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<ScreenRecordModel> {
     override fun map(config: QSTileConfig, data: ScreenRecordModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             label = resources.getString(R.string.quick_settings_screen_record_label)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
index b4cfec4..73e61b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
@@ -18,12 +18,12 @@
 
 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.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -32,7 +32,7 @@
 class SensorPrivacyToggleTileMapper
 @AssistedInject
 constructor(
-    @Main private val resources: Resources,
+    @ShadeDisplayAware private val resources: Resources,
     private val theme: Resources.Theme,
     @Assisted private val sensorPrivacyTileResources: SensorPrivacyTileResources,
 ) : QSTileDataToStateMapper<SensorPrivacyToggleTileModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
index eda8e5c..e9aa46c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
@@ -21,12 +21,12 @@
 import android.content.res.Resources.Theme
 import android.text.TextUtils
 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.uimodenight.domain.model.UiModeNightTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import java.time.LocalTime
 import java.time.format.DateTimeFormatter
 import javax.inject.Inject
@@ -34,7 +34,7 @@
 /** Maps [UiModeNightTileModel] to [QSTileState]. */
 class UiModeNightTileMapper
 @Inject
-constructor(@Main private val resources: Resources, private val theme: Theme) :
+constructor(@ShadeDisplayAware private val resources: Resources, private val theme: Theme) :
     QSTileDataToStateMapper<UiModeNightTileModel> {
     companion object {
         val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt
index 7af3576..925b913 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt
@@ -21,7 +21,6 @@
 import android.content.res.Configuration
 import android.os.UserHandle
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow
-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.uimodenight.domain.model.UiModeNightTileModel
@@ -40,7 +39,7 @@
 @Inject
 constructor(
     @ShadeDisplayAware private val context: Context,
-    private val configurationController: ConfigurationController,
+    @ShadeDisplayAware private val configurationController: ConfigurationController,
     private val uiModeManager: UiModeManager,
     private val batteryController: BatteryController,
     private val locationController: LocationController,
@@ -49,7 +48,7 @@
 
     override fun tileData(
         user: UserHandle,
-        triggers: Flow<DataUpdateTrigger>
+        triggers: Flow<DataUpdateTrigger>,
     ): Flow<UiModeNightTileModel> =
         ConflatedCallbackFlow.conflatedCallbackFlow {
             // send initial state
@@ -106,7 +105,7 @@
             nightModeCustomType,
             use24HourFormat,
             customNightModeEnd,
-            customNightModeStart
+            customNightModeStart,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
index a1bc8a8..6a3195a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
@@ -21,19 +21,19 @@
 import android.content.res.Resources
 import android.service.quicksettings.Tile
 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.work.domain.model.WorkModeTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Maps [WorkModeTileModel] to [QSTileState]. */
 class WorkModeTileMapper
 @Inject
 constructor(
-    @Main private val resources: Resources,
+    @ShadeDisplayAware private val resources: Resources,
     private val theme: Resources.Theme,
     private val devicePolicyManager: DevicePolicyManager,
 ) : QSTileDataToStateMapper<WorkModeTileModel> {
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 ab3862b..f9a1ad5 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
@@ -25,6 +25,7 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon
@@ -35,14 +36,17 @@
 import dagger.assisted.AssistedInject
 import java.io.PrintWriter
 import java.util.concurrent.CopyOnWriteArraySet
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.collectIndexed
 import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.launch
 
 // TODO(b/http://b/299909989): Use QSTileViewModel directly after the rollout
 class QSTileViewModelAdapter
@@ -51,6 +55,7 @@
     @Application private val applicationScope: CoroutineScope,
     private val qsHost: QSHost,
     @Assisted private val qsTileViewModel: QSTileViewModel,
+    @UiBackground private val uiBgDispatcher: CoroutineDispatcher,
 ) : QSTile, Dumpable {
 
     private val context
@@ -162,19 +167,25 @@
     override fun setListening(client: Any?, listening: Boolean) {
         client ?: return
         if (listening) {
-            val clientWasNotAlreadyListening = listeningClients.add(client)
-            if (clientWasNotAlreadyListening && listeningClients.size == 1) {
-                stateJob =
-                    qsTileViewModel.state
-                        .filterNotNull()
-                        .map { mapState(context, it, qsTileViewModel.config) }
-                        .onEach { legacyState ->
-                            val changed = legacyState.copyTo(cachedState)
-                            if (changed) {
-                                callbacks.forEach { it.onStateChanged(legacyState) }
+            applicationScope.launch(uiBgDispatcher) {
+                val shouldStartMappingJob =
+                    listeningClients.add(client) // new client
+                    && listeningClients.size == 1 // first client
+
+                if (shouldStartMappingJob) {
+                    stateJob =
+                        qsTileViewModel.state
+                            .filterNotNull()
+                            .map { mapState(context, it, qsTileViewModel.config) }
+                            .onEach { legacyState ->
+                                val changed = legacyState.copyTo(cachedState)
+                                if (changed) {
+                                    callbacks.forEach { it.onStateChanged(legacyState) }
+                                }
                             }
-                        }
-                        .launchIn(applicationScope)
+                            .flowOn(uiBgDispatcher)
+                            .launchIn(applicationScope)
+                }
             }
         } else {
             listeningClients.remove(client)
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 da175c9..62b1203 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.lifecycle.ExclusiveActivatable
+import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
@@ -36,6 +37,7 @@
     @Assisted supportsBrightnessMirroring: Boolean,
     val tileGridViewModel: TileGridViewModel,
     val editModeViewModel: EditModeViewModel,
+    val detailsViewModel: DetailsViewModel,
 ) : ExclusiveActivatable() {
 
     val brightnessSliderViewModel =
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index a5eb92b..e3cf411 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -26,11 +26,6 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 import static android.window.BackEvent.EDGE_NONE;
 
-import static com.android.window.flags.Flags.predictiveBackSwipeEdgeNoneApi;
-import static com.android.window.flags.Flags.predictiveBackThreeButtonNav;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_COMMUNAL_HUB_SHOWING;
@@ -42,6 +37,9 @@
 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_VOICE_INTERACTION_WINDOW_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_TRANSITION;
+import static com.android.systemui.shared.system.QuickStepContract.addInterface;
+import static com.android.window.flags.Flags.predictiveBackSwipeEdgeNoneApi;
+import static com.android.window.flags.Flags.predictiveBackThreeButtonNav;
 
 import android.annotation.FloatRange;
 import android.annotation.Nullable;
@@ -559,13 +557,9 @@
             mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
 
             Bundle params = new Bundle();
-            params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
-            params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
-                    mSysuiUnlockAnimationController.asBinder());
-            mUnfoldTransitionProgressForwarder.ifPresent(
-                    unfoldProgressForwarder -> params.putBinder(
-                            KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER,
-                            unfoldProgressForwarder.asBinder()));
+            addInterface(mSysUiProxy, params);
+            addInterface(mSysuiUnlockAnimationController, params);
+            addInterface(mUnfoldTransitionProgressForwarder.orElse(null), params);
             // Add all the interfaces exposed by the shell
             mShellInterface.createExternalInterfaces(params);
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index e441a23..e36e40d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
 import com.android.systemui.scene.ui.viewmodel.SplitEdgeDetector
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.flag.DualShade
@@ -98,6 +99,7 @@
                         Scenes.Shade.takeUnless { DualShade.isEnabled },
                     ),
                 initialSceneKey = Scenes.Gone,
+                transitions = SceneContainerTransitions,
                 overlayKeys =
                     listOfNotNull(
                         Overlays.NotificationsShade.takeIf { DualShade.isEnabled },
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 4beec10..fe01452 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
 import com.android.systemui.scene.ui.viewmodel.SplitEdgeDetector
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.flag.DualShade
@@ -106,6 +107,7 @@
                         Scenes.Shade.takeUnless { DualShade.isEnabled },
                     ),
                 initialSceneKey = Scenes.Lockscreen,
+                transitions = SceneContainerTransitions,
                 overlayKeys =
                     listOfNotNull(
                         Overlays.NotificationsShade.takeIf { DualShade.isEnabled },
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
index 16ed59f4..c1646b8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolverModule
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
 import dagger.Module
 import dagger.Provides
 
@@ -35,7 +36,7 @@
 
             // List SceneResolver modules for supported SceneFamilies
             HomeSceneFamilyResolverModule::class,
-        ],
+        ]
 )
 object ShadelessSceneContainerFrameworkModule {
 
@@ -46,20 +47,12 @@
         return SceneContainerConfig(
             // Note that this list is in z-order. The first one is the bottom-most and the
             // last one is top-most.
-            sceneKeys =
-                listOf(
-                    Scenes.Gone,
-                    Scenes.Lockscreen,
-                    Scenes.Bouncer,
-                ),
+            sceneKeys = listOf(Scenes.Gone, Scenes.Lockscreen, Scenes.Bouncer),
             initialSceneKey = Scenes.Lockscreen,
+            transitions = SceneContainerTransitions,
             overlayKeys = emptyList(),
             navigationDistances =
-                mapOf(
-                    Scenes.Gone to 0,
-                    Scenes.Lockscreen to 0,
-                    Scenes.Bouncer to 1,
-                )
+                mapOf(Scenes.Gone to 0, Scenes.Lockscreen to 0, Scenes.Bouncer to 1),
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index ba789a0..3a07ce9 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.scene.domain.interactor
 
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
@@ -29,9 +30,9 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.init.NotificationsController
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import javax.inject.Inject
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
@@ -44,7 +45,6 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Business logic about the visibility of various parts of the window root view. */
 @OptIn(ExperimentalCoroutinesApi::class)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
index 2311e47..ce7be83 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
@@ -18,6 +18,7 @@
 
 import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitions
 
 /** Models the configuration of the scene container. */
 data class SceneContainerConfig(
@@ -38,6 +39,9 @@
      */
     val initialSceneKey: SceneKey,
 
+    /** Transition definitions to be used when animating between scene transitions. */
+    val transitions: SceneTransitions,
+
     /**
      * The keys to all overlays in the container, sorted by z-order such that the last one renders
      * on top of all previous ones. Overlay keys within the same container must not repeat but it's
@@ -61,7 +65,7 @@
      * Note that this is not the z-order of rendering; that's determined by the order of declaration
      * of scenes in the [sceneKeys] list.
      */
-    val navigationDistances: Map<SceneKey, Int>
+    val navigationDistances: Map<SceneKey, Int>,
 ) {
     init {
         check(sceneKeys.isNotEmpty()) { "A container must have at least one scene key." }
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 1e3a233..1c15c74 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
@@ -196,6 +196,7 @@
                             sceneByKey = sceneByKey,
                             overlayByKey = overlayByKey,
                             initialSceneKey = containerConfig.initialSceneKey,
+                            sceneTransitions = containerConfig.transitions,
                             dataSourceDelegator = dataSourceDelegator,
                             qsSceneAdapter = qsSceneAdapter,
                         )
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index d7463f8..9ee99e4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.media.projection.StopReason;
 import android.os.Bundle;
 import android.os.CountDownTimer;
 import android.os.Process;
@@ -58,6 +59,7 @@
     private boolean mIsStarting;
     private boolean mIsRecording;
     private PendingIntent mStopIntent;
+    private @StopReason int mStopReason = StopReason.STOP_UNKNOWN;
     private final Bundle mInteractiveBroadcastOption;
     private CountDownTimer mCountDownTimer = null;
     private final Executor mMainExecutor;
@@ -83,7 +85,7 @@
             new UserTracker.Callback() {
                 @Override
                 public void onUserChanged(int newUser, @NonNull Context userContext) {
-                    stopRecording();
+                    stopRecording(StopReason.STOP_USER_SWITCH);
                 }
             };
 
@@ -240,9 +242,11 @@
     }
 
     /**
-     * Stop the recording
+     * Stop the recording and sets the stop reason to be used by the RecordingService
+     * @param stopReason the method of the recording stopped (i.e. QS tile, status bar chip, etc.)
      */
-    public void stopRecording() {
+    public void stopRecording(@StopReason int stopReason) {
+        mStopReason = stopReason;
         try {
             if (mStopIntent != null) {
                 mRecordingControllerLogger.logRecordingStopped();
@@ -277,6 +281,10 @@
         }
     }
 
+    public @StopReason int getStopReason() {
+        return mStopReason;
+    }
+
     @Override
     public void addCallback(@NonNull RecordingStateChangeCallback listener) {
         mListeners.add(listener);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 8c207d1..f7b5271 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.media.MediaRecorder;
+import android.media.projection.StopReason;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -78,6 +79,7 @@
     private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
     private static final String EXTRA_CAPTURE_TARGET = "extra_captureTarget";
     private static final String EXTRA_DISPLAY_ID = "extra_displayId";
+    private static final String EXTRA_STOP_REASON = "extra_stopReason";
 
     protected static final String ACTION_START = "com.android.systemui.screenrecord.START";
     protected static final String ACTION_SHOW_START_NOTIF =
@@ -242,7 +244,8 @@
                 // Check user ID - we may be getting a stop intent after user switch, in which case
                 // we want to post the notifications for that user, which is NOT current user
                 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_ID_NOT_SPECIFIED);
-                stopService(userId);
+                int stopReason = intent.getIntExtra(EXTRA_STOP_REASON, mController.getStopReason());
+                stopService(userId, stopReason);
                 break;
 
             case ACTION_SHARE:
@@ -486,11 +489,11 @@
                 getTag(), notificationIdForGroup, groupNotif, currentUser);
     }
 
-    private void stopService() {
-        stopService(USER_ID_NOT_SPECIFIED);
+    private void stopService(@StopReason int stopReason) {
+        stopService(USER_ID_NOT_SPECIFIED, stopReason);
     }
 
-    private void stopService(int userId) {
+    private void stopService(int userId, @StopReason int stopReason) {
         if (userId == USER_ID_NOT_SPECIFIED) {
             userId = mUserContextTracker.getUserContext().getUserId();
         }
@@ -499,7 +502,7 @@
         setTapsVisible(mOriginalShowTaps);
         try {
             if (getRecorder() != null) {
-                getRecorder().end();
+                getRecorder().end(stopReason);
             }
             saveRecording(userId);
         } catch (RuntimeException exception) {
@@ -598,7 +601,8 @@
      * @return
      */
     protected Intent getNotificationIntent(Context context) {
-        return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF);
+        return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF)
+                .putExtra(EXTRA_STOP_REASON, StopReason.STOP_HOST_APP);
     }
 
     private Intent getShareIntent(Context context, Uri path) {
@@ -610,14 +614,17 @@
     @Override
     public void onInfo(MediaRecorder mr, int what, int extra) {
         Log.d(getTag(), "Media recorder info: " + what);
-        onStartCommand(getStopIntent(this), 0, 0);
+        // Stop due to record reaching size limits so log as stopping due to error
+        Intent stopIntent = getStopIntent(this);
+        stopIntent.putExtra(EXTRA_STOP_REASON, StopReason.STOP_ERROR);
+        onStartCommand(stopIntent, 0, 0);
     }
 
     @Override
-    public void onStopped() {
+    public void onStopped(@StopReason int stopReason) {
         if (mController.isRecording()) {
             Log.d(getTag(), "Stopping recording because the system requested the stop");
-            stopService();
+            stopService(stopReason);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 2ca0621..f4455bf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -41,6 +41,7 @@
 import android.media.projection.IMediaProjectionManager;
 import android.media.projection.MediaProjection;
 import android.media.projection.MediaProjectionManager;
+import android.media.projection.StopReason;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
@@ -300,7 +301,7 @@
     /**
      * End screen recording, throws an exception if stopping recording failed
      */
-    void end() throws IOException {
+    void end(@StopReason int stopReason) throws IOException {
         Closer closer = new Closer();
 
         // MediaRecorder might throw RuntimeException if stopped immediately after starting
@@ -309,7 +310,17 @@
         closer.register(mMediaRecorder::release);
         closer.register(mInputSurface::release);
         closer.register(mVirtualDisplay::release);
-        closer.register(mMediaProjection::stop);
+        closer.register(() -> {
+            if (stopReason == StopReason.STOP_UNKNOWN) {
+                // Attempt to call MediaProjection#stop() even if it might have already been called.
+                // If projection has already been stopped, then nothing will happen. Else, stop
+                // will be logged as a manually requested stop from host app.
+                mMediaProjection.stop();
+            } else {
+                // In any other case, the stop reason is related to the recorder, so pass it on here
+                mMediaProjection.stop(stopReason);
+            }
+        });
         closer.register(this::stopInternalAudioRecording);
 
         closer.close();
@@ -323,7 +334,7 @@
     @Override
     public void onStop() {
         Log.d(TAG, "The system notified about stopping the projection");
-        mListener.onStopped();
+        mListener.onStopped(StopReason.STOP_UNKNOWN);
     }
 
     private void stopInternalAudioRecording() {
@@ -453,7 +464,7 @@
          * For example, this might happen when doing partial screen sharing of an app
          * and the app that is being captured is closed.
          */
-        void onStopped();
+        void onStopped(@StopReason int stopReason);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index bdc58c1..d9cc0c8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -133,6 +133,7 @@
         dialog.setTitle(R.string.screenrecord_title)
         setStartButtonOnClickListener { v: View? ->
             onStartRecordingClicked?.run()
+            val selectedScreenShareOption = getSelectedScreenShareOption()
             if (selectedScreenShareOption.mode == ENTIRE_SCREEN) {
                 requestScreenCapture(/* captureTarget= */ null, selectedScreenShareOption.displayId)
             }
@@ -212,7 +213,8 @@
     }
 
     private fun updateTapsViewVisibility() {
-        tapsView.visibility = if (selectedScreenShareOption.mode == SINGLE_APP) GONE else VISIBLE
+        tapsView.visibility =
+            if (getSelectedScreenShareOption().mode == SINGLE_APP) GONE else VISIBLE
     }
 
     /**
@@ -226,7 +228,7 @@
         displayId: Int = Display.DEFAULT_DISPLAY,
     ) {
         val userContext = userContextProvider.userContext
-        val showTaps = selectedScreenShareOption.mode != SINGLE_APP && tapsSwitch.isChecked
+        val showTaps = getSelectedScreenShareOption().mode != SINGLE_APP && tapsSwitch.isChecked
         val audioMode =
             if (audioSwitch.isChecked) options.selectedItem as ScreenRecordingAudioSource
             else ScreenRecordingAudioSource.NONE
@@ -280,6 +282,18 @@
         private const val DELAY_MS: Long = 3000
         private const val INTERVAL_MS: Long = 1000
 
+        private val RECORDABLE_DISPLAY_TYPES =
+            intArrayOf(
+                Display.TYPE_OVERLAY,
+                Display.TYPE_EXTERNAL,
+                Display.TYPE_INTERNAL,
+                Display.TYPE_WIFI,
+            )
+
+        private val filterDeviceTypeFlag: Boolean =
+            com.android.media.projection.flags.Flags
+                .mediaProjectionConnectedDisplayNoVirtualDevice()
+
         private fun createOptionList(displayManager: DisplayManager): List<ScreenShareOption> {
             if (!com.android.media.projection.flags.Flags.mediaProjectionConnectedDisplay()) {
                 return listOf(
@@ -302,6 +316,7 @@
                     ),
                 )
             }
+
             return listOf(
                 ScreenShareOption(
                     SINGLE_APP,
@@ -322,7 +337,10 @@
                 ),
             ) +
                 displayManager.displays
-                    .filter { it.displayId != Display.DEFAULT_DISPLAY }
+                    .filter {
+                        it.displayId != Display.DEFAULT_DISPLAY &&
+                            (!filterDeviceTypeFlag || it.type in RECORDABLE_DISPLAY_TYPES)
+                    }
                     .map {
                         ScreenShareOption(
                             ENTIRE_SCREEN,
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt
index 9eeb3b9..b6b8ffa 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/data/repository/ScreenRecordRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.screenrecord.data.repository
 
+import android.media.projection.StopReason
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.screenrecord.RecordingController
@@ -41,7 +42,7 @@
     val screenRecordState: Flow<ScreenRecordModel>
 
     /** Stops the recording. */
-    suspend fun stopRecording()
+    suspend fun stopRecording(@StopReason stopReason: Int)
 }
 
 @SysUISingleton
@@ -95,7 +96,7 @@
         }
     }
 
-    override suspend fun stopRecording() {
-        withContext(bgCoroutineContext) { recordingController.stopRecording() }
+    override suspend fun stopRecording(@StopReason stopReason: Int) {
+        withContext(bgCoroutineContext) { recordingController.stopRecording(stopReason) }
     }
 }
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 b8ea8f9..c5c705c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
@@ -29,6 +29,7 @@
 import android.view.ViewGroup
 import android.view.WindowInsets
 import android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+import android.view.accessibility.AccessibilityEvent
 import android.widget.FrameLayout
 import android.widget.ImageView
 import com.android.systemui.res.R
@@ -83,6 +84,20 @@
         })
 
         gestureDetector.setIsLongpressEnabled(false)
+
+        // Extend the timeout on any accessibility event (e.g. voice access or explore-by-touch).
+        setAccessibilityDelegate(
+            object : AccessibilityDelegate() {
+                override fun onRequestSendAccessibilityEvent(
+                    host: ViewGroup,
+                    child: View,
+                    event: AccessibilityEvent,
+                ): Boolean {
+                    userInteractionCallback?.invoke()
+                    return super.onRequestSendAccessibilityEvent(host, child, event)
+                }
+            }
+        )
     }
 
     override fun onFinishInflate() {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 8c004c4..6844f05 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -48,7 +48,7 @@
 import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel;
 import com.android.systemui.compose.ComposeInitializer;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.qs.flags.QSComposeFragment;
+import com.android.systemui.qs.flags.QsInCompose;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
@@ -96,7 +96,7 @@
         super.onCreate(savedInstanceState);
         setWindowAttributes();
         View view;
-        if (!QSComposeFragment.isEnabled()) {
+        if (!QsInCompose.isEnabled()) {
             setContentView(R.layout.brightness_mirror_container);
             view = findViewById(R.id.brightness_mirror_container);
             setDialogContent((FrameLayout) view);
@@ -140,7 +140,7 @@
         window.getDecorView();
         window.setLayout(WRAP_CONTENT, WRAP_CONTENT);
         getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false);
-        if (QSComposeFragment.isEnabled()) {
+        if (QsInCompose.isEnabled()) {
             window.getDecorView().addOnAttachStateChangeListener(
                     new View.OnAttachStateChangeListener() {
                         @Override
@@ -217,7 +217,7 @@
     @Override
     protected void onStart() {
         super.onStart();
-        if (!QSComposeFragment.isEnabled()) {
+        if (!QsInCompose.isEnabled()) {
             mBrightnessController.registerCallbacks();
         }
         MetricsLogger.visible(this, MetricsEvent.BRIGHTNESS_DIALOG);
@@ -241,7 +241,7 @@
     protected void onStop() {
         super.onStop();
         MetricsLogger.hidden(this, MetricsEvent.BRIGHTNESS_DIALOG);
-        if (!QSComposeFragment.isEnabled()) {
+        if (!QsInCompose.isEnabled()) {
             mBrightnessController.unregisterCallbacks();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 49ceba8..31780a5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -37,9 +37,11 @@
 import androidx.lifecycle.LifecycleRegistry
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.theme.PlatformTheme
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.Flags
+import com.android.systemui.Flags.communalHubOnMobile
 import com.android.systemui.ambient.touch.TouchMonitor
 import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
 import com.android.systemui.communal.dagger.Communal
@@ -70,7 +72,6 @@
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Controller that's responsible for the glanceable hub container view and its touch handling.
@@ -513,14 +514,19 @@
         val touchOnUmo = keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt())
         val touchOnSmartspace =
             lockscreenSmartspaceController.isWithinSmartspaceBounds(ev.x.toInt(), ev.y.toInt())
-        if (!hubShowing && (touchOnNotifications || touchOnUmo || touchOnSmartspace)) {
+        val glanceableHubV2 = communalHubOnMobile()
+        if (
+            !hubShowing &&
+                (touchOnNotifications || touchOnUmo || touchOnSmartspace || glanceableHubV2)
+        ) {
             logger.d({
                 "Lockscreen touch ignored: touchOnNotifications: $bool1, touchOnUmo: $bool2, " +
-                    "touchOnSmartspace: $bool3"
+                    "touchOnSmartspace: $bool3, glanceableHubV2: $bool4"
             }) {
                 bool1 = touchOnNotifications
                 bool2 = touchOnUmo
                 bool3 = touchOnSmartspace
+                bool4 = glanceableHubV2
             }
             return false
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 42a756c..3a6c250 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -172,6 +172,7 @@
 import com.android.systemui.shade.data.repository.FlingInfo;
 import com.android.systemui.shade.data.repository.ShadeRepository;
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.GestureRecorder;
@@ -186,13 +187,15 @@
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.ConversationNotificationManager;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -217,17 +220,15 @@
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ShadeTouchableRegionManager;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.TapAgainViewController;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.util.Compile;
@@ -298,7 +299,7 @@
      * The minimum scale to "squish" the Shade and associated elements down to, for Back gesture
      */
     public static final float SHADE_BACK_ANIM_MIN_SCALE = 0.9f;
-    private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+    private final ShadeTouchableRegionManager mShadeTouchableRegionManager;
     private final Resources mResources;
     private final KeyguardStateController mKeyguardStateController;
     private final SysuiStatusBarStateController mStatusBarStateController;
@@ -695,7 +696,7 @@
             ShadeLogger shadeLogger,
             @ShadeDisplayAware ConfigurationController configurationController,
             Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
-            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+            ShadeTouchableRegionManager shadeTouchableRegionManager,
             ConversationNotificationManager conversationNotificationManager,
             MediaHierarchyManager mediaHierarchyManager,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -840,7 +841,7 @@
         mVibratorHelper = vibratorHelper;
         mMSDLPlayer = msdlPlayer;
         mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
-        mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
+        mShadeTouchableRegionManager = shadeTouchableRegionManager;
         mSystemClock = systemClock;
         mKeyguardMediaController = keyguardMediaController;
         mMetricsLogger = metricsLogger;
@@ -1551,7 +1552,7 @@
 
     private Rect calculateGestureExclusionRect() {
         Rect exclusionRect = null;
-        Region touchableRegion = mStatusBarTouchableRegionManager.calculateTouchableRegion();
+        Region touchableRegion = mShadeTouchableRegionManager.calculateTouchableRegion();
         if (isFullyCollapsed() && touchableRegion != null) {
             // Note: The manager also calculates the non-pinned touchable region
             exclusionRect = touchableRegion.getBounds();
@@ -2269,7 +2270,11 @@
     }
 
     float getDisplayDensity() {
-        return mCentralSurfaces.getDisplayDensity();
+        if (ShadeWindowGoesAround.isEnabled()) {
+            return mView.getContext().getResources().getConfiguration().densityDpi;
+        } else {
+            return mCentralSurfaces.getDisplayDensity();
+        }
     }
 
     /** Return whether a touch is near the gesture handle at the bottom of screen */
@@ -3830,7 +3835,7 @@
                     /* screenOnFromTouch=*/ getWakefulness().isAwakeFromTapOrGesture());
             // Log collapse gesture if on lock screen.
             if (!expand && onKeyguard) {
-                float displayDensity = mCentralSurfaces.getDisplayDensity();
+                float displayDensity = getDisplayDensity();
                 int heightDp = (int) Math.abs((y - mInitialExpandY) / displayDensity);
                 int velocityDp = (int) Math.abs(vel / displayDensity);
                 mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 4d77e3e..69b3cc8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -27,9 +27,10 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.os.Build;
 import android.os.RemoteException;
 import android.os.Trace;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.Display;
 import android.view.IWindow;
@@ -37,10 +38,10 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
+import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManagerGlobal;
 
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Flags;
@@ -73,6 +74,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.settings.SecureSettings;
 
 import dagger.Lazy;
 
@@ -100,11 +102,12 @@
 
     private final Context mContext;
     private final WindowRootViewComponent.Factory mWindowRootViewComponentFactory;
-    private final ViewCaptureAwareWindowManager mWindowManager;
+    private final WindowManager mWindowManager;
     private final IActivityManager mActivityManager;
     private final DozeParameters mDozeParameters;
     private final KeyguardStateController mKeyguardStateController;
     private final ShadeWindowLogger mLogger;
+    private final LayoutParams mShadeWindowLayoutParams;
     private final LayoutParams mLpChanged;
     private final long mLockScreenDisplayTimeout;
     private final float mKeyguardPreferredRefreshRate; // takes precedence over max
@@ -130,6 +133,7 @@
 
     private final SysuiColorExtractor mColorExtractor;
     private final NotificationShadeWindowModel mNotificationShadeWindowModel;
+    private final SecureSettings mSecureSettings;
     /**
      * Layout params would be aggregated and dispatched all at once if this is > 0.
      *
@@ -145,7 +149,7 @@
     public NotificationShadeWindowControllerImpl(
             @ShadeDisplayAware Context context,
             WindowRootViewComponent.Factory windowRootViewComponentFactory,
-            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+            @ShadeDisplayAware WindowManager windowManager,
             IActivityManager activityManager,
             DozeParameters dozeParameters,
             StatusBarStateController statusBarStateController,
@@ -163,14 +167,17 @@
             Lazy<SelectedUserInteractor> userInteractor,
             UserTracker userTracker,
             NotificationShadeWindowModel notificationShadeWindowModel,
-            Lazy<CommunalInteractor> communalInteractor) {
+            SecureSettings secureSettings,
+            Lazy<CommunalInteractor> communalInteractor,
+            @ShadeDisplayAware LayoutParams shadeWindowLayoutParams) {
         mContext = context;
         mWindowRootViewComponentFactory = windowRootViewComponentFactory;
-        mWindowManager = viewCaptureAwareWindowManager;
+        mWindowManager = windowManager;
         mActivityManager = activityManager;
         mDozeParameters = dozeParameters;
         mKeyguardStateController = keyguardStateController;
         mLogger = logger;
+        mShadeWindowLayoutParams = shadeWindowLayoutParams;
         mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
         mLpChanged = new LayoutParams();
         mKeyguardViewMediator = keyguardViewMediator;
@@ -178,6 +185,7 @@
         mBackgroundExecutor = backgroundExecutor;
         mColorExtractor = colorExtractor;
         mNotificationShadeWindowModel = notificationShadeWindowModel;
+        mSecureSettings = secureSettings;
         // prefix with {slow} to make sure this dumps at the END of the critical section.
         dumpManager.registerCriticalDumpable("{slow}NotificationShadeWindowControllerImpl", this);
         mAuthController = authController;
@@ -266,7 +274,9 @@
         // Now that the notification shade encompasses the sliding panel and its
         // translucent backdrop, the entire thing is made TRANSLUCENT and is
         // hardware-accelerated.
-        mLp = ShadeWindowLayoutParams.INSTANCE.create(mContext);
+        // mLP is assigned here (instead of the constructor) as its null value is also used to check
+        // if the shade window has been attached.
+        mLp = mShadeWindowLayoutParams;
         mWindowManager.addView(mWindowRootView, mLp);
 
         // We use BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE here, however, there is special logic in
@@ -400,7 +410,7 @@
                     (long) mLpChanged.preferredMaxDisplayRefreshRate);
         }
 
-        if (state.bouncerShowing && !isDebuggable()) {
+        if (state.bouncerShowing && !isSecureWindowsDisabled()) {
             mLpChanged.flags |= LayoutParams.FLAG_SECURE;
         } else {
             mLpChanged.flags &= ~LayoutParams.FLAG_SECURE;
@@ -413,8 +423,11 @@
         }
     }
 
-    protected boolean isDebuggable() {
-        return Build.IS_DEBUGGABLE;
+    private boolean isSecureWindowsDisabled() {
+        return mSecureSettings.getIntForUser(
+            Settings.Secure.DISABLE_SECURE_WINDOWS,
+            0,
+            UserHandle.USER_CURRENT) == 1;
     }
 
     private void adjustScreenOrientation(NotificationShadeWindowState state) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 04f89be..0df2299 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -98,7 +98,7 @@
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.phone.ShadeTouchableRegionManager;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
@@ -141,7 +141,7 @@
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private final NotificationShadeDepthController mDepthController;
     private final ShadeHeaderController mShadeHeaderController;
-    private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+    private final ShadeTouchableRegionManager mShadeTouchableRegionManager;
     private final Provider<StatusBarLongPressGestureDetector> mStatusBarLongPressGestureDetector;
     private final KeyguardStateController mKeyguardStateController;
     private final KeyguardBypassController mKeyguardBypassController;
@@ -317,7 +317,7 @@
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
             NotificationShadeDepthController notificationShadeDepthController,
             ShadeHeaderController shadeHeaderController,
-            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+            ShadeTouchableRegionManager shadeTouchableRegionManager,
             Provider<StatusBarLongPressGestureDetector> statusBarLongPressGestureDetector,
             KeyguardStateController keyguardStateController,
             KeyguardBypassController keyguardBypassController,
@@ -366,7 +366,7 @@
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
         mDepthController = notificationShadeDepthController;
         mShadeHeaderController = shadeHeaderController;
-        mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
+        mShadeTouchableRegionManager = shadeTouchableRegionManager;
         mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
         mKeyguardStateController = keyguardStateController;
         mKeyguardBypassController = keyguardBypassController;
@@ -695,7 +695,7 @@
                 /* right= */ (int) mQsFrame.getX() + mQsFrame.getWidth(),
                 /* bottom= */ headerBottom + frameTop);
         // Also allow QS to intercept if the touch is near the notch.
-        mStatusBarTouchableRegionManager.updateRegionForNotch(mInterceptRegion);
+        mShadeTouchableRegionManager.updateRegionForNotch(mInterceptRegion);
         final boolean onHeader = mInterceptRegion.contains((int) x, (int) y);
 
         if (getExpanded()) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index bf2ea56..ff39a3d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -17,9 +17,11 @@
 package com.android.systemui.shade
 
 import android.content.Context
-import android.content.MutableContextWrapper
 import android.content.res.Resources
 import android.view.LayoutInflater
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams
+import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE
 import com.android.systemui.CoreStartable
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.common.ui.ConfigurationStateImpl
@@ -29,16 +31,23 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
 import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
 import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl
+import com.android.systemui.shade.display.ShadeDisplayPolicyModule
+import com.android.systemui.shade.domain.interactor.ShadeDisplaysInteractor
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
 import com.android.systemui.statusbar.phone.ConfigurationForwarder
 import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.BindsOptionalOf
 import dagger.Module
 import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
+import javax.inject.Provider
 
 /**
  * Module responsible for managing display-specific components and resources for the notification
@@ -52,7 +61,7 @@
  * By using this dedicated module, we ensure the notification shade window always utilizes the
  * correct display context and resources, regardless of the display it's on.
  */
-@Module
+@Module(includes = [OptionalShadeDisplayAwareBindings::class, ShadeDisplayPolicyModule::class])
 object ShadeDisplayAwareModule {
 
     /** Creates a new context for the shade window. */
@@ -61,7 +70,9 @@
     @SysUISingleton
     fun provideShadeDisplayAwareContext(context: Context): Context {
         return if (ShadeWindowGoesAround.isEnabled) {
-            MutableContextWrapper(context)
+            context
+                .createWindowContext(context.display, TYPE_NOTIFICATION_SHADE, /* options= */ null)
+                .apply { setTheme(R.style.Theme_SystemUI) }
         } else {
             context
         }
@@ -70,6 +81,27 @@
     @Provides
     @ShadeDisplayAware
     @SysUISingleton
+    fun provideShadeWindowLayoutParams(@ShadeDisplayAware context: Context): LayoutParams {
+        return ShadeWindowLayoutParams.create(context)
+    }
+
+    @Provides
+    @ShadeDisplayAware
+    @SysUISingleton
+    fun provideShadeWindowManager(
+        defaultWindowManager: WindowManager,
+        @ShadeDisplayAware context: Context,
+    ): WindowManager {
+        return if (ShadeWindowGoesAround.isEnabled) {
+            context.getSystemService(WindowManager::class.java) as WindowManager
+        } else {
+            defaultWindowManager
+        }
+    }
+
+    @Provides
+    @ShadeDisplayAware
+    @SysUISingleton
     fun provideShadeDisplayAwareResources(@ShadeDisplayAware context: Context): Resources {
         return context.resources
     }
@@ -159,14 +191,39 @@
         return impl
     }
 
+    @SysUISingleton
+    @Provides
+    fun provideMutableShadePositionRepository(
+        impl: ShadeDisplaysRepositoryImpl
+    ): MutableShadeDisplaysRepository {
+        ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+        return impl
+    }
+
     @Provides
     @IntoMap
     @ClassKey(ShadePrimaryDisplayCommand::class)
-    fun provideShadePrimaryDisplayCommand(impl: ShadePrimaryDisplayCommand): CoreStartable {
+    fun provideShadePrimaryDisplayCommand(impl: Provider<ShadePrimaryDisplayCommand>): CoreStartable {
         return if (ShadeWindowGoesAround.isEnabled) {
-            impl
+            impl.get()
         } else {
             CoreStartable.NOP
         }
     }
+
+    @Provides
+    @IntoMap
+    @ClassKey(ShadeDisplaysInteractor::class)
+    fun provideShadeDisplaysInteractor(impl: Provider<ShadeDisplaysInteractor>): CoreStartable {
+        return if (ShadeWindowGoesAround.isEnabled) {
+            impl.get()
+        } else {
+            CoreStartable.NOP
+        }
+    }
+}
+
+@Module
+internal interface OptionalShadeDisplayAwareBindings {
+    @BindsOptionalOf fun bindOptionalOfWindowRootView(): WindowRootView
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
index a5d9e96..a54f6b9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
@@ -20,11 +20,14 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.display.data.repository.DisplayRepository
-import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
+import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
+import com.android.systemui.shade.display.ShadeDisplayPolicy
+import com.android.systemui.shade.display.SpecificDisplayIdPolicy
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import java.io.PrintWriter
 import javax.inject.Inject
+import kotlin.text.toIntOrNull
 
 @SysUISingleton
 class ShadePrimaryDisplayCommand
@@ -32,7 +35,9 @@
 constructor(
     private val commandRegistry: CommandRegistry,
     private val displaysRepository: DisplayRepository,
-    private val positionRepository: ShadeDisplaysRepository,
+    private val positionRepository: MutableShadeDisplaysRepository,
+    private val policies: Set<@JvmSuppressWildcards ShadeDisplayPolicy>,
+    private val defaultPolicy: ShadeDisplayPolicy,
 ) : Command, CoreStartable {
 
     override fun start() {
@@ -40,8 +45,11 @@
     }
 
     override fun help(pw: PrintWriter) {
-        pw.println("shade_display_override <displayId> ")
-        pw.println("Set the display which is holding the shade.")
+        pw.println("shade_display_override (<displayId>|<policyName>) ")
+        pw.println("Set the display which is holding the shade, or the policy that defines it.")
+        pw.println()
+        pw.println("shade_display_override policies")
+        pw.println("Lists available policies")
         pw.println()
         pw.println("shade_display_override reset ")
         pw.println("Reset the display which is holding the shade.")
@@ -68,21 +76,27 @@
                 "reset" -> reset()
                 "list",
                 "status" -> printStatus()
+                "policies" -> printPolicies()
                 "any_external" -> anyExternal()
-                else -> {
-                    val cmdAsInteger = command?.toIntOrNull()
-                    if (cmdAsInteger != null) {
-                        changeDisplay(displayId = cmdAsInteger)
-                    } else {
-                        help(pw)
-                    }
+                null -> help(pw)
+                else -> parsePolicy(command)
+            }
+        }
+
+        private fun parsePolicy(policyIdentifier: String) {
+            val displayId = policyIdentifier.toIntOrNull()
+            when {
+                displayId != null -> changeDisplay(displayId = displayId)
+                policies.any { it.name == policyIdentifier } -> {
+                    positionRepository.policy.value = policies.first { it.name == policyIdentifier }
                 }
+                else -> help(pw)
             }
         }
 
         private fun reset() {
-            positionRepository.resetDisplayId()
-            pw.println("Reset shade primary display id to ${Display.DEFAULT_DISPLAY}")
+            positionRepository.policy.value = defaultPolicy
+            pw.println("Reset shade display policy to default policy: ${defaultPolicy.name}")
         }
 
         private fun printStatus() {
@@ -95,6 +109,15 @@
             }
         }
 
+        private fun printPolicies() {
+            val currentPolicyName = positionRepository.policy.value.name
+            pw.println("Available policies: ")
+            policies.forEach {
+                pw.print(" - ${it.name}")
+                pw.println(if (currentPolicyName == it.name) " (Current policy)" else "")
+            }
+        }
+
         private fun anyExternal() {
             val anyExternalDisplay =
                 displaysRepository.displays.value.firstOrNull {
@@ -116,7 +139,7 @@
         }
 
         private fun setDisplay(id: Int) {
-            positionRepository.setDisplayId(id)
+            positionRepository.policy.value = SpecificDisplayIdPolicy(id)
             pw.println("New shade primary display id is $id")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 7346a28..9ca23f0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -19,8 +19,8 @@
 import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
 import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
 import com.android.systemui.statusbar.GestureRecorder
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.policy.HeadsUpManager
 
 /**
  * Allows CentralSurfacesImpl to interact with the shade. Only CentralSurfacesImpl should reference
@@ -37,7 +37,7 @@
         centralSurfaces: CentralSurfaces,
         recorder: GestureRecorder,
         hideExpandedRunnable: Runnable,
-        headsUpManager: HeadsUpManager
+        headsUpManager: HeadsUpManager,
     )
 
     /** Cancels any pending collapses. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
index ec4018c..3bbda16 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.shade
 
 import com.android.systemui.statusbar.GestureRecorder
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import javax.inject.Inject
 
 class ShadeSurfaceImpl @Inject constructor() : ShadeSurface, ShadeViewControllerEmptyImpl() {
@@ -26,7 +26,7 @@
         centralSurfaces: CentralSurfaces,
         recorder: GestureRecorder,
         hideExpandedRunnable: Runnable,
-        headsUpManager: HeadsUpManager
+        headsUpManager: HeadsUpManager,
     ) {}
 
     override fun cancelPendingCollapse() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
index 71c5658..732d4d1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
@@ -23,14 +23,14 @@
 class FakeShadeDisplayRepository : ShadeDisplaysRepository {
     private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
 
-    override fun setDisplayId(displayId: Int) {
+    fun setDisplayId(displayId: Int) {
         _displayId.value = displayId
     }
 
     override val displayId: StateFlow<Int>
         get() = _displayId
 
-    override fun resetDisplayId() {
+    fun resetDisplayId() {
         _displayId.value = Display.DEFAULT_DISPLAY
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
index 4a95e33..756241e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
@@ -18,37 +18,40 @@
 
 import android.view.Display
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shade.display.ShadeDisplayPolicy
 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.flatMapLatest
+import kotlinx.coroutines.flow.stateIn
 
+/** Source of truth for the display currently holding the shade. */
 interface ShadeDisplaysRepository {
     /** ID of the display which currently hosts the shade */
     val displayId: StateFlow<Int>
-
-    /**
-     * Updates the value of the shade display id stored, emitting to the new display id to every
-     * component dependent on the shade display id
-     */
-    fun setDisplayId(displayId: Int)
-
-    /** Resets value of shade primary display to the default display */
-    fun resetDisplayId()
 }
 
-/** Source of truth for the display currently holding the shade. */
+/** Allows to change the policy that determines in which display the Shade window is visible. */
+interface MutableShadeDisplaysRepository : ShadeDisplaysRepository {
+    /** Updates the policy to select where the shade is visible. */
+    val policy: MutableStateFlow<ShadeDisplayPolicy>
+}
+
+/** Keeps the policy and propagates the display id for the shade from it. */
 @SysUISingleton
-class ShadeDisplaysRepositoryImpl @Inject constructor() : ShadeDisplaysRepository {
-    private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
+@OptIn(ExperimentalCoroutinesApi::class)
+class ShadeDisplaysRepositoryImpl
+@Inject
+constructor(defaultPolicy: ShadeDisplayPolicy, @Background bgScope: CoroutineScope) :
+    MutableShadeDisplaysRepository {
+    override val policy = MutableStateFlow<ShadeDisplayPolicy>(defaultPolicy)
 
-    override val displayId: StateFlow<Int>
-        get() = _displayId
-
-    override fun setDisplayId(displayId: Int) {
-        _displayId.value = displayId
-    }
-
-    override fun resetDisplayId() {
-        _displayId.value = Display.DEFAULT_DISPLAY
-    }
+    override val displayId: StateFlow<Int> =
+        policy
+            .flatMapLatest { it.displayId }
+            .stateIn(bgScope, SharingStarted.WhileSubscribed(), Display.DEFAULT_DISPLAY)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicy.kt
new file mode 100644
index 0000000..3f6c949
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicy.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.shade.display
+
+import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Returns an external display if one exists, otherwise the default display.
+ *
+ * If there are multiple external displays, the one with minimum display ID is returned.
+ */
+@SysUISingleton
+class AnyExternalShadeDisplayPolicy
+@Inject
+constructor(displayRepository: DisplayRepository, @Background bgScope: CoroutineScope) :
+    ShadeDisplayPolicy {
+    override val name: String
+        get() = "any_external_display"
+
+    override val displayId: StateFlow<Int> =
+        displayRepository.displays
+            .map { displays ->
+                displays
+                    .filter { it.displayId != DEFAULT_DISPLAY && it.type in ALLOWED_DISPLAY_TYPES }
+                    .minOfOrNull { it.displayId } ?: DEFAULT_DISPLAY
+            }
+            .stateIn(bgScope, SharingStarted.WhileSubscribed(), DEFAULT_DISPLAY)
+
+    private companion object {
+        val ALLOWED_DISPLAY_TYPES =
+            setOf(Display.TYPE_EXTERNAL, Display.TYPE_OVERLAY, Display.TYPE_WIFI)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
new file mode 100644
index 0000000..bb96b0b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.shade.display
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import kotlinx.coroutines.flow.StateFlow
+
+/** Describes the display the shade should be shown in. */
+interface ShadeDisplayPolicy {
+    val name: String
+
+    /** The display id the shade should be at, according to this policy. */
+    val displayId: StateFlow<Int>
+}
+
+@Module
+interface ShadeDisplayPolicyModule {
+
+    @Binds fun provideDefaultPolicy(impl: StatusBarTouchShadeDisplayPolicy): ShadeDisplayPolicy
+
+    @IntoSet
+    @Binds
+    fun provideDefaultDisplayPolicyToSet(impl: DefaultDisplayShadePolicy): ShadeDisplayPolicy
+
+    @IntoSet
+    @Binds
+    fun provideAnyExternalShadeDisplayPolicyToSet(
+        impl: AnyExternalShadeDisplayPolicy
+    ): ShadeDisplayPolicy
+
+    @Binds
+    @IntoSet
+    fun provideStatusBarTouchShadeDisplayPolicy(
+        impl: StatusBarTouchShadeDisplayPolicy
+    ): ShadeDisplayPolicy
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt
new file mode 100644
index 0000000..d43aad7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.shade.display
+
+import android.view.Display
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Policy to specify a display id explicitly. */
+open class SpecificDisplayIdPolicy(id: Int) : ShadeDisplayPolicy {
+    override val name: String = "display_${id}_policy"
+
+    override val displayId: StateFlow<Int> = MutableStateFlow(id)
+}
+
+class DefaultDisplayShadePolicy @Inject constructor() :
+    SpecificDisplayIdPolicy(Display.DEFAULT_DISPLAY)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
new file mode 100644
index 0000000..22e9487
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.display
+
+import android.util.Log
+import android.view.Display
+import com.android.app.tracing.coroutines.launchTraced
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/**
+ * Moves the shade on the last display that received a status bar touch.
+ *
+ * If the display is removed, falls back to the default one.
+ */
+@SysUISingleton
+class StatusBarTouchShadeDisplayPolicy
+@Inject
+constructor(displayRepository: DisplayRepository, @Background val backgroundScope: CoroutineScope) :
+    ShadeDisplayPolicy {
+    override val name: String
+        get() = "status_bar_latest_touch"
+
+    private val currentDisplayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
+    private val availableDisplayIds: StateFlow<Set<Int>> = displayRepository.displayIds
+
+    override val displayId: StateFlow<Int>
+        get() = currentDisplayId
+
+    private var removalListener: Job? = null
+
+    /** Called when the status bar on the given display is touched. */
+    fun onStatusBarTouched(statusBarDisplayId: Int) {
+        ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+        if (statusBarDisplayId !in availableDisplayIds.value) {
+            Log.e(TAG, "Got touch on unknown display $statusBarDisplayId")
+            return
+        }
+        currentDisplayId.value = statusBarDisplayId
+        if (removalListener == null) {
+            // Lazy start this at the first invocation. it's fine to let it run also when the policy
+            // is not selected anymore, as the job doesn't do anything until someone subscribes to
+            // displayId.
+            removalListener = monitorDisplayRemovals()
+        }
+    }
+
+    private fun monitorDisplayRemovals(): Job {
+        return backgroundScope.launchTraced("StatusBarTouchDisplayPolicy#monitorDisplayRemovals") {
+            currentDisplayId.subscriptionCount
+                .map { it > 0 }
+                .distinctUntilChanged()
+                // When Active is false, no collect happens, and the old one is cancelled.
+                // This is needed to prevent "availableDisplayIds" collection while nobody is
+                // listening at the flow provided by this class.
+                .collectLatest { active ->
+                    if (active) {
+                        availableDisplayIds.collect { availableIds ->
+                            if (currentDisplayId.value !in availableIds) {
+                                currentDisplayId.value = Display.DEFAULT_DISPLAY
+                            }
+                        }
+                    }
+                }
+        }
+    }
+
+    private companion object {
+        const val TAG = "StatusBarTouchDisplayPolicy"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
index 18dbba1..3414867 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -16,28 +16,23 @@
 
 package com.android.systemui.shade.domain.interactor
 
-import android.content.ComponentCallbacks
 import android.content.Context
-import android.content.MutableContextWrapper
-import android.content.res.Configuration
-import android.content.res.Resources
 import android.util.Log
 import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE
+import android.view.WindowManager.LayoutParams
+import androidx.annotation.UiThread
 import com.android.app.tracing.coroutines.launchTraced
 import com.android.app.tracing.traceSection
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
-import com.android.systemui.display.shared.model.DisplayWindowProperties
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.ShadeDisplayAware
-import com.android.systemui.shade.ShadeWindowLayoutParams
 import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
-import com.android.systemui.statusbar.phone.ConfigurationForwarder
+import com.android.systemui.util.kotlin.getOrNull
+import java.util.Optional
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
@@ -48,19 +43,24 @@
 class ShadeDisplaysInteractor
 @Inject
 constructor(
-    private val shadeRootView: WindowRootView,
+    optionalShadeRootView: Optional<WindowRootView>,
     private val shadePositionRepository: ShadeDisplaysRepository,
     @ShadeDisplayAware private val shadeContext: Context,
-    @ShadeDisplayAware private val shadeResources: Resources,
-    private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+    @ShadeDisplayAware private val shadeLayoutParams: LayoutParams,
+    @ShadeDisplayAware private val wm: WindowManager,
     @Background private val bgScope: CoroutineScope,
-    @ShadeDisplayAware private val shadeConfigurationForwarder: ConfigurationForwarder,
     @Main private val mainThreadContext: CoroutineContext,
 ) : CoreStartable {
 
-    // TODO: b/362719719 - Get rid of this callback as the root view should automatically get the
-    //  correct configuration once it's moved to another window.
-    private var unregisterConfigChangedCallbacks: (() -> Unit)? = null
+    private val shadeRootView =
+        optionalShadeRootView.getOrNull()
+            ?: error(
+                """
+            ShadeRootView must be provided for this ShadeDisplayInteractor to work.
+            If it is not, it means this is being instantiated in a SystemUI variant that shouldn't.
+            """
+                    .trimIndent()
+            )
 
     override fun start() {
         ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
@@ -83,7 +83,7 @@
             return
         }
         try {
-            moveShadeWindow(fromId = currentId, toId = destinationId)
+            withContext(mainThreadContext) { moveShadeWindow(toId = destinationId) }
         } catch (e: IllegalStateException) {
             Log.e(
                 TAG,
@@ -93,68 +93,26 @@
         }
     }
 
-    private suspend fun moveShadeWindow(fromId: Int, toId: Int) {
-        val (_, _, _, sourceWm) = getDisplayWindowProperties(fromId)
-        val (_, _, destContext, destWm) = getDisplayWindowProperties(toId)
-        withContext(mainThreadContext) {
-            traceSection({ "MovingShadeWindow from $fromId to $toId" }) {
-                removeShade(sourceWm)
-                addShade(destWm)
-                overrideContextAndResources(newContext = destContext)
-                registerConfigurationChange(destContext)
-            }
-            traceSection("ShadeDisplaysInteractor#onConfigurationChanged") {
-                dispatchConfigurationChanged(destContext.resources.configuration)
-            }
+    @UiThread
+    private fun moveShadeWindow(toId: Int) {
+        traceSection({ "moveShadeWindow  to $toId" }) {
+            removeShadeWindow()
+            updateContextDisplay(toId)
+            addShadeWindow()
         }
     }
 
-    private fun removeShade(wm: WindowManager): Unit =
-        traceSection("removeView") { wm.removeView(shadeRootView) }
+    @UiThread
+    private fun removeShadeWindow(): Unit =
+        traceSection("removeShadeWindow") { wm.removeView(shadeRootView) }
 
-    private fun addShade(wm: WindowManager): Unit =
-        traceSection("addView") {
-            wm.addView(shadeRootView, ShadeWindowLayoutParams.create(shadeContext))
-        }
+    @UiThread
+    private fun addShadeWindow(): Unit =
+        traceSection("addShadeWindow") { wm.addView(shadeRootView, shadeLayoutParams) }
 
-    private fun overrideContextAndResources(newContext: Context) {
-        val contextWrapper =
-            shadeContext as? MutableContextWrapper
-                ?: error("Shade context is not a MutableContextWrapper!")
-        contextWrapper.baseContext = newContext
-        // Override needed in case someone is keeping a reference to the resources from the old
-        // context.
-        // TODO: b/362719719 - This shouldn't be needed, as resources should be updated when the
-        //  window is moved to the new display automatically.
-        shadeResources.impl = shadeContext.resources.impl
-    }
-
-    private fun dispatchConfigurationChanged(newConfig: Configuration) {
-        shadeConfigurationForwarder.onConfigurationChanged(newConfig)
-        shadeRootView.dispatchConfigurationChanged(newConfig)
-        shadeRootView.requestLayout()
-    }
-
-    private fun registerConfigurationChange(context: Context) {
-        // we should keep only one at the time.
-        unregisterConfigChangedCallbacks?.invoke()
-        val callback =
-            object : ComponentCallbacks {
-                override fun onConfigurationChanged(newConfig: Configuration) {
-                    dispatchConfigurationChanged(newConfig)
-                }
-
-                override fun onLowMemory() {}
-            }
-        context.registerComponentCallbacks(callback)
-        unregisterConfigChangedCallbacks = {
-            context.unregisterComponentCallbacks(callback)
-            unregisterConfigChangedCallbacks = null
-        }
-    }
-
-    private fun getDisplayWindowProperties(displayId: Int): DisplayWindowProperties {
-        return displayWindowPropertiesRepository.get(displayId, TYPE_NOTIFICATION_SHADE)
+    @UiThread
+    private fun updateContextDisplay(newDisplayId: Int) {
+        traceSection("updateContextDisplay") { shadeContext.updateDisplay(newDisplayId) }
     }
 
     private companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
index ce4c081..02531221 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
@@ -30,15 +30,21 @@
 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
+import com.android.systemui.statusbar.disableflags.domain.interactor.DisableFlagsInteractor
 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 
 /**
  * Models UI state used to render the content of the shade scene.
@@ -54,6 +60,7 @@
     val brightnessMirrorViewModelFactory: BrightnessMirrorViewModel.Factory,
     val mediaCarouselInteractor: MediaCarouselInteractor,
     shadeInteractor: ShadeInteractor,
+    private val disableFlagsInteractor: DisableFlagsInteractor,
     private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
     private val footerActionsController: FooterActionsController,
     private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
@@ -70,12 +77,21 @@
 
     val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation
 
+    private val _isQsEnabled =
+        MutableStateFlow(!disableFlagsInteractor.disableFlags.value.isQuickSettingsEnabled())
+    val isQsEnabled: StateFlow<Boolean> = _isQsEnabled.asStateFlow()
+
     private val footerActionsControllerInitialized = AtomicBoolean(false)
 
-    override suspend fun onActivated(): Nothing {
-        deviceEntryInteractor.isDeviceEntered.collect { isDeviceEntered ->
-            _isEmptySpaceClickable.value = !isDeviceEntered
-        }
+    override suspend fun onActivated(): Nothing = coroutineScope {
+        deviceEntryInteractor.isDeviceEntered
+            .onEach { isDeviceEntered -> _isEmptySpaceClickable.value = !isDeviceEntered }
+            .launchIn(this)
+        disableFlagsInteractor.disableFlags
+            .map { it.isQuickSettingsEnabled() }
+            .onEach { _isQsEnabled.value = it }
+            .launchIn(this)
+        awaitCancellation()
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
index b0777c9..c6752f8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
@@ -27,29 +27,33 @@
 
 /** Returns collection of [UserAction] to [UserActionResult] pairs for opening the single shade. */
 fun singleShadeActions(
-    requireTwoPointersForTopEdgeForQs: Boolean = false
+    isDownFromTopEdgeEnabled: Boolean = true,
+    requireTwoPointersForTopEdgeForQs: Boolean = false,
 ): Array<Pair<UserAction, UserActionResult>> {
-    val shadeUserActionResult = UserActionResult(Scenes.Shade, isIrreversible = true)
-    val qsSceneUserActionResult = UserActionResult(Scenes.QuickSettings, isIrreversible = true)
-    return arrayOf(
-        // Swiping down, not from the edge, always goes to shade.
-        Swipe.Down to shadeUserActionResult,
-        Swipe.Down(pointerCount = 2) to shadeUserActionResult,
-
-        // Swiping down from the top edge.
-        swipeDownFromTop(pointerCount = 1) to
-            if (requireTwoPointersForTopEdgeForQs) {
-                shadeUserActionResult
-            } else {
-                qsSceneUserActionResult
-            },
-        swipeDownFromTop(pointerCount = 2) to qsSceneUserActionResult,
-    )
+    val shadeUserActionResult = UserActionResult(Scenes.Shade)
+    val qsSceneUserActionResult = UserActionResult(Scenes.QuickSettings)
+    return buildList {
+            // Swiping down, not from the edge, always goes to shade.
+            add(Swipe.Down to shadeUserActionResult)
+            add(Swipe.Down(pointerCount = 2) to shadeUserActionResult)
+            if (isDownFromTopEdgeEnabled) {
+                add(
+                    swipeDownFromTop(pointerCount = 1) to
+                        if (requireTwoPointersForTopEdgeForQs) {
+                            shadeUserActionResult
+                        } else {
+                            qsSceneUserActionResult
+                        }
+                )
+                add(swipeDownFromTop(pointerCount = 2) to qsSceneUserActionResult)
+            }
+        }
+        .toTypedArray()
 }
 
 /** Returns collection of [UserAction] to [UserActionResult] pairs for opening the split shade. */
 fun splitShadeActions(): Array<Pair<UserAction, UserActionResult>> {
-    val shadeUserActionResult = UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true)
+    val shadeUserActionResult = UserActionResult(Scenes.Shade, ToSplitShade)
     return arrayOf(
         // Swiping down, not from the edge, always goes to shade.
         Swipe.Down to shadeUserActionResult,
@@ -62,10 +66,8 @@
 
 /** Returns collection of [UserAction] to [UserActionResult] pairs for opening the dual shade. */
 fun dualShadeActions(): Array<Pair<UserAction, UserActionResult>> {
-    val notifShadeUserActionResult =
-        UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
-    val qsShadeuserActionResult =
-        UserActionResult.ShowOverlay(Overlays.QuickSettingsShade, isIrreversible = true)
+    val notifShadeUserActionResult = UserActionResult.ShowOverlay(Overlays.NotificationsShade)
+    val qsShadeuserActionResult = UserActionResult.ShowOverlay(Overlays.QuickSettingsShade)
     return arrayOf(
         Swipe.Down to notifShadeUserActionResult,
         Swipe.Down(fromSource = SceneContainerEdge.TopRight) to qsShadeuserActionResult,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index e19fcd0..85b8bf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -39,24 +39,24 @@
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import java.io.PrintWriter
 import javax.inject.Inject
 import kotlin.math.max
 
 /**
  * A utility class that handles notification panel expansion when a user swipes downward on a
- * notification from the pulsing state.
- * If face-bypass is enabled, the user can swipe down anywhere on the screen (not just from a
- * notification) to trigger the notification panel expansion.
+ * notification from the pulsing state. If face-bypass is enabled, the user can swipe down anywhere
+ * on the screen (not just from a notification) to trigger the notification panel expansion.
  */
 @SysUISingleton
-class PulseExpansionHandler @Inject
+class PulseExpansionHandler
+@Inject
 constructor(
     context: Context,
     private val wakeUpCoordinator: NotificationWakeUpCoordinator,
@@ -67,7 +67,7 @@
     private val falsingManager: FalsingManager,
     private val shadeInteractor: ShadeInteractor,
     private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
-    dumpManager: DumpManager
+    dumpManager: DumpManager,
 ) : Gefingerpoken, Dumpable {
     companion object {
         private val SPRING_BACK_ANIMATION_LENGTH_MS = 375
@@ -91,14 +91,13 @@
                         pulseExpandAbortListener?.run()
                     }
                 }
-                headsUpManager.unpinAll(
-                    /*userUnPinned= */
-                    true,
-                )
+                headsUpManager.unpinAll(/* userUnPinned= */ true)
             }
         }
+
     var leavingLockscreen: Boolean = false
         private set
+
     private var touchSlop = 0f
     private var minDragDistance = 0
     private lateinit var stackScrollerController: NotificationStackScrollLayoutController
@@ -111,24 +110,27 @@
 
     private val isFalseTouch: Boolean
         get() = falsingManager.isFalseTouch(NOTIFICATION_DRAG_DOWN)
+
     var pulseExpandAbortListener: Runnable? = null
     var bouncerShowing: Boolean = false
 
     init {
         initResources(context)
-        configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
-            override fun onConfigChanged(newConfig: Configuration?) {
-                initResources(context)
+        configurationController.addCallback(
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration?) {
+                    initResources(context)
+                }
             }
-        })
+        )
 
         mPowerManager = context.getSystemService(PowerManager::class.java)
         dumpManager.registerDumpable(this)
     }
 
     private fun initResources(context: Context) {
-        minDragDistance = context.resources.getDimensionPixelSize(
-            R.dimen.keyguard_drag_down_min_distance)
+        minDragDistance =
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_drag_down_min_distance)
         touchSlop = ViewConfiguration.get(context).scaledTouchSlop.toFloat()
     }
 
@@ -137,7 +139,8 @@
     }
 
     private fun canHandleMotionEvent(): Boolean {
-        return wakeUpCoordinator.canShowPulsingHuns && !shadeInteractor.isQsExpanded.value &&
+        return wakeUpCoordinator.canShowPulsingHuns &&
+            !shadeInteractor.isQsExpanded.value &&
             !bouncerShowing
     }
 
@@ -189,18 +192,20 @@
     }
 
     override fun onTouchEvent(event: MotionEvent): Boolean {
-        val finishExpanding = (event.action == MotionEvent.ACTION_CANCEL ||
-            event.action == MotionEvent.ACTION_UP) && isExpanding
+        val finishExpanding =
+            (event.action == MotionEvent.ACTION_CANCEL || event.action == MotionEvent.ACTION_UP) &&
+                isExpanding
 
-        val isDraggingNotificationOrCanBypass = mStartingChild?.showingPulsing() == true ||
-            bypassController.canBypass()
+        val isDraggingNotificationOrCanBypass =
+            mStartingChild?.showingPulsing() == true || bypassController.canBypass()
         if ((!canHandleMotionEvent() || !isDraggingNotificationOrCanBypass) && !finishExpanding) {
             // We allow cancellations/finishing to still go through here to clean up the state
             return false
         }
 
-        if (velocityTracker == null || !isExpanding ||
-            event.actionMasked == MotionEvent.ACTION_DOWN) {
+        if (
+            velocityTracker == null || !isExpanding || event.actionMasked == MotionEvent.ACTION_DOWN
+        ) {
             return startExpansion(event)
         }
         velocityTracker!!.addMovement(event)
@@ -210,12 +215,11 @@
         when (event.actionMasked) {
             MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance)
             MotionEvent.ACTION_UP -> {
-                velocityTracker!!.computeCurrentVelocity(
-                    /* units= */
-                    1000,
-                )
-                val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 &&
-                    statusBarStateController.state != StatusBarState.SHADE
+                velocityTracker!!.computeCurrentVelocity(/* units= */ 1000)
+                val canExpand =
+                    moveDistance > 0 &&
+                        velocityTracker!!.getYVelocity() > -1000 &&
+                        statusBarStateController.state != StatusBarState.SHADE
                 if (!falsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
                     finishExpansion()
                 } else {
@@ -243,22 +247,16 @@
             mPowerManager!!.wakeUp(
                 SystemClock.uptimeMillis(),
                 PowerManager.WAKE_REASON_GESTURE,
-                "com.android.systemui:PULSEDRAG"
+                "com.android.systemui:PULSEDRAG",
             )
         }
-        lockscreenShadeTransitionController.goToLockedShade(
-            startingChild,
-            needsQSAnimation = false
-        )
+        lockscreenShadeTransitionController.goToLockedShade(startingChild, needsQSAnimation = false)
         lockscreenShadeTransitionController.finishPulseAnimation(cancelled = false)
         leavingLockscreen = true
         isExpanding = false
         if (mStartingChild is ExpandableNotificationRow) {
             val row = mStartingChild as ExpandableNotificationRow?
-            row!!.onExpandedByGesture(
-                /*userExpanded= */
-                true,
-            )
+            row!!.onExpandedByGesture(/* userExpanded= */ true)
         }
     }
 
@@ -266,19 +264,15 @@
         var expansionHeight = max(height, 0.0f)
         if (mStartingChild != null) {
             val child = mStartingChild!!
-            val newHeight = Math.min(
-                (child.collapsedHeight + expansionHeight).toInt(),
-                child.maxContentHeight
-            )
+            val newHeight =
+                Math.min((child.collapsedHeight + expansionHeight).toInt(), child.maxContentHeight)
             child.actualHeight = newHeight
         } else {
             wakeUpCoordinator.setNotificationsVisibleForExpansion(
-                height
-                    > lockscreenShadeTransitionController.distanceUntilShowingPulsingNotifications,
-                /*animate= */
-                true,
-                /*increaseSpeed= */
-                true
+                height >
+                    lockscreenShadeTransitionController.distanceUntilShowingPulsingNotifications,
+                /*animate= */ true,
+                /*increaseSpeed= */ true,
             )
         }
         lockscreenShadeTransitionController.setPulseHeight(expansionHeight, animate = false)
@@ -296,7 +290,7 @@
     @VisibleForTesting
     fun reset(
         child: ExpandableView,
-        animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
+        animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS.toLong(),
     ) {
         if (child.actualHeight == child.collapsedHeight) {
             setUserLocked(child, false)
@@ -309,11 +303,13 @@
             // don't use reflection, because the `actualHeight` field may be obfuscated
             child.actualHeight = animation.animatedValue as Int
         }
-        anim.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator) {
-                setUserLocked(child, false)
+        anim.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    setUserLocked(child, false)
+                }
             }
-        })
+        )
         anim.start()
     }
 
@@ -331,12 +327,9 @@
         }
         lockscreenShadeTransitionController.finishPulseAnimation(cancelled = true)
         wakeUpCoordinator.setNotificationsVisibleForExpansion(
-            /*visible= */
-            false,
-            /*animate= */
-            true,
-            /*increaseSpeed= */
-            false
+            /*visible= */ false,
+            /*animate= */ true,
+            /*increaseSpeed= */ false,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
index 8ce0dbf..6db610b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
@@ -21,7 +21,10 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModel
+import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import dagger.Binds
+import dagger.Lazy
 import dagger.Module
 import dagger.Provides
 import dagger.multibindings.ClassKey
@@ -41,5 +44,19 @@
         fun provideChipsLogBuffer(factory: LogBufferFactory): LogBuffer {
             return factory.create("StatusBarChips", 200)
         }
+
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(StatusBarNotificationChipsInteractor::class)
+        fun statusBarNotificationChipsInteractorAsCoreStartable(
+            interactorLazy: Lazy<StatusBarNotificationChipsInteractor>
+        ): CoreStartable {
+            return if (StatusBarNotifChips.isEnabled) {
+                interactorLazy.get()
+            } else {
+                CoreStartable.NOP
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
index bb0467f..2eae3eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
@@ -23,6 +23,8 @@
 import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import com.android.systemui.statusbar.phone.ongoingcall.domain.interactor.OngoingCallInteractor
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -37,17 +39,30 @@
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
+    ongoingCallInteractor: OngoingCallInteractor,
     repository: OngoingCallRepository,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) {
     val ongoingCallState: StateFlow<OngoingCallModel> =
-        repository.ongoingCallState
+        (if (StatusBarChipsModernization.isEnabled)
+            ongoingCallInteractor.ongoingCallState
+        else
+            repository.ongoingCallState)
             .onEach {
-                logger.log(TAG, LogLevel.INFO, { str1 = it::class.simpleName }, { "State: $str1" })
+                logger.log(
+                    TAG,
+                    LogLevel.INFO,
+                    { str1 = it::class.simpleName },
+                    { "State: $str1" }
+                )
             }
-            .stateIn(scope, SharingStarted.Lazily, OngoingCallModel.NoCall)
+            .stateIn(
+                scope,
+                SharingStarted.Lazily,
+                OngoingCallModel.NoCall
+            )
 
     companion object {
         private val TAG = "OngoingCall".pad()
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index b8cdd25..6f491e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -59,7 +59,8 @@
         interactor.ongoingCallState
             .map { state ->
                 when (state) {
-                    is OngoingCallModel.NoCall -> OngoingActivityChipModel.Hidden()
+                    is OngoingCallModel.NoCall,
+                    is OngoingCallModel.InCallWithVisibleApp -> OngoingActivityChipModel.Hidden()
                     is OngoingCallModel.InCall -> {
                         val icon =
                             if (
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
index b3dbf29..229cef9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor
 
+import android.media.projection.StopReason
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
@@ -65,7 +66,9 @@
 
     /** Stops the currently active MediaRouter cast. */
     fun stopCasting() {
-        activeCastDevice.value?.let { mediaRouterRepository.stopCasting(it) }
+        activeCastDevice.value?.let {
+            mediaRouterRepository.stopCasting(it, StopReason.STOP_PRIVACY_CHIP)
+        }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index af238f6..49c4479 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.chips.mediaprojection.domain.interactor
 
 import android.content.pm.PackageManager
+import android.media.projection.StopReason
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
@@ -105,7 +106,7 @@
 
     /** Stops the currently active projection. */
     fun stopProjecting() {
-        scope.launch { mediaProjectionRepository.stopProjecting() }
+        scope.launch { mediaProjectionRepository.stopProjecting(StopReason.STOP_PRIVACY_CHIP) }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
new file mode 100644
index 0000000..c57c807
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.notification.domain.interactor
+
+import com.android.systemui.activity.data.repository.ActivityManagerRepository
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
+import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+/**
+ * Interactor representing a single notification's status bar chip.
+ *
+ * [startingModel.key] dictates which notification this interactor corresponds to - all updates sent
+ * to this interactor via [setNotification] should only be for the notification with the same key.
+ *
+ * [StatusBarNotificationChipsInteractor] will collect all the individual instances of this
+ * interactor and send all the necessary information to the UI layer.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class SingleNotificationChipInteractor
+@AssistedInject
+constructor(
+    @Assisted startingModel: ActiveNotificationModel,
+    private val activityManagerRepository: ActivityManagerRepository,
+    @StatusBarChipsLog private val logBuffer: LogBuffer,
+) {
+    private val key = startingModel.key
+    private val logger = Logger(logBuffer, "Notif".pad())
+    // [StatusBarChipLogTag] recommends a max tag length of 20, so [extraLogTag] should NOT be the
+    // top-level tag. It should instead be provided as the first string in each log message.
+    private val extraLogTag = "SingleChipInteractor[key=$key]"
+
+    private val _notificationModel = MutableStateFlow(startingModel)
+
+    /**
+     * Sets the new notification info corresponding to this interactor. The key on [model] *must*
+     * match the key on the original [startingModel], otherwise the update won't be processed.
+     */
+    fun setNotification(model: ActiveNotificationModel) {
+        if (model.key != this.key) {
+            logger.w({ "$str1: received model for different key $str2" }) {
+                str1 = extraLogTag
+                str2 = model.key
+            }
+            return
+        }
+        _notificationModel.value = model
+    }
+
+    private val uid: Flow<Int> = _notificationModel.map { it.uid }
+
+    /** True if the application managing the notification is visible to the user. */
+    private val isAppVisible: Flow<Boolean> =
+        uid.flatMapLatest { currentUid ->
+            activityManagerRepository.createIsAppVisibleFlow(currentUid, logger, extraLogTag)
+        }
+
+    /**
+     * Emits this notification's status bar chip, or null if this notification shouldn't show a
+     * status bar chip.
+     */
+    val notificationChip: Flow<NotificationChipModel?> =
+        combine(_notificationModel, isAppVisible) { notif, isAppVisible ->
+            if (isAppVisible) {
+                // If the app that posted this notification is visible, we want to hide the chip
+                // because information between the status bar chip and the app itself could be
+                // out-of-sync (like a timer that's slightly off)
+                null
+            } else {
+                notif.toNotificationChipModel()
+            }
+        }
+
+    private fun ActiveNotificationModel.toNotificationChipModel(): NotificationChipModel? {
+        val statusBarChipIconView = this.statusBarChipIconView
+        if (statusBarChipIconView == null) {
+            logger.w({ "$str1: Can't show chip because status bar chip icon view is null" }) {
+                str1 = extraLogTag
+            }
+            return null
+        }
+        return NotificationChipModel(key, statusBarChipIconView, whenTime)
+    }
+
+    @AssistedFactory
+    fun interface Factory {
+        fun create(startingModel: ActiveNotificationModel): SingleNotificationChipInteractor
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
index 9e09671..e8cb35b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
@@ -17,16 +17,42 @@
 package com.android.systemui.statusbar.chips.notification.domain.interactor
 
 import android.annotation.SuppressLint
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.CoreStartable
 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.statusbar.chips.StatusBarChipLogTags.pad
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
+import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 
 /** An interactor for the notification chips shown in the status bar. */
 @SysUISingleton
-class StatusBarNotificationChipsInteractor @Inject constructor() {
+@OptIn(ExperimentalCoroutinesApi::class)
+class StatusBarNotificationChipsInteractor
+@Inject
+constructor(
+    @Background private val backgroundScope: CoroutineScope,
+    private val activeNotificationsInteractor: ActiveNotificationsInteractor,
+    private val singleNotificationChipInteractorFactory: SingleNotificationChipInteractor.Factory,
+    @StatusBarChipsLog private val logBuffer: LogBuffer,
+) : CoreStartable {
+    private val logger = Logger(logBuffer, "AllNotifs".pad())
 
     // Each chip tap is an individual event, *not* a state, which is why we're using SharedFlow not
     // StateFlow. There shouldn't be multiple updates per frame, which should avoid performance
@@ -45,4 +71,79 @@
         StatusBarNotifChips.assertInNewMode()
         _promotedNotificationChipTapEvent.emit(key)
     }
+
+    /**
+     * A cache of interactors. Each currently-promoted notification should have a corresponding
+     * interactor in this map.
+     */
+    private val promotedNotificationInteractorMap =
+        mutableMapOf<String, SingleNotificationChipInteractor>()
+
+    /**
+     * A list of interactors. Each currently-promoted notification should have a corresponding
+     * interactor in this list.
+     */
+    private val promotedNotificationInteractors =
+        MutableStateFlow<List<SingleNotificationChipInteractor>>(emptyList())
+
+    override fun start() {
+        if (!StatusBarNotifChips.isEnabled) {
+            return
+        }
+
+        backgroundScope.launch("StatusBarNotificationChipsInteractor") {
+            activeNotificationsInteractor.promotedOngoingNotifications
+                .pairwise(initialValue = emptyList())
+                .collect { (oldNotifs, currentNotifs) ->
+                    val removedNotifs = oldNotifs.minus(currentNotifs.toSet())
+                    removedNotifs.forEach { removedNotif ->
+                        val wasRemoved = promotedNotificationInteractorMap.remove(removedNotif.key)
+                        if (wasRemoved == null) {
+                            logger.w({
+                                "Attempted to remove $str1 from interactor map but it wasn't present"
+                            }) {
+                                str1 = removedNotif.key
+                            }
+                        }
+                    }
+                    currentNotifs.forEach { notif ->
+                        val interactor =
+                            promotedNotificationInteractorMap.computeIfAbsent(notif.key) {
+                                singleNotificationChipInteractorFactory.create(notif)
+                            }
+                        interactor.setNotification(notif)
+                    }
+                    logger.d({ "Interactors: $str1" }) {
+                        str1 =
+                            promotedNotificationInteractorMap.keys.joinToString(separator = " /// ")
+                    }
+                    promotedNotificationInteractors.value =
+                        promotedNotificationInteractorMap.values.toList()
+                }
+        }
+    }
+
+    /**
+     * A flow modeling the notifications that should be shown as chips in the status bar. Emits an
+     * empty list if there are no notifications that should show a status bar chip.
+     */
+    val notificationChips: Flow<List<NotificationChipModel>> =
+        if (StatusBarNotifChips.isEnabled) {
+            // For all our current interactors...
+            promotedNotificationInteractors.flatMapLatest { interactors ->
+                if (interactors.isNotEmpty()) {
+                    // Combine each interactor's [notificationChip] flow...
+                    val allNotificationChips: List<Flow<NotificationChipModel?>> =
+                        interactors.map { interactor -> interactor.notificationChip }
+                    combine(allNotificationChips) {
+                        // ... and emit just the non-null chips
+                        it.filterNotNull()
+                    }
+                } else {
+                    flowOf(emptyList())
+                }
+            }
+        } else {
+            flowOf(emptyList())
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
new file mode 100644
index 0000000..bc4241d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.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.statusbar.chips.notification.domain.model
+
+import com.android.systemui.statusbar.StatusBarIconView
+
+/** Modeling all the data needed to render a status bar notification chip. */
+data class NotificationChipModel(
+    val key: String,
+    val statusBarChipIconView: StatusBarIconView,
+    val whenTime: Long,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 7526748..b2f7e2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -20,11 +20,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
+import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
-import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -37,7 +36,6 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    activeNotificationsInteractor: ActiveNotificationsInteractor,
     private val notifChipsInteractor: StatusBarNotificationChipsInteractor,
 ) {
     /**
@@ -45,19 +43,14 @@
      * no notifications that should show a status bar chip.
      */
     val chips: Flow<List<OngoingActivityChipModel.Shown>> =
-        activeNotificationsInteractor.promotedOngoingNotifications.map { notifications ->
-            notifications.mapNotNull { it.toChipModel() }
+        notifChipsInteractor.notificationChips.map { notifications ->
+            notifications.map { it.toActivityChipModel() }
         }
 
-    /**
-     * Converts the notification to the [OngoingActivityChipModel] object. Returns null if the
-     * notification has invalid data such that it can't be displayed as a chip.
-     */
-    private fun ActiveNotificationModel.toChipModel(): OngoingActivityChipModel.Shown? {
+    /** Converts the notification to the [OngoingActivityChipModel] object. */
+    private fun NotificationChipModel.toActivityChipModel(): OngoingActivityChipModel.Shown {
         StatusBarNotifChips.assertInNewMode()
-        // TODO(b/364653005): Log error if there's no icon view.
-        val rawIcon = this.statusBarChipIconView ?: return null
-        val icon = OngoingActivityChipModel.ChipIcon.StatusBarView(rawIcon)
+        val icon = OngoingActivityChipModel.ChipIcon.StatusBarView(this.statusBarChipIconView)
         // TODO(b/364653005): Use the notification color if applicable.
         val colors = ColorsModel.Themed
         val onClickListener =
@@ -65,12 +58,18 @@
                 // The notification pipeline needs everything to run on the main thread, so keep
                 // this event on the main thread.
                 applicationScope.launch {
-                    notifChipsInteractor.onPromotedNotificationChipTapped(this@toChipModel.key)
+                    notifChipsInteractor.onPromotedNotificationChipTapped(
+                        this@toActivityChipModel.key
+                    )
                 }
             }
-        return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
+        return OngoingActivityChipModel.Shown.ShortTimeDelta(
+            icon,
+            colors,
+            time = this.whenTime,
+            onClickListener,
+        )
         // TODO(b/364653005): Use Notification.showWhen to determine if we should show the time.
-        // TODO(b/364653005): If Notification.whenTime is in the past, show "ago" in the text.
         // TODO(b/364653005): If Notification.shortCriticalText is set, use that instead of `when`.
         // TODO(b/364653005): If the app that posted the notification is in the foreground, don't
         // show that app's chip.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
index f5952f4..0b5e669 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.chips.screenrecord.domain.interactor
 
+import android.media.projection.StopReason
 import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -140,7 +141,7 @@
 
     /** Stops the recording. */
     fun stopRecording() {
-        scope.launch { screenRecordRepository.stopRecording() }
+        scope.launch { screenRecordRepository.stopRecording(StopReason.STOP_PRIVACY_CHIP) }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index f4462a4..730784a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -192,10 +192,14 @@
             }
             is OngoingActivityChipModel.Shown.ShortTimeDelta -> {
                 chipShortTimeDeltaView.setTime(chipModel.time)
-                // TODO(b/364653005): DateTimeView's relative time doesn't quite match the format we
-                //  want in the status bar chips.
-                chipShortTimeDeltaView.isShowRelativeTime = true
                 chipShortTimeDeltaView.visibility = View.VISIBLE
+                chipShortTimeDeltaView.isShowRelativeTime = true
+                chipShortTimeDeltaView.setRelativeTimeDisambiguationTextMask(
+                    DateTimeView.DISAMBIGUATION_TEXT_PAST
+                )
+                chipShortTimeDeltaView.setRelativeTimeUnitDisplayLength(
+                    DateTimeView.UNIT_DISPLAY_LENGTH_MEDIUM
+                )
 
                 chipTextView.visibility = View.GONE
                 chipTimeView.hide()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
index 52a79d3..0de4ac0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
@@ -132,13 +132,10 @@
         }
 
         if (mWifiPickerTracker != null) {
-            mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.CREATED));
+            mWifiPickerTracker.onStop();
         }
         Context context = mContext.createContextAsUser(UserHandle.of(newUserId), /* flags= */ 0);
         mWifiPickerTracker = mWifiPickerTrackerFactory.create(context, mLifecycle, this, TAG);
-        if (!mCallbacks.isEmpty()) {
-            mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.STARTED));
-        }
     }
 
     @Override
@@ -147,7 +144,11 @@
         if (DEBUG) Log.d(TAG, "addCallback " + callback);
         mCallbacks.add(callback);
         if (mCallbacks.size() == 1) {
-            mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.STARTED));
+            if (mWifiPickerTrackerFactory.isSupported()) {
+                mWifiPickerTracker.onStart();
+            } else {
+                mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.STARTED));
+            }
         }
     }
 
@@ -157,7 +158,11 @@
         if (DEBUG) Log.d(TAG, "removeCallback " + callback);
         mCallbacks.remove(callback);
         if (mCallbacks.isEmpty()) {
-            mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.CREATED));
+            if (mWifiPickerTrackerFactory.isSupported()) {
+                mWifiPickerTracker.onStop();
+            } else {
+                mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.CREATED));
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
index 84c7ab2..9e9a38e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.data.repository.LightBarControllerStore
 import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStore
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.phone.AutoHideControllerStore
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
 import com.android.systemui.util.kotlin.pairwiseBy
@@ -50,6 +51,7 @@
     private val initializerStore: StatusBarInitializerStore,
     private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
     private val statusBarInitializerStore: StatusBarInitializerStore,
+    private val autoHideControllerStore: AutoHideControllerStore,
     private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore,
     private val lightBarControllerStore: LightBarControllerStore,
 ) : CoreStartable {
@@ -95,6 +97,7 @@
                 statusBarModeRepositoryStore.forDisplay(displayId),
                 initializerStore.forDisplay(displayId),
                 statusBarWindowControllerStore.forDisplay(displayId),
+                autoHideControllerStore.forDisplay(displayId),
             )
             .start()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
index ff4760f..f91c5dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
@@ -71,9 +71,9 @@
     @Assisted private val statusBarInitializer: StatusBarInitializer,
     @Assisted private val statusBarWindowController: StatusBarWindowController,
     @Main private val mainContext: CoroutineContext,
+    @Assisted private val autoHideController: AutoHideController,
     private val demoModeController: DemoModeController,
     private val pluginDependencyProvider: PluginDependencyProvider,
-    private val autoHideController: AutoHideController,
     private val remoteInputManager: NotificationRemoteInputManager,
     private val notificationShadeWindowViewControllerLazy:
         Lazy<NotificationShadeWindowViewController>,
@@ -210,10 +210,6 @@
     }
 
     private fun setUpAutoHide() {
-        if (displayId != Display.DEFAULT_DISPLAY) {
-            return
-        }
-        // TODO(b/373309973): per display implementation of auto hide controller
         autoHideController.setStatusBar(
             object : AutoHideUiElement {
                 override fun synchronizeState() {}
@@ -241,18 +237,15 @@
         if (!demoModeController.isInDemoMode) {
             barTransitions.transitionTo(barMode.toTransitionModeInt(), animate)
         }
-        if (displayId == Display.DEFAULT_DISPLAY) {
-            // TODO(b/373309973): per display implementation of auto hide controller
-            autoHideController.touchAutoHide()
-        }
+        autoHideController.touchAutoHide()
     }
 
     private fun updateBubblesVisibility(statusBarVisible: Boolean) {
         if (displayId != Display.DEFAULT_DISPLAY) {
+            // Bubbles are currently only supported on the default display.
             return
         }
         bubblesOptional.ifPresent { bubbles: Bubbles ->
-            // TODO(b/373311537): per display implementation of Bubbles
             bubbles.onStatusBarVisibilityChanged(statusBarVisible)
         }
     }
@@ -288,6 +281,7 @@
             statusBarModeRepository: StatusBarModePerDisplayRepository,
             statusBarInitializer: StatusBarInitializer,
             statusBarWindowController: StatusBarWindowController,
+            autoHideController: AutoHideController,
         ): StatusBarOrchestrator
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 4341200..2588c7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -27,12 +27,16 @@
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.data.StatusBarDataLayerModule
 import com.android.systemui.statusbar.data.repository.LightBarControllerStore
+import com.android.systemui.statusbar.phone.AutoHideController
+import com.android.systemui.statusbar.phone.AutoHideControllerImpl
 import com.android.systemui.statusbar.phone.LightBarController
+import com.android.systemui.statusbar.phone.LightBarControllerImpl
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
 import com.android.systemui.statusbar.window.MultiDisplayStatusBarWindowControllerStore
@@ -60,11 +64,6 @@
 
     @Binds
     @IntoMap
-    @ClassKey(OngoingCallController::class)
-    fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
-
-    @Binds
-    @IntoMap
     @ClassKey(LightBarController::class)
     fun lightBarControllerAsCoreStartable(controller: LightBarController): CoreStartable
 
@@ -79,7 +78,26 @@
         implFactory: StatusBarWindowControllerImpl.Factory
     ): StatusBarWindowController.Factory
 
+    @Binds @SysUISingleton fun autoHideController(impl: AutoHideControllerImpl): AutoHideController
+
+    @Binds
+    fun lightBarControllerFactory(
+        legacyFactory: LightBarControllerImpl.LegacyFactory
+    ): LightBarController.Factory
+
     companion object {
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(OngoingCallController::class)
+        fun ongoingCallController(
+            controller: OngoingCallController
+        ): CoreStartable =
+            if (StatusBarChipsModernization.isEnabled) {
+                CoreStartable.NOP
+            } else {
+                controller
+            }
 
         @Provides
         @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
index 24088d2..11ec2ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
@@ -30,6 +30,7 @@
 
 import com.android.internal.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -72,7 +73,7 @@
     /** Injected constructor */
     @Inject
     public AssistantFeedbackController(@Main Handler handler,
-            Context context, DeviceConfigProxy proxy) {
+            @Application Context context, DeviceConfigProxy proxy) {
         mHandler = handler;
         mContext = context;
         mDeviceConfigProxy = proxy;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 9a779300..3825c09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -34,12 +34,12 @@
 import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.NotificationContentView
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinderLogger
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
 import com.android.systemui.util.children
 import java.util.concurrent.ConcurrentHashMap
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpNotificationViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpNotificationViewControllerEmptyImpl.kt
deleted file mode 100644
index 021d301..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpNotificationViewControllerEmptyImpl.kt
+++ /dev/null
@@ -1,34 +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.statusbar.notification
-
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController
-
-/** Empty impl of [HeadsUpNotificationViewController] for use with Scene Container */
-class HeadsUpNotificationViewControllerEmptyImpl : HeadsUpNotificationViewController {
-    override fun setHeadsUpDraggingStartingHeight(startHeight: Int) {}
-
-    override fun setTrackedHeadsUp(expandableNotificationRow: ExpandableNotificationRow?) {}
-
-    override fun startExpand(
-        newX: Float,
-        newY: Float,
-        startTracking: Boolean,
-        expandedHeight: Float
-    ) {}
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java
deleted file mode 100644
index a614638..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java
+++ /dev/null
@@ -1,267 +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.statusbar.notification;
-
-import android.content.Context;
-import android.os.RemoteException;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.Gefingerpoken;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-
-/**
- * A helper class to handle touches on the heads-up views.
- */
-public class HeadsUpTouchHelper implements Gefingerpoken {
-
-    private final HeadsUpManager mHeadsUpManager;
-    private final IStatusBarService mStatusBarService;
-    private final Callback mCallback;
-    private int mTrackingPointer;
-    private final float mTouchSlop;
-    private float mInitialTouchX;
-    private float mInitialTouchY;
-    private boolean mTouchingHeadsUpView;
-    private boolean mTrackingHeadsUp;
-    private boolean mCollapseSnoozes;
-    private final HeadsUpNotificationViewController mPanel;
-    private ExpandableNotificationRow mPickedChild;
-
-    public HeadsUpTouchHelper(HeadsUpManager headsUpManager,
-            IStatusBarService statusBarService,
-            Callback callback,
-            HeadsUpNotificationViewController notificationPanelView) {
-        mHeadsUpManager = headsUpManager;
-        mStatusBarService = statusBarService;
-        mCallback = callback;
-        mPanel = notificationPanelView;
-        Context context = mCallback.getContext();
-        final ViewConfiguration configuration = ViewConfiguration.get(context);
-        mTouchSlop = configuration.getScaledTouchSlop();
-    }
-
-    public boolean isTrackingHeadsUp() {
-        return mTrackingHeadsUp;
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent event) {
-        if (!mTouchingHeadsUpView && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
-            return false;
-        }
-        int pointerIndex = event.findPointerIndex(mTrackingPointer);
-        if (pointerIndex < 0) {
-            pointerIndex = 0;
-            mTrackingPointer = event.getPointerId(pointerIndex);
-        }
-        final float x = event.getX(pointerIndex);
-        final float y = event.getY(pointerIndex);
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                mInitialTouchY = y;
-                mInitialTouchX = x;
-                setTrackingHeadsUp(false);
-                ExpandableView child = mCallback.getChildAtRawPosition(x, y);
-                mTouchingHeadsUpView = false;
-                if (child instanceof ExpandableNotificationRow) {
-                    ExpandableNotificationRow pickedChild = (ExpandableNotificationRow) child;
-                    mTouchingHeadsUpView = !mCallback.isExpanded()
-                            && pickedChild.isHeadsUp() && pickedChild.isPinned();
-                    if (mTouchingHeadsUpView) {
-                        mPickedChild = pickedChild;
-                    }
-                } else if (child == null && !mCallback.isExpanded()) {
-                    // We might touch above the visible heads up child, but then we still would
-                    // like to capture it.
-                    NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
-                    if (topEntry != null && topEntry.isRowPinned()) {
-                        mPickedChild = topEntry.getRow();
-                        mTouchingHeadsUpView = true;
-                    }
-                }
-                break;
-            case MotionEvent.ACTION_POINTER_UP:
-                final int upPointer = event.getPointerId(event.getActionIndex());
-                if (mTrackingPointer == upPointer) {
-                    // gesture is ongoing, find a new pointer to track
-                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
-                    mTrackingPointer = event.getPointerId(newIndex);
-                    mInitialTouchX = event.getX(newIndex);
-                    mInitialTouchY = event.getY(newIndex);
-                }
-                break;
-
-            case MotionEvent.ACTION_MOVE:
-                final float h = y - mInitialTouchY;
-                if (mTouchingHeadsUpView && Math.abs(h) > mTouchSlop
-                        && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
-                    if (!SceneContainerFlag.isEnabled()) {
-                        setTrackingHeadsUp(true);
-                        mCollapseSnoozes = h < 0;
-                        mInitialTouchX = x;
-                        mInitialTouchY = y;
-                        int startHeight = (int) (mPickedChild.getActualHeight()
-                                + mPickedChild.getTranslationY());
-                        mPanel.setHeadsUpDraggingStartingHeight(startHeight);
-                        mPanel.startExpand(x, y, true /* startTracking */, startHeight);
-
-                        // This call needs to be after the expansion start otherwise we will get a
-                        // flicker of one frame as it's not expanded yet.
-                        mHeadsUpManager.unpinAll(true);
-
-                        clearNotificationEffects();
-                        endMotion();
-                    }
-                    return true;
-                }
-                break;
-
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                if (mPickedChild != null && mTouchingHeadsUpView) {
-                    // We may swallow this click if the heads up just came in.
-                    if (mHeadsUpManager.shouldSwallowClick(
-                            mPickedChild.getEntry().getSbn().getKey())) {
-                        endMotion();
-                        return true;
-                    }
-                }
-                endMotion();
-                break;
-        }
-        return false;
-    }
-
-    private void setTrackingHeadsUp(boolean tracking) {
-        mTrackingHeadsUp = tracking;
-        mHeadsUpManager.setTrackingHeadsUp(tracking);
-        mPanel.setTrackedHeadsUp(tracking ? mPickedChild : null);
-    }
-
-    public void notifyFling(boolean collapse) {
-        if (collapse && mCollapseSnoozes) {
-            mHeadsUpManager.snooze();
-        }
-        mCollapseSnoozes = false;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        if (SceneContainerFlag.isEnabled()) {
-            int pointerIndex = event.findPointerIndex(mTrackingPointer);
-            if (pointerIndex < 0) {
-                pointerIndex = 0;
-                mTrackingPointer = event.getPointerId(pointerIndex);
-            }
-            final float x = event.getX(pointerIndex);
-            final float y = event.getY(pointerIndex);
-            switch (event.getActionMasked()) {
-                case MotionEvent.ACTION_POINTER_UP:
-                    final int upPointer = event.getPointerId(event.getActionIndex());
-                    if (mTrackingPointer == upPointer) {
-                        // gesture is ongoing, find a new pointer to track
-                        final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
-                        mTrackingPointer = event.getPointerId(newIndex);
-                        mInitialTouchX = event.getX(newIndex);
-                        mInitialTouchY = event.getY(newIndex);
-                    }
-                    break;
-                case MotionEvent.ACTION_MOVE:
-                    final float h = y - mInitialTouchY;
-                    if (mTouchingHeadsUpView && Math.abs(h) > mTouchSlop
-                            && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
-                        setTrackingHeadsUp(true);
-                        mCollapseSnoozes = h < 0;
-                        mInitialTouchX = x;
-                        mInitialTouchY = y;
-                        int startHeight = (int) (mPickedChild.getActualHeight()
-                                + mPickedChild.getTranslationY());
-                        mPanel.setHeadsUpDraggingStartingHeight(startHeight);
-                        mPanel.startExpand(x, y, true /* startTracking */, startHeight);
-
-                        clearNotificationEffects();
-                        endMotion();
-                        return true;
-                    }
-                    break;
-                case MotionEvent.ACTION_CANCEL:
-                case MotionEvent.ACTION_UP:
-                    if (mPickedChild != null && mTouchingHeadsUpView) {
-                        // We may swallow this click if the heads up just came in.
-                        if (mHeadsUpManager.shouldSwallowClick(
-                                mPickedChild.getEntry().getSbn().getKey())) {
-                            endMotion();
-                            return true;
-                        }
-                    }
-                    endMotion();
-                    return false;
-            }
-            return false;
-        } else {
-            if (!mTrackingHeadsUp) {
-                return false;
-            }
-            switch (event.getActionMasked()) {
-                case MotionEvent.ACTION_UP:
-                case MotionEvent.ACTION_CANCEL:
-                    endMotion();
-                    setTrackingHeadsUp(false);
-                    break;
-            }
-            return true;
-        }
-    }
-
-    private void endMotion() {
-        mTrackingPointer = -1;
-        mPickedChild = null;
-        mTouchingHeadsUpView = false;
-    }
-
-    private void clearNotificationEffects() {
-        try {
-            mStatusBarService.clearNotificationEffects();
-        } catch (RemoteException e) {
-            // Won't fail unless the world has ended.
-        }
-    }
-
-    public interface Callback {
-        ExpandableView getChildAtRawPosition(float touchX, float touchY);
-        boolean isExpanded();
-        Context getContext();
-    }
-
-    /** The controller for a view that houses heads up notifications. */
-    public interface HeadsUpNotificationViewController {
-        /** Called when a HUN is dragged to indicate the starting height for shade motion. */
-        void setHeadsUpDraggingStartingHeight(int startHeight);
-
-        /** Sets notification that is being expanded. */
-        void setTrackedHeadsUp(ExpandableNotificationRow expandableNotificationRow);
-
-        /** Called when a MotionEvent is about to trigger expansion. */
-        void startExpand(float newX, float newY, boolean startTracking, float expandedHeight);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index 60d846e..2d6ed70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -55,6 +55,7 @@
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.NotificationChannels;
@@ -84,7 +85,7 @@
 
     @Inject
     public InstantAppNotifier(
-            Context context,
+            @ShadeDisplayAware Context context,
             CommandQueue commandQueue,
             UserTracker userTracker,
             @Main Executor mainExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
index b67092c..91864c22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
@@ -17,98 +17,17 @@
 package com.android.systemui.statusbar.notification
 
 import android.content.Context
-import android.provider.DeviceConfig
-import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
-import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
-import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
-import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
-import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
-import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
-import com.android.systemui.statusbar.notification.stack.BUCKET_MEDIA_CONTROLS
-import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
-import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
-import com.android.systemui.statusbar.notification.stack.PriorityBucket
-import com.android.systemui.util.DeviceConfigProxy
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.Utils
 import javax.inject.Inject
 
-private var sUsePeopleFiltering: Boolean? = null
-
-/** Feature controller for the NOTIFICATIONS_USE_PEOPLE_FILTERING config. */
 @SysUISingleton
 class NotificationSectionsFeatureManager
 @Inject
-constructor(val proxy: DeviceConfigProxy, val context: Context) {
-
-    fun isFilteringEnabled(): Boolean {
-        return usePeopleFiltering(proxy)
-    }
+constructor(@ShadeDisplayAware val context: Context) {
 
     fun isMediaControlsEnabled(): Boolean {
         return Utils.useQsMediaPlayer(context)
     }
-
-    fun getNotificationBuckets(): IntArray {
-        if (
-            PriorityPeopleSection.isEnabled ||
-                NotificationMinimalism.isEnabled ||
-                NotificationClassificationFlag.isEnabled
-        ) {
-            // We don't need this list to be adaptive, it can be the superset of all features.
-            return PriorityBucket.getAllInOrder()
-        }
-        return when {
-            isFilteringEnabled() && isMediaControlsEnabled() ->
-                intArrayOf(
-                    BUCKET_HEADS_UP,
-                    BUCKET_FOREGROUND_SERVICE,
-                    BUCKET_MEDIA_CONTROLS,
-                    BUCKET_PEOPLE,
-                    BUCKET_ALERTING,
-                    BUCKET_SILENT
-                )
-            !isFilteringEnabled() && isMediaControlsEnabled() ->
-                intArrayOf(
-                    BUCKET_HEADS_UP,
-                    BUCKET_FOREGROUND_SERVICE,
-                    BUCKET_MEDIA_CONTROLS,
-                    BUCKET_ALERTING,
-                    BUCKET_SILENT
-                )
-            isFilteringEnabled() && !isMediaControlsEnabled() ->
-                intArrayOf(
-                    BUCKET_HEADS_UP,
-                    BUCKET_FOREGROUND_SERVICE,
-                    BUCKET_PEOPLE,
-                    BUCKET_ALERTING,
-                    BUCKET_SILENT
-                )
-            else -> intArrayOf(BUCKET_ALERTING, BUCKET_SILENT)
-        }
-    }
-
-    fun getNumberOfBuckets(): Int {
-        return getNotificationBuckets().size
-    }
-
-    @VisibleForTesting
-    fun clearCache() {
-        sUsePeopleFiltering = null
-    }
-}
-
-private fun usePeopleFiltering(proxy: DeviceConfigProxy): Boolean {
-    if (sUsePeopleFiltering == null) {
-        sUsePeopleFiltering =
-            proxy.getBoolean(
-                DeviceConfig.NAMESPACE_SYSTEMUI,
-                NOTIFICATIONS_USE_PEOPLE_FILTERING,
-                true
-            )
-    }
-
-    return sUsePeopleFiltering!!
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
index 67c53d46..383227d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
@@ -22,10 +22,10 @@
 import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.animation.TransitionAnimator
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.HeadsUpUtil
 import kotlin.math.ceil
 import kotlin.math.max
 
@@ -36,12 +36,12 @@
     private val notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
     private val notificationListContainer: NotificationListContainer,
     private val headsUpManager: HeadsUpManager,
-    private val jankMonitor: InteractionJankMonitor
+    private val jankMonitor: InteractionJankMonitor,
 ) {
     @JvmOverloads
     fun getAnimatorController(
         notification: ExpandableNotificationRow,
-        onFinishAnimationCallback: Runnable? = null
+        onFinishAnimationCallback: Runnable? = null,
     ): NotificationTransitionAnimatorController {
         return NotificationTransitionAnimatorController(
             notificationLaunchAnimationInteractor,
@@ -49,7 +49,7 @@
             headsUpManager,
             notification,
             jankMonitor,
-            onFinishAnimationCallback
+            onFinishAnimationCallback,
         )
     }
 }
@@ -65,7 +65,7 @@
     private val headsUpManager: HeadsUpManager,
     private val notification: ExpandableNotificationRow,
     private val jankMonitor: InteractionJankMonitor,
-    private val onFinishAnimationCallback: Runnable?
+    private val onFinishAnimationCallback: Runnable?,
 ) : ActivityTransitionAnimator.Controller {
 
     companion object {
@@ -109,7 +109,7 @@
                 left = location[0],
                 right = location[0] + notification.width,
                 topCornerRadius = topCornerRadius,
-                bottomCornerRadius = notification.bottomCornerRadius
+                bottomCornerRadius = notification.bottomCornerRadius,
             )
 
         params.startTranslationZ = notification.translationZ
@@ -177,7 +177,7 @@
             row.entry.key,
             true /* releaseImmediately */,
             animate,
-            reason
+            reason,
         )
     }
 
@@ -224,7 +224,7 @@
     override fun onTransitionAnimationProgress(
         state: TransitionAnimator.State,
         progress: Float,
-        linearProgress: Float
+        linearProgress: Float,
     ) {
         val params = state as LaunchAnimationParameters
         params.progress = progress
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 08ffbf2..7a59f79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -36,14 +36,14 @@
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.KeyguardBypassController.OnBypassStateChangedListener
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
 import com.android.systemui.util.doOnEnd
 import com.android.systemui.util.doOnStart
 import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index 3b48b39..d0d2258 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -4,7 +4,6 @@
 import android.view.View
 import androidx.annotation.FloatRange
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
 import com.android.systemui.statusbar.notification.stack.AnimationProperties
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import kotlin.math.abs
@@ -39,15 +38,11 @@
 
     /** Current top corner in pixel, based on [topRoundness] and [maxRadius] */
     val topCornerRadius: Float
-        get() =
-            if (NotificationsImprovedHunAnimation.isEnabled) roundableState.topCornerRadius
-            else topRoundness * maxRadius
+        get() = roundableState.topCornerRadius
 
     /** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */
     val bottomCornerRadius: Float
-        get() =
-            if (NotificationsImprovedHunAnimation.isEnabled) roundableState.bottomCornerRadius
-            else bottomRoundness * maxRadius
+        get() = roundableState.bottomCornerRadius
 
     /** Get and update the current radii */
     val updatedRadii: FloatArray
@@ -123,7 +118,7 @@
         return requestTopRoundness(
             value = value,
             sourceType = sourceType,
-            animate = roundableState.targetView.isShown
+            animate = roundableState.targetView.isShown,
         )
     }
 
@@ -190,7 +185,7 @@
         return requestBottomRoundness(
             value = value,
             sourceType = sourceType,
-            animate = roundableState.targetView.isShown
+            animate = roundableState.targetView.isShown,
         )
     }
 
@@ -289,11 +284,7 @@
      *
      * This method reuses the previous [radii] for performance reasons.
      */
-    fun updateRadii(
-        topCornerRadius: Float,
-        bottomCornerRadius: Float,
-        radii: FloatArray,
-    ) {
+    fun updateRadii(topCornerRadius: Float, bottomCornerRadius: Float, radii: FloatArray) {
         if (radii.size != 8) error("Unexpected radiiBuffer size ${radii.size}")
 
         if (radii[0] != topCornerRadius || radii[4] != bottomCornerRadius) {
@@ -312,11 +303,7 @@
  */
 class RoundableState
 @JvmOverloads
-constructor(
-    internal val targetView: View,
-    private val roundable: Roundable,
-    maxRadius: Float,
-) {
+constructor(internal val targetView: View, private val roundable: Roundable, maxRadius: Float) {
     internal var maxRadius = maxRadius
         private set
 
@@ -387,18 +374,12 @@
     internal fun isBottomAnimating() = PropertyAnimator.isAnimating(targetView, bottomAnimatable)
 
     /** Set the current top roundness */
-    internal fun setTopRoundness(
-        value: Float,
-        animated: Boolean,
-    ) {
+    internal fun setTopRoundness(value: Float, animated: Boolean) {
         PropertyAnimator.setProperty(targetView, topAnimatable, value, DURATION, animated)
     }
 
     /** Set the current bottom roundness */
-    internal fun setBottomRoundness(
-        value: Float,
-        animated: Boolean,
-    ) {
+    internal fun setBottomRoundness(value: Float, animated: Boolean) {
         PropertyAnimator.setProperty(targetView, bottomAnimatable, value, DURATION, animated)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index c487ff5..6b84b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -65,6 +65,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
 import com.android.systemui.statusbar.notification.icon.IconPack;
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -675,8 +676,8 @@
         return row != null && row.isPinnedAndExpanded();
     }
 
-    public void setRowPinned(boolean pinned) {
-        if (row != null) row.setPinned(pinned);
+    public void setRowPinnedStatus(PinnedStatus pinnedStatus) {
+        if (row != null) row.setPinnedStatus(pinnedStatus);
     }
 
     public boolean isRowHeadsUp() {
@@ -1075,7 +1076,7 @@
         if (PromotedNotificationContentModel.featureFlagEnabled()) {
             return mPromotedNotificationContentModel;
         } else {
-            Log.wtf(TAG, "getting promoted content without feature flag enabled");
+            Log.wtf(TAG, "getting promoted content without feature flag enabled", new Throwable());
             return null;
         }
     }
@@ -1089,7 +1090,7 @@
         if (PromotedNotificationContentModel.featureFlagEnabled()) {
             this.mPromotedNotificationContentModel = promotedNotificationContentModel;
         } else {
-            Log.wtf(TAG, "setting promoted content without feature flag enabled");
+            Log.wtf(TAG, "setting promoted content without feature flag enabled", new Throwable());
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
index 0c49713..0f08ae4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
@@ -23,35 +23,37 @@
 import android.service.notification.StatusBarNotification
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import javax.inject.Inject
 
 @SysUISingleton
-class TargetSdkResolver @Inject constructor(
-    private val context: Context
-) {
+class TargetSdkResolver @Inject constructor(@Application private val context: Context) {
     fun initialize(collection: CommonNotifCollection) {
-        collection.addCollectionListener(object : NotifCollectionListener {
-            override fun onEntryBind(entry: NotificationEntry, sbn: StatusBarNotification) {
-                entry.targetSdk = resolveNotificationSdk(sbn)
+        collection.addCollectionListener(
+            object : NotifCollectionListener {
+                override fun onEntryBind(entry: NotificationEntry, sbn: StatusBarNotification) {
+                    entry.targetSdk = resolveNotificationSdk(sbn)
+                }
             }
-        })
+        )
     }
 
     private fun resolveNotificationSdk(sbn: StatusBarNotification): Int {
-        val applicationInfo = getApplicationInfoFromExtras(sbn.notification)
+        val applicationInfo =
+            getApplicationInfoFromExtras(sbn.notification)
                 ?: getApplicationInfoFromPackageManager(sbn)
 
         return applicationInfo?.targetSdkVersion ?: 0
     }
 
     private fun getApplicationInfoFromExtras(notification: Notification): ApplicationInfo? =
-            notification.extras.getParcelable(
-                    Notification.EXTRA_BUILDER_APPLICATION_INFO,
-                    ApplicationInfo::class.java
-            )
+        notification.extras.getParcelable(
+            Notification.EXTRA_BUILDER_APPLICATION_INFO,
+            ApplicationInfo::class.java,
+        )
 
     private fun getApplicationInfoFromPackageManager(sbn: StatusBarNotification): ApplicationInfo? {
         val pmUser = CentralSurfaces.getPackageManagerForUser(context, sbn.user.identifier)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index c7b47ee..0269b16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -41,13 +41,13 @@
 import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.dagger.IncomingHeader
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
 import com.android.systemui.statusbar.notification.logKey
 import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix
 import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.time.SystemClock
 import java.util.function.Consumer
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
index 366704e..de6f257 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
@@ -18,6 +18,7 @@
 
 import android.annotation.SuppressLint
 import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dump.DumpManager
@@ -35,8 +36,8 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.headsUpEvents
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.headsUpEvents
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.indentIfPossible
 import java.io.PrintWriter
@@ -52,7 +53,6 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.yield
 
 /**
@@ -123,7 +123,7 @@
                     unseenNotifications.removeAll(notificationsSeenWhileLocked)
                     logger.logAllMarkedSeenOnUnlock(
                         seenCount = notificationsSeenWhileLocked.size,
-                        remainingUnseenCount = unseenNotifications.size
+                        remainingUnseenCount = unseenNotifications.size,
                     )
                     notificationsSeenWhileLocked.clear()
                 }
@@ -140,7 +140,7 @@
      * been "seen" while the device is on the keyguard.
      */
     private suspend fun trackSeenNotificationsWhileLocked(
-        notificationsSeenWhileLocked: MutableSet<NotificationEntry>,
+        notificationsSeenWhileLocked: MutableSet<NotificationEntry>
     ) = coroutineScope {
         // Remove removed notifications from the set
         launch {
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 8660cd1..3c31d89 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
@@ -45,7 +45,8 @@
 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.NotificationMinimalism;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.kotlin.BooleanFlowOperators;
 import com.android.systemui.util.kotlin.JavaAdapter;
@@ -78,6 +79,7 @@
     private final CommunalSceneInteractor mCommunalSceneInteractor;
     private final ShadeInteractor mShadeInteractor;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final KeyguardStateController mKeyguardStateController;
     private final VisualStabilityCoordinatorLogger mLogger;
 
     private boolean mSleepy = true;
@@ -120,6 +122,7 @@
             CommunalSceneInteractor communalSceneInteractor,
             ShadeInteractor shadeInteractor,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
+            KeyguardStateController keyguardStateController,
             VisualStabilityCoordinatorLogger logger) {
         mHeadsUpManager = headsUpManager;
         mShadeAnimationInteractor = shadeAnimationInteractor;
@@ -133,6 +136,7 @@
         mCommunalSceneInteractor = communalSceneInteractor;
         mShadeInteractor = shadeInteractor;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mKeyguardStateController = keyguardStateController;
         mLogger = logger;
 
         dumpManager.registerDumpable(this);
@@ -162,17 +166,29 @@
                             KeyguardState.LOCKSCREEN),
                     this::onLockscreenKeyguardStateTransitionValueChanged);
         }
-        if (Flags.checkLockscreenGoneTransition()) {
-            mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.isInTransition(
-                            Edge.create(KeyguardState.LOCKSCREEN, Scenes.Gone),
-                            Edge.create(KeyguardState.LOCKSCREEN, KeyguardState.GONE)),
-                    this::onLockscreenInGoneTransitionChanged);
-        }
 
+        if (Flags.checkLockscreenGoneTransition()) {
+            if (SceneContainerFlag.isEnabled()) {
+                mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.isInTransition(
+                                Edge.create(KeyguardState.LOCKSCREEN, Scenes.Gone), null),
+                        this::onLockscreenInGoneTransitionChanged);
+            } else {
+                mKeyguardStateController.addCallback(mKeyguardFadeAwayAnimationCallback);
+            }
+        }
 
         pipeline.setVisualStabilityManager(mNotifStabilityManager);
     }
 
+    final KeyguardStateController.Callback mKeyguardFadeAwayAnimationCallback =
+            new KeyguardStateController.Callback() {
+                @Override
+                public void onKeyguardFadingAwayChanged() {
+                    onLockscreenInGoneTransitionChanged(
+                            mKeyguardStateController.isKeyguardFadingAway());
+                }
+            };
+
     // TODO(b/203826051): Ensure stability manager can allow reordering off-screen
     //  HUNs to the top of the shade
     private final NotifStabilityManager mNotifStabilityManager =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
index 5743ab0..07fa6ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
@@ -34,7 +34,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 
 import javax.inject.Inject;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
index 6b70a08..03b38f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
@@ -17,8 +17,9 @@
 package com.android.systemui.statusbar.notification.collection.provider
 
 import android.content.Context
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /**
@@ -31,9 +32,7 @@
  * visibility when it invalidates, and we just store that state here.)
  */
 @SysUISingleton
-class SectionHeaderVisibilityProvider @Inject constructor(
-    context: Context
-) {
+class SectionHeaderVisibilityProvider @Inject constructor(@ShadeDisplayAware context: Context) {
     val neverShowSectionHeaders =
         context.resources.getBoolean(R.bool.config_notification_never_show_section_headers)
     var sectionHeadersVisible = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
index 70fabc0..c731ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
@@ -19,15 +19,16 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.stack.MediaContainerView
 import javax.inject.Inject
 
 @SysUISingleton
-class MediaContainerController @Inject constructor(
-    private val layoutInflater: LayoutInflater
-) : NodeController {
+class MediaContainerController
+@Inject
+constructor(@ShadeDisplayAware private val layoutInflater: LayoutInflater) : NodeController {
 
     override val nodeLabel = "MediaContainer"
     var mediaContainerView: MediaContainerView? = null
@@ -42,11 +43,12 @@
                 parent.removeView(_view)
             }
         }
-        val inflated = layoutInflater.inflate(
+        val inflated =
+            layoutInflater.inflate(
                 R.layout.keyguard_media_container,
                 parent,
-                false /* attachToRoot */)
-                as MediaContainerView
+                false, /* attachToRoot */
+            ) as MediaContainerView
         if (oldPos != -1) {
             parent.addView(inflated, oldPos)
         }
@@ -57,6 +59,8 @@
         get() = mediaContainerView!!
 
     override fun offerToKeepInParentForAnimation(): Boolean = false
+
     override fun removeFromParentIfKeptForAnimation(): Boolean = false
+
     override fun resetKeepInParentForAnimation() {}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 5464d08..37005c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -21,8 +21,9 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import com.android.systemui.res.R
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.dagger.HeaderClickAction
 import com.android.systemui.statusbar.notification.dagger.HeaderText
 import com.android.systemui.statusbar.notification.dagger.NodeLabel
@@ -32,30 +33,37 @@
 
 interface SectionHeaderController {
     fun reinflateView(parent: ViewGroup)
+
     val headerView: SectionHeaderView?
+
     fun setClearSectionEnabled(enabled: Boolean)
+
     fun setOnClearSectionClickListener(listener: View.OnClickListener)
 }
 
 @SectionHeaderScope
-class SectionHeaderNodeControllerImpl @Inject constructor(
+class SectionHeaderNodeControllerImpl
+@Inject
+constructor(
     @NodeLabel override val nodeLabel: String,
-    private val layoutInflater: LayoutInflater,
+    @ShadeDisplayAware private val layoutInflater: LayoutInflater,
     @HeaderText @StringRes private val headerTextResId: Int,
     private val activityStarter: ActivityStarter,
-    @HeaderClickAction private val clickIntentAction: String
+    @HeaderClickAction private val clickIntentAction: String,
 ) : NodeController, SectionHeaderController {
 
     private var _view: SectionHeaderView? = null
     private var clearAllButtonEnabled = false
     private var clearAllClickListener: View.OnClickListener? = null
-    private val onHeaderClickListener = View.OnClickListener {
-        activityStarter.startActivity(
+    private val onHeaderClickListener =
+        View.OnClickListener {
+            activityStarter.startActivity(
                 Intent(clickIntentAction),
                 true /* onlyProvisioned */,
                 true /* dismissShade */,
-                Intent.FLAG_ACTIVITY_SINGLE_TOP)
-    }
+                Intent.FLAG_ACTIVITY_SINGLE_TOP,
+            )
+        }
 
     override fun reinflateView(parent: ViewGroup) {
         var oldPos = -1
@@ -66,11 +74,12 @@
                 parent.removeView(_view)
             }
         }
-        val inflated = layoutInflater.inflate(
+        val inflated =
+            layoutInflater.inflate(
                 R.layout.status_bar_notification_section_header,
                 parent,
-                false /* attachToRoot */)
-                as SectionHeaderView
+                false, /* attachToRoot */
+            ) as SectionHeaderView
         inflated.setHeaderText(headerTextResId)
         inflated.setOnHeaderClickListener(onHeaderClickListener)
         clearAllClickListener?.let { inflated.setOnClearAllClickListener(it) }
@@ -100,7 +109,10 @@
 
     override val view: View
         get() = _view!!
+
     override fun offerToKeepInParentForAnimation(): Boolean = false
+
     override fun removeFromParentIfKeptForAnimation(): Boolean = false
+
     override fun resetKeepInParentForAnimation() {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 42aadd1..8a1371f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -77,6 +77,10 @@
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
 import com.android.systemui.statusbar.notification.logging.dagger.NotificationsLogModule;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
 import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactory;
 import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactoryLooperImpl;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -88,7 +92,7 @@
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModesCleanupStartable;
 
 import dagger.Binds;
@@ -101,6 +105,8 @@
 
 import kotlinx.coroutines.CoroutineScope;
 
+import java.util.Optional;
+
 import javax.inject.Provider;
 
 /**
@@ -308,4 +314,22 @@
     @IntoMap
     @ClassKey(ZenModesCleanupStartable.class)
     CoreStartable bindsZenModesCleanup(ZenModesCleanupStartable zenModesCleanup);
+
+    /**
+     * Provides {@link
+     * com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor} if
+     * one of the relevant feature flags is enabled.
+     */
+    @Provides
+    @SysUISingleton
+    static Optional<PromotedNotificationContentExtractor>
+            providePromotedNotificationContentExtractor(
+                    PromotedNotificationsProvider provider, Context context,
+                    PromotedNotificationLogger logger) {
+        if (PromotedNotificationContentModel.featureFlagEnabled()) {
+            return Optional.of(new PromotedNotificationContentExtractor(provider, context, logger));
+        } else {
+            return Optional.empty();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
index cf4fb25..3759835 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -16,11 +16,11 @@
 package com.android.systemui.statusbar.notification.data
 
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
-import com.android.systemui.statusbar.policy.BaseHeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl
 import dagger.Binds
 import dagger.Module
 
 @Module(includes = [NotificationSettingsRepositoryModule::class])
 interface NotificationDataLayerModule {
-    @Binds fun bindHeadsUpNotificationRepository(impl: BaseHeadsUpManager): HeadsUpRepository
+    @Binds fun bindHeadsUpNotificationRepository(impl: HeadsUpManagerImpl): HeadsUpRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRowRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRowRepository.kt
index 7b40812..2663104 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRowRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRowRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.data.repository
 
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus
 import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
 import kotlinx.coroutines.flow.StateFlow
 
@@ -31,6 +32,6 @@
     /** A key to identify this row in the view hierarchy. */
     val elementKey: Any
 
-    /** Whether this notification is "pinned", meaning that it should stay on top of the screen. */
-    val isPinned: StateFlow<Boolean>
+    /** This notification's pinning status. */
+    val pinnedStatus: StateFlow<PinnedStatus>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index cff5bef..6b93ee1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -79,11 +79,10 @@
     /** The notifications that are promoted and ongoing. Sorted by priority order. */
     val promotedOngoingNotifications: Flow<List<ActiveNotificationModel>> =
         if (StatusBarNotifChips.isEnabled) {
-            // TODO(b/364653005): Filter all the notifications down to just the promoted ones.
             // TODO(b/364653005): [ongoingCallNotification] should be incorporated into this flow
             // instead of being separate.
             topLevelRepresentativeNotifications
-                .map { notifs -> notifs.filter { it.isPromoted } }
+                .map { notifs -> notifs.filter { it.promotedContent != null } }
                 .distinctUntilChanged()
                 .flowOn(backgroundDispatcher)
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index e25127e..64e78e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -52,7 +52,9 @@
     val topHeadsUpRowIfPinned: Flow<HeadsUpRowKey?> =
         headsUpRepository.topHeadsUpRow
             .flatMapLatest { repository ->
-                repository?.isPinned?.map { pinned -> repository.takeIf { pinned } } ?: flowOf(null)
+                repository?.pinnedStatus?.map { pinnedStatus ->
+                    repository.takeIf { pinnedStatus.isPinned }
+                } ?: flowOf(null)
             }
             .distinctUntilChanged()
 
@@ -64,7 +66,7 @@
                 if (repositories.isNotEmpty()) {
                     val toCombine: List<Flow<Pair<HeadsUpRowRepository, Boolean>>> =
                         repositories.map { repo ->
-                            repo.isPinned.map { isPinned -> repo to isPinned }
+                            repo.pinnedStatus.map { pinnedStatus -> repo to pinnedStatus.isPinned }
                         }
                     combine(toCombine) { pairs -> pairs.toSet() }
                 } else {
@@ -96,20 +98,17 @@
     }
 
     /** Are there any pinned heads up rows to display? */
-    val hasPinnedRows: Flow<Boolean> by lazy {
-        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
-            flowOf(false)
-        } else {
-            headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
-                if (rows.isNotEmpty()) {
-                    combine(rows.map { it.isPinned }) { pins -> pins.any { it } }
-                } else {
-                    // if the set is empty, there are no flows to combine
-                    flowOf(false)
+    val hasPinnedRows: Flow<Boolean> =
+        headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
+            if (rows.isNotEmpty()) {
+                combine(rows.map { it.pinnedStatus }) { pinnedStatus ->
+                    pinnedStatus.any { it.isPinned }
                 }
+            } else {
+                // if the set is empty, there are no flows to combine
+                flowOf(false)
             }
         }
-    }
 
     val isHeadsUpOrAnimatingAway: Flow<Boolean> by lazy {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
@@ -139,15 +138,10 @@
             }
         }
 
-    val showHeadsUpStatusBar: Flow<Boolean> by lazy {
-        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
-            flowOf(false)
-        } else {
-            combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp ->
-                hasPinnedRows && canShowHeadsUp
-            }
+    val showHeadsUpStatusBar =
+        combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp ->
+            hasPinnedRows && canShowHeadsUp
         }
-    }
 
     fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
         HeadsUpRowInteractor(key as HeadsUpRowRepository)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 8bd7a1a..042389f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider
 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
@@ -52,7 +51,6 @@
 constructor(
     private val repository: ActiveNotificationListRepository,
     private val sectionStyleProvider: SectionStyleProvider,
-    private val promotedNotificationsProvider: PromotedNotificationsProvider,
 ) {
     /**
      * Sets the current list of rendered notification entries as displayed in the notification list.
@@ -60,11 +58,7 @@
     fun setRenderedList(entries: List<ListEntry>) {
         traceSection("RenderNotificationListInteractor.setRenderedList") {
             repository.activeNotifications.update { existingModels ->
-                buildActiveNotificationsStore(
-                    existingModels,
-                    sectionStyleProvider,
-                    promotedNotificationsProvider,
-                ) {
+                buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
                     entries.forEach(::addListEntry)
                     setRankingsMap(entries)
                 }
@@ -76,21 +70,13 @@
 private fun buildActiveNotificationsStore(
     existingModels: ActiveNotificationsStore,
     sectionStyleProvider: SectionStyleProvider,
-    promotedNotificationsProvider: PromotedNotificationsProvider,
     block: ActiveNotificationsStoreBuilder.() -> Unit,
 ): ActiveNotificationsStore =
-    ActiveNotificationsStoreBuilder(
-            existingModels,
-            sectionStyleProvider,
-            promotedNotificationsProvider,
-        )
-        .apply(block)
-        .build()
+    ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider).apply(block).build()
 
 private class ActiveNotificationsStoreBuilder(
     private val existingModels: ActiveNotificationsStore,
     private val sectionStyleProvider: SectionStyleProvider,
-    private val promotedNotificationsProvider: PromotedNotificationsProvider,
 ) {
     private val builder = ActiveNotificationsStore.Builder()
 
@@ -163,7 +149,6 @@
             key = key,
             groupKey = sbn.groupKey,
             whenTime = sbn.notification.`when`,
-            isPromoted = promotedNotificationsProvider.shouldPromote(this),
             isAmbient = sectionStyleProvider.isMinimized(this),
             isRowDismissed = isRowDismissed,
             isSilent = sectionStyleProvider.isSilent(this),
@@ -190,7 +175,6 @@
     key: String,
     groupKey: String?,
     whenTime: Long,
-    isPromoted: Boolean,
     isAmbient: Boolean,
     isRowDismissed: Boolean,
     isSilent: Boolean,
@@ -215,7 +199,6 @@
             key = key,
             groupKey = groupKey,
             whenTime = whenTime,
-            isPromoted = isPromoted,
             isAmbient = isAmbient,
             isRowDismissed = isRowDismissed,
             isSilent = isSilent,
@@ -240,7 +223,6 @@
             key = key,
             groupKey = groupKey,
             whenTime = whenTime,
-            isPromoted = isPromoted,
             isAmbient = isAmbient,
             isRowDismissed = isRowDismissed,
             isSilent = isSilent,
@@ -266,7 +248,6 @@
     key: String,
     groupKey: String?,
     whenTime: Long,
-    isPromoted: Boolean,
     isAmbient: Boolean,
     isRowDismissed: Boolean,
     isSilent: Boolean,
@@ -290,7 +271,6 @@
         key != this.key -> false
         groupKey != this.groupKey -> false
         whenTime != this.whenTime -> false
-        isPromoted != this.isPromoted -> false
         isAmbient != this.isAmbient -> false
         isRowDismissed != this.isRowDismissed -> false
         isSilent != this.isSilent -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
new file mode 100644
index 0000000..5c7c020
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
@@ -0,0 +1,483 @@
+/*
+ * 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.headsup
+
+import android.os.Handler
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl.HeadsUpEntry
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.util.Compile
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/*
+ * Control when heads up notifications show during an avalanche where notifications arrive in fast
+ * succession, by delaying visual listener side effects and removal handling from
+ * [HeadsUpManagerImpl].
+ */
+@SysUISingleton
+class AvalancheController
+@Inject
+constructor(
+    dumpManager: DumpManager,
+    private val uiEventLogger: UiEventLogger,
+    private val headsUpManagerLogger: HeadsUpManagerLogger,
+    @Background private val bgHandler: Handler,
+) : Dumpable {
+
+    private val tag = "AvalancheController"
+    private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
+    var baseEntryMapStr: () -> String = { "baseEntryMapStr not initialized" }
+
+    var enableAtRuntime = true
+        set(value) {
+            if (!value) {
+                // Waiting HUNs in AvalancheController are shown in the HUN section in open shade.
+                // Clear them so we don't show them again when the shade closes and reordering is
+                // allowed again.
+                logDroppedHunsInBackground(getWaitingKeys().size)
+                clearNext()
+                headsUpEntryShowing = null
+            }
+            if (field != value) {
+                field = value
+            }
+        }
+
+    // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
+    @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
+
+    // Key of HUN previously showing, is being removed or was removed
+    var previousHunKey: String = ""
+
+    // List of runnables to run for the HUN showing right now
+    private var headsUpEntryShowingRunnableList: MutableList<Runnable> = ArrayList()
+
+    // HeadsUpEntry waiting to show
+    // Use sortable list instead of priority queue for debugging
+    private val nextList: MutableList<HeadsUpEntry> = ArrayList()
+
+    // Map of HeadsUpEntry waiting to show, and runnables to run when it shows.
+    // Use HashMap instead of SortedMap for faster lookup, and also because the ordering
+    // provided by HeadsUpEntry.compareTo is not consistent over time or with HeadsUpEntry.equals
+    @VisibleForTesting var nextMap: MutableMap<HeadsUpEntry, MutableList<Runnable>> = HashMap()
+
+    // Map of Runnable to label for debugging only
+    private val debugRunnableLabelMap: MutableMap<Runnable, String> = HashMap()
+
+    // HeadsUpEntry we did not show at all because they are not the top priority hun in their batch
+    // For debugging only
+    @VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet()
+
+    enum class ThrottleEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "HUN was shown.") AVALANCHE_THROTTLING_HUN_SHOWN(1821),
+        @UiEvent(doc = "HUN was dropped to show higher priority HUNs.")
+        AVALANCHE_THROTTLING_HUN_DROPPED(1822),
+        @UiEvent(doc = "HUN was removed while waiting to show.")
+        AVALANCHE_THROTTLING_HUN_REMOVED(1823);
+
+        override fun getId(): Int {
+            return id
+        }
+    }
+
+    init {
+        dumpManager.registerNormalDumpable(tag, /* module */ this)
+    }
+
+    fun getShowingHunKey(): String {
+        return getKey(headsUpEntryShowing)
+    }
+
+    fun isEnabled(): Boolean {
+        return NotificationThrottleHun.isEnabled && enableAtRuntime
+    }
+
+    /** Run or delay Runnable for given HeadsUpEntry */
+    fun update(entry: HeadsUpEntry?, runnable: Runnable?, caller: String) {
+        val isEnabled = isEnabled()
+        val key = getKey(entry)
+
+        if (runnable == null) {
+            headsUpManagerLogger.logAvalancheUpdate(
+                caller,
+                isEnabled,
+                key,
+                "Runnable NULL, stop. ${getStateStr()}",
+            )
+            return
+        }
+        if (!isEnabled) {
+            headsUpManagerLogger.logAvalancheUpdate(
+                caller,
+                isEnabled,
+                key,
+                "NOT ENABLED, run runnable. ${getStateStr()}",
+            )
+            runnable.run()
+            return
+        }
+        if (entry == null) {
+            headsUpManagerLogger.logAvalancheUpdate(
+                caller,
+                isEnabled,
+                key,
+                "Entry NULL, stop. ${getStateStr()}",
+            )
+            return
+        }
+        if (debug) {
+            debugRunnableLabelMap[runnable] = caller
+        }
+        var outcome = ""
+        if (isShowing(entry)) {
+            outcome = "update showing"
+            runnable.run()
+        } else if (entry in nextMap) {
+            outcome = "update next"
+            nextMap[entry]?.add(runnable)
+        } else if (headsUpEntryShowing == null) {
+            outcome = "show now"
+            showNow(entry, arrayListOf(runnable))
+        } else {
+            // Clean up invalid state when entry is in list but not map and vice versa
+            if (entry in nextMap) nextMap.remove(entry)
+            if (entry in nextList) nextList.remove(entry)
+
+            addToNext(entry, runnable)
+
+            // Shorten headsUpEntryShowing display time
+            val nextIndex = nextList.indexOf(entry)
+            val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1
+            if (isOnlyNextEntry) {
+                // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
+                // and goes to the isShowing case above
+                headsUpEntryShowing!!.updateEntry(
+                    /* updatePostTime= */ false,
+                    /* updateEarliestRemovalTime= */ false,
+                    /* reason= */ "avalanche duration update",
+                )
+            }
+        }
+        outcome += getStateStr()
+        headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, outcome)
+    }
+
+    @VisibleForTesting
+    fun addToNext(entry: HeadsUpEntry, runnable: Runnable) {
+        nextMap[entry] = arrayListOf(runnable)
+        nextList.add(entry)
+    }
+
+    /**
+     * Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
+     * all Runnables associated with that entry.
+     */
+    fun delete(entry: HeadsUpEntry?, runnable: Runnable?, caller: String) {
+        val isEnabled = isEnabled()
+        val key = getKey(entry)
+
+        if (runnable == null) {
+            headsUpManagerLogger.logAvalancheDelete(
+                caller,
+                isEnabled,
+                key,
+                "Runnable NULL, stop. ${getStateStr()}",
+            )
+            return
+        }
+        if (!isEnabled) {
+            runnable.run()
+            headsUpManagerLogger.logAvalancheDelete(
+                caller,
+                isEnabled = false,
+                key,
+                "NOT ENABLED, run runnable. ${getStateStr()}",
+            )
+            return
+        }
+        if (entry == null) {
+            runnable.run()
+            headsUpManagerLogger.logAvalancheDelete(
+                caller,
+                isEnabled = true,
+                key,
+                "Entry NULL, run runnable. ${getStateStr()}",
+            )
+            return
+        }
+        val outcome: String
+        if (entry in nextMap) {
+            if (entry in nextMap) nextMap.remove(entry)
+            if (entry in nextList) nextList.remove(entry)
+            uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_REMOVED)
+            outcome = "remove from next. ${getStateStr()}"
+        } else if (entry in debugDropSet) {
+            debugDropSet.remove(entry)
+            outcome = "remove from dropset. ${getStateStr()}"
+        } else if (isShowing(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()
+            outcome = "remove showing. ${getStateStr()}"
+        } else {
+            runnable.run()
+            outcome = "run runnable for untracked shown HUN. ${getStateStr()}"
+        }
+        headsUpManagerLogger.logAvalancheDelete(caller, isEnabled(), getKey(entry), outcome)
+    }
+
+    /**
+     * Returns duration based on
+     * 1) Whether HeadsUpEntry is the last one tracked by AvalancheController
+     * 2) The priority of the top HUN in the next batch Used by
+     *    BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
+     */
+    fun getDurationMs(entry: HeadsUpEntry?, autoDismissMs: Int): Int {
+        if (!isEnabled()) {
+            // Use default duration, like we did before AvalancheController existed
+            return autoDismissMs
+        }
+        if (entry == null) {
+            // This should never happen
+            return autoDismissMs
+        }
+        val showingList: MutableList<HeadsUpEntry> = mutableListOf()
+        if (headsUpEntryShowing != null) {
+            showingList.add(headsUpEntryShowing!!)
+        }
+        nextList.sort()
+        val entryList = showingList + nextList
+        if (entryList.isEmpty()) {
+            log { "No avalanche HUNs, use default ms: $autoDismissMs" }
+            return autoDismissMs
+        }
+        // entryList.indexOf(entry) returns -1 even when the entry is in entryList
+        var thisEntryIndex = -1
+        for ((i, e) in entryList.withIndex()) {
+            if (e == entry) {
+                thisEntryIndex = i
+            }
+        }
+        if (thisEntryIndex == -1) {
+            log { "Untracked entry, use default ms: $autoDismissMs" }
+            return autoDismissMs
+        }
+        val nextEntryIndex = thisEntryIndex + 1
+
+        // If last entry, use default duration
+        if (nextEntryIndex >= entryList.size) {
+            log { "Last entry, use default ms: $autoDismissMs" }
+            return autoDismissMs
+        }
+        val nextEntry = entryList[nextEntryIndex]
+        if (nextEntry.compareNonTimeFields(entry) == -1) {
+            // Next entry is higher priority
+            log { "Next entry is higher priority: 500ms" }
+            return 500
+        } else if (nextEntry.compareNonTimeFields(entry) == 0) {
+            // Next entry is same priority
+            log { "Next entry is same priority: 1000ms" }
+            return 1000
+        } else {
+            log { "Next entry is lower priority, use default ms: $autoDismissMs" }
+            return autoDismissMs
+        }
+    }
+
+    /** Return true if entry is waiting to show. */
+    fun isWaiting(key: String): Boolean {
+        if (!isEnabled()) {
+            return false
+        }
+        for (entry in nextMap.keys) {
+            if (entry.mEntry?.key.equals(key)) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /** Return list of keys for huns waiting */
+    fun getWaitingKeys(): MutableList<String> {
+        if (!isEnabled()) {
+            return mutableListOf()
+        }
+        val keyList = mutableListOf<String>()
+        for (entry in nextMap.keys) {
+            entry.mEntry?.let { keyList.add(entry.mEntry!!.key) }
+        }
+        return keyList
+    }
+
+    fun getWaitingEntry(key: String): HeadsUpEntry? {
+        if (!isEnabled()) {
+            return null
+        }
+        for (headsUpEntry in nextMap.keys) {
+            if (headsUpEntry.mEntry?.key.equals(key)) {
+                return headsUpEntry
+            }
+        }
+        return null
+    }
+
+    fun getWaitingEntryList(): List<HeadsUpEntry> {
+        if (!isEnabled()) {
+            return mutableListOf()
+        }
+        return nextMap.keys.toList()
+    }
+
+    private fun isShowing(entry: HeadsUpEntry): Boolean {
+        return headsUpEntryShowing != null && entry.mEntry?.key == headsUpEntryShowing?.mEntry?.key
+    }
+
+    private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) {
+        log { "SHOW: " + getKey(entry) }
+
+        uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_SHOWN)
+        headsUpEntryShowing = entry
+
+        runnableList.forEach {
+            if (it in debugRunnableLabelMap) {
+                log { "RUNNABLE: ${debugRunnableLabelMap[it]}" }
+            }
+            it.run()
+        }
+    }
+
+    private fun showNext() {
+        log { "SHOW NEXT" }
+        headsUpEntryShowing = null
+
+        if (nextList.isEmpty()) {
+            log { "NO MORE TO SHOW" }
+            previousHunKey = ""
+            return
+        }
+
+        // Only show first (top priority) entry in next batch
+        nextList.sort()
+        headsUpEntryShowing = nextList[0]
+        headsUpEntryShowingRunnableList = nextMap[headsUpEntryShowing]!!
+
+        // Remove runnable labels for dropped huns
+        val listToDrop = nextList.subList(1, nextList.size)
+        logDroppedHunsInBackground(listToDrop.size)
+
+        if (debug) {
+            // Clear runnable labels
+            for (e in listToDrop) {
+                val runnableList = nextMap[e]!!
+                for (r in runnableList) {
+                    debugRunnableLabelMap.remove(r)
+                }
+            }
+            debugDropSet.addAll(listToDrop)
+        }
+
+        clearNext()
+        showNow(headsUpEntryShowing!!, headsUpEntryShowingRunnableList)
+    }
+
+    private fun logDroppedHunsInBackground(numDropped: Int) {
+        bgHandler.post(
+            Runnable {
+                // Do this in the background to avoid missing frames when closing the shade
+                for (n in 1..numDropped) {
+                    uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_DROPPED)
+                }
+            }
+        )
+    }
+
+    fun clearNext() {
+        nextList.clear()
+        nextMap.clear()
+    }
+
+    // Methods below are for logging only ==========================================================
+
+    private inline fun log(s: () -> String) {
+        if (debug) {
+            Log.d(tag, s())
+        }
+    }
+
+    private fun getStateStr(): String {
+        return "\navalanche state:" +
+            "\n\tshowing: [${getKey(headsUpEntryShowing)}]" +
+            "\n\tprevious: [$previousHunKey]" +
+            "\n\tnext list: $nextListStr" +
+            "\n\tnext map: $nextMapStr" +
+            "\n\tdropped: $dropSetStr" +
+            "\nBHUM.mHeadsUpEntryMap: " +
+            baseEntryMapStr()
+    }
+
+    private val dropSetStr: String
+        get() {
+            val queue = ArrayList<String>()
+            for (entry in debugDropSet) {
+                queue.add("[${getKey(entry)}]")
+            }
+            return java.lang.String.join("\n", queue)
+        }
+
+    private val nextListStr: String
+        get() {
+            val queue = ArrayList<String>()
+            for (entry in nextList) {
+                queue.add("[${getKey(entry)}]")
+            }
+            return java.lang.String.join("\n", queue)
+        }
+
+    private val nextMapStr: String
+        get() {
+            val queue = ArrayList<String>()
+            for (entry in nextMap.keys) {
+                queue.add("[${getKey(entry)}]")
+            }
+            return java.lang.String.join("\n", queue)
+        }
+
+    fun getKey(entry: HeadsUpEntry?): String {
+        if (entry == null) {
+            return "HeadsUpEntry null"
+        }
+        if (entry.mEntry == null) {
+            return "HeadsUpEntry.mEntry null"
+        }
+        return entry.mEntry!!.key
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("AvalancheController: ${getStateStr()}")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt
new file mode 100644
index 0000000..424a3c5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt
@@ -0,0 +1,323 @@
+/*
+ * 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.headsup
+
+import android.graphics.Region
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import dagger.Binds
+import dagger.Module
+import java.io.PrintWriter
+import java.util.stream.Stream
+import javax.inject.Inject
+
+/**
+ * A manager which handles heads up notifications which is a special mode where they simply peek
+ * from the top of the screen.
+ */
+interface HeadsUpManager : Dumpable {
+    /** The stream of all current notifications managed by this manager. */
+    val allEntries: Stream<NotificationEntry>
+
+    /** Add a listener to receive callbacks onHeadsUpGoingAway. */
+    fun addHeadsUpPhoneListener(listener: OnHeadsUpPhoneListenerChange)
+
+    /** Adds an OnHeadUpChangedListener to observe events. */
+    fun addListener(listener: OnHeadsUpChangedListener)
+
+    fun addSwipedOutNotification(key: String)
+
+    /**
+     * Whether or not the alert can be removed currently. If it hasn't been on screen long enough it
+     * should not be removed unless forced
+     *
+     * @param key the key to check if removable
+     * @return true if the alert entry can be removed
+     */
+    fun canRemoveImmediately(key: String): Boolean
+
+    /**
+     * Compare two entries and decide how they should be ranked.
+     *
+     * @return -1 if the first argument should be ranked higher than the second, 1 if the second one
+     *   should be ranked higher and 0 if they are equal.
+     */
+    fun compare(a: NotificationEntry?, b: NotificationEntry?): Int
+
+    /**
+     * Extends the lifetime of the currently showing pulsing notification so that the pulse lasts
+     * longer.
+     */
+    fun extendHeadsUp()
+
+    /** Returns when a HUN entry should be removed in milliseconds from now. */
+    fun getEarliestRemovalTime(key: String?): Long
+
+    /** Returns the top Heads Up Notification, which appears to show at first. */
+    fun getTopEntry(): NotificationEntry?
+
+    /**
+     * Gets the touchable region needed for heads up notifications. Returns null if no touchable
+     * region is required (ie: no heads up notification currently exists).
+     */
+    // TODO(b/347007367): With scene container enabled this method may report outdated regions
+    fun getTouchableRegion(): Region?
+
+    /**
+     * Whether or not there are any entries managed by HeadsUpManager.
+     *
+     * @return true if there is a heads up entry, false otherwise
+     */
+    fun hasNotifications(): Boolean = false
+
+    /** Returns whether there are any pinned Heads Up Notifications or not. */
+    fun hasPinnedHeadsUp(): Boolean
+
+    /** Returns whether or not the given notification is managed by this manager. */
+    fun isHeadsUpEntry(key: String): Boolean
+
+    /** @see setHeadsUpAnimatingAway */
+    fun isHeadsUpAnimatingAwayValue(): Boolean
+
+    /** Returns if the given notification is snoozed or not. */
+    fun isSnoozed(packageName: String): Boolean
+
+    /** Returns whether the entry is (pinned and expanded) or (has an active remote input). */
+    fun isSticky(key: String?): Boolean
+
+    /**
+     * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as
+     * well.
+     */
+    fun isTrackingHeadsUp(): Boolean
+
+    fun onExpandingFinished()
+
+    /** Removes the OnHeadUpChangedListener from the observer list. */
+    fun removeListener(listener: OnHeadsUpChangedListener)
+
+    /**
+     * Try to remove the notification. May not succeed if the notification has not been shown long
+     * enough and needs to be kept around.
+     *
+     * @param key the key of the notification to remove
+     * @param releaseImmediately force a remove regardless of earliest removal time
+     * @param reason reason for removing the notification
+     * @return true if notification is removed, false otherwise
+     */
+    fun removeNotification(key: String, releaseImmediately: Boolean, reason: String): Boolean
+
+    /**
+     * Try to remove the notification. May not succeed if the notification has not been shown long
+     * enough and needs to be kept around.
+     *
+     * @param key the key of the notification to remove
+     * @param releaseImmediately force a remove regardless of earliest removal time
+     * @param animate if true, animate the removal
+     * @param reason reason for removing the notification
+     * @return true if notification is removed, false otherwise
+     */
+    fun removeNotification(
+        key: String,
+        releaseImmediately: Boolean,
+        animate: Boolean,
+        reason: String,
+    ): Boolean
+
+    /** Clears all managed notifications. */
+    fun releaseAllImmediately()
+
+    fun setAnimationStateHandler(handler: AnimationStateHandler)
+
+    /**
+     * Set an entry to be expanded and therefore stick in the heads up area if it's pinned until
+     * it's collapsed again.
+     */
+    fun setExpanded(entry: NotificationEntry, expanded: Boolean)
+
+    /**
+     * Sets whether an entry's guts are exposed and therefore it should stick in the heads up area
+     * if it's pinned until it's hidden again.
+     */
+    fun setGutsShown(entry: NotificationEntry, gutsShown: Boolean)
+
+    /**
+     * Set that we are exiting the headsUp pinned mode, but some notifications might still be
+     * animating out. This is used to keep the touchable regions in a reasonable state.
+     */
+    fun setHeadsUpAnimatingAway(headsUpAnimatingAway: Boolean)
+
+    /**
+     * Notifies that a remote input textbox in notification gets active or inactive.
+     *
+     * @param entry The entry of the target notification.
+     * @param remoteInputActive True to notify active, False to notify inactive.
+     */
+    fun setRemoteInputActive(entry: NotificationEntry, remoteInputActive: Boolean)
+
+    /**
+     * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry
+     * from the list even after a Heads Up Notification is gone.
+     */
+    fun setTrackingHeadsUp(tracking: Boolean)
+
+    /** Sets the current user. */
+    fun setUser(user: Int)
+
+    /**
+     * Notes that the user took an action on an entry that might indirectly cause the system or the
+     * app to remove the notification.
+     *
+     * @param entry the entry that might be indirectly removed by the user's action
+     * @see
+     *   com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator.mActionPressListener
+     * @see .canRemoveImmediately
+     */
+    fun setUserActionMayIndirectlyRemove(entry: NotificationEntry)
+
+    /**
+     * Decides whether a click is invalid for a notification, i.e. it has not been shown long enough
+     * that a user might have consciously clicked on it.
+     *
+     * @param key the key of the touched notification
+     * @return whether the touch is invalid and should be discarded
+     */
+    fun shouldSwallowClick(key: String): Boolean
+
+    /**
+     * Called when posting a new notification that should alert the user and appear on screen. Adds
+     * the notification to be managed.
+     *
+     * @param entry entry to show
+     */
+    fun showNotification(entry: NotificationEntry)
+
+    fun snooze()
+
+    /**
+     * Unpins all pinned Heads Up Notifications.
+     *
+     * @param userUnPinned The unpinned action is trigger by user real operation.
+     */
+    fun unpinAll(userUnPinned: Boolean)
+
+    fun updateNotification(key: String, shouldHeadsUpAgain: Boolean)
+
+    fun onEntryAnimatingAwayEnded(entry: NotificationEntry)
+}
+
+/** Sets the animation state of the HeadsUpManager. */
+interface AnimationStateHandler {
+    fun setHeadsUpGoingAwayAnimationsAllowed(allowed: Boolean)
+}
+
+/** Listener to register for HeadsUpNotification Phone changes. */
+interface OnHeadsUpPhoneListenerChange {
+    /**
+     * Called when a heads up notification is 'going away' or no longer 'going away'. See
+     * [HeadsUpManager.setHeadsUpAnimatingAway].
+     */
+    // TODO(b/325936094) delete this callback, and listen to the flow instead
+    fun onHeadsUpAnimatingAwayStateChanged(headsUpAnimatingAway: Boolean)
+}
+
+/* No op impl of HeadsUpManager. */
+class HeadsUpManagerEmptyImpl @Inject constructor() : HeadsUpManager {
+    override val allEntries = Stream.empty<NotificationEntry>()
+
+    override fun addHeadsUpPhoneListener(listener: OnHeadsUpPhoneListenerChange) {}
+
+    override fun addListener(listener: OnHeadsUpChangedListener) {}
+
+    override fun addSwipedOutNotification(key: String) {}
+
+    override fun canRemoveImmediately(key: String) = false
+
+    override fun compare(a: NotificationEntry?, b: NotificationEntry?) = 0
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {}
+
+    override fun extendHeadsUp() {}
+
+    override fun getEarliestRemovalTime(key: String?) = 0L
+
+    override fun getTouchableRegion(): Region? = null
+
+    override fun getTopEntry() = null
+
+    override fun hasPinnedHeadsUp() = false
+
+    override fun isHeadsUpEntry(key: String) = false
+
+    override fun isHeadsUpAnimatingAwayValue() = false
+
+    override fun isSnoozed(packageName: String) = false
+
+    override fun isSticky(key: String?) = false
+
+    override fun isTrackingHeadsUp() = false
+
+    override fun onExpandingFinished() {}
+
+    override fun releaseAllImmediately() {}
+
+    override fun removeListener(listener: OnHeadsUpChangedListener) {}
+
+    override fun removeNotification(key: String, releaseImmediately: Boolean, reason: String) =
+        false
+
+    override fun removeNotification(
+        key: String,
+        releaseImmediately: Boolean,
+        animate: Boolean,
+        reason: String,
+    ) = false
+
+    override fun setAnimationStateHandler(handler: AnimationStateHandler) {}
+
+    override fun setExpanded(entry: NotificationEntry, expanded: Boolean) {}
+
+    override fun setGutsShown(entry: NotificationEntry, gutsShown: Boolean) {}
+
+    override fun setHeadsUpAnimatingAway(headsUpAnimatingAway: Boolean) {}
+
+    override fun setRemoteInputActive(entry: NotificationEntry, remoteInputActive: Boolean) {}
+
+    override fun setTrackingHeadsUp(tracking: Boolean) {}
+
+    override fun setUser(user: Int) {}
+
+    override fun setUserActionMayIndirectlyRemove(entry: NotificationEntry) {}
+
+    override fun shouldSwallowClick(key: String): Boolean = false
+
+    override fun showNotification(entry: NotificationEntry) {}
+
+    override fun snooze() {}
+
+    override fun unpinAll(userUnPinned: Boolean) {}
+
+    override fun updateNotification(key: String, alert: Boolean) {}
+
+    override fun onEntryAnimatingAwayEnded(entry: NotificationEntry) {}
+}
+
+@Module
+interface HeadsUpEmptyImplModule {
+    @Binds @SysUISingleton fun bindsHeadsUpManager(noOpHum: HeadsUpManagerEmptyImpl): HeadsUpManager
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerExt.kt
new file mode 100644
index 0000000..6525b6f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerExt.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.statusbar.notification.headsup
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Returns a [Flow] that emits events whenever a [NotificationEntry] enters or exists the "heads up"
+ * state.
+ */
+val HeadsUpManager.headsUpEvents: Flow<Pair<NotificationEntry, Boolean>>
+    get() = conflatedCallbackFlow {
+        val listener =
+            object : OnHeadsUpChangedListener {
+                override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
+                    trySend(entry to isHeadsUp)
+                }
+            }
+        addListener(listener)
+        awaitClose { removeListener(listener) }
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
new file mode 100644
index 0000000..0b188afa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
@@ -0,0 +1,1672 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.headsup;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.Region;
+import android.os.Handler;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Pools;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.policy.SystemBarUtils;
+import com.android.systemui.EventLogTags;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.shade.ShadeDisplayAware;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingBannedListener;
+import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
+import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.util.ListenerSet;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.Stack;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
+import kotlinx.coroutines.flow.StateFlowKt;
+
+/**
+ * A manager which handles heads up notifications which is a special mode where
+ * they simply peek from the top of the screen.
+ */
+@SysUISingleton
+public class HeadsUpManagerImpl
+        implements HeadsUpManager, HeadsUpRepository, OnHeadsUpChangedListener {
+    private static final String TAG = "BaseHeadsUpManager";
+    private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
+    private static final String REASON_REORDER_ALLOWED = "mOnReorderingAllowedListener";
+    protected final ListenerSet<OnHeadsUpChangedListener> mListeners = new ListenerSet<>();
+
+    protected final Context mContext;
+
+    protected int mTouchAcceptanceDelay;
+    protected int mSnoozeLengthMs;
+    protected boolean mHasPinnedNotification;
+    protected int mUser;
+
+    private final ArrayMap<String, Long> mSnoozedPackages;
+    private final AccessibilityManagerWrapper mAccessibilityMgr;
+
+    private final UiEventLogger mUiEventLogger;
+    private AvalancheController mAvalancheController;
+    private final KeyguardBypassController mBypassController;
+    private final GroupMembershipManager mGroupMembershipManager;
+    private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
+    private final VisualStabilityProvider mVisualStabilityProvider;
+
+    protected final SystemClock mSystemClock;
+    protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
+    protected final HeadsUpManagerLogger mLogger;
+    protected int mMinimumDisplayTime;
+    protected int mStickyForSomeTimeAutoDismissTime;
+    protected int mAutoDismissTime;
+    protected DelayableExecutor mExecutor;
+
+    private final int mExtensionTime;
+
+    // TODO(b/328393698) move the topHeadsUpRow logic to an interactor
+    private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow =
+            StateFlowKt.MutableStateFlow(null);
+    private final MutableStateFlow<Set<HeadsUpRowRepository>> mHeadsUpNotificationRows =
+            StateFlowKt.MutableStateFlow(new HashSet<>());
+    private final MutableStateFlow<Boolean> mHeadsUpAnimatingAway =
+            StateFlowKt.MutableStateFlow(false);
+    private final HashSet<String> mSwipedOutKeys = new HashSet<>();
+    private final HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
+    @VisibleForTesting
+    public final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
+            = new ArraySet<>();
+
+    private boolean mReleaseOnExpandFinish;
+    private boolean mTrackingHeadsUp;
+    private boolean mIsShadeOrQsExpanded;
+    private boolean mIsQsExpanded;
+    private int mStatusBarState;
+    private AnimationStateHandler mAnimationStateHandler;
+    private int mHeadsUpInset;
+
+    // Used for determining the region for touch interaction
+    private final Region mTouchableRegion = new Region();
+
+    private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<>() {
+        private final Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
+
+        @Override
+        public HeadsUpEntry acquire() {
+            NotificationThrottleHun.assertInLegacyMode();
+            if (!mPoolObjects.isEmpty()) {
+                return mPoolObjects.pop();
+            }
+            return new HeadsUpEntry();
+        }
+
+        @Override
+        public boolean release(@NonNull HeadsUpEntry instance) {
+            NotificationThrottleHun.assertInLegacyMode();
+            mPoolObjects.push(instance);
+            return true;
+        }
+    };
+
+    /**
+     * Enum entry for notification peek logged from this class.
+     */
+    enum NotificationPeekEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Heads-up notification peeked on screen.")
+        NOTIFICATION_PEEK(801);
+
+        private final int mId;
+        NotificationPeekEvent(int id) {
+            mId = id;
+        }
+        @Override public int getId() {
+            return mId;
+        }
+    }
+
+    @Inject
+    public HeadsUpManagerImpl(
+            @NonNull final Context context,
+            HeadsUpManagerLogger logger,
+            StatusBarStateController statusBarStateController,
+            KeyguardBypassController bypassController,
+            GroupMembershipManager groupMembershipManager,
+            VisualStabilityProvider visualStabilityProvider,
+            @ShadeDisplayAware ConfigurationController configurationController,
+            @Main Handler handler,
+            GlobalSettings globalSettings,
+            SystemClock systemClock,
+            @Main DelayableExecutor executor,
+            AccessibilityManagerWrapper accessibilityManagerWrapper,
+            UiEventLogger uiEventLogger,
+            JavaAdapter javaAdapter,
+            ShadeInteractor shadeInteractor,
+            AvalancheController avalancheController) {
+        mLogger = logger;
+        mExecutor = executor;
+        mSystemClock = systemClock;
+        mContext = context;
+        mAccessibilityMgr = accessibilityManagerWrapper;
+        mUiEventLogger = uiEventLogger;
+        mAvalancheController = avalancheController;
+        mAvalancheController.setBaseEntryMapStr(this::getEntryMapStr);
+        mBypassController = bypassController;
+        mGroupMembershipManager = groupMembershipManager;
+        mVisualStabilityProvider = visualStabilityProvider;
+        Resources resources = context.getResources();
+        mMinimumDisplayTime = NotificationThrottleHun.isEnabled()
+                ? resources.getInteger(R.integer.heads_up_notification_minimum_time_with_throttling)
+                : 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);
+        mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
+        mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
+        mSnoozedPackages = new ArrayMap<>();
+        int defaultSnoozeLengthMs =
+                resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
+
+        mSnoozeLengthMs = globalSettings.getInt(SETTING_HEADS_UP_SNOOZE_LENGTH_MS,
+                defaultSnoozeLengthMs);
+        ContentObserver settingsObserver = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                final int packageSnoozeLengthMs = globalSettings.getInt(
+                        SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1);
+                if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) {
+                    mSnoozeLengthMs = packageSnoozeLengthMs;
+                    mLogger.logSnoozeLengthChange(packageSnoozeLengthMs);
+                }
+            }
+        };
+        globalSettings.registerContentObserverSync(
+                globalSettings.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS),
+                /* notifyForDescendants = */ false,
+                settingsObserver);
+
+        statusBarStateController.addCallback(mStatusBarStateListener);
+        updateResources();
+        configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
+            @Override
+            public void onDensityOrFontScaleChanged() {
+                updateResources();
+            }
+
+            @Override
+            public void onThemeChanged() {
+                updateResources();
+            }
+        });
+        javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
+                this::onShadeOrQsExpanded);
+        if (SceneContainerFlag.isEnabled()) {
+            javaAdapter.alwaysCollectFlow(shadeInteractor.isQsExpanded(),
+                    this::onQsExpanded);
+        }
+        if (NotificationThrottleHun.isEnabled()) {
+            mVisualStabilityProvider.addPersistentReorderingBannedListener(
+                    mOnReorderingBannedListener);
+            mVisualStabilityProvider.addPersistentReorderingAllowedListener(
+                    mOnReorderingAllowedListener);
+        }
+    }
+
+    /**
+     * Adds an OnHeadUpChangedListener to observe events.
+     */
+    @Override
+    public void addListener(@NonNull OnHeadsUpChangedListener listener) {
+        mListeners.addIfAbsent(listener);
+    }
+
+    /**
+     * Removes the OnHeadUpChangedListener from the observer list.
+     */
+    @Override
+    public void removeListener(@NonNull OnHeadsUpChangedListener listener) {
+        mListeners.remove(listener);
+    }
+
+    /**
+     * Add a listener to receive callbacks {@link #setHeadsUpAnimatingAway(boolean)}
+     */
+    @Override
+    public void addHeadsUpPhoneListener(@NonNull OnHeadsUpPhoneListenerChange listener) {
+        mHeadsUpPhoneListeners.add(listener);
+    }
+
+    @Override
+    public void setAnimationStateHandler(@NonNull AnimationStateHandler handler) {
+        mAnimationStateHandler = handler;
+    }
+
+    private void updateResources() {
+        Resources resources = mContext.getResources();
+        mHeadsUpInset = SystemBarUtils.getStatusBarHeight(mContext)
+                + resources.getDimensionPixelSize(R.dimen.heads_up_status_bar_padding);
+    }
+
+    /**
+     * Called when posting a new notification that should appear on screen.
+     * Adds the notification to be managed.
+     * @param entry entry to show
+     */
+    @Override
+    public void showNotification(@NonNull NotificationEntry entry) {
+        HeadsUpEntry headsUpEntry = createHeadsUpEntry(entry);
+
+        mLogger.logShowNotificationRequest(entry);
+
+        Runnable runnable = () -> {
+            mLogger.logShowNotification(entry);
+
+            // Add new entry and begin managing it
+            mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
+            onEntryAdded(headsUpEntry);
+            // TODO(b/328390331) move accessibility events to the view layer
+            entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+            entry.setIsHeadsUpEntry(true);
+
+            updateNotificationInternal(entry.getKey(), true /* shouldHeadsUpAgain */);
+            entry.setInterruption();
+        };
+        mAvalancheController.update(headsUpEntry, runnable, "showNotification");
+    }
+
+    @Override
+    public boolean removeNotification(
+            @NonNull String key,
+            boolean releaseImmediately,
+            boolean animate,
+            @NonNull String reason) {
+        if (animate) {
+            return removeNotification(key, releaseImmediately,
+                    "removeNotification(animate: true), reason: " + reason);
+        } else {
+            mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
+            final boolean removed = removeNotification(key, releaseImmediately,
+                    "removeNotification(animate: false), reason: " + reason);
+            mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
+            return removed;
+        }
+    }
+
+    @Override
+    public boolean removeNotification(@NotNull String key, boolean releaseImmediately,
+            @NonNull String reason) {
+        final boolean isWaiting = mAvalancheController.isWaiting(key);
+        mLogger.logRemoveNotification(key, releaseImmediately, isWaiting, reason);
+
+        if (mAvalancheController.isWaiting(key)) {
+            removeEntry(key, "removeNotification (isWaiting)");
+            return true;
+        }
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        if (headsUpEntry == null) {
+            mLogger.logNullEntry(key, reason);
+            return true;
+        }
+        if (releaseImmediately) {
+            removeEntry(key, "removeNotification (releaseImmediately)");
+            return true;
+        }
+        if (canRemoveImmediately(key)) {
+            removeEntry(key, "removeNotification (canRemoveImmediately)");
+            return true;
+        }
+        headsUpEntry.removeAsSoonAsPossible();
+        return false;
+    }
+
+    /**
+     * Called when the notification state has been updated.
+     * @param key the key of the entry that was updated
+     * @param shouldHeadsUpAgain whether the notification should show again and force reevaluation
+     *                           of removal time
+     */
+    public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        mLogger.logUpdateNotificationRequest(key, shouldHeadsUpAgain, headsUpEntry != null);
+
+        Runnable runnable = () -> {
+            updateNotificationInternal(key, shouldHeadsUpAgain);
+        };
+        mAvalancheController.update(headsUpEntry, runnable, "updateNotification");
+    }
+
+    private void updateNotificationInternal(@NonNull String key, boolean shouldHeadsUpAgain) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null);
+        if (headsUpEntry == null) {
+            // the entry was released before this update (i.e by a listener) This can happen
+            // with the groupmanager
+            return;
+        }
+        // TODO(b/328390331) move accessibility events to the view layer
+        if (headsUpEntry.mEntry != null) {
+            headsUpEntry.mEntry.sendAccessibilityEvent(
+                    AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+        }
+        if (shouldHeadsUpAgain) {
+            headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification");
+            PinnedStatus pinnedStatus = shouldHeadsUpBecomePinned(headsUpEntry.mEntry)
+                    ? PinnedStatus.PinnedBySystem
+                    : PinnedStatus.NotPinned;
+            if (headsUpEntry != null) {
+                setEntryPinned(headsUpEntry, pinnedStatus, "updateNotificationInternal");
+            }
+        }
+    }
+
+    @Override
+    public void setTrackingHeadsUp(boolean trackingHeadsUp) {
+        mTrackingHeadsUp = trackingHeadsUp;
+    }
+
+    @Override
+    public boolean shouldSwallowClick(@NonNull String key) {
+        HeadsUpManagerImpl.HeadsUpEntry entry = getHeadsUpEntry(key);
+        return entry != null && mSystemClock.elapsedRealtime() < entry.mPostTime;
+    }
+
+    @Override
+    public void releaseAfterExpansion() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        onExpandingFinished();
+    }
+
+    @Override
+    public void onExpandingFinished() {
+        if (mReleaseOnExpandFinish) {
+            releaseAllImmediately();
+            mReleaseOnExpandFinish = false;
+        } else {
+            for (NotificationEntry entry : getAllEntries().toList()) {
+                entry.setSeenInShade(true);
+            }
+            for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
+                if (isHeadsUpEntry(entry.getKey())) {
+                    // Maybe the heads-up was removed already
+                    removeEntry(entry.getKey(), "onExpandingFinished");
+                }
+            }
+        }
+        mEntriesToRemoveAfterExpand.clear();
+    }
+
+    /**
+     * Clears all managed notifications.
+     */
+    public void releaseAllImmediately() {
+        mLogger.logReleaseAllImmediately();
+        // A copy is necessary here as we are changing the underlying map.  This would cause
+        // undefined behavior if we iterated over the key set directly.
+        ArraySet<String> keysToRemove = new ArraySet<>(mHeadsUpEntryMap.keySet());
+
+        // Must get waiting keys before calling removeEntry, which clears waiting entries in
+        // AvalancheController
+        List<String> waitingKeysToRemove = mAvalancheController.getWaitingKeys();
+
+        for (String key : keysToRemove) {
+            removeEntry(key, "releaseAllImmediately (keysToRemove)");
+        }
+        for (String key : waitingKeysToRemove) {
+            removeEntry(key, "releaseAllImmediately (waitingKeysToRemove)");
+        }
+    }
+
+    /**
+     * Returns the entry if it is managed by this manager.
+     * @param key key of notification
+     * @return the entry
+     */
+    @Nullable
+    public NotificationEntry getEntry(@NonNull String key) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        return headsUpEntry != null ? headsUpEntry.mEntry : null;
+    }
+
+    /**
+     * Returns the stream of all current notifications managed by this manager.
+     * @return all entries
+     */
+    @NonNull
+    @Override
+    public Stream<NotificationEntry> getAllEntries() {
+        return getHeadsUpEntryList().stream().map(headsUpEntry -> headsUpEntry.mEntry);
+    }
+
+    public List<HeadsUpEntry> getHeadsUpEntryList() {
+        List<HeadsUpEntry> entryList = new ArrayList<>(mHeadsUpEntryMap.values());
+        entryList.addAll(mAvalancheController.getWaitingEntryList());
+        return entryList;
+    }
+
+    /**
+     * Whether or not there are any active notifications.
+     * @return true if there is an entry, false otherwise
+     */
+    @Override
+    public boolean hasNotifications() {
+        return !mHeadsUpEntryMap.isEmpty()
+                || !mAvalancheController.getWaitingEntryList().isEmpty();
+    }
+
+    /**
+     * @return true if the notification is managed by this manager
+     */
+    public boolean isHeadsUpEntry(@NonNull String key) {
+        return mHeadsUpEntryMap.containsKey(key) || mAvalancheController.isWaiting(key);
+    }
+
+    /**
+     * @param key
+     * @return When a HUN entry should be removed in milliseconds from now
+     */
+    @Override
+    public long getEarliestRemovalTime(String key) {
+        HeadsUpEntry entry = mHeadsUpEntryMap.get(key);
+        if (entry != null) {
+            return Math.max(0, entry.mEarliestRemovalTime - mSystemClock.elapsedRealtime());
+        }
+        return 0;
+    }
+
+    @VisibleForTesting
+    protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationEntry entry) {
+        boolean pin = mStatusBarState == StatusBarState.SHADE && !mIsShadeOrQsExpanded;
+        if (SceneContainerFlag.isEnabled()) {
+            pin |= mIsQsExpanded;
+        }
+        if (mBypassController.getBypassEnabled()) {
+            pin |= mStatusBarState == StatusBarState.KEYGUARD;
+        }
+        if (pin) {
+            return true;
+        }
+
+        final HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
+        if (headsUpEntry == null) {
+            // This should not happen since shouldHeadsUpBecomePinned is always called after adding
+            // the NotificationEntry into mHeadsUpEntryMap.
+            return hasFullScreenIntent(entry);
+        }
+        return hasFullScreenIntent(entry) && !headsUpEntry.mWasUnpinned;
+    }
+
+    protected boolean hasFullScreenIntent(@NonNull NotificationEntry entry) {
+        if (entry == null) {
+            return false;
+        }
+        if (entry.getSbn() == null) {
+            return false;
+        }
+        if (entry.getSbn().getNotification() == null) {
+            return false;
+        }
+        return entry.getSbn().getNotification().fullScreenIntent != null;
+    }
+
+    protected void setEntryPinned(
+            @NonNull HeadsUpManagerImpl.HeadsUpEntry headsUpEntry, PinnedStatus pinnedStatus,
+            String reason) {
+        mLogger.logSetEntryPinned(headsUpEntry.mEntry, pinnedStatus, reason);
+        NotificationEntry entry = headsUpEntry.mEntry;
+        boolean isPinned = pinnedStatus.isPinned();
+        if (!isPinned) {
+            headsUpEntry.mWasUnpinned = true;
+        }
+        if (headsUpEntry.getPinnedStatus().getValue() != pinnedStatus) {
+            headsUpEntry.setRowPinnedStatus(pinnedStatus);
+            updatePinnedMode();
+            if (isPinned && entry.getSbn() != null) {
+               mUiEventLogger.logWithInstanceId(
+                        NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(),
+                        entry.getSbn().getPackageName(), entry.getSbn().getInstanceId());
+            }
+        // TODO(b/325936094) use the isPinned Flow instead
+            for (OnHeadsUpChangedListener listener : mListeners) {
+                if (isPinned) {
+                    listener.onHeadsUpPinned(entry);
+                } else {
+                    listener.onHeadsUpUnPinned(entry);
+                }
+            }
+        }
+    }
+
+    /**
+     * Manager-specific logic that should occur when an entry is added.
+     * @param headsUpEntry entry added
+     */
+    protected void onEntryAdded(HeadsUpEntry headsUpEntry) {
+        NotificationEntry entry = headsUpEntry.mEntry;
+        entry.setHeadsUp(true);
+
+        final PinnedStatus pinnedStatus = shouldHeadsUpBecomePinned(entry)
+                ? PinnedStatus.PinnedBySystem
+                : PinnedStatus.NotPinned;
+        setEntryPinned(headsUpEntry, pinnedStatus, "onEntryAdded");
+        EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
+        for (OnHeadsUpChangedListener listener : mListeners) {
+            listener.onHeadsUpStateChanged(entry, true);
+        }
+        updateTopHeadsUpFlow();
+        updateHeadsUpFlow();
+    }
+
+    /**
+     * Remove a notification from the alerting entries.
+     * @param key key of notification to remove
+     */
+    protected final void removeEntry(@NonNull String key, String reason) {
+        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+        boolean isWaiting;
+        if (headsUpEntry == null) {
+            headsUpEntry = mAvalancheController.getWaitingEntry(key);
+            isWaiting = true;
+        } else {
+            isWaiting = false;
+        }
+        mLogger.logRemoveEntryRequest(key, reason, isWaiting);
+        HeadsUpEntry finalHeadsUpEntry = headsUpEntry;
+        Runnable runnable = () -> {
+            mLogger.logRemoveEntry(key, reason, isWaiting);
+
+            if (finalHeadsUpEntry == null) {
+                return;
+            }
+            NotificationEntry entry = finalHeadsUpEntry.mEntry;
+
+            // If the notification is animating, we will remove it at the end of the animation.
+            if (entry != null && entry.isExpandAnimationRunning()) {
+                return;
+            }
+            entry.demoteStickyHun();
+            mHeadsUpEntryMap.remove(key);
+            onEntryRemoved(finalHeadsUpEntry, reason);
+            // TODO(b/328390331) move accessibility events to the view layer
+            entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+            if (NotificationThrottleHun.isEnabled()) {
+                finalHeadsUpEntry.cancelAutoRemovalCallbacks("removeEntry");
+            } else {
+                finalHeadsUpEntry.reset();
+            }
+        };
+        mAvalancheController.delete(headsUpEntry, runnable, "removeEntry");
+    }
+
+    /**
+     * Manager-specific logic that should occur when an entry is removed.
+     * @param headsUpEntry entry removed
+     * @param reason why onEntryRemoved was called
+     */
+    protected void onEntryRemoved(HeadsUpEntry headsUpEntry, String reason) {
+        NotificationEntry entry = headsUpEntry.mEntry;
+        entry.setHeadsUp(false);
+        setEntryPinned(headsUpEntry, PinnedStatus.NotPinned, "onEntryRemoved");
+        EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
+        mLogger.logNotificationActuallyRemoved(entry);
+        for (OnHeadsUpChangedListener listener : mListeners) {
+            listener.onHeadsUpStateChanged(entry, false);
+        }
+        if (!NotificationThrottleHun.isEnabled()) {
+            mEntryPool.release(headsUpEntry);
+        }
+        updateTopHeadsUpFlow();
+        updateHeadsUpFlow();
+        if (NotificationThrottleHun.isEnabled()) {
+            NotificationEntry notifEntry = headsUpEntry.mEntry;
+            if (notifEntry == null) {
+                return;
+            }
+            // If reorder was just allowed and we called onEntryRemoved while iterating over
+            // mEntriesToRemoveWhenReorderingAllowed, we should not remove from this list (and cause
+            // ArrayIndexOutOfBoundsException). We don't need to in this case anyway, because we
+            // clear mEntriesToRemoveWhenReorderingAllowed after removing these entries.
+            if (!reason.equals(REASON_REORDER_ALLOWED)
+                    && mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)) {
+                mEntriesToRemoveWhenReorderingAllowed.remove(notifEntry);
+            }
+        }
+    }
+
+    private void updateTopHeadsUpFlow() {
+        mTopHeadsUpRow.setValue((HeadsUpRowRepository) getTopHeadsUpEntry());
+    }
+
+    private void updateHeadsUpFlow() {
+        mHeadsUpNotificationRows.setValue(new HashSet<>(getHeadsUpEntryPhoneMap().values()));
+    }
+
+    @Override
+    @NonNull
+    public Flow<HeadsUpRowRepository> getTopHeadsUpRow() {
+        return mTopHeadsUpRow;
+    }
+
+    @Override
+    @NonNull
+    public Flow<Set<HeadsUpRowRepository>> getActiveHeadsUpRows() {
+        return mHeadsUpNotificationRows;
+    }
+
+    @Override
+    @NonNull
+    public StateFlow<Boolean> isHeadsUpAnimatingAway() {
+        return mHeadsUpAnimatingAway;
+    }
+
+    @Override
+    public boolean isHeadsUpAnimatingAwayValue() {
+        return mHeadsUpAnimatingAway.getValue();
+    }
+
+    @NonNull
+    private ArrayMap<String, HeadsUpEntry> getHeadsUpEntryPhoneMap() {
+        return mHeadsUpEntryMap;
+    }
+
+    /**
+     * Called to notify the listeners that the HUN animating away animation has ended.
+     */
+    @Override
+    public void onEntryAnimatingAwayEnded(@NonNull NotificationEntry entry) {
+        for (OnHeadsUpChangedListener listener : mListeners) {
+            listener.onHeadsUpAnimatingAwayEnded(entry);
+        }
+    }
+
+    /**
+     * Manager-specific logic, that should occur, when the entry is updated, and its posted time has
+     * changed.
+     *
+     * @param headsUpEntry entry updated
+     */
+    protected void onEntryUpdated(HeadsUpEntry headsUpEntry) {
+        // no need to update the list here
+        updateTopHeadsUpFlow();
+    }
+
+    protected void updatePinnedMode() {
+        boolean hasPinnedNotification = hasPinnedNotificationInternal();
+        if (hasPinnedNotification == mHasPinnedNotification) {
+            return;
+        }
+        mLogger.logUpdatePinnedMode(hasPinnedNotification);
+        mHasPinnedNotification = hasPinnedNotification;
+        if (mHasPinnedNotification) {
+            MetricsLogger.count(mContext, "note_peek", 1);
+        }
+        for (OnHeadsUpChangedListener listener : mListeners) {
+            listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
+        }
+    }
+
+    /**
+     * Returns if the given notification is snoozed or not.
+     */
+    public boolean isSnoozed(@NonNull String packageName) {
+        final String key = snoozeKey(packageName, mUser);
+        Long snoozedUntil = mSnoozedPackages.get(key);
+        if (snoozedUntil != null) {
+            if (snoozedUntil > mSystemClock.elapsedRealtime()) {
+                mLogger.logIsSnoozedReturned(key);
+                return true;
+            }
+            mLogger.logPackageUnsnoozed(key);
+            mSnoozedPackages.remove(key);
+        }
+        return false;
+    }
+
+    /**
+     * Snoozes all current Heads Up Notifications.
+     */
+    @Override
+    public void snooze() {
+        List<String> keySet = new ArrayList<>(mHeadsUpEntryMap.keySet());
+        keySet.addAll(mAvalancheController.getWaitingKeys());
+        for (String key : keySet) {
+            HeadsUpEntry entry = getHeadsUpEntry(key);
+            if (entry.mEntry == null) {
+                continue;
+            }
+            String packageName = entry.mEntry.getSbn().getPackageName();
+            String snoozeKey = snoozeKey(packageName, mUser);
+            mLogger.logPackageSnoozed(snoozeKey);
+            mSnoozedPackages.put(snoozeKey, mSystemClock.elapsedRealtime() + mSnoozeLengthMs);
+        }
+        mReleaseOnExpandFinish = true;
+    }
+
+    @NonNull
+    private static String snoozeKey(@NonNull String packageName, int user) {
+        return user + "," + packageName;
+    }
+
+    @Override
+    public void addSwipedOutNotification(@NonNull String key) {
+        mSwipedOutKeys.add(key);
+    }
+
+    @Nullable
+    protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
+        if (mHeadsUpEntryMap.containsKey(key)) {
+            return mHeadsUpEntryMap.get(key);
+        }
+        return mAvalancheController.getWaitingEntry(key);
+    }
+
+    /**
+     * Returns the top Heads Up Notification, which appears to show at first.
+     */
+    @Nullable
+    public NotificationEntry getTopEntry() {
+        HeadsUpEntry topEntry = getTopHeadsUpEntry();
+        return (topEntry != null) ? topEntry.mEntry : null;
+    }
+
+    @Nullable
+    protected HeadsUpEntry getTopHeadsUpEntry() {
+        if (mHeadsUpEntryMap.isEmpty()) {
+            return null;
+        }
+        HeadsUpEntry topEntry = null;
+        for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
+            if (topEntry == null || entry.compareTo(topEntry) < 0) {
+                topEntry = entry;
+            }
+        }
+        return topEntry;
+    }
+
+    /**
+     * Sets the current user.
+     */
+    public void setUser(int user) {
+        mUser = user;
+    }
+
+    /** Returns the ID of the current user. */
+    public int getUser() {
+        return  mUser;
+    }
+
+    private String getEntryMapStr() {
+        if (mHeadsUpEntryMap.isEmpty()) {
+            return "EMPTY";
+        }
+        StringBuilder entryMapStr = new StringBuilder();
+        for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
+            entryMapStr.append("\n\t").append(
+                    entry.mEntry == null ? "null" : entry.mEntry.getKey());
+        }
+        return entryMapStr.toString();
+    }
+
+    @Override
+    public @Nullable Region getTouchableRegion() {
+        NotificationEntry topEntry = getTopEntry();
+
+        // This call could be made in an inconsistent state while the pinnedMode hasn't been
+        // updated yet, but callbacks leading out of the headsUp manager, querying it. Let's
+        // therefore also check if the topEntry is null.
+        if (!hasPinnedHeadsUp() || topEntry == null) {
+            return null;
+        } else {
+            if (topEntry.rowIsChildInGroup()) {
+                final NotificationEntry groupSummary =
+                        mGroupMembershipManager.getGroupSummary(topEntry);
+                if (groupSummary != null) {
+                    topEntry = groupSummary;
+                }
+            }
+            ExpandableNotificationRow topRow = topEntry.getRow();
+            int[] tmpArray = new int[2];
+            topRow.getLocationOnScreen(tmpArray);
+            int minX = tmpArray[0];
+            int maxX = tmpArray[0] + topRow.getWidth();
+            int height = topRow.getIntrinsicHeight();
+            final boolean stretchToTop = tmpArray[1] <= mHeadsUpInset;
+            mTouchableRegion.set(minX, stretchToTop ? 0 : tmpArray[1], maxX, tmpArray[1] + height);
+            return mTouchableRegion;
+        }
+    }
+
+    @Override
+    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+        if (headsUpAnimatingAway != mHeadsUpAnimatingAway.getValue()) {
+            for (OnHeadsUpPhoneListenerChange listener : mHeadsUpPhoneListeners) {
+                listener.onHeadsUpAnimatingAwayStateChanged(headsUpAnimatingAway);
+            }
+            mHeadsUpAnimatingAway.setValue(headsUpAnimatingAway);
+        }
+    }
+
+    private void onShadeOrQsExpanded(Boolean isExpanded) {
+        if (isExpanded != mIsShadeOrQsExpanded) {
+            mIsShadeOrQsExpanded = isExpanded;
+            if (!SceneContainerFlag.isEnabled() && isExpanded) {
+                mHeadsUpAnimatingAway.setValue(false);
+            }
+        }
+    }
+
+    private void onQsExpanded(Boolean isQsExpanded) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        if (isQsExpanded != mIsQsExpanded) mIsQsExpanded = isQsExpanded;
+    }
+
+    @Override
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("HeadsUpManager state:");
+        dumpInternal(pw, args);
+    }
+
+    protected void dumpInternal(@NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.print("  mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
+        pw.print("  mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
+        pw.print("  now="); pw.println(mSystemClock.elapsedRealtime());
+        pw.print("  mUser="); pw.println(mUser);
+        for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
+            pw.println(entry.mEntry == null ? "null" : entry.mEntry);
+        }
+        int n = mSnoozedPackages.size();
+        pw.println("  snoozed packages: " + n);
+        for (int i = 0; i < n; i++) {
+            pw.print("    "); pw.print(mSnoozedPackages.valueAt(i));
+            pw.print(", "); pw.println(mSnoozedPackages.keyAt(i));
+        }
+        pw.print("  mBarState=");
+        pw.println(mStatusBarState);
+        pw.print("  mTouchableRegion=");
+        pw.println(mTouchableRegion);
+    }
+
+    /**
+     * Returns if there are any pinned Heads Up Notifications or not.
+     */
+    public boolean hasPinnedHeadsUp() {
+        return mHasPinnedNotification;
+    }
+
+    private boolean hasPinnedNotificationInternal() {
+        for (String key : mHeadsUpEntryMap.keySet()) {
+            HeadsUpEntry entry = getHeadsUpEntry(key);
+            if (entry.mEntry != null && entry.mEntry.isRowPinned()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Unpins all pinned Heads Up Notifications.
+     * @param userUnPinned The unpinned action is trigger by user real operation.
+     */
+    @Override
+    public void unpinAll(boolean userUnPinned) {
+        for (String key : mHeadsUpEntryMap.keySet()) {
+            HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+            mLogger.logUnpinEntryRequest(key);
+            Runnable runnable = () -> {
+                mLogger.logUnpinEntry(key);
+
+                setEntryPinned(headsUpEntry, PinnedStatus.NotPinned, "unpinAll");
+                // maybe it got un sticky
+                headsUpEntry.updateEntry(false /* updatePostTime */, "unpinAll");
+
+                // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
+                // on the screen.
+                if (userUnPinned && headsUpEntry.mEntry != null) {
+                    if (headsUpEntry.mEntry != null && headsUpEntry.mEntry.mustStayOnScreen()) {
+                        headsUpEntry.mEntry.setHeadsUpIsVisible();
+                    }
+                }
+            };
+            mAvalancheController.delete(headsUpEntry, runnable, "unpinAll");
+        }
+    }
+
+    @Override
+    public void setRemoteInputActive(
+            @NonNull NotificationEntry entry, boolean remoteInputActive) {
+        HeadsUpEntry headsUpEntry = getHeadsUpEntryPhone(entry.getKey());
+        if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
+            headsUpEntry.mRemoteInputActive = remoteInputActive;
+            if (ExpandHeadsUpOnInlineReply.isEnabled() && remoteInputActive) {
+                headsUpEntry.mRemoteInputActivatedAtLeastOnce = true;
+            }
+            if (remoteInputActive) {
+                headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
+            } else {
+                headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
+            }
+            onEntryUpdated(headsUpEntry);
+        }
+    }
+
+    @Nullable
+    private HeadsUpEntry getHeadsUpEntryPhone(@NonNull String key) {
+        return mHeadsUpEntryMap.get(key);
+    }
+
+    @Override
+    public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) {
+        HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
+        if (headsUpEntry == null) return;
+        if (entry.isRowPinned() || !gutsShown) {
+            headsUpEntry.setGutsShownPinned(gutsShown);
+        }
+    }
+
+    @Override
+    public void extendHeadsUp() {
+        HeadsUpEntry topEntry = getTopHeadsUpEntryPhone();
+        if (topEntry == null) {
+            return;
+        }
+        topEntry.extendPulse();
+    }
+
+    @Nullable
+    private HeadsUpEntry getTopHeadsUpEntryPhone() {
+        if (SceneContainerFlag.isEnabled()) {
+            return (HeadsUpEntry) mTopHeadsUpRow.getValue();
+        } else {
+            return getTopHeadsUpEntry();
+        }
+    }
+
+    @Override
+    public boolean isTrackingHeadsUp() {
+        return mTrackingHeadsUp;
+    }
+
+    /**
+     * Compare two entries and decide how they should be ranked.
+     *
+     * @return -1 if the first argument should be ranked higher than the second, 1 if the second
+     * one should be ranked higher and 0 if they are equal.
+     */
+    public int compare(@Nullable NotificationEntry a, @Nullable NotificationEntry b) {
+        if (a == null || b == null) {
+            return Boolean.compare(a == null, b == null);
+        }
+        HeadsUpEntry aEntry = getHeadsUpEntry(a.getKey());
+        HeadsUpEntry bEntry = getHeadsUpEntry(b.getKey());
+        if (aEntry == null || bEntry == null) {
+            return Boolean.compare(aEntry == null, bEntry == null);
+        }
+        return aEntry.compareTo(bEntry);
+    }
+
+    /**
+     * Set an entry to be expanded and therefore stick in the heads up area if it's pinned
+     * until it's collapsed again.
+     */
+    public void setExpanded(@NonNull NotificationEntry entry, boolean expanded) {
+        HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
+        if (headsUpEntry != null && entry.isRowPinned()) {
+            headsUpEntry.setExpanded(expanded);
+        }
+    }
+
+    /**
+     * Notes that the user took an action on an entry that might indirectly cause the system or the
+     * app to remove the notification.
+     *
+     * @param entry the entry that might be indirectly removed by the user's action
+     *
+     * @see HeadsUpCoordinator#mActionPressListener
+     * @see #canRemoveImmediately(String)
+     */
+    public void setUserActionMayIndirectlyRemove(@NonNull NotificationEntry entry) {
+        HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
+        if (headsUpEntry != null) {
+            headsUpEntry.mUserActionMayIndirectlyRemove = true;
+        }
+    }
+
+    /**
+     * Whether or not the entry can be removed currently.  If it hasn't been on screen long enough
+     * it should not be removed unless forced
+     * @param key the key to check if removable
+     * @return true if the entry can be removed
+     */
+    @Override
+    public boolean canRemoveImmediately(@NonNull String key) {
+        if (mSwipedOutKeys.contains(key)) {
+            // We always instantly dismiss views being manually swiped out.
+            mSwipedOutKeys.remove(key);
+            return true;
+        }
+
+        HeadsUpEntry headsUpEntry = getHeadsUpEntryPhone(key);
+        HeadsUpEntry topEntry = getTopHeadsUpEntryPhone();
+
+        if (headsUpEntry == null || headsUpEntry != topEntry) {
+            return true;
+        }
+
+        if (headsUpEntry.mUserActionMayIndirectlyRemove) {
+            return true;
+        }
+        return headsUpEntry.wasShownLongEnough()
+                || (headsUpEntry.mEntry != null && headsUpEntry.mEntry.isRowDismissed());
+    }
+
+    /**
+     * @param key
+     * @return true if the entry is (pinned and expanded) or (has an active remote input)
+     */
+    @Override
+    public boolean isSticky(String key) {
+        HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+        if (headsUpEntry != null) {
+            return headsUpEntry.isSticky();
+        }
+        return false;
+    }
+
+    @NonNull
+    protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) {
+        if (NotificationThrottleHun.isEnabled()) {
+            return new HeadsUpEntry(entry);
+        } else {
+            HeadsUpEntry headsUpEntry = mEntryPool.acquire();
+            headsUpEntry.setEntry(entry);
+            return headsUpEntry;
+        }
+    }
+
+    /**
+     * Determines if the notification is for a critical call that must display on top of an active
+     * input notification.
+     * The call isOngoing check is for a special case of incoming calls (see b/164291424).
+     */
+    private static boolean isCriticalCallNotif(NotificationEntry entry) {
+        Notification n = entry.getSbn().getNotification();
+        boolean isIncomingCall = n.isStyle(Notification.CallStyle.class) && n.extras.getInt(
+                Notification.EXTRA_CALL_TYPE) == Notification.CallStyle.CALL_TYPE_INCOMING;
+        return isIncomingCall || (entry.getSbn().isOngoing()
+                && Notification.CATEGORY_CALL.equals(n.category));
+    }
+
+    @VisibleForTesting
+    public final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
+        if (NotificationThrottleHun.isEnabled()) {
+            mAvalancheController.setEnableAtRuntime(true);
+            if (mEntriesToRemoveWhenReorderingAllowed.isEmpty()) {
+                return;
+            }
+        }
+        mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
+        for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
+            if (entry != null && isHeadsUpEntry(entry.getKey())) {
+                // Maybe the heads-up was removed already
+                removeEntry(entry.getKey(), REASON_REORDER_ALLOWED);
+            }
+        }
+        mEntriesToRemoveWhenReorderingAllowed.clear();
+        mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
+    };
+
+    private final OnReorderingBannedListener mOnReorderingBannedListener = () -> {
+        if (mAvalancheController != null) {
+            // In open shade the first HUN is pinned, and visual stability logic prevents us from
+            // unpinning this first HUN as long as the shade remains open. AvalancheController only
+            // shows the next HUN when the currently showing HUN is unpinned, so we must disable
+            // throttling here so that the incoming HUN stream is not forever paused. This is reset
+            // when reorder becomes allowed.
+            mAvalancheController.setEnableAtRuntime(false);
+
+            // Note that we cannot do the above when
+            // 1) The remove runnable runs because its delay means it may not run before shade close
+            // 2) Reordering is allowed again (when shade closes) because the HUN appear animation
+            // will have started by then
+        }
+    };
+
+    private final StatusBarStateController.StateListener
+            mStatusBarStateListener = new StatusBarStateController.StateListener() {
+        @Override
+        public void onStateChanged(int newState) {
+            boolean wasKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
+            boolean isKeyguard = newState == StatusBarState.KEYGUARD;
+            mStatusBarState = newState;
+
+            if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
+                ArrayList<String> keysToRemove = new ArrayList<>();
+                for (HeadsUpEntry entry : getHeadsUpEntryList()) {
+                    if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) {
+                        keysToRemove.add(entry.mEntry.getKey());
+                    }
+                }
+                for (String key : keysToRemove) {
+                    removeEntry(key, "mStatusBarStateListener");
+                }
+            }
+        }
+
+        @Override
+        public void onDozingChanged(boolean isDozing) {
+            if (!isDozing) {
+                // Let's make sure all huns we got while dozing time out within the normal timeout
+                // duration. Otherwise they could get stuck for a very long time
+                for (HeadsUpEntry entry : getHeadsUpEntryList()) {
+                    entry.updateEntry(true /* updatePostTime */, "onDozingChanged(false)");
+                }
+            }
+        }
+    };
+
+    /**
+     * This represents a notification and how long it is in a heads up mode. It also manages its
+     * lifecycle automatically when created. This class is public because it is exposed by methods
+     * of AvalancheController that take it as param.
+     */
+    public class HeadsUpEntry implements Comparable<HeadsUpEntry>, HeadsUpRowRepository {
+        public boolean mRemoteInputActivatedAtLeastOnce;
+        public boolean mRemoteInputActive;
+        public boolean mUserActionMayIndirectlyRemove;
+
+        protected boolean mExpanded;
+        protected boolean mWasUnpinned;
+
+        @Nullable public NotificationEntry mEntry;
+        public long mPostTime;
+        public long mEarliestRemovalTime;
+
+        @Nullable protected Runnable mRemoveRunnable;
+
+        @Nullable private Runnable mCancelRemoveRunnable;
+
+        private boolean mGutsShownPinned;
+        private final MutableStateFlow<PinnedStatus> mPinnedStatus =
+                StateFlowKt.MutableStateFlow(PinnedStatus.NotPinned);
+
+        /**
+         * If the time this entry has been on was extended
+         */
+        private boolean extended;
+
+        public HeadsUpEntry() {
+            NotificationThrottleHun.assertInLegacyMode();
+        }
+
+        public HeadsUpEntry(NotificationEntry entry) {
+            // Attach NotificationEntry for AvalancheController to log key and
+            // record mPostTime for AvalancheController sorting
+            setEntry(entry, createRemoveRunnable(entry));
+        }
+
+        @Override
+        @NonNull
+        public String getKey() {
+            return requireEntry().getKey();
+        }
+
+        @Override
+        @NonNull
+        public Object getElementKey() {
+            return requireEntry().getRow();
+        }
+
+        private NotificationEntry requireEntry() {
+            /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode();
+            return Objects.requireNonNull(mEntry);
+        }
+
+        @Override
+        @NonNull
+        public StateFlow<PinnedStatus> getPinnedStatus() {
+            return mPinnedStatus;
+        }
+
+        /** Attach a NotificationEntry. */
+        public void setEntry(@NonNull final NotificationEntry entry) {
+            NotificationThrottleHun.assertInLegacyMode();
+            setEntry(entry, createRemoveRunnable(entry));
+        }
+
+        protected void setEntry(
+                @NonNull final NotificationEntry entry,
+                @Nullable Runnable removeRunnable) {
+            mEntry = entry;
+            mRemoveRunnable = removeRunnable;
+
+            mPostTime = calculatePostTime();
+            updateEntry(true /* updatePostTime */, "setEntry");
+
+            if (NotificationThrottleHun.isEnabled()) {
+                mEntriesToRemoveWhenReorderingAllowed.add(entry);
+                if (!mVisualStabilityProvider.isReorderingAllowed()) {
+                    entry.setSeenInShade(true);
+                }
+            }
+        }
+
+        protected boolean isRowPinned() {
+            return mEntry != null && mEntry.isRowPinned();
+        }
+
+        protected void setRowPinnedStatus(PinnedStatus pinnedStatus) {
+            if (mEntry != null) mEntry.setRowPinnedStatus(pinnedStatus);
+            mPinnedStatus.setValue(pinnedStatus);
+        }
+
+        /**
+         * An interface that returns the amount of time left this HUN should show.
+         */
+        interface FinishTimeUpdater {
+            long updateAndGetTimeRemaining();
+        }
+
+        /**
+         * Updates an entry's removal time.
+         * @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();
+                if (updateEarliestRemovalTime) {
+                    mEarliestRemovalTime = now + mMinimumDisplayTime;
+                }
+
+                if (updatePostTime) {
+                    mPostTime = Math.max(mPostTime, now);
+                }
+            };
+            mAvalancheController.update(this, runnable, "updateEntry (updatePostTime)");
+
+            if (isSticky()) {
+                cancelAutoRemovalCallbacks("updateEntry (sticky)");
+                return;
+            }
+
+            FinishTimeUpdater finishTimeCalculator = () -> {
+                final long finishTime = calculateFinishTime();
+                final long now = mSystemClock.elapsedRealtime();
+                final long timeLeft = NotificationThrottleHun.isEnabled()
+                        ? Math.max(finishTime, mEarliestRemovalTime) - now
+                        : Math.max(finishTime - now, mMinimumDisplayTime);
+                return timeLeft;
+            };
+            scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
+
+            // Notify the manager, that the posted time has changed.
+            onEntryUpdated(this);
+
+            if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
+                mEntriesToRemoveAfterExpand.remove(mEntry);
+            }
+            if (!NotificationThrottleHun.isEnabled()) {
+                if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
+                    mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
+                }
+            }
+        }
+
+        private void extendPulse() {
+            if (!extended) {
+                extended = true;
+                updateEntry(false, "extendPulse()");
+            }
+        }
+
+        /**
+         * Whether or not the notification is "sticky" i.e. should stay on screen regardless
+         * of the timer (forever) and should be removed externally.
+         * @return true if the notification is sticky
+         */
+        public boolean isSticky() {
+            if (mGutsShownPinned) return true;
+
+            if (mEntry == null) return false;
+
+            if (ExpandHeadsUpOnInlineReply.isEnabled()) {
+                // we don't consider pinned and expanded huns as sticky after the remote input
+                // has been activated for them
+                if (!mRemoteInputActive && mRemoteInputActivatedAtLeastOnce) {
+                    return false;
+                }
+            }
+
+            return (mEntry.isRowPinned() && mExpanded)
+                    || mRemoteInputActive
+                    || hasFullScreenIntent(mEntry);
+        }
+
+        public boolean isStickyForSomeTime() {
+            if (mEntry == null) return false;
+
+            return mEntry.isStickyAndNotDemoted();
+        }
+
+        /**
+         * Whether the notification has been on screen long enough and can be removed.
+         * @return true if the notification has been on screen long enough
+         */
+        public boolean wasShownLongEnough() {
+            return mEarliestRemovalTime < mSystemClock.elapsedRealtime();
+        }
+
+        public int compareNonTimeFields(HeadsUpEntry headsUpEntry) {
+            if (mEntry == null && headsUpEntry.mEntry == null) {
+                return 0;
+            } else if (headsUpEntry.mEntry == null) {
+                return -1;
+            } else if (mEntry == null) {
+                return 1;
+            }
+
+            boolean selfFullscreen = hasFullScreenIntent(mEntry);
+            boolean otherFullscreen = hasFullScreenIntent(headsUpEntry.mEntry);
+            if (selfFullscreen && !otherFullscreen) {
+                return -1;
+            } else if (!selfFullscreen && otherFullscreen) {
+                return 1;
+            }
+
+            boolean selfCall = isCriticalCallNotif(mEntry);
+            boolean otherCall = isCriticalCallNotif(headsUpEntry.mEntry);
+
+            if (selfCall && !otherCall) {
+                return -1;
+            } else if (!selfCall && otherCall) {
+                return 1;
+            }
+
+            if (mRemoteInputActive && !headsUpEntry.mRemoteInputActive) {
+                return -1;
+            } else if (!mRemoteInputActive && headsUpEntry.mRemoteInputActive) {
+                return 1;
+            }
+            return 0;
+        }
+
+        public int compareTo(@NonNull HeadsUpEntry headsUpEntry) {
+            if (mEntry == null && headsUpEntry.mEntry == null) {
+                return 0;
+            } else if (headsUpEntry.mEntry == null) {
+                return -1;
+            } else if (mEntry == null) {
+                return 1;
+            }
+            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;
+            }
+            if (mPostTime > headsUpEntry.mPostTime) {
+                return -1;
+            } else if (mPostTime == headsUpEntry.mPostTime) {
+                return mEntry.getKey().compareTo(headsUpEntry.mEntry.getKey());
+            } else {
+                return 1;
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            if (mEntry == null) return super.hashCode();
+            int result = mEntry.getKey().hashCode();
+            result = 31 * result;
+            return result;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) return true;
+            if (o == null || !(o instanceof HeadsUpEntry)) return false;
+            HeadsUpEntry otherHeadsUpEntry = (HeadsUpEntry) o;
+            if (mEntry != null && otherHeadsUpEntry.mEntry != null) {
+                return mEntry.getKey().equals(otherHeadsUpEntry.mEntry.getKey());
+            }
+            return false;
+        }
+
+        public void setExpanded(boolean expanded) {
+            if (this.mExpanded == expanded) {
+                return;
+            }
+
+            this.mExpanded = expanded;
+            if (expanded) {
+                cancelAutoRemovalCallbacks("setExpanded(true)");
+            } else {
+                updateEntry(false /* updatePostTime */, "setExpanded(false)");
+            }
+        }
+
+        public void setGutsShownPinned(boolean gutsShownPinned) {
+            if (mGutsShownPinned == gutsShownPinned) {
+                return;
+            }
+
+            mGutsShownPinned = gutsShownPinned;
+            if (gutsShownPinned) {
+                cancelAutoRemovalCallbacks("setGutsShownPinned(true)");
+            } else {
+                updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)");
+            }
+        }
+
+        public void reset() {
+            NotificationThrottleHun.assertInLegacyMode();
+            cancelAutoRemovalCallbacks("reset()");
+            mEntry = null;
+            mRemoveRunnable = null;
+            mExpanded = false;
+            mRemoteInputActive = false;
+            mGutsShownPinned = false;
+            extended = false;
+        }
+
+        /**
+         * Clear any pending removal runnables.
+         */
+        public void cancelAutoRemovalCallbacks(@Nullable String reason) {
+            Runnable runnable = () -> {
+                final boolean removed = cancelAutoRemovalCallbackInternal();
+
+                if (removed) {
+                    mLogger.logAutoRemoveCanceled(mEntry, reason);
+                }
+            };
+            if (mEntry != null && isHeadsUpEntry(mEntry.getKey())) {
+                mLogger.logAutoRemoveCancelRequest(this.mEntry, reason);
+                mAvalancheController.update(this, runnable, reason + " cancelAutoRemovalCallbacks");
+            } else {
+                // Just removed
+                runnable.run();
+            }
+        }
+
+        public void scheduleAutoRemovalCallback(FinishTimeUpdater finishTimeCalculator,
+                @NonNull String reason) {
+
+            mLogger.logAutoRemoveRequest(this.mEntry, reason);
+            Runnable runnable = () -> {
+                long delayMs = finishTimeCalculator.updateAndGetTimeRemaining();
+
+                if (mRemoveRunnable == null) {
+                    Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
+                    return;
+                }
+
+                final boolean deletedExistingRemovalRunnable = cancelAutoRemovalCallbackInternal();
+                mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
+                        delayMs);
+
+                if (deletedExistingRemovalRunnable) {
+                    mLogger.logAutoRemoveRescheduled(mEntry, delayMs, reason);
+                } else {
+                    mLogger.logAutoRemoveScheduled(mEntry, delayMs, reason);
+                }
+            };
+            mAvalancheController.update(this, runnable,
+                    reason + " scheduleAutoRemovalCallback");
+        }
+
+        public boolean cancelAutoRemovalCallbackInternal() {
+            final boolean scheduled = (mCancelRemoveRunnable != null);
+
+            if (scheduled) {
+                mCancelRemoveRunnable.run();  // Delete removal runnable from Executor queue
+                mCancelRemoveRunnable = null;
+            }
+
+            return scheduled;
+        }
+
+        /**
+         * Remove the entry at the earliest allowed removal time.
+         */
+        public void removeAsSoonAsPossible() {
+            if (mRemoveRunnable != null) {
+
+                FinishTimeUpdater finishTimeCalculator = () -> {
+                    final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
+                    return timeLeft;
+                };
+                scheduleAutoRemovalCallback(finishTimeCalculator, "removeAsSoonAsPossible");
+            }
+        }
+
+        /** Creates a runnable to remove this notification from the alerting entries. */
+        protected Runnable createRemoveRunnable(NotificationEntry entry) {
+            return () -> {
+                if (!NotificationThrottleHun.isEnabled()
+                        && !mVisualStabilityProvider.isReorderingAllowed()
+                        // We don't want to allow reordering while pulsing, but headsup need to
+                        // time out anyway
+                        && !entry.showingPulsing()) {
+                    mEntriesToRemoveWhenReorderingAllowed.add(entry);
+                    mVisualStabilityProvider.addTemporaryReorderingAllowedListener(
+                            mOnReorderingAllowedListener);
+                } else if (mTrackingHeadsUp) {
+                    mEntriesToRemoveAfterExpand.add(entry);
+                    mLogger.logRemoveEntryAfterExpand(entry);
+                } else if (mVisualStabilityProvider.isReorderingAllowed()
+                        || entry.showingPulsing()) {
+                    removeEntry(entry.getKey(), "createRemoveRunnable");
+                }
+            };
+        }
+
+        /**
+         * Calculate what the post time of a notification is at some current time.
+         * @return the post time
+         */
+        protected long calculatePostTime() {
+            // The actual post time will be just after the heads-up really slided in
+            return mSystemClock.elapsedRealtime() + mTouchAcceptanceDelay;
+        }
+
+        /**
+         * @return When the notification should auto-dismiss itself, based on
+         * {@link SystemClock#elapsedRealtime()}
+         */
+        protected long calculateFinishTime() {
+            int requestedTimeOutMs;
+            if (isStickyForSomeTime()) {
+                requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime;
+            } else {
+                requestedTimeOutMs = mAvalancheController.getDurationMs(this, mAutoDismissTime);
+            }
+            final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs);
+            return mPostTime + duration + (extended ? mExtensionTime : 0);
+        }
+
+        /**
+         * Get user-preferred or default timeout duration. The larger one will be returned.
+         * @return milliseconds before auto-dismiss
+         * @param requestedTimeout
+         */
+        protected int getRecommendedHeadsUpTimeoutMs(int requestedTimeout) {
+            return mAccessibilityMgr.getRecommendedTimeoutMillis(
+                    requestedTimeout,
+                    AccessibilityManager.FLAG_CONTENT_CONTROLS
+                            | AccessibilityManager.FLAG_CONTENT_ICONS
+                            | AccessibilityManager.FLAG_CONTENT_TEXT);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
new file mode 100644
index 0000000..80225c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.headsup
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.VERBOSE
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+/** Logger for [HeadsUpManager]. */
+class HeadsUpManagerLogger
+@Inject
+constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
+    fun logPackageSnoozed(snoozeKey: String) {
+        buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package snoozed $str1" })
+    }
+
+    fun logPackageUnsnoozed(snoozeKey: String) {
+        buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package unsnoozed $str1" })
+    }
+
+    fun logIsSnoozedReturned(snoozeKey: String) {
+        buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package snoozed when queried $str1" })
+    }
+
+    fun logReleaseAllImmediately() {
+        buffer.log(TAG, INFO, {}, { "release all immediately" })
+    }
+
+    fun logShowNotificationRequest(entry: NotificationEntry) {
+        buffer.log(TAG, INFO, { str1 = entry.logKey }, { "request: show notification $str1" })
+    }
+
+    fun logAvalancheUpdate(
+        caller: String,
+        isEnabled: Boolean,
+        notifEntryKey: String,
+        outcome: String,
+    ) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = caller
+                str2 = notifEntryKey
+                str3 = outcome
+                bool1 = isEnabled
+            },
+            { "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3" },
+        )
+    }
+
+    fun logAvalancheDelete(
+        caller: String,
+        isEnabled: Boolean,
+        notifEntryKey: String,
+        outcome: String,
+    ) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = caller
+                str2 = notifEntryKey
+                str3 = outcome
+                bool1 = isEnabled
+            },
+            { "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3" },
+        )
+    }
+
+    fun logShowNotification(entry: NotificationEntry) {
+        buffer.log(TAG, INFO, { str1 = entry.logKey }, { "show notification $str1" })
+    }
+
+    fun logAutoRemoveScheduled(entry: NotificationEntry, delayMillis: Long, reason: String) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = entry.logKey
+                long1 = delayMillis
+                str2 = reason
+            },
+            { "schedule auto remove of $str1 in $long1 ms reason: $str2" },
+        )
+    }
+
+    fun logAutoRemoveRequest(entry: NotificationEntry, reason: String) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = entry.logKey
+                str2 = reason
+            },
+            { "request: reschedule auto remove of $str1 reason: $str2" },
+        )
+    }
+
+    fun logAutoRemoveRescheduled(entry: NotificationEntry, delayMillis: Long, reason: String) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = entry.logKey
+                long1 = delayMillis
+                str2 = reason
+            },
+            { "reschedule auto remove of $str1 in $long1 ms reason: $str2" },
+        )
+    }
+
+    fun logAutoRemoveCancelRequest(entry: NotificationEntry, reason: String?) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = entry.logKey
+                str2 = reason ?: "unknown"
+            },
+            { "request: cancel auto remove of $str1 reason: $str2" },
+        )
+    }
+
+    fun logAutoRemoveCanceled(entry: NotificationEntry, reason: String?) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = entry.logKey
+                str2 = reason ?: "unknown"
+            },
+            { "cancel auto remove of $str1 reason: $str2" },
+        )
+    }
+
+    fun logRemoveEntryRequest(key: String, reason: String, isWaiting: Boolean) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = logKey(key)
+                str2 = reason
+                bool1 = isWaiting
+            },
+            { "request: $str2 => remove entry $str1 isWaiting: $isWaiting" },
+        )
+    }
+
+    fun logRemoveEntry(key: String, reason: String, isWaiting: Boolean) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = logKey(key)
+                str2 = reason
+                bool1 = isWaiting
+            },
+            { "$str2 => remove entry $str1 isWaiting: $isWaiting" },
+        )
+    }
+
+    fun logUnpinEntryRequest(key: String) {
+        buffer.log(TAG, INFO, { str1 = logKey(key) }, { "request: unpin entry $str1" })
+    }
+
+    fun logUnpinEntry(key: String) {
+        buffer.log(TAG, INFO, { str1 = logKey(key) }, { "unpin entry $str1" })
+    }
+
+    fun logRemoveNotification(
+        key: String,
+        releaseImmediately: Boolean,
+        isWaiting: Boolean,
+        reason: String,
+    ) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = logKey(key)
+                bool1 = releaseImmediately
+                bool2 = isWaiting
+                str2 = reason
+            },
+            {
+                "remove notification $str1 releaseImmediately: $bool1 isWaiting: $bool2 " +
+                    "reason: $str2"
+            },
+        )
+    }
+
+    fun logNullEntry(key: String, reason: String) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = logKey(key)
+                str2 = reason
+            },
+            { "remove notification $str1 when headsUpEntry is null, reason: $str2" },
+        )
+    }
+
+    fun logNotificationActuallyRemoved(entry: NotificationEntry) {
+        buffer.log(TAG, INFO, { str1 = entry.logKey }, { "notification removed $str1 " })
+    }
+
+    fun logUpdateNotificationRequest(key: String, alert: Boolean, hasEntry: Boolean) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = logKey(key)
+                bool1 = alert
+                bool2 = hasEntry
+            },
+            { "request: update notification $str1 alert: $bool1 hasEntry: $bool2" },
+        )
+    }
+
+    fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = logKey(key)
+                bool1 = alert
+                bool2 = hasEntry
+            },
+            { "update notification $str1 alert: $bool1 hasEntry: $bool2" },
+        )
+    }
+
+    fun logUpdateEntry(entry: NotificationEntry, updatePostTime: Boolean, reason: String?) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = entry.logKey
+                bool1 = updatePostTime
+                str2 = reason ?: "unknown"
+            },
+            { "update entry $str1 updatePostTime: $bool1 reason: $str2" },
+        )
+    }
+
+    fun logSnoozeLengthChange(packageSnoozeLengthMs: Int) {
+        buffer.log(
+            TAG,
+            INFO,
+            { int1 = packageSnoozeLengthMs },
+            { "snooze length changed: ${int1}ms" },
+        )
+    }
+
+    fun logSetEntryPinned(entry: NotificationEntry, pinnedStatus: PinnedStatus, reason: String) {
+        buffer.log(
+            TAG,
+            VERBOSE,
+            {
+                str1 = entry.logKey
+                str2 = reason
+                str3 = pinnedStatus.name
+            },
+            { "$str2 => set entry pinned $str1 pinned: $str3" },
+        )
+    }
+
+    fun logUpdatePinnedMode(hasPinnedNotification: Boolean) {
+        buffer.log(
+            TAG,
+            INFO,
+            { bool1 = hasPinnedNotification },
+            { "has pinned notification changed to $bool1" },
+        )
+    }
+
+    fun logRemoveEntryAfterExpand(entry: NotificationEntry) {
+        buffer.log(TAG, VERBOSE, { str1 = entry.logKey }, { "remove entry after expand: $str1" })
+    }
+}
+
+private const val TAG = "HeadsUpManager"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpModule.kt
new file mode 100644
index 0000000..f9502ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpModule.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.statusbar.notification.headsup
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface HeadsUpModule {
+    @Binds @SysUISingleton fun bindsHeadsUpManager(hum: HeadsUpManagerImpl): HeadsUpManager
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpNotificationViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpNotificationViewControllerEmptyImpl.kt
new file mode 100644
index 0000000..84754e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpNotificationViewControllerEmptyImpl.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.notification.headsup
+
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper.HeadsUpNotificationViewController
+
+/** Empty impl of [HeadsUpNotificationViewController] for use with Scene Container */
+class HeadsUpNotificationViewControllerEmptyImpl : HeadsUpNotificationViewController {
+    override fun setHeadsUpDraggingStartingHeight(startHeight: Int) {}
+
+    override fun setTrackedHeadsUp(expandableNotificationRow: ExpandableNotificationRow?) {}
+
+    override fun startExpand(
+        newX: Float,
+        newY: Float,
+        startTracking: Boolean,
+        expandedHeight: Float
+    ) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpTouchHelper.java
new file mode 100644
index 0000000..3b6b9ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpTouchHelper.java
@@ -0,0 +1,266 @@
+/*
+ * 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.headsup;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.Gefingerpoken;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableView;
+
+/**
+ * A helper class to handle touches on the heads-up views.
+ */
+public class HeadsUpTouchHelper implements Gefingerpoken {
+
+    private final HeadsUpManager mHeadsUpManager;
+    private final IStatusBarService mStatusBarService;
+    private final Callback mCallback;
+    private int mTrackingPointer;
+    private final float mTouchSlop;
+    private float mInitialTouchX;
+    private float mInitialTouchY;
+    private boolean mTouchingHeadsUpView;
+    private boolean mTrackingHeadsUp;
+    private boolean mCollapseSnoozes;
+    private final HeadsUpNotificationViewController mPanel;
+    private ExpandableNotificationRow mPickedChild;
+
+    public HeadsUpTouchHelper(HeadsUpManager headsUpManager,
+            IStatusBarService statusBarService,
+            Callback callback,
+            HeadsUpNotificationViewController notificationPanelView) {
+        mHeadsUpManager = headsUpManager;
+        mStatusBarService = statusBarService;
+        mCallback = callback;
+        mPanel = notificationPanelView;
+        Context context = mCallback.getContext();
+        final ViewConfiguration configuration = ViewConfiguration.get(context);
+        mTouchSlop = configuration.getScaledTouchSlop();
+    }
+
+    public boolean isTrackingHeadsUp() {
+        return mTrackingHeadsUp;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        if (!mTouchingHeadsUpView && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+            return false;
+        }
+        int pointerIndex = event.findPointerIndex(mTrackingPointer);
+        if (pointerIndex < 0) {
+            pointerIndex = 0;
+            mTrackingPointer = event.getPointerId(pointerIndex);
+        }
+        final float x = event.getX(pointerIndex);
+        final float y = event.getY(pointerIndex);
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                mInitialTouchY = y;
+                mInitialTouchX = x;
+                setTrackingHeadsUp(false);
+                ExpandableView child = mCallback.getChildAtRawPosition(x, y);
+                mTouchingHeadsUpView = false;
+                if (child instanceof ExpandableNotificationRow) {
+                    ExpandableNotificationRow pickedChild = (ExpandableNotificationRow) child;
+                    mTouchingHeadsUpView = !mCallback.isExpanded()
+                            && pickedChild.isHeadsUp() && pickedChild.isPinned();
+                    if (mTouchingHeadsUpView) {
+                        mPickedChild = pickedChild;
+                    }
+                } else if (child == null && !mCallback.isExpanded()) {
+                    // We might touch above the visible heads up child, but then we still would
+                    // like to capture it.
+                    NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
+                    if (topEntry != null && topEntry.isRowPinned()) {
+                        mPickedChild = topEntry.getRow();
+                        mTouchingHeadsUpView = true;
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                final int upPointer = event.getPointerId(event.getActionIndex());
+                if (mTrackingPointer == upPointer) {
+                    // gesture is ongoing, find a new pointer to track
+                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+                    mTrackingPointer = event.getPointerId(newIndex);
+                    mInitialTouchX = event.getX(newIndex);
+                    mInitialTouchY = event.getY(newIndex);
+                }
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                final float h = y - mInitialTouchY;
+                if (mTouchingHeadsUpView && Math.abs(h) > mTouchSlop
+                        && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
+                    if (!SceneContainerFlag.isEnabled()) {
+                        setTrackingHeadsUp(true);
+                        mCollapseSnoozes = h < 0;
+                        mInitialTouchX = x;
+                        mInitialTouchY = y;
+                        int startHeight = (int) (mPickedChild.getActualHeight()
+                                + mPickedChild.getTranslationY());
+                        mPanel.setHeadsUpDraggingStartingHeight(startHeight);
+                        mPanel.startExpand(x, y, true /* startTracking */, startHeight);
+
+                        // This call needs to be after the expansion start otherwise we will get a
+                        // flicker of one frame as it's not expanded yet.
+                        mHeadsUpManager.unpinAll(true);
+
+                        clearNotificationEffects();
+                        endMotion();
+                    }
+                    return true;
+                }
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                if (mPickedChild != null && mTouchingHeadsUpView) {
+                    // We may swallow this click if the heads up just came in.
+                    if (mHeadsUpManager.shouldSwallowClick(
+                            mPickedChild.getEntry().getSbn().getKey())) {
+                        endMotion();
+                        return true;
+                    }
+                }
+                endMotion();
+                break;
+        }
+        return false;
+    }
+
+    private void setTrackingHeadsUp(boolean tracking) {
+        mTrackingHeadsUp = tracking;
+        mHeadsUpManager.setTrackingHeadsUp(tracking);
+        mPanel.setTrackedHeadsUp(tracking ? mPickedChild : null);
+    }
+
+    public void notifyFling(boolean collapse) {
+        if (collapse && mCollapseSnoozes) {
+            mHeadsUpManager.snooze();
+        }
+        mCollapseSnoozes = false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (SceneContainerFlag.isEnabled()) {
+            int pointerIndex = event.findPointerIndex(mTrackingPointer);
+            if (pointerIndex < 0) {
+                pointerIndex = 0;
+                mTrackingPointer = event.getPointerId(pointerIndex);
+            }
+            final float x = event.getX(pointerIndex);
+            final float y = event.getY(pointerIndex);
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_POINTER_UP:
+                    final int upPointer = event.getPointerId(event.getActionIndex());
+                    if (mTrackingPointer == upPointer) {
+                        // gesture is ongoing, find a new pointer to track
+                        final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+                        mTrackingPointer = event.getPointerId(newIndex);
+                        mInitialTouchX = event.getX(newIndex);
+                        mInitialTouchY = event.getY(newIndex);
+                    }
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    final float h = y - mInitialTouchY;
+                    if (mTouchingHeadsUpView && Math.abs(h) > mTouchSlop
+                            && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
+                        setTrackingHeadsUp(true);
+                        mCollapseSnoozes = h < 0;
+                        mInitialTouchX = x;
+                        mInitialTouchY = y;
+                        int startHeight = (int) (mPickedChild.getActualHeight()
+                                + mPickedChild.getTranslationY());
+                        mPanel.setHeadsUpDraggingStartingHeight(startHeight);
+                        mPanel.startExpand(x, y, true /* startTracking */, startHeight);
+
+                        clearNotificationEffects();
+                        endMotion();
+                        return true;
+                    }
+                    break;
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    if (mPickedChild != null && mTouchingHeadsUpView) {
+                        // We may swallow this click if the heads up just came in.
+                        if (mHeadsUpManager.shouldSwallowClick(
+                                mPickedChild.getEntry().getSbn().getKey())) {
+                            endMotion();
+                            return true;
+                        }
+                    }
+                    endMotion();
+                    return false;
+            }
+            return false;
+        } else {
+            if (!mTrackingHeadsUp) {
+                return false;
+            }
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    endMotion();
+                    setTrackingHeadsUp(false);
+                    break;
+            }
+            return true;
+        }
+    }
+
+    private void endMotion() {
+        mTrackingPointer = -1;
+        mPickedChild = null;
+        mTouchingHeadsUpView = false;
+    }
+
+    private void clearNotificationEffects() {
+        try {
+            mStatusBarService.clearNotificationEffects();
+        } catch (RemoteException e) {
+            // Won't fail unless the world has ended.
+        }
+    }
+
+    public interface Callback {
+        ExpandableView getChildAtRawPosition(float touchX, float touchY);
+        boolean isExpanded();
+        Context getContext();
+    }
+
+    /** The controller for a view that houses heads up notifications. */
+    public interface HeadsUpNotificationViewController {
+        /** Called when a HUN is dragged to indicate the starting height for shade motion. */
+        void setHeadsUpDraggingStartingHeight(int startHeight);
+
+        /** Sets notification that is being expanded. */
+        void setTrackedHeadsUp(ExpandableNotificationRow expandableNotificationRow);
+
+        /** Called when a MotionEvent is about to trigger expansion. */
+        void startExpand(float newX, float newY, boolean startTracking, float expandedHeight);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpUtil.java
new file mode 100644
index 0000000..40da232
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpUtil.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.headsup;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.util.Compile;
+
+/**
+ * A class of utility static methods for heads up notifications.
+ */
+public final class HeadsUpUtil {
+    private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
+
+    private static final String LOG_TAG = "HeadsUpUtil";
+    private static final boolean LOG_DEBUG = Compile.IS_DEBUG && Log.isLoggable(LOG_TAG, Log.DEBUG);
+
+    /**
+     * Set the given view as clicked or not-clicked.
+     * @param view The view to be set the flag to.
+     * @param clicked True to set as clicked. False to not-clicked.
+     */
+    public static void setNeedsHeadsUpDisappearAnimationAfterClick(View view, boolean clicked) {
+        if (LOG_DEBUG) {
+            logTagClickedNotificationChanged(view, clicked);
+        }
+        view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
+    }
+
+    /**
+     * Check if the given view has the flag of "clicked notification"
+     * @param view The view to be checked.
+     * @return True if the view has clicked. False othrewise.
+     */
+    public static boolean isClickedHeadsUpNotification(View view) {
+        Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION);
+        return clicked != null && clicked;
+    }
+
+    private static void logTagClickedNotificationChanged(@Nullable View view, boolean isClicked) {
+        if (view == null) {
+            return;
+        }
+
+        final boolean wasClicked = isClickedHeadsUpNotification(view);
+        if (isClicked == wasClicked) {
+            return;
+        }
+
+        Log.d(LOG_TAG, getViewKey(view) + ": TAG_CLICKED_NOTIFICATION set to " + isClicked);
+    }
+
+    private static @NonNull String getViewKey(@NonNull View view) {
+        if (!(view instanceof ExpandableNotificationRow)) {
+            return "(not a row)";
+        }
+
+        final ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+        final NotificationEntry entry = row.getEntry();
+        if (entry == null) {
+            return "(null entry)";
+        }
+
+        final String key = entry.getKey();
+        if (key == null) {
+            return "(null key)";
+        }
+
+        return key;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/OnHeadsUpChangedListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/OnHeadsUpChangedListener.java
new file mode 100644
index 0000000..b1fd784
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/OnHeadsUpChangedListener.java
@@ -0,0 +1,56 @@
+/*
+ * 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.statusbar.notification.headsup;
+
+import android.annotation.NonNull;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * A listener to heads up changes
+ */
+public interface OnHeadsUpChangedListener {
+    /**
+     * The state whether there exist pinned heads-ups or not changed.
+     *
+     * @param inPinnedMode whether there are any pinned heads-ups
+     */
+    default void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {}
+
+    /**
+     * A notification was just pinned to the top.
+     */
+    default void onHeadsUpPinned(NotificationEntry entry) {}
+
+    /**
+     * A notification was just unpinned from the top.
+     */
+    default void onHeadsUpUnPinned(NotificationEntry entry) {}
+
+    /**
+     * A notification just became a heads up or turned back to its normal state.
+     *
+     * @param entry     the entry of the changed notification
+     * @param isHeadsUp whether the notification is now a headsUp notification
+     */
+    default void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {}
+
+    /**
+     * Called on HUN disappearing animation ends
+     */
+    default void onHeadsUpAnimatingAwayEnded(@NonNull NotificationEntry entry) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/PinnedStatus.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/PinnedStatus.kt
new file mode 100644
index 0000000..af18054
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/PinnedStatus.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.statusbar.notification.headsup
+
+/**
+ * A status representing whether and how a notification is pinned.
+ *
+ * @property isPinned true if a notification should be "pinned", meaning that a notification should
+ *   stay on top of the screen.
+ */
+enum class PinnedStatus(val isPinned: Boolean) {
+    /** This notification is not pinned. */
+    NotPinned(isPinned = false),
+    /**
+     * This notification is pinned by the system - likely because when the notification was added or
+     * updated, it required pinning.
+     */
+    PinnedBySystem(isPinned = true),
+    /**
+     * This notification is pinned because the user did an explicit action to pin it (like tapping
+     * the notification chip in the status bar).
+     */
+    // TODO(b/364653005): Use this status when a user taps the notification chip.
+    PinnedByUser(isPinned = true),
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/StatusBarHeadsUpChangeListener.java
new file mode 100644
index 0000000..23eb507
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/StatusBarHeadsUpChangeListener.java
@@ -0,0 +1,120 @@
+/*
+ * 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.headsup;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
+
+import javax.inject.Inject;
+
+/**
+ * Ties the status bar to {@link HeadsUpManager}.
+ */
+@SysUISingleton
+public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener, CoreStartable {
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final StatusBarWindowControllerStore mStatusBarWindowControllerStore;
+    private final ShadeViewController mShadeViewController;
+    private final PanelExpansionInteractor mPanelExpansionInteractor;
+    private final NotificationStackScrollLayoutController mNsslController;
+    private final KeyguardBypassController mKeyguardBypassController;
+    private final HeadsUpManager mHeadsUpManager;
+    private final StatusBarStateController mStatusBarStateController;
+    private final NotificationRemoteInputManager mNotificationRemoteInputManager;
+
+    @Inject
+    StatusBarHeadsUpChangeListener(
+            NotificationShadeWindowController notificationShadeWindowController,
+            StatusBarWindowControllerStore statusBarWindowControllerStore,
+            ShadeViewController shadeViewController,
+            PanelExpansionInteractor panelExpansionInteractor,
+            NotificationStackScrollLayoutController nsslController,
+            KeyguardBypassController keyguardBypassController,
+            HeadsUpManager headsUpManager,
+            StatusBarStateController statusBarStateController,
+            NotificationRemoteInputManager notificationRemoteInputManager) {
+        mNotificationShadeWindowController = notificationShadeWindowController;
+        mStatusBarWindowControllerStore = statusBarWindowControllerStore;
+        mShadeViewController = shadeViewController;
+        mPanelExpansionInteractor = panelExpansionInteractor;
+        mNsslController = nsslController;
+        mKeyguardBypassController = keyguardBypassController;
+        mHeadsUpManager = headsUpManager;
+        mStatusBarStateController = statusBarStateController;
+        mNotificationRemoteInputManager = notificationRemoteInputManager;
+    }
+
+    @Override
+    public void start() {
+        mHeadsUpManager.addListener(this);
+    }
+
+    @Override
+    public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
+        if (inPinnedMode) {
+            mNotificationShadeWindowController.setHeadsUpShowing(true);
+            mStatusBarWindowControllerStore.getDefaultDisplay().setForceStatusBarVisible(true);
+            if (mPanelExpansionInteractor.isFullyCollapsed()) {
+                mShadeViewController.updateTouchableRegion();
+            }
+        } else {
+            boolean bypassKeyguard = mKeyguardBypassController.getBypassEnabled()
+                    && mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+            if (!mPanelExpansionInteractor.isFullyCollapsed()
+                    || mPanelExpansionInteractor.isTracking()
+                    || bypassKeyguard) {
+                // We are currently tracking or is open and the shade doesn't need to
+                //be kept
+                // open artificially.
+                mNotificationShadeWindowController.setHeadsUpShowing(false);
+                if (bypassKeyguard) {
+                    mStatusBarWindowControllerStore
+                            .getDefaultDisplay()
+                            .setForceStatusBarVisible(false);
+                }
+            } else {
+                // we need to keep the panel open artificially, let's wait until the
+                //animation
+                // is finished.
+                setHeadsAnimatingAway(true);
+                mNsslController.runAfterAnimationFinished(() -> {
+                    if (!mHeadsUpManager.hasPinnedHeadsUp()) {
+                        mNotificationShadeWindowController.setHeadsUpShowing(false);
+                        setHeadsAnimatingAway(false);
+                    }
+                    mNotificationRemoteInputManager.onPanelCollapsed();
+                });
+            }
+        }
+    }
+
+    private void setHeadsAnimatingAway(boolean headsUpAnimatingAway) {
+        if (!SceneContainerFlag.isEnabled()) {
+            mHeadsUpManager.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
index 3c8c42f..0f19d72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
@@ -25,7 +25,11 @@
 
 /** Testable wrapper around Context. */
 class IconBuilder @Inject constructor(private val context: Context) {
-    fun createIconView(entry: NotificationEntry): StatusBarIconView {
+    @JvmOverloads
+    fun createIconView(
+        entry: NotificationEntry,
+        context: Context = this.context,
+    ): StatusBarIconView {
         return StatusBarIconView(
             context,
             "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}",
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 4717194..98ce163 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -19,6 +19,7 @@
 import android.app.Notification
 import android.app.Notification.MessagingStyle
 import android.app.Person
+import android.content.Context
 import android.content.pm.LauncherApps
 import android.graphics.drawable.Icon
 import android.os.Build
@@ -36,6 +37,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.notification.InflationException
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -68,6 +70,17 @@
     @Background private val bgCoroutineContext: CoroutineContext,
     @Main private val mainCoroutineContext: CoroutineContext,
 ) : ConversationIconManager {
+
+    /**
+     * A listener that is notified when a [NotificationEntry] has been updated and the associated
+     * icons have to be updated as well.
+     */
+    fun interface OnIconUpdateRequiredListener {
+        fun onIconUpdateRequired(entry: NotificationEntry)
+    }
+
+    private val onIconUpdateRequiredListeners = mutableSetOf<OnIconUpdateRequiredListener>()
+
     private var unimportantConversationKeys: Set<String> = emptySet()
     /**
      * A map of running jobs for fetching the person avatar from launcher. The key is the
@@ -76,6 +89,16 @@
     private var launcherPeopleAvatarIconJobs: ConcurrentHashMap<String, Job> =
         ConcurrentHashMap<String, Job>()
 
+    fun addIconsUpdateListener(listener: OnIconUpdateRequiredListener) {
+        StatusBarConnectedDisplays.assertInNewMode()
+        onIconUpdateRequiredListeners += listener
+    }
+
+    fun removeIconsUpdateListener(listener: OnIconUpdateRequiredListener) {
+        StatusBarConnectedDisplays.assertInNewMode()
+        onIconUpdateRequiredListeners -= listener
+    }
+
     fun attach() {
         notifCollection.addCollectionListener(entryListener)
     }
@@ -112,6 +135,21 @@
     }
 
     /**
+     * Inflate the [StatusBarIconView] for the given [NotificationEntry], using the specified
+     * [Context].
+     */
+    fun createSbIconView(context: Context, entry: NotificationEntry): StatusBarIconView =
+        traceSection("IconManager.createSbIconView") {
+            StatusBarConnectedDisplays.assertInNewMode()
+
+            val sbIcon = iconBuilder.createIconView(entry, context)
+            sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+            val (normalIconDescriptor, _) = getIconDescriptors(entry)
+            setIcon(entry, normalIconDescriptor, sbIcon)
+            return sbIcon
+        }
+
+    /**
      * Inflate icon views for each icon variant and assign appropriate icons to them. Stores the
      * result in [NotificationEntry.getIcons].
      *
@@ -159,6 +197,18 @@
             }
         }
 
+    /** Update the [StatusBarIconView] for the given [NotificationEntry]. */
+    fun updateSbIcon(entry: NotificationEntry, iconView: StatusBarIconView) =
+        traceSection("IconManager.updateSbIcon") {
+            StatusBarConnectedDisplays.assertInNewMode()
+
+            val (normalIconDescriptor, _) = getIconDescriptors(entry)
+            val notificationContentDescription =
+                entry.sbn.notification?.let { iconBuilder.getIconContentDescription(it) }
+            iconView.setNotification(entry.sbn, notificationContentDescription)
+            setIcon(entry, normalIconDescriptor, iconView)
+        }
+
     /**
      * Update the notification icons.
      *
@@ -172,6 +222,10 @@
                 return@traceSection
             }
 
+            if (StatusBarConnectedDisplays.isEnabled) {
+                onIconUpdateRequiredListeners.onEach { it.onIconUpdateRequired(entry) }
+            }
+
             if (usingCache && !Flags.notificationsBackgroundIcons()) {
                 Log.wtf(
                     TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStore.kt
new file mode 100644
index 0000000..227a1fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStore.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.statusbar.notification.icon.ui.viewbinder
+
+import com.android.systemui.display.domain.interactor.DisplayWindowPropertiesInteractor
+import com.android.systemui.lifecycle.Activatable
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.icon.IconManager
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.ConcurrentHashMap
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+
+/** [IconViewStore] for the status bar on multiple displays. */
+class ConnectedDisplaysStatusBarNotificationIconViewStore
+@AssistedInject
+constructor(
+    @Assisted private val displayId: Int,
+    private val notifCollection: NotifCollection,
+    private val iconManager: IconManager,
+    private val displayWindowPropertiesInteractor: DisplayWindowPropertiesInteractor,
+    private val notifPipeline: NotifPipeline,
+) : IconViewStore, Activatable {
+
+    private val cachedIcons = ConcurrentHashMap<String, StatusBarIconView>()
+
+    private val iconUpdateRequiredListener =
+        object : IconManager.OnIconUpdateRequiredListener {
+            override fun onIconUpdateRequired(entry: NotificationEntry) {
+                val iconView = iconView(entry.key) ?: return
+                iconManager.updateSbIcon(entry, iconView)
+            }
+        }
+
+    private val notifCollectionListener =
+        object : NotifCollectionListener {
+            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+                cachedIcons.remove(entry.key)
+            }
+        }
+
+    override fun iconView(key: String): StatusBarIconView? {
+        val entry = notifCollection.getEntry(key) ?: return null
+        return cachedIcons.computeIfAbsent(key) {
+            val context = displayWindowPropertiesInteractor.getForStatusBar(displayId).context
+            iconManager.createSbIconView(context, entry)
+        }
+    }
+
+    override suspend fun activate() = coroutineScope {
+        start()
+        try {
+            awaitCancellation()
+        } finally {
+            stop()
+        }
+    }
+
+    private fun start() {
+        notifPipeline.addCollectionListener(notifCollectionListener)
+        iconManager.addIconsUpdateListener(iconUpdateRequiredListener)
+    }
+
+    private fun stop() {
+        notifPipeline.removeCollectionListener(notifCollectionListener)
+        iconManager.removeIconsUpdateListener(iconUpdateRequiredListener)
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(displayId: Int): ConnectedDisplaysStatusBarNotificationIconViewStore
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
index a21dabb..aa81ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.icon.ui.viewbinder
 
+import android.view.Display
 import androidx.lifecycle.lifecycleScope
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.app.tracing.traceSection
@@ -29,6 +30,7 @@
 import com.android.systemui.statusbar.ui.SystemBarUtilsState
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
 
 /** Binds a [NotificationIconContainer] to a [NotificationIconContainerStatusBarViewModel]. */
 class NotificationIconContainerStatusBarViewBinder
@@ -38,11 +40,22 @@
     @ShadeDisplayAware private val configuration: ConfigurationState,
     private val systemBarUtilsState: SystemBarUtilsState,
     private val failureTracker: StatusBarIconViewBindingFailureTracker,
-    private val viewStore: StatusBarNotificationIconViewStore,
+    private val defaultDisplayViewStore: StatusBarNotificationIconViewStore,
+    private val connectedDisplaysViewStoreFactory:
+        ConnectedDisplaysStatusBarNotificationIconViewStore.Factory,
 ) {
+
     fun bindWhileAttached(view: NotificationIconContainer, displayId: Int): DisposableHandle {
         return traceSection("NICStatusBar#bindWhileAttached") {
             view.repeatWhenAttached {
+                val viewStore =
+                    if (displayId == Display.DEFAULT_DISPLAY) {
+                        defaultDisplayViewStore
+                    } else {
+                        connectedDisplaysViewStoreFactory.create(displayId = displayId).also {
+                            lifecycleScope.launch { it.activate() }
+                        }
+                    }
                 lifecycleScope.launch {
                     NotificationIconContainerViewBinder.bind(
                         displayId = displayId,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
index 8768ea2..f86ae68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -18,11 +18,11 @@
 import android.content.res.Resources
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.icon.domain.interactor.AlwaysOnDisplayNotificationIconsInteractor
 import javax.inject.Inject
@@ -44,17 +44,16 @@
     iconsInteractor: AlwaysOnDisplayNotificationIconsInteractor,
     keyguardInteractor: KeyguardInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    @Main resources: Resources,
+    @ShadeDisplayAware resources: Resources,
     shadeInteractor: ShadeInteractor,
 ) {
     private val maxIcons = resources.getInteger(R.integer.max_notif_icons_on_aod)
 
     /** Are changes to the icon container animated? */
     val areContainerChangesAnimated: Flow<Boolean> =
-        combine(
-                shadeInteractor.isShadeTouchable,
-                keyguardInteractor.isKeyguardVisible,
-            ) { panelTouchesEnabled, isKeyguardVisible ->
+        combine(shadeInteractor.isShadeTouchable, keyguardInteractor.isKeyguardVisible) {
+                panelTouchesEnabled,
+                isKeyguardVisible ->
                 panelTouchesEnabled && isKeyguardVisible
             }
             .flowOn(bgContext)
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 ec0827b..caa6ccf 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
@@ -52,13 +52,13 @@
 import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
 import com.android.systemui.statusbar.StatusBarState.SHADE
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
 import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.util.NotificationChannels
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.settings.SystemSettings
@@ -102,7 +102,7 @@
         globalSettings.registerContentObserverSync(
             globalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
             /* notifyForDescendants = */ true,
-            observer
+            observer,
         )
 
         // QQQ: Do we need to register for SETTING_HEADS_UP_TICKER? It seems unused.
@@ -139,7 +139,7 @@
 
 class PeekAlreadyBubbledSuppressor(
     private val statusBarStateController: StatusBarStateController,
-    private val bubbles: Optional<Bubbles>
+    private val bubbles: Optional<Bubbles>,
 ) : VisualInterruptionFilter(types = setOf(PEEK), reason = "already bubbled") {
     override fun shouldSuppress(entry: NotificationEntry) =
         when {
@@ -164,7 +164,7 @@
 
 class PeekDeviceNotInUseSuppressor(
     private val powerManager: PowerManager,
-    private val statusBarStateController: StatusBarStateController
+    private val statusBarStateController: StatusBarStateController,
 ) : VisualInterruptionCondition(types = setOf(PEEK), reason = "device not in use") {
     override fun shouldSuppress() =
         when {
@@ -177,7 +177,7 @@
     VisualInterruptionFilter(
         types = setOf(PEEK),
         reason = "has old `when`",
-        uiEventId = HUN_SUPPRESSED_OLD_WHEN
+        uiEventId = HUN_SUPPRESSED_OLD_WHEN,
     ) {
     private fun whenAge(entry: NotificationEntry) =
         systemClock.currentTimeMillis() - entry.sbn.notification.getWhen()
@@ -206,7 +206,7 @@
 class PulseLockscreenVisibilityPrivateSuppressor :
     VisualInterruptionFilter(
         types = setOf(PULSE),
-        reason = "hidden by lockscreen visibility override"
+        reason = "hidden by lockscreen visibility override",
     ) {
     override fun shouldSuppress(entry: NotificationEntry) =
         entry.ranking.lockscreenVisibilityOverride == VISIBILITY_PRIVATE
@@ -220,7 +220,7 @@
 class HunGroupAlertBehaviorSuppressor :
     VisualInterruptionFilter(
         types = setOf(PEEK, PULSE),
-        reason = "suppressive group alert behavior"
+        reason = "suppressive group alert behavior",
     ) {
     override fun shouldSuppress(entry: NotificationEntry) =
         entry.sbn.let { it.isGroup && it.notification.suppressAlertingDueToGrouping() }
@@ -282,11 +282,7 @@
     private val notificationManager: NotificationManager,
     private val logger: VisualInterruptionDecisionLogger,
     private val systemSettings: SystemSettings,
-) :
-    VisualInterruptionFilter(
-        types = setOf(PEEK, PULSE),
-        reason = "avalanche",
-    ) {
+) : VisualInterruptionFilter(types = setOf(PEEK, PULSE), reason = "avalanche") {
     val TAG = "AvalancheSuppressor"
 
     private val prefs = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
@@ -324,7 +320,7 @@
         ALLOW_FSI_WITH_PERMISSION_ON,
         ALLOW_COLORIZED,
         ALLOW_EMERGENCY,
-        SUPPRESS
+        SUPPRESS,
     }
 
     enum class AvalancheEvent(private val id: Int) : UiEventLogger.UiEventEnum {
@@ -354,6 +350,7 @@
         AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_EMERGENCY(1868),
         @UiEvent(doc = "HUN allowed during avalanche because it is a car warning")
         AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_WARNING(1869);
+
         override fun getId(): Int {
             return id
         }
@@ -399,7 +396,7 @@
         val bundle = Bundle()
         bundle.putString(
             Notification.EXTRA_SUBSTITUTE_APP_NAME,
-            context.getString(com.android.internal.R.string.android_system_label)
+            context.getString(com.android.internal.R.string.android_system_label),
         )
 
         val builder =
@@ -452,7 +449,8 @@
 
         if (entry.sbn.notification.category == CATEGORY_CAR_EMERGENCY) {
             uiEventLogger.log(
-                    AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_EMERGENCY)
+                AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_EMERGENCY
+            )
             return State.ALLOW_CATEGORY_CAR_EMERGENCY
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 450067a..f586051 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -47,7 +47,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.EventLog;
 import com.android.systemui.util.settings.GlobalSettings;
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 52336be..b831b94 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
@@ -32,6 +32,7 @@
 import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionSuppressor.EventLogData
@@ -41,7 +42,6 @@
 import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.EventLog
 import com.android.systemui.util.settings.GlobalSettings
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
new file mode 100644
index 0000000..38eaf27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import android.app.Notification
+import android.app.Notification.BigPictureStyle
+import android.app.Notification.BigTextStyle
+import android.app.Notification.CallStyle
+import android.app.Notification.EXTRA_CHRONOMETER_COUNT_DOWN
+import android.app.Notification.EXTRA_SUB_TEXT
+import android.app.Notification.EXTRA_TEXT
+import android.app.Notification.EXTRA_TITLE
+import android.app.Notification.ProgressStyle
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
+import javax.inject.Inject
+
+@SysUISingleton
+class PromotedNotificationContentExtractor
+@Inject
+constructor(
+    private val promotedNotificationsProvider: PromotedNotificationsProvider,
+    @ShadeDisplayAware private val context: Context,
+    private val logger: PromotedNotificationLogger,
+) {
+    fun extractContent(
+        entry: NotificationEntry,
+        recoveredBuilder: Notification.Builder,
+    ): PromotedNotificationContentModel? {
+        if (!PromotedNotificationContentModel.featureFlagEnabled()) {
+            logger.logExtractionSkipped(entry, "feature flags disabled")
+            return null
+        }
+
+        if (!promotedNotificationsProvider.shouldPromote(entry)) {
+            logger.logExtractionSkipped(entry, "shouldPromote returned false")
+            return null
+        }
+
+        val notification = entry.sbn.notification
+        if (notification == null) {
+            logger.logExtractionFailed(entry, "entry.sbn.notification is null")
+            return null
+        }
+
+        val contentBuilder = PromotedNotificationContentModel.Builder(entry.key)
+
+        // TODO: Pitch a fit if style is unsupported or mandatory fields are missing once
+        // FLAG_PROMOTED_ONGOING is set reliably and we're not testing status bar chips.
+
+        contentBuilder.skeletonSmallIcon = entry.icons.aodIcon?.sourceIcon
+        contentBuilder.appName = notification.loadHeaderAppName(context)
+        contentBuilder.subText = notification.subText()
+        contentBuilder.time = notification.extractWhen()
+        contentBuilder.lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs
+        contentBuilder.profileBadgeResId = null // TODO
+        contentBuilder.title = notification.title()
+        contentBuilder.text = notification.text()
+        contentBuilder.skeletonLargeIcon = null // TODO
+
+        recoveredBuilder.style?.extractContent(contentBuilder)
+            ?: run { contentBuilder.style = Style.Ineligible }
+
+        return contentBuilder.build().also { logger.logExtractionSucceeded(entry, it) }
+    }
+}
+
+private fun Notification.title(): CharSequence? = extras?.getCharSequence(EXTRA_TITLE)
+
+private fun Notification.text(): CharSequence? = extras?.getCharSequence(EXTRA_TEXT)
+
+private fun Notification.subText(): String? = extras?.getString(EXTRA_SUB_TEXT)
+
+private fun Notification.chronometerCountDown(): Boolean =
+    extras?.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, /* defaultValue= */ false) ?: false
+
+private fun Notification.extractWhen(): When? {
+    val time = `when`
+    val showsTime = showsTime()
+    val showsChronometer = showsChronometer()
+    val countDown = chronometerCountDown()
+
+    return when {
+        showsTime -> When(time, When.Mode.Absolute)
+        showsChronometer -> When(time, if (countDown) When.Mode.CountDown else When.Mode.CountUp)
+        else -> null
+    }
+}
+
+private fun Notification.Style.extractContent(
+    contentBuilder: PromotedNotificationContentModel.Builder
+) {
+    contentBuilder.style =
+        when (this) {
+            is BigPictureStyle -> {
+                extractContent(contentBuilder)
+                Style.BigPicture
+            }
+
+            is BigTextStyle -> {
+                extractContent(contentBuilder)
+                Style.BigText
+            }
+
+            is CallStyle -> {
+                extractContent(contentBuilder)
+                Style.Call
+            }
+
+            is ProgressStyle -> {
+                extractContent(contentBuilder)
+                Style.Progress
+            }
+
+            else -> Style.Ineligible
+        }
+}
+
+private fun BigPictureStyle.extractContent(
+    contentBuilder: PromotedNotificationContentModel.Builder
+) {
+    // TODO?
+}
+
+private fun BigTextStyle.extractContent(contentBuilder: PromotedNotificationContentModel.Builder) {
+    // TODO?
+}
+
+private fun CallStyle.extractContent(contentBuilder: PromotedNotificationContentModel.Builder) {
+    contentBuilder.personIcon = null // TODO
+    contentBuilder.personName = null // TODO
+    contentBuilder.verificationIcon = null // TODO
+    contentBuilder.verificationText = null // TODO
+}
+
+private fun ProgressStyle.extractContent(contentBuilder: PromotedNotificationContentModel.Builder) {
+    // TODO: Create NotificationProgressModel.toSkeleton, or something similar.
+    contentBuilder.progress = createProgressModel(0xffffffff.toInt(), 0x00000000)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
new file mode 100644
index 0000000..13ad141
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.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.statusbar.notification.promoted
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import javax.inject.Inject
+
+class PromotedNotificationLogger
+@Inject
+constructor(@NotificationLog private val buffer: LogBuffer) {
+    fun logExtractionSkipped(entry: NotificationEntry, reason: String) {
+        buffer.log(
+            EXTRACTION_TAG,
+            INFO,
+            {
+                str1 = entry.logKey
+                str2 = reason
+            },
+            { "extraction skipped: $str2 for $str1" },
+        )
+    }
+
+    fun logExtractionFailed(entry: NotificationEntry, reason: String) {
+        buffer.log(
+            EXTRACTION_TAG,
+            ERROR,
+            {
+                str1 = entry.logKey
+                str2 = reason
+            },
+            { "extraction failed: $str2 for $str1" },
+        )
+    }
+
+    fun logExtractionSucceeded(
+        entry: NotificationEntry,
+        content: PromotedNotificationContentModel,
+    ) {
+        buffer.log(
+            EXTRACTION_TAG,
+            INFO,
+            {
+                str1 = entry.logKey
+                str2 = content.toString()
+            },
+            { "extraction succeeded: $str2 for $str1" },
+        )
+    }
+}
+
+private const val EXTRACTION_TAG = "PromotedNotificationContentExtractor"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt
index 691dc6f..947d9e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt
@@ -19,6 +19,7 @@
 import android.app.Notification.FLAG_PROMOTED_ONGOING
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import javax.inject.Inject
 
 /** A provider for making decisions on which notifications should be promoted. */
@@ -30,7 +31,7 @@
 @SysUISingleton
 open class PromotedNotificationsProviderImpl @Inject constructor() : PromotedNotificationsProvider {
     override fun shouldPromote(entry: NotificationEntry): Boolean {
-        if (!PromotedNotificationUi.isEnabled) {
+        if (!PromotedNotificationContentModel.featureFlagEnabled()) {
             return false
         }
         return (entry.sbn.notification.flags and FLAG_PROMOTED_ONGOING) != 0
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 d538f52..5c51ada 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
@@ -46,7 +46,6 @@
 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.NotificationsImprovedHunAnimation;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.util.DumpUtilsKt;
@@ -407,12 +406,7 @@
 
         mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
                 targetValue);
-        if (NotificationsImprovedHunAnimation.isEnabled()
-                || NotificationHeadsUpCycling.isEnabled()) {
-            mAppearAnimator.setInterpolator(mCurrentAppearInterpolator);
-        } else {
-            mAppearAnimator.setInterpolator(Interpolators.LINEAR);
-        }
+        mAppearAnimator.setInterpolator(mCurrentAppearInterpolator);
         mAppearAnimator.setDuration(
                 (long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
         mAppearAnimator.addUpdateListener(animation -> {
@@ -531,10 +525,7 @@
      * @param clipSide Which side if view we want to clip from
      */
     private void updateAppearRect(ClipSide clipSide) {
-        float interpolatedFraction =
-                NotificationsImprovedHunAnimation.isEnabled()
-                        || NotificationHeadsUpCycling.isEnabled() ? mAppearAnimationFraction
-                        : mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
+        float interpolatedFraction = mAppearAnimationFraction;
         mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY;
         final int fullHeight = getActualHeight();
         float height = fullHeight * interpolatedFraction;
@@ -566,14 +557,6 @@
         updateAppearRect(ClipSide.BOTTOM);
     }
 
-    private float getInterpolatedAppearAnimationFraction() {
-
-        if (mAppearAnimationFraction >= 0) {
-            return mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
-        }
-        return 1.0f;
-    }
-
     private void updateAppearAnimationAlpha() {
         updateAppearAnimationContentAlpha(
                 mAppearAnimationFraction,
@@ -643,26 +626,6 @@
         super.applyRoundnessAndInvalidate();
     }
 
-    @Override
-    public float getTopCornerRadius() {
-        if (NotificationsImprovedHunAnimation.isEnabled()) {
-            return super.getTopCornerRadius();
-        }
-
-        float fraction = getInterpolatedAppearAnimationFraction();
-        return MathUtils.lerp(0, super.getTopCornerRadius(), fraction);
-    }
-
-    @Override
-    public float getBottomCornerRadius() {
-        if (NotificationsImprovedHunAnimation.isEnabled()) {
-            return super.getBottomCornerRadius();
-        }
-
-        float fraction = getInterpolatedAppearAnimationFraction();
-        return MathUtils.lerp(0, super.getBottomCornerRadius(), fraction);
-    }
-
     private void applyBackgroundRoundness(float topRadius, float bottomRadius) {
         mBackgroundNormal.setRadius(topRadius, bottomRadius);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/EnsureEnrViewsVisibility.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/EnsureEnrViewsVisibility.kt
deleted file mode 100644
index aa63f4ddb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/EnsureEnrViewsVisibility.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.statusbar.notification.row
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the ensure enr views visibility flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object EnsureEnrViewsVisibility {
-    /** The aconfig flag name */
-    const val FLAG_NAME = Flags.FLAG_ENSURE_ENR_VIEWS_VISIBILITY
-
-    /** A token used for dependency declaration */
-    val token: FlagToken
-        get() = FlagToken(FLAG_NAME, isEnabled)
-
-    /** Is the refactor enabled */
-    @JvmStatic
-    inline val isEnabled
-        get() = Flags.ensureEnrViewsVisibility()
-
-    /**
-     * Called to ensure code is only run when the flag is enabled. This protects users from the
-     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
-     * build to ensure that the refactor author catches issues in testing.
-     */
-    @JvmStatic
-    inline fun isUnexpectedlyInLegacyMode() =
-        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
-    /**
-     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
-     * the flag is not enabled to ensure that the refactor author catches issues in testing.
-     * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
-     */
-    @JvmStatic
-    inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
-
-    /**
-     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
-     * the flag is enabled to ensure that the refactor author catches issues in testing.
-     */
-    @JvmStatic
-    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index fb62f80..d71b1b2 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static android.app.Flags.notificationsRedesignTemplates;
 import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 
@@ -36,6 +37,7 @@
 import android.graphics.Canvas;
 import android.graphics.Path;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.Drawable;
@@ -101,6 +103,8 @@
 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -108,6 +112,7 @@
 import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
 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.NotificationAddXOnHoverToDismiss;
 import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
 import com.android.systemui.statusbar.notification.shared.TransparentHeaderFix;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -118,13 +123,13 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.SwipeableView;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
 import com.android.systemui.statusbar.policy.RemoteInputView;
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
 import com.android.systemui.util.Compile;
 import com.android.systemui.util.DumpUtilsKt;
+import com.android.systemui.util.ListenerSet;
 import com.android.systemui.wmshell.BubblesManager;
 
 import java.io.PrintWriter;
@@ -277,7 +282,7 @@
     private NotificationMenuRowPlugin mMenuRow;
     private ViewStub mGutsStub;
     private boolean mIsSystemChildExpanded;
-    private boolean mIsPinned;
+    private PinnedStatus mPinnedStatus = PinnedStatus.NotPinned;
     private boolean mExpandAnimationRunning;
     private AboveShelfChangedListener mAboveShelfChangedListener;
     private HeadsUpManager mHeadsUpManager;
@@ -430,6 +435,10 @@
     private float mBottomRoundnessDuringLaunchAnimation;
     private float mSmallRoundness;
 
+    private ListenerSet<DismissButtonTargetVisibilityListener>
+            mDismissButtonTargetVisibilityListeners
+            = new ListenerSet();
+
     public NotificationContentView[] getLayouts() {
         return Arrays.copyOf(mLayouts, mLayouts.length);
     }
@@ -739,6 +748,73 @@
         }
     }
 
+    public interface DismissButtonTargetVisibilityListener {
+        // Called when the notification dismiss button's target visibility changes.
+        // NOTE: This can be called when the dismiss button already has the target visibility.
+        void onTargetVisibilityChanged(boolean targetVisible);
+    }
+
+    public void addDismissButtonTargetStateListener(
+            DismissButtonTargetVisibilityListener listener) {
+        if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) {
+            return;
+        }
+
+        mDismissButtonTargetVisibilityListeners.addIfAbsent(listener);
+    }
+
+    public void removeDismissButtonTargetStateListener(
+            DismissButtonTargetVisibilityListener listener) {
+        if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) {
+            return;
+        }
+
+        mDismissButtonTargetVisibilityListeners.remove(listener);
+    }
+
+    @Override
+    public boolean onInterceptHoverEvent(MotionEvent event) {
+        if (!NotificationAddXOnHoverToDismiss.isEnabled()) {
+            return super.onInterceptHoverEvent(event);
+        }
+
+        // Do not bother checking the dismiss button's target visibility if the notification cannot
+        // be dismissed.
+        if (!canEntryBeDismissed()) {
+            return false;
+        }
+
+        final Boolean targetVisible = getDismissButtonTargetVisibilityIfAny(event);
+        if (targetVisible != null) {
+            for (DismissButtonTargetVisibilityListener listener :
+                    mDismissButtonTargetVisibilityListeners) {
+                listener.onTargetVisibilityChanged(targetVisible.booleanValue());
+            }
+        }
+
+        // Do not consume the hover event so that children still have a chance to process it.
+        return false;
+    }
+
+    private @Nullable Boolean getDismissButtonTargetVisibilityIfAny(MotionEvent event) {
+        // Returns the dismiss button's target visibility resulted by `event`. Returns null if the
+        // target visibility should not change.
+
+        if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
+            // The notification dismiss button should be hidden when the hover exit event is located
+            // outside of the notification. NOTE: The hover exit event can be inside the
+            // notification if hover moves from one hoverable child to another.
+            final Rect localBounds = new Rect(0, 0, this.getWidth(), this.getActualHeight());
+            if (!localBounds.contains((int) event.getX(), (int) event.getY())) {
+                return Boolean.FALSE;
+            }
+        } else if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+            return Boolean.TRUE;
+        }
+
+        return null;
+    }
+
     private void updateLimitsForView(NotificationContentView layout) {
         View contractedView = layout.getContractedChild();
         boolean customView = contractedView != null
@@ -1154,17 +1230,15 @@
     /**
      * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this
      * the notification will be rendered on top of the screen.
-     *
-     * @param pinned whether it is pinned
      */
-    public void setPinned(boolean pinned) {
+    public void setPinnedStatus(PinnedStatus pinnedStatus) {
         int intrinsicHeight = getIntrinsicHeight();
         boolean wasAboveShelf = isAboveShelf();
-        mIsPinned = pinned;
+        mPinnedStatus = pinnedStatus;
         if (intrinsicHeight != getIntrinsicHeight()) {
             notifyHeightChanged(/* needsAnimation= */ false);
         }
-        if (pinned) {
+        if (pinnedStatus.isPinned()) {
             setAnimationRunning(true);
             mExpandedWhenPinned = false;
         } else if (mExpandedWhenPinned) {
@@ -1178,7 +1252,7 @@
 
     @Override
     public boolean isPinned() {
-        return mIsPinned;
+        return mPinnedStatus.isPinned();
     }
 
     @Override
@@ -2010,8 +2084,13 @@
                 R.dimen.notification_min_height_before_p);
         mMaxSmallHeightBeforeS = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_min_height_before_s);
-        mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext,
-                R.dimen.notification_min_height);
+        if (notificationsRedesignTemplates()) {
+            mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext,
+                    R.dimen.notification_2025_min_height);
+        } else {
+            mMaxSmallHeight = NotificationUtils.getFontScaledHeight(mContext,
+                    R.dimen.notification_min_height);
+        }
         mMaxSmallHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_min_height_increased);
         mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
@@ -2218,6 +2297,10 @@
         mTranslateableViews.remove(mGutsStub);
         // We don't handle focus highlight in this view, it's done in background drawable instead
         setDefaultFocusHighlightEnabled(false);
+
+        if (NotificationAddXOnHoverToDismiss.isEnabled()) {
+            addDismissButtonTargetStateListener(findViewById(R.id.backgroundNormal));
+        }
     }
 
     /**
@@ -2520,10 +2603,6 @@
     }
 
     private void updateChildrenVisibility() {
-        if (EnsureEnrViewsVisibility.isEnabled()) {
-            mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
-        }
-
         boolean hideContentWhileLaunching = mExpandAnimationRunning && mGuts != null
                 && mGuts.isExposed();
         mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren
@@ -3083,13 +3162,7 @@
             } else {
                 mLogger.logSkipResetAllContentAlphas(getEntry());
             }
-
-            if (!EnsureEnrViewsVisibility.isEnabled()) {
-                // mPublicLayout.setVisibility moved to updateChildrenVisibility when the flag is on
-                // in order to ensure public and private views are not visible
-                // together at the same time.
-                mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
-            }
+            mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
             updateChildrenVisibility();
         } else {
             animateShowingPublic(delay, duration, mShowingPublic);
@@ -3754,7 +3827,8 @@
     @Override
     public boolean isAboveShelf() {
         return (canShowHeadsUp()
-                && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf)
+                && (mPinnedStatus.isPinned()
+                || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf)
                 || mExpandAnimationRunning || mChildIsExpanding));
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index ffe1b6f..a150f7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -59,7 +59,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
 import com.android.systemui.util.time.SystemClock;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 77f5717..d5551b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -50,9 +50,10 @@
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 
 import javax.inject.Inject;
 
@@ -69,7 +70,8 @@
     private NotificationPanelLogger mNotificationPanelLogger;
 
     @Inject
-    public ExpandableNotificationRowDragController(Context context,
+    public ExpandableNotificationRowDragController(
+            @ShadeDisplayAware Context context,
             HeadsUpManager headsUpManager,
             ShadeController shadeController,
             NotificationPanelLogger notificationPanelLogger) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index a323c26..80cf818 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -30,7 +30,6 @@
 
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.RoundableState;
-import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 import com.android.systemui.util.DumpUtilsKt;
 
@@ -123,15 +122,6 @@
             return EMPTY_PATH;
         }
         float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
-        if (!NotificationsImprovedHunAnimation.isEnabled() && (topRadius + bottomRadius > height)) {
-            float overShoot = topRadius + bottomRadius - height;
-            float currentTopRoundness = getTopRoundness();
-            float currentBottomRoundness = getBottomRoundness();
-            topRadius -= overShoot * currentTopRoundness
-                    / (currentTopRoundness + currentBottomRoundness);
-            bottomRadius -= overShoot * currentBottomRoundness
-                    / (currentTopRoundness + currentBottomRoundness);
-        }
         getRoundedRectPath(left, top, right, bottom, topRadius, bottomRadius, mTmpPath);
         return mTmpPath;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index d0db514..34ef639 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -21,7 +21,9 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Canvas;
+import android.graphics.Path;
 import android.graphics.PorterDuff;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.LayerDrawable;
@@ -36,6 +38,7 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
 import com.android.systemui.util.DrawableDumpKt;
 
 import java.io.PrintWriter;
@@ -44,7 +47,8 @@
 /**
  * A view that can be used for both the dimmed and normal background of an notification.
  */
-public class NotificationBackgroundView extends View implements Dumpable {
+public class NotificationBackgroundView extends View implements Dumpable,
+        ExpandableNotificationRow.DismissButtonTargetVisibilityListener {
 
     private final boolean mDontModifyCorners;
     private Drawable mBackground;
@@ -66,6 +70,11 @@
     private final ColorStateList mLightColoredStatefulColors;
     private final ColorStateList mDarkColoredStatefulColors;
     private final int mNormalColor;
+    private final int convexR = 9;
+    private final int concaveR = 22;
+
+    // True only if the dismiss button is visible.
+    private boolean mDrawDismissButtonCutout = false;
 
     public NotificationBackgroundView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -80,6 +89,18 @@
     }
 
     @Override
+    public void onTargetVisibilityChanged(boolean targetVisible) {
+        if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) {
+            return;
+        }
+
+        if (mDrawDismissButtonCutout != targetVisible) {
+            mDrawDismissButtonCutout = targetVisible;
+            invalidate();
+        }
+    }
+
+    @Override
     protected void onDraw(Canvas canvas) {
         if (mClipTopAmount + mClipBottomAmount < getActualHeight() || mExpandAnimationRunning) {
             canvas.save();
@@ -87,12 +108,87 @@
                 canvas.clipRect(0, mClipTopAmount, getWidth(),
                         getActualHeight() - mClipBottomAmount);
             }
-            draw(canvas, mBackground);
+
+            if (!NotificationAddXOnHoverToDismiss.isEnabled()) {
+                draw(canvas, mBackground);
+                canvas.restore();
+                return;
+            }
+
+            Rect backgroundBounds = null;
+            if (mBackground != null || mDrawDismissButtonCutout) {
+                backgroundBounds = calculateBackgroundBounds();
+            }
+
+            if (mDrawDismissButtonCutout) {
+                canvas.clipPath(calculateDismissButtonCutoutPath(backgroundBounds));
+            }
+
+            if (mBackground != null) {
+                mBackground.setBounds(backgroundBounds);
+                mBackground.draw(canvas);
+            }
+
             canvas.restore();
         }
     }
 
+    private Path calculateDismissButtonCutoutPath(Rect backgroundBounds) {
+        // TODO(b/365585705): Adapt to RTL after the UX design is finalized.
+
+        NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode();
+
+        Path path = new Path();
+
+        final int left = backgroundBounds.left;
+        final int right = backgroundBounds.right;
+        final int top = backgroundBounds.top;
+        final int bottom = backgroundBounds.bottom;
+
+        // Generate the path clockwise from the left-top corner.
+        path.moveTo(left, top);
+        path.lineTo(right - 2 * convexR - concaveR, top);
+        path.quadTo(right - convexR - concaveR, top, right - convexR - concaveR,
+                top + convexR);
+        path.quadTo(right - convexR - concaveR, top + convexR + concaveR, right - convexR,
+                top + convexR + concaveR);
+        path.quadTo(right, top + convexR + concaveR, right, top + 2 * convexR + concaveR);
+        path.lineTo(right, bottom);
+        path.lineTo(left, bottom);
+        path.lineTo(left, top);
+
+        return path;
+    }
+
+    private Rect calculateBackgroundBounds() {
+        NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode();
+
+        int top = 0;
+        int bottom = getActualHeight();
+        if (mBottomIsRounded
+                && mBottomAmountClips
+                && !mExpandAnimationRunning) {
+            bottom -= mClipBottomAmount;
+        }
+        final boolean isRtl = isLayoutRtl();
+        final int width = getWidth();
+        final int actualWidth = getActualWidth();
+
+        int left = isRtl ? width - actualWidth : 0;
+        int right = isRtl ? width : actualWidth;
+
+        if (mExpandAnimationRunning) {
+            // Horizontally center this background view inside of the container
+            left = (int) ((width - actualWidth) / 2.0f);
+            right = (int) (left + actualWidth);
+        }
+
+        return new Rect(left, top, right, bottom);
+    }
+
     private void draw(Canvas canvas, Drawable drawable) {
+        NotificationAddXOnHoverToDismiss.assertInLegacyMode();
+
         if (drawable != null) {
             int top = 0;
             int bottom = getActualHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 41abac1..6e05e8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -54,6 +54,8 @@
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
 import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
@@ -92,6 +94,7 @@
     private final SmartReplyStateInflater mSmartReplyStateInflater;
     private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;
     private final HeadsUpStyleProvider mHeadsUpStyleProvider;
+    private final PromotedNotificationContentExtractor mPromotedNotificationContentExtractor;
 
     private final NotificationRowContentBinderLogger mLogger;
 
@@ -105,6 +108,7 @@
             SmartReplyStateInflater smartRepliesInflater,
             NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
             HeadsUpStyleProvider headsUpStyleProvider,
+            PromotedNotificationContentExtractor promotedNotificationContentExtractor,
             NotificationRowContentBinderLogger logger) {
         NotificationRowContentBinderRefactor.assertInLegacyMode();
         mRemoteViewCache = remoteViewCache;
@@ -115,6 +119,7 @@
         mSmartReplyStateInflater = smartRepliesInflater;
         mNotifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider;
         mHeadsUpStyleProvider = headsUpStyleProvider;
+        mPromotedNotificationContentExtractor = promotedNotificationContentExtractor;
         mLogger = logger;
     }
 
@@ -165,6 +170,7 @@
                 mSmartReplyStateInflater,
                 mNotifLayoutInflaterFactoryProvider,
                 mHeadsUpStyleProvider,
+                mPromotedNotificationContentExtractor,
                 mLogger);
         if (mInflateSynchronously) {
             task.onPostExecute(task.doInBackground());
@@ -913,6 +919,11 @@
         NotificationContentView privateLayout = row.getPrivateLayout();
         NotificationContentView publicLayout = row.getPublicLayout();
         logger.logAsyncTaskProgress(entry, "finishing");
+
+        if (PromotedNotificationContentModel.featureFlagEnabled()) {
+            entry.setPromotedNotificationContentModel(result.mExtractedPromotedNotificationContent);
+        }
+
         boolean setRepliesAndActions = true;
         if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
             if (result.inflatedContentView != null) {
@@ -1123,6 +1134,7 @@
         private final SmartReplyStateInflater mSmartRepliesInflater;
         private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;
         private final HeadsUpStyleProvider mHeadsUpStyleProvider;
+        private final PromotedNotificationContentExtractor mPromotedNotificationContentExtractor;
         private final NotificationRowContentBinderLogger mLogger;
 
         private AsyncInflationTask(
@@ -1142,6 +1154,7 @@
                 SmartReplyStateInflater smartRepliesInflater,
                 NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
                 HeadsUpStyleProvider headsUpStyleProvider,
+                PromotedNotificationContentExtractor promotedNotificationContentExtractor,
                 NotificationRowContentBinderLogger logger) {
             mEntry = entry;
             mRow = row;
@@ -1160,6 +1173,7 @@
             mIsMediaInQS = isMediaFlagEnabled;
             mNotifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider;
             mHeadsUpStyleProvider = headsUpStyleProvider;
+            mPromotedNotificationContentExtractor = promotedNotificationContentExtractor;
             mLogger = logger;
             entry.setInflationTask(this);
         }
@@ -1276,6 +1290,14 @@
                         );
             }
 
+            if (PromotedNotificationContentModel.featureFlagEnabled()) {
+                mLogger.logAsyncTaskProgress(mEntry, "extracting promoted notification content");
+                result.mExtractedPromotedNotificationContent = mPromotedNotificationContentExtractor
+                        .extractContent(mEntry, recoveredBuilder);
+                mLogger.logAsyncTaskProgress(mEntry, "extracted promoted notification content: "
+                        + result.mExtractedPromotedNotificationContent);
+            }
+
             mLogger.logAsyncTaskProgress(mEntry,
                     "getting row image resolver (on wrong thread!)");
             final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
@@ -1377,6 +1399,8 @@
 
     @VisibleForTesting
     static class InflationProgress {
+        PromotedNotificationContentModel mExtractedPromotedNotificationContent;
+
         private RemoteViews newContentView;
         private RemoteViews newHeadsUpView;
         private RemoteViews newExpandedView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 5ff9bc6..9712db8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -59,6 +59,7 @@
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.StatusBarState;
@@ -72,7 +73,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.wmshell.BubblesManager;
 
@@ -134,7 +135,8 @@
     private final ActivityStarter mActivityStarter;
 
     @Inject
-    public NotificationGutsManager(Context context,
+    public NotificationGutsManager(
+            @ShadeDisplayAware Context context,
             @Main Handler mainHandler,
             @Background Handler bgHandler,
             JavaAdapter javaAdapter,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index d0c033b..c7d80e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.notification.row
 
 import android.annotation.SuppressLint
+import android.app.Flags
 import android.app.Notification
 import android.content.Context
 import android.content.ContextWrapper
@@ -46,6 +47,8 @@
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
 import com.android.systemui.statusbar.notification.InflationException
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
 import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED
 import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
 import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP
@@ -95,6 +98,7 @@
     private val smartReplyStateInflater: SmartReplyStateInflater,
     private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
     private val headsUpStyleProvider: HeadsUpStyleProvider,
+    private val promotedNotificationContentExtractor: PromotedNotificationContentExtractor,
     private val logger: NotificationRowContentBinderLogger,
 ) : NotificationRowContentBinder {
 
@@ -147,6 +151,7 @@
                 /* isMediaFlagEnabled = */ smartReplyStateInflater,
                 notifLayoutInflaterFactoryProvider,
                 headsUpStyleProvider,
+                promotedNotificationContentExtractor,
                 logger,
             )
         if (inflateSynchronously) {
@@ -166,6 +171,7 @@
         builder: Notification.Builder,
         packageContext: Context,
         smartRepliesInflater: SmartReplyStateInflater,
+        promotedNotificationContentExtractor: PromotedNotificationContentExtractor,
     ): InflationProgress {
         val systemUIContext = row.context
         val result =
@@ -182,6 +188,7 @@
                 notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
                 headsUpStyleProvider = headsUpStyleProvider,
                 conversationProcessor = conversationProcessor,
+                promotedNotificationContentExtractor = promotedNotificationContentExtractor,
                 logger = logger,
             )
         inflateSmartReplyViews(
@@ -372,6 +379,7 @@
         private val smartRepliesInflater: SmartReplyStateInflater,
         private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
         private val headsUpStyleProvider: HeadsUpStyleProvider,
+        private val promotedNotificationContentExtractor: PromotedNotificationContentExtractor,
         private val logger: NotificationRowContentBinderLogger,
     ) : AsyncTask<Void, Void, Result<InflationProgress>>(), InflationCallback, InflationTask {
         private val context: Context
@@ -442,6 +450,7 @@
                     notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
                     headsUpStyleProvider = headsUpStyleProvider,
                     conversationProcessor = conversationProcessor,
+                    promotedNotificationContentExtractor = promotedNotificationContentExtractor,
                     logger = logger,
                 )
             logger.logAsyncTaskProgress(
@@ -582,6 +591,7 @@
         @VisibleForTesting val packageContext: Context,
         val remoteViews: NewRemoteViews,
         val contentModel: NotificationContentModel,
+        val extractedPromotedNotificationContentModel: PromotedNotificationContentModel?,
     ) {
 
         var inflatedContentView: View? = null
@@ -670,8 +680,23 @@
             notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
             headsUpStyleProvider: HeadsUpStyleProvider,
             conversationProcessor: ConversationNotificationProcessor,
+            promotedNotificationContentExtractor: PromotedNotificationContentExtractor,
             logger: NotificationRowContentBinderLogger,
         ): InflationProgress {
+            val promoted =
+                if (PromotedNotificationContentModel.featureFlagEnabled()) {
+                    logger.logAsyncTaskProgress(entry, "extracting promoted notification content")
+                    val extracted =
+                        promotedNotificationContentExtractor.extractContent(entry, builder)
+                    logger.logAsyncTaskProgress(
+                        entry,
+                        "extracted promoted notification content: {extracted}",
+                    )
+                    extracted
+                } else {
+                    null
+                }
+
             // process conversations and extract the messaging style
             val messagingStyle =
                 if (entry.ranking.isConversation) {
@@ -734,6 +759,7 @@
                 packageContext = packageContext,
                 remoteViews = remoteViews,
                 contentModel = contentModel,
+                extractedPromotedNotificationContentModel = promoted,
             )
         }
 
@@ -1393,6 +1419,11 @@
             logger.logAsyncTaskProgress(entry, "finishing")
 
             entry.setContentModel(result.contentModel)
+            if (PromotedNotificationContentModel.featureFlagEnabled()) {
+                entry.promotedNotificationContentModel =
+                    result.extractedPromotedNotificationContentModel
+            }
+
             result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
 
             setContentViewsFromRemoteViews(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index b622def..e9eecdd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.notification.row.wrapper;
 
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
 import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
 
 import android.app.Notification;
@@ -48,6 +51,7 @@
 import com.android.systemui.statusbar.notification.RoundableState;
 import com.android.systemui.statusbar.notification.TransformState;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
 
 import java.util.Stack;
 
@@ -115,6 +119,10 @@
         resolveHeaderViews();
         addFeedbackOnClickListener(row);
         addCloseButtonOnClickListener(row);
+
+        if (NotificationAddXOnHoverToDismiss.isEnabled()) {
+            mRow.addDismissButtonTargetStateListener(mHoverListener);
+        }
     }
 
     @Override
@@ -166,13 +174,34 @@
         }
     }
 
+    private ExpandableNotificationRow.DismissButtonTargetVisibilityListener mHoverListener = new
+            ExpandableNotificationRow.DismissButtonTargetVisibilityListener() {
+                @Override
+                public void onTargetVisibilityChanged(boolean targetVisible) {
+                    NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode();
+
+                    if (mCloseButton != null) {
+                        mCloseButton.setVisibility(targetVisible ? VISIBLE : GONE);
+                    }
+                }
+            };
+
+    @Override
+    public void setRemoved() {
+        super.setRemoved();
+
+        if (NotificationAddXOnHoverToDismiss.isEnabled()) {
+            mRow.removeDismissButtonTargetStateListener(mHoverListener);
+        }
+    }
+
     /**
      * Shows the given feedback icon, or hides the icon if null.
      */
     @Override
     public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
         if (mFeedbackIcon != null) {
-            mFeedbackIcon.setVisibility(icon != null ? View.VISIBLE : View.GONE);
+            mFeedbackIcon.setVisibility(icon != null ? VISIBLE : GONE);
             if (icon != null) {
                 if (mFeedbackIcon instanceof ImageButton) {
                     ((ImageButton) mFeedbackIcon).setImageResource(icon.getIconRes());
@@ -266,7 +295,7 @@
             boolean expandable,
             View.OnClickListener onClickListener,
             boolean requestLayout) {
-        mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
+        mExpandButton.setVisibility(expandable ? VISIBLE : GONE);
         mExpandButton.setOnClickListener(expandable ? onClickListener : null);
         if (mAltExpandTarget != null) {
             mAltExpandTarget.setOnClickListener(expandable ? onClickListener : null);
@@ -294,7 +323,7 @@
     @Override
     public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
         if (mAudiblyAlertedIcon != null) {
-            mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE);
+            mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? VISIBLE : GONE);
         }
     }
 
@@ -371,6 +400,7 @@
             ((DateTimeView) timeView).setTime(whenMillis);
         }
     }
+
     protected void addTransformedViews(View... views) {
         for (View view : views) {
             if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index a2b7155..ab8be30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -38,9 +38,6 @@
     val groupKey: String?,
     /** When this notification was posted. */
     val whenTime: Long,
-    // TODO(b/377566661): Make isPromoted just check if promotedContent != null.
-    /** True if this notification should be promoted and false otherwise. */
-    val isPromoted: Boolean,
     /** Is this entry in the ambient / minimized section (lowest priority)? */
     val isAmbient: Boolean,
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAddXOnHoverToDismiss.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAddXOnHoverToDismiss.kt
new file mode 100644
index 0000000..0961874
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAddXOnHoverToDismiss.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.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification dismiss button on hover flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationAddXOnHoverToDismiss {
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS
+
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationAddXOnHoverToDismiss()
+
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt
deleted file mode 100644
index 16d35fe..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt
+++ /dev/null
@@ -1,53 +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.statusbar.notification.shared
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the notifications improved hun animation flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object NotificationsImprovedHunAnimation {
-    /** The aconfig flag name */
-    const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_IMPROVED_HUN_ANIMATION
-
-    /** A token used for dependency declaration */
-    val token: FlagToken
-        get() = FlagToken(FLAG_NAME, isEnabled)
-
-    /** Is the refactor enabled */
-    @JvmStatic
-    inline val isEnabled
-        get() = Flags.notificationsImprovedHunAnimation()
-
-    /**
-     * 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 7771421..64ca815 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
@@ -30,6 +30,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarState;
@@ -39,7 +40,7 @@
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.BypassController;
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.AvalancheController;
+import com.android.systemui.statusbar.notification.headsup.AvalancheController;
 
 import java.io.PrintWriter;
 
@@ -297,7 +298,7 @@
 
     @Inject
     public AmbientState(
-            @NonNull Context context,
+            @NonNull @ShadeDisplayAware Context context,
             @NonNull DumpManager dumpManager,
             @NonNull SectionProvider sectionProvider,
             @NonNull BypassController bypassController,
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 cfca830..6293640 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
@@ -499,7 +499,7 @@
 
         mGroupHeaderWrapper.setExpanded(mChildrenExpanded);
         mGroupHeaderWrapper.onContentUpdated(mContainingNotification);
-
+        resetHeaderVisibilityIfNeeded(mGroupHeader, calculateDesiredHeader());
         updateHeaderVisibility(false /* animate */);
         updateChildrenAppearance();
 
@@ -535,6 +535,7 @@
         invalidate();
 
         mMinimizedGroupHeaderWrapper.onContentUpdated(mContainingNotification);
+        resetHeaderVisibilityIfNeeded(mMinimizedGroupHeader, calculateDesiredHeader());
         updateHeaderVisibility(false /* animate */);
         updateChildrenAppearance();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index 31e4d2c..043d64e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -21,7 +21,6 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
 import com.android.systemui.shade.ShadeDisplayAware
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
 import com.android.systemui.statusbar.notification.SourceType
 import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
 import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
@@ -36,6 +35,7 @@
 import com.android.systemui.statusbar.notification.dagger.SocialHeader
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.stack.PriorityBucket
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.foldToSparseArray
@@ -51,7 +51,6 @@
 internal constructor(
     @ShadeDisplayAware private val configurationController: ConfigurationController,
     private val keyguardMediaController: KeyguardMediaController,
-    private val sectionsFeatureManager: NotificationSectionsFeatureManager,
     private val mediaContainerController: MediaContainerController,
     private val notificationRoundnessManager: NotificationRoundnessManager,
     @IncomingHeader private val incomingHeaderController: SectionHeaderController,
@@ -120,8 +119,8 @@
     }
 
     fun createSectionsForBuckets(): Array<NotificationSection> =
-        sectionsFeatureManager
-            .getNotificationBuckets()
+        PriorityBucket
+            .getAllInOrder()
             .map { NotificationSection(it) }
             .toTypedArray()
 
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 bddf6df..c7b3fd7 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
@@ -99,7 +99,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
 import com.android.systemui.statusbar.notification.FakeShadowView;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
 import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -118,7 +118,6 @@
 import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
 import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
 import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent;
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds;
@@ -127,7 +126,7 @@
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.HeadsUpUtil;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.util.Assert;
@@ -3459,11 +3458,8 @@
             }
             AnimationEvent event = new AnimationEvent(row, type);
             event.headsUpFromBottom = onBottom;
-            if (NotificationsImprovedHunAnimation.isEnabled()) {
-                // TODO(b/283084712) remove this with the flag and update the HUN filters at
-                //  creation
-                event.filter.animateHeight = false;
-            }
+            // TODO(b/283084712) remove this and update the HUN filters at creation
+            event.filter.animateHeight = false;
             mAnimationEvents.add(event);
             if (SPEW) {
                 Log.v(TAG, "Generating HUN animation event: "
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 3d7501d..ba707a5 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
@@ -86,6 +86,7 @@
 import com.android.systemui.scene.ui.view.WindowRootView;
 import com.android.systemui.shade.QSHeaderBoundsProvider;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -98,9 +99,9 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpNotificationViewControllerEmptyImpl;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper.HeadsUpNotificationViewController;
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -138,8 +139,8 @@
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -734,7 +735,7 @@
             TunerService tunerService,
             DeviceProvisionedController deviceProvisionedController,
             DynamicPrivacyController dynamicPrivacyController,
-            ConfigurationController configurationController,
+            @ShadeDisplayAware ConfigurationController configurationController,
             SysuiStatusBarStateController statusBarStateController,
             KeyguardMediaController keyguardMediaController,
             KeyguardBypassController keyguardBypassController,
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 5dff812..a96d972 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
@@ -22,9 +22,9 @@
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState.KEYGUARD
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -60,7 +60,7 @@
     private val statusBarStateController: SysuiStatusBarStateController,
     private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
     private val mediaDataManager: MediaDataManager,
-    @Main private val resources: Resources,
+    @ShadeDisplayAware private val resources: Resources,
     private val splitShadeStateController: SplitShadeStateController,
     private val seenNotificationsInteractor: SeenNotificationsInteractor,
     @Application private val scope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 0e94ca35..5045744 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -40,6 +40,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -560,7 +561,7 @@
         private NotificationRoundnessManager mNotificationRoundnessManager;
 
         @Inject
-        Builder(@Main Resources resources, ViewConfiguration viewConfiguration,
+        Builder(@ShadeDisplayAware Resources resources, ViewConfiguration viewConfiguration,
                 DumpManager dumpManager,
                 FalsingManager falsingManager, FeatureFlags featureFlags,
                 NotificationRoundnessManager notificationRoundnessManager) {
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 b251b07..1653029 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
@@ -41,7 +41,6 @@
 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;
 import java.util.List;
@@ -1065,8 +1064,7 @@
                             headsUpTranslation);
                     childState.setYTranslation(inSpaceTranslation + extraTranslation);
                     cyclingInHunHeight = -1;
-                } else
-                if (NotificationsImprovedHunAnimation.isEnabled() && !ambientState.isDozing()) {
+                } else if (!ambientState.isDozing()) {
                     if (shouldHunAppearFromBottom(ambientState, childState)) {
                         // move to the bottom of the screen
                         childState.setYTranslation(
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 058233f..4686bef 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
@@ -37,8 +37,6 @@
 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.NotificationsImprovedHunAnimation;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -179,10 +177,7 @@
         mHeadsUpDisappearChildren.clear();
         mNewEvents.clear();
         mNewAddChildren.clear();
-        if (NotificationsImprovedHunAnimation.isEnabled()
-                || NotificationHeadsUpCycling.isEnabled()) {
-            mAnimationProperties.resetCustomInterpolators();
-        }
+        mAnimationProperties.resetCustomInterpolators();
     }
 
     private void initAnimationProperties(ExpandableView child,
@@ -498,8 +493,7 @@
                 }
                 changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_CYCLING,
                         /* isHeadsUpAppear= */ true, onAnimationEnd);
-            } else if (NotificationsImprovedHunAnimation.isEnabled()
-                    && (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR)) {
+            } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) {
                 mHeadsUpAppearChildren.add(changingView);
 
                 mTmpState.copyFrom(changingView.getViewState());
@@ -602,30 +596,6 @@
                     endRunnable.run();
                 }
                 needsCustomAnimation |= needsAnimation;
-            } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) {
-                NotificationsImprovedHunAnimation.assertInLegacyMode();
-                // This item is added, initialize its properties.
-                ExpandableViewState viewState = changingView.getViewState();
-                mTmpState.copyFrom(viewState);
-                if (event.headsUpFromBottom) {
-                    mTmpState.setYTranslation(mHeadsUpAppearHeightBottom);
-                } else {
-                    Runnable onAnimationEnd = null;
-                    if (loggable) {
-                        String finalKey = key;
-                        onAnimationEnd = () -> mLogger.appearAnimationEnded(finalKey);
-                    }
-                    changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
-                            true /* isHeadsUpAppear */, onAnimationEnd);
-                }
-                mHeadsUpAppearChildren.add(changingView);
-                // this only captures HEADS_UP_APPEAR animations, but HUNs can appear with normal
-                // ADD animations, which would not be logged here.
-                if (loggable) {
-                    mLogger.logHUNViewAppearing(key);
-                }
-
-                mTmpState.applyToView(changingView);
             } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR
                     || event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
                 mHeadsUpDisappearChildren.add(changingView);
@@ -636,14 +606,10 @@
                     // transiently
                     mHostLayout.addTransientView(changingView, 0);
                     changingView.setTransientContainer(mHostLayout);
-                    if (NotificationsImprovedHunAnimation.isEnabled()) {
-                        // 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(
-                                getHeadsUpYTranslationStart(event.headsUpFromBottom)
-                        );
-                    }
+                    // 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(getHeadsUpYTranslationStart(event.headsUpFromBottom));
                     endRunnable = changingView::removeFromTransientContainer;
                 }
 
@@ -697,14 +663,12 @@
                             startAnimation, postAnimation,
                             getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.BOTTOM);
                     mAnimationProperties.delay += removeAnimationDelay;
-                    if (NotificationsImprovedHunAnimation.isEnabled()) {
-                        mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
-                        mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
-                                Interpolators.FAST_OUT_SLOW_IN_REVERSE);
-                        mAnimationProperties.getAnimationFilter().animateY = true;
-                        mTmpState.animateTo(changingView, mAnimationProperties);
-                        mAnimationProperties.resetCustomInterpolators();
-                    }
+                    mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
+                    mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+                            Interpolators.FAST_OUT_SLOW_IN_REVERSE);
+                    mAnimationProperties.getAnimationFilter().animateY = true;
+                    mTmpState.animateTo(changingView, mAnimationProperties);
+                    mAnimationProperties.resetCustomInterpolators();
                 } else if (endRunnable != null) {
                     endRunnable.run();
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
index ef14557..b2ffa4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
@@ -33,7 +33,7 @@
 import com.android.systemui.statusbar.notification.NotificationFadeAware.FadeOptimizedNotification;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.policy.HeadsUpUtil;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
 
 import java.io.PrintWriter;
 import java.lang.reflect.Field;
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 a55a165..f8f29ff 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
@@ -43,6 +43,7 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
@@ -116,7 +117,7 @@
     private val interactor: SharedNotificationContainerInteractor,
     dumpManager: DumpManager,
     @Application applicationScope: CoroutineScope,
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
@@ -124,6 +125,8 @@
     private val notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
     private val alternateBouncerToGoneTransitionViewModel:
         AlternateBouncerToGoneTransitionViewModel,
+    private val alternateBouncerToPrimaryBouncerTransitionViewModel:
+        AlternateBouncerToPrimaryBouncerTransitionViewModel,
     private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
     private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
     private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
@@ -560,6 +563,7 @@
             lockscreenToGoneTransitionViewModel.notificationAlpha(viewState),
             lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
             lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
+            alternateBouncerToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
             occludedToAodTransitionViewModel.lockscreenAlpha,
             occludedToGoneTransitionViewModel.notificationAlpha(viewState),
             occludedToLockscreenTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
deleted file mode 100644
index 1358cfd..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.IWindowManager;
-import android.view.MotionEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.AutoHideUiElement;
-
-import java.io.PrintWriter;
-
-import javax.inject.Inject;
-
-/** A controller to control all auto-hide things. Also see {@link AutoHideUiElement}. */
-@SysUISingleton
-public class AutoHideController {
-    private static final String TAG = "AutoHideController";
-    private static final int AUTO_HIDE_TIMEOUT_MS = 2250;
-    private static final int USER_AUTO_HIDE_TIMEOUT_MS = 350;
-
-    private final AccessibilityManager mAccessibilityManager;
-    private final IWindowManager mWindowManagerService;
-    private final Handler mHandler;
-
-    private AutoHideUiElement mStatusBar;
-    /** For tablets, this will represent the Taskbar */
-    private AutoHideUiElement mNavigationBar;
-    private int mDisplayId;
-
-    private boolean mAutoHideSuspended;
-
-    private final Runnable mAutoHide = () -> {
-        if (isAnyTransientBarShown()) {
-            hideTransientBars();
-        }
-    };
-
-    @Inject
-    public AutoHideController(Context context,
-            @Main Handler handler,
-            IWindowManager iWindowManager) {
-        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
-        mHandler = handler;
-        mWindowManagerService = iWindowManager;
-        mDisplayId = context.getDisplayId();
-    }
-
-    /**
-     * Sets a {@link AutoHideUiElement} status bar that should be controlled by the
-     * {@link AutoHideController}.
-     */
-    public void setStatusBar(AutoHideUiElement element) {
-        mStatusBar = element;
-    }
-
-    /**
-     * Sets a {@link AutoHideUiElement} navigation bar that should be controlled by the
-     * {@link AutoHideController}.
-     */
-    public void setNavigationBar(AutoHideUiElement element) {
-        mNavigationBar = element;
-    }
-
-    private void hideTransientBars() {
-        try {
-            mWindowManagerService.hideTransientBars(mDisplayId);
-        } catch (RemoteException ex) {
-            Log.w(TAG, "Cannot get WindowManager");
-        }
-
-        if (mStatusBar != null) {
-            mStatusBar.hide();
-        }
-
-        if (mNavigationBar != null) {
-            mNavigationBar.hide();
-        }
-    }
-
-    public void resumeSuspendedAutoHide() {
-        if (mAutoHideSuspended) {
-            scheduleAutoHide();
-            Runnable checkBarModesRunnable = getCheckBarModesRunnable();
-            if (checkBarModesRunnable != null) {
-                mHandler.postDelayed(checkBarModesRunnable, 500); // longer than home -> launcher
-            }
-        }
-    }
-
-    public void suspendAutoHide() {
-        mHandler.removeCallbacks(mAutoHide);
-        Runnable checkBarModesRunnable = getCheckBarModesRunnable();
-        if (checkBarModesRunnable != null) {
-            mHandler.removeCallbacks(checkBarModesRunnable);
-        }
-        mAutoHideSuspended = isAnyTransientBarShown();
-    }
-
-    /** Schedules or cancels auto hide behavior based on current system bar state. */
-    public void touchAutoHide() {
-        // update transient bar auto hide
-        if (isAnyTransientBarShown()) {
-            scheduleAutoHide();
-        } else {
-            cancelAutoHide();
-        }
-    }
-
-    private Runnable getCheckBarModesRunnable() {
-        if (mStatusBar != null) {
-            return () -> mStatusBar.synchronizeState();
-        } else if (mNavigationBar != null) {
-            return () -> mNavigationBar.synchronizeState();
-        } else {
-            return null;
-        }
-    }
-
-    private void cancelAutoHide() {
-        mAutoHideSuspended = false;
-        mHandler.removeCallbacks(mAutoHide);
-    }
-
-    private void scheduleAutoHide() {
-        cancelAutoHide();
-        mHandler.postDelayed(mAutoHide, getAutoHideTimeout());
-    }
-
-    private int getAutoHideTimeout() {
-        return mAccessibilityManager.getRecommendedTimeoutMillis(AUTO_HIDE_TIMEOUT_MS,
-                FLAG_CONTENT_CONTROLS);
-    }
-
-    public void checkUserAutoHide(MotionEvent event) {
-        boolean shouldHide = isAnyTransientBarShown()
-                && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar.
-                && event.getX() == 0 && event.getY() == 0;
-
-        if (mStatusBar != null) {
-            shouldHide &= mStatusBar.shouldHideOnTouch();
-        }
-        if (mNavigationBar != null) {
-            shouldHide &= mNavigationBar.shouldHideOnTouch();
-        }
-
-        if (shouldHide) {
-            userAutoHide();
-        }
-    }
-
-    private void userAutoHide() {
-        cancelAutoHide();
-        // longer than app gesture -> flag clear
-        mHandler.postDelayed(mAutoHide, getUserAutoHideTimeout());
-    }
-
-    private int getUserAutoHideTimeout() {
-        return mAccessibilityManager.getRecommendedTimeoutMillis(USER_AUTO_HIDE_TIMEOUT_MS,
-                FLAG_CONTENT_CONTROLS);
-    }
-
-    private boolean isAnyTransientBarShown() {
-        if (mStatusBar != null && mStatusBar.isVisible()) {
-            return true;
-        }
-
-        if (mNavigationBar != null && mNavigationBar.isVisible()) {
-            return true;
-        }
-
-        return false;
-    }
-
-    public void dump(@NonNull PrintWriter pw) {
-        pw.println("AutoHideController:");
-        pw.println("\tmAutoHideSuspended=" + mAutoHideSuspended);
-        pw.println("\tisAnyTransientBarShown=" + isAnyTransientBarShown());
-        pw.println("\thasPendingAutoHide=" + mHandler.hasCallbacks(mAutoHide));
-        pw.println("\tgetAutoHideTimeout=" + getAutoHideTimeout());
-        pw.println("\tgetUserAutoHideTimeout=" + getUserAutoHideTimeout());
-    }
-
-    /**
-     * Injectable factory for creating a {@link AutoHideController}.
-     */
-    public static class Factory {
-        private final Handler mHandler;
-        private final IWindowManager mIWindowManager;
-
-        @Inject
-        public Factory(@Main Handler handler, IWindowManager iWindowManager) {
-            mHandler = handler;
-            mIWindowManager = iWindowManager;
-        }
-
-        /** Create an {@link AutoHideController} */
-        public AutoHideController create(Context context) {
-            return new AutoHideController(context, mHandler, mIWindowManager);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.kt
new file mode 100644
index 0000000..636e1c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.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.statusbar.phone
+
+import android.content.Context
+import android.view.MotionEvent
+import com.android.systemui.statusbar.AutoHideUiElement
+import java.io.PrintWriter
+
+/**
+ * Controls the auto-hide behavior of system bars (status bar, navigation bar).
+ *
+ * This interface provides methods to manage the auto-hide schedule of system bars, allowing them to
+ * be shown or hidden.
+ */
+interface AutoHideController {
+    /**
+     * Sets a [AutoHideUiElement] status bar that should be controlled by the [AutoHideController].
+     */
+    fun setStatusBar(element: AutoHideUiElement)
+
+    /**
+     * Sets a [AutoHideUiElement] navigation bar that should be controlled by the
+     * [AutoHideController].
+     */
+    fun setNavigationBar(element: AutoHideUiElement)
+
+    /** Resumes the auto-hide behavior that was previously suspended. */
+    fun resumeSuspendedAutoHide()
+
+    /** Suspends the auto-hide behavior. */
+    fun suspendAutoHide()
+
+    /** Schedules or cancels auto hide behavior based on current system bar state. */
+    fun touchAutoHide()
+
+    /** Hides system bars on user touch if the interaction requires them to be hidden. */
+    fun checkUserAutoHide(event: MotionEvent)
+
+    /** Called when work should stop and resources should be released. */
+    fun stop()
+
+    /** Dumps the current state of the [AutoHideController] */
+    fun dump(pw: PrintWriter)
+
+    /** Injectable factory for creating a [AutoHideController]. */
+    interface Factory {
+        /** Create an [AutoHideController] */
+        fun create(context: Context): AutoHideController
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerImpl.java
new file mode 100644
index 0000000..4fbfbb2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerImpl.java
@@ -0,0 +1,224 @@
+/*
+ * 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 static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IWindowManager;
+import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.AutoHideUiElement;
+
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+
+public class AutoHideControllerImpl implements AutoHideController {
+    private static final String TAG = "AutoHideController";
+    private static final int AUTO_HIDE_TIMEOUT_MS = 2250;
+    private static final int USER_AUTO_HIDE_TIMEOUT_MS = 350;
+
+    private final AccessibilityManager mAccessibilityManager;
+    private final IWindowManager mWindowManagerService;
+    private final Handler mHandler;
+
+    private AutoHideUiElement mStatusBar;
+    /** For tablets, this will represent the Taskbar */
+    private AutoHideUiElement mNavigationBar;
+    private int mDisplayId;
+
+    private boolean mAutoHideSuspended;
+
+    private final Runnable mAutoHide = () -> {
+        if (isAnyTransientBarShown()) {
+            hideTransientBars();
+        }
+    };
+
+    @Inject
+    public AutoHideControllerImpl(Context context,
+            @Main Handler handler,
+            IWindowManager iWindowManager) {
+        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+        mHandler = handler;
+        mWindowManagerService = iWindowManager;
+        mDisplayId = context.getDisplayId();
+    }
+
+    @Override
+    public void setStatusBar(AutoHideUiElement element) {
+        mStatusBar = element;
+    }
+
+    @Override
+    public void setNavigationBar(AutoHideUiElement element) {
+        mNavigationBar = element;
+    }
+
+    private void hideTransientBars() {
+        try {
+            mWindowManagerService.hideTransientBars(mDisplayId);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Cannot get WindowManager");
+        }
+
+        if (mStatusBar != null) {
+            mStatusBar.hide();
+        }
+
+        if (mNavigationBar != null) {
+            mNavigationBar.hide();
+        }
+    }
+
+    @Override
+    public void resumeSuspendedAutoHide() {
+        if (mAutoHideSuspended) {
+            scheduleAutoHide();
+            Runnable checkBarModesRunnable = getCheckBarModesRunnable();
+            if (checkBarModesRunnable != null) {
+                mHandler.postDelayed(checkBarModesRunnable, 500); // longer than home -> launcher
+            }
+        }
+    }
+
+    @Override
+    public void suspendAutoHide() {
+        mHandler.removeCallbacks(mAutoHide);
+        Runnable checkBarModesRunnable = getCheckBarModesRunnable();
+        if (checkBarModesRunnable != null) {
+            mHandler.removeCallbacks(checkBarModesRunnable);
+        }
+        mAutoHideSuspended = isAnyTransientBarShown();
+    }
+
+    @Override
+    public void touchAutoHide() {
+        // update transient bar auto hide
+        if (isAnyTransientBarShown()) {
+            scheduleAutoHide();
+        } else {
+            cancelAutoHide();
+        }
+    }
+
+    private Runnable getCheckBarModesRunnable() {
+        if (mStatusBar != null) {
+            return () -> mStatusBar.synchronizeState();
+        } else if (mNavigationBar != null) {
+            return () -> mNavigationBar.synchronizeState();
+        } else {
+            return null;
+        }
+    }
+
+    private void cancelAutoHide() {
+        mAutoHideSuspended = false;
+        mHandler.removeCallbacks(mAutoHide);
+    }
+
+    private void scheduleAutoHide() {
+        cancelAutoHide();
+        mHandler.postDelayed(mAutoHide, getAutoHideTimeout());
+    }
+
+    private int getAutoHideTimeout() {
+        return mAccessibilityManager.getRecommendedTimeoutMillis(AUTO_HIDE_TIMEOUT_MS,
+                FLAG_CONTENT_CONTROLS);
+    }
+
+    @Override
+    public void checkUserAutoHide(MotionEvent event) {
+        boolean shouldHide = isAnyTransientBarShown()
+                && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar.
+                && event.getX() == 0 && event.getY() == 0;
+
+        if (mStatusBar != null) {
+            shouldHide &= mStatusBar.shouldHideOnTouch();
+        }
+        if (mNavigationBar != null) {
+            shouldHide &= mNavigationBar.shouldHideOnTouch();
+        }
+
+        if (shouldHide) {
+            userAutoHide();
+        }
+    }
+
+    private void userAutoHide() {
+        cancelAutoHide();
+        // longer than app gesture -> flag clear
+        mHandler.postDelayed(mAutoHide, getUserAutoHideTimeout());
+    }
+
+    private int getUserAutoHideTimeout() {
+        return mAccessibilityManager.getRecommendedTimeoutMillis(USER_AUTO_HIDE_TIMEOUT_MS,
+                FLAG_CONTENT_CONTROLS);
+    }
+
+    private boolean isAnyTransientBarShown() {
+        if (mStatusBar != null && mStatusBar.isVisible()) {
+            return true;
+        }
+
+        if (mNavigationBar != null && mNavigationBar.isVisible()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void stop() {
+        mHandler.removeCallbacks(mAutoHide);
+    }
+
+    @Override
+    public void dump(@NonNull PrintWriter pw) {
+        pw.println("AutoHideController:");
+        pw.println("\tmAutoHideSuspended=" + mAutoHideSuspended);
+        pw.println("\tisAnyTransientBarShown=" + isAnyTransientBarShown());
+        pw.println("\thasPendingAutoHide=" + mHandler.hasCallbacks(mAutoHide));
+        pw.println("\tgetAutoHideTimeout=" + getAutoHideTimeout());
+        pw.println("\tgetUserAutoHideTimeout=" + getUserAutoHideTimeout());
+    }
+
+    public static class Factory implements AutoHideController.Factory {
+        private final Handler mHandler;
+        private final IWindowManager mIWindowManager;
+
+        @Inject
+        public Factory(@Main Handler handler, IWindowManager iWindowManager) {
+            mHandler = handler;
+            mIWindowManager = iWindowManager;
+        }
+
+        /** Create an {@link AutoHideController} */
+        @Override
+        public AutoHideController create(Context context) {
+            return new AutoHideControllerImpl(context, mHandler, mIWindowManager);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt
new file mode 100644
index 0000000..744f969
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.view.WindowManager.LayoutParams.TYPE_STATUS_BAR
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.display.data.repository.SingleDisplayStore
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Provides per display instances of [AutoHideController] */
+interface AutoHideControllerStore : PerDisplayStore<AutoHideController>
+
+@SysUISingleton
+class MultiDisplayAutoHideControllerStore
+@Inject
+constructor(
+    @Background backgroundApplicationScope: CoroutineScope,
+    displayRepository: DisplayRepository,
+    private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+    private val autoHideControllerFactory: AutoHideControllerImpl.Factory,
+) :
+    AutoHideControllerStore,
+    PerDisplayStoreImpl<AutoHideController>(backgroundApplicationScope, displayRepository) {
+
+    init {
+        StatusBarConnectedDisplays.assertInNewMode()
+    }
+
+    override fun createInstanceForDisplay(displayId: Int): AutoHideController {
+        val displayWindowProperties =
+            displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR)
+        return autoHideControllerFactory.create(displayWindowProperties.context)
+    }
+
+    override suspend fun onDisplayRemovalAction(instance: AutoHideController) {
+        instance.stop()
+    }
+
+    override val instanceClass = AutoHideController::class.java
+}
+
+@SysUISingleton
+class SingleDisplayAutoHideControllerStore
+@Inject
+constructor(defaultController: AutoHideController) :
+    AutoHideControllerStore,
+    PerDisplayStore<AutoHideController> by SingleDisplayStore(defaultController) {
+
+    init {
+        StatusBarConnectedDisplays.assertInLegacyMode()
+    }
+}
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 5209d0f..7f95fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -66,7 +66,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
 
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 db29493..c6af328 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -221,7 +221,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
@@ -401,7 +401,7 @@
     private final KeyguardBypassController mKeyguardBypassController;
     private final KeyguardStateController mKeyguardStateController;
     private final HeadsUpManager mHeadsUpManager;
-    private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+    private final ShadeTouchableRegionManager mShadeTouchableRegionManager;
     private final FalsingCollector mFalsingCollector;
     private final FalsingManager mFalsingManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -681,7 +681,7 @@
             KeyguardIndicationController keyguardIndicationController,
             DemoModeController demoModeController,
             Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
-            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+            ShadeTouchableRegionManager shadeTouchableRegionManager,
             BrightnessSliderController.Factory brightnessSliderFactory,
             ScreenOffAnimationController screenOffAnimationController,
             WallpaperController wallpaperController,
@@ -724,7 +724,7 @@
         mHeadsUpManager = headsUpManager;
         mBackActionInteractor = backActionInteractor;
         mKeyguardIndicationController = keyguardIndicationController;
-        mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
+        mShadeTouchableRegionManager = shadeTouchableRegionManager;
         mFalsingCollector = falsingCollector;
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -1232,7 +1232,7 @@
             mStatusBarInitializer.initializeStatusBar();
         }
 
-        mStatusBarTouchableRegionManager.setup(getNotificationShadeWindowView());
+        mShadeTouchableRegionManager.setup(getNotificationShadeWindowView());
 
         if (!StatusBarConnectedDisplays.isEnabled()) {
             createNavigationBar(result);
@@ -1856,10 +1856,10 @@
             pw.println("  mHeadsUpManager: null");
         }
 
-        if (mStatusBarTouchableRegionManager != null) {
-            mStatusBarTouchableRegionManager.dump(pw, args);
+        if (mShadeTouchableRegionManager != null) {
+            mShadeTouchableRegionManager.dump(pw, args);
         } else {
-            pw.println("  mStatusBarTouchableRegionManager: null");
+            pw.println("  mShadeTouchableRegionManager: null");
         }
 
         if (mLightBarController != null) {
@@ -2566,7 +2566,7 @@
             dismissVolumeDialog();
             mWakeUpCoordinator.setFullyAwake(false);
             mKeyguardBypassController.onStartedGoingToSleep();
-            mStatusBarTouchableRegionManager.updateTouchableRegion();
+            mShadeTouchableRegionManager.updateTouchableRegion();
 
             // The unlocked screen off and fold to aod animations might use our LightRevealScrim -
             // we need to be expanded for it to be visible.
@@ -2655,7 +2655,7 @@
             // once we fully woke up.
             updateRevealEffect(true /* wakingUp */);
             updateNotificationPanelTouchState();
-            mStatusBarTouchableRegionManager.updateTouchableRegion();
+            mShadeTouchableRegionManager.updateTouchableRegion();
 
             // If we are waking up during the screen off animation, we should undo making the
             // expanded visible (we did that so the LightRevealScrim would be visible).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index ec92990..57e26d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -54,8 +54,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.CopyOnLoopListenerSet;
 import com.android.systemui.util.IListenerSet;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 8de03d8..6cad68f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -27,7 +27,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.dagger.qualifiers.DisplaySpecific;
-import com.android.systemui.flags.FeatureFlagsClassic;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
@@ -37,19 +36,21 @@
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.HeadsUpStatusBarView;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.core.StatusBarRootModernization;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarScope;
 import com.android.systemui.statusbar.policy.Clock;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.util.ViewController;
 
 import java.util.ArrayList;
@@ -97,7 +98,7 @@
     @VisibleForTesting
     float mAppearFraction;
     private ExpandableNotificationRow mTrackedChild;
-    private boolean mShown;
+    private PinnedStatus mPinnedStatus = PinnedStatus.NotPinned;
     private final ViewClippingUtil.ClippingParameters mParentClippingParams =
             new ViewClippingUtil.ClippingParameters() {
                 @Override
@@ -107,7 +108,6 @@
             };
     private boolean mAnimationsEnabled = true;
     private final KeyguardStateController mKeyguardStateController;
-    private final FeatureFlagsClassic mFeatureFlags;
     private final HeadsUpNotificationIconInteractor mHeadsUpNotificationIconInteractor;
 
     @VisibleForTesting
@@ -126,7 +126,6 @@
             NotificationRoundnessManager notificationRoundnessManager,
             HeadsUpStatusBarView headsUpStatusBarView,
             Clock clockView,
-            FeatureFlagsClassic featureFlags,
             HeadsUpNotificationIconInteractor headsUpNotificationIconInteractor,
             @Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
         super(headsUpStatusBarView);
@@ -144,7 +143,6 @@
 
         mStackScrollerController = stackScrollerController;
         mShadeViewController = shadeViewController;
-        mFeatureFlags = featureFlags;
         mHeadsUpNotificationIconInteractor = headsUpNotificationIconInteractor;
         mStackScrollerController.setHeadsUpAppearanceController(this);
         mClockView = clockView;
@@ -156,7 +154,7 @@
             public void onLayoutChange(View v, int left, int top, int right, int bottom,
                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
                 if (shouldBeVisible()) {
-                    updateTopEntry("onLayoutChange");
+                    updateTopEntry();
 
                     // trigger scroller to notify the latest panel translation
                     mStackScrollerController.requestLayout();
@@ -206,7 +204,7 @@
 
     @Override
     public void onHeadsUpPinned(NotificationEntry entry) {
-        updateTopEntry("onHeadsUpPinned");
+        updateTopEntry();
         updateHeader(entry);
         updateHeadsUpAndPulsingRoundness(entry);
     }
@@ -217,7 +215,7 @@
         mPhoneStatusBarTransitions.onHeadsUpStateChanged(isHeadsUp);
     }
 
-    private void updateTopEntry(String reason) {
+    private void updateTopEntry() {
         NotificationEntry newEntry = null;
         if (shouldBeVisible()) {
             newEntry = mHeadsUpManager.getTopEntry();
@@ -225,34 +223,40 @@
         NotificationEntry previousEntry = mView.getShowingEntry();
         mView.setEntry(newEntry);
         if (newEntry != previousEntry) {
-            boolean animateIsolation = false;
             if (newEntry == null) {
                 // no heads up anymore, lets start the disappear animation
-
-                setShown(false);
-                animateIsolation = !isExpanded();
+                setPinnedStatus(PinnedStatus.NotPinned);
             } else if (previousEntry == null) {
                 // We now have a headsUp and didn't have one before. Let's start the disappear
                 // animation
-                setShown(true);
-                animateIsolation = !isExpanded();
+                setPinnedStatus(PinnedStatus.PinnedBySystem);
             }
-            mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(
-                    newEntry == null ? null : newEntry.getRepresentativeEntry().getKey());
+
+            String isolatedIconKey;
+            if (newEntry != null) {
+                isolatedIconKey = newEntry.getRepresentativeEntry().getKey();
+            } else {
+                isolatedIconKey = null;
+            }
+            mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(isolatedIconKey);
         }
     }
 
-    private void setShown(boolean isShown) {
-        if (mShown != isShown) {
-            mShown = isShown;
-            if (isShown) {
+    private void setPinnedStatus(PinnedStatus pinnedStatus) {
+        if (mPinnedStatus != pinnedStatus) {
+            mPinnedStatus = pinnedStatus;
+            if (pinnedStatus.isPinned()) {
                 updateParentClipping(false /* shouldClip */);
                 mView.setVisibility(View.VISIBLE);
                 show(mView);
-                hide(mClockView, View.INVISIBLE);
+                if (!StatusBarRootModernization.isEnabled()) {
+                    hide(mClockView, View.INVISIBLE);
+                }
                 mOperatorNameViewOptional.ifPresent(view -> hide(view, View.INVISIBLE));
             } else {
-                show(mClockView);
+                if (!StatusBarRootModernization.isEnabled()) {
+                    show(mClockView);
+                }
                 mOperatorNameViewOptional.ifPresent(this::show);
                 hide(mView, View.GONE, () -> {
                     updateParentClipping(true /* shouldClip */);
@@ -325,8 +329,8 @@
     }
 
     @VisibleForTesting
-    public boolean isShown() {
-        return mShown;
+    public PinnedStatus getPinnedStatus() {
+        return mPinnedStatus;
     }
 
     /**
@@ -350,7 +354,7 @@
 
     @Override
     public void onHeadsUpUnPinned(NotificationEntry entry) {
-        updateTopEntry("onHeadsUpUnPinned");
+        updateTopEntry();
         updateHeader(entry);
         updateHeadsUpAndPulsingRoundness(entry);
     }
@@ -368,7 +372,7 @@
             updateHeadsUpHeaders();
         }
         if (isExpanded() != oldIsExpanded) {
-            updateTopEntry("setAppearFraction");
+            updateTopEntry();
         }
     }
 
@@ -442,11 +446,11 @@
     }
 
     public void onStateChanged() {
-        updateTopEntry("onStateChanged");
+        updateTopEntry();
     }
 
     @Override
     public void onFullyHiddenChanged(boolean isFullyHidden) {
-        updateTopEntry("onFullyHiddenChanged");
+        updateTopEntry();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt
deleted file mode 100644
index 83551e9..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt
+++ /dev/null
@@ -1,28 +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.statusbar.phone
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.policy.BaseHeadsUpManager
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import dagger.Binds
-import dagger.Module
-
-@Module
-interface HeadsUpModule {
-    @Binds @SysUISingleton fun bindsHeadsUpManager(hum: BaseHeadsUpManager): HeadsUpManager
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
index a6374a6..cd9b9d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone
 
+import android.content.Context
 import android.view.WindowInsetsController
 import com.android.internal.colorextraction.ColorExtractor
 import com.android.internal.view.AppearanceRegion
@@ -64,4 +65,8 @@
         scrimBehindAlpha: Float,
         scrimInFrontColor: ColorExtractor.GradientColors,
     )
+
+    fun interface Factory {
+        fun create(context: Context): LightBarController
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
index ccb9a11..ea67f1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
@@ -22,6 +22,7 @@
 import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
 import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
 
+import android.content.Context;
 import android.graphics.Rect;
 import android.util.Log;
 import android.view.Display;
@@ -34,12 +35,15 @@
 
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.view.AppearanceRegion;
+import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.statusbar.data.model.StatusBarAppearance;
+import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore;
 import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.Compile;
 import com.android.systemui.util.kotlin.JavaAdapterKt;
@@ -55,6 +59,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
+import javax.inject.Inject;
+
 /**
  * Controls how light status bar flag applies to the icons.
  */
@@ -67,6 +73,7 @@
 
     private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
 
+    private final int mDisplayId;
     private final CoroutineScope mCoroutineScope;
     private final SysuiDarkIconDispatcher mStatusBarIconController;
     private final BatteryController mBatteryController;
@@ -140,6 +147,7 @@
             DumpManager dumpManager,
             @Main CoroutineContext mainContext,
             BiometricUnlockController biometricUnlockController) {
+        mDisplayId = displayId;
         mCoroutineScope = coroutineScope;
         mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
         mBatteryController = batteryController;
@@ -155,7 +163,12 @@
 
     @Override
     public void start() {
-        mDumpManager.registerCriticalDumpable(mDumpableName, this);
+        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+            // Can only register on default display, because NavigationBar creates its own instance
+            // as well as PerDisplayStore.
+            // TODO: b/380394368 - make sure there is only one instance per display.
+            mDumpManager.registerCriticalDumpable(mDumpableName, this);
+        }
         mBatteryController.addCallback(this);
         mNavigationMode = mNavModeController.addListener(mNavigationModeListener);
         JavaAdapterKt.collectFlow(
@@ -490,4 +503,40 @@
                 DarkIconDispatcher darkIconDispatcher,
                 StatusBarModePerDisplayRepository statusBarModePerDisplayRepository);
     }
+
+    public static class LegacyFactory implements LightBarController.Factory {
+
+        private final Factory mFactory;
+        private final CoroutineScope mApplicationScope;
+        private final DarkIconDispatcherStore mDarkIconDispatcherStore;
+        private final StatusBarModeRepositoryStore mStatusBarModeRepositoryStore;
+
+        @Inject
+        public LegacyFactory(
+                LightBarControllerImpl.Factory factory,
+                @Application CoroutineScope applicationScope,
+                DarkIconDispatcherStore darkIconDispatcherStore,
+                StatusBarModeRepositoryStore statusBarModeRepositoryStore) {
+            mFactory = factory;
+            mApplicationScope = applicationScope;
+            mDarkIconDispatcherStore = darkIconDispatcherStore;
+            mStatusBarModeRepositoryStore = statusBarModeRepositoryStore;
+        }
+
+        @NonNull
+        @Override
+        public LightBarController create(@NonNull Context context) {
+            // TODO: b/380394368 - Make sure correct per display instances are used.
+            LightBarControllerImpl lightBarController = mFactory.create(
+                    context.getDisplayId(),
+                    mApplicationScope,
+                    mDarkIconDispatcherStore.getDefaultDisplay(),
+                    mStatusBarModeRepositoryStore.getDefaultDisplay()
+            );
+            // Calling start() manually to keep the legacy behavior. Before, LightBarControllerImpl
+            // was doing work in the constructor, which moved to start().
+            lightBarController.start();
+            return lightBarController;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 16e023c..f19d707 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -39,7 +39,9 @@
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.StatusBarLongPressGestureDetector
+import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
 import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
 import com.android.systemui.statusbar.policy.Clock
@@ -52,6 +54,7 @@
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.systemui.util.view.ViewUtil
+import dagger.Lazy
 import java.util.Optional
 import javax.inject.Inject
 import javax.inject.Named
@@ -79,6 +82,7 @@
     private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
     private val darkIconDispatcher: DarkIconDispatcher,
     private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider,
+    private val lazyStatusBarShadeDisplayPolicy: Lazy<StatusBarTouchShadeDisplayPolicy>,
 ) : ViewController<PhoneStatusBarView>(view) {
 
     private lateinit var battery: BatteryMeterView
@@ -226,6 +230,9 @@
                 !upOrCancel || shadeController.isExpandedVisible,
             )
         }
+        if (ShadeWindowGoesAround.isEnabled && event.action == MotionEvent.ACTION_DOWN) {
+            lazyStatusBarShadeDisplayPolicy.get().onStatusBarTouched(context.displayId)
+        }
     }
 
     private fun addDarkReceivers() {
@@ -344,6 +351,7 @@
         private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
         @DisplaySpecific private val darkIconDispatcher: DarkIconDispatcher,
         private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
+        private val lazyStatusBarShadeDisplayPolicy: Lazy<StatusBarTouchShadeDisplayPolicy>,
     ) {
         fun create(view: PhoneStatusBarView): PhoneStatusBarViewController {
             val statusBarMoveFromCenterAnimationController =
@@ -371,6 +379,7 @@
                 statusOverlayHoverListenerFactory,
                 darkIconDispatcher,
                 statusBarContentInsetsProviderStore.defaultDisplay,
+                lazyStatusBarShadeDisplayPolicy,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManager.java
new file mode 100644
index 0000000..bea8397
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManager.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.Log;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
+import android.view.WindowInsets;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.compose.animation.scene.ObservableTransitionState;
+import com.android.internal.policy.SystemBarUtils;
+import com.android.systemui.Dumpable;
+import com.android.systemui.ScreenDecorations;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
+import com.android.systemui.dagger.SysUISingleton;
+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.shade.domain.interactor.ShadeInteractor;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
+import com.android.systemui.util.kotlin.JavaAdapter;
+
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+/**
+ * Manages what parts of the status bar are touchable. Clients are primarily UI that display in the
+ * status bar even though the UI doesn't look like part of the status bar. Currently this consists
+ * of HeadsUpNotifications.
+ */
+@SysUISingleton
+public final class ShadeTouchableRegionManager implements Dumpable {
+    private static final String TAG = "TouchableRegionManager";
+
+    private final Context mContext;
+    private final HeadsUpManager mHeadsUpManager;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+
+    private boolean mIsStatusBarExpanded = false;
+    // Whether the scene container has no UI to render, i.e. is in idle state on the Gone scene and
+    // without any overlays to display.
+    private boolean mIsSceneContainerUiEmpty = true;
+    private boolean mIsRemoteUserInteractionOngoing = false;
+    private boolean mShouldAdjustInsets = false;
+    private View mNotificationShadeWindowView;
+    private View mNotificationPanelView;
+    private boolean mForceCollapsedUntilLayout = false;
+    private Boolean mCommunalVisible = false;
+
+    private Region mTouchableRegion = new Region();
+    private int mDisplayCutoutTouchableRegionSize;
+    private int mStatusBarHeight;
+    private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+    private final AlternateBouncerInteractor mAlternateBouncerInteractor;
+
+    private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener;
+
+    @Inject
+    public ShadeTouchableRegionManager(
+            Context context,
+            NotificationShadeWindowController notificationShadeWindowController,
+            ConfigurationController configurationController,
+            HeadsUpManager headsUpManager,
+            ShadeInteractor shadeInteractor,
+            Provider<SceneInteractor> sceneInteractor,
+            JavaAdapter javaAdapter,
+            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+            PrimaryBouncerInteractor primaryBouncerInteractor,
+            AlternateBouncerInteractor alternateBouncerInteractor,
+            CommunalSceneInteractor communalSceneInteractor
+    ) {
+        mContext = context;
+        initResources();
+        configurationController.addCallback(new ConfigurationListener() {
+            @Override
+            public void onDensityOrFontScaleChanged() {
+                initResources();
+            }
+
+            @Override
+            public void onThemeChanged() {
+                initResources();
+            }
+        });
+
+        mHeadsUpManager = headsUpManager;
+        mHeadsUpManager.addListener(
+                new OnHeadsUpChangedListener() {
+                    @Override
+                    public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) {
+                        if (Log.isLoggable(TAG, Log.WARN)) {
+                            Log.w(TAG, "onHeadsUpPinnedModeChanged");
+                        }
+                        updateTouchableRegion();
+                    }
+                });
+        mHeadsUpManager.addHeadsUpPhoneListener(this::onHeadsUpAnimatingAwayStateChanged);
+
+        mNotificationShadeWindowController = notificationShadeWindowController;
+        mNotificationShadeWindowController.setForcePluginOpenListener((forceOpen) -> {
+            updateTouchableRegion();
+        });
+
+        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+
+        if (SceneContainerFlag.isEnabled()) {
+            javaAdapter.alwaysCollectFlow(
+                    sceneInteractor.get().getTransitionState(),
+                    this::onSceneContainerTransition);
+            javaAdapter.alwaysCollectFlow(
+                    sceneInteractor.get().isRemoteUserInteractionOngoing(),
+                    this::onRemoteUserInteractionOngoingChanged);
+        } else {
+            javaAdapter.alwaysCollectFlow(
+                    shadeInteractor.isAnyExpanded(),
+                    this::onShadeOrQsExpanded);
+            javaAdapter.alwaysCollectFlow(
+                    communalSceneInteractor.isCommunalVisible(),
+                    this::onCommunalVisible);
+        }
+
+        mPrimaryBouncerInteractor = primaryBouncerInteractor;
+        mAlternateBouncerInteractor = alternateBouncerInteractor;
+        mOnComputeInternalInsetsListener = this::onComputeInternalInsets;
+    }
+
+    protected void setup(@NonNull View notificationShadeWindowView) {
+        mNotificationShadeWindowView = notificationShadeWindowView;
+        mNotificationPanelView = mNotificationShadeWindowView.findViewById(R.id.notification_panel);
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String[] args) {
+        pw.println("ShadeTouchableRegionManager state:");
+        pw.print("  mTouchableRegion=");
+        pw.println(mTouchableRegion);
+    }
+
+    private void onShadeOrQsExpanded(Boolean isExpanded) {
+        if (isExpanded != mIsStatusBarExpanded) {
+            mIsStatusBarExpanded = isExpanded;
+            if (isExpanded) {
+                // make sure our state is sensible
+                mForceCollapsedUntilLayout = false;
+            }
+            updateTouchableRegion();
+        }
+    }
+
+    private void onSceneContainerTransition(ObservableTransitionState transitionState) {
+        boolean isSceneContainerUiEmpty = transitionState.isIdle(Scenes.Gone)
+                && ((ObservableTransitionState.Idle) transitionState).getCurrentOverlays()
+                .isEmpty();
+        if (isSceneContainerUiEmpty != mIsSceneContainerUiEmpty) {
+            mIsSceneContainerUiEmpty = isSceneContainerUiEmpty;
+            if (!isSceneContainerUiEmpty) {
+                // make sure our state is sensible
+                mForceCollapsedUntilLayout = false;
+            }
+            updateTouchableRegion();
+        }
+    }
+
+    private void onRemoteUserInteractionOngoingChanged(Boolean ongoing) {
+        if (ongoing != mIsRemoteUserInteractionOngoing) {
+            mIsRemoteUserInteractionOngoing = ongoing;
+            updateTouchableRegion();
+        }
+    }
+
+    private void onCommunalVisible(Boolean visible) {
+        mCommunalVisible = visible;
+    }
+
+    /**
+     * Calculates the touch region needed for heads up notifications, taking into consideration
+     * any existing display cutouts (notch)
+     * @return the heads up notification touch area
+     */
+    public Region calculateTouchableRegion() {
+        // Update touchable region for HeadsUp notifications
+        final Region headsUpTouchableRegion = mHeadsUpManager.getTouchableRegion();
+        if (headsUpTouchableRegion != null) {
+            mTouchableRegion.set(headsUpTouchableRegion);
+        } else {
+            // If there aren't any HUNs, update the touch region to the status bar
+            // width/height, potentially adjusting for a display cutout (notch)
+            mTouchableRegion.set(0, 0, mNotificationShadeWindowView.getWidth(),
+                    mStatusBarHeight);
+            updateRegionForNotch(mTouchableRegion);
+        }
+        return mTouchableRegion;
+    }
+
+    private void initResources() {
+        Resources resources = mContext.getResources();
+        mDisplayCutoutTouchableRegionSize = resources.getDimensionPixelSize(
+                com.android.internal.R.dimen.display_cutout_touchable_region_size);
+        mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
+    }
+
+    /**
+     * Set the touchable portion of the status bar based on what elements are visible.
+     */
+    public void updateTouchableRegion() {
+        boolean hasCutoutInset = (mNotificationShadeWindowView != null)
+                && (mNotificationShadeWindowView.getRootWindowInsets() != null)
+                && (mNotificationShadeWindowView.getRootWindowInsets().getDisplayCutout() != null);
+        boolean shouldObserve = mHeadsUpManager.hasPinnedHeadsUp()
+                        || mHeadsUpManager.isHeadsUpAnimatingAwayValue()
+                        || mForceCollapsedUntilLayout
+                        || hasCutoutInset
+                        || mNotificationShadeWindowController.getForcePluginOpen();
+        if (shouldObserve == mShouldAdjustInsets) {
+            return;
+        }
+
+        if (shouldObserve) {
+            mNotificationShadeWindowView.getViewTreeObserver()
+                    .addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
+            mNotificationShadeWindowView.requestLayout();
+        } else {
+            mNotificationShadeWindowView.getViewTreeObserver()
+                    .removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
+        }
+        mShouldAdjustInsets = shouldObserve;
+    }
+
+    /**
+     * Calls {@code updateTouchableRegion()} after a layout pass completes.
+     */
+    private void updateTouchableRegionAfterLayout() {
+        if (mNotificationPanelView != null) {
+            mForceCollapsedUntilLayout = true;
+            mNotificationPanelView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                    if (!mNotificationPanelView.isVisibleToUser()) {
+                        mNotificationPanelView.removeOnLayoutChangeListener(this);
+                        mForceCollapsedUntilLayout = false;
+                        updateTouchableRegion();
+                    }
+                }
+            });
+        }
+    }
+
+    public void updateRegionForNotch(Region touchableRegion) {
+        WindowInsets windowInsets = mNotificationShadeWindowView.getRootWindowInsets();
+        if (windowInsets == null) {
+            Log.w(TAG, "StatusBarWindowView is not attached.");
+            return;
+        }
+        DisplayCutout cutout = windowInsets.getDisplayCutout();
+        if (cutout == null) {
+            return;
+        }
+
+        // Expand touchable region such that we also catch touches that just start below the notch
+        // area.
+        Rect bounds = new Rect();
+        ScreenDecorations.DisplayCutoutView.boundsFromDirection(cutout, Gravity.TOP, bounds);
+        bounds.offset(0, mDisplayCutoutTouchableRegionSize);
+        touchableRegion.union(bounds);
+    }
+
+    /**
+     * Helper to let us know when calculating the region is not needed because we know the entire
+     * screen needs to be touchable.
+     */
+    @VisibleForTesting
+    boolean shouldMakeEntireScreenTouchable() {
+        // The touchable region is always the full area when expanded, whether we're showing the
+        // shade or the bouncer. It's also fully touchable when the screen off animation is playing
+        // since we don't want stray touches to go through the light reveal scrim to whatever is
+        // underneath.
+        return mIsStatusBarExpanded
+                || (SceneContainerFlag.isEnabled()
+                && (!mIsSceneContainerUiEmpty || mIsRemoteUserInteractionOngoing))
+                || mPrimaryBouncerInteractor.isShowing().getValue()
+                || mAlternateBouncerInteractor.isVisibleState()
+                // The glanceable hub is a full-screen UI within the notification shade window. When
+                // it's visible, the touchable region should be the full screen.
+                || mCommunalVisible
+                || mUnlockedScreenOffAnimationController.isAnimationPlaying();
+    }
+
+    private void onHeadsUpAnimatingAwayStateChanged(boolean headsUpAnimatingAway) {
+        if (!headsUpAnimatingAway) {
+            updateTouchableRegionAfterLayout();
+        } else {
+            updateTouchableRegion();
+        }
+    }
+
+    private void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
+        if (shouldMakeEntireScreenTouchable()) {
+            return;
+        }
+
+        // Update touch insets to include any area needed for touching features that live in
+        // the status bar (ie: heads up notifications)
+        info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+        info.touchableRegion.set(calculateTouchableRegion());
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
deleted file mode 100644
index 7145ffe..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import com.android.systemui.CoreStartable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
-
-import javax.inject.Inject;
-
-/**
- * Ties the status bar to {@link com.android.systemui.statusbar.policy.HeadsUpManager}.
- */
-@SysUISingleton
-public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener, CoreStartable {
-    private final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final StatusBarWindowControllerStore mStatusBarWindowControllerStore;
-    private final ShadeViewController mShadeViewController;
-    private final PanelExpansionInteractor mPanelExpansionInteractor;
-    private final NotificationStackScrollLayoutController mNsslController;
-    private final KeyguardBypassController mKeyguardBypassController;
-    private final HeadsUpManager mHeadsUpManager;
-    private final StatusBarStateController mStatusBarStateController;
-    private final NotificationRemoteInputManager mNotificationRemoteInputManager;
-
-    @Inject
-    StatusBarHeadsUpChangeListener(
-            NotificationShadeWindowController notificationShadeWindowController,
-            StatusBarWindowControllerStore statusBarWindowControllerStore,
-            ShadeViewController shadeViewController,
-            PanelExpansionInteractor panelExpansionInteractor,
-            NotificationStackScrollLayoutController nsslController,
-            KeyguardBypassController keyguardBypassController,
-            HeadsUpManager headsUpManager,
-            StatusBarStateController statusBarStateController,
-            NotificationRemoteInputManager notificationRemoteInputManager) {
-        mNotificationShadeWindowController = notificationShadeWindowController;
-        mStatusBarWindowControllerStore = statusBarWindowControllerStore;
-        mShadeViewController = shadeViewController;
-        mPanelExpansionInteractor = panelExpansionInteractor;
-        mNsslController = nsslController;
-        mKeyguardBypassController = keyguardBypassController;
-        mHeadsUpManager = headsUpManager;
-        mStatusBarStateController = statusBarStateController;
-        mNotificationRemoteInputManager = notificationRemoteInputManager;
-    }
-
-    @Override
-    public void start() {
-        mHeadsUpManager.addListener(this);
-    }
-
-    @Override
-    public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
-        if (inPinnedMode) {
-            mNotificationShadeWindowController.setHeadsUpShowing(true);
-            mStatusBarWindowControllerStore.getDefaultDisplay().setForceStatusBarVisible(true);
-            if (mPanelExpansionInteractor.isFullyCollapsed()) {
-                mShadeViewController.updateTouchableRegion();
-            }
-        } else {
-            boolean bypassKeyguard = mKeyguardBypassController.getBypassEnabled()
-                    && mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
-            if (!mPanelExpansionInteractor.isFullyCollapsed()
-                    || mPanelExpansionInteractor.isTracking()
-                    || bypassKeyguard) {
-                // We are currently tracking or is open and the shade doesn't need to
-                //be kept
-                // open artificially.
-                mNotificationShadeWindowController.setHeadsUpShowing(false);
-                if (bypassKeyguard) {
-                    mStatusBarWindowControllerStore
-                            .getDefaultDisplay()
-                            .setForceStatusBarVisible(false);
-                }
-            } else {
-                // we need to keep the panel open artificially, let's wait until the
-                //animation
-                // is finished.
-                setHeadsAnimatingAway(true);
-                mNsslController.runAfterAnimationFinished(() -> {
-                    if (!mHeadsUpManager.hasPinnedHeadsUp()) {
-                        mNotificationShadeWindowController.setHeadsUpShowing(false);
-                        setHeadsAnimatingAway(false);
-                    }
-                    mNotificationRemoteInputManager.onPanelCollapsed();
-                });
-            }
-        }
-    }
-
-    private void setHeadsAnimatingAway(boolean headsUpAnimatingAway) {
-        if (!SceneContainerFlag.isEnabled()) {
-            mHeadsUpManager.setHeadsUpAnimatingAway(headsUpAnimatingAway);
-        }
-    }
-}
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 aef26de..bd1360f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -818,7 +818,10 @@
      * dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
      */
     public void showPrimaryBouncer(boolean scrimmed) {
-        hideAlternateBouncer(false);
+        hideAlternateBouncer(
+                /* updateScrim= */ false,
+                // When the scene framework is on, don't ever clear the pending dismiss action from
+                /* clearDismissAction= */ !SceneContainerFlag.isEnabled());
         if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
             if (SceneContainerFlag.isEnabled()) {
                 mSceneInteractorLazy.get().changeScene(
@@ -1005,6 +1008,15 @@
 
     @Override
     public void hideAlternateBouncer(boolean updateScrim) {
+        hideAlternateBouncer(updateScrim, /* clearDismissAction= */ true);
+    }
+
+    @Override
+    public void hideAlternateBouncer(boolean updateScrim, boolean clearDismissAction) {
+        if (clearDismissAction) {
+            mKeyguardDismissActionInteractor.get().clearDismissAction();
+        }
+
         updateAlternateBouncerShowing(mAlternateBouncerInteractor.hide() && updateScrim);
     }
 
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 af98311..e33baf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -79,8 +79,8 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowDragController;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.HeadsUpUtil;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.wmshell.BubblesManager;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 79ea59c..b3cc047 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -69,7 +69,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.util.Set;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
deleted file mode 100644
index d2c2003..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.util.Log;
-import android.view.DisplayCutout;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
-import android.view.WindowInsets;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.compose.animation.scene.ObservableTransitionState;
-import com.android.internal.policy.SystemBarUtils;
-import com.android.systemui.Dumpable;
-import com.android.systemui.ScreenDecorations;
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
-import com.android.systemui.dagger.SysUISingleton;
-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.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.util.kotlin.JavaAdapter;
-
-import java.io.PrintWriter;
-
-import javax.inject.Inject;
-import javax.inject.Provider;
-
-/**
- * Manages what parts of the status bar are touchable. Clients are primarily UI that display in the
- * status bar even though the UI doesn't look like part of the status bar. Currently this consists
- * of HeadsUpNotifications.
- */
-@SysUISingleton
-public final class StatusBarTouchableRegionManager implements Dumpable {
-    private static final String TAG = "TouchableRegionManager";
-
-    private final Context mContext;
-    private final HeadsUpManager mHeadsUpManager;
-    private final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
-
-    private boolean mIsStatusBarExpanded = false;
-    // Whether the scene container has no UI to render, i.e. is in idle state on the Gone scene and
-    // without any overlays to display.
-    private boolean mIsSceneContainerUiEmpty = true;
-    private boolean mIsRemoteUserInteractionOngoing = false;
-    private boolean mShouldAdjustInsets = false;
-    private View mNotificationShadeWindowView;
-    private View mNotificationPanelView;
-    private boolean mForceCollapsedUntilLayout = false;
-    private Boolean mCommunalVisible = false;
-
-    private Region mTouchableRegion = new Region();
-    private int mDisplayCutoutTouchableRegionSize;
-    private int mStatusBarHeight;
-    private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
-    private final AlternateBouncerInteractor mAlternateBouncerInteractor;
-
-    private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener;
-
-    @Inject
-    public StatusBarTouchableRegionManager(
-            Context context,
-            NotificationShadeWindowController notificationShadeWindowController,
-            ConfigurationController configurationController,
-            HeadsUpManager headsUpManager,
-            ShadeInteractor shadeInteractor,
-            Provider<SceneInteractor> sceneInteractor,
-            JavaAdapter javaAdapter,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
-            PrimaryBouncerInteractor primaryBouncerInteractor,
-            AlternateBouncerInteractor alternateBouncerInteractor,
-            CommunalSceneInteractor communalSceneInteractor
-    ) {
-        mContext = context;
-        initResources();
-        configurationController.addCallback(new ConfigurationListener() {
-            @Override
-            public void onDensityOrFontScaleChanged() {
-                initResources();
-            }
-
-            @Override
-            public void onThemeChanged() {
-                initResources();
-            }
-        });
-
-        mHeadsUpManager = headsUpManager;
-        mHeadsUpManager.addListener(
-                new OnHeadsUpChangedListener() {
-                    @Override
-                    public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) {
-                        if (Log.isLoggable(TAG, Log.WARN)) {
-                            Log.w(TAG, "onHeadsUpPinnedModeChanged");
-                        }
-                        updateTouchableRegion();
-                    }
-                });
-        mHeadsUpManager.addHeadsUpPhoneListener(this::onHeadsUpAnimatingAwayStateChanged);
-
-        mNotificationShadeWindowController = notificationShadeWindowController;
-        mNotificationShadeWindowController.setForcePluginOpenListener((forceOpen) -> {
-            updateTouchableRegion();
-        });
-
-        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
-
-        if (SceneContainerFlag.isEnabled()) {
-            javaAdapter.alwaysCollectFlow(
-                    sceneInteractor.get().getTransitionState(),
-                    this::onSceneContainerTransition);
-            javaAdapter.alwaysCollectFlow(
-                    sceneInteractor.get().isRemoteUserInteractionOngoing(),
-                    this::onRemoteUserInteractionOngoingChanged);
-        } else {
-            javaAdapter.alwaysCollectFlow(
-                    shadeInteractor.isAnyExpanded(),
-                    this::onShadeOrQsExpanded);
-            javaAdapter.alwaysCollectFlow(
-                    communalSceneInteractor.isCommunalVisible(),
-                    this::onCommunalVisible);
-        }
-
-        mPrimaryBouncerInteractor = primaryBouncerInteractor;
-        mAlternateBouncerInteractor = alternateBouncerInteractor;
-        mOnComputeInternalInsetsListener = this::onComputeInternalInsets;
-    }
-
-    protected void setup(@NonNull View notificationShadeWindowView) {
-        mNotificationShadeWindowView = notificationShadeWindowView;
-        mNotificationPanelView = mNotificationShadeWindowView.findViewById(R.id.notification_panel);
-    }
-
-    @Override
-    public void dump(PrintWriter pw, String[] args) {
-        pw.println("StatusBarTouchableRegionManager state:");
-        pw.print("  mTouchableRegion=");
-        pw.println(mTouchableRegion);
-    }
-
-    private void onShadeOrQsExpanded(Boolean isExpanded) {
-        if (isExpanded != mIsStatusBarExpanded) {
-            mIsStatusBarExpanded = isExpanded;
-            if (isExpanded) {
-                // make sure our state is sensible
-                mForceCollapsedUntilLayout = false;
-            }
-            updateTouchableRegion();
-        }
-    }
-
-    private void onSceneContainerTransition(ObservableTransitionState transitionState) {
-        boolean isSceneContainerUiEmpty = transitionState.isIdle(Scenes.Gone)
-                && ((ObservableTransitionState.Idle) transitionState).getCurrentOverlays()
-                .isEmpty();
-        if (isSceneContainerUiEmpty != mIsSceneContainerUiEmpty) {
-            mIsSceneContainerUiEmpty = isSceneContainerUiEmpty;
-            if (!isSceneContainerUiEmpty) {
-                // make sure our state is sensible
-                mForceCollapsedUntilLayout = false;
-            }
-            updateTouchableRegion();
-        }
-    }
-
-    private void onRemoteUserInteractionOngoingChanged(Boolean ongoing) {
-        if (ongoing != mIsRemoteUserInteractionOngoing) {
-            mIsRemoteUserInteractionOngoing = ongoing;
-            updateTouchableRegion();
-        }
-    }
-
-    private void onCommunalVisible(Boolean visible) {
-        mCommunalVisible = visible;
-    }
-
-    /**
-     * Calculates the touch region needed for heads up notifications, taking into consideration
-     * any existing display cutouts (notch)
-     * @return the heads up notification touch area
-     */
-    public Region calculateTouchableRegion() {
-        // Update touchable region for HeadsUp notifications
-        final Region headsUpTouchableRegion = mHeadsUpManager.getTouchableRegion();
-        if (headsUpTouchableRegion != null) {
-            mTouchableRegion.set(headsUpTouchableRegion);
-        } else {
-            // If there aren't any HUNs, update the touch region to the status bar
-            // width/height, potentially adjusting for a display cutout (notch)
-            mTouchableRegion.set(0, 0, mNotificationShadeWindowView.getWidth(),
-                    mStatusBarHeight);
-            updateRegionForNotch(mTouchableRegion);
-        }
-        return mTouchableRegion;
-    }
-
-    private void initResources() {
-        Resources resources = mContext.getResources();
-        mDisplayCutoutTouchableRegionSize = resources.getDimensionPixelSize(
-                com.android.internal.R.dimen.display_cutout_touchable_region_size);
-        mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
-    }
-
-    /**
-     * Set the touchable portion of the status bar based on what elements are visible.
-     */
-    public void updateTouchableRegion() {
-        boolean hasCutoutInset = (mNotificationShadeWindowView != null)
-                && (mNotificationShadeWindowView.getRootWindowInsets() != null)
-                && (mNotificationShadeWindowView.getRootWindowInsets().getDisplayCutout() != null);
-        boolean shouldObserve = mHeadsUpManager.hasPinnedHeadsUp()
-                        || mHeadsUpManager.isHeadsUpAnimatingAwayValue()
-                        || mForceCollapsedUntilLayout
-                        || hasCutoutInset
-                        || mNotificationShadeWindowController.getForcePluginOpen();
-        if (shouldObserve == mShouldAdjustInsets) {
-            return;
-        }
-
-        if (shouldObserve) {
-            mNotificationShadeWindowView.getViewTreeObserver()
-                    .addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
-            mNotificationShadeWindowView.requestLayout();
-        } else {
-            mNotificationShadeWindowView.getViewTreeObserver()
-                    .removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
-        }
-        mShouldAdjustInsets = shouldObserve;
-    }
-
-    /**
-     * Calls {@code updateTouchableRegion()} after a layout pass completes.
-     */
-    private void updateTouchableRegionAfterLayout() {
-        if (mNotificationPanelView != null) {
-            mForceCollapsedUntilLayout = true;
-            mNotificationPanelView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
-                @Override
-                public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
-                    if (!mNotificationPanelView.isVisibleToUser()) {
-                        mNotificationPanelView.removeOnLayoutChangeListener(this);
-                        mForceCollapsedUntilLayout = false;
-                        updateTouchableRegion();
-                    }
-                }
-            });
-        }
-    }
-
-    public void updateRegionForNotch(Region touchableRegion) {
-        WindowInsets windowInsets = mNotificationShadeWindowView.getRootWindowInsets();
-        if (windowInsets == null) {
-            Log.w(TAG, "StatusBarWindowView is not attached.");
-            return;
-        }
-        DisplayCutout cutout = windowInsets.getDisplayCutout();
-        if (cutout == null) {
-            return;
-        }
-
-        // Expand touchable region such that we also catch touches that just start below the notch
-        // area.
-        Rect bounds = new Rect();
-        ScreenDecorations.DisplayCutoutView.boundsFromDirection(cutout, Gravity.TOP, bounds);
-        bounds.offset(0, mDisplayCutoutTouchableRegionSize);
-        touchableRegion.union(bounds);
-    }
-
-    /**
-     * Helper to let us know when calculating the region is not needed because we know the entire
-     * screen needs to be touchable.
-     */
-    @VisibleForTesting
-    boolean shouldMakeEntireScreenTouchable() {
-        // The touchable region is always the full area when expanded, whether we're showing the
-        // shade or the bouncer. It's also fully touchable when the screen off animation is playing
-        // since we don't want stray touches to go through the light reveal scrim to whatever is
-        // underneath.
-        return mIsStatusBarExpanded
-                || (SceneContainerFlag.isEnabled()
-                && (!mIsSceneContainerUiEmpty || mIsRemoteUserInteractionOngoing))
-                || mPrimaryBouncerInteractor.isShowing().getValue()
-                || mAlternateBouncerInteractor.isVisibleState()
-                // The glanceable hub is a full-screen UI within the notification shade window. When
-                // it's visible, the touchable region should be the full screen.
-                || mCommunalVisible
-                || mUnlockedScreenOffAnimationController.isAnimationPlaying();
-    }
-
-    private void onHeadsUpAnimatingAwayStateChanged(boolean headsUpAnimatingAway) {
-        if (!headsUpAnimatingAway) {
-            updateTouchableRegionAfterLayout();
-        } else {
-            updateTouchableRegion();
-        }
-    }
-
-    private void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
-        if (shouldMakeEntireScreenTouchable()) {
-            return;
-        }
-
-        // Update touch insets to include any area needed for touching features that live in
-        // the status bar (ie: heads up notifications)
-        info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-        info.touchableRegion.set(calculateTouchableRegion());
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 58386b0..4f32aaa26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -35,7 +35,10 @@
 import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStoreModule
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import com.android.systemui.statusbar.events.PrivacyDotViewControllerModule
+import com.android.systemui.statusbar.phone.AutoHideControllerStore
 import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
+import com.android.systemui.statusbar.phone.MultiDisplayAutoHideControllerStore
+import com.android.systemui.statusbar.phone.SingleDisplayAutoHideControllerStore
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
 import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStoreImpl
@@ -120,6 +123,7 @@
             statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
             initializerStore: StatusBarInitializerStore,
             statusBarWindowControllerStore: StatusBarWindowControllerStore,
+            autoHideControllerStore: AutoHideControllerStore,
             statusBarOrchestratorFactory: StatusBarOrchestrator.Factory,
         ): StatusBarOrchestrator {
             return statusBarOrchestratorFactory.create(
@@ -129,6 +133,7 @@
                 statusBarModeRepositoryStore.defaultDisplay,
                 initializerStore.defaultDisplay,
                 statusBarWindowControllerStore.defaultDisplay,
+                autoHideControllerStore.defaultDisplay,
             )
         }
 
@@ -186,5 +191,32 @@
                 singleDisplayStoreLazy.get()
             }
         }
+
+        @Provides
+        @SysUISingleton
+        fun autoHideStore(
+            singleDisplayLazy: Lazy<SingleDisplayAutoHideControllerStore>,
+            multiDisplayLazy: Lazy<MultiDisplayAutoHideControllerStore>,
+        ): AutoHideControllerStore {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                multiDisplayLazy.get()
+            } else {
+                singleDisplayLazy.get()
+            }
+        }
+
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(AutoHideControllerStore::class)
+        fun storeAsCoreStartable(
+            multiDisplayLazy: Lazy<MultiDisplayAutoHideControllerStore>
+        ): CoreStartable {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                multiDisplayLazy.get()
+            } else {
+                CoreStartable.NOP
+            }
+        }
     }
 }
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 23b4b65..e4768e8 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
@@ -71,6 +71,7 @@
 import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent.Startable;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization;
 import com.android.systemui.statusbar.phone.ui.DarkIconManager;
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder;
@@ -504,12 +505,7 @@
                 notificationIconArea.requireViewById(R.id.notificationIcons);
         mNotificationIconAreaInner = notificationIcons;
         int displayId = mHomeStatusBarComponent.getDisplayId();
-        if (displayId == Display.DEFAULT_DISPLAY) {
-            //TODO(b/369337701): implement notification icons for all displays.
-            // Currently if we try to bind for all displays, there is a crash, because the same
-            // notification icon view can't have multiple parents.
-            mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons, displayId);
-        }
+        mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons, displayId);
 
         if (!StatusBarRootModernization.isEnabled()) {
             updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false);
@@ -965,7 +961,9 @@
             mOngoingCallController.addCallback(mOngoingCallListener);
         }
         // TODO(b/364653005): Do we also need to set the secondary activity chip?
-        mOngoingCallController.setChipView(mPrimaryOngoingActivityChip);
+        if (!StatusBarChipsModernization.isEnabled()) {
+            mOngoingCallController.setChipView(mPrimaryOngoingActivityChip);
+        }
     }
 
     @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 aac2cd1..2166304 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
@@ -25,6 +25,7 @@
 import android.content.Context
 import android.view.View
 import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.CoreStartable
 import com.android.systemui.Dumpable
@@ -58,9 +59,11 @@
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
 
-/** A controller to handle the ongoing call chip in the collapsed status bar. */
+/** A controller to handle the ongoing call chip in the collapsed status bar.
+ * @deprecated Use [OngoingCallInteractor] instead, which follows recommended architecture patterns
+ */
+@Deprecated("Use OngoingCallInteractor instead")
 @SysUISingleton
 class OngoingCallController
 @Inject
@@ -122,9 +125,9 @@
                             entry.sbn.uid,
                             entry.sbn.notification.extras.getInt(
                                 Notification.EXTRA_CALL_TYPE,
-                                -1
+                                -1,
                             ) == CALL_TYPE_ONGOING,
-                            statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
+                            statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false,
                         )
                     if (newOngoingCallInfo == callNotificationInfo) {
                         return
@@ -165,6 +168,9 @@
         }
 
     override fun start() {
+        if (StatusBarChipsModernization.isEnabled)
+            return
+
         dumpManager.registerDumpable(this)
 
         if (Flags.statusBarUseReposForCallChip()) {
@@ -201,6 +207,8 @@
      * [com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment].
      */
     fun setChipView(chipView: View) {
+        StatusBarChipsModernization.assertInLegacyMode()
+
         tearDownChipView()
         this.chipView = chipView
         val backgroundView: ChipBackgroundContainer? =
@@ -217,6 +225,8 @@
      * Returns true if there's an active ongoing call that should be displayed in a status bar chip.
      */
     fun hasOngoingCall(): Boolean {
+        StatusBarChipsModernization.assertInLegacyMode()
+
         return callNotificationInfo?.isOngoing == true &&
             // When the user is in the phone app, don't show the chip.
             !uidObserver.isCallAppVisible
@@ -224,6 +234,8 @@
 
     /** Creates the right [OngoingCallModel] based on the call state. */
     private fun getOngoingCallModel(): OngoingCallModel {
+        StatusBarChipsModernization.assertInLegacyMode()
+
         if (hasOngoingCall()) {
             val currentInfo =
                 callNotificationInfo
@@ -236,7 +248,7 @@
                     bool1 = Flags.statusBarCallChipNotificationIcon()
                     bool2 = currentInfo.notificationIconView != null
                 },
-                { "Creating OngoingCallModel.InCall. notifIconFlag=$bool1 hasIcon=$bool2" }
+                { "Creating OngoingCallModel.InCall. notifIconFlag=$bool1 hasIcon=$bool2" },
             )
             val icon =
                 if (Flags.statusBarCallChipNotificationIcon()) {
@@ -255,6 +267,8 @@
     }
 
     override fun addCallback(listener: OngoingCallListener) {
+        StatusBarChipsModernization.assertInLegacyMode()
+
         synchronized(mListeners) {
             if (!mListeners.contains(listener)) {
                 mListeners.add(listener)
@@ -263,10 +277,14 @@
     }
 
     override fun removeCallback(listener: OngoingCallListener) {
+        StatusBarChipsModernization.assertInLegacyMode()
+
         synchronized(mListeners) { mListeners.remove(listener) }
     }
 
     private fun updateInfoFromNotifModel(notifModel: ActiveNotificationModel?) {
+        StatusBarChipsModernization.assertInLegacyMode()
+
         if (notifModel == null) {
             logger.log(TAG, LogLevel.DEBUG, {}, { "NotifInteractorCallModel: null" })
             removeChip()
@@ -288,7 +306,7 @@
                     str1 = notifModel.callType.name
                     bool1 = notifModel.statusBarChipIconView != null
                 },
-                { "NotifInteractorCallModel: key=$str1 when=$long1 callType=$str2 hasIcon=$bool1" }
+                { "NotifInteractorCallModel: key=$str1 when=$long1 callType=$str2 hasIcon=$bool1" },
             )
 
             val newOngoingCallInfo =
@@ -299,7 +317,7 @@
                     notifModel.contentIntent,
                     notifModel.uid,
                     isOngoing = true,
-                    statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
+                    statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false,
                 )
             if (newOngoingCallInfo == callNotificationInfo) {
                 return
@@ -310,6 +328,8 @@
     }
 
     private fun updateChip() {
+        StatusBarChipsModernization.assertInLegacyMode()
+
         val currentCallNotificationInfo = callNotificationInfo ?: return
 
         val currentChipView = chipView
@@ -360,6 +380,8 @@
     }
 
     private fun updateChipClickListener() {
+        StatusBarChipsModernization.assertInLegacyMode()
+
         if (Flags.statusBarScreenSharingChips()) {
             return
         }
@@ -378,7 +400,7 @@
                     ActivityTransitionAnimator.Controller.fromView(
                         backgroundView,
                         InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
-                    )
+                    ),
                 )
             }
         }
@@ -386,10 +408,14 @@
 
     /** Returns true if the given [procState] represents a process that's visible to the user. */
     private fun isProcessVisibleToUser(procState: Int): Boolean {
+        StatusBarChipsModernization.assertInLegacyMode()
+
         return procState <= ActivityManager.PROCESS_STATE_TOP
     }
 
     private fun updateGestureListening() {
+        StatusBarChipsModernization.assertInLegacyMode()
+
         if (
             callNotificationInfo == null ||
                 callNotificationInfo?.statusBarSwipedAway == true ||
@@ -404,6 +430,8 @@
     }
 
     private fun removeChip() {
+        StatusBarChipsModernization.assertInLegacyMode()
+
         callNotificationInfo = null
         if (!Flags.statusBarScreenSharingChips()) {
             tearDownChipView()
@@ -432,6 +460,8 @@
      * detected.
      */
     private fun onSwipeAwayGestureDetected() {
+        StatusBarChipsModernization.assertInLegacyMode()
+
         logger.log(TAG, LogLevel.DEBUG, {}, { "Swipe away gesture detected" })
         callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
         statusBarWindowControllerStore.defaultDisplay.setOngoingProcessRequiresStatusBarVisible(
@@ -441,6 +471,8 @@
     }
 
     private fun sendStateChangeEvent() {
+        StatusBarChipsModernization.assertInLegacyMode()
+
         ongoingCallRepository.setOngoingCallState(getOngoingCallModel())
         mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
     }
@@ -455,7 +487,7 @@
         /** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
         val isOngoing: Boolean,
         /** True if the user has swiped away the status bar while in this phone call. */
-        val statusBarSwipedAway: Boolean
+        val statusBarSwipedAway: Boolean,
     ) {
         /**
          * Returns true if the notification information has a valid call start time. See
@@ -472,6 +504,9 @@
     /**
      * Observer to tell us when the app that posted the ongoing call notification is visible so that
      * we don't show the call chip at the same time (since the timers could be out-of-sync).
+     *
+     * For a more recommended architecture implementation, see
+     * [com.android.systemui.activity.data.repository.ActivityManagerRepository].
      */
     inner class CallAppUidObserver : UidObserver() {
         /** True if the application managing the call is visible to the user. */
@@ -512,7 +547,7 @@
                     uidObserver,
                     ActivityManager.UID_OBSERVER_PROCSTATE,
                     ActivityManager.PROCESS_STATE_UNKNOWN,
-                    context.opPackageName
+                    context.opPackageName,
                 )
                 isRegistered = true
             } catch (se: SecurityException) {
@@ -537,7 +572,7 @@
             uid: Int,
             procState: Int,
             procStateSeq: Long,
-            capability: Int
+            capability: Int,
         ) {
             val currentCallAppUid = callAppUid ?: return
             if (uid != currentCallAppUid) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
new file mode 100644
index 0000000..2ab2b68
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
@@ -0,0 +1,52 @@
+/*
+* Copyright (C) 2024 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.android.systemui.statusbar.phone.ongoingcall
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the status_bar_use_interactor_for_call_chip flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object StatusBarChipsModernization {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_STATUS_BAR_CHIPS_MODERNIZATION
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.statusBarChipsModernization() && Flags.statusBarRootModernization()
+
+    /**
+     * 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)
+}
\ No newline at end of file
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 f16371a..b932c71a 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
@@ -20,6 +20,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -33,7 +34,9 @@
  * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController] and
  * [com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore]. Instead, those two
  * classes both refer to this repository.
+ * @deprecated Use [OngoingCallInteractor] instead.
  */
+@Deprecated("Use OngoingCallInteractor instead")
 @SysUISingleton
 class OngoingCallRepository
 @Inject
@@ -49,6 +52,8 @@
      * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController].
      */
     fun setOngoingCallState(state: OngoingCallModel) {
+        StatusBarChipsModernization.assertInLegacyMode()
+
         logger.log(
             TAG,
             LogLevel.DEBUG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
new file mode 100644
index 0000000..1f7bd14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.ongoingcall.domain.interactor
+
+import com.android.systemui.activity.data.repository.ActivityManagerRepository
+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.Logger
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Interactor for determining whether to show a chip in the status bar for ongoing phone calls.
+ *
+ * This class monitors call notifications and the visibility of call apps to determine the appropriate
+ * chip state. It emits:
+ *  * - [OngoingCallModel.NoCall] when there is no call notification
+ *  * - [OngoingCallModel.InCallWithVisibleApp] when there is a call notification but the call app is visible
+ *  * - [OngoingCallModel.InCall] when there is a call notification and the call app is not visible
+ *  */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class OngoingCallInteractor @Inject constructor(
+    @Application private val scope: CoroutineScope,
+    activityManagerRepository: ActivityManagerRepository,
+    activeNotificationsInteractor: ActiveNotificationsInteractor,
+    @OngoingCallLog private val logBuffer: LogBuffer,
+) {
+    private val logger = Logger(logBuffer, TAG)
+
+    /**
+     * The current state of ongoing calls.
+     */
+    val ongoingCallState: StateFlow<OngoingCallModel> =
+        activeNotificationsInteractor.ongoingCallNotification
+            .flatMapLatest { notificationModel ->
+                when (notificationModel) {
+                    null -> {
+                        logger.d("No active call notification - hiding chip")
+                        flowOf(OngoingCallModel.NoCall)
+                    }
+
+                    else -> combine(
+                        flowOf(notificationModel),
+                        activityManagerRepository.createIsAppVisibleFlow(
+                            creationUid = notificationModel.uid,
+                            logger = logger,
+                            identifyingLogTag = TAG,
+                        ),
+                    ) { model, isVisible ->
+                        when {
+                            isVisible -> {
+                                logger.d({ "Call app is visible: uid=$int1" }) {
+                                    int1 = model.uid
+                                }
+                                OngoingCallModel.InCallWithVisibleApp
+                            }
+
+                            else -> {
+                                logger.d({ "Active call detected: startTime=$long1 hasIcon=$bool1" }) {
+                                    long1 = model.whenTime
+                                    bool1 = model.statusBarChipIconView != null
+                                }
+                                OngoingCallModel.InCall(
+                                    startTimeMs = model.whenTime,
+                                    notificationIconView = model.statusBarChipIconView,
+                                    intent = model.contentIntent,
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingCallModel.NoCall)
+
+    companion object {
+        private val TAG = "OngoingCall"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
index 34bff80..c2c91b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
@@ -25,6 +25,12 @@
     data object NoCall : OngoingCallModel
 
     /**
+     * There is an ongoing call but the call app is currently visible, so we don't need to show
+     * the chip.
+     */
+    data object InCallWithVisibleApp : OngoingCallModel
+
+    /**
      * There *is* an ongoing call.
      *
      * @property startTimeMs the time that the phone call started, based on the notification's
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt
index b2a0272..a0cb829 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt
@@ -30,13 +30,13 @@
 @SysUISingleton
 class CollapsedStatusBarInteractor
 @Inject
-constructor(DisableFlagsInteractor: DisableFlagsInteractor) {
+constructor(disableFlagsInteractor: DisableFlagsInteractor) {
     /**
      * The visibilities of various status bar child views, based only on the information we received
      * from disable flags.
      */
     val visibilityViaDisableFlags: Flow<StatusBarDisableFlagsVisibilityModel> =
-        DisableFlagsInteractor.disableFlags.map {
+        disableFlagsInteractor.disableFlags.map {
             StatusBarDisableFlagsVisibilityModel(
                 isClockAllowed = it.isClockEnabled,
                 areNotificationIconsAllowed = it.areNotificationIconsEnabled,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index 1faa9f3..812e0eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.pipeline.shared.ui.composable
 
-import android.view.Display
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -42,6 +41,7 @@
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.phone.StatusIconContainer
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
 import com.android.systemui.statusbar.phone.ui.DarkIconManager
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
@@ -151,12 +151,11 @@
                         )
                     iconController.addIconGroup(darkIconManager)
 
-                    // TODO(b/372657935): This won't be needed once OngoingCallController is
-                    // implemented in recommended architecture
-                    ongoingCallController.setChipView(
-                        phoneStatusBarView.requireViewById(R.id.ongoing_activity_chip_primary)
-                    )
-
+                    if (!StatusBarChipsModernization.isEnabled) {
+                        ongoingCallController.setChipView(
+                            phoneStatusBarView.requireViewById(R.id.ongoing_activity_chip_primary)
+                        )
+                    }
                     // For notifications, first inflate the [NotificationIconContainer]
                     val notificationIconArea =
                         phoneStatusBarView.requireViewById<ViewGroup>(R.id.notification_icon_area)
@@ -167,17 +166,11 @@
                             R.id.notificationIcons
                         )
 
-                    // TODO(b/369337701): implement notification icons for all displays.
-                    //  Currently if we try to bind for all displays, there is a crash, because the
-                    //  same notification icon view can't have multiple parents.
-                    val displayId = context.displayId
-                    if (displayId == Display.DEFAULT_DISPLAY) {
-                        scope.launch {
-                            notificationIconsBinder.bindWhileAttached(
-                                notificationIconContainer,
-                                displayId,
-                            )
-                        }
+                    scope.launch {
+                        notificationIconsBinder.bindWhileAttached(
+                            notificationIconContainer,
+                            context.displayId,
+                        )
                     }
 
                     // This binder handles everything else
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt
index ed0eb6d..d3e3711 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.shared.ui.model
 
+import android.annotation.SuppressLint
 import android.content.Context
 import android.graphics.drawable.Drawable
 import android.service.quicksettings.Tile
@@ -36,6 +37,7 @@
     val stateDescription: ContentDescription?
     val contentDescription: ContentDescription?
 
+    @SuppressLint("UseCompatLoadingForDrawables")
     fun applyTo(state: QSTile.BooleanState, context: Context) {
         if (secondaryLabel != null) {
             state.secondaryLabel = secondaryLabel.loadText(context)
@@ -50,7 +52,7 @@
         if (icon != null) {
             state.icon = icon
         } else if (iconId != null) {
-            state.icon = QSTileImpl.ResourceIcon.get(iconId!!)
+            state.icon = QSTileImpl.maybeLoadResourceIcon(iconId!!, context)
         }
 
         state.state =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 6a9b43c..c52275a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -32,6 +32,7 @@
 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.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
@@ -39,6 +40,7 @@
 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
 import com.android.systemui.statusbar.pipeline.shared.domain.interactor.CollapsedStatusBarInteractor
@@ -137,6 +139,7 @@
     collapsedStatusBarInteractor: CollapsedStatusBarInteractor,
     private val lightsOutInteractor: LightsOutInteractor,
     private val notificationsInteractor: ActiveNotificationsInteractor,
+    headsUpNotificationInteractor: HeadsUpNotificationInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     keyguardInteractor: KeyguardInteractor,
     sceneInteractor: SceneInteractor,
@@ -222,27 +225,43 @@
             isHomeStatusBarAllowed && !isSecureCameraActive
         }
 
+    private val isAnyChipVisible =
+        if (StatusBarNotifChips.isEnabled) {
+            ongoingActivityChips.map { it.primary is OngoingActivityChipModel.Shown }
+        } else {
+            primaryOngoingActivityChip.map { it is OngoingActivityChipModel.Shown }
+        }
+
     override val isClockVisible: Flow<VisibilityModel> =
         combine(
             shouldHomeStatusBarBeVisible,
+            headsUpNotificationInteractor.showHeadsUpStatusBar,
             collapsedStatusBarInteractor.visibilityViaDisableFlags,
-        ) { shouldStatusBarBeVisible, visibilityViaDisableFlags ->
-            val showClock = shouldStatusBarBeVisible && visibilityViaDisableFlags.isClockAllowed
-            // TODO(b/364360986): Take CollapsedStatusBarFragment.clockHiddenMode into account.
-            VisibilityModel(showClock.toVisibilityInt(), visibilityViaDisableFlags.animate)
+        ) { shouldStatusBarBeVisible, showHeadsUp, visibilityViaDisableFlags ->
+            val showClock =
+                shouldStatusBarBeVisible && visibilityViaDisableFlags.isClockAllowed && !showHeadsUp
+            // Always use View.INVISIBLE here, so that animations work
+            VisibilityModel(showClock.toVisibleOrInvisible(), visibilityViaDisableFlags.animate)
         }
     override val isNotificationIconContainerVisible: Flow<VisibilityModel> =
         combine(
             shouldHomeStatusBarBeVisible,
+            isAnyChipVisible,
             collapsedStatusBarInteractor.visibilityViaDisableFlags,
-        ) { shouldStatusBarBeVisible, visibilityViaDisableFlags ->
+        ) { shouldStatusBarBeVisible, anyChipVisible, visibilityViaDisableFlags ->
             val showNotificationIconContainer =
-                shouldStatusBarBeVisible && visibilityViaDisableFlags.areNotificationIconsAllowed
+                if (anyChipVisible) {
+                    false
+                } else {
+                    shouldStatusBarBeVisible &&
+                        visibilityViaDisableFlags.areNotificationIconsAllowed
+                }
             VisibilityModel(
-                showNotificationIconContainer.toVisibilityInt(),
+                showNotificationIconContainer.toVisibleOrGone(),
                 visibilityViaDisableFlags.animate,
             )
         }
+
     private val isSystemInfoVisible =
         combine(
             shouldHomeStatusBarBeVisible,
@@ -250,7 +269,7 @@
         ) { shouldStatusBarBeVisible, visibilityViaDisableFlags ->
             val showSystemInfo =
                 shouldStatusBarBeVisible && visibilityViaDisableFlags.isSystemInfoAllowed
-            VisibilityModel(showSystemInfo.toVisibilityInt(), visibilityViaDisableFlags.animate)
+            VisibilityModel(showSystemInfo.toVisibleOrGone(), visibilityViaDisableFlags.animate)
         }
 
     override val systemInfoCombinedVis =
@@ -270,7 +289,11 @@
             )
 
     @View.Visibility
-    private fun Boolean.toVisibilityInt(): Int {
+    private fun Boolean.toVisibleOrGone(): Int {
         return if (this) View.VISIBLE else View.GONE
     }
+
+    // Similar to the above, but uses INVISIBLE in place of GONE
+    @View.Visibility
+    private fun Boolean.toVisibleOrInvisible(): Int = if (this) View.VISIBLE else View.INVISIBLE
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index a72c83e..6ed2ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -87,7 +87,11 @@
 
     override val lifecycle =
         LifecycleRegistry(this).also {
-            mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
+            if (multiuserWifiPickerTrackerSupport()) {
+                mainExecutor.execute { it.currentState = Lifecycle.State.STARTED }
+            } else {
+                mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
+            }
         }
 
     private var wifiPickerTracker: WifiPickerTracker? = null
@@ -178,6 +182,10 @@
                                         trySend(new)
                                     }
                                 }
+
+                            // If a WifiPicker already exists, call onStop to begin its shutdown
+                            // process in preparation for a new one to be created.
+                            wifiPickerTracker?.onStop()
                             wifiPickerTracker =
                                 wifiPickerTrackerFactory
                                     .create(currentContext, lifecycle, callback, "WifiRepository")
@@ -189,17 +197,7 @@
                                         // costly and should be avoided whenever possible).
                                         this?.disableScanning()
                                     }
-
-                            // The lifecycle must be STARTED in order for the callback to receive
-                            // events.
-                            mainExecutor.execute {
-                                lifecycle.currentState = Lifecycle.State.STARTED
-                            }
-                            awaitClose {
-                                mainExecutor.execute {
-                                    lifecycle.currentState = Lifecycle.State.CREATED
-                                }
-                            }
+                            awaitClose { mainExecutor.execute { wifiPickerTracker?.onStop() } }
                         }
                         .stateIn(scope, SharingStarted.Eagerly, current)
                 }
@@ -275,7 +273,6 @@
                                     trySend(new)
                                 }
                             }
-
                         wifiPickerTracker =
                             wifiPickerTrackerFactory
                                 .create(applicationContext, lifecycle, callback, "WifiRepository")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
deleted file mode 100644
index d406ed4..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ /dev/null
@@ -1,473 +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.statusbar.policy
-
-import android.os.Handler
-import android.util.Log
-import androidx.annotation.VisibleForTesting
-import com.android.internal.logging.UiEvent
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-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
-
-/*
- * Control when heads up notifications show during an avalanche where notifications arrive in fast
- * succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager
- */
-@SysUISingleton
-class AvalancheController
-@Inject
-constructor(
-        dumpManager: DumpManager,
-        private val uiEventLogger: UiEventLogger,
-        private val headsUpManagerLogger: HeadsUpManagerLogger,
-        @Background private val bgHandler: Handler
-) : Dumpable {
-
-    private val tag = "AvalancheController"
-    private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
-    var baseEntryMapStr : () -> String = { "baseEntryMapStr not initialized" }
-
-    var enableAtRuntime = true
-        set(value) {
-            if (!value) {
-                // Waiting HUNs in AvalancheController are shown in the HUN section in open shade.
-                // Clear them so we don't show them again when the shade closes and reordering is
-                // allowed again.
-                logDroppedHunsInBackground(getWaitingKeys().size)
-                clearNext()
-                headsUpEntryShowing = null
-            }
-            if (field != value) {
-                field = value
-            }
-        }
-
-    // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
-    @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
-
-    // Key of HUN previously showing, is being removed or was removed
-    var previousHunKey: String = ""
-
-    // List of runnables to run for the HUN showing right now
-    private var headsUpEntryShowingRunnableList: MutableList<Runnable> = ArrayList()
-
-    // HeadsUpEntry waiting to show
-    // Use sortable list instead of priority queue for debugging
-    private val nextList: MutableList<HeadsUpEntry> = ArrayList()
-
-    // Map of HeadsUpEntry waiting to show, and runnables to run when it shows.
-    // Use HashMap instead of SortedMap for faster lookup, and also because the ordering
-    // provided by HeadsUpEntry.compareTo is not consistent over time or with HeadsUpEntry.equals
-    @VisibleForTesting var nextMap: MutableMap<HeadsUpEntry, MutableList<Runnable>> = HashMap()
-
-    // Map of Runnable to label for debugging only
-    private val debugRunnableLabelMap: MutableMap<Runnable, String> = HashMap()
-
-    // HeadsUpEntry we did not show at all because they are not the top priority hun in their batch
-    // For debugging only
-    @VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet()
-
-    enum class ThrottleEvent(private val id: Int) : UiEventLogger.UiEventEnum {
-        @UiEvent(doc = "HUN was shown.") AVALANCHE_THROTTLING_HUN_SHOWN(1821),
-        @UiEvent(doc = "HUN was dropped to show higher priority HUNs.")
-        AVALANCHE_THROTTLING_HUN_DROPPED(1822),
-        @UiEvent(doc = "HUN was removed while waiting to show.")
-        AVALANCHE_THROTTLING_HUN_REMOVED(1823);
-
-        override fun getId(): Int {
-            return id
-        }
-    }
-
-    init {
-        dumpManager.registerNormalDumpable(tag, /* module */ this)
-    }
-
-    fun getShowingHunKey(): String {
-        return getKey(headsUpEntryShowing)
-    }
-
-    fun isEnabled(): Boolean {
-        return NotificationThrottleHun.isEnabled && enableAtRuntime
-    }
-
-    /** Run or delay Runnable for given HeadsUpEntry */
-    fun update(entry: HeadsUpEntry?, runnable: Runnable?, caller: String) {
-        val isEnabled = isEnabled()
-        val key = getKey(entry)
-
-        if (runnable == null) {
-            headsUpManagerLogger.logAvalancheUpdate(
-                caller, isEnabled, key,
-                "Runnable NULL, stop. ${getStateStr()}"
-            )
-            return
-        }
-        if (!isEnabled) {
-            headsUpManagerLogger.logAvalancheUpdate(
-                caller, isEnabled, key,
-                "NOT ENABLED, run runnable. ${getStateStr()}"
-            )
-            runnable.run()
-            return
-        }
-        if (entry == null) {
-            headsUpManagerLogger.logAvalancheUpdate(
-                caller, isEnabled, key,
-                "Entry NULL, stop. ${getStateStr()}"
-            )
-            return
-        }
-        if (debug) {
-            debugRunnableLabelMap[runnable] = caller
-        }
-        var outcome = ""
-        if (isShowing(entry)) {
-            outcome = "update showing"
-            runnable.run()
-        } else if (entry in nextMap) {
-            outcome = "update next"
-            nextMap[entry]?.add(runnable)
-        } else if (headsUpEntryShowing == null) {
-            outcome = "show now"
-            showNow(entry, arrayListOf(runnable))
-        } else {
-            // Clean up invalid state when entry is in list but not map and vice versa
-            if (entry in nextMap) nextMap.remove(entry)
-            if (entry in nextList) nextList.remove(entry)
-
-            addToNext(entry, runnable)
-
-            // Shorten headsUpEntryShowing display time
-            val nextIndex = nextList.indexOf(entry)
-            val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1
-            if (isOnlyNextEntry) {
-                // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
-                // and goes to the isShowing case above
-                headsUpEntryShowing!!.updateEntry(
-                    /* updatePostTime= */ false,
-                    /* updateEarliestRemovalTime= */ false,
-                    /* reason= */ "avalanche duration update"
-                )
-            }
-        }
-        outcome += getStateStr()
-        headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, outcome)
-    }
-
-    @VisibleForTesting
-    fun addToNext(entry: HeadsUpEntry, runnable: Runnable) {
-        nextMap[entry] = arrayListOf(runnable)
-        nextList.add(entry)
-    }
-
-    /**
-     * Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
-     * all Runnables associated with that entry.
-     */
-    fun delete(entry: HeadsUpEntry?, runnable: Runnable?, caller: String) {
-        val isEnabled = isEnabled()
-        val key = getKey(entry)
-
-        if (runnable == null) {
-            headsUpManagerLogger.logAvalancheDelete(
-                caller, isEnabled, key,
-                "Runnable NULL, stop. ${getStateStr()}"
-            )
-            return
-        }
-        if (!isEnabled) {
-            runnable.run()
-            headsUpManagerLogger.logAvalancheDelete(
-                caller, isEnabled = false, key,
-                "NOT ENABLED, run runnable. ${getStateStr()}"
-            )
-            return
-        }
-        if (entry == null) {
-            runnable.run()
-            headsUpManagerLogger.logAvalancheDelete(
-                caller, isEnabled = true, key,
-                "Entry NULL, run runnable. ${getStateStr()}"
-            )
-            return
-        }
-        val outcome: String
-        if (entry in nextMap) {
-            if (entry in nextMap) nextMap.remove(entry)
-            if (entry in nextList) nextList.remove(entry)
-            uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_REMOVED)
-            outcome = "remove from next. ${getStateStr()}"
-
-        } else if (entry in debugDropSet) {
-            debugDropSet.remove(entry)
-            outcome = "remove from dropset. ${getStateStr()}"
-
-        } else if (isShowing(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()
-            outcome = "remove showing. ${getStateStr()}"
-
-        } else {
-            runnable.run()
-            outcome = "run runnable for untracked shown HUN. ${getStateStr()}"
-        }
-        headsUpManagerLogger.logAvalancheDelete(caller, isEnabled(), getKey(entry), outcome)
-    }
-
-    /**
-     * Returns duration based on
-     * 1) Whether HeadsUpEntry is the last one tracked by AvalancheController
-     * 2) The priority of the top HUN in the next batch Used by
-     *    BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
-     */
-    fun getDurationMs(entry: HeadsUpEntry?, autoDismissMs: Int): Int {
-        if (!isEnabled()) {
-            // Use default duration, like we did before AvalancheController existed
-            return autoDismissMs
-        }
-        if (entry == null) {
-            // This should never happen
-            return autoDismissMs
-        }
-        val showingList: MutableList<HeadsUpEntry> = mutableListOf()
-        if (headsUpEntryShowing != null) {
-            showingList.add(headsUpEntryShowing!!)
-        }
-        nextList.sort()
-        val entryList = showingList + nextList
-        if (entryList.isEmpty()) {
-            log { "No avalanche HUNs, use default ms: $autoDismissMs" }
-            return autoDismissMs
-        }
-        // entryList.indexOf(entry) returns -1 even when the entry is in entryList
-        var thisEntryIndex = -1
-        for ((i, e) in entryList.withIndex()) {
-            if (e == entry) {
-                thisEntryIndex = i
-            }
-        }
-        if (thisEntryIndex == -1) {
-            log { "Untracked entry, use default ms: $autoDismissMs" }
-            return autoDismissMs
-        }
-        val nextEntryIndex = thisEntryIndex + 1
-
-        // If last entry, use default duration
-        if (nextEntryIndex >= entryList.size) {
-            log { "Last entry, use default ms: $autoDismissMs" }
-            return autoDismissMs
-        }
-        val nextEntry = entryList[nextEntryIndex]
-        if (nextEntry.compareNonTimeFields(entry) == -1) {
-            // Next entry is higher priority
-            log { "Next entry is higher priority: 500ms" }
-            return 500
-        } else if (nextEntry.compareNonTimeFields(entry) == 0) {
-            // Next entry is same priority
-            log { "Next entry is same priority: 1000ms" }
-            return 1000
-        } else {
-            log { "Next entry is lower priority, use default ms: $autoDismissMs" }
-            return autoDismissMs
-        }
-    }
-
-    /** Return true if entry is waiting to show. */
-    fun isWaiting(key: String): Boolean {
-        if (!isEnabled()) {
-            return false
-        }
-        for (entry in nextMap.keys) {
-            if (entry.mEntry?.key.equals(key)) {
-                return true
-            }
-        }
-        return false
-    }
-
-    /** Return list of keys for huns waiting */
-    fun getWaitingKeys(): MutableList<String> {
-        if (!isEnabled()) {
-            return mutableListOf()
-        }
-        val keyList = mutableListOf<String>()
-        for (entry in nextMap.keys) {
-            entry.mEntry?.let { keyList.add(entry.mEntry!!.key) }
-        }
-        return keyList
-    }
-
-    fun getWaitingEntry(key: String): HeadsUpEntry? {
-        if (!isEnabled()) {
-            return null
-        }
-        for (headsUpEntry in nextMap.keys) {
-            if (headsUpEntry.mEntry?.key.equals(key)) {
-                return headsUpEntry
-            }
-        }
-        return null
-    }
-
-    fun getWaitingEntryList(): List<HeadsUpEntry> {
-        if (!isEnabled()) {
-            return mutableListOf()
-        }
-        return nextMap.keys.toList()
-    }
-
-    private fun isShowing(entry: HeadsUpEntry): Boolean {
-        return headsUpEntryShowing != null && entry.mEntry?.key == headsUpEntryShowing?.mEntry?.key
-    }
-
-    private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) {
-        log { "SHOW: " + getKey(entry) }
-
-        uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_SHOWN)
-        headsUpEntryShowing = entry
-
-        runnableList.forEach {
-            if (it in debugRunnableLabelMap) {
-                log { "RUNNABLE: ${debugRunnableLabelMap[it]}" }
-            }
-            it.run()
-        }
-    }
-
-    private fun showNext() {
-        log { "SHOW NEXT" }
-        headsUpEntryShowing = null
-
-        if (nextList.isEmpty()) {
-            log { "NO MORE TO SHOW" }
-            previousHunKey = ""
-            return
-        }
-
-        // Only show first (top priority) entry in next batch
-        nextList.sort()
-        headsUpEntryShowing = nextList[0]
-        headsUpEntryShowingRunnableList = nextMap[headsUpEntryShowing]!!
-
-        // Remove runnable labels for dropped huns
-        val listToDrop = nextList.subList(1, nextList.size)
-        logDroppedHunsInBackground(listToDrop.size)
-
-        if (debug) {
-            // Clear runnable labels
-            for (e in listToDrop) {
-                val runnableList = nextMap[e]!!
-                for (r in runnableList) {
-                    debugRunnableLabelMap.remove(r)
-                }
-            }
-            debugDropSet.addAll(listToDrop)
-        }
-
-        clearNext()
-        showNow(headsUpEntryShowing!!, headsUpEntryShowingRunnableList)
-    }
-
-    private fun logDroppedHunsInBackground(numDropped: Int) {
-        bgHandler.post(
-            Runnable {
-                // Do this in the background to avoid missing frames when closing the shade
-                for (n in 1..numDropped) {
-                    uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_DROPPED)
-                }
-            }
-        )
-    }
-
-    fun clearNext() {
-        nextList.clear()
-        nextMap.clear()
-    }
-
-    // Methods below are for logging only ==========================================================
-
-    private inline fun log(s: () -> String) {
-        if (debug) {
-            Log.d(tag, s())
-        }
-    }
-
-    private fun getStateStr(): String {
-        return "\navalanche state:" +
-                "\n\tshowing: [${getKey(headsUpEntryShowing)}]" +
-                "\n\tprevious: [$previousHunKey]" +
-                "\n\tnext list: $nextListStr" +
-                "\n\tnext map: $nextMapStr" +
-                "\n\tdropped: $dropSetStr" +
-                "\nBHUM.mHeadsUpEntryMap: " +
-                baseEntryMapStr()
-    }
-
-    private val dropSetStr: String
-        get() {
-            val queue = ArrayList<String>()
-            for (entry in debugDropSet) {
-                queue.add("[${getKey(entry)}]")
-            }
-            return java.lang.String.join("\n", queue)
-        }
-
-    private val nextListStr: String
-        get() {
-            val queue = ArrayList<String>()
-            for (entry in nextList) {
-                queue.add("[${getKey(entry)}]")
-            }
-            return java.lang.String.join("\n", queue)
-        }
-
-    private val nextMapStr: String
-        get() {
-            val queue = ArrayList<String>()
-            for (entry in nextMap.keys) {
-                queue.add("[${getKey(entry)}]")
-            }
-            return java.lang.String.join("\n", queue)
-        }
-
-    fun getKey(entry: HeadsUpEntry?): String {
-        if (entry == null) {
-            return "HeadsUpEntry null"
-        }
-        if (entry.mEntry == null) {
-            return "HeadsUpEntry.mEntry null"
-        }
-        return entry.mEntry!!.key
-    }
-
-    override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println("AvalancheController: ${getStateStr()}")
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
deleted file mode 100644
index 298ef7e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ /dev/null
@@ -1,1663 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Notification;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.graphics.Region;
-import android.os.Handler;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Pools;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEvent;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.SystemBarUtils;
-import com.android.systemui.EventLogTags;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.res.R;
-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.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
-import com.android.systemui.statusbar.notification.collection.provider.OnReorderingBannedListener;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
-import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.util.ListenerSet;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.time.SystemClock;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.Stack;
-import java.util.stream.Stream;
-
-import javax.inject.Inject;
-
-import kotlinx.coroutines.flow.Flow;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlow;
-import kotlinx.coroutines.flow.StateFlowKt;
-
-/**
- * A manager which handles heads up notifications which is a special mode where
- * they simply peek from the top of the screen.
- */
-@SysUISingleton
-public class BaseHeadsUpManager
-        implements HeadsUpManager, HeadsUpRepository, OnHeadsUpChangedListener {
-    private static final String TAG = "BaseHeadsUpManager";
-    private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
-    private static final String REASON_REORDER_ALLOWED = "mOnReorderingAllowedListener";
-    protected final ListenerSet<OnHeadsUpChangedListener> mListeners = new ListenerSet<>();
-
-    protected final Context mContext;
-
-    protected int mTouchAcceptanceDelay;
-    protected int mSnoozeLengthMs;
-    protected boolean mHasPinnedNotification;
-    protected int mUser;
-
-    private final ArrayMap<String, Long> mSnoozedPackages;
-    private final AccessibilityManagerWrapper mAccessibilityMgr;
-
-    private final UiEventLogger mUiEventLogger;
-    private AvalancheController mAvalancheController;
-    private final KeyguardBypassController mBypassController;
-    private final GroupMembershipManager mGroupMembershipManager;
-    private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
-    private final VisualStabilityProvider mVisualStabilityProvider;
-
-    protected final SystemClock mSystemClock;
-    protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
-    protected final HeadsUpManagerLogger mLogger;
-    protected int mMinimumDisplayTime;
-    protected int mStickyForSomeTimeAutoDismissTime;
-    protected int mAutoDismissTime;
-    protected DelayableExecutor mExecutor;
-
-    @VisibleForTesting
-    public final int mExtensionTime;
-
-    // TODO(b/328393698) move the topHeadsUpRow logic to an interactor
-    private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow =
-            StateFlowKt.MutableStateFlow(null);
-    private final MutableStateFlow<Set<HeadsUpRowRepository>> mHeadsUpNotificationRows =
-            StateFlowKt.MutableStateFlow(new HashSet<>());
-    private final MutableStateFlow<Boolean> mHeadsUpAnimatingAway =
-            StateFlowKt.MutableStateFlow(false);
-    private final HashSet<String> mSwipedOutKeys = new HashSet<>();
-    private final HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
-    @VisibleForTesting
-    public final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
-            = new ArraySet<>();
-
-    private boolean mReleaseOnExpandFinish;
-    private boolean mTrackingHeadsUp;
-    private boolean mIsShadeOrQsExpanded;
-    private boolean mIsQsExpanded;
-    private int mStatusBarState;
-    private AnimationStateHandler mAnimationStateHandler;
-    private int mHeadsUpInset;
-
-    // Used for determining the region for touch interaction
-    private final Region mTouchableRegion = new Region();
-
-    private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<>() {
-        private final Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
-
-        @Override
-        public HeadsUpEntry acquire() {
-            NotificationThrottleHun.assertInLegacyMode();
-            if (!mPoolObjects.isEmpty()) {
-                return mPoolObjects.pop();
-            }
-            return new HeadsUpEntry();
-        }
-
-        @Override
-        public boolean release(@NonNull HeadsUpEntry instance) {
-            NotificationThrottleHun.assertInLegacyMode();
-            mPoolObjects.push(instance);
-            return true;
-        }
-    };
-
-    /**
-     * Enum entry for notification peek logged from this class.
-     */
-    enum NotificationPeekEvent implements UiEventLogger.UiEventEnum {
-        @UiEvent(doc = "Heads-up notification peeked on screen.")
-        NOTIFICATION_PEEK(801);
-
-        private final int mId;
-        NotificationPeekEvent(int id) {
-            mId = id;
-        }
-        @Override public int getId() {
-            return mId;
-        }
-    }
-
-    @Inject
-    public BaseHeadsUpManager(
-            @NonNull final Context context,
-            HeadsUpManagerLogger logger,
-            StatusBarStateController statusBarStateController,
-            KeyguardBypassController bypassController,
-            GroupMembershipManager groupMembershipManager,
-            VisualStabilityProvider visualStabilityProvider,
-            ConfigurationController configurationController,
-            @Main Handler handler,
-            GlobalSettings globalSettings,
-            SystemClock systemClock,
-            @Main DelayableExecutor executor,
-            AccessibilityManagerWrapper accessibilityManagerWrapper,
-            UiEventLogger uiEventLogger,
-            JavaAdapter javaAdapter,
-            ShadeInteractor shadeInteractor,
-            AvalancheController avalancheController) {
-        mLogger = logger;
-        mExecutor = executor;
-        mSystemClock = systemClock;
-        mContext = context;
-        mAccessibilityMgr = accessibilityManagerWrapper;
-        mUiEventLogger = uiEventLogger;
-        mAvalancheController = avalancheController;
-        mAvalancheController.setBaseEntryMapStr(this::getEntryMapStr);
-        mBypassController = bypassController;
-        mGroupMembershipManager = groupMembershipManager;
-        mVisualStabilityProvider = visualStabilityProvider;
-        Resources resources = context.getResources();
-        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);
-        mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
-        mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
-        mSnoozedPackages = new ArrayMap<>();
-        int defaultSnoozeLengthMs =
-                resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
-
-        mSnoozeLengthMs = globalSettings.getInt(SETTING_HEADS_UP_SNOOZE_LENGTH_MS,
-                defaultSnoozeLengthMs);
-        ContentObserver settingsObserver = new ContentObserver(handler) {
-            @Override
-            public void onChange(boolean selfChange) {
-                final int packageSnoozeLengthMs = globalSettings.getInt(
-                        SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1);
-                if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) {
-                    mSnoozeLengthMs = packageSnoozeLengthMs;
-                    mLogger.logSnoozeLengthChange(packageSnoozeLengthMs);
-                }
-            }
-        };
-        globalSettings.registerContentObserverSync(
-                globalSettings.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS),
-                /* notifyForDescendants = */ false,
-                settingsObserver);
-
-        statusBarStateController.addCallback(mStatusBarStateListener);
-        updateResources();
-        configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
-            @Override
-            public void onDensityOrFontScaleChanged() {
-                updateResources();
-            }
-
-            @Override
-            public void onThemeChanged() {
-                updateResources();
-            }
-        });
-        javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
-                this::onShadeOrQsExpanded);
-        if (SceneContainerFlag.isEnabled()) {
-            javaAdapter.alwaysCollectFlow(shadeInteractor.isQsExpanded(),
-                    this::onQsExpanded);
-        }
-        if (NotificationThrottleHun.isEnabled()) {
-            mVisualStabilityProvider.addPersistentReorderingBannedListener(
-                    mOnReorderingBannedListener);
-            mVisualStabilityProvider.addPersistentReorderingAllowedListener(
-                    mOnReorderingAllowedListener);
-        }
-    }
-
-    /**
-     * Adds an OnHeadUpChangedListener to observe events.
-     */
-    @Override
-    public void addListener(@NonNull OnHeadsUpChangedListener listener) {
-        mListeners.addIfAbsent(listener);
-    }
-
-    /**
-     * Removes the OnHeadUpChangedListener from the observer list.
-     */
-    @Override
-    public void removeListener(@NonNull OnHeadsUpChangedListener listener) {
-        mListeners.remove(listener);
-    }
-
-    /**
-     * Add a listener to receive callbacks {@link #setHeadsUpAnimatingAway(boolean)}
-     */
-    @Override
-    public void addHeadsUpPhoneListener(@NonNull OnHeadsUpPhoneListenerChange listener) {
-        mHeadsUpPhoneListeners.add(listener);
-    }
-
-    @Override
-    public void setAnimationStateHandler(@NonNull AnimationStateHandler handler) {
-        mAnimationStateHandler = handler;
-    }
-
-    private void updateResources() {
-        Resources resources = mContext.getResources();
-        mHeadsUpInset = SystemBarUtils.getStatusBarHeight(mContext)
-                + resources.getDimensionPixelSize(R.dimen.heads_up_status_bar_padding);
-    }
-
-    /**
-     * Called when posting a new notification that should appear on screen.
-     * Adds the notification to be managed.
-     * @param entry entry to show
-     */
-    @Override
-    public void showNotification(@NonNull NotificationEntry entry) {
-        HeadsUpEntry headsUpEntry = createHeadsUpEntry(entry);
-
-        mLogger.logShowNotificationRequest(entry);
-
-        Runnable runnable = () -> {
-            mLogger.logShowNotification(entry);
-
-            // Add new entry and begin managing it
-            mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
-            onEntryAdded(headsUpEntry);
-            // TODO(b/328390331) move accessibility events to the view layer
-            entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-            entry.setIsHeadsUpEntry(true);
-
-            updateNotificationInternal(entry.getKey(), true /* shouldHeadsUpAgain */);
-            entry.setInterruption();
-        };
-        mAvalancheController.update(headsUpEntry, runnable, "showNotification");
-    }
-
-    @Override
-    public boolean removeNotification(
-            @NonNull String key,
-            boolean releaseImmediately,
-            boolean animate,
-            @NonNull String reason) {
-        if (animate) {
-            return removeNotification(key, releaseImmediately,
-                    "removeNotification(animate: true), reason: " + reason);
-        } else {
-            mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
-            final boolean removed = removeNotification(key, releaseImmediately,
-                    "removeNotification(animate: false), reason: " + reason);
-            mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
-            return removed;
-        }
-    }
-
-    @Override
-    public boolean removeNotification(@NotNull String key, boolean releaseImmediately,
-            @NonNull String reason) {
-        final boolean isWaiting = mAvalancheController.isWaiting(key);
-        mLogger.logRemoveNotification(key, releaseImmediately, isWaiting, reason);
-
-        if (mAvalancheController.isWaiting(key)) {
-            removeEntry(key, "removeNotification (isWaiting)");
-            return true;
-        }
-        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
-        if (headsUpEntry == null) {
-            mLogger.logNullEntry(key, reason);
-            return true;
-        }
-        if (releaseImmediately) {
-            removeEntry(key, "removeNotification (releaseImmediately)");
-            return true;
-        }
-        if (canRemoveImmediately(key)) {
-            removeEntry(key, "removeNotification (canRemoveImmediately)");
-            return true;
-        }
-        headsUpEntry.removeAsSoonAsPossible();
-        return false;
-    }
-
-    /**
-     * Called when the notification state has been updated.
-     * @param key the key of the entry that was updated
-     * @param shouldHeadsUpAgain whether the notification should show again and force reevaluation
-     *                           of removal time
-     */
-    public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) {
-        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
-        mLogger.logUpdateNotificationRequest(key, shouldHeadsUpAgain, headsUpEntry != null);
-
-        Runnable runnable = () -> {
-            updateNotificationInternal(key, shouldHeadsUpAgain);
-        };
-        mAvalancheController.update(headsUpEntry, runnable, "updateNotification");
-    }
-
-    private void updateNotificationInternal(@NonNull String key, boolean shouldHeadsUpAgain) {
-        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
-        mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null);
-        if (headsUpEntry == null) {
-            // the entry was released before this update (i.e by a listener) This can happen
-            // with the groupmanager
-            return;
-        }
-        // TODO(b/328390331) move accessibility events to the view layer
-        if (headsUpEntry.mEntry != null) {
-            headsUpEntry.mEntry.sendAccessibilityEvent(
-                    AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        }
-        if (shouldHeadsUpAgain) {
-            headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification");
-            if (headsUpEntry != null) {
-                setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry),
-                        "updateNotificationInternal");
-            }
-        }
-    }
-
-    @Override
-    public void setTrackingHeadsUp(boolean trackingHeadsUp) {
-        mTrackingHeadsUp = trackingHeadsUp;
-    }
-
-    @Override
-    public boolean shouldSwallowClick(@NonNull String key) {
-        BaseHeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
-        return entry != null && mSystemClock.elapsedRealtime() < entry.mPostTime;
-    }
-
-    @Override
-    public void releaseAfterExpansion() {
-        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
-        onExpandingFinished();
-    }
-
-    @Override
-    public void onExpandingFinished() {
-        if (mReleaseOnExpandFinish) {
-            releaseAllImmediately();
-            mReleaseOnExpandFinish = false;
-        } else {
-            for (NotificationEntry entry : getAllEntries().toList()) {
-                entry.setSeenInShade(true);
-            }
-            for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
-                if (isHeadsUpEntry(entry.getKey())) {
-                    // Maybe the heads-up was removed already
-                    removeEntry(entry.getKey(), "onExpandingFinished");
-                }
-            }
-        }
-        mEntriesToRemoveAfterExpand.clear();
-    }
-
-    /**
-     * Clears all managed notifications.
-     */
-    public void releaseAllImmediately() {
-        mLogger.logReleaseAllImmediately();
-        // A copy is necessary here as we are changing the underlying map.  This would cause
-        // undefined behavior if we iterated over the key set directly.
-        ArraySet<String> keysToRemove = new ArraySet<>(mHeadsUpEntryMap.keySet());
-
-        // Must get waiting keys before calling removeEntry, which clears waiting entries in
-        // AvalancheController
-        List<String> waitingKeysToRemove = mAvalancheController.getWaitingKeys();
-
-        for (String key : keysToRemove) {
-            removeEntry(key, "releaseAllImmediately (keysToRemove)");
-        }
-        for (String key : waitingKeysToRemove) {
-            removeEntry(key, "releaseAllImmediately (waitingKeysToRemove)");
-        }
-    }
-
-    /**
-     * Returns the entry if it is managed by this manager.
-     * @param key key of notification
-     * @return the entry
-     */
-    @Nullable
-    public NotificationEntry getEntry(@NonNull String key) {
-        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
-        return headsUpEntry != null ? headsUpEntry.mEntry : null;
-    }
-
-    /**
-     * Returns the stream of all current notifications managed by this manager.
-     * @return all entries
-     */
-    @NonNull
-    @Override
-    public Stream<NotificationEntry> getAllEntries() {
-        return getHeadsUpEntryList().stream().map(headsUpEntry -> headsUpEntry.mEntry);
-    }
-
-    public List<HeadsUpEntry> getHeadsUpEntryList() {
-        List<HeadsUpEntry> entryList = new ArrayList<>(mHeadsUpEntryMap.values());
-        entryList.addAll(mAvalancheController.getWaitingEntryList());
-        return entryList;
-    }
-
-    /**
-     * Whether or not there are any active notifications.
-     * @return true if there is an entry, false otherwise
-     */
-    @Override
-    public boolean hasNotifications() {
-        return !mHeadsUpEntryMap.isEmpty()
-                || !mAvalancheController.getWaitingEntryList().isEmpty();
-    }
-
-    /**
-     * @return true if the notification is managed by this manager
-     */
-    public boolean isHeadsUpEntry(@NonNull String key) {
-        return mHeadsUpEntryMap.containsKey(key) || mAvalancheController.isWaiting(key);
-    }
-
-    /**
-     * @param key
-     * @return When a HUN entry should be removed in milliseconds from now
-     */
-    @Override
-    public long getEarliestRemovalTime(String key) {
-        HeadsUpEntry entry = mHeadsUpEntryMap.get(key);
-        if (entry != null) {
-            return Math.max(0, entry.mEarliestRemovalTime - mSystemClock.elapsedRealtime());
-        }
-        return 0;
-    }
-
-    @VisibleForTesting
-    protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationEntry entry) {
-        boolean pin = mStatusBarState == StatusBarState.SHADE && !mIsShadeOrQsExpanded;
-        if (SceneContainerFlag.isEnabled()) {
-            pin |= mIsQsExpanded;
-        }
-        if (mBypassController.getBypassEnabled()) {
-            pin |= mStatusBarState == StatusBarState.KEYGUARD;
-        }
-        if (pin) {
-            return true;
-        }
-
-        final HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
-        if (headsUpEntry == null) {
-            // This should not happen since shouldHeadsUpBecomePinned is always called after adding
-            // the NotificationEntry into mHeadsUpEntryMap.
-            return hasFullScreenIntent(entry);
-        }
-        return hasFullScreenIntent(entry) && !headsUpEntry.mWasUnpinned;
-    }
-
-    protected boolean hasFullScreenIntent(@NonNull NotificationEntry entry) {
-        if (entry == null) {
-            return false;
-        }
-        if (entry.getSbn() == null) {
-            return false;
-        }
-        if (entry.getSbn().getNotification() == null) {
-            return false;
-        }
-        return entry.getSbn().getNotification().fullScreenIntent != null;
-    }
-
-    protected void setEntryPinned(
-            @NonNull BaseHeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned,
-            String reason) {
-        mLogger.logSetEntryPinned(headsUpEntry.mEntry, isPinned, reason);
-        NotificationEntry entry = headsUpEntry.mEntry;
-        if (!isPinned) {
-            headsUpEntry.mWasUnpinned = true;
-        }
-        if (headsUpEntry.isRowPinned() != isPinned) {
-            headsUpEntry.setRowPinned(isPinned);
-            updatePinnedMode();
-            if (isPinned && entry.getSbn() != null) {
-               mUiEventLogger.logWithInstanceId(
-                        NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(),
-                        entry.getSbn().getPackageName(), entry.getSbn().getInstanceId());
-            }
-        // TODO(b/325936094) use the isPinned Flow instead
-            for (OnHeadsUpChangedListener listener : mListeners) {
-                if (isPinned) {
-                    listener.onHeadsUpPinned(entry);
-                } else {
-                    listener.onHeadsUpUnPinned(entry);
-                }
-            }
-        }
-    }
-
-    /**
-     * Manager-specific logic that should occur when an entry is added.
-     * @param headsUpEntry entry added
-     */
-    protected void onEntryAdded(HeadsUpEntry headsUpEntry) {
-        NotificationEntry entry = headsUpEntry.mEntry;
-        entry.setHeadsUp(true);
-
-        final boolean shouldPin = shouldHeadsUpBecomePinned(entry);
-        setEntryPinned(headsUpEntry, shouldPin, "onEntryAdded");
-        EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
-        for (OnHeadsUpChangedListener listener : mListeners) {
-            listener.onHeadsUpStateChanged(entry, true);
-        }
-        updateTopHeadsUpFlow();
-        updateHeadsUpFlow();
-    }
-
-    /**
-     * Remove a notification from the alerting entries.
-     * @param key key of notification to remove
-     */
-    protected final void removeEntry(@NonNull String key, String reason) {
-        HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
-        boolean isWaiting;
-        if (headsUpEntry == null) {
-            headsUpEntry = mAvalancheController.getWaitingEntry(key);
-            isWaiting = true;
-        } else {
-            isWaiting = false;
-        }
-        mLogger.logRemoveEntryRequest(key, reason, isWaiting);
-        HeadsUpEntry finalHeadsUpEntry = headsUpEntry;
-        Runnable runnable = () -> {
-            mLogger.logRemoveEntry(key, reason, isWaiting);
-
-            if (finalHeadsUpEntry == null) {
-                return;
-            }
-            NotificationEntry entry = finalHeadsUpEntry.mEntry;
-
-            // If the notification is animating, we will remove it at the end of the animation.
-            if (entry != null && entry.isExpandAnimationRunning()) {
-                return;
-            }
-            entry.demoteStickyHun();
-            mHeadsUpEntryMap.remove(key);
-            onEntryRemoved(finalHeadsUpEntry, reason);
-            // TODO(b/328390331) move accessibility events to the view layer
-            entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-            if (NotificationThrottleHun.isEnabled()) {
-                finalHeadsUpEntry.cancelAutoRemovalCallbacks("removeEntry");
-            } else {
-                finalHeadsUpEntry.reset();
-            }
-        };
-        mAvalancheController.delete(headsUpEntry, runnable, "removeEntry");
-    }
-
-    /**
-     * Manager-specific logic that should occur when an entry is removed.
-     * @param headsUpEntry entry removed
-     * @param reason why onEntryRemoved was called
-     */
-    protected void onEntryRemoved(HeadsUpEntry headsUpEntry, String reason) {
-        NotificationEntry entry = headsUpEntry.mEntry;
-        entry.setHeadsUp(false);
-        setEntryPinned(headsUpEntry, false /* isPinned */, "onEntryRemoved");
-        EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
-        mLogger.logNotificationActuallyRemoved(entry);
-        for (OnHeadsUpChangedListener listener : mListeners) {
-            listener.onHeadsUpStateChanged(entry, false);
-        }
-        if (!NotificationThrottleHun.isEnabled()) {
-            mEntryPool.release(headsUpEntry);
-        }
-        updateTopHeadsUpFlow();
-        updateHeadsUpFlow();
-        if (NotificationThrottleHun.isEnabled()) {
-            NotificationEntry notifEntry = headsUpEntry.mEntry;
-            if (notifEntry == null) {
-                return;
-            }
-            // If reorder was just allowed and we called onEntryRemoved while iterating over
-            // mEntriesToRemoveWhenReorderingAllowed, we should not remove from this list (and cause
-            // ArrayIndexOutOfBoundsException). We don't need to in this case anyway, because we
-            // clear mEntriesToRemoveWhenReorderingAllowed after removing these entries.
-            if (!reason.equals(REASON_REORDER_ALLOWED)
-                    && mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)) {
-                mEntriesToRemoveWhenReorderingAllowed.remove(notifEntry);
-            }
-        }
-    }
-
-    private void updateTopHeadsUpFlow() {
-        mTopHeadsUpRow.setValue((HeadsUpRowRepository) getTopHeadsUpEntry());
-    }
-
-    private void updateHeadsUpFlow() {
-        mHeadsUpNotificationRows.setValue(new HashSet<>(getHeadsUpEntryPhoneMap().values()));
-    }
-
-    @Override
-    @NonNull
-    public Flow<HeadsUpRowRepository> getTopHeadsUpRow() {
-        return mTopHeadsUpRow;
-    }
-
-    @Override
-    @NonNull
-    public Flow<Set<HeadsUpRowRepository>> getActiveHeadsUpRows() {
-        return mHeadsUpNotificationRows;
-    }
-
-    @Override
-    @NonNull
-    public StateFlow<Boolean> isHeadsUpAnimatingAway() {
-        return mHeadsUpAnimatingAway;
-    }
-
-    @Override
-    public boolean isHeadsUpAnimatingAwayValue() {
-        return mHeadsUpAnimatingAway.getValue();
-    }
-
-    @NonNull
-    private ArrayMap<String, HeadsUpEntry> getHeadsUpEntryPhoneMap() {
-        return mHeadsUpEntryMap;
-    }
-
-    /**
-     * Called to notify the listeners that the HUN animating away animation has ended.
-     */
-    @Override
-    public void onEntryAnimatingAwayEnded(@NonNull NotificationEntry entry) {
-        for (OnHeadsUpChangedListener listener : mListeners) {
-            listener.onHeadsUpAnimatingAwayEnded(entry);
-        }
-    }
-
-    /**
-     * Manager-specific logic, that should occur, when the entry is updated, and its posted time has
-     * changed.
-     *
-     * @param headsUpEntry entry updated
-     */
-    protected void onEntryUpdated(HeadsUpEntry headsUpEntry) {
-        // no need to update the list here
-        updateTopHeadsUpFlow();
-    }
-
-    protected void updatePinnedMode() {
-        boolean hasPinnedNotification = hasPinnedNotificationInternal();
-        if (hasPinnedNotification == mHasPinnedNotification) {
-            return;
-        }
-        mLogger.logUpdatePinnedMode(hasPinnedNotification);
-        mHasPinnedNotification = hasPinnedNotification;
-        if (mHasPinnedNotification) {
-            MetricsLogger.count(mContext, "note_peek", 1);
-        }
-        for (OnHeadsUpChangedListener listener : mListeners) {
-            listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
-        }
-    }
-
-    /**
-     * Returns if the given notification is snoozed or not.
-     */
-    public boolean isSnoozed(@NonNull String packageName) {
-        final String key = snoozeKey(packageName, mUser);
-        Long snoozedUntil = mSnoozedPackages.get(key);
-        if (snoozedUntil != null) {
-            if (snoozedUntil > mSystemClock.elapsedRealtime()) {
-                mLogger.logIsSnoozedReturned(key);
-                return true;
-            }
-            mLogger.logPackageUnsnoozed(key);
-            mSnoozedPackages.remove(key);
-        }
-        return false;
-    }
-
-    /**
-     * Snoozes all current Heads Up Notifications.
-     */
-    @Override
-    public void snooze() {
-        List<String> keySet = new ArrayList<>(mHeadsUpEntryMap.keySet());
-        keySet.addAll(mAvalancheController.getWaitingKeys());
-        for (String key : keySet) {
-            HeadsUpEntry entry = getHeadsUpEntry(key);
-            if (entry.mEntry == null) {
-                continue;
-            }
-            String packageName = entry.mEntry.getSbn().getPackageName();
-            String snoozeKey = snoozeKey(packageName, mUser);
-            mLogger.logPackageSnoozed(snoozeKey);
-            mSnoozedPackages.put(snoozeKey, mSystemClock.elapsedRealtime() + mSnoozeLengthMs);
-        }
-        mReleaseOnExpandFinish = true;
-    }
-
-    @NonNull
-    private static String snoozeKey(@NonNull String packageName, int user) {
-        return user + "," + packageName;
-    }
-
-    @Override
-    public void addSwipedOutNotification(@NonNull String key) {
-        mSwipedOutKeys.add(key);
-    }
-
-    @Nullable
-    protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
-        if (mHeadsUpEntryMap.containsKey(key)) {
-            return mHeadsUpEntryMap.get(key);
-        }
-        return mAvalancheController.getWaitingEntry(key);
-    }
-
-    /**
-     * Returns the top Heads Up Notification, which appears to show at first.
-     */
-    @Nullable
-    public NotificationEntry getTopEntry() {
-        HeadsUpEntry topEntry = getTopHeadsUpEntry();
-        return (topEntry != null) ? topEntry.mEntry : null;
-    }
-
-    @Nullable
-    protected HeadsUpEntry getTopHeadsUpEntry() {
-        if (mHeadsUpEntryMap.isEmpty()) {
-            return null;
-        }
-        HeadsUpEntry topEntry = null;
-        for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
-            if (topEntry == null || entry.compareTo(topEntry) < 0) {
-                topEntry = entry;
-            }
-        }
-        return topEntry;
-    }
-
-    /**
-     * Sets the current user.
-     */
-    public void setUser(int user) {
-        mUser = user;
-    }
-
-    /** Returns the ID of the current user. */
-    public int getUser() {
-        return  mUser;
-    }
-
-    private String getEntryMapStr() {
-        if (mHeadsUpEntryMap.isEmpty()) {
-            return "EMPTY";
-        }
-        StringBuilder entryMapStr = new StringBuilder();
-        for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
-            entryMapStr.append("\n\t").append(
-                    entry.mEntry == null ? "null" : entry.mEntry.getKey());
-        }
-        return entryMapStr.toString();
-    }
-
-    @Override
-    public @Nullable Region getTouchableRegion() {
-        NotificationEntry topEntry = getTopEntry();
-
-        // This call could be made in an inconsistent state while the pinnedMode hasn't been
-        // updated yet, but callbacks leading out of the headsUp manager, querying it. Let's
-        // therefore also check if the topEntry is null.
-        if (!hasPinnedHeadsUp() || topEntry == null) {
-            return null;
-        } else {
-            if (topEntry.rowIsChildInGroup()) {
-                final NotificationEntry groupSummary =
-                        mGroupMembershipManager.getGroupSummary(topEntry);
-                if (groupSummary != null) {
-                    topEntry = groupSummary;
-                }
-            }
-            ExpandableNotificationRow topRow = topEntry.getRow();
-            int[] tmpArray = new int[2];
-            topRow.getLocationOnScreen(tmpArray);
-            int minX = tmpArray[0];
-            int maxX = tmpArray[0] + topRow.getWidth();
-            int height = topRow.getIntrinsicHeight();
-            final boolean stretchToTop = tmpArray[1] <= mHeadsUpInset;
-            mTouchableRegion.set(minX, stretchToTop ? 0 : tmpArray[1], maxX, tmpArray[1] + height);
-            return mTouchableRegion;
-        }
-    }
-
-    @Override
-    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
-        if (headsUpAnimatingAway != mHeadsUpAnimatingAway.getValue()) {
-            for (OnHeadsUpPhoneListenerChange listener : mHeadsUpPhoneListeners) {
-                listener.onHeadsUpAnimatingAwayStateChanged(headsUpAnimatingAway);
-            }
-            mHeadsUpAnimatingAway.setValue(headsUpAnimatingAway);
-        }
-    }
-
-    private void onShadeOrQsExpanded(Boolean isExpanded) {
-        if (isExpanded != mIsShadeOrQsExpanded) {
-            mIsShadeOrQsExpanded = isExpanded;
-            if (!SceneContainerFlag.isEnabled() && isExpanded) {
-                mHeadsUpAnimatingAway.setValue(false);
-            }
-        }
-    }
-
-    private void onQsExpanded(Boolean isQsExpanded) {
-        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
-        if (isQsExpanded != mIsQsExpanded) mIsQsExpanded = isQsExpanded;
-    }
-
-    @Override
-    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println("HeadsUpManager state:");
-        dumpInternal(pw, args);
-    }
-
-    protected void dumpInternal(@NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.print("  mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay);
-        pw.print("  mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
-        pw.print("  now="); pw.println(mSystemClock.elapsedRealtime());
-        pw.print("  mUser="); pw.println(mUser);
-        for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
-            pw.println(entry.mEntry == null ? "null" : entry.mEntry);
-        }
-        int n = mSnoozedPackages.size();
-        pw.println("  snoozed packages: " + n);
-        for (int i = 0; i < n; i++) {
-            pw.print("    "); pw.print(mSnoozedPackages.valueAt(i));
-            pw.print(", "); pw.println(mSnoozedPackages.keyAt(i));
-        }
-        pw.print("  mBarState=");
-        pw.println(mStatusBarState);
-        pw.print("  mTouchableRegion=");
-        pw.println(mTouchableRegion);
-    }
-
-    /**
-     * Returns if there are any pinned Heads Up Notifications or not.
-     */
-    public boolean hasPinnedHeadsUp() {
-        return mHasPinnedNotification;
-    }
-
-    private boolean hasPinnedNotificationInternal() {
-        for (String key : mHeadsUpEntryMap.keySet()) {
-            HeadsUpEntry entry = getHeadsUpEntry(key);
-            if (entry.mEntry != null && entry.mEntry.isRowPinned()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Unpins all pinned Heads Up Notifications.
-     * @param userUnPinned The unpinned action is trigger by user real operation.
-     */
-    @Override
-    public void unpinAll(boolean userUnPinned) {
-        for (String key : mHeadsUpEntryMap.keySet()) {
-            HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
-            mLogger.logUnpinEntryRequest(key);
-            Runnable runnable = () -> {
-                mLogger.logUnpinEntry(key);
-
-                setEntryPinned(headsUpEntry, false /* isPinned */, "unpinAll");
-                // maybe it got un sticky
-                headsUpEntry.updateEntry(false /* updatePostTime */, "unpinAll");
-
-                // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
-                // on the screen.
-                if (userUnPinned && headsUpEntry.mEntry != null) {
-                    if (headsUpEntry.mEntry != null && headsUpEntry.mEntry.mustStayOnScreen()) {
-                        headsUpEntry.mEntry.setHeadsUpIsVisible();
-                    }
-                }
-            };
-            mAvalancheController.delete(headsUpEntry, runnable, "unpinAll");
-        }
-    }
-
-    @Override
-    public void setRemoteInputActive(
-            @NonNull NotificationEntry entry, boolean remoteInputActive) {
-        HeadsUpEntry headsUpEntry = getHeadsUpEntryPhone(entry.getKey());
-        if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
-            headsUpEntry.mRemoteInputActive = remoteInputActive;
-            if (ExpandHeadsUpOnInlineReply.isEnabled() && remoteInputActive) {
-                headsUpEntry.mRemoteInputActivatedAtLeastOnce = true;
-            }
-            if (remoteInputActive) {
-                headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
-            } else {
-                headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
-            }
-            onEntryUpdated(headsUpEntry);
-        }
-    }
-
-    @Nullable
-    private HeadsUpEntry getHeadsUpEntryPhone(@NonNull String key) {
-        return mHeadsUpEntryMap.get(key);
-    }
-
-    @Override
-    public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) {
-        HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
-        if (headsUpEntry == null) return;
-        if (entry.isRowPinned() || !gutsShown) {
-            headsUpEntry.setGutsShownPinned(gutsShown);
-        }
-    }
-
-    @Override
-    public void extendHeadsUp() {
-        HeadsUpEntry topEntry = getTopHeadsUpEntryPhone();
-        if (topEntry == null) {
-            return;
-        }
-        topEntry.extendPulse();
-    }
-
-    @Nullable
-    private HeadsUpEntry getTopHeadsUpEntryPhone() {
-        if (SceneContainerFlag.isEnabled()) {
-            return (HeadsUpEntry) mTopHeadsUpRow.getValue();
-        } else {
-            return getTopHeadsUpEntry();
-        }
-    }
-
-    @Override
-    public boolean isTrackingHeadsUp() {
-        return mTrackingHeadsUp;
-    }
-
-    /**
-     * Compare two entries and decide how they should be ranked.
-     *
-     * @return -1 if the first argument should be ranked higher than the second, 1 if the second
-     * one should be ranked higher and 0 if they are equal.
-     */
-    public int compare(@Nullable NotificationEntry a, @Nullable NotificationEntry b) {
-        if (a == null || b == null) {
-            return Boolean.compare(a == null, b == null);
-        }
-        HeadsUpEntry aEntry = getHeadsUpEntry(a.getKey());
-        HeadsUpEntry bEntry = getHeadsUpEntry(b.getKey());
-        if (aEntry == null || bEntry == null) {
-            return Boolean.compare(aEntry == null, bEntry == null);
-        }
-        return aEntry.compareTo(bEntry);
-    }
-
-    /**
-     * Set an entry to be expanded and therefore stick in the heads up area if it's pinned
-     * until it's collapsed again.
-     */
-    public void setExpanded(@NonNull NotificationEntry entry, boolean expanded) {
-        HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
-        if (headsUpEntry != null && entry.isRowPinned()) {
-            headsUpEntry.setExpanded(expanded);
-        }
-    }
-
-    /**
-     * Notes that the user took an action on an entry that might indirectly cause the system or the
-     * app to remove the notification.
-     *
-     * @param entry the entry that might be indirectly removed by the user's action
-     *
-     * @see HeadsUpCoordinator#mActionPressListener
-     * @see #canRemoveImmediately(String)
-     */
-    public void setUserActionMayIndirectlyRemove(@NonNull NotificationEntry entry) {
-        HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
-        if (headsUpEntry != null) {
-            headsUpEntry.mUserActionMayIndirectlyRemove = true;
-        }
-    }
-
-    /**
-     * Whether or not the entry can be removed currently.  If it hasn't been on screen long enough
-     * it should not be removed unless forced
-     * @param key the key to check if removable
-     * @return true if the entry can be removed
-     */
-    @Override
-    public boolean canRemoveImmediately(@NonNull String key) {
-        if (mSwipedOutKeys.contains(key)) {
-            // We always instantly dismiss views being manually swiped out.
-            mSwipedOutKeys.remove(key);
-            return true;
-        }
-
-        HeadsUpEntry headsUpEntry = getHeadsUpEntryPhone(key);
-        HeadsUpEntry topEntry = getTopHeadsUpEntryPhone();
-
-        if (headsUpEntry == null || headsUpEntry != topEntry) {
-            return true;
-        }
-
-        if (headsUpEntry.mUserActionMayIndirectlyRemove) {
-            return true;
-        }
-        return headsUpEntry.wasShownLongEnough()
-                || (headsUpEntry.mEntry != null && headsUpEntry.mEntry.isRowDismissed());
-    }
-
-    /**
-     * @param key
-     * @return true if the entry is (pinned and expanded) or (has an active remote input)
-     */
-    @Override
-    public boolean isSticky(String key) {
-        HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
-        if (headsUpEntry != null) {
-            return headsUpEntry.isSticky();
-        }
-        return false;
-    }
-
-    @NonNull
-    protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) {
-        if (NotificationThrottleHun.isEnabled()) {
-            return new HeadsUpEntry(entry);
-        } else {
-            HeadsUpEntry headsUpEntry = mEntryPool.acquire();
-            headsUpEntry.setEntry(entry);
-            return headsUpEntry;
-        }
-    }
-
-    /**
-     * Determines if the notification is for a critical call that must display on top of an active
-     * input notification.
-     * The call isOngoing check is for a special case of incoming calls (see b/164291424).
-     */
-    private static boolean isCriticalCallNotif(NotificationEntry entry) {
-        Notification n = entry.getSbn().getNotification();
-        boolean isIncomingCall = n.isStyle(Notification.CallStyle.class) && n.extras.getInt(
-                Notification.EXTRA_CALL_TYPE) == Notification.CallStyle.CALL_TYPE_INCOMING;
-        return isIncomingCall || (entry.getSbn().isOngoing()
-                && Notification.CATEGORY_CALL.equals(n.category));
-    }
-
-    @VisibleForTesting
-    public final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
-        if (NotificationThrottleHun.isEnabled()) {
-            mAvalancheController.setEnableAtRuntime(true);
-            if (mEntriesToRemoveWhenReorderingAllowed.isEmpty()) {
-                return;
-            }
-        }
-        mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
-        for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
-            if (entry != null && isHeadsUpEntry(entry.getKey())) {
-                // Maybe the heads-up was removed already
-                removeEntry(entry.getKey(), REASON_REORDER_ALLOWED);
-            }
-        }
-        mEntriesToRemoveWhenReorderingAllowed.clear();
-        mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
-    };
-
-    private final OnReorderingBannedListener mOnReorderingBannedListener = () -> {
-        if (mAvalancheController != null) {
-            // In open shade the first HUN is pinned, and visual stability logic prevents us from
-            // unpinning this first HUN as long as the shade remains open. AvalancheController only
-            // shows the next HUN when the currently showing HUN is unpinned, so we must disable
-            // throttling here so that the incoming HUN stream is not forever paused. This is reset
-            // when reorder becomes allowed.
-            mAvalancheController.setEnableAtRuntime(false);
-
-            // Note that we cannot do the above when
-            // 1) The remove runnable runs because its delay means it may not run before shade close
-            // 2) Reordering is allowed again (when shade closes) because the HUN appear animation
-            // will have started by then
-        }
-    };
-
-    private final StatusBarStateController.StateListener
-            mStatusBarStateListener = new StatusBarStateController.StateListener() {
-        @Override
-        public void onStateChanged(int newState) {
-            boolean wasKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
-            boolean isKeyguard = newState == StatusBarState.KEYGUARD;
-            mStatusBarState = newState;
-
-            if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
-                ArrayList<String> keysToRemove = new ArrayList<>();
-                for (HeadsUpEntry entry : getHeadsUpEntryList()) {
-                    if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) {
-                        keysToRemove.add(entry.mEntry.getKey());
-                    }
-                }
-                for (String key : keysToRemove) {
-                    removeEntry(key, "mStatusBarStateListener");
-                }
-            }
-        }
-
-        @Override
-        public void onDozingChanged(boolean isDozing) {
-            if (!isDozing) {
-                // Let's make sure all huns we got while dozing time out within the normal timeout
-                // duration. Otherwise they could get stuck for a very long time
-                for (HeadsUpEntry entry : getHeadsUpEntryList()) {
-                    entry.updateEntry(true /* updatePostTime */, "onDozingChanged(false)");
-                }
-            }
-        }
-    };
-
-    /**
-     * This represents a notification and how long it is in a heads up mode. It also manages its
-     * lifecycle automatically when created. This class is public because it is exposed by methods
-     * of AvalancheController that take it as param.
-     */
-    public class HeadsUpEntry implements Comparable<HeadsUpEntry>, HeadsUpRowRepository {
-        public boolean mRemoteInputActivatedAtLeastOnce;
-        public boolean mRemoteInputActive;
-        public boolean mUserActionMayIndirectlyRemove;
-
-        protected boolean mExpanded;
-        protected boolean mWasUnpinned;
-
-        @Nullable public NotificationEntry mEntry;
-        public long mPostTime;
-        public long mEarliestRemovalTime;
-
-        @Nullable protected Runnable mRemoveRunnable;
-
-        @Nullable private Runnable mCancelRemoveRunnable;
-
-        private boolean mGutsShownPinned;
-        private final MutableStateFlow<Boolean> mIsPinned = StateFlowKt.MutableStateFlow(false);
-
-        /**
-         * If the time this entry has been on was extended
-         */
-        private boolean extended;
-
-        public HeadsUpEntry() {
-            NotificationThrottleHun.assertInLegacyMode();
-        }
-
-        public HeadsUpEntry(NotificationEntry entry) {
-            // Attach NotificationEntry for AvalancheController to log key and
-            // record mPostTime for AvalancheController sorting
-            setEntry(entry, createRemoveRunnable(entry));
-        }
-
-        @Override
-        @NonNull
-        public String getKey() {
-            return requireEntry().getKey();
-        }
-
-        @Override
-        @NonNull
-        public Object getElementKey() {
-            return requireEntry().getRow();
-        }
-
-        private NotificationEntry requireEntry() {
-            /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode();
-            return Objects.requireNonNull(mEntry);
-        }
-
-        @Override
-        @NonNull
-        public StateFlow<Boolean> isPinned() {
-            return mIsPinned;
-        }
-
-        /** Attach a NotificationEntry. */
-        public void setEntry(@NonNull final NotificationEntry entry) {
-            NotificationThrottleHun.assertInLegacyMode();
-            setEntry(entry, createRemoveRunnable(entry));
-        }
-
-        protected void setEntry(
-                @NonNull final NotificationEntry entry,
-                @Nullable Runnable removeRunnable) {
-            mEntry = entry;
-            mRemoveRunnable = removeRunnable;
-
-            mPostTime = calculatePostTime();
-            updateEntry(true /* updatePostTime */, "setEntry");
-
-            if (NotificationThrottleHun.isEnabled()) {
-                mEntriesToRemoveWhenReorderingAllowed.add(entry);
-                if (!mVisualStabilityProvider.isReorderingAllowed()) {
-                    entry.setSeenInShade(true);
-                }
-            }
-        }
-
-        protected boolean isRowPinned() {
-            return mEntry != null && mEntry.isRowPinned();
-        }
-
-        protected void setRowPinned(boolean pinned) {
-            if (mEntry != null) mEntry.setRowPinned(pinned);
-            mIsPinned.setValue(pinned);
-        }
-
-        /**
-         * An interface that returns the amount of time left this HUN should show.
-         */
-        interface FinishTimeUpdater {
-            long updateAndGetTimeRemaining();
-        }
-
-        /**
-         * Updates an entry's removal time.
-         * @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();
-                if (updateEarliestRemovalTime) {
-                    mEarliestRemovalTime = now + mMinimumDisplayTime;
-                }
-
-                if (updatePostTime) {
-                    mPostTime = Math.max(mPostTime, now);
-                }
-            };
-            mAvalancheController.update(this, runnable, "updateEntry (updatePostTime)");
-
-            if (isSticky()) {
-                cancelAutoRemovalCallbacks("updateEntry (sticky)");
-                return;
-            }
-
-            FinishTimeUpdater finishTimeCalculator = () -> {
-                final long finishTime = calculateFinishTime();
-                final long now = mSystemClock.elapsedRealtime();
-                final long timeLeft = NotificationThrottleHun.isEnabled()
-                        ? Math.max(finishTime, mEarliestRemovalTime) - now
-                        : Math.max(finishTime - now, mMinimumDisplayTime);
-                return timeLeft;
-            };
-            scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
-
-            // Notify the manager, that the posted time has changed.
-            onEntryUpdated(this);
-
-            if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
-                mEntriesToRemoveAfterExpand.remove(mEntry);
-            }
-            if (!NotificationThrottleHun.isEnabled()) {
-                if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
-                    mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
-                }
-            }
-        }
-
-        private void extendPulse() {
-            if (!extended) {
-                extended = true;
-                updateEntry(false, "extendPulse()");
-            }
-        }
-
-        /**
-         * Whether or not the notification is "sticky" i.e. should stay on screen regardless
-         * of the timer (forever) and should be removed externally.
-         * @return true if the notification is sticky
-         */
-        public boolean isSticky() {
-            if (mGutsShownPinned) return true;
-
-            if (mEntry == null) return false;
-
-            if (ExpandHeadsUpOnInlineReply.isEnabled()) {
-                // we don't consider pinned and expanded huns as sticky after the remote input
-                // has been activated for them
-                if (!mRemoteInputActive && mRemoteInputActivatedAtLeastOnce) {
-                    return false;
-                }
-            }
-
-            return (mEntry.isRowPinned() && mExpanded)
-                    || mRemoteInputActive
-                    || hasFullScreenIntent(mEntry);
-        }
-
-        public boolean isStickyForSomeTime() {
-            if (mEntry == null) return false;
-
-            return mEntry.isStickyAndNotDemoted();
-        }
-
-        /**
-         * Whether the notification has been on screen long enough and can be removed.
-         * @return true if the notification has been on screen long enough
-         */
-        public boolean wasShownLongEnough() {
-            return mEarliestRemovalTime < mSystemClock.elapsedRealtime();
-        }
-
-        public int compareNonTimeFields(HeadsUpEntry headsUpEntry) {
-            if (mEntry == null && headsUpEntry.mEntry == null) {
-                return 0;
-            } else if (headsUpEntry.mEntry == null) {
-                return -1;
-            } else if (mEntry == null) {
-                return 1;
-            }
-
-            boolean selfFullscreen = hasFullScreenIntent(mEntry);
-            boolean otherFullscreen = hasFullScreenIntent(headsUpEntry.mEntry);
-            if (selfFullscreen && !otherFullscreen) {
-                return -1;
-            } else if (!selfFullscreen && otherFullscreen) {
-                return 1;
-            }
-
-            boolean selfCall = isCriticalCallNotif(mEntry);
-            boolean otherCall = isCriticalCallNotif(headsUpEntry.mEntry);
-
-            if (selfCall && !otherCall) {
-                return -1;
-            } else if (!selfCall && otherCall) {
-                return 1;
-            }
-
-            if (mRemoteInputActive && !headsUpEntry.mRemoteInputActive) {
-                return -1;
-            } else if (!mRemoteInputActive && headsUpEntry.mRemoteInputActive) {
-                return 1;
-            }
-            return 0;
-        }
-
-        public int compareTo(@NonNull HeadsUpEntry headsUpEntry) {
-            if (mEntry == null && headsUpEntry.mEntry == null) {
-                return 0;
-            } else if (headsUpEntry.mEntry == null) {
-                return -1;
-            } else if (mEntry == null) {
-                return 1;
-            }
-            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;
-            }
-            if (mPostTime > headsUpEntry.mPostTime) {
-                return -1;
-            } else if (mPostTime == headsUpEntry.mPostTime) {
-                return mEntry.getKey().compareTo(headsUpEntry.mEntry.getKey());
-            } else {
-                return 1;
-            }
-        }
-
-        @Override
-        public int hashCode() {
-            if (mEntry == null) return super.hashCode();
-            int result = mEntry.getKey().hashCode();
-            result = 31 * result;
-            return result;
-        }
-
-        @Override
-        public boolean equals(@Nullable Object o) {
-            if (this == o) return true;
-            if (o == null || !(o instanceof HeadsUpEntry)) return false;
-            HeadsUpEntry otherHeadsUpEntry = (HeadsUpEntry) o;
-            if (mEntry != null && otherHeadsUpEntry.mEntry != null) {
-                return mEntry.getKey().equals(otherHeadsUpEntry.mEntry.getKey());
-            }
-            return false;
-        }
-
-        public void setExpanded(boolean expanded) {
-            if (this.mExpanded == expanded) {
-                return;
-            }
-
-            this.mExpanded = expanded;
-            if (expanded) {
-                cancelAutoRemovalCallbacks("setExpanded(true)");
-            } else {
-                updateEntry(false /* updatePostTime */, "setExpanded(false)");
-            }
-        }
-
-        public void setGutsShownPinned(boolean gutsShownPinned) {
-            if (mGutsShownPinned == gutsShownPinned) {
-                return;
-            }
-
-            mGutsShownPinned = gutsShownPinned;
-            if (gutsShownPinned) {
-                cancelAutoRemovalCallbacks("setGutsShownPinned(true)");
-            } else {
-                updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)");
-            }
-        }
-
-        public void reset() {
-            NotificationThrottleHun.assertInLegacyMode();
-            cancelAutoRemovalCallbacks("reset()");
-            mEntry = null;
-            mRemoveRunnable = null;
-            mExpanded = false;
-            mRemoteInputActive = false;
-            mGutsShownPinned = false;
-            extended = false;
-        }
-
-        /**
-         * Clear any pending removal runnables.
-         */
-        public void cancelAutoRemovalCallbacks(@Nullable String reason) {
-            Runnable runnable = () -> {
-                final boolean removed = cancelAutoRemovalCallbackInternal();
-
-                if (removed) {
-                    mLogger.logAutoRemoveCanceled(mEntry, reason);
-                }
-            };
-            if (mEntry != null && isHeadsUpEntry(mEntry.getKey())) {
-                mLogger.logAutoRemoveCancelRequest(this.mEntry, reason);
-                mAvalancheController.update(this, runnable, reason + " cancelAutoRemovalCallbacks");
-            } else {
-                // Just removed
-                runnable.run();
-            }
-        }
-
-        public void scheduleAutoRemovalCallback(FinishTimeUpdater finishTimeCalculator,
-                @NonNull String reason) {
-
-            mLogger.logAutoRemoveRequest(this.mEntry, reason);
-            Runnable runnable = () -> {
-                long delayMs = finishTimeCalculator.updateAndGetTimeRemaining();
-
-                if (mRemoveRunnable == null) {
-                    Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
-                    return;
-                }
-
-                final boolean deletedExistingRemovalRunnable = cancelAutoRemovalCallbackInternal();
-                mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
-                        delayMs);
-
-                if (deletedExistingRemovalRunnable) {
-                    mLogger.logAutoRemoveRescheduled(mEntry, delayMs, reason);
-                } else {
-                    mLogger.logAutoRemoveScheduled(mEntry, delayMs, reason);
-                }
-            };
-            mAvalancheController.update(this, runnable,
-                    reason + " scheduleAutoRemovalCallback");
-        }
-
-        public boolean cancelAutoRemovalCallbackInternal() {
-            final boolean scheduled = (mCancelRemoveRunnable != null);
-
-            if (scheduled) {
-                mCancelRemoveRunnable.run();  // Delete removal runnable from Executor queue
-                mCancelRemoveRunnable = null;
-            }
-
-            return scheduled;
-        }
-
-        /**
-         * Remove the entry at the earliest allowed removal time.
-         */
-        public void removeAsSoonAsPossible() {
-            if (mRemoveRunnable != null) {
-
-                FinishTimeUpdater finishTimeCalculator = () -> {
-                    final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
-                    return timeLeft;
-                };
-                scheduleAutoRemovalCallback(finishTimeCalculator, "removeAsSoonAsPossible");
-            }
-        }
-
-        /** Creates a runnable to remove this notification from the alerting entries. */
-        protected Runnable createRemoveRunnable(NotificationEntry entry) {
-            return () -> {
-                if (!NotificationThrottleHun.isEnabled()
-                        && !mVisualStabilityProvider.isReorderingAllowed()
-                        // We don't want to allow reordering while pulsing, but headsup need to
-                        // time out anyway
-                        && !entry.showingPulsing()) {
-                    mEntriesToRemoveWhenReorderingAllowed.add(entry);
-                    mVisualStabilityProvider.addTemporaryReorderingAllowedListener(
-                            mOnReorderingAllowedListener);
-                } else if (mTrackingHeadsUp) {
-                    mEntriesToRemoveAfterExpand.add(entry);
-                    mLogger.logRemoveEntryAfterExpand(entry);
-                } else if (mVisualStabilityProvider.isReorderingAllowed()
-                        || entry.showingPulsing()) {
-                    removeEntry(entry.getKey(), "createRemoveRunnable");
-                }
-            };
-        }
-
-        /**
-         * Calculate what the post time of a notification is at some current time.
-         * @return the post time
-         */
-        protected long calculatePostTime() {
-            // The actual post time will be just after the heads-up really slided in
-            return mSystemClock.elapsedRealtime() + mTouchAcceptanceDelay;
-        }
-
-        /**
-         * @return When the notification should auto-dismiss itself, based on
-         * {@link SystemClock#elapsedRealtime()}
-         */
-        protected long calculateFinishTime() {
-            int requestedTimeOutMs;
-            if (isStickyForSomeTime()) {
-                requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime;
-            } else {
-                requestedTimeOutMs = mAvalancheController.getDurationMs(this, mAutoDismissTime);
-            }
-            final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs);
-            return mPostTime + duration + (extended ? mExtensionTime : 0);
-        }
-
-        /**
-         * Get user-preferred or default timeout duration. The larger one will be returned.
-         * @return milliseconds before auto-dismiss
-         * @param requestedTimeout
-         */
-        protected int getRecommendedHeadsUpTimeoutMs(int requestedTimeout) {
-            return mAccessibilityMgr.getRecommendedTimeoutMillis(
-                    requestedTimeout,
-                    AccessibilityManager.FLAG_CONTENT_CONTROLS
-                            | AccessibilityManager.FLAG_CONTENT_ICONS
-                            | AccessibilityManager.FLAG_CONTENT_TEXT);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
index a3dcc3b..ece5a3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.media.projection.StopReason;
 import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.policy.CastController.Callback;
 
@@ -26,7 +27,7 @@
     void setCurrentUserId(int currentUserId);
     List<CastDevice> getCastDevices();
     void startCasting(CastDevice device);
-    void stopCasting(CastDevice device);
+    void stopCasting(CastDevice device, @StopReason int stopReason);
 
     /**
      * @return whether we have a connected device.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index a115baa..ab20850 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -24,6 +24,7 @@
 import android.media.MediaRouter.RouteInfo;
 import android.media.projection.MediaProjectionInfo;
 import android.media.projection.MediaProjectionManager;
+import android.media.projection.StopReason;
 import android.os.Handler;
 import android.util.ArrayMap;
 
@@ -184,13 +185,13 @@
     }
 
     @Override
-    public void stopCasting(CastDevice device) {
+    public void stopCasting(CastDevice device, @StopReason int stopReason) {
         final boolean isProjection = device.getTag() instanceof MediaProjectionInfo;
         mLogger.logStopCasting(isProjection);
         if (isProjection) {
             final MediaProjectionInfo projection = (MediaProjectionInfo) device.getTag();
             if (Objects.equals(mProjectionManager.getActiveProjectionInfo(), projection)) {
-                mProjectionManager.stopActiveProjection();
+                mProjectionManager.stopActiveProjection(stopReason);
             } else {
                 mLogger.logStopCastingNoProjection(projection);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
deleted file mode 100644
index b37194b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
+++ /dev/null
@@ -1,307 +0,0 @@
-package com.android.systemui.statusbar.policy
-
-import android.graphics.Region
-import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import dagger.Binds
-import dagger.Module
-import java.io.PrintWriter
-import java.util.stream.Stream
-import javax.inject.Inject
-
-/**
- * A manager which handles heads up notifications which is a special mode where they simply peek
- * from the top of the screen.
- */
-interface HeadsUpManager : Dumpable {
-    /** The stream of all current notifications managed by this manager. */
-    val allEntries: Stream<NotificationEntry>
-
-    /** Add a listener to receive callbacks onHeadsUpGoingAway. */
-    fun addHeadsUpPhoneListener(listener: OnHeadsUpPhoneListenerChange)
-
-    /** Adds an OnHeadUpChangedListener to observe events. */
-    fun addListener(listener: OnHeadsUpChangedListener)
-
-    fun addSwipedOutNotification(key: String)
-
-    /**
-     * Whether or not the alert can be removed currently. If it hasn't been on screen long enough it
-     * should not be removed unless forced
-     *
-     * @param key the key to check if removable
-     * @return true if the alert entry can be removed
-     */
-    fun canRemoveImmediately(key: String): Boolean
-
-    /**
-     * Compare two entries and decide how they should be ranked.
-     *
-     * @return -1 if the first argument should be ranked higher than the second, 1 if the second one
-     *   should be ranked higher and 0 if they are equal.
-     */
-    fun compare(a: NotificationEntry?, b: NotificationEntry?): Int
-
-    /**
-     * Extends the lifetime of the currently showing pulsing notification so that the pulse lasts
-     * longer.
-     */
-    fun extendHeadsUp()
-
-    /** Returns when a HUN entry should be removed in milliseconds from now. */
-    fun getEarliestRemovalTime(key: String?): Long
-
-    /** Returns the top Heads Up Notification, which appears to show at first. */
-    fun getTopEntry(): NotificationEntry?
-
-    /**
-     * Gets the touchable region needed for heads up notifications. Returns null if no touchable
-     * region is required (ie: no heads up notification currently exists).
-     */
-    // TODO(b/347007367): With scene container enabled this method may report outdated regions
-    fun getTouchableRegion(): Region?
-
-    /**
-     * Whether or not there are any entries managed by HeadsUpManager.
-     *
-     * @return true if there is a heads up entry, false otherwise
-     */
-    fun hasNotifications(): Boolean = false
-
-    /** Returns whether there are any pinned Heads Up Notifications or not. */
-    fun hasPinnedHeadsUp(): Boolean
-
-    /** Returns whether or not the given notification is managed by this manager. */
-    fun isHeadsUpEntry(key: String): Boolean
-
-    /** @see setHeadsUpAnimatingAway */
-    fun isHeadsUpAnimatingAwayValue(): Boolean
-
-    /** Returns if the given notification is snoozed or not. */
-    fun isSnoozed(packageName: String): Boolean
-
-    /** Returns whether the entry is (pinned and expanded) or (has an active remote input). */
-    fun isSticky(key: String?): Boolean
-
-    /**
-     * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as
-     * well.
-     */
-    fun isTrackingHeadsUp(): Boolean
-
-    fun onExpandingFinished()
-
-    /** Removes the OnHeadUpChangedListener from the observer list. */
-    fun removeListener(listener: OnHeadsUpChangedListener)
-
-    /**
-     * Try to remove the notification. May not succeed if the notification has not been shown long
-     * enough and needs to be kept around.
-     *
-     * @param key the key of the notification to remove
-     * @param releaseImmediately force a remove regardless of earliest removal time
-     * @param reason reason for removing the notification
-     * @return true if notification is removed, false otherwise
-     */
-    fun removeNotification(key: String, releaseImmediately: Boolean, reason: String): Boolean
-
-    /**
-     * Try to remove the notification. May not succeed if the notification has not been shown long
-     * enough and needs to be kept around.
-     *
-     * @param key the key of the notification to remove
-     * @param releaseImmediately force a remove regardless of earliest removal time
-     * @param animate if true, animate the removal
-     * @param reason reason for removing the notification
-     * @return true if notification is removed, false otherwise
-     */
-    fun removeNotification(
-        key: String,
-        releaseImmediately: Boolean,
-        animate: Boolean,
-        reason: String,
-    ): Boolean
-
-    /** Clears all managed notifications. */
-    fun releaseAllImmediately()
-
-    fun setAnimationStateHandler(handler: AnimationStateHandler)
-
-    /**
-     * Set an entry to be expanded and therefore stick in the heads up area if it's pinned until
-     * it's collapsed again.
-     */
-    fun setExpanded(entry: NotificationEntry, expanded: Boolean)
-
-    /**
-     * Sets whether an entry's guts are exposed and therefore it should stick in the heads up area
-     * if it's pinned until it's hidden again.
-     */
-    fun setGutsShown(entry: NotificationEntry, gutsShown: Boolean)
-
-    /**
-     * Set that we are exiting the headsUp pinned mode, but some notifications might still be
-     * animating out. This is used to keep the touchable regions in a reasonable state.
-     */
-    fun setHeadsUpAnimatingAway(headsUpAnimatingAway: Boolean)
-
-    /**
-     * Notifies that a remote input textbox in notification gets active or inactive.
-     *
-     * @param entry The entry of the target notification.
-     * @param remoteInputActive True to notify active, False to notify inactive.
-     */
-    fun setRemoteInputActive(entry: NotificationEntry, remoteInputActive: Boolean)
-
-    /**
-     * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry
-     * from the list even after a Heads Up Notification is gone.
-     */
-    fun setTrackingHeadsUp(tracking: Boolean)
-
-    /** Sets the current user. */
-    fun setUser(user: Int)
-
-    /**
-     * Notes that the user took an action on an entry that might indirectly cause the system or the
-     * app to remove the notification.
-     *
-     * @param entry the entry that might be indirectly removed by the user's action
-     * @see
-     *   com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator.mActionPressListener
-     * @see .canRemoveImmediately
-     */
-    fun setUserActionMayIndirectlyRemove(entry: NotificationEntry)
-
-    /**
-     * Decides whether a click is invalid for a notification, i.e. it has not been shown long enough
-     * that a user might have consciously clicked on it.
-     *
-     * @param key the key of the touched notification
-     * @return whether the touch is invalid and should be discarded
-     */
-    fun shouldSwallowClick(key: String): Boolean
-
-    /**
-     * Called when posting a new notification that should alert the user and appear on screen. Adds
-     * the notification to be managed.
-     *
-     * @param entry entry to show
-     */
-    fun showNotification(entry: NotificationEntry)
-
-    fun snooze()
-
-    /**
-     * Unpins all pinned Heads Up Notifications.
-     *
-     * @param userUnPinned The unpinned action is trigger by user real operation.
-     */
-    fun unpinAll(userUnPinned: Boolean)
-
-    fun updateNotification(key: String, shouldHeadsUpAgain: Boolean)
-
-    fun onEntryAnimatingAwayEnded(entry: NotificationEntry)
-}
-
-/** Sets the animation state of the HeadsUpManager. */
-interface AnimationStateHandler {
-    fun setHeadsUpGoingAwayAnimationsAllowed(allowed: Boolean)
-}
-
-/** Listener to register for HeadsUpNotification Phone changes. */
-interface OnHeadsUpPhoneListenerChange {
-    /**
-     * Called when a heads up notification is 'going away' or no longer 'going away'. See
-     * [HeadsUpManager.setHeadsUpAnimatingAway].
-     */
-    // TODO(b/325936094) delete this callback, and listen to the flow instead
-    fun onHeadsUpAnimatingAwayStateChanged(headsUpAnimatingAway: Boolean)
-}
-
-/* No op impl of HeadsUpManager. */
-class HeadsUpManagerEmptyImpl @Inject constructor() : HeadsUpManager {
-    override val allEntries = Stream.empty<NotificationEntry>()
-
-    override fun addHeadsUpPhoneListener(listener: OnHeadsUpPhoneListenerChange) {}
-
-    override fun addListener(listener: OnHeadsUpChangedListener) {}
-
-    override fun addSwipedOutNotification(key: String) {}
-
-    override fun canRemoveImmediately(key: String) = false
-
-    override fun compare(a: NotificationEntry?, b: NotificationEntry?) = 0
-
-    override fun dump(pw: PrintWriter, args: Array<out String>) {}
-
-    override fun extendHeadsUp() {}
-
-    override fun getEarliestRemovalTime(key: String?) = 0L
-
-    override fun getTouchableRegion(): Region? = null
-
-    override fun getTopEntry() = null
-
-    override fun hasPinnedHeadsUp() = false
-
-    override fun isHeadsUpEntry(key: String) = false
-
-    override fun isHeadsUpAnimatingAwayValue() = false
-
-    override fun isSnoozed(packageName: String) = false
-
-    override fun isSticky(key: String?) = false
-
-    override fun isTrackingHeadsUp() = false
-
-    override fun onExpandingFinished() {}
-
-    override fun releaseAllImmediately() {}
-
-    override fun removeListener(listener: OnHeadsUpChangedListener) {}
-
-    override fun removeNotification(key: String, releaseImmediately: Boolean, reason: String) =
-        false
-
-    override fun removeNotification(
-        key: String,
-        releaseImmediately: Boolean,
-        animate: Boolean,
-        reason: String,
-    ) = false
-
-    override fun setAnimationStateHandler(handler: AnimationStateHandler) {}
-
-    override fun setExpanded(entry: NotificationEntry, expanded: Boolean) {}
-
-    override fun setGutsShown(entry: NotificationEntry, gutsShown: Boolean) {}
-
-    override fun setHeadsUpAnimatingAway(headsUpAnimatingAway: Boolean) {}
-
-    override fun setRemoteInputActive(entry: NotificationEntry, remoteInputActive: Boolean) {}
-
-    override fun setTrackingHeadsUp(tracking: Boolean) {}
-
-    override fun setUser(user: Int) {}
-
-    override fun setUserActionMayIndirectlyRemove(entry: NotificationEntry) {}
-
-    override fun shouldSwallowClick(key: String): Boolean = false
-
-    override fun showNotification(entry: NotificationEntry) {}
-
-    override fun snooze() {}
-
-    override fun unpinAll(userUnPinned: Boolean) {}
-
-    override fun updateNotification(key: String, alert: Boolean) {}
-
-    override fun onEntryAnimatingAwayEnded(entry: NotificationEntry) {}
-}
-
-@Module
-interface HeadsUpEmptyImplModule {
-    @Binds @SysUISingleton fun bindsHeadsUpManager(noOpHum: HeadsUpManagerEmptyImpl): HeadsUpManager
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerExt.kt
deleted file mode 100644
index 5e36750..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerExt.kt
+++ /dev/null
@@ -1,38 +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.statusbar.policy
-
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-/**
- * Returns a [Flow] that emits events whenever a [NotificationEntry] enters or exists the "heads up"
- * state.
- */
-val HeadsUpManager.headsUpEvents: Flow<Pair<NotificationEntry, Boolean>>
-    get() = conflatedCallbackFlow {
-        val listener =
-            object : OnHeadsUpChangedListener {
-                override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
-                    trySend(entry to isHeadsUp)
-                }
-            }
-        addListener(listener)
-        awaitClose { removeListener(listener) }
-    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
deleted file mode 100644
index 600270c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy
-
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel.INFO
-import com.android.systemui.log.core.LogLevel.VERBOSE
-import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.logKey
-import javax.inject.Inject
-
-/** Logger for [HeadsUpManager]. */
-class HeadsUpManagerLogger
-@Inject
-constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
-    fun logPackageSnoozed(snoozeKey: String) {
-        buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package snoozed $str1" })
-    }
-
-    fun logPackageUnsnoozed(snoozeKey: String) {
-        buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package unsnoozed $str1" })
-    }
-
-    fun logIsSnoozedReturned(snoozeKey: String) {
-        buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package snoozed when queried $str1" })
-    }
-
-    fun logReleaseAllImmediately() {
-        buffer.log(TAG, INFO, {}, { "release all immediately" })
-    }
-
-    fun logShowNotificationRequest(entry: NotificationEntry) {
-        buffer.log(TAG, INFO, { str1 = entry.logKey }, { "request: show notification $str1" })
-    }
-
-    fun logAvalancheUpdate(
-        caller: String,
-        isEnabled: Boolean,
-        notifEntryKey: String,
-        outcome: String
-    ) {
-        buffer.log(
-            TAG,
-            INFO,
-            {
-                str1 = caller
-                str2 = notifEntryKey
-                str3 = outcome
-                bool1 = isEnabled
-            },
-            { "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3" }
-        )
-    }
-
-    fun logAvalancheDelete(
-        caller: String,
-        isEnabled: Boolean,
-        notifEntryKey: String,
-        outcome: String
-    ) {
-        buffer.log(
-            TAG,
-            INFO,
-            {
-                str1 = caller
-                str2 = notifEntryKey
-                str3 = outcome
-                bool1 = isEnabled
-            },
-            { "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3" }
-        )
-    }
-
-    fun logShowNotification(entry: NotificationEntry) {
-        buffer.log(TAG, INFO, { str1 = entry.logKey }, { "show notification $str1" })
-    }
-
-    fun logAutoRemoveScheduled(entry: NotificationEntry, delayMillis: Long, reason: String) {
-        buffer.log(
-            TAG,
-            INFO,
-            {
-                str1 = entry.logKey
-                long1 = delayMillis
-                str2 = reason
-            },
-            { "schedule auto remove of $str1 in $long1 ms reason: $str2" }
-        )
-    }
-
-    fun logAutoRemoveRequest(entry: NotificationEntry, reason: String) {
-        buffer.log(
-            TAG,
-            INFO,
-            {
-                str1 = entry.logKey
-                str2 = reason
-            },
-            { "request: reschedule auto remove of $str1 reason: $str2" }
-        )
-    }
-
-    fun logAutoRemoveRescheduled(entry: NotificationEntry, delayMillis: Long, reason: String) {
-        buffer.log(
-            TAG,
-            INFO,
-            {
-                str1 = entry.logKey
-                long1 = delayMillis
-                str2 = reason
-            },
-            { "reschedule auto remove of $str1 in $long1 ms reason: $str2" }
-        )
-    }
-
-    fun logAutoRemoveCancelRequest(entry: NotificationEntry, reason: String?) {
-        buffer.log(
-            TAG,
-            INFO,
-            {
-                str1 = entry.logKey
-                str2 = reason ?: "unknown"
-            },
-            { "request: cancel auto remove of $str1 reason: $str2" }
-        )
-    }
-
-    fun logAutoRemoveCanceled(entry: NotificationEntry, reason: String?) {
-        buffer.log(
-            TAG,
-            INFO,
-            {
-                str1 = entry.logKey
-                str2 = reason ?: "unknown"
-            },
-            { "cancel auto remove of $str1 reason: $str2" }
-        )
-    }
-
-    fun logRemoveEntryRequest(key: String, reason: String, isWaiting: Boolean) {
-        buffer.log(
-            TAG,
-            INFO,
-            {
-                str1 = logKey(key)
-                str2 = reason
-                bool1 = isWaiting
-            },
-            { "request: $str2 => remove entry $str1 isWaiting: $isWaiting" }
-        )
-    }
-
-    fun logRemoveEntry(key: String, reason: String, isWaiting: Boolean) {
-        buffer.log(
-            TAG,
-            INFO,
-            {
-                str1 = logKey(key)
-                str2 = reason
-                bool1 = isWaiting
-            },
-            { "$str2 => remove entry $str1 isWaiting: $isWaiting" }
-        )
-    }
-
-    fun logUnpinEntryRequest(key: String) {
-        buffer.log(TAG, INFO, { str1 = logKey(key) }, { "request: unpin entry $str1" })
-    }
-
-    fun logUnpinEntry(key: String) {
-        buffer.log(TAG, INFO, { str1 = logKey(key) }, { "unpin entry $str1" })
-    }
-
-    fun logRemoveNotification(
-        key: String,
-        releaseImmediately: Boolean,
-        isWaiting: Boolean,
-        reason: String
-    ) {
-        buffer.log(
-            TAG,
-            INFO,
-            {
-                str1 = logKey(key)
-                bool1 = releaseImmediately
-                bool2 = isWaiting
-                str2 = reason
-            },
-            {
-                "remove notification $str1 releaseImmediately: $bool1 isWaiting: $bool2 " +
-                    "reason: $str2"
-            }
-        )
-    }
-
-    fun logNullEntry(key: String, reason: String) {
-        buffer.log(
-            TAG,
-            INFO,
-            {
-                str1 = logKey(key)
-                str2 = reason
-            },
-            { "remove notification $str1 when headsUpEntry is null, reason: $str2" }
-        )
-    }
-
-    fun logNotificationActuallyRemoved(entry: NotificationEntry) {
-        buffer.log(TAG, INFO, { str1 = entry.logKey }, { "notification removed $str1 " })
-    }
-
-    fun logUpdateNotificationRequest(key: String, alert: Boolean, hasEntry: Boolean) {
-        buffer.log(
-            TAG,
-            INFO,
-            {
-                str1 = logKey(key)
-                bool1 = alert
-                bool2 = hasEntry
-            },
-            { "request: update notification $str1 alert: $bool1 hasEntry: $bool2" }
-        )
-    }
-
-    fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) {
-        buffer.log(
-            TAG,
-            INFO,
-            {
-                str1 = logKey(key)
-                bool1 = alert
-                bool2 = hasEntry
-            },
-            { "update notification $str1 alert: $bool1 hasEntry: $bool2" }
-        )
-    }
-
-    fun logUpdateEntry(entry: NotificationEntry, updatePostTime: Boolean, reason: String?) {
-        buffer.log(
-            TAG,
-            INFO,
-            {
-                str1 = entry.logKey
-                bool1 = updatePostTime
-                str2 = reason ?: "unknown"
-            },
-            { "update entry $str1 updatePostTime: $bool1 reason: $str2" }
-        )
-    }
-
-    fun logSnoozeLengthChange(packageSnoozeLengthMs: Int) {
-        buffer.log(
-            TAG,
-            INFO,
-            { int1 = packageSnoozeLengthMs },
-            { "snooze length changed: ${int1}ms" }
-        )
-    }
-
-    fun logSetEntryPinned(entry: NotificationEntry, isPinned: Boolean, reason: String) {
-        buffer.log(
-            TAG,
-            VERBOSE,
-            {
-                str1 = entry.logKey
-                bool1 = isPinned
-                str2 = reason
-            },
-            { "$str2 => set entry pinned $str1 pinned: $bool1" }
-        )
-    }
-
-    fun logUpdatePinnedMode(hasPinnedNotification: Boolean) {
-        buffer.log(
-            TAG,
-            INFO,
-            { bool1 = hasPinnedNotification },
-            { "has pinned notification changed to $bool1" }
-        )
-    }
-
-    fun logRemoveEntryAfterExpand(entry: NotificationEntry) {
-        buffer.log(TAG, VERBOSE, {
-            str1 = entry.logKey
-        }, {
-            "remove entry after expand: $str1"
-        })
-    }
-}
-
-private const val TAG = "HeadsUpManager"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
deleted file mode 100644
index f4a1975..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.Log;
-import android.view.View;
-
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.util.Compile;
-
-/**
- * A class of utility static methods for heads up notifications.
- */
-public final class HeadsUpUtil {
-    private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
-
-    private static final String LOG_TAG = "HeadsUpUtil";
-    private static final boolean LOG_DEBUG = Compile.IS_DEBUG && Log.isLoggable(LOG_TAG, Log.DEBUG);
-
-    /**
-     * Set the given view as clicked or not-clicked.
-     * @param view The view to be set the flag to.
-     * @param clicked True to set as clicked. False to not-clicked.
-     */
-    public static void setNeedsHeadsUpDisappearAnimationAfterClick(View view, boolean clicked) {
-        if (LOG_DEBUG) {
-            logTagClickedNotificationChanged(view, clicked);
-        }
-        view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
-    }
-
-    /**
-     * Check if the given view has the flag of "clicked notification"
-     * @param view The view to be checked.
-     * @return True if the view has clicked. False othrewise.
-     */
-    public static boolean isClickedHeadsUpNotification(View view) {
-        Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION);
-        return clicked != null && clicked;
-    }
-
-    private static void logTagClickedNotificationChanged(@Nullable View view, boolean isClicked) {
-        if (view == null) {
-            return;
-        }
-
-        final boolean wasClicked = isClickedHeadsUpNotification(view);
-        if (isClicked == wasClicked) {
-            return;
-        }
-
-        Log.d(LOG_TAG, getViewKey(view) + ": TAG_CLICKED_NOTIFICATION set to " + isClicked);
-    }
-
-    private static @NonNull String getViewKey(@NonNull View view) {
-        if (!(view instanceof ExpandableNotificationRow)) {
-            return "(not a row)";
-        }
-
-        final ExpandableNotificationRow row = (ExpandableNotificationRow) view;
-        final NotificationEntry entry = row.getEntry();
-        if (entry == null) {
-            return "(null entry)";
-        }
-
-        final String key = entry.getKey();
-        if (key == null) {
-            return "(null key)";
-        }
-
-        return key;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index d210e93..c03ba01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -26,7 +26,12 @@
 
 /**
  * Source of truth for keyguard state: If locked, occluded, has password, trusted etc.
+ *
+ * @deprecated this class is not supported when KEYGUARD_WM_STATE_REFACTOR is enabled.
+ * Use {@link com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor}
+ * or {@link com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor} instead.
  */
+@Deprecated
 public interface KeyguardStateController extends CallbackController<Callback>, Dumpable {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
deleted file mode 100644
index de3bf04..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
+++ /dev/null
@@ -1,56 +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.statusbar.policy;
-
-import android.annotation.NonNull;
-
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-/**
- * A listener to heads up changes
- */
-public interface OnHeadsUpChangedListener {
-    /**
-     * The state whether there exist pinned heads-ups or not changed.
-     *
-     * @param inPinnedMode whether there are any pinned heads-ups
-     */
-    default void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {}
-
-    /**
-     * A notification was just pinned to the top.
-     */
-    default void onHeadsUpPinned(NotificationEntry entry) {}
-
-    /**
-     * A notification was just unpinned from the top.
-     */
-    default void onHeadsUpUnPinned(NotificationEntry entry) {}
-
-    /**
-     * A notification just became a heads up or turned back to its normal state.
-     *
-     * @param entry     the entry of the changed notification
-     * @param isHeadsUp whether the notification is now a headsUp notification
-     */
-    default void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {}
-
-    /**
-     * Called on HUN disappearing animation ends
-     */
-    default void onHeadsUpAnimatingAwayEnded(@NonNull NotificationEntry entry) {}
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index 616992e..56c9e9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -41,8 +41,8 @@
 import android.view.accessibility.AccessibilityNodeInfo
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
 import android.widget.Button
-import com.android.systemui.res.R
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.shared.system.ActivityManagerWrapper
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper
 import com.android.systemui.shared.system.PackageManagerWrapper
@@ -50,6 +50,7 @@
 import com.android.systemui.statusbar.NotificationUiAdjustment
 import com.android.systemui.statusbar.SmartReplyController
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState.SuppressedActions
@@ -63,40 +64,42 @@
 import javax.inject.Inject
 import kotlin.system.measureTimeMillis
 
-
 /** Returns whether we should show the smart reply view and its smart suggestions. */
 fun shouldShowSmartReplyView(
     entry: NotificationEntry,
-    smartReplyState: InflatedSmartReplyState
+    smartReplyState: InflatedSmartReplyState,
 ): Boolean {
-    if (smartReplyState.smartReplies == null &&
-            smartReplyState.smartActions == null) {
+    if (smartReplyState.smartReplies == null && smartReplyState.smartActions == null) {
         // There are no smart replies and no smart actions.
         return false
     }
     // If we are showing the spinner we don't want to add the buttons.
-    val showingSpinner = entry.sbn.notification.extras
-            .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)
+    val showingSpinner =
+        entry.sbn.notification.extras.getBoolean(
+            Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER,
+            false,
+        )
     if (showingSpinner) {
         return false
     }
     // If we are keeping the notification around while sending we don't want to add the buttons.
-    return !entry.sbn.notification.extras
-            .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)
+    return !entry.sbn.notification.extras.getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)
 }
 
 /** Determines if two [InflatedSmartReplyState] are visually similar. */
 fun areSuggestionsSimilar(
     left: InflatedSmartReplyState?,
-    right: InflatedSmartReplyState?
-): Boolean = when {
-    left === right -> true
-    left == null || right == null -> false
-    left.hasPhishingAction != right.hasPhishingAction -> false
-    left.smartRepliesList != right.smartRepliesList -> false
-    left.suppressedActionIndices != right.suppressedActionIndices -> false
-    else -> !NotificationUiAdjustment.areDifferent(left.smartActionsList, right.smartActionsList)
-}
+    right: InflatedSmartReplyState?,
+): Boolean =
+    when {
+        left === right -> true
+        left == null || right == null -> false
+        left.hasPhishingAction != right.hasPhishingAction -> false
+        left.smartRepliesList != right.smartRepliesList -> false
+        left.suppressedActionIndices != right.suppressedActionIndices -> false
+        else ->
+            !NotificationUiAdjustment.areDifferent(left.smartActionsList, right.smartActionsList)
+    }
 
 interface SmartReplyStateInflater {
     fun inflateSmartReplyState(entry: NotificationEntry): InflatedSmartReplyState
@@ -106,181 +109,211 @@
         notifPackageContext: Context,
         entry: NotificationEntry,
         existingSmartReplyState: InflatedSmartReplyState?,
-        newSmartReplyState: InflatedSmartReplyState
+        newSmartReplyState: InflatedSmartReplyState,
     ): InflatedSmartReplyViewHolder
 }
 
-/*internal*/ class SmartReplyStateInflaterImpl @Inject constructor(
+/*internal*/ class SmartReplyStateInflaterImpl
+@Inject
+constructor(
     private val constants: SmartReplyConstants,
     private val activityManagerWrapper: ActivityManagerWrapper,
     private val packageManagerWrapper: PackageManagerWrapper,
     private val devicePolicyManagerWrapper: DevicePolicyManagerWrapper,
     private val smartRepliesInflater: SmartReplyInflater,
-    private val smartActionsInflater: SmartActionInflater
+    private val smartActionsInflater: SmartActionInflater,
 ) : SmartReplyStateInflater {
 
     override fun inflateSmartReplyState(entry: NotificationEntry): InflatedSmartReplyState =
-            chooseSmartRepliesAndActions(entry)
+        chooseSmartRepliesAndActions(entry)
 
     override fun inflateSmartReplyViewHolder(
         sysuiContext: Context,
         notifPackageContext: Context,
         entry: NotificationEntry,
         existingSmartReplyState: InflatedSmartReplyState?,
-        newSmartReplyState: InflatedSmartReplyState
+        newSmartReplyState: InflatedSmartReplyState,
     ): InflatedSmartReplyViewHolder {
         if (!shouldShowSmartReplyView(entry, newSmartReplyState)) {
             return InflatedSmartReplyViewHolder(
-                    null /* smartReplyView */,
-                    null /* smartSuggestionButtons */)
+                null /* smartReplyView */,
+                null, /* smartSuggestionButtons */
+            )
         }
 
         // Only block clicks if the smart buttons are different from the previous set - to avoid
         // scenarios where a user incorrectly cannot click smart buttons because the
         // notification is updated.
         val delayOnClickListener =
-                !areSuggestionsSimilar(existingSmartReplyState, newSmartReplyState)
+            !areSuggestionsSimilar(existingSmartReplyState, newSmartReplyState)
 
         val smartReplyView = SmartReplyView.inflate(sysuiContext, constants)
 
         val smartReplies = newSmartReplyState.smartReplies
         smartReplyView.setSmartRepliesGeneratedByAssistant(smartReplies?.fromAssistant ?: false)
-        val smartReplyButtons = smartReplies?.let {
-            smartReplies.choices.asSequence().mapIndexed { index, choice ->
-                smartRepliesInflater.inflateReplyButton(
+        val smartReplyButtons =
+            smartReplies?.let {
+                smartReplies.choices.asSequence().mapIndexed { index, choice ->
+                    smartRepliesInflater.inflateReplyButton(
                         smartReplyView,
                         entry,
                         smartReplies,
                         index,
                         choice,
-                        delayOnClickListener)
-            }
-        } ?: emptySequence()
+                        delayOnClickListener,
+                    )
+                }
+            } ?: emptySequence()
 
-        val smartActionButtons = newSmartReplyState.smartActions?.let { smartActions ->
-            val themedPackageContext =
+        val smartActionButtons =
+            newSmartReplyState.smartActions?.let { smartActions ->
+                val themedPackageContext =
                     ContextThemeWrapper(notifPackageContext, sysuiContext.theme)
-            smartActions.actions.asSequence()
+                smartActions.actions
+                    .asSequence()
                     .filter { it.actionIntent != null }
                     .mapIndexed { index, action ->
                         smartActionsInflater.inflateActionButton(
-                                smartReplyView,
-                                entry,
-                                smartActions,
-                                index,
-                                action,
-                                delayOnClickListener,
-                                themedPackageContext)
+                            smartReplyView,
+                            entry,
+                            smartActions,
+                            index,
+                            action,
+                            delayOnClickListener,
+                            themedPackageContext,
+                        )
                     }
-        } ?: emptySequence()
+            } ?: emptySequence()
 
         return InflatedSmartReplyViewHolder(
-                smartReplyView,
-                (smartReplyButtons + smartActionButtons).toList())
+            smartReplyView,
+            (smartReplyButtons + smartActionButtons).toList(),
+        )
     }
 
     /**
      * Chose what smart replies and smart actions to display. App generated suggestions take
-     * precedence. So if the app provides any smart replies, we don't show any
-     * replies or actions generated by the NotificationAssistantService (NAS), and if the app
-     * provides any smart actions we also don't show any NAS-generated replies or actions.
+     * precedence. So if the app provides any smart replies, we don't show any replies or actions
+     * generated by the NotificationAssistantService (NAS), and if the app provides any smart
+     * actions we also don't show any NAS-generated replies or actions.
      */
     fun chooseSmartRepliesAndActions(entry: NotificationEntry): InflatedSmartReplyState {
         val notification = entry.sbn.notification
         val remoteInputActionPair = notification.findRemoteInputActionPair(false /* freeform */)
         val freeformRemoteInputActionPair =
-                notification.findRemoteInputActionPair(true /* freeform */)
+            notification.findRemoteInputActionPair(true /* freeform */)
         if (!constants.isEnabled) {
             if (DEBUG) {
-                Log.d(TAG, "Smart suggestions not enabled, not adding suggestions for " +
-                        entry.sbn.key)
+                Log.d(
+                    TAG,
+                    "Smart suggestions not enabled, not adding suggestions for " + entry.sbn.key,
+                )
             }
             return InflatedSmartReplyState(null, null, null, false)
         }
         // Only use smart replies from the app if they target P or above. We have this check because
         // the smart reply API has been used for other things (Wearables) in the past. The API to
         // add smart actions is new in Q so it doesn't require a target-sdk check.
-        val enableAppGeneratedSmartReplies = (!constants.requiresTargetingP() ||
-                entry.targetSdk >= Build.VERSION_CODES.P)
+        val enableAppGeneratedSmartReplies =
+            (!constants.requiresTargetingP() || entry.targetSdk >= Build.VERSION_CODES.P)
         val appGeneratedSmartActions = notification.contextualActions
 
-        var smartReplies: SmartReplies? = when {
-            enableAppGeneratedSmartReplies -> remoteInputActionPair?.let { pair ->
-                pair.second.actionIntent?.let { actionIntent ->
-                    if (pair.first.choices?.isNotEmpty() == true)
-                        SmartReplies(
-                                pair.first.choices.asList(),
-                                pair.first,
-                                actionIntent,
-                                false /* fromAssistant */)
-                    else null
-                }
+        var smartReplies: SmartReplies? =
+            when {
+                enableAppGeneratedSmartReplies ->
+                    remoteInputActionPair?.let { pair ->
+                        pair.second.actionIntent?.let { actionIntent ->
+                            if (pair.first.choices?.isNotEmpty() == true)
+                                SmartReplies(
+                                    pair.first.choices.asList(),
+                                    pair.first,
+                                    actionIntent,
+                                    false, /* fromAssistant */
+                                )
+                            else null
+                        }
+                    }
+                else -> null
             }
-            else -> null
-        }
-        var smartActions: SmartActions? = when {
-            appGeneratedSmartActions.isNotEmpty() ->
-                SmartActions(appGeneratedSmartActions, false /* fromAssistant */)
-            else -> null
-        }
+        var smartActions: SmartActions? =
+            when {
+                appGeneratedSmartActions.isNotEmpty() ->
+                    SmartActions(appGeneratedSmartActions, false /* fromAssistant */)
+                else -> null
+            }
         // Apps didn't provide any smart replies / actions, use those from NAS (if any).
         if (smartReplies == null && smartActions == null) {
             val entryReplies = entry.smartReplies
             val entryActions = entry.smartActions
-            if (entryReplies.isNotEmpty() &&
+            if (
+                entryReplies.isNotEmpty() &&
                     freeformRemoteInputActionPair != null &&
                     freeformRemoteInputActionPair.second.allowGeneratedReplies &&
-                    freeformRemoteInputActionPair.second.actionIntent != null) {
-                smartReplies = SmartReplies(
+                    freeformRemoteInputActionPair.second.actionIntent != null
+            ) {
+                smartReplies =
+                    SmartReplies(
                         entryReplies,
                         freeformRemoteInputActionPair.first,
                         freeformRemoteInputActionPair.second.actionIntent,
-                        true /* fromAssistant */)
+                        true, /* fromAssistant */
+                    )
             }
-            if (entryActions.isNotEmpty() &&
-                    notification.allowSystemGeneratedContextualActions) {
-                val systemGeneratedActions: List<Notification.Action> = when {
-                    activityManagerWrapper.isLockTaskKioskModeActive ->
-                        // Filter actions if we're in kiosk-mode - we don't care about screen
-                        // pinning mode, since notifications aren't shown there anyway.
-                        filterAllowlistedLockTaskApps(entryActions)
-                    else -> entryActions
-                }
+            if (entryActions.isNotEmpty() && notification.allowSystemGeneratedContextualActions) {
+                val systemGeneratedActions: List<Notification.Action> =
+                    when {
+                        activityManagerWrapper.isLockTaskKioskModeActive ->
+                            // Filter actions if we're in kiosk-mode - we don't care about screen
+                            // pinning mode, since notifications aren't shown there anyway.
+                            filterAllowlistedLockTaskApps(entryActions)
+                        else -> entryActions
+                    }
                 smartActions = SmartActions(systemGeneratedActions, true /* fromAssistant */)
             }
         }
-        val hasPhishingAction = smartActions?.actions?.any {
-            it.isContextual && it.semanticAction ==
-                    Notification.Action.SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
-        } ?: false
+        val hasPhishingAction =
+            smartActions?.actions?.any {
+                it.isContextual &&
+                    it.semanticAction ==
+                        Notification.Action.SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
+            } ?: false
         var suppressedActions: SuppressedActions? = null
         if (hasPhishingAction) {
             // If there is a phishing action, calculate the indices of the actions with RemoteInput
             //  as those need to be hidden from the view.
-            val suppressedActionIndices = notification.actions.mapIndexedNotNull { index, action ->
-                if (action.remoteInputs?.isNotEmpty() == true) index else null
-            }
+            val suppressedActionIndices =
+                notification.actions.mapIndexedNotNull { index, action ->
+                    if (action.remoteInputs?.isNotEmpty() == true) index else null
+                }
             suppressedActions = SuppressedActions(suppressedActionIndices)
         }
-        return InflatedSmartReplyState(smartReplies, smartActions, suppressedActions,
-                hasPhishingAction)
+        return InflatedSmartReplyState(
+            smartReplies,
+            smartActions,
+            suppressedActions,
+            hasPhishingAction,
+        )
     }
 
     /**
-     * Filter actions so that only actions pointing to allowlisted apps are permitted.
-     * This filtering is only meaningful when in lock-task mode.
+     * Filter actions so that only actions pointing to allowlisted apps are permitted. This
+     * filtering is only meaningful when in lock-task mode.
      */
     private fun filterAllowlistedLockTaskApps(
         actions: List<Notification.Action>
-    ): List<Notification.Action> = actions.filter { action ->
-        //  Only allow actions that are explicit (implicit intents are not handled in lock-task
-        //  mode), and link to allowlisted apps.
-        action.actionIntent?.intent?.let { intent ->
-            packageManagerWrapper.resolveActivity(intent, 0 /* flags */)
-        }?.let { resolveInfo ->
-            devicePolicyManagerWrapper.isLockTaskPermitted(resolveInfo.activityInfo.packageName)
-        } ?: false
-    }
+    ): List<Notification.Action> =
+        actions.filter { action ->
+            //  Only allow actions that are explicit (implicit intents are not handled in lock-task
+            //  mode), and link to allowlisted apps.
+            action.actionIntent
+                ?.intent
+                ?.let { intent -> packageManagerWrapper.resolveActivity(intent, 0 /* flags */) }
+                ?.let { resolveInfo ->
+                    devicePolicyManagerWrapper.isLockTaskPermitted(
+                        resolveInfo.activityInfo.packageName
+                    )
+                } ?: false
+        }
 }
 
 interface SmartActionInflater {
@@ -291,7 +324,7 @@
         actionIndex: Int,
         action: Notification.Action,
         delayOnClickListener: Boolean,
-        packageContext: Context
+        packageContext: Context,
     ): Button
 }
 
@@ -310,28 +343,32 @@
         val bitmap: Bitmap?
         val durationMillis = measureTimeMillis {
             val source = ImageDecoder.createSource(packageContext.contentResolver, icon.uri)
-            bitmap = ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
-                decoder.setTargetSize(targetSize, targetSize)
-                decoder.allocator = ImageDecoder.ALLOCATOR_DEFAULT
-            }
+            bitmap =
+                ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
+                    decoder.setTargetSize(targetSize, targetSize)
+                    decoder.allocator = ImageDecoder.ALLOCATOR_DEFAULT
+                }
         }
         if (durationMillis > ICON_TASK_TIMEOUT_MS) {
             Log.w(TAG, "Loading $icon took ${durationMillis / 1000f} sec")
         }
         checkNotNull(bitmap) { "ImageDecoder.decodeBitmap() returned null" }
     }
-    val bitmap = runCatching {
-        iconTaskThreadPool.execute(bitmapTask)
-        bitmapTask.get(ICON_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS)
-    }.getOrElse { ex ->
-        Log.e(TAG, "Failed to load $icon: $ex")
-        bitmapTask.cancel(true)
-        return null
-    }
+    val bitmap =
+        runCatching {
+                iconTaskThreadPool.execute(bitmapTask)
+                bitmapTask.get(ICON_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+            }
+            .getOrElse { ex ->
+                Log.e(TAG, "Failed to load $icon: $ex")
+                bitmapTask.cancel(true)
+                return null
+            }
     // TODO(b/288561520): rewrite Icon so that we don't need to duplicate this logic
     val bitmapDrawable = BitmapDrawable(packageContext.resources, bitmap)
-    val result = if (icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP)
-        AdaptiveIconDrawable(null, bitmapDrawable) else bitmapDrawable
+    val result =
+        if (icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP) AdaptiveIconDrawable(null, bitmapDrawable)
+        else bitmapDrawable
     if (icon.hasTint()) {
         result.mutate()
         result.setTintList(icon.tintList)
@@ -340,11 +377,13 @@
     return result
 }
 
-/* internal */ class SmartActionInflaterImpl @Inject constructor(
+/* internal */ class SmartActionInflaterImpl
+@Inject
+constructor(
     private val constants: SmartReplyConstants,
     private val activityStarter: ActivityStarter,
     private val smartReplyController: SmartReplyController,
-    private val headsUpManager: HeadsUpManager
+    private val headsUpManager: HeadsUpManager,
 ) : SmartActionInflater {
 
     override fun inflateActionButton(
@@ -354,17 +393,18 @@
         actionIndex: Int,
         action: Notification.Action,
         delayOnClickListener: Boolean,
-        packageContext: Context
+        packageContext: Context,
     ): Button =
-            (LayoutInflater.from(parent.context)
-                    .inflate(R.layout.smart_action_button, parent, false) as Button
-            ).apply {
+        (LayoutInflater.from(parent.context).inflate(R.layout.smart_action_button, parent, false)
+                as Button)
+            .apply {
                 text = action.title
 
-                // We received the Icon from the application - so use the Context of the application to
+                // We received the Icon from the application - so use the Context of the application
+                // to
                 // reference icon resources.
-                val newIconSize = context.resources
-                    .getDimensionPixelSize(R.dimen.smart_action_button_icon_size)
+                val newIconSize =
+                    context.resources.getDimensionPixelSize(R.dimen.smart_action_button_icon_size)
                 val iconDrawable =
                     loadIconDrawableWithTimeout(action.getIcon(), packageContext, newIconSize)
                         ?: GradientDrawable()
@@ -372,13 +412,15 @@
                 // Add the action icon to the Smart Action button.
                 setCompoundDrawablesRelative(iconDrawable, null, null, null)
 
-                val onClickListener = View.OnClickListener {
-                    onSmartActionClick(entry, smartActions, actionIndex, action)
-                }
+                val onClickListener =
+                    View.OnClickListener {
+                        onSmartActionClick(entry, smartActions, actionIndex, action)
+                    }
                 setOnClickListener(
-                        if (delayOnClickListener)
-                            DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
-                        else onClickListener)
+                    if (delayOnClickListener)
+                        DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
+                    else onClickListener
+                )
 
                 // Mark this as an Action button
                 (layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.ACTION
@@ -388,18 +430,31 @@
         entry: NotificationEntry,
         smartActions: SmartActions,
         actionIndex: Int,
-        action: Notification.Action
+        action: Notification.Action,
     ) =
-        if (smartActions.fromAssistant &&
-            SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY == action.semanticAction) {
-            entry.row.doSmartActionClick(entry.row.x.toInt() / 2,
-                entry.row.y.toInt() / 2, SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY)
-            smartReplyController
-                .smartActionClicked(entry, actionIndex, action, smartActions.fromAssistant)
+        if (
+            smartActions.fromAssistant &&
+                SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY == action.semanticAction
+        ) {
+            entry.row.doSmartActionClick(
+                entry.row.x.toInt() / 2,
+                entry.row.y.toInt() / 2,
+                SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY,
+            )
+            smartReplyController.smartActionClicked(
+                entry,
+                actionIndex,
+                action,
+                smartActions.fromAssistant,
+            )
         } else {
             activityStarter.startPendingIntentDismissingKeyguard(action.actionIntent, entry.row) {
-                smartReplyController
-                    .smartActionClicked(entry, actionIndex, action, smartActions.fromAssistant)
+                smartReplyController.smartActionClicked(
+                    entry,
+                    actionIndex,
+                    action,
+                    smartActions.fromAssistant,
+                )
             }
         }
 }
@@ -411,16 +466,18 @@
         smartReplies: SmartReplies,
         replyIndex: Int,
         choice: CharSequence,
-        delayOnClickListener: Boolean
+        delayOnClickListener: Boolean,
     ): Button
 }
 
-class SmartReplyInflaterImpl @Inject constructor(
+class SmartReplyInflaterImpl
+@Inject
+constructor(
     private val constants: SmartReplyConstants,
     private val keyguardDismissUtil: KeyguardDismissUtil,
     private val remoteInputManager: NotificationRemoteInputManager,
     private val smartReplyController: SmartReplyController,
-    private val context: Context
+    private val context: Context,
 ) : SmartReplyInflater {
 
     override fun inflateReplyButton(
@@ -429,37 +486,35 @@
         smartReplies: SmartReplies,
         replyIndex: Int,
         choice: CharSequence,
-        delayOnClickListener: Boolean
+        delayOnClickListener: Boolean,
     ): Button =
-            (LayoutInflater.from(parent.context)
-                    .inflate(R.layout.smart_reply_button, parent, false) as Button
-            ).apply {
+        (LayoutInflater.from(parent.context).inflate(R.layout.smart_reply_button, parent, false)
+                as Button)
+            .apply {
                 text = choice
-                val onClickListener = View.OnClickListener {
-                    onSmartReplyClick(
-                            entry,
-                            smartReplies,
-                            replyIndex,
-                            parent,
-                            this,
-                            choice)
-                }
-                setOnClickListener(
-                        if (delayOnClickListener)
-                            DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
-                        else onClickListener)
-                accessibilityDelegate = object : View.AccessibilityDelegate() {
-                    override fun onInitializeAccessibilityNodeInfo(
-                        host: View,
-                        info: AccessibilityNodeInfo
-                    ) {
-                        super.onInitializeAccessibilityNodeInfo(host, info)
-                        val label = parent.resources
-                                .getString(R.string.accessibility_send_smart_reply)
-                        val action = AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, label)
-                        info.addAction(action)
+                val onClickListener =
+                    View.OnClickListener {
+                        onSmartReplyClick(entry, smartReplies, replyIndex, parent, this, choice)
                     }
-                }
+                setOnClickListener(
+                    if (delayOnClickListener)
+                        DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
+                    else onClickListener
+                )
+                accessibilityDelegate =
+                    object : View.AccessibilityDelegate() {
+                        override fun onInitializeAccessibilityNodeInfo(
+                            host: View,
+                            info: AccessibilityNodeInfo,
+                        ) {
+                            super.onInitializeAccessibilityNodeInfo(host, info)
+                            val label =
+                                parent.resources.getString(R.string.accessibility_send_smart_reply)
+                            val action =
+                                AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, label)
+                            info.addAction(action)
+                        }
+                    }
                 // TODO: probably shouldn't do this here, bad API
                 // Mark this as a Reply button
                 (layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.REPLY
@@ -471,39 +526,52 @@
         replyIndex: Int,
         smartReplyView: SmartReplyView,
         button: Button,
-        choice: CharSequence
-    ) = keyguardDismissUtil.executeWhenUnlocked(!entry.isRowPinned) {
-        val canEditBeforeSend = constants.getEffectiveEditChoicesBeforeSending(
-                smartReplies.remoteInput.editChoicesBeforeSending)
-        if (canEditBeforeSend) {
-            remoteInputManager.activateRemoteInput(
+        choice: CharSequence,
+    ) =
+        keyguardDismissUtil.executeWhenUnlocked(!entry.isRowPinned) {
+            val canEditBeforeSend =
+                constants.getEffectiveEditChoicesBeforeSending(
+                    smartReplies.remoteInput.editChoicesBeforeSending
+                )
+            if (canEditBeforeSend) {
+                remoteInputManager.activateRemoteInput(
                     button,
                     arrayOf(smartReplies.remoteInput),
                     smartReplies.remoteInput,
                     smartReplies.pendingIntent,
-                    NotificationEntry.EditedSuggestionInfo(choice, replyIndex))
-        } else {
-            smartReplyController.smartReplySent(
+                    NotificationEntry.EditedSuggestionInfo(choice, replyIndex),
+                )
+            } else {
+                smartReplyController.smartReplySent(
                     entry,
                     replyIndex,
                     button.text,
                     NotificationLogger.getNotificationLocation(entry).toMetricsEventEnum(),
-                    false /* modifiedBeforeSending */)
-            entry.setHasSentReply()
-            try {
-                val intent = createRemoteInputIntent(smartReplies, choice)
-                val opts = ActivityOptions.makeBasic()
-                opts.setPendingIntentBackgroundActivityStartMode(
-                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
-                smartReplies.pendingIntent.send(context, 0, intent, /* onFinished */null,
-                        /* handler */ null, /* requiredPermission */ null, opts.toBundle())
-            } catch (e: PendingIntent.CanceledException) {
-                Log.w(TAG, "Unable to send smart reply", e)
+                    false, /* modifiedBeforeSending */
+                )
+                entry.setHasSentReply()
+                try {
+                    val intent = createRemoteInputIntent(smartReplies, choice)
+                    val opts = ActivityOptions.makeBasic()
+                    opts.setPendingIntentBackgroundActivityStartMode(
+                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                    )
+                    smartReplies.pendingIntent.send(
+                        context,
+                        0,
+                        intent, /* onFinished */
+                        null,
+                        /* handler */ null, /* requiredPermission */
+                        null,
+                        opts.toBundle(),
+                    )
+                } catch (e: PendingIntent.CanceledException) {
+                    Log.w(TAG, "Unable to send smart reply", e)
+                }
+                smartReplyView.hideSmartSuggestions()
             }
-            smartReplyView.hideSmartSuggestions()
+            false // do not defer
         }
-        false // do not defer
-    }
 
     private fun createRemoteInputIntent(smartReplies: SmartReplies, choice: CharSequence): Intent {
         val results = Bundle()
@@ -516,12 +584,11 @@
 }
 
 /**
- * An OnClickListener wrapper that blocks the underlying OnClickListener for a given amount of
- * time.
+ * An OnClickListener wrapper that blocks the underlying OnClickListener for a given amount of time.
  */
 private class DelayedOnClickListener(
     private val mActualListener: View.OnClickListener,
-    private val mInitDelayMs: Long
+    private val mInitDelayMs: Long,
 ) : View.OnClickListener {
 
     private val mInitTimeMs = SystemClock.elapsedRealtime()
@@ -535,7 +602,7 @@
     }
 
     private fun hasFinishedInitialization(): Boolean =
-            SystemClock.elapsedRealtime() >= mInitTimeMs + mInitDelayMs
+        SystemClock.elapsedRealtime() >= mInitTimeMs + mInitDelayMs
 }
 
 private const val TAG = "SmartReplyViewInflater"
@@ -544,12 +611,12 @@
 // convenience function that swaps parameter order so that lambda can be placed at the end
 private fun KeyguardDismissUtil.executeWhenUnlocked(
     requiresShadeOpen: Boolean,
-    onDismissAction: () -> Boolean
+    onDismissAction: () -> Boolean,
 ) = executeWhenUnlocked(onDismissAction, requiresShadeOpen, false)
 
 // convenience function that swaps parameter order so that lambda can be placed at the end
 private fun ActivityStarter.startPendingIntentDismissingKeyguard(
     intent: PendingIntent,
     associatedView: View?,
-    runnable: () -> Unit
-) = startPendingIntentDismissingKeyguard(intent, runnable::invoke, associatedView)
\ No newline at end of file
+    runnable: () -> Unit,
+) = startPendingIntentDismissingKeyguard(intent, runnable::invoke, associatedView)
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 1c3fece..28cf78f 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -149,7 +149,8 @@
     private double mContrast = 0.0;
     // Theme variant: Vibrant, Tonal, Expressive, etc
     @VisibleForTesting
-    protected Style mThemeStyle = Style.TONAL_SPOT;
+    @Style.Type
+    protected int mThemeStyle = Style.TONAL_SPOT;
     // Accent colors overlay
     private FabricatedOverlay mSecondaryOverlay;
     // Neutral system colors overlay
@@ -826,15 +827,16 @@
 
     }
 
-    private Style fetchThemeStyleFromSetting() {
+    @Style.Type
+    private int fetchThemeStyleFromSetting() {
         // Allow-list of Style objects that can be created from a setting string, i.e. can be
         // used as a system-wide theme.
         // - Content intentionally excluded, intended for media player, not system-wide
-        List<Style> validStyles = new ArrayList<>(Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ,
-                Style.TONAL_SPOT, Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT,
+        @Style.Type List<Integer> validStyles = new ArrayList<>(Arrays.asList(Style.EXPRESSIVE,
+                Style.SPRITZ, Style.TONAL_SPOT, Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT,
                 Style.MONOCHROMATIC));
 
-        Style style = mThemeStyle;
+        @Style.Type int style = mThemeStyle;
         final String overlayPackageJson = mSecureSettings.getStringForUser(
                 Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
                 mUserTracker.getUserId());
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index bcf4bad..45fdd21 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -63,7 +63,8 @@
 private fun rememberHomeGestureRecognizer(resources: Resources): GestureRecognizer {
     val distance =
         resources.getDimensionPixelSize(R.dimen.touchpad_tutorial_gestures_distance_threshold)
-    return remember(distance) { HomeGestureRecognizer(distance) }
+    val velocity = resources.getDimension(R.dimen.touchpad_home_gesture_velocity_threshold)
+    return remember(distance) { HomeGestureRecognizer(distance, velocity) }
 }
 
 @Composable
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
index e10b825..9801626 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
@@ -19,9 +19,14 @@
 import android.util.MathUtils
 import android.view.MotionEvent
 import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
+import kotlin.math.abs
 
 /** Recognizes touchpad home gesture, that is - using three fingers on touchpad - swiping up. */
-class HomeGestureRecognizer(private val gestureDistanceThresholdPx: Int) : GestureRecognizer {
+class HomeGestureRecognizer(
+    private val gestureDistanceThresholdPx: Int,
+    private val velocityThresholdPxPerMs: Float,
+    private val velocityTracker: VelocityTracker = VerticalVelocityTracker(),
+) : GestureRecognizer {
 
     private val distanceTracker = DistanceTracker()
     private var gestureStateChangedCallback: (GestureState) -> Unit = {}
@@ -37,10 +42,14 @@
     override fun accept(event: MotionEvent) {
         if (!isThreeFingerTouchpadSwipe(event)) return
         val gestureState = distanceTracker.processEvent(event)
+        velocityTracker.accept(event)
         updateGestureState(
             gestureStateChangedCallback,
             gestureState,
-            isFinished = { -it.deltaY >= gestureDistanceThresholdPx },
+            isFinished = {
+                -it.deltaY >= gestureDistanceThresholdPx &&
+                    abs(velocityTracker.calculateVelocity().value) >= velocityThresholdPxPerMs
+            },
             progress = { InProgress(MathUtils.saturate(-it.deltaY / gestureDistanceThresholdPx)) },
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
index c478886..5ff583a 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
@@ -28,10 +28,10 @@
 class RecentAppsGestureRecognizer(
     private val gestureDistanceThresholdPx: Int,
     private val velocityThresholdPxPerMs: Float,
-    private val distanceTracker: DistanceTracker = DistanceTracker(),
-    private val velocityTracker: VerticalVelocityTracker = VerticalVelocityTracker(),
+    private val velocityTracker: VelocityTracker = VerticalVelocityTracker(),
 ) : GestureRecognizer {
 
+    private val distanceTracker = DistanceTracker()
     private var gestureStateChangedCallback: (GestureState) -> Unit = {}
 
     override fun addGestureStateCallback(callback: (GestureState) -> Unit) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
index 5c0cc81..39b434ad 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -21,20 +21,24 @@
 import android.graphics.PixelFormat
 import android.os.Bundle
 import android.view.MotionEvent
+import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
+import com.android.app.tracing.coroutines.coroutineScopeTraced
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
-import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder
 import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
 
 class VolumeDialog
 @Inject
 constructor(
     @Application context: Context,
-    private val viewBinder: VolumeDialogViewBinder,
+    private val componentFactory: VolumeDialogComponent.Factory,
     private val visibilityInteractor: VolumeDialogVisibilityInteractor,
 ) : Dialog(context, R.style.Theme_SystemUI_Dialog_Volume) {
 
@@ -64,7 +68,14 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.volume_dialog)
-        viewBinder.bind(this)
+        requireViewById<View>(R.id.volume_dialog_root).repeatWhenAttached {
+            coroutineScopeTraced("[Volume]dialog") {
+                val component = componentFactory.create(this)
+                with(component.volumeDialogViewBinder()) { bind(this@VolumeDialog) }
+
+                awaitCancellation()
+            }
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
index b912361..203a157 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
@@ -16,19 +16,31 @@
 
 package com.android.systemui.volume.dialog
 
+import android.content.Context
+import android.media.AudioManager
+import com.android.app.tracing.coroutines.coroutineScopeTraced
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.plugins.VolumeDialog
+import com.android.systemui.volume.SafetyWarningDialog
 import com.android.systemui.volume.dialog.dagger.VolumeDialogPluginComponent
+import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogPluginViewModel
 import javax.inject.Inject
+import kotlin.coroutines.resume
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.suspendCancellableCoroutine
 
+@OptIn(ExperimentalCoroutinesApi::class)
 class VolumeDialogPlugin
 @Inject
 constructor(
     @Application private val applicationCoroutineScope: CoroutineScope,
+    private val context: Context,
+    private val audioManager: AudioManager,
     private val volumeDialogPluginComponentFactory: VolumeDialogPluginComponent.Factory,
 ) : VolumeDialog {
 
@@ -38,17 +50,42 @@
     override fun init(windowType: Int, callback: VolumeDialog.Callback?) {
         job =
             applicationCoroutineScope.launch {
-                coroutineScope {
+                coroutineScopeTraced("[Volume]plugin") {
                     pluginComponent =
                         volumeDialogPluginComponentFactory.create(this).also {
-                            it.viewModel().launchVolumeDialog()
+                            bindPlugin(it.viewModel())
                         }
                 }
             }
     }
 
+    private fun CoroutineScope.bindPlugin(viewModel: VolumeDialogPluginViewModel) {
+        viewModel.launchVolumeDialog()
+
+        viewModel.isShowingSafetyWarning
+            .mapLatest { isShowingSafetyWarning ->
+                if (isShowingSafetyWarning) {
+                    showSafetyWarningVisibility { viewModel.onSafetyWarningDismissed() }
+                }
+            }
+            .launchIn(this)
+    }
+
     override fun destroy() {
         job?.cancel()
         pluginComponent = null
     }
+
+    private suspend fun showSafetyWarningVisibility(onDismissed: () -> Unit) =
+        suspendCancellableCoroutine { continuation ->
+            val dialog =
+                object : SafetyWarningDialog(context, audioManager) {
+                    override fun cleanUp() {
+                        onDismissed()
+                        continuation.resume(Unit)
+                    }
+                }
+            dialog.show()
+            continuation.invokeOnCancellation { dialog.dismiss() }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
index fb15795..434f6b5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
+import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder
 import dagger.BindsInstance
 import dagger.Subcomponent
 import kotlinx.coroutines.CoroutineScope
@@ -32,21 +33,21 @@
 @Subcomponent(modules = [VolumeDialogModule::class])
 interface VolumeDialogComponent {
 
-    /**
-     * Provides a coroutine scope to use inside [VolumeDialogScope].
-     * [com.android.systemui.volume.dialog.VolumeDialogPlugin] manages the lifecycle of this scope.
-     * It's cancelled when the dialog is disposed. This helps to free occupied resources when volume
-     * dialog is not shown.
-     */
-    @VolumeDialog fun coroutineScope(): CoroutineScope
-
-    @VolumeDialogScope fun volumeDialog(): com.android.systemui.volume.dialog.VolumeDialog
+    fun volumeDialogViewBinder(): VolumeDialogViewBinder
 
     fun sliderComponentFactory(): VolumeDialogSliderComponent.Factory
 
     @Subcomponent.Factory
     interface Factory {
 
-        fun create(@BindsInstance @VolumeDialog scope: CoroutineScope): VolumeDialogComponent
+        fun create(
+            /**
+             * Provides a coroutine scope to use inside [VolumeDialogScope].
+             * [com.android.systemui.volume.dialog.VolumeDialogPlugin] manages the lifecycle of this
+             * scope. It's cancelled when the dialog is disposed. This helps to free occupied
+             * resources when volume dialog is not shown.
+             */
+            @BindsInstance @VolumeDialog scope: CoroutineScope
+        ): VolumeDialogComponent
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractor.kt
new file mode 100644
index 0000000..f707d67
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractor.kt
@@ -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.systemui.volume.dialog.domain.interactor
+
+import android.media.AudioManager
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogSafetyWarningModel
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+
+private const val VISIBLE_FLAGS = AudioManager.FLAG_SHOW_UI or AudioManager.FLAG_SHOW_UI_WARNINGS
+
+@VolumeDialogPluginScope
+class VolumeDialogSafetyWarningInteractor
+@Inject
+constructor(
+    private val stateInteractor: VolumeDialogStateInteractor,
+    visibilityInteractor: VolumeDialogVisibilityInteractor,
+) {
+
+    val isShowingSafetyWarning: Flow<Boolean> =
+        stateInteractor.volumeDialogState.map {
+            when (it.isShowingSafetyWarning) {
+                is VolumeDialogSafetyWarningModel.Visible ->
+                    if (it.isShowingSafetyWarning.flags and VISIBLE_FLAGS == 0) {
+                        visibilityInteractor.dialogVisibility.first() is
+                            VolumeDialogVisibilityModel.Visible
+                    } else {
+                        true
+                    }
+                is VolumeDialogSafetyWarningModel.Invisible -> false
+            }
+        }
+
+    fun onSafetyWarningDismissed() {
+        stateInteractor.setSafetyWarning(VolumeDialogSafetyWarningModel.Invisible)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
index 5c7289b..51e7924 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
 import com.android.systemui.volume.dialog.data.repository.VolumeDialogStateRepository
 import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogSafetyWarningModel
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
 import javax.inject.Inject
@@ -61,6 +62,9 @@
                             oldState.copy(shouldShowA11ySlider = event.showA11yStream)
                         }
                     }
+                    is VolumeDialogEventModel.ShowSafetyWarning -> {
+                        setSafetyWarning(VolumeDialogSafetyWarningModel.Visible(event.flags))
+                    }
                     else -> {
                         // do nothing
                     }
@@ -72,6 +76,10 @@
 
     val volumeDialogState: Flow<VolumeDialogStateModel> = volumeDialogStateRepository.state
 
+    fun setSafetyWarning(model: VolumeDialogSafetyWarningModel) {
+        volumeDialogStateRepository.updateState { it.copy(isShowingSafetyWarning = model) }
+    }
+
     /** Returns a copy of [model] filled with the values from [VolumeDialogController.State]. */
     private fun VolumeDialogController.State.copyIntoModel(
         model: VolumeDialogStateModel
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index 1963ba2..f98ad45 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.volume.dialog.ringer.ui.binder
 
+import android.animation.ArgbEvaluator
+import android.graphics.drawable.GradientDrawable
 import android.view.LayoutInflater
 import android.view.View
 import android.widget.ImageButton
@@ -23,128 +25,213 @@
 import androidx.compose.ui.util.fastForEachIndexed
 import androidx.constraintlayout.motion.widget.MotionLayout
 import androidx.constraintlayout.widget.ConstraintSet
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
 import com.android.internal.R as internalR
 import com.android.settingslib.Utils
-import com.android.systemui.lifecycle.WindowLifecycleState
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.viewModel
 import com.android.systemui.res.R
 import com.android.systemui.util.children
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerButtonUiModel
 import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerButtonViewModel
 import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerDrawerState
 import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModel
 import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModelState
 import com.android.systemui.volume.dialog.ringer.ui.viewmodel.VolumeDialogRingerDrawerViewModel
+import com.android.systemui.volume.dialog.ui.utils.suspendAnimate
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+private const val CLOSE_DRAWER_DELAY = 300L
 
 @VolumeDialogScope
 class VolumeDialogRingerViewBinder
 @Inject
-constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Factory) {
+constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) {
+    private val roundnessSpringForce =
+        SpringForce(0F).apply {
+            stiffness = 800F
+            dampingRatio = 0.6F
+        }
+    private val colorSpringForce =
+        SpringForce(0F).apply {
+            stiffness = 3800F
+            dampingRatio = 1F
+        }
+    private val rgbEvaluator = ArgbEvaluator()
 
-    fun bind(view: View) {
-        with(view) {
-            val volumeDialogBackgroundView = requireViewById<View>(R.id.volume_dialog_background)
-            val drawerContainer = requireViewById<MotionLayout>(R.id.volume_ringer_drawer)
-            repeatWhenAttached {
-                viewModel(
-                    traceName = "VolumeDialogRingerViewBinder",
-                    minWindowLifecycleState = WindowLifecycleState.ATTACHED,
-                    factory = { viewModelFactory.create() },
-                ) { viewModel ->
-                    viewModel.ringerViewModel
-                        .onEach { ringerState ->
-                            when (ringerState) {
-                                is RingerViewModelState.Available -> {
-                                    val uiModel = ringerState.uiModel
+    fun CoroutineScope.bind(view: View) {
+        val volumeDialogBackgroundView = view.requireViewById<View>(R.id.volume_dialog_background)
+        val drawerContainer = view.requireViewById<MotionLayout>(R.id.volume_ringer_drawer)
+        val unselectedButtonUiModel = RingerButtonUiModel.getUnselectedButton(view.context)
+        val selectedButtonUiModel = RingerButtonUiModel.getSelectedButton(view.context)
+        viewModel.ringerViewModel
+            .onEach { ringerState ->
+                when (ringerState) {
+                    is RingerViewModelState.Available -> {
+                        val uiModel = ringerState.uiModel
 
-                                    bindDrawerButtons(viewModel, uiModel)
+                        // Set up view background and visibility
+                        drawerContainer.visibility = View.VISIBLE
+                        when (uiModel.drawerState) {
+                            is RingerDrawerState.Initial -> {
+                                drawerContainer.animateAndBindDrawerButtons(
+                                    viewModel,
+                                    uiModel,
+                                    selectedButtonUiModel,
+                                    unselectedButtonUiModel,
+                                )
+                                drawerContainer.closeDrawer(uiModel.currentButtonIndex)
+                                volumeDialogBackgroundView.setBackgroundResource(
+                                    R.drawable.volume_dialog_background
+                                )
+                            }
 
-                                    // Set up view background and visibility
-                                    drawerContainer.visibility = View.VISIBLE
-                                    when (uiModel.drawerState) {
-                                        is RingerDrawerState.Initial -> {
-                                            drawerContainer.closeDrawer(uiModel.currentButtonIndex)
-                                            volumeDialogBackgroundView.setBackgroundResource(
-                                                R.drawable.volume_dialog_background
-                                            )
-                                        }
-                                        is RingerDrawerState.Closed -> {
-                                            drawerContainer.closeDrawer(uiModel.currentButtonIndex)
-                                            volumeDialogBackgroundView.setBackgroundResource(
-                                                R.drawable.volume_dialog_background
-                                            )
-                                        }
-                                        is RingerDrawerState.Open -> {
-                                            // Open drawer
-                                            drawerContainer.transitionToEnd()
-                                            if (
-                                                uiModel.currentButtonIndex !=
-                                                    uiModel.availableButtons.size - 1
-                                            ) {
-                                                volumeDialogBackgroundView.setBackgroundResource(
-                                                    R.drawable.volume_dialog_background_small_radius
-                                                )
-                                            }
-                                        }
+                            is RingerDrawerState.Closed -> {
+                                if (
+                                    uiModel.selectedButton.ringerMode ==
+                                        uiModel.drawerState.currentMode
+                                ) {
+                                    drawerContainer.animateAndBindDrawerButtons(
+                                        viewModel,
+                                        uiModel,
+                                        selectedButtonUiModel,
+                                        unselectedButtonUiModel,
+                                    ) {
+                                        drawerContainer.closeDrawer(uiModel.currentButtonIndex)
+                                        volumeDialogBackgroundView.setBackgroundResource(
+                                            R.drawable.volume_dialog_background
+                                        )
                                     }
                                 }
-                                is RingerViewModelState.Unavailable -> {
-                                    drawerContainer.visibility = View.GONE
+                            }
+
+                            is RingerDrawerState.Open -> {
+                                drawerContainer.animateAndBindDrawerButtons(
+                                    viewModel,
+                                    uiModel,
+                                    selectedButtonUiModel,
+                                    unselectedButtonUiModel,
+                                )
+                                // Open drawer
+                                drawerContainer.transitionToState(
+                                    R.id.volume_dialog_ringer_drawer_open
+                                )
+                                if (
+                                    uiModel.currentButtonIndex != uiModel.availableButtons.size - 1
+                                ) {
                                     volumeDialogBackgroundView.setBackgroundResource(
-                                        R.drawable.volume_dialog_background
+                                        R.drawable.volume_dialog_background_small_radius
                                     )
                                 }
                             }
                         }
-                        .launchIn(this)
+                    }
+
+                    is RingerViewModelState.Unavailable -> {
+                        drawerContainer.visibility = View.GONE
+                        volumeDialogBackgroundView.setBackgroundResource(
+                            R.drawable.volume_dialog_background
+                        )
+                    }
                 }
             }
+            .launchIn(this)
+    }
+
+    private suspend fun MotionLayout.animateAndBindDrawerButtons(
+        viewModel: VolumeDialogRingerDrawerViewModel,
+        uiModel: RingerViewModel,
+        selectedButtonUiModel: RingerButtonUiModel,
+        unselectedButtonUiModel: RingerButtonUiModel,
+        onAnimationEnd: Runnable? = null,
+    ) {
+        ensureChildCount(R.layout.volume_ringer_button, uiModel.availableButtons.size)
+        if (
+            uiModel.drawerState is RingerDrawerState.Closed &&
+                uiModel.drawerState.currentMode != uiModel.drawerState.previousMode
+        ) {
+            val count = uiModel.availableButtons.size
+            val selectedButton =
+                getChildAt(count - uiModel.currentButtonIndex - 1)
+                    .requireViewById<ImageButton>(R.id.volume_drawer_button)
+            val previousIndex =
+                uiModel.availableButtons.indexOfFirst {
+                    it?.ringerMode == uiModel.drawerState.previousMode
+                }
+            val unselectedButton =
+                getChildAt(count - previousIndex - 1)
+                    .requireViewById<ImageButton>(R.id.volume_drawer_button)
+
+            // On roundness animation end.
+            val roundnessAnimationEndListener =
+                DynamicAnimation.OnAnimationEndListener { _, _, _, _ ->
+                    postDelayed(
+                        { bindButtons(viewModel, uiModel, onAnimationEnd, isAnimated = true) },
+                        CLOSE_DRAWER_DELAY,
+                    )
+                }
+
+            // We only need to execute on roundness animation end once.
+            selectedButton.animateTo(selectedButtonUiModel, roundnessAnimationEndListener)
+            unselectedButton.animateTo(unselectedButtonUiModel)
+        } else {
+            bindButtons(viewModel, uiModel, onAnimationEnd)
         }
     }
 
-    private fun View.bindDrawerButtons(
+    private fun MotionLayout.bindButtons(
         viewModel: VolumeDialogRingerDrawerViewModel,
         uiModel: RingerViewModel,
+        onAnimationEnd: Runnable? = null,
+        isAnimated: Boolean = false,
     ) {
-        val drawerContainer = requireViewById<MotionLayout>(R.id.volume_ringer_drawer)
         val count = uiModel.availableButtons.size
-        drawerContainer.ensureChildCount(R.layout.volume_ringer_button, count)
-
         uiModel.availableButtons.fastForEachIndexed { index, ringerButton ->
             ringerButton?.let {
-                val view = drawerContainer.getChildAt(count - index - 1)
-                // TODO (b/369995871): object animator for button switch ( active <-> inactive )
+                val view = getChildAt(count - index - 1)
                 if (index == uiModel.currentButtonIndex) {
-                    view.bindDrawerButton(uiModel.selectedButton, viewModel, isSelected = true)
+                    view.bindDrawerButton(
+                        uiModel.selectedButton,
+                        viewModel,
+                        isSelected = true,
+                        isAnimated = isAnimated,
+                    )
                 } else {
-                    view.bindDrawerButton(it, viewModel)
+                    view.bindDrawerButton(it, viewModel, isAnimated)
                 }
             }
         }
+        onAnimationEnd?.run()
     }
 
     private fun View.bindDrawerButton(
         buttonViewModel: RingerButtonViewModel,
         viewModel: VolumeDialogRingerDrawerViewModel,
         isSelected: Boolean = false,
+        isAnimated: Boolean = false,
     ) {
         with(requireViewById<ImageButton>(R.id.volume_drawer_button)) {
             setImageResource(buttonViewModel.imageResId)
             contentDescription = context.getString(buttonViewModel.contentDescriptionResId)
-            if (isSelected) {
+            if (isSelected && !isAnimated) {
                 setBackgroundResource(R.drawable.volume_drawer_selection_bg)
                 setColorFilter(
                     Utils.getColorAttrDefaultColor(context, internalR.attr.materialColorOnPrimary)
                 )
-            } else {
+                background = background.mutate()
+            } else if (!isAnimated) {
                 setBackgroundResource(R.drawable.volume_ringer_item_bg)
                 setColorFilter(
                     Utils.getColorAttrDefaultColor(context, internalR.attr.materialColorOnSurface)
                 )
+                background = background.mutate()
             }
             setOnClickListener {
                 viewModel.onRingerButtonClicked(buttonViewModel.ringerMode, isSelected)
@@ -171,9 +258,10 @@
     }
 
     private fun MotionLayout.closeDrawer(selectedIndex: Int) {
+        setTransition(R.id.close_to_open_transition)
         cloneConstraintSet(R.id.volume_dialog_ringer_drawer_close)
             .adjustClosedConstraintsForDrawer(selectedIndex, this)
-        transitionToStart()
+        transitionToState(R.id.volume_dialog_ringer_drawer_close)
     }
 
     private fun ConstraintSet.adjustOpenConstraintsForDrawer(motionLayout: MotionLayout) {
@@ -263,4 +351,47 @@
         connect(button.id, ConstraintSet.START, motionLayout.id, ConstraintSet.START)
         connect(button.id, ConstraintSet.END, motionLayout.id, ConstraintSet.END)
     }
+
+    private suspend fun ImageButton.animateTo(
+        ringerButtonUiModel: RingerButtonUiModel,
+        roundnessAnimationEndListener: DynamicAnimation.OnAnimationEndListener? = null,
+    ) {
+        val roundnessAnimation =
+            SpringAnimation(FloatValueHolder(0F)).setSpring(roundnessSpringForce)
+        val colorAnimation = SpringAnimation(FloatValueHolder(0F)).setSpring(colorSpringForce)
+        val radius = (background as GradientDrawable).cornerRadius
+        val cornerRadiusDiff =
+            ringerButtonUiModel.cornerRadius - (background as GradientDrawable).cornerRadius
+        val roundnessAnimationUpdateListener =
+            DynamicAnimation.OnAnimationUpdateListener { _, value, _ ->
+                (background as GradientDrawable).cornerRadius = radius + value * cornerRadiusDiff
+                background.invalidateSelf()
+            }
+        val colorAnimationUpdateListener =
+            DynamicAnimation.OnAnimationUpdateListener { _, value, _ ->
+                val currentIconColor =
+                    rgbEvaluator.evaluate(
+                        value.coerceIn(0F, 1F),
+                        imageTintList?.colors?.first(),
+                        ringerButtonUiModel.tintColor,
+                    ) as Int
+                val currentBgColor =
+                    rgbEvaluator.evaluate(
+                        value.coerceIn(0F, 1F),
+                        (background as GradientDrawable).color?.colors?.get(0),
+                        ringerButtonUiModel.backgroundColor,
+                    ) as Int
+
+                (background as GradientDrawable).setColor(currentBgColor)
+                background.invalidateSelf()
+                setColorFilter(currentIconColor)
+            }
+        coroutineScope {
+            launch { colorAnimation.suspendAnimate(colorAnimationUpdateListener) }
+            roundnessAnimation.suspendAnimate(
+                roundnessAnimationUpdateListener,
+                roundnessAnimationEndListener,
+            )
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonUiModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonUiModel.kt
new file mode 100644
index 0000000..3c46567
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonUiModel.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ringer.ui.viewmodel
+
+import android.content.Context
+import com.android.internal.R as internalR
+import com.android.settingslib.Utils
+import com.android.systemui.res.R
+
+/** Models the UI state of ringer button */
+data class RingerButtonUiModel(
+    /** Icon color. */
+    val tintColor: Int,
+    val backgroundColor: Int,
+    val cornerRadius: Int,
+) {
+    companion object {
+        fun getUnselectedButton(context: Context): RingerButtonUiModel {
+            return RingerButtonUiModel(
+                tintColor =
+                    Utils.getColorAttrDefaultColor(context, internalR.attr.materialColorOnSurface),
+                backgroundColor =
+                    Utils.getColorAttrDefaultColor(
+                        context,
+                        internalR.attr.materialColorSurfaceContainerHighest,
+                    ),
+                cornerRadius =
+                    context.resources.getDimensionPixelSize(
+                        R.dimen.volume_dialog_background_square_corner_radius
+                    ),
+            )
+        }
+
+        fun getSelectedButton(context: Context): RingerButtonUiModel {
+            return RingerButtonUiModel(
+                tintColor =
+                    Utils.getColorAttrDefaultColor(context, internalR.attr.materialColorOnPrimary),
+                backgroundColor =
+                    Utils.getColorAttrDefaultColor(context, internalR.attr.materialColorPrimary),
+                cornerRadius =
+                    context.resources.getDimensionPixelSize(
+                        R.dimen.volume_dialog_ringer_selected_button_background_radius
+                    ),
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt
index f321837..afb3f68 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt
@@ -25,7 +25,8 @@
     data class Open(val mode: RingerMode) : RingerDrawerState
 
     /** When clicked to close drawer */
-    data class Closed(val mode: RingerMode) : RingerDrawerState
+    data class Closed(val currentMode: RingerMode, val previousMode: RingerMode) :
+        RingerDrawerState
 
     /** Initial state when volume dialog is shown with a closed drawer. */
     interface Initial : RingerDrawerState {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
index 624dcc7..e646636 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
@@ -32,12 +32,12 @@
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.volume.Events
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.ringer.domain.VolumeDialogRingerInteractor
 import com.android.systemui.volume.dialog.ringer.shared.model.VolumeDialogRingerModel
 import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -50,8 +50,9 @@
 
 private const val SHOW_RINGER_TOAST_COUNT = 12
 
+@VolumeDialogScope
 class VolumeDialogRingerDrawerViewModel
-@AssistedInject
+@Inject
 constructor(
     @Application private val applicationContext: Context,
     @VolumeDialog private val coroutineScope: CoroutineScope,
@@ -98,7 +99,10 @@
                     RingerDrawerState.Open(ringerMode)
                 }
                 is RingerDrawerState.Open -> {
-                    RingerDrawerState.Closed(ringerMode)
+                    RingerDrawerState.Closed(
+                        ringerMode,
+                        (drawerState.value as RingerDrawerState.Open).mode,
+                    )
                 }
                 is RingerDrawerState.Closed -> {
                     RingerDrawerState.Open(ringerMode)
@@ -259,9 +263,4 @@
             interactor.updateToastCount(seenToastCount)
         }
     }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(): VolumeDialogRingerDrawerViewModel
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogSafetyWarningModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogSafetyWarningModel.kt
new file mode 100644
index 0000000..39fc222
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogSafetyWarningModel.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.volume.dialog.shared.model
+
+/** Models current Volume Safety Dialog state. */
+sealed interface VolumeDialogSafetyWarningModel {
+
+    /** Volume Safety Dialog is visible and has been shown with the [flags]. */
+    data class Visible(val flags: Int) : VolumeDialogSafetyWarningModel
+
+    /** Volume Safety Dialog is invisible. */
+    data object Invisible : VolumeDialogSafetyWarningModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt
index 1792b99..838006d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/model/VolumeDialogStateModel.kt
@@ -21,6 +21,8 @@
 /** Models a state of the Volume Dialog. */
 data class VolumeDialogStateModel(
     val shouldShowA11ySlider: Boolean = false,
+    val isShowingSafetyWarning: VolumeDialogSafetyWarningModel =
+        VolumeDialogSafetyWarningModel.Invisible,
     val streamModels: Map<Int, VolumeDialogStreamModel> = mapOf(),
     val ringerModeInternal: Int = 0,
     val ringerModeExternal: Int = 0,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
index 772ae77..c0c525b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.dialog.sliders.dagger
 
 import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder
 import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderTouchesViewBinder
 import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
 import dagger.BindsInstance
@@ -34,6 +35,8 @@
 
     fun sliderTouchesViewBinder(): VolumeDialogSliderTouchesViewBinder
 
+    fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder
+
     @Subcomponent.Factory
     interface Factory {
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt
new file mode 100644
index 0000000..5a7fbc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui
+
+import android.view.View
+import com.android.systemui.haptics.slider.HapticSlider
+import com.android.systemui.haptics.slider.HapticSliderPlugin
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
+import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel
+import com.google.android.material.slider.Slider
+import com.google.android.msdl.domain.MSDLPlayer
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@VolumeDialogSliderScope
+class VolumeDialogSliderHapticsViewBinder
+@Inject
+constructor(
+    private val inputEventsViewModel: VolumeDialogSliderInputEventsViewModel,
+    private val vibratorHelper: VibratorHelper,
+    private val msdlPlayer: MSDLPlayer,
+    private val systemClock: SystemClock,
+) {
+
+    fun CoroutineScope.bind(view: View) {
+        val sliderView = view.requireViewById<Slider>(R.id.volume_dialog_slider)
+        val hapticSliderPlugin =
+            HapticSliderPlugin(
+                slider = HapticSlider.Slider(sliderView),
+                vibratorHelper = vibratorHelper,
+                msdlPlayer = msdlPlayer,
+                systemClock = systemClock,
+            )
+        hapticSliderPlugin.startInScope(this)
+
+        sliderView.addOnChangeListener { _, value, fromUser ->
+            hapticSliderPlugin.onProgressChanged(value.roundToInt(), fromUser)
+        }
+        sliderView.addOnSliderTouchListener(
+            object : Slider.OnSliderTouchListener {
+
+                override fun onStartTrackingTouch(slider: Slider) {
+                    hapticSliderPlugin.onStartTrackingTouch()
+                }
+
+                override fun onStopTrackingTouch(slider: Slider) {
+                    hapticSliderPlugin.onStopTrackingTouch()
+                }
+            }
+        )
+
+        inputEventsViewModel.event
+            .onEach {
+                when (it) {
+                    is SliderInputEvent.Button -> hapticSliderPlugin.onKeyDown()
+                    is SliderInputEvent.Touch -> hapticSliderPlugin.onTouchEvent(it.event)
+                }
+            }
+            .launchIn(this)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt
index 7fd177d..4ecac7a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt
@@ -18,34 +18,23 @@
 
 import android.annotation.SuppressLint
 import android.view.View
-import com.android.systemui.lifecycle.WindowLifecycleState
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.viewModel
 import com.android.systemui.res.R
 import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
-import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderTouchesViewModel
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel
 import com.google.android.material.slider.Slider
 import javax.inject.Inject
 
 @VolumeDialogSliderScope
 class VolumeDialogSliderTouchesViewBinder
 @Inject
-constructor(private val viewModelFactory: VolumeDialogSliderTouchesViewModel.Factory) {
+constructor(private val viewModel: VolumeDialogSliderInputEventsViewModel) {
 
     @SuppressLint("ClickableViewAccessibility")
     fun bind(view: View) {
         with(view.requireViewById<Slider>(R.id.volume_dialog_slider)) {
-            repeatWhenAttached {
-                viewModel(
-                    traceName = "VolumeDialogSliderTouchesViewBinder",
-                    minWindowLifecycleState = WindowLifecycleState.ATTACHED,
-                    factory = { viewModelFactory.create() },
-                ) { viewModel ->
-                    setOnTouchListener { _, event ->
-                        viewModel.onTouchEvent(event)
-                        false
-                    }
-                }
+            setOnTouchListener { _, event ->
+                viewModel.onTouchEvent(event)
+                false
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index 1c231b5..e52bad9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -20,9 +20,6 @@
 import android.animation.ObjectAnimator
 import android.view.View
 import android.view.animation.DecelerateInterpolator
-import com.android.systemui.lifecycle.WindowLifecycleState
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.viewModel
 import com.android.systemui.res.R
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
 import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
@@ -33,7 +30,7 @@
 import com.google.android.material.slider.Slider
 import javax.inject.Inject
 import kotlin.math.roundToInt
-import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 
@@ -43,36 +40,23 @@
 class VolumeDialogSliderViewBinder
 @Inject
 constructor(
-    private val viewModelFactory: VolumeDialogSliderViewModel.Factory,
+    private val viewModel: VolumeDialogSliderViewModel,
     private val jankListenerFactory: JankListenerFactory,
 ) {
 
-    fun bind(view: View) {
-        with(view) {
-            val sliderView: Slider =
-                requireViewById<Slider>(R.id.volume_dialog_slider).apply {
-                    labelBehavior = LabelFormatter.LABEL_GONE
-                }
-            repeatWhenAttached {
-                viewModel(
-                    traceName = "VolumeDialogSliderViewBinder",
-                    minWindowLifecycleState = WindowLifecycleState.ATTACHED,
-                    factory = { viewModelFactory.create() },
-                ) { viewModel ->
-                    sliderView.addOnChangeListener { _, value, fromUser ->
-                        viewModel.setStreamVolume(value.roundToInt(), fromUser)
-                    }
-
-                    viewModel.model.onEach { it.bindToSlider(sliderView) }.launchIn(this)
-
-                    awaitCancellation()
-                }
+    fun CoroutineScope.bind(view: View) {
+        val sliderView: Slider =
+            view.requireViewById<Slider>(R.id.volume_dialog_slider).apply {
+                labelBehavior = LabelFormatter.LABEL_GONE
             }
+        sliderView.addOnChangeListener { _, value, fromUser ->
+            viewModel.setStreamVolume(value.roundToInt(), fromUser)
         }
+
+        viewModel.model.onEach { it.bindToSlider(sliderView) }.launchIn(this)
     }
 
     private suspend fun VolumeDialogStreamModel.bindToSlider(slider: Slider) {
-        slider.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> }
         with(slider) {
             valueFrom = levelMin.toFloat()
             valueTo = levelMax.toFloat()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index 1b2b4ff..c9b5259 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -21,58 +21,48 @@
 import android.view.ViewGroup
 import androidx.annotation.LayoutRes
 import androidx.compose.ui.util.fastForEachIndexed
-import com.android.systemui.lifecycle.WindowLifecycleState
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.viewModel
 import com.android.systemui.res.R
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
 import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlidersViewModel
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 
 @VolumeDialogScope
 class VolumeDialogSlidersViewBinder
 @Inject
-constructor(private val viewModelFactory: VolumeDialogSlidersViewModel.Factory) {
+constructor(private val viewModel: VolumeDialogSlidersViewModel) {
 
-    fun bind(view: View) {
-        with(view) {
-            val floatingSlidersContainer: ViewGroup =
-                requireViewById(R.id.volume_dialog_floating_sliders_container)
-            val mainSliderContainer: View =
-                requireViewById(R.id.volume_dialog_main_slider_container)
-            repeatWhenAttached {
-                viewModel(
-                    traceName = "VolumeDialogSlidersViewBinder",
-                    minWindowLifecycleState = WindowLifecycleState.ATTACHED,
-                    factory = { viewModelFactory.create() },
-                ) { viewModel ->
-                    viewModel.sliders
-                        .onEach { uiModel ->
-                            uiModel.sliderComponent.bindSlider(mainSliderContainer)
+    fun CoroutineScope.bind(view: View) {
+        val floatingSlidersContainer: ViewGroup =
+            view.requireViewById(R.id.volume_dialog_floating_sliders_container)
+        val mainSliderContainer: View =
+            view.requireViewById(R.id.volume_dialog_main_slider_container)
+        viewModel.sliders
+            .onEach { uiModel ->
+                bindSlider(uiModel.sliderComponent, mainSliderContainer)
 
-                            val floatingSliderViewBinders = uiModel.floatingSliderComponent
-                            floatingSlidersContainer.ensureChildCount(
-                                viewLayoutId = R.layout.volume_dialog_slider_floating,
-                                count = floatingSliderViewBinders.size,
-                            )
-                            floatingSliderViewBinders.fastForEachIndexed { index, sliderComponent ->
-                                sliderComponent.bindSlider(
-                                    floatingSlidersContainer.getChildAt(index)
-                                )
-                            }
-                        }
-                        .launchIn(this)
+                val floatingSliderViewBinders = uiModel.floatingSliderComponent
+                floatingSlidersContainer.ensureChildCount(
+                    viewLayoutId = R.layout.volume_dialog_slider_floating,
+                    count = floatingSliderViewBinders.size,
+                )
+                floatingSliderViewBinders.fastForEachIndexed { index, sliderComponent ->
+                    bindSlider(sliderComponent, floatingSlidersContainer.getChildAt(index))
                 }
             }
-        }
+            .launchIn(this)
     }
 
-    private fun VolumeDialogSliderComponent.bindSlider(sliderContainer: View) {
-        sliderViewBinder().bind(sliderContainer)
-        sliderTouchesViewBinder().bind(sliderContainer)
+    private fun CoroutineScope.bindSlider(
+        component: VolumeDialogSliderComponent,
+        sliderContainer: View,
+    ) {
+        with(component.sliderViewBinder()) { bind(sliderContainer) }
+        with(component.sliderTouchesViewBinder()) { bind(sliderContainer) }
+        with(component.sliderHapticsViewBinder()) { bind(sliderContainer) }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt
new file mode 100644
index 0000000..755776ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.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.dialog.sliders.ui.viewmodel
+
+import android.view.MotionEvent
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
+import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInputEventsInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.stateIn
+
+@VolumeDialogSliderScope
+class VolumeDialogSliderInputEventsViewModel
+@Inject
+constructor(
+    @VolumeDialog private val coroutineScope: CoroutineScope,
+    private val interactor: VolumeDialogSliderInputEventsInteractor,
+) {
+
+    val event =
+        interactor.event.stateIn(coroutineScope, SharingStarted.Eagerly, null).filterNotNull()
+
+    fun onTouchEvent(event: MotionEvent) {
+        interactor.onTouchEvent(event)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt
deleted file mode 100644
index 144c75d..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt
+++ /dev/null
@@ -1,36 +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.dialog.sliders.ui.viewmodel
-
-import android.view.MotionEvent
-import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInputEventsInteractor
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-
-class VolumeDialogSliderTouchesViewModel
-@AssistedInject
-constructor(private val interactor: VolumeDialogSliderInputEventsInteractor) {
-
-    fun onTouchEvent(event: MotionEvent) {
-        interactor.onTouchEvent(event)
-    }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(): VolumeDialogSliderTouchesViewModel
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
index cf04d45..6dd5b63 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -21,9 +21,9 @@
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -49,8 +49,9 @@
 private const val VOLUME_UPDATE_GRACE_PERIOD = 1000
 
 @OptIn(ExperimentalCoroutinesApi::class)
+@VolumeDialogSliderScope
 class VolumeDialogSliderViewModel
-@AssistedInject
+@Inject
 constructor(
     private val interactor: VolumeDialogSliderInteractor,
     private val visibilityInteractor: VolumeDialogVisibilityInteractor,
@@ -90,10 +91,4 @@
     private fun getTimestampMillis(): Long = systemClock.uptimeMillis()
 
     private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long)
-
-    @AssistedFactory
-    interface Factory {
-
-        fun create(): VolumeDialogSliderViewModel
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
index d197223..d8e6aec 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.volume.dialog.sliders.ui.viewmodel
 
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
@@ -28,8 +28,9 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
+@VolumeDialogScope
 class VolumeDialogSlidersViewModel
-@AssistedInject
+@Inject
 constructor(
     @VolumeDialog coroutineScope: CoroutineScope,
     private val slidersInteractor: VolumeDialogSlidersInteractor,
@@ -47,12 +48,6 @@
             }
             .stateIn(coroutineScope, SharingStarted.Eagerly, null)
             .filterNotNull()
-
-    @AssistedFactory
-    interface Factory {
-
-        fun create(): VolumeDialogSlidersViewModel
-    }
 }
 
 /** Models slider ui */
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index f6c1743..a3166a9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -25,9 +25,6 @@
 import android.view.ViewTreeObserver.InternalInsetsInfo
 import androidx.constraintlayout.motion.widget.MotionLayout
 import com.android.internal.view.RotationPolicy
-import com.android.systemui.lifecycle.WindowLifecycleState
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.viewModel
 import com.android.systemui.res.R
 import com.android.systemui.util.children
 import com.android.systemui.volume.SystemUIInterpolators
@@ -44,7 +41,6 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.launchIn
@@ -61,7 +57,7 @@
 @Inject
 constructor(
     private val volumeResources: VolumeDialogResources,
-    private val dialogViewModelFactory: VolumeDialogViewModel.Factory,
+    private val viewModel: VolumeDialogViewModel,
     private val jankListenerFactory: JankListenerFactory,
     private val tracer: VolumeTracer,
     private val volumeDialogRingerViewBinder: VolumeDialogRingerViewBinder,
@@ -69,35 +65,27 @@
     private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
 ) {
 
-    fun bind(dialog: Dialog) {
+    fun CoroutineScope.bind(dialog: Dialog) {
         // Root view of the Volume Dialog.
         val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog_root)
         root.alpha = 0f
-        root.repeatWhenAttached {
-            root.viewModel(
-                traceName = "VolumeDialogViewBinder",
-                minWindowLifecycleState = WindowLifecycleState.ATTACHED,
-                factory = { dialogViewModelFactory.create() },
-            ) { viewModel ->
-                animateVisibility(root, dialog, viewModel.dialogVisibilityModel)
 
-                viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this)
-                viewModel.motionState
-                    .scan(0) { acc, motionState ->
-                        // don't animate the initial state
-                        root.transitionToState(motionState, animate = acc != 0)
-                        acc + 1
-                    }
-                    .launchIn(this)
+        animateVisibility(root, dialog, viewModel.dialogVisibilityModel)
 
-                launch { root.viewTreeObserver.computeInternalInsetsListener(root) }
-
-                awaitCancellation()
+        viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this)
+        viewModel.motionState
+            .scan(0) { acc, motionState ->
+                // don't animate the initial state
+                root.transitionToState(motionState, animate = acc != 0)
+                acc + 1
             }
-        }
-        volumeDialogRingerViewBinder.bind(root)
-        slidersViewBinder.bind(root)
-        settingsButtonViewBinder.bind(root)
+            .launchIn(this)
+
+        launch { root.viewTreeObserver.computeInternalInsetsListener(root) }
+
+        with(volumeDialogRingerViewBinder) { bind(root) }
+        with(slidersViewBinder) { bind(root) }
+        with(settingsButtonViewBinder) { bind(root) }
     }
 
     private fun CoroutineScope.animateVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
index c7f5801..10cf615 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
@@ -20,6 +20,8 @@
 import android.animation.AnimatorListenerAdapter
 import android.animation.ValueAnimator
 import android.view.ViewPropertyAnimator
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringAnimation
 import kotlin.coroutines.resume
 import kotlinx.coroutines.CancellableContinuation
 import kotlinx.coroutines.suspendCancellableCoroutine
@@ -80,6 +82,27 @@
     }
 }
 
+/**
+ * Starts spring animation and suspends until it's finished. Cancels the animation if the running
+ * coroutine is cancelled.
+ */
+suspend fun SpringAnimation.suspendAnimate(
+    animationUpdateListener: DynamicAnimation.OnAnimationUpdateListener? = null,
+    animationEndListener: DynamicAnimation.OnAnimationEndListener? = null,
+) = suspendCancellableCoroutine { continuation ->
+    animationUpdateListener?.let(::addUpdateListener)
+    addEndListener { animation, canceled, value, velocity ->
+        continuation.resumeIfCan(Unit)
+        animationEndListener?.onAnimationEnd(animation, canceled, value, velocity)
+    }
+    animateToFinalPosition(1F)
+    continuation.invokeOnCancellation {
+        animationUpdateListener?.let(::removeUpdateListener)
+        animationEndListener?.let(::removeEndListener)
+        cancel()
+    }
+}
+
 private fun <T> CancellableContinuation<T>.resumeIfCan(value: T) {
     if (!isCancelled && !isCompleted) {
         resume(value)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
index e858cfe..9bab1b0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
@@ -17,15 +17,18 @@
 package com.android.systemui.volume.dialog.ui.viewmodel
 
 import com.android.systemui.volume.Events
-import com.android.systemui.volume.dialog.dagger.VolumeDialogComponent
+import com.android.systemui.volume.dialog.VolumeDialog
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogSafetyWarningInteractor
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
 import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.mapLatest
 
@@ -34,41 +37,45 @@
 class VolumeDialogPluginViewModel
 @Inject
 constructor(
-    private val componentFactory: VolumeDialogComponent.Factory,
+    @VolumeDialogPlugin private val coroutineScope: CoroutineScope,
     private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
+    private val dialogSafetyWarningInteractor: VolumeDialogSafetyWarningInteractor,
+    private val volumeDialogProvider: Provider<VolumeDialog>,
     private val logger: VolumeDialogLogger,
 ) {
 
-    suspend fun launchVolumeDialog() {
-        coroutineScope {
-            dialogVisibilityInteractor.dialogVisibility
-                .mapLatest { visibilityModel ->
-                    with(visibilityModel) {
-                        if (this is VolumeDialogVisibilityModel.Visible) {
-                            showDialog(componentFactory)
-                            Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked)
-                            logger.onShow(reason)
-                        }
-                        if (this is VolumeDialogVisibilityModel.Dismissed) {
-                            Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason)
-                            logger.onDismiss(reason)
-                        }
+    fun launchVolumeDialog() {
+        dialogVisibilityInteractor.dialogVisibility
+            .mapLatest { visibilityModel ->
+                with(visibilityModel) {
+                    if (this is VolumeDialogVisibilityModel.Visible) {
+                        showDialog()
+                        Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, keyguardLocked)
+                        logger.onShow(reason)
+                    }
+                    if (this is VolumeDialogVisibilityModel.Dismissed) {
+                        Events.writeEvent(Events.EVENT_DISMISS_DIALOG, reason)
+                        logger.onDismiss(reason)
                     }
                 }
-                .launchIn(this)
-        }
+            }
+            .launchIn(coroutineScope)
     }
 
-    private suspend fun showDialog(componentFactory: VolumeDialogComponent.Factory): Unit =
-        coroutineScope {
-            val volumeDialogComponent: VolumeDialogComponent = componentFactory.create(this)
-            val dialog =
-                volumeDialogComponent.volumeDialog().apply {
-                    setOnDismissListener {
-                        volumeDialogComponent.coroutineScope().cancel()
-                        dialogVisibilityInteractor.dismissDialog(Events.DISMISS_REASON_UNKNOWN)
-                    }
+    val isShowingSafetyWarning: Flow<Boolean> = dialogSafetyWarningInteractor.isShowingSafetyWarning
+
+    fun onSafetyWarningDismissed() {
+        dialogSafetyWarningInteractor.onSafetyWarningDismissed()
+    }
+
+    private fun showDialog() {
+        volumeDialogProvider
+            .get()
+            .apply {
+                setOnDismissListener {
+                    dialogVisibilityInteractor.dismissDialog(Events.DISMISS_REASON_UNKNOWN)
                 }
-            dialog.show()
-        }
+            }
+            .show()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
index 0352799..b20dffb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.statusbar.policy.devicePosture
 import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
@@ -30,9 +31,7 @@
 import com.android.systemui.volume.dialog.shared.model.streamLabel
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
 import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
+import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filterNotNull
@@ -40,14 +39,14 @@
 import kotlinx.coroutines.flow.onStart
 
 /** Provides a state for the Volume Dialog. */
-@OptIn(ExperimentalCoroutinesApi::class)
+@VolumeDialogScope
 class VolumeDialogViewModel
-@AssistedInject
+@Inject
 constructor(
     private val context: Context,
-    dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
+    private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
     volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor,
-    volumeDialogStateInteractor: VolumeDialogStateInteractor,
+    private val volumeDialogStateInteractor: VolumeDialogStateInteractor,
     devicePostureController: DevicePostureController,
     configurationController: ConfigurationController,
 ) {
@@ -84,9 +83,4 @@
         val isHalfOpen = devicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED
         return isLandscape && isHalfOpen
     }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(): VolumeDialogViewModel
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index 1d32a4f..389b6fb 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -32,6 +32,7 @@
 import android.service.quickaccesswallet.GetWalletCardsRequest;
 import android.service.quickaccesswallet.QuickAccessWalletClient;
 import android.service.quickaccesswallet.QuickAccessWalletClientImpl;
+import android.service.quickaccesswallet.WalletCard;
 import android.util.Log;
 
 import com.android.systemui.animation.ActivityTransitionAnimator;
@@ -268,6 +269,23 @@
                 });
     }
 
+    /**
+     * Starts the {@link android.app.PendingIntent} for a {@link WalletCard}.
+     *
+     * This should be used to open a selected card from the QuickAccessWallet UI or
+     * the settings tile.
+     *
+     * @param activityStarter an {@link ActivityStarter} to launch the Intent or PendingIntent.
+     * @param animationController an {@link ActivityTransitionAnimator.Controller} to provide a
+     *                            smooth animation for the activity launch.
+     */
+    public void startWalletCardPendingIntent(WalletCard card,
+            ActivityStarter activityStarter,
+            ActivityTransitionAnimator.Controller animationController) {
+        activityStarter.postStartActivityDismissingKeyguard(
+                card.getPendingIntent(), animationController);
+    }
+
     private Intent getSysUiWalletIntent() {
         return new Intent(mContext, WalletActivity.class)
                 .setAction(Intent.ACTION_VIEW);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index bf13ceb..4bc5f23 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -45,6 +45,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
@@ -56,6 +58,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.logging.CarrierTextManagerLogger;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -200,6 +203,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void testAirplaneMode() {
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
         reset(mCarrierTextCallback);
@@ -220,8 +224,32 @@
         assertEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testAirplaneMode_flagEnabled() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(0)).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        mCarrierTextManager.updateCarrierText();
+
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+        assertEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
+    }
+
     /** regression test for b/281706473, caused by sending NULL plmn / spn to the logger */
     @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void testAirplaneMode_noSim_nullPlmn_nullSpn_doesNotCrash() {
         // GIVEN - sticy broadcast that returns a null PLMN and null SPN
         Intent stickyIntent = new Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
@@ -263,7 +291,52 @@
         // No assert, this test should not crash
     }
 
+    /** regression test for b/281706473, caused by sending NULL plmn / spn to the logger */
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testAirplaneMode_noSim_nullPlmn_nullSpn_doesNotCrash_flagEnabled() {
+        // GIVEN - sticy broadcast that returns a null PLMN and null SPN
+        Intent stickyIntent = new Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
+        stickyIntent.putExtra(TelephonyManager.EXTRA_SHOW_PLMN, true);
+        stickyIntent.removeExtra(TelephonyManager.EXTRA_PLMN);
+        stickyIntent.putExtra(TelephonyManager.EXTRA_SHOW_SPN, true);
+        stickyIntent.removeExtra(TelephonyManager.EXTRA_SPN);
+
+        mCarrierTextManager = new CarrierTextManager.Builder(
+                getContextSpyForStickyBroadcast(stickyIntent),
+                mContext.getResources(),
+                mWifiRepository,
+                mSatelliteViewModel,
+                mJavaAdapter,
+                mTelephonyManager,
+                mTelephonyListenerManager,
+                mWakefulnessLifecycle,
+                mMainExecutor,
+                mBgExecutor,
+                mKeyguardUpdateMonitor,
+                mLogger
+        )
+                .setShowAirplaneMode(true)
+                .setShowMissingSim(true)
+                .build();
+
+        // GIVEN - airplane mode is off (causing CTM to fetch the sticky broadcast)
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(0))
+                .thenReturn(TelephonyManager.SIM_STATE_NOT_READY);
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        // WHEN CTM fetches the broadcast and attempts to log the result, no crash results
+        mCarrierTextManager.updateCarrierText();
+
+        // No assert, this test should not crash
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void testCardIOError() {
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
@@ -299,6 +372,44 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testCardIOError_flagEnabled() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(0)).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(1)).thenReturn(
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR);
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        mCarrierTextManager.mCallback.onSimStateChanged(3, 1,
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR);
+
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+        assertEquals("TEST_CARRIER" + SEPARATOR + INVALID_CARD_TEXT, captor.getValue().carrierText);
+        // There's only one subscription in the list
+        assertEquals(1, captor.getValue().listOfCarriers.length);
+        assertEquals(TEST_CARRIER, captor.getValue().listOfCarriers[0]);
+
+        // Now it becomes single SIM active mode.
+        reset(mCarrierTextCallback);
+        when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
+        // Update carrier text. It should ignore error state of subId 3 in inactive slotId.
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+        assertEquals("TEST_CARRIER", captor.getValue().carrierText);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void testWrongSlots() {
         reset(mCarrierTextCallback);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(
@@ -313,6 +424,22 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testWrongSlots_flagEnabled() {
+        reset(mCarrierTextCallback);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(
+                new ArrayList<>());
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR);
+        // This should not produce an out of bounds error, even though there are no subscriptions
+        mCarrierTextManager.mCallback.onSimStateChanged(0, -3,
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR);
+        mCarrierTextManager.mCallback.onSimStateChanged(0, 3, TelephonyManager.SIM_STATE_READY);
+        verify(mCarrierTextCallback, never()).updateCarrierInfo(any());
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void testMoreSlotsThanSubs() {
         reset(mCarrierTextCallback);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(
@@ -335,6 +462,29 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testMoreSlotsThanSubs_flagEnabled() {
+        reset(mCarrierTextCallback);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(
+                new ArrayList<>());
+
+        // STOPSHIP(b/130246708) This line makes sure that SubscriptionManager provides the
+        // same answer as KeyguardUpdateMonitor. Remove when this is addressed
+        when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(
+                new ArrayList<>());
+
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR);
+        // This should not produce an out of bounds error, even though there are no subscriptions
+        mCarrierTextManager.mCallback.onSimStateChanged(0, 1,
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR);
+
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(
+                any(CarrierTextManager.CarrierTextCallbackInfo.class));
+    }
+
+    @Test
     public void testCallback() {
         reset(mCarrierTextCallback);
         mCarrierTextManager.postToCallback(mCarrierTextCallbackInfo);
@@ -360,6 +510,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void testCreateInfo_OneValidSubscription() {
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
@@ -385,6 +536,33 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testCreateInfo_OneValidSubscription_flagEnabled() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
+        assertEquals(1, info.listOfCarriers.length);
+        assertEquals(TEST_CARRIER, info.listOfCarriers[0]);
+        assertEquals(1, info.subscriptionIds.length);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void testCreateInfo_OneValidSubscriptionWithRoaming() {
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
@@ -410,6 +588,33 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testCreateInfo_OneValidSubscriptionWithRoaming_flagEnabled() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION_ROAMING);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
+        assertEquals(1, info.listOfCarriers.length);
+        assertTrue(info.listOfCarriers[0].toString().contains(TEST_CARRIER));
+        assertEquals(1, info.subscriptionIds.length);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void testCarrierText_noTextOnReadySimWhenNull() {
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
@@ -434,6 +639,32 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testCarrierText_noTextOnReadySimWhenNull_flagEnabled() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION_NULL);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertTrue("Carrier text should be empty, instead it's " + captor.getValue().carrierText,
+                TextUtils.isEmpty(captor.getValue().carrierText));
+        assertFalse("No SIM should be available", captor.getValue().anySimReady);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void testCarrierText_noTextOnReadySimWhenNull_airplaneMode_wifiOn() {
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
         reset(mCarrierTextCallback);
@@ -472,6 +703,46 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testCarrierText_noTextOnReadySimWhenNull_airplaneMode_wifiOn_flagEnabled() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION_NULL);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+        assertFalse(mWifiRepository.isWifiConnectedWithValidSsid());
+        mWifiRepository.setWifiNetwork(
+                WifiNetworkModel.Active.Companion.of(
+                        /* isValidated= */ false,
+                        /* level= */ 0,
+                        /* ssid= */ "",
+                        /* hotspotDeviceType= */ WifiNetworkModel.HotspotDeviceType.NONE));
+        assertTrue(mWifiRepository.isWifiConnectedWithValidSsid());
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+        ServiceState ss = mock(ServiceState.class);
+        when(ss.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        mKeyguardUpdateMonitor.mServiceStates.put(TEST_SUBSCRIPTION_NULL.getSubscriptionId(), ss);
+
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertFalse("No SIM should be available", captor.getValue().anySimReady);
+        // There's no airplane mode if at least one SIM is State.READY and there's wifi
+        assertFalse("Device should not be in airplane mode", captor.getValue().airplaneMode);
+        assertNotEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void carrierText_satelliteTextNull_isSatelliteFalse_textNotUsed() {
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
@@ -497,6 +768,33 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void carrierText_satelliteTextNull_isSatelliteFalse_textNotUsed_flagEnabled() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        // WHEN the satellite text is null
+        mSatelliteViewModel.getCarrierText().setValue(null);
+        mTestScope.getTestScheduler().runCurrent();
+
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+
+        // THEN satellite mode is false and the default subscription carrier text is used
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+        assertThat(captor.getValue().isInSatelliteMode).isFalse();
+        assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void carrierText_hasSatelliteText_isSatelliteTrue_textUsed() {
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
@@ -522,6 +820,33 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void carrierText_hasSatelliteText_isSatelliteTrue_textUsed_flagEnabled() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        // WHEN the satellite text is non-null
+        mSatelliteViewModel.getCarrierText().setValue("Satellite Test Text");
+        mTestScope.getTestScheduler().runCurrent();
+
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+
+        // THEN satellite mode is true and the satellite text is used
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+        assertThat(captor.getValue().isInSatelliteMode).isTrue();
+        assertThat(captor.getValue().carrierText).isEqualTo("Satellite Test Text");
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void carrierText_satelliteTextUpdates_autoTriggersCallback() {
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
@@ -559,6 +884,45 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void carrierText_satelliteTextUpdates_autoTriggersCallback_flagEnabled() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        // WHEN the satellite text is set
+        mSatelliteViewModel.getCarrierText().setValue("Test satellite text");
+        mTestScope.getTestScheduler().runCurrent();
+
+        // THEN we should automatically re-trigger #updateCarrierText and get callback info
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+        // AND use the satellite text as the carrier text
+        assertThat(captor.getValue().isInSatelliteMode).isTrue();
+        assertThat(captor.getValue().carrierText).isEqualTo("Test satellite text");
+
+        // WHEN the satellite text is reset to null
+        reset(mCarrierTextCallback);
+        mSatelliteViewModel.getCarrierText().setValue(null);
+        mTestScope.getTestScheduler().runCurrent();
+
+        // THEN we should automatically re-trigger #updateCarrierText and get callback info
+        // that doesn't include the satellite info
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+        assertThat(captor.getValue().isInSatelliteMode).isFalse();
+        assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void carrierText_updatedWhileNotListening_getsNewValueWhenListening() {
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
@@ -602,6 +966,50 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void carrierText_updatedWhileNotListening_getsNewValueWhenListening_flagEnabled() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        mSatelliteViewModel.getCarrierText().setValue("Old satellite text");
+        mTestScope.getTestScheduler().runCurrent();
+
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+        assertThat(captor.getValue().carrierText).isEqualTo("Old satellite text");
+
+        // WHEN we stop listening
+        reset(mCarrierTextCallback);
+        mCarrierTextManager.setListening(null);
+
+        // AND the satellite text updates
+        mSatelliteViewModel.getCarrierText().setValue("New satellite text");
+
+        // THEN we don't get new callback info because we aren't listening
+        verify(mCarrierTextCallback, never()).updateCarrierInfo(any());
+
+        // WHEN we start listening again
+        reset(mCarrierTextCallback);
+        mCarrierTextManager.setListening(mCarrierTextCallback);
+
+        // THEN we should automatically re-trigger #updateCarrierText and get callback info
+        // that includes the new satellite state and text
+        mTestScope.getTestScheduler().runCurrent();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+        assertThat(captor.getValue().isInSatelliteMode).isTrue();
+        assertThat(captor.getValue().carrierText).isEqualTo("New satellite text");
+    }
+
+    @Test
     public void testCreateInfo_noSubscriptions() {
         reset(mCarrierTextCallback);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(
@@ -622,6 +1030,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void testCarrierText_oneValidSubscription() {
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
@@ -644,6 +1053,30 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testCarrierText_oneValidSubscription_flagEnabled() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void testCarrierText_twoValidSubscriptions() {
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
@@ -668,6 +1101,32 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testCarrierText_twoValidSubscriptions_flagEnabled() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+                TelephonyManager.SIM_STATE_READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER,
+                captor.getValue().carrierText);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void testCarrierText_oneDisabledSub() {
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
@@ -693,6 +1152,33 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testCarrierText_oneDisabledSub_flagEnabled() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt()))
+                .thenReturn(TelephonyManager.SIM_STATE_READY)
+                .thenReturn(TelephonyManager.SIM_STATE_NOT_READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertEquals(TEST_CARRIER,
+                captor.getValue().carrierText);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void testCarrierText_firstDisabledSub() {
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
@@ -718,6 +1204,33 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testCarrierText_firstDisabledSub_flagEnabled() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt()))
+                .thenReturn(TelephonyManager.SIM_STATE_NOT_READY)
+                .thenReturn(TelephonyManager.SIM_STATE_READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertEquals(TEST_CARRIER,
+                captor.getValue().carrierText);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
     public void testCarrierText_threeSubsMiddleDisabled() {
         reset(mCarrierTextCallback);
         List<SubscriptionInfo> list = new ArrayList<>();
@@ -744,6 +1257,33 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testCarrierText_threeSubsMiddleDisabled_flagEnabled() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        list.add(TEST_SUBSCRIPTION);
+        list.add(TEST_SUBSCRIPTION);
+        when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt()))
+                .thenReturn(TelephonyManager.SIM_STATE_READY)
+                .thenReturn(TelephonyManager.SIM_STATE_NOT_READY)
+                .thenReturn(TelephonyManager.SIM_STATE_READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
+
+        mCarrierTextManager.updateCarrierText();
+        FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER,
+                captor.getValue().carrierText);
+    }
+
+    @Test
     public void testGetStatusForIccState() {
         when(mKeyguardUpdateMonitor.isDeviceProvisioned()).thenReturn(false);
         assertEquals(CarrierTextManager.StatusMode.SimMissingLocked,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 2aa6e7b..ae94544 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -24,7 +24,6 @@
 import android.widget.FrameLayout
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND_ACTIVE
 import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND_INACTIVE
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
@@ -107,6 +106,7 @@
     private lateinit var repository: FakeKeyguardRepository
     private val clockBuffers = ClockMessageBuffers(LogcatOnlyMessageBuffer(LogLevel.DEBUG))
     private lateinit var underTest: ClockEventController
+    private lateinit var dndModeId: String
 
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock private lateinit var batteryController: BatteryController
@@ -156,6 +156,7 @@
         whenever(largeClockController.theme).thenReturn(ThemeConfig(true, null))
         whenever(userTracker.userId).thenReturn(1)
 
+        dndModeId = MANUAL_DND_INACTIVE.id
         zenModeRepository.addMode(MANUAL_DND_INACTIVE)
 
         repository = FakeKeyguardRepository()
@@ -528,7 +529,7 @@
         testScope.runTest {
             underTest.listenForDnd(testScope.backgroundScope)
 
-            zenModeRepository.replaceMode(MANUAL_DND_INACTIVE.id, MANUAL_DND_ACTIVE)
+            zenModeRepository.activateMode(dndModeId)
             runCurrent()
 
             verify(events)
@@ -536,7 +537,7 @@
                     eq(ZenData(ZenMode.IMPORTANT_INTERRUPTIONS, R.string::dnd_is_on.name))
                 )
 
-            zenModeRepository.replaceMode(MANUAL_DND_ACTIVE.id, MANUAL_DND_INACTIVE)
+            zenModeRepository.deactivateMode(dndModeId)
             runCurrent()
 
             verify(events).onZenDataChanged(eq(ZenData(ZenMode.OFF, R.string::dnd_is_off.name)))
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index a39ca5d..839a2bd 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -103,6 +103,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.FlagsParameterization;
 import android.service.dreams.IDreamManager;
@@ -200,8 +201,9 @@
     private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
     private static final int TEST_CARRIER_ID = 1;
     private static final String TEST_GROUP_UUID = "59b5c870-fc4c-47a4-a99e-9db826b48b24";
-    private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(1, "", 0,
-            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
+    private static final int TEST_SLOT_ID = 3;
+    private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(1, "",
+            TEST_SLOT_ID, TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
             DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID,
             TEST_CARRIER_ID, 0);
     private static final SubscriptionInfo TEST_SUBSCRIPTION_2 = new SubscriptionInfo(2, "", 0,
@@ -483,7 +485,8 @@
     }
 
     @Test
-    public void testSimStateInitialized() {
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testSimStateInitialized_flagDisabled() {
         cleanupKeyguardUpdateMonitor();
         final int subId = 3;
         final int state = TelephonyManager.SIM_STATE_ABSENT;
@@ -500,6 +503,24 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testSimStateInitialized_flagEnabled() {
+        cleanupKeyguardUpdateMonitor();
+        final int state = TelephonyManager.SIM_STATE_ABSENT;
+        final int slotId = 0;
+        final int subId = 3;
+        when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
+        when(mTelephonyManager.getSimState(anyInt())).thenReturn(state);
+        when(mSubscriptionManager.getSubscriptionIds(anyInt())).thenReturn(new int[]{subId});
+
+        KeyguardUpdateMonitor testKUM = new TestableKeyguardUpdateMonitor(mContext);
+
+        mTestableLooper.processAllMessages();
+
+        assertThat(testKUM.getSimStateForSlotId(slotId)).isEqualTo(state);
+    }
+
+    @Test
     public void testIgnoresSimStateCallback_rebroadcast() {
         Intent intent = defaultSimStateChangedIntent();
 
@@ -1240,7 +1261,8 @@
     }
 
     @Test
-    public void testActiveSubscriptionBecomesInactive() {
+    @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testActiveSubscriptionBecomesInactive_flagDisabled() {
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION);
         when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(list);
@@ -1263,6 +1285,28 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+    public void testActiveSubscriptionBecomesInactive_flagEnabled() {
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION);
+        when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(list);
+        mKeyguardUpdateMonitor.mPhoneStateListener.onActiveDataSubscriptionIdChanged(
+                TEST_SUBSCRIPTION.getSubscriptionId());
+        mTestableLooper.processAllMessages();
+        assertThat(mKeyguardUpdateMonitor.mSimDatasBySlotId.get(TEST_SLOT_ID))
+                .isNotNull();
+
+        when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList())
+                .thenReturn(new ArrayList<>());
+        mKeyguardUpdateMonitor.mPhoneStateListener.onActiveDataSubscriptionIdChanged(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        mTestableLooper.processAllMessages();
+
+        assertThat(mKeyguardUpdateMonitor.mSimDatasBySlotId.get(TEST_SLOT_ID))
+                .isNull();
+    }
+
+    @Test
     public void testActiveSubscriptionList_filtersProvisioningNetworks() {
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION_PROVISIONING);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index fa88f62..ad5f960 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -930,6 +930,10 @@
     }
 
     private void advanceTimeBy(long timeDelta) {
+        if (timeDelta == mWaitAnimationDuration) {
+            mAnimatorTestRule.advanceAnimationDuration(timeDelta);
+            return;
+        }
         mAnimatorTestRule.advanceTimeBy(timeDelta);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
new file mode 100644
index 0000000..8552e48
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -0,0 +1,1543 @@
+/*
+ * 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;
+
+import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.WindowInsets.Type.systemGestures;
+import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.AdditionalAnswers.returnsSecondArg;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static java.util.Arrays.asList;
+
+import android.animation.ValueAnimator;
+import android.annotation.IdRes;
+import android.annotation.Nullable;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.testing.TestableLooper;
+import android.testing.TestableResources;
+import android.util.Size;
+import android.view.AttachedSurfaceControl;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewRootImpl;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IRemoteMagnificationAnimationCallback;
+import android.widget.FrameLayout;
+import android.window.InputTransferToken;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.AnimatorTestRule;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.res.R;
+import com.android.systemui.settings.FakeDisplayTracker;
+import com.android.systemui.util.FakeSharedPreferences;
+import com.android.systemui.util.leak.ReferenceTestUtils;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.utils.os.FakeHandler;
+
+import com.google.common.util.concurrent.AtomicDouble;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+
+@LargeTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidJUnit4.class)
+public class WindowMagnificationControllerTest extends SysuiTestCase {
+
+    @Rule
+    // NOTE: pass 'null' to allow this test advances time on the main thread.
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null);
+
+    private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
+    @Mock
+    private MirrorWindowControl mMirrorWindowControl;
+    @Mock
+    private WindowMagnifierCallback mWindowMagnifierCallback;
+    @Mock
+    IRemoteMagnificationAnimationCallback mAnimationCallback;
+    @Mock
+    IRemoteMagnificationAnimationCallback mAnimationCallback2;
+
+    private SurfaceControl.Transaction mTransaction;
+    @Mock
+    private SecureSettings mSecureSettings;
+
+    private long mWaitAnimationDuration;
+    private long mWaitBounceEffectDuration;
+
+    private Handler mHandler;
+    private TestableWindowManager mWindowManager;
+    private SysUiState mSysUiState;
+    private Resources mResources;
+    private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
+    private WindowMagnificationController mWindowMagnificationController;
+    private Instrumentation mInstrumentation;
+    private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0);
+    private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+
+    private View mSpyView;
+    private View.OnTouchListener mTouchListener;
+
+    private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+
+    // This list contains all SurfaceControlViewHosts created during a given test. If the
+    // magnification window is recreated during a test, the list will contain more than a single
+    // element.
+    private List<SurfaceControlViewHost> mSurfaceControlViewHosts = new ArrayList<>();
+    // The most recently created SurfaceControlViewHost.
+    private SurfaceControlViewHost mSurfaceControlViewHost;
+    private KosmosJavaAdapter mKosmos;
+    private FakeSharedPreferences mSharedPreferences;
+
+    /**
+     *  return whether window magnification is supported for current test context.
+     */
+    private boolean isWindowModeSupported() {
+        return getContext().getPackageManager().hasSystemFeature(FEATURE_WINDOW_MAGNIFICATION);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(mContext);
+        mKosmos = new KosmosJavaAdapter(this);
+        mContext = Mockito.spy(getContext());
+        mHandler = new FakeHandler(TestableLooper.get(this).getLooper());
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        final WindowManager wm = mContext.getSystemService(WindowManager.class);
+        mWindowManager = spy(new TestableWindowManager(wm));
+
+        mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+        mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin());
+        mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
+        when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then(
+                returnsSecondArg());
+        when(mSecureSettings.getFloatForUser(anyString(), anyFloat(), anyInt())).then(
+                returnsSecondArg());
+
+        mResources = getContext().getOrCreateTestableResources().getResources();
+        // prevent the config orientation from undefined, which may cause config.diff method
+        // neglecting the orientation update.
+        if (mResources.getConfiguration().orientation == ORIENTATION_UNDEFINED) {
+            mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT;
+        }
+
+        // Using the animation duration in WindowMagnificationAnimationController for testing.
+        mWaitAnimationDuration = mResources.getInteger(
+                com.android.internal.R.integer.config_longAnimTime);
+        // Using the bounce effect duration in WindowMagnificationController for testing.
+        mWaitBounceEffectDuration = mResources.getInteger(
+                com.android.internal.R.integer.config_shortAnimTime);
+
+        mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
+                mContext, mValueAnimator);
+        Supplier<SurfaceControlViewHost> scvhSupplier = () -> {
+            mSurfaceControlViewHost = spy(new SurfaceControlViewHost(
+                    mContext, mContext.getDisplay(), new InputTransferToken(),
+                    "WindowMagnification"));
+            ViewRootImpl viewRoot = mock(ViewRootImpl.class);
+            when(mSurfaceControlViewHost.getRootSurfaceControl()).thenReturn(viewRoot);
+            mSurfaceControlViewHosts.add(mSurfaceControlViewHost);
+            return mSurfaceControlViewHost;
+        };
+        mTransaction = spy(new SurfaceControl.Transaction());
+        mSharedPreferences = new FakeSharedPreferences();
+        when(mContext.getSharedPreferences(
+                eq("window_magnification_preferences"), anyInt()))
+                .thenReturn(mSharedPreferences);
+        mWindowMagnificationController =
+                new WindowMagnificationController(
+                        mContext,
+                        mHandler,
+                        mWindowMagnificationAnimationController,
+                        mMirrorWindowControl,
+                        mTransaction,
+                        mWindowMagnifierCallback,
+                        mSysUiState,
+                        mSecureSettings,
+                        scvhSupplier);
+
+        verify(mMirrorWindowControl).setWindowDelegate(
+                any(MirrorWindowControl.MirrorWindowDelegate.class));
+        mSpyView = Mockito.spy(new View(mContext));
+        doAnswer((invocation) -> {
+            mTouchListener = invocation.getArgument(0);
+            return null;
+        }).when(mSpyView).setOnTouchListener(
+                any(View.OnTouchListener.class));
+
+        // skip test if window magnification is not supported to prevent fail results. (b/279820875)
+        Assume.assumeTrue(isWindowModeSupported());
+    }
+
+    @After
+    public void tearDown() {
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.deleteWindowMagnification();
+                });
+        mValueAnimator.cancel();
+    }
+
+    @Test
+    public void initWindowMagnificationController_checkAllowDiagonalScrollingWithSecureSettings() {
+        verify(mSecureSettings).getIntForUser(
+                eq(Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING),
+                /* def */ eq(1), /* userHandle= */ anyInt());
+        assertThat(mWindowMagnificationController.isDiagonalScrollingEnabled()).isTrue();
+    }
+
+    @Test
+    public void enableWindowMagnification_showControlAndNotifyBoundsChanged() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+
+        verify(mMirrorWindowControl).showControl();
+        verify(mWindowMagnifierCallback,
+                timeout(LAYOUT_CHANGE_TIMEOUT_MS).atLeastOnce()).onWindowMagnifierBoundsChanged(
+                eq(mContext.getDisplayId()), any(Rect.class));
+    }
+
+    @Test
+    public void enableWindowMagnification_notifySourceBoundsChanged() {
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+                        Float.NaN, /* magnificationFrameOffsetRatioX= */ 0,
+                        /* magnificationFrameOffsetRatioY= */ 0, null));
+
+        // Waits for the surface created
+        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)).onSourceBoundsChanged(
+                (eq(mContext.getDisplayId())), any());
+    }
+
+    @Test
+    public void enableWindowMagnification_disabled_notifySourceBoundsChanged() {
+        enableWindowMagnification_notifySourceBoundsChanged();
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.deleteWindowMagnification(null));
+        Mockito.reset(mWindowMagnifierCallback);
+
+        enableWindowMagnification_notifySourceBoundsChanged();
+    }
+
+    @Test
+    public void enableWindowMagnification_withAnimation_schedulesFrame() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnification(2.0f, 10,
+                    10, /* magnificationFrameOffsetRatioX= */ 0,
+                    /* magnificationFrameOffsetRatioY= */ 0,
+                    Mockito.mock(IRemoteMagnificationAnimationCallback.class));
+        });
+        advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS);
+
+        verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(),
+                eq(Surface.ROTATION_0));
+    }
+
+    @Test
+    public void moveWindowMagnifier_enabled_notifySourceBoundsChanged() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+                    Float.NaN, 0, 0, null);
+        });
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.moveWindowMagnifier(10, 10);
+        });
+
+        final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
+        verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged(
+                (eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+        assertThat(mWindowMagnificationController.getCenterX())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+        assertThat(mWindowMagnificationController.getCenterY())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
+    }
+
+    @Test
+    public void enableWindowMagnification_systemGestureExclusionRectsIsSet() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+        // Wait for Rects updated.
+        waitForIdleSync();
+
+        List<Rect> rects = mSurfaceControlViewHost.getView().getSystemGestureExclusionRects();
+        assertThat(rects).isNotEmpty();
+    }
+
+    @Ignore("The default window size should be constrained after fixing b/288056772")
+    @Test
+    public void enableWindowMagnification_LargeScreen_windowSizeIsConstrained() {
+        final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
+        mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+
+        final int halfScreenSize = screenSize / 2;
+        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
+        // The frame size should be the half of smaller value of window height/width unless it
+        //exceed the max frame size.
+        assertThat(params.width).isLessThan(halfScreenSize);
+        assertThat(params.height).isLessThan(halfScreenSize);
+    }
+
+    @Test
+    public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() {
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
+                        Float.NaN,
+                        Float.NaN));
+
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.deleteWindowMagnification());
+
+        verify(mMirrorWindowControl).destroyControl();
+        verify(mContext).unregisterComponentCallbacks(mWindowMagnificationController);
+    }
+
+    @Test
+    public void deleteWindowMagnification_enableAtTheBottom_overlapFlagIsFalse() {
+        final WindowManager wm = mContext.getSystemService(WindowManager.class);
+        final Rect bounds = wm.getCurrentWindowMetrics().getBounds();
+        setSystemGestureInsets();
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    bounds.bottom);
+        });
+        ReferenceTestUtils.waitForCondition(this::hasMagnificationOverlapFlag);
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.deleteWindowMagnification();
+        });
+
+        verify(mMirrorWindowControl).destroyControl();
+        assertThat(hasMagnificationOverlapFlag()).isFalse();
+    }
+
+    @Test
+    public void deleteWindowMagnification_notifySourceBoundsChanged() {
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
+                        Float.NaN,
+                        Float.NaN));
+
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.deleteWindowMagnification());
+
+        // The first time is for notifying magnification enabled and the second time is for
+        // notifying magnification disabled.
+        verify(mWindowMagnifierCallback, times(2)).onSourceBoundsChanged(
+                (eq(mContext.getDisplayId())), any());
+    }
+
+    @Test
+    public void moveMagnifier_schedulesFrame() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+
+        waitForIdleSync();
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.moveWindowMagnifier(100f, 100f));
+
+        verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(),
+                eq(Surface.ROTATION_0));
+    }
+
+    @Test
+    public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback()
+            throws RemoteException {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+                    Float.NaN, 0, 0, null);
+        });
+
+        final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
+        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+        final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
+        final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
+
+        reset(mWindowMagnifierCallback);
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.moveWindowMagnifierToPosition(
+                    targetCenterX, targetCenterY, mAnimationCallback);
+        });
+        advanceTimeBy(mWaitAnimationDuration);
+
+        verify(mAnimationCallback, times(1)).onResult(eq(true));
+        verify(mAnimationCallback, never()).onResult(eq(false));
+        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+        assertThat(mWindowMagnificationController.getCenterX())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+        assertThat(mWindowMagnificationController.getCenterY())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
+        assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(targetCenterX);
+        assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(targetCenterY);
+    }
+
+    @Test
+    public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback()
+            throws RemoteException {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+                    Float.NaN, 0, 0, null);
+        });
+
+        final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
+        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+        final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
+        final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
+
+        reset(mWindowMagnifierCallback);
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.moveWindowMagnifierToPosition(
+                    centerX + 10, centerY + 10, mAnimationCallback);
+            mWindowMagnificationController.moveWindowMagnifierToPosition(
+                    centerX + 20, centerY + 20, mAnimationCallback);
+            mWindowMagnificationController.moveWindowMagnifierToPosition(
+                    centerX + 30, centerY + 30, mAnimationCallback);
+            mWindowMagnificationController.moveWindowMagnifierToPosition(
+                    centerX + 40, centerY + 40, mAnimationCallback2);
+        });
+        advanceTimeBy(mWaitAnimationDuration);
+
+        // only the last one callback will return true
+        verify(mAnimationCallback2).onResult(eq(true));
+        // the others will return false
+        verify(mAnimationCallback, times(3)).onResult(eq(false));
+        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
+                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
+        assertThat(mWindowMagnificationController.getCenterX())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+        assertThat(mWindowMagnificationController.getCenterY())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
+        assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(centerX + 40);
+        assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(centerY + 40);
+    }
+
+    @Test
+    public void setScale_enabled_expectedValueAndUpdateStateDescription() {
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(2.0f,
+                        Float.NaN, Float.NaN));
+
+        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
+
+        assertThat(mWindowMagnificationController.getScale()).isEqualTo(3.0f);
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        assertThat(mirrorView).isNotNull();
+        assertThat(mirrorView.getStateDescription().toString()).contains("300");
+    }
+
+    @Test
+    public void onConfigurationChanged_disabled_withoutException() {
+        Display display = Mockito.spy(mContext.getDisplay());
+        when(display.getRotation()).thenReturn(Surface.ROTATION_90);
+        when(mContext.getDisplay()).thenReturn(display);
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+        });
+    }
+
+    @Test
+    public void onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition() {
+        final int newRotation = simulateRotateTheDevice();
+        final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
+        final float center = Math.min(windowBounds.exactCenterX(), windowBounds.exactCenterY());
+        final float displayWidth = windowBounds.width();
+        final PointF magnifiedCenter = new PointF(center, center + 5f);
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
+                    magnifiedCenter.x, magnifiedCenter.y);
+            // Get the center again in case the center we set is out of screen.
+            magnifiedCenter.set(mWindowMagnificationController.getCenterX(),
+                    mWindowMagnificationController.getCenterY());
+        });
+        // Rotate the window clockwise 90 degree.
+        windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
+                windowBounds.right);
+        mWindowManager.setWindowBounds(windowBounds);
+
+        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
+                ActivityInfo.CONFIG_ORIENTATION));
+
+        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
+        final PointF expectedCenter = new PointF(magnifiedCenter.y,
+                displayWidth - magnifiedCenter.x);
+        final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(),
+                mWindowMagnificationController.getCenterY());
+        assertThat(actualCenter).isEqualTo(expectedCenter);
+    }
+
+    @Test
+    public void onOrientationChanged_disabled_updateDisplayRotation() {
+        final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
+        // Rotate the window clockwise 90 degree.
+        windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
+                windowBounds.right);
+        mWindowManager.setWindowBounds(windowBounds);
+        final int newRotation = simulateRotateTheDevice();
+
+        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
+                ActivityInfo.CONFIG_ORIENTATION));
+
+        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
+    }
+
+    @Test
+    public void onScreenSizeAndDensityChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() {
+        // The default position is at the center of the screen.
+        final float expectedRatio = 0.5f;
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+
+        // Screen size and density change
+        mContext.getResources().getConfiguration().smallestScreenWidthDp =
+                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+        final Rect testWindowBounds = new Rect(
+                mWindowManager.getCurrentWindowMetrics().getBounds());
+        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+        mWindowManager.setWindowBounds(testWindowBounds);
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+        });
+
+        // The ratio of center to window size should be the same.
+        assertThat(mWindowMagnificationController.getCenterX() / testWindowBounds.width())
+                .isEqualTo(expectedRatio);
+        assertThat(mWindowMagnificationController.getCenterY() / testWindowBounds.height())
+                .isEqualTo(expectedRatio);
+    }
+
+    @Test
+    public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() {
+        int newSmallestScreenWidthDp =
+                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+        int windowFrameSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize);
+        mSharedPreferences
+                .edit()
+                .putString(String.valueOf(newSmallestScreenWidthDp),
+                        WindowMagnificationFrameSpec.serialize(
+                                WindowMagnificationSettings.MagnificationSize.CUSTOM,
+                                preferredWindowSize))
+                .commit();
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+
+        // Screen density and size change
+        mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
+        final Rect testWindowBounds = new Rect(
+                mWindowManager.getCurrentWindowMetrics().getBounds());
+        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+        mWindowManager.setWindowBounds(testWindowBounds);
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+        });
+
+        // wait for rect update
+        waitForIdleSync();
+        verify(mWindowMagnifierCallback).onWindowMagnifierBoundsRestored(
+                eq(mContext.getDisplayId()),
+                eq(WindowMagnificationSettings.MagnificationSize.CUSTOM));
+        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
+        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+        // The width and height of the view include the magnification frame and the margins.
+        assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
+        assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
+    }
+
+    @Test
+    public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+        final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
+        // Screen size and density change
+        mContext.getResources().getConfiguration().smallestScreenWidthDp =
+                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+        mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+        });
+
+        final int defaultWindowSize =
+                mWindowMagnificationController.getMagnificationWindowSizeFromIndex(
+                        WindowMagnificationSettings.MagnificationSize.MEDIUM);
+        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
+
+        assertThat(params.width).isEqualTo(defaultWindowSize);
+        assertThat(params.height).isEqualTo(defaultWindowSize);
+    }
+
+    @Test
+    public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            Mockito.reset(mWindowManager);
+            Mockito.reset(mMirrorWindowControl);
+        });
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
+        });
+
+        verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
+        verify(mSurfaceControlViewHosts.get(0)).release();
+        verify(mMirrorWindowControl).destroyControl();
+        verify(mSurfaceControlViewHosts.get(1)).setView(any(), any());
+        verify(mMirrorWindowControl).showControl();
+    }
+
+    @Test
+    public void onDensityChanged_disabled_updateDimensions() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
+        });
+
+        verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
+    }
+
+    @Test
+    public void initializeA11yNode_enabled_expectedValues() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
+                    Float.NaN);
+        });
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        assertThat(mirrorView).isNotNull();
+        final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
+
+        mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo);
+
+        assertThat(nodeInfo.getContentDescription()).isNotNull();
+        assertThat(nodeInfo.getStateDescription().toString()).contains("250");
+        assertThat(nodeInfo.getActionList()).containsExactlyElementsIn(asList(
+                new AccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
+                        mContext.getResources().getString(
+                                R.string.magnification_open_settings_click_label)),
+                new AccessibilityAction(R.id.accessibility_action_zoom_in,
+                        mContext.getString(R.string.accessibility_control_zoom_in)),
+                new AccessibilityAction(R.id.accessibility_action_zoom_out,
+                        mContext.getString(R.string.accessibility_control_zoom_out)),
+                new AccessibilityAction(R.id.accessibility_action_move_right,
+                        mContext.getString(R.string.accessibility_control_move_right)),
+                new AccessibilityAction(R.id.accessibility_action_move_left,
+                        mContext.getString(R.string.accessibility_control_move_left)),
+                new AccessibilityAction(R.id.accessibility_action_move_down,
+                        mContext.getString(R.string.accessibility_control_move_down)),
+                new AccessibilityAction(R.id.accessibility_action_move_up,
+                        mContext.getString(R.string.accessibility_control_move_up))));
+    }
+
+    @Test
+    public void performA11yActions_visible_expectedResults() {
+        final int displayId = mContext.getDisplayId();
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(1.5f, Float.NaN,
+                    Float.NaN);
+        });
+
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null))
+                .isTrue();
+        // Minimum scale is 1.0.
+        verify(mWindowMagnifierCallback).onPerformScaleAction(
+                eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true));
+
+        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null))
+                .isTrue();
+        verify(mWindowMagnifierCallback).onPerformScaleAction(
+                eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true));
+
+        // TODO: Verify the final state when the mirror surface is visible.
+        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null))
+                .isTrue();
+        assertThat(
+                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null))
+                .isTrue();
+        assertThat(
+                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null))
+                .isTrue();
+        assertThat(
+                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null))
+                .isTrue();
+        verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId));
+
+        assertThat(mirrorView.performAccessibilityAction(
+                AccessibilityAction.ACTION_CLICK.getId(), null)).isTrue();
+        verify(mWindowMagnifierCallback).onClickSettingsButton(eq(displayId));
+    }
+
+    @Test
+    public void performA11yActions_visible_notifyAccessibilityActionPerformed() {
+        final int displayId = mContext.getDisplayId();
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
+                    Float.NaN);
+        });
+
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null);
+
+        verify(mWindowMagnifierCallback).onAccessibilityActionPerformed(eq(displayId));
+    }
+
+    @Test
+    public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        View closeButton = getInternalView(R.id.close_button);
+        View bottomRightCorner = getInternalView(R.id.bottom_right_corner);
+        View bottomLeftCorner = getInternalView(R.id.bottom_left_corner);
+        View topRightCorner = getInternalView(R.id.top_right_corner);
+        View topLeftCorner = getInternalView(R.id.top_left_corner);
+
+        assertThat(closeButton.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(topRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(topLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
+
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        mInstrumentation.runOnMainSync(() ->
+                mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
+                        null));
+
+        assertThat(closeButton.getVisibility()).isEqualTo(View.GONE);
+        assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.GONE);
+        assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.GONE);
+        assertThat(topRightCorner.getVisibility()).isEqualTo(View.GONE);
+        assertThat(topLeftCorner.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void windowWidthIsNotMax_performA11yActionIncreaseWidth_windowWidthIncreased() {
+        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        final int startingWidth = (int) (windowBounds.width() * 0.8);
+        final int startingHeight = (int) (windowBounds.height() * 0.8);
+        final float changeWindowSizeAmount = mContext.getResources().getFraction(
+                R.fraction.magnification_resize_window_size_amount,
+                /* base= */ 1,
+                /* pbase= */ 1);
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mirrorView.performAccessibilityAction(
+                            R.id.accessibility_action_increase_window_width, null);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
+                });
+
+        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+        // Window width includes the magnifier frame and the margin. Increasing the window size
+        // will be increasing the amount of the frame size only.
+        int newWindowWidth =
+                (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
+                        + 2 * mirrorSurfaceMargin;
+        assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
+        assertThat(actualWindowHeight.get()).isEqualTo(startingHeight);
+    }
+
+    @Test
+    public void windowHeightIsNotMax_performA11yActionIncreaseHeight_windowHeightIncreased() {
+        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        final int startingWidth = (int) (windowBounds.width() * 0.8);
+        final int startingHeight = (int) (windowBounds.height() * 0.8);
+        final float changeWindowSizeAmount = mContext.getResources().getFraction(
+                R.fraction.magnification_resize_window_size_amount,
+                /* base= */ 1,
+                /* pbase= */ 1);
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mirrorView.performAccessibilityAction(
+                            R.id.accessibility_action_increase_window_height, null);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
+                });
+
+        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+        // Window height includes the magnifier frame and the margin. Increasing the window size
+        // will be increasing the amount of the frame size only.
+        int newWindowHeight =
+                (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
+                        + 2 * mirrorSurfaceMargin;
+        assertThat(actualWindowWidth.get()).isEqualTo(startingWidth);
+        assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
+    }
+
+    @Test
+    public void windowWidthIsMax_noIncreaseWindowWidthA11yAction() {
+        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        final int startingWidth = windowBounds.width();
+        final int startingHeight = windowBounds.height();
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        final AccessibilityNodeInfo accessibilityNodeInfo =
+                mirrorView.createAccessibilityNodeInfo();
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_increase_window_width,
+                        mContext.getString(
+                                R.string.accessibility_control_increase_window_width)));
+    }
+
+    @Test
+    public void windowHeightIsMax_noIncreaseWindowHeightA11yAction() {
+        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        final int startingWidth = windowBounds.width();
+        final int startingHeight = windowBounds.height();
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        final AccessibilityNodeInfo accessibilityNodeInfo =
+                mirrorView.createAccessibilityNodeInfo();
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_increase_window_height, null));
+    }
+
+    @Test
+    public void windowWidthIsNotMin_performA11yActionDecreaseWidth_windowWidthDecreased() {
+        int mMinWindowSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        final int startingSize = (int) (mMinWindowSize * 1.1);
+        final float changeWindowSizeAmount = mContext.getResources().getFraction(
+                R.fraction.magnification_resize_window_size_amount,
+                /* base= */ 1,
+                /* pbase= */ 1);
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mirrorView.performAccessibilityAction(
+                            R.id.accessibility_action_decrease_window_width, null);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
+                });
+
+        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+        // Window width includes the magnifier frame and the margin. Decreasing the window size
+        // will be decreasing the amount of the frame size only.
+        int newWindowWidth =
+                (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
+                        + 2 * mirrorSurfaceMargin;
+        assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
+        assertThat(actualWindowHeight.get()).isEqualTo(startingSize);
+    }
+
+    @Test
+    public void windowHeightIsNotMin_performA11yActionDecreaseHeight_windowHeightDecreased() {
+        int mMinWindowSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        final int startingSize = (int) (mMinWindowSize * 1.1);
+        final float changeWindowSizeAmount = mContext.getResources().getFraction(
+                R.fraction.magnification_resize_window_size_amount,
+                /* base= */ 1,
+                /* pbase= */ 1);
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mirrorView.performAccessibilityAction(
+                            R.id.accessibility_action_decrease_window_height, null);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
+                });
+
+        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+        // Window height includes the magnifier frame and the margin. Decreasing the window size
+        // will be decreasing the amount of the frame size only.
+        int newWindowHeight =
+                (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
+                        + 2 * mirrorSurfaceMargin;
+        assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
+        assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
+    }
+
+    @Test
+    public void windowWidthIsMin_noDecreaseWindowWidthA11yAction() {
+        int mMinWindowSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        final int startingSize = mMinWindowSize;
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        final AccessibilityNodeInfo accessibilityNodeInfo =
+                mirrorView.createAccessibilityNodeInfo();
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_decrease_window_width, null));
+    }
+
+    @Test
+    public void windowHeightIsMin_noDecreaseWindowHeightA11yAction() {
+        int mMinWindowSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        final int startingSize = mMinWindowSize;
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        final AccessibilityNodeInfo accessibilityNodeInfo =
+                mirrorView.createAccessibilityNodeInfo();
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_decrease_window_height, null));
+    }
+
+    @Test
+    public void enableWindowMagnification_hasA11yWindowTitle() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+
+        assertThat(getAccessibilityWindowTitle()).isEqualTo(getContext().getResources().getString(
+                com.android.internal.R.string.android_system_label));
+    }
+
+    @Test
+    public void enableWindowMagnificationWithScaleLessThanOne_enabled_disabled() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(0.9f, Float.NaN,
+                    Float.NaN);
+        });
+
+        assertThat(mWindowMagnificationController.getScale()).isEqualTo(Float.NaN);
+    }
+
+    @Test
+    public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
+        // the config orientation should not be undefined, since it would cause config.diff
+        // returning 0 and thus the orientation changed would not be detected
+        assertThat(mResources.getConfiguration().orientation).isNotEqualTo(ORIENTATION_UNDEFINED);
+
+        final Configuration config = mResources.getConfiguration();
+        config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
+                : ORIENTATION_LANDSCAPE;
+        final int newRotation = simulateRotateTheDevice();
+
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
+                        Float.NaN, Float.NaN));
+
+        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
+    }
+
+    @Test
+    public void enableWindowMagnification_registerComponentCallback() {
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
+                        Float.NaN,
+                        Float.NaN));
+
+        verify(mContext).registerComponentCallbacks(mWindowMagnificationController);
+    }
+
+    @Test
+    public void onLocaleChanged_enabled_updateA11yWindowTitle() {
+        final String newA11yWindowTitle = "new a11y window title";
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+        final TestableResources testableResources = getContext().getOrCreateTestableResources();
+        testableResources.addOverride(com.android.internal.R.string.android_system_label,
+                newA11yWindowTitle);
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
+        });
+
+        assertThat(getAccessibilityWindowTitle()).isEqualTo(newA11yWindowTitle);
+    }
+
+    @Ignore("it's flaky in presubmit but works in abtd, filter for now. b/305654925")
+    @Test
+    public void onSingleTap_enabled_scaleAnimates() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onSingleTap(mSpyView);
+        });
+
+        final View mirrorView = mSurfaceControlViewHost.getView();
+
+        final AtomicDouble maxScaleX = new AtomicDouble();
+        advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> {
+            // For some reason the fancy way doesn't compile...
+            // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
+            final double oldMax = maxScaleX.get();
+            final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
+            assertThat(maxScaleX.compareAndSet(oldMax, newMax)).isTrue();
+        });
+
+        assertThat(maxScaleX.get()).isGreaterThan(1.0);
+    }
+
+    @Test
+    public void moveWindowMagnificationToTheBottom_enabledWithGestureInset_overlapFlagIsTrue() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        setSystemGestureInsets();
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.moveWindowMagnifier(0, bounds.height());
+        });
+
+        ReferenceTestUtils.waitForCondition(() -> hasMagnificationOverlapFlag());
+    }
+
+    @Test
+    public void moveWindowMagnificationToRightEdge_dragHandleMovesToLeftAndUpdatesTapExcludeRegion()
+            throws RemoteException {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        setSystemGestureInsets();
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.updateWindowMagnificationInternal(
+                            Float.NaN, Float.NaN, Float.NaN);
+                });
+        // Wait for Region updated.
+        waitForIdleSync();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.moveWindowMagnifier(bounds.width(), 0);
+                });
+        // Wait for Region updated.
+        waitForIdleSync();
+
+        AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl();
+        // Verifying two times in: (1) enable window magnification (2) reposition drag handle
+        verify(viewRoot, times(2)).setTouchableRegion(any());
+
+        View dragButton = getInternalView(R.id.drag_handle);
+        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
+        assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.LEFT);
+    }
+
+    @Test
+    public void moveWindowMagnificationToLeftEdge_dragHandleMovesToRightAndUpdatesTapExcludeRegion()
+            throws RemoteException {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        setSystemGestureInsets();
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.updateWindowMagnificationInternal(
+                            Float.NaN, Float.NaN, Float.NaN);
+                });
+        // Wait for Region updated.
+        waitForIdleSync();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.moveWindowMagnifier(-bounds.width(), 0);
+                });
+        // Wait for Region updated.
+        waitForIdleSync();
+
+        AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl();
+        // Verifying one times in: (1) enable window magnification
+        verify(viewRoot).setTouchableRegion(any());
+
+        View dragButton = getInternalView(R.id.drag_handle);
+        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
+        assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.RIGHT);
+    }
+
+    @Test
+    public void setMinimumWindowSize_enabled_expectedWindowSize() {
+        final int minimumWindowSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        final int  expectedWindowHeight = minimumWindowSize;
+        final int  expectedWindowWidth = minimumWindowSize;
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
+                        Float.NaN, Float.NaN));
+
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
+            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
+
+        });
+
+        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
+    }
+
+    @Test
+    public void setMinimumWindowSizeThenEnable_expectedWindowSize() {
+        final int minimumWindowSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        final int  expectedWindowHeight = minimumWindowSize;
+        final int  expectedWindowWidth = minimumWindowSize;
+
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
+                    Float.NaN, Float.NaN);
+            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
+        });
+
+        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
+    }
+
+    @Test
+    public void setWindowSizeLessThanMin_enabled_minimumWindowSize() {
+        final int minimumWindowSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
+                        Float.NaN, Float.NaN));
+
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.setWindowSize(minimumWindowSize - 10,
+                    minimumWindowSize - 10);
+            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
+        });
+
+        assertThat(actualWindowHeight.get()).isEqualTo(minimumWindowSize);
+        assertThat(actualWindowWidth.get()).isEqualTo(minimumWindowSize);
+    }
+
+    @Test
+    public void setWindowSizeLargerThanScreenSize_enabled_windowSizeIsScreenSize() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
+                        Float.NaN, Float.NaN));
+
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.setWindowSize(bounds.width() + 10, bounds.height() + 10);
+            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
+        });
+
+        assertThat(actualWindowHeight.get()).isEqualTo(bounds.height());
+        assertThat(actualWindowWidth.get()).isEqualTo(bounds.width());
+    }
+
+    @Test
+    public void changeMagnificationSize_expectedWindowSize() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+        final float magnificationScaleLarge = 2.5f;
+        final int initSize = Math.min(bounds.width(), bounds.height()) / 3;
+        final int magnificationSize = (int) (initSize * magnificationScaleLarge)
+                - (int) (initSize * magnificationScaleLarge) % 2;
+
+        final int expectedWindowHeight = magnificationSize;
+        final int expectedWindowWidth = magnificationSize;
+
+        mInstrumentation.runOnMainSync(
+                () ->
+                        mWindowMagnificationController.updateWindowMagnificationInternal(
+                                Float.NaN, Float.NaN, Float.NaN));
+
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.changeMagnificationSize(
+                            WindowMagnificationSettings.MagnificationSize.LARGE);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
+                });
+
+        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
+    }
+
+    @Test
+    public void editModeOnDragCorner_resizesWindow() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+        final int startingSize = (int) (bounds.width() / 2);
+
+        mInstrumentation.runOnMainSync(
+                () ->
+                        mWindowMagnificationController.updateWindowMagnificationInternal(
+                                Float.NaN, Float.NaN, Float.NaN));
+
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+                    mWindowMagnificationController.setEditMagnifierSizeMode(true);
+                });
+
+        waitForIdleSync();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController
+                            .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
+                });
+
+        assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
+        assertThat(actualWindowWidth.get()).isEqualTo(startingSize + 2);
+    }
+
+    @Test
+    public void editModeOnDragEdge_resizesWindowInOnlyOneDirection() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+
+        final int startingSize = (int) (bounds.width() / 2f);
+
+        mInstrumentation.runOnMainSync(
+                () ->
+                        mWindowMagnificationController.updateWindowMagnificationInternal(
+                                Float.NaN, Float.NaN, Float.NaN));
+
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+                    mWindowMagnificationController.setEditMagnifierSizeMode(true);
+                    mWindowMagnificationController
+                            .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
+                });
+        assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
+        assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
+    }
+
+    @Test
+    public void setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen() {
+
+        final int minimumWindowSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
+                        Float.NaN, Float.NaN));
+
+        final AtomicInteger magnificationCenterX = new AtomicInteger();
+        final AtomicInteger magnificationCenterY = new AtomicInteger();
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.setWindowSizeAndCenter(minimumWindowSize,
+                    minimumWindowSize, bounds.right, bounds.bottom);
+            magnificationCenterX.set((int) mWindowMagnificationController.getCenterX());
+            magnificationCenterY.set((int) mWindowMagnificationController.getCenterY());
+        });
+
+        assertThat(magnificationCenterX.get()).isLessThan(bounds.right);
+        assertThat(magnificationCenterY.get()).isLessThan(bounds.bottom);
+    }
+
+    @Test
+    public void performSingleTap_DragHandle() {
+        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mWindowMagnificationController.updateWindowMagnificationInternal(
+                            1.5f, bounds.centerX(), bounds.centerY());
+                });
+        View dragButton = getInternalView(R.id.drag_handle);
+
+        // Perform a single-tap
+        final long downTime = SystemClock.uptimeMillis();
+        dragButton.dispatchTouchEvent(
+                obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100));
+        dragButton.dispatchTouchEvent(
+                obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100));
+
+        verify(mSurfaceControlViewHost).setView(any(View.class), any());
+    }
+
+    private <T extends View> T getInternalView(@IdRes int idRes) {
+        View mirrorView = mSurfaceControlViewHost.getView();
+        T view = mirrorView.findViewById(idRes);
+        assertThat(view).isNotNull();
+        return view;
+    }
+
+    private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
+            float y) {
+        return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
+    }
+
+    private String getAccessibilityWindowTitle() {
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        if (mirrorView == null) {
+            return null;
+        }
+        WindowManager.LayoutParams layoutParams =
+                (WindowManager.LayoutParams) mirrorView.getLayoutParams();
+        return layoutParams.accessibilityTitle.toString();
+    }
+
+    private boolean hasMagnificationOverlapFlag() {
+        return (mSysUiState.getFlags() & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0;
+    }
+
+    private void setSystemGestureInsets() {
+        final WindowInsets testInsets = new WindowInsets.Builder()
+                .setInsets(systemGestures(), Insets.of(0, 0, 0, 10))
+                .build();
+        mWindowManager.setWindowInsets(testInsets);
+    }
+
+    private int updateMirrorSurfaceMarginDimension() {
+        return mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+    }
+
+    @Surface.Rotation
+    private int simulateRotateTheDevice() {
+        final Display display = Mockito.spy(mContext.getDisplay());
+        final int currentRotation = display.getRotation();
+        final int newRotation = (currentRotation + 1) % 4;
+        when(display.getRotation()).thenReturn(newRotation);
+        when(mContext.getDisplay()).thenReturn(display);
+        return newRotation;
+    }
+
+    // advance time based on the device frame refresh rate
+    private void advanceTimeBy(long timeDelta) {
+        advanceTimeBy(timeDelta, /* runnableOnEachRefresh= */ null);
+    }
+
+    // advance time based on the device frame refresh rate, and trigger runnable on each refresh
+    private void advanceTimeBy(long timeDelta, @Nullable Runnable runnableOnEachRefresh) {
+        final float frameRate = mContext.getDisplay().getRefreshRate();
+        final int timeSlot = (int) (1000 / frameRate);
+        int round = (int) Math.ceil((double) timeDelta / timeSlot);
+        for (; round >= 0; round--) {
+            mInstrumentation.runOnMainSync(() -> {
+                mAnimatorTestRule.advanceTimeBy(timeSlot);
+                if (runnableOnEachRefresh != null) {
+                    runnableOnEachRefresh.run();
+                }
+            });
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index 7c0c5c2..4553f98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -59,6 +59,7 @@
 import android.widget.Button;
 import android.widget.CompoundButton;
 import android.widget.LinearLayout;
+import android.widget.SeekBar;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -81,6 +82,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
@@ -544,9 +546,10 @@
         OnSeekBarWithIconButtonsChangeListener onChangeListener =
                 mZoomSeekbar.getOnSeekBarWithIconButtonsChangeListener();
 
-        mZoomSeekbar.setProgress(30);
+        SeekBar mockSeekBar = Mockito.mock(SeekBar.class);
+        when(mockSeekBar.getProgress()).thenReturn(30);
         onChangeListener.onUserInteractionFinalized(
-                mZoomSeekbar.getSeekbar(),
+                mockSeekBar,
                 OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER);
 
         // should trigger callback to update magnifier scale and persist the scale
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 dddaabb..50d0049 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,12 +26,10 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import android.graphics.PointF;
-
 import android.testing.TestableLooper;
 import android.view.View;
 import android.view.ViewPropertyAnimator;
 import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.FlingAnimation;
@@ -51,7 +49,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -72,17 +69,13 @@
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
-    @Mock
-    private AccessibilityManager mAccessibilityManager;
-
     @Before
     public void setUp() throws Exception {
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                 stubWindowManager);
         final SecureSettings secureSettings = TestUtils.mockSecureSettings();
-        final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
-                secureSettings);
+        final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, secureSettings);
 
         mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
                 secureSettings));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 400b3b3..f4580c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -159,8 +159,7 @@
                 new WindowMetrics(mDisplayBounds, fakeDisplayInsets(), /* density = */ 0.0f));
         doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics();
 
-        mMenuViewModel = new MenuViewModel(
-                mSpyContext, mStubAccessibilityManager, mSecureSettings);
+        mMenuViewModel = new MenuViewModel(mSpyContext, mSecureSettings);
         MenuViewAppearance menuViewAppearance = new MenuViewAppearance(
                 mSpyContext, mStubWindowManager);
         mMenuView = spy(
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 425aad2..4aaa82e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -404,7 +404,7 @@
     @Test
     fun creatingRunnerWithLazyInitializationThrows_whenTheFlagsAreDisabled() {
         assertThrows(IllegalStateException::class.java) {
-            activityTransitionAnimator.createRunner(controller, initializeLazily = true)
+            activityTransitionAnimator.createRunner(controller, longLived = true)
         }
     }
 
@@ -414,7 +414,7 @@
     )
     @Test
     fun runnerCreatesDelegateLazily_whenPostingTimeouts() {
-        val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = true)
+        val runner = activityTransitionAnimator.createRunner(controller, longLived = true)
         assertNull(runner.delegate)
         runner.postTimeouts()
         assertNotNull(runner.delegate)
@@ -426,7 +426,7 @@
     )
     @Test
     fun runnerCreatesDelegateLazily_onAnimationStart() {
-        val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = true)
+        val runner = activityTransitionAnimator.createRunner(controller, longLived = true)
         assertNull(runner.delegate)
 
         // The delegate is cleaned up after execution (which happens in another thread), so what we
@@ -458,7 +458,7 @@
     )
     @Test
     fun runnerCreatesDelegateLazily_onAnimationTakeover() {
-        val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = true)
+        val runner = activityTransitionAnimator.createRunner(controller, longLived = true)
         assertNull(runner.delegate)
 
         // The delegate is cleaned up after execution (which happens in another thread), so what we
@@ -489,7 +489,7 @@
     )
     @Test
     fun animationTakeoverThrows_whenTheFlagsAreDisabled() {
-        val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = false)
+        val runner = activityTransitionAnimator.createRunner(controller, longLived = false)
         assertThrows(IllegalStateException::class.java) {
             runner.takeOverAnimation(
                 arrayOf(fakeWindow()),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
deleted file mode 100644
index 91f9cce..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ /dev/null
@@ -1,736 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.biometrics
-
-import android.app.ActivityTaskManager
-import android.app.admin.DevicePolicyManager
-import android.content.pm.PackageManager
-import android.content.res.Configuration
-import android.hardware.biometrics.BiometricAuthenticator
-import android.hardware.biometrics.BiometricConstants
-import android.hardware.biometrics.BiometricManager
-import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
-import android.hardware.biometrics.PromptInfo
-import android.hardware.biometrics.PromptVerticalListContentView
-import android.hardware.face.FaceSensorPropertiesInternal
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.os.IBinder
-import android.os.UserManager
-import android.testing.TestableLooper
-import android.testing.TestableLooper.RunWithLooper
-import android.testing.ViewUtils
-import android.view.View
-import android.view.WindowInsets
-import android.view.WindowManager
-import android.widget.ScrollView
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.app.viewcapture.ViewCapture
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.widget.LockPatternUtils
-import com.android.launcher3.icons.IconProvider
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
-import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.data.repository.FakePromptRepository
-import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
-import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
-import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
-import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
-import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
-import com.android.systemui.display.data.repository.FakeDisplayRepository
-import com.android.systemui.haptics.msdl.msdlPlayer
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.testKosmos
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-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
-import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyLong
-import org.mockito.Mockito.eq
-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
-
-private const val OP_PACKAGE_NAME = "biometric.testapp"
-
-@RunWith(AndroidJUnit4::class)
-@RunWithLooper(setAsMainLooper = true)
-@SmallTest
-open class AuthContainerViewTest : SysuiTestCase() {
-
-    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-
-    @Mock lateinit var callback: AuthDialogCallback
-    @Mock lateinit var userManager: UserManager
-    @Mock lateinit var fingerprintManager: FingerprintManager
-    @Mock lateinit var lockPatternUtils: LockPatternUtils
-    @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
-    @Mock lateinit var windowToken: IBinder
-    @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
-    @Mock lateinit var vibrator: VibratorHelper
-    @Mock lateinit var udfpsUtils: UdfpsUtils
-    @Mock lateinit var authController: AuthController
-    @Mock lateinit var selectedUserInteractor: SelectedUserInteractor
-    @Mock private lateinit var packageManager: PackageManager
-    @Mock private lateinit var activityTaskManager: ActivityTaskManager
-    @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture>
-
-    private lateinit var displayRepository: FakeDisplayRepository
-    private lateinit var displayStateInteractor: DisplayStateInteractor
-    private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
-    private lateinit var biometricStatusInteractor: BiometricStatusInteractor
-    private lateinit var iconProvider: IconProvider
-
-    private val testScope = TestScope(StandardTestDispatcher())
-    private val fakeExecutor = FakeExecutor(FakeSystemClock())
-    private val biometricPromptRepository = FakePromptRepository()
-    private val biometricStatusRepository = FakeBiometricStatusRepository()
-    private val fingerprintRepository = FakeFingerprintPropertyRepository()
-    private val displayStateRepository = FakeDisplayStateRepository()
-    private val credentialInteractor = FakeCredentialInteractor()
-    private val bpCredentialInteractor =
-        PromptCredentialInteractor(
-            Dispatchers.Main.immediate,
-            biometricPromptRepository,
-            credentialInteractor,
-        )
-    private val promptSelectorInteractor by lazy {
-        PromptSelectorInteractorImpl(
-            fingerprintRepository,
-            displayStateInteractor,
-            credentialInteractor,
-            biometricPromptRepository,
-            lockPatternUtils,
-        )
-    }
-
-    private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
-    private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
-
-    private val kosmos = testKosmos()
-    private val msdlPlayer = kosmos.msdlPlayer
-
-    private var authContainer: TestAuthContainerView? = null
-
-    @Before
-    fun setup() {
-        displayRepository = FakeDisplayRepository()
-
-        displayStateInteractor =
-            DisplayStateInteractorImpl(
-                testScope.backgroundScope,
-                mContext,
-                fakeExecutor,
-                displayStateRepository,
-                displayRepository,
-            )
-        udfpsOverlayInteractor =
-            UdfpsOverlayInteractor(
-                context,
-                authController,
-                selectedUserInteractor,
-                fingerprintManager,
-                testScope.backgroundScope,
-            )
-        biometricStatusInteractor =
-            BiometricStatusInteractorImpl(
-                activityTaskManager,
-                biometricStatusRepository,
-                fingerprintRepository,
-            )
-        iconProvider = IconProvider(context)
-        // Set up default logo icon
-        whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
-        context.setMockPackageManager(packageManager)
-    }
-
-    @After
-    fun tearDown() {
-        if (authContainer?.isAttachedToWindow == true) {
-            ViewUtils.detachView(authContainer)
-        }
-    }
-
-    @Test
-    fun testNotifiesAnimatedIn() {
-        initializeFingerprintContainer()
-        verify(callback)
-            .onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
-    }
-
-    @Test
-    fun testDismissesOnBack() {
-        val container = initializeFingerprintContainer(addToView = true)
-        assertThat(container.parent).isNotNull()
-        val root = container.rootView
-
-        // Simulate back invocation
-        container.onBackInvoked()
-        waitForIdleSync()
-
-        assertThat(container.parent).isNull()
-        assertThat(root.isAttachedToWindow).isFalse()
-    }
-
-    @Test
-    fun testDimissOnLock() {
-        val container = initializeFingerprintContainer(addToView = true)
-        assertThat(container.parent).isNotNull()
-        val root = container.rootView
-
-        // Simulate sleep/lock invocation
-        container.onStartedGoingToSleep()
-        waitForIdleSync()
-
-        assertThat(container.parent).isNull()
-        assertThat(root.isAttachedToWindow).isFalse()
-    }
-
-    @Test
-    fun testCredentialPasswordDismissesOnBack() {
-        val container = initializeCredentialPasswordContainer(addToView = true)
-        assertThat(container.parent).isNotNull()
-        val root = container.rootView
-
-        // Simulate back invocation
-        container.onBackInvoked()
-        waitForIdleSync()
-
-        assertThat(container.parent).isNull()
-        assertThat(root.isAttachedToWindow).isFalse()
-    }
-
-    @Test
-    fun testIgnoresAnimatedInWhenDismissed() {
-        val container = initializeFingerprintContainer(addToView = false)
-        container.dismissFromSystemServer()
-        waitForIdleSync()
-
-        verify(callback, never()).onDialogAnimatedIn(anyLong(), anyBoolean())
-
-        container.addToView()
-        waitForIdleSync()
-
-        // attaching the view resets the state and allows this to happen again
-        verify(callback)
-            .onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
-    }
-
-    @Test
-    fun testIgnoresAnimatedInWhenDialogAnimatingOut() {
-        val container = initializeFingerprintContainer(addToView = false)
-        container.mContainerState = 4 // STATE_ANIMATING_OUT
-        container.addToView()
-        waitForIdleSync()
-
-        verify(callback, never()).onDialogAnimatedIn(anyLong(), anyBoolean())
-    }
-
-    @Test
-    fun testDismissBeforeIntroEnd() {
-        val container = initializeFingerprintContainer()
-        waitForIdleSync()
-
-        // STATE_ANIMATING_IN = 1
-        container?.mContainerState = 1
-
-        container.dismissWithoutCallback(false)
-
-        // the first time is triggered by initializeFingerprintContainer()
-        // the second time was triggered by dismissWithoutCallback()
-        verify(callback, times(2))
-            .onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
-    }
-
-    @Test
-    fun testActionAuthenticated_sendsDismissedAuthenticated() {
-        val container = initializeFingerprintContainer()
-        container.mBiometricCallback.onAuthenticated()
-        waitForIdleSync()
-
-        verify(callback)
-            .onDismissed(
-                eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED),
-                eq<ByteArray?>(null), /* credentialAttestation */
-                eq(authContainer?.requestId ?: 0L),
-            )
-        assertThat(container.parent).isNull()
-    }
-
-    @Test
-    fun testActionUserCanceled_sendsDismissedUserCanceled() {
-        val container = initializeFingerprintContainer()
-        container.mBiometricCallback.onUserCanceled()
-        waitForIdleSync()
-
-        verify(callback)
-            .onSystemEvent(
-                eq(BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL),
-                eq(authContainer?.requestId ?: 0L),
-            )
-        verify(callback)
-            .onDismissed(
-                eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
-                eq<ByteArray?>(null), /* credentialAttestation */
-                eq(authContainer?.requestId ?: 0L),
-            )
-        assertThat(container.parent).isNull()
-    }
-
-    @Test
-    fun testActionButtonNegative_sendsDismissedButtonNegative() {
-        val container = initializeFingerprintContainer()
-        container.mBiometricCallback.onButtonNegative()
-        waitForIdleSync()
-
-        verify(callback)
-            .onDismissed(
-                eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE),
-                eq<ByteArray?>(null), /* credentialAttestation */
-                eq(authContainer?.requestId ?: 0L),
-            )
-        assertThat(container.parent).isNull()
-    }
-
-    @Test
-    fun testActionTryAgain_sendsTryAgain() {
-        val container =
-            initializeFingerprintContainer(
-                authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
-            )
-        container.mBiometricCallback.onButtonTryAgain()
-        waitForIdleSync()
-
-        verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L)
-    }
-
-    @Test
-    fun testActionError_sendsDismissedError() {
-        val container = initializeFingerprintContainer()
-        container.mBiometricCallback.onError()
-        waitForIdleSync()
-
-        verify(callback)
-            .onDismissed(
-                eq(AuthDialogCallback.DISMISSED_ERROR),
-                eq<ByteArray?>(null), /* credentialAttestation */
-                eq(authContainer?.requestId ?: 0L),
-            )
-        assertThat(authContainer!!.parent).isNull()
-    }
-
-    @Ignore("b/279650412")
-    @Test
-    fun testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
-        val container =
-            initializeFingerprintContainer(
-                authenticators =
-                    BiometricManager.Authenticators.BIOMETRIC_WEAK or
-                        BiometricManager.Authenticators.DEVICE_CREDENTIAL
-            )
-        container.mBiometricCallback.onUseDeviceCredential()
-        waitForIdleSync()
-
-        verify(callback).onDeviceCredentialPressed(authContainer?.requestId ?: 0L)
-        assertThat(container.hasCredentialView()).isTrue()
-    }
-
-    @Test
-    fun testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
-        val container =
-            initializeFingerprintContainer(
-                authenticators =
-                    BiometricManager.Authenticators.BIOMETRIC_WEAK or
-                        BiometricManager.Authenticators.DEVICE_CREDENTIAL
-            )
-        container.animateToCredentialUI(false)
-        waitForIdleSync()
-
-        assertThat(container.hasCredentialView()).isTrue()
-        assertThat(container.hasBiometricPrompt()).isFalse()
-
-        // Check credential view persists after new attachment
-        container.onAttachedToWindow()
-
-        assertThat(container.hasCredentialView()).isTrue()
-        assertThat(container.hasBiometricPrompt()).isFalse()
-    }
-
-    @Test
-    fun testAnimateToCredentialUI_rotateCredentialUI() {
-        val container =
-            initializeFingerprintContainer(
-                authenticators =
-                    BiometricManager.Authenticators.BIOMETRIC_WEAK or
-                        BiometricManager.Authenticators.DEVICE_CREDENTIAL
-            )
-        container.animateToCredentialUI(false)
-        waitForIdleSync()
-
-        assertThat(container.hasCredentialView()).isTrue()
-        assertThat(container.hasBiometricPrompt()).isFalse()
-
-        // Check credential view persists after new attachment
-        container.onAttachedToWindow()
-
-        assertThat(container.hasCredentialView()).isTrue()
-        assertThat(container.hasBiometricPrompt()).isFalse()
-
-        val configuration = Configuration(context.resources.configuration)
-        configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
-        container.dispatchConfigurationChanged(configuration)
-        waitForIdleSync()
-
-        assertThat(container.hasCredentialView()).isTrue()
-        assertThat(container.hasBiometricPrompt()).isFalse()
-    }
-
-    @Test
-    fun testShowBiometricUI_ContentViewWithMoreOptionsButton() {
-        var isButtonClicked = false
-        val contentView =
-            PromptContentViewWithMoreOptionsButton.Builder()
-                .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> isButtonClicked = true }
-                .build()
-
-        val container =
-            initializeFingerprintContainer(contentViewWithMoreOptionsButton = contentView)
-
-        waitForIdleSync()
-
-        assertThat(container.hasCredentialView()).isFalse()
-        assertThat(container.hasConstraintBiometricPrompt()).isTrue()
-
-        // TODO(b/328843028): Use button.performClick() instead of calling
-        //  onContentViewMoreOptionsButtonPressed() directly, and check |isButtonClicked| is true.
-        container.mBiometricCallback.onContentViewMoreOptionsButtonPressed()
-        waitForIdleSync()
-        // container is gone
-        assertThat(container.mContainerState).isEqualTo(5)
-    }
-
-    @Test
-    fun testShowCredentialUI_withDescription() {
-        val container =
-            initializeFingerprintContainer(
-                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
-            )
-        waitForIdleSync()
-
-        assertThat(container.hasCredentialView()).isTrue()
-        assertThat(container.hasBiometricPrompt()).isFalse()
-    }
-
-    @Test
-    fun testShowCredentialUI_withVerticalListContentView() {
-        val container =
-            initializeFingerprintContainer(
-                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
-                verticalListContentView = PromptVerticalListContentView.Builder().build(),
-            )
-        // Two-step credential view should show -
-        // 1. biometric prompt without sensor 2. credential view ui
-        waitForIdleSync()
-        assertThat(container.hasConstraintBiometricPrompt()).isTrue()
-        assertThat(container.hasCredentialView()).isFalse()
-
-        container.animateToCredentialUI(false)
-        waitForIdleSync()
-        // TODO(b/302735104): Check the reason why hasConstraintBiometricPrompt() is still true
-        // assertThat(container.hasConstraintBiometricPrompt()).isFalse()
-        assertThat(container.hasCredentialView()).isTrue()
-    }
-
-    @Test
-    fun testShowCredentialUI_withContentViewWithMoreOptionsButton() {
-        val contentView =
-            PromptContentViewWithMoreOptionsButton.Builder()
-                .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> }
-                .build()
-        val container =
-            initializeFingerprintContainer(
-                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
-                contentViewWithMoreOptionsButton = contentView,
-            )
-        waitForIdleSync()
-
-        assertThat(container.hasCredentialView()).isTrue()
-        assertThat(container.hasBiometricPrompt()).isFalse()
-    }
-
-    @Test
-    fun testCredentialViewUsesEffectiveUserId() {
-        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(200)
-        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(200)))
-            .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING)
-
-        val container =
-            initializeFingerprintContainer(
-                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
-            )
-        waitForIdleSync()
-
-        assertThat(container.hasCredentialPatternView()).isTrue()
-        assertThat(container.hasBiometricPrompt()).isFalse()
-    }
-
-    @Test
-    fun testCredentialUI_disablesClickingOnBackground() {
-        val container = initializeCredentialPasswordContainer()
-        assertThat(container.hasBiometricPrompt()).isFalse()
-        assertThat(container.findViewById<View>(R.id.background)?.isImportantForAccessibility)
-            .isFalse()
-
-        container.findViewById<View>(R.id.background)?.performClick()
-        waitForIdleSync()
-
-        assertThat(container.hasCredentialPasswordView()).isTrue()
-        assertThat(container.hasBiometricPrompt()).isFalse()
-    }
-
-    @Test
-    fun testLayoutParams_hasSecureWindowFlag() {
-        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
-        assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SECURE) != 0).isTrue()
-    }
-
-    @Test
-    fun testLayoutParams_hasShowWhenLockedFlag() {
-        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
-        assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) != 0)
-            .isTrue()
-    }
-
-    @Test
-    fun testLayoutParams_hasDimbehindWindowFlag() {
-        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
-        val lpFlags = layoutParams.flags
-        val lpDimAmount = layoutParams.dimAmount
-
-        assertThat((lpFlags and WindowManager.LayoutParams.FLAG_DIM_BEHIND) != 0).isTrue()
-        assertThat(lpDimAmount).isGreaterThan(0f)
-    }
-
-    @Test
-    fun testLayoutParams_excludesImeInsets() {
-        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
-        assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.ime()) == 0).isTrue()
-    }
-
-    @Test
-    fun coexFaceRestartsOnTouch() {
-        val container = initializeCoexContainer()
-
-        container.onPointerDown()
-        waitForIdleSync()
-
-        container.onAuthenticationFailed(BiometricAuthenticator.TYPE_FACE, "failed")
-        waitForIdleSync()
-
-        verify(callback, never()).onTryAgainPressed(anyLong())
-
-        container.onPointerDown()
-        waitForIdleSync()
-
-        verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L)
-    }
-
-    private fun initializeCredentialPasswordContainer(
-        addToView: Boolean = true
-    ): TestAuthContainerView {
-        whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
-        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20)))
-            .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC)
-
-        // In the credential view, clicking on the background (to cancel authentication) is not
-        // valid. Thus, the listener should be null, and it should not be in the accessibility
-        // hierarchy.
-        val container =
-            initializeFingerprintContainer(
-                authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
-                addToView = addToView,
-            )
-        waitForIdleSync()
-
-        assertThat(container.hasCredentialPasswordView()).isTrue()
-        return container
-    }
-
-    private fun initializeFingerprintContainer(
-        authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
-        addToView: Boolean = true,
-        verticalListContentView: PromptVerticalListContentView? = null,
-        contentViewWithMoreOptionsButton: PromptContentViewWithMoreOptionsButton? = null,
-    ) =
-        initializeContainer(
-            TestAuthContainerView(
-                authenticators = authenticators,
-                fingerprintProps = fingerprintSensorPropertiesInternal(),
-                verticalListContentView = verticalListContentView,
-            ),
-            addToView,
-        )
-
-    private fun initializeCoexContainer(
-        authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
-        addToView: Boolean = true,
-    ) =
-        initializeContainer(
-            TestAuthContainerView(
-                authenticators = authenticators,
-                fingerprintProps = fingerprintSensorPropertiesInternal(),
-                faceProps = faceSensorPropertiesInternal(),
-            ),
-            addToView,
-        )
-
-    private fun initializeContainer(
-        view: TestAuthContainerView,
-        addToView: Boolean,
-    ): TestAuthContainerView {
-        authContainer = view
-
-        if (addToView) {
-            authContainer!!.addToView()
-        }
-
-        return authContainer!!
-    }
-
-    private inner class TestAuthContainerView(
-        authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
-        fingerprintProps: List<FingerprintSensorPropertiesInternal> = listOf(),
-        faceProps: List<FaceSensorPropertiesInternal> = listOf(),
-        verticalListContentView: PromptVerticalListContentView? = null,
-        contentViewWithMoreOptionsButton: PromptContentViewWithMoreOptionsButton? = null,
-    ) :
-        AuthContainerView(
-            Config().apply {
-                mContext = this@AuthContainerViewTest.context
-                mCallback = callback
-                mSensorIds =
-                    (fingerprintProps.map { it.sensorId } + faceProps.map { it.sensorId })
-                        .toIntArray()
-                mSkipAnimation = true
-                mPromptInfo =
-                    PromptInfo().apply {
-                        this.authenticators = authenticators
-                        if (verticalListContentView != null) {
-                            this.contentView = verticalListContentView
-                        } else if (contentViewWithMoreOptionsButton != null) {
-                            this.contentView = contentViewWithMoreOptionsButton
-                        }
-                    }
-                mOpPackageName = OP_PACKAGE_NAME
-            },
-            testScope.backgroundScope,
-            fingerprintProps,
-            faceProps,
-            wakefulnessLifecycle,
-            userManager,
-            lockPatternUtils,
-            interactionJankMonitor,
-            { promptSelectorInteractor },
-            PromptViewModel(
-                displayStateInteractor,
-                promptSelectorInteractor,
-                context,
-                udfpsOverlayInteractor,
-                biometricStatusInteractor,
-                udfpsUtils,
-                iconProvider,
-                activityTaskManager,
-            ),
-            { credentialViewModel },
-            fakeExecutor,
-            vibrator,
-            lazyViewCapture,
-            msdlPlayer,
-        ) {
-        override fun postOnAnimation(runnable: Runnable) {
-            runnable.run()
-        }
-    }
-
-    override fun waitForIdleSync() {
-        testScope.runCurrent()
-        TestableLooper.get(this).processAllMessages()
-    }
-
-    private fun AuthContainerView.addToView() {
-        ViewUtils.attachView(this)
-        waitForIdleSync()
-        assertThat(isAttachedToWindow()).isTrue()
-    }
-
-    @Test
-    fun testLayoutParams_hasCutoutModeAlwaysFlag() {
-        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
-        val lpFlags = layoutParams.flags
-
-        assertThat(
-                (lpFlags and WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) != 0
-            )
-            .isTrue()
-    }
-
-    @Test
-    fun testLayoutParams_excludesSystemBarInsets() {
-        val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
-        assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.systemBars()) == 0).isTrue()
-    }
-}
-
-private fun AuthContainerView.hasConstraintBiometricPrompt() =
-    (findViewById<ConstraintLayout>(R.id.biometric_prompt_constraint_layout)?.childCount ?: 0) > 0
-
-private fun AuthContainerView.hasBiometricPrompt() =
-    (findViewById<ScrollView>(R.id.biometric_scrollview)?.childCount ?: 0) > 0
-
-private fun AuthContainerView.hasCredentialView() =
-    hasCredentialPatternView() || hasCredentialPasswordView()
-
-private fun AuthContainerView.hasCredentialPatternView() =
-    findViewById<View>(R.id.lockPattern) != null
-
-private fun AuthContainerView.hasCredentialPasswordView() =
-    findViewById<View>(R.id.lockPassword) != null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
new file mode 100644
index 0000000..77db977
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
@@ -0,0 +1,740 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.udfps
+
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.MotionEvent.INVALID_POINTER_ID
+import android.view.MotionEvent.PointerProperties
+import android.view.Surface
+import android.view.Surface.Rotation
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() {
+    private val overlapDetector = FakeOverlapDetector()
+    private val underTest = SinglePointerTouchProcessor(overlapDetector)
+
+    @Test
+    fun processTouch() {
+        overlapDetector.shouldReturn =
+            testCase.currentPointers.associate { pointer -> pointer.id to pointer.onSensor }
+
+        val actual =
+            underTest.processTouch(
+                testCase.event,
+                testCase.previousPointerOnSensorId,
+                testCase.overlayParams,
+            )
+
+        assertThat(actual).isInstanceOf(testCase.expected.javaClass)
+        if (actual is TouchProcessorResult.ProcessedTouch) {
+            assertThat(actual).isEqualTo(testCase.expected)
+        }
+    }
+
+    data class TestCase(
+        val event: MotionEvent,
+        val currentPointers: List<TestPointer>,
+        val previousPointerOnSensorId: Int,
+        val overlayParams: UdfpsOverlayParams,
+        val expected: TouchProcessorResult,
+    ) {
+        override fun toString(): String {
+            val expectedOutput =
+                if (expected is TouchProcessorResult.ProcessedTouch) {
+                    expected.event.toString() +
+                        ", (x: ${expected.touchData.x}, y: ${expected.touchData.y})" +
+                        ", pointerOnSensorId: ${expected.pointerOnSensorId}" +
+                        ", ..."
+                } else {
+                    TouchProcessorResult.Failure().toString()
+                }
+            return "{" +
+                MotionEvent.actionToString(event.action) +
+                ", (x: ${event.x}, y: ${event.y})" +
+                ", scale: ${overlayParams.scaleFactor}" +
+                ", rotation: " +
+                Surface.rotationToString(overlayParams.rotation) +
+                ", previousPointerOnSensorId: $previousPointerOnSensorId" +
+                ", ...} expected: {$expectedOutput}"
+        }
+    }
+
+    companion object {
+        @Parameters(name = "{0}")
+        @JvmStatic
+        fun data(): List<TestCase> =
+            listOf(
+                    // MotionEvent.ACTION_DOWN
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_DOWN,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID_1,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_DOWN,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_DOWN,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    // MotionEvent.ACTION_HOVER_ENTER
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID_1,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    // MotionEvent.ACTION_MOVE
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_MOVE,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID_1,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_MOVE,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = POINTER_ID_1,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_MOVE,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_MOVE,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_MOVE,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = false),
+                                TestPointer(id = POINTER_ID_2, onSensor = true)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID_2,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_MOVE,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = false),
+                                TestPointer(id = POINTER_ID_2, onSensor = true)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = POINTER_ID_2,
+                    ),
+                    // MotionEvent.ACTION_HOVER_MOVE
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID_1,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = POINTER_ID_1,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    // MotionEvent.ACTION_UP
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_UP,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_UP,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_UP,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    // MotionEvent.ACTION_HOVER_EXIT
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    // MotionEvent.ACTION_CANCEL
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_CANCEL,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.CANCEL,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_CANCEL,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.CANCEL,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_CANCEL,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.CANCEL,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_CANCEL,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.CANCEL,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    // MotionEvent.ACTION_POINTER_DOWN
+                    genPositiveTestCases(
+                        motionEventAction =
+                            MotionEvent.ACTION_POINTER_DOWN +
+                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = true),
+                                TestPointer(id = POINTER_ID_2, onSensor = false)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID_1,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction =
+                            MotionEvent.ACTION_POINTER_DOWN +
+                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = false),
+                                TestPointer(id = POINTER_ID_2, onSensor = true)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID_2
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction =
+                            MotionEvent.ACTION_POINTER_DOWN +
+                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = true),
+                                TestPointer(id = POINTER_ID_2, onSensor = false)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = POINTER_ID_1,
+                    ),
+                    // MotionEvent.ACTION_POINTER_UP
+                    genPositiveTestCases(
+                        motionEventAction =
+                            MotionEvent.ACTION_POINTER_UP +
+                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = false),
+                                TestPointer(id = POINTER_ID_2, onSensor = false)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction =
+                            MotionEvent.ACTION_POINTER_UP +
+                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+                        previousPointerOnSensorId = POINTER_ID_2,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = false),
+                                TestPointer(id = POINTER_ID_2, onSensor = true)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_POINTER_UP,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = true),
+                                TestPointer(id = POINTER_ID_2, onSensor = false)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction =
+                            MotionEvent.ACTION_POINTER_UP +
+                                (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = true),
+                                TestPointer(id = POINTER_ID_2, onSensor = false)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = POINTER_ID_1
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_POINTER_UP,
+                        previousPointerOnSensorId = POINTER_ID_2,
+                        currentPointers =
+                            listOf(
+                                TestPointer(id = POINTER_ID_1, onSensor = false),
+                                TestPointer(id = POINTER_ID_2, onSensor = true)
+                            ),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = POINTER_ID_2
+                    )
+                )
+                .flatten()
+    }
+}
+
+data class TestPointer(val id: Int, val onSensor: Boolean)
+
+/* Display dimensions in native resolution and natural orientation. */
+private const val ROTATION_0_NATIVE_DISPLAY_WIDTH = 400
+private const val ROTATION_0_NATIVE_DISPLAY_HEIGHT = 600
+
+/* Placeholder touch parameters. */
+private const val POINTER_ID_1 = 42
+private const val POINTER_ID_2 = 43
+private const val NATIVE_MINOR = 2.71828f
+private const val NATIVE_MAJOR = 3.14f
+private const val ORIENTATION = 1.2345f
+private const val TIME = 12345699L
+private const val GESTURE_START = 12345600L
+
+/*
+ * ROTATION_0 map:
+ * _ _ _ _
+ * _ _ O _
+ * _ _ _ _
+ * _ S _ _
+ * _ S _ _
+ * _ _ _ _
+ *
+ * (_) empty space
+ * (S) sensor
+ * (O) touch outside of the sensor
+ */
+private val ROTATION_0_NATIVE_SENSOR_BOUNDS =
+    Rect(
+        100, /* left */
+        300, /* top */
+        200, /* right */
+        500, /* bottom */
+    )
+private val ROTATION_0_INPUTS =
+    OrientationBasedInputs(
+        rotation = Surface.ROTATION_0,
+        nativeOrientation = ORIENTATION,
+        nativeXWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterX(),
+        nativeYWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterY(),
+        nativeXOutsideSensor = 250f,
+        nativeYOutsideSensor = 150f,
+    )
+
+/*
+ * ROTATION_90 map:
+ * _ _ _ _ _ _
+ * _ O _ _ _ _
+ * _ _ _ S S _
+ * _ _ _ _ _ _
+ *
+ * (_) empty space
+ * (S) sensor
+ * (O) touch outside of the sensor
+ */
+private val ROTATION_90_NATIVE_SENSOR_BOUNDS =
+    Rect(
+        300, /* left */
+        200, /* top */
+        500, /* right */
+        300, /* bottom */
+    )
+private val ROTATION_90_INPUTS =
+    OrientationBasedInputs(
+        rotation = Surface.ROTATION_90,
+        nativeOrientation = (ORIENTATION - Math.PI.toFloat() / 2),
+        nativeXWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterX(),
+        nativeYWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterY(),
+        nativeXOutsideSensor = 150f,
+        nativeYOutsideSensor = 150f,
+    )
+
+/*
+ * ROTATION_180 map:
+ * _ _ _ _
+ * _ _ s _
+ * _ _ s _
+ * _ _ _ _
+ * _ O _ _
+ * _ _ _ _
+ *
+ * (_) empty space
+ * (S) sensor
+ * (O) touch outside of the sensor
+ */
+private val ROTATION_180_NATIVE_SENSOR_BOUNDS =
+    Rect(
+        200, /* left */
+        100, /* top */
+        300, /* right */
+        300, /* bottom */
+    )
+private val ROTATION_180_INPUTS =
+    OrientationBasedInputs(
+        rotation = Surface.ROTATION_180,
+        nativeOrientation = (ORIENTATION - Math.PI.toFloat() / 2),
+        nativeXWithinSensor = ROTATION_180_NATIVE_SENSOR_BOUNDS.exactCenterX(),
+        nativeYWithinSensor = ROTATION_180_NATIVE_SENSOR_BOUNDS.exactCenterY(),
+        nativeXOutsideSensor = 150f,
+        nativeYOutsideSensor = 450f,
+    )
+
+/*
+ * ROTATION_270 map:
+ * _ _ _ _ _ _
+ * _ S S _ _ _
+ * _ _ _ _ O _
+ * _ _ _ _ _ _
+ *
+ * (_) empty space
+ * (S) sensor
+ * (O) touch outside of the sensor
+ */
+private val ROTATION_270_NATIVE_SENSOR_BOUNDS =
+    Rect(
+        100, /* left */
+        100, /* top */
+        300, /* right */
+        200, /* bottom */
+    )
+private val ROTATION_270_INPUTS =
+    OrientationBasedInputs(
+        rotation = Surface.ROTATION_270,
+        nativeOrientation = (ORIENTATION + Math.PI.toFloat() / 2),
+        nativeXWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterX(),
+        nativeYWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterY(),
+        nativeXOutsideSensor = 450f,
+        nativeYOutsideSensor = 250f,
+    )
+
+/* Template [MotionEvent]. */
+private val MOTION_EVENT =
+    obtainMotionEvent(
+        action = 0,
+        pointerId = POINTER_ID_1,
+        x = 0f,
+        y = 0f,
+        minor = 0f,
+        major = 0f,
+        orientation = ORIENTATION,
+        time = TIME,
+        gestureStart = GESTURE_START,
+    )
+
+/* Template [NormalizedTouchData]. */
+private val NORMALIZED_TOUCH_DATA =
+    NormalizedTouchData(
+        POINTER_ID_1,
+        x = 0f,
+        y = 0f,
+        NATIVE_MINOR,
+        NATIVE_MAJOR,
+        ORIENTATION,
+        TIME,
+        GESTURE_START
+    )
+
+/*
+ * Contains test inputs that are tied to a particular device orientation.
+ *
+ * "native" means in native resolution (not scaled).
+ */
+private data class OrientationBasedInputs(
+    @Rotation val rotation: Int,
+    val nativeOrientation: Float,
+    val nativeXWithinSensor: Float,
+    val nativeYWithinSensor: Float,
+    val nativeXOutsideSensor: Float,
+    val nativeYOutsideSensor: Float,
+) {
+
+    fun toOverlayParams(scaleFactor: Float): UdfpsOverlayParams =
+        UdfpsOverlayParams(
+            sensorBounds = ROTATION_0_NATIVE_SENSOR_BOUNDS.scaled(scaleFactor),
+            overlayBounds = ROTATION_0_NATIVE_SENSOR_BOUNDS.scaled(scaleFactor),
+            naturalDisplayHeight = (ROTATION_0_NATIVE_DISPLAY_HEIGHT * scaleFactor).toInt(),
+            naturalDisplayWidth = (ROTATION_0_NATIVE_DISPLAY_WIDTH * scaleFactor).toInt(),
+            scaleFactor = scaleFactor,
+            rotation = rotation
+        )
+
+    fun getNativeX(isWithinSensor: Boolean): Float {
+        return if (isWithinSensor) nativeXWithinSensor else nativeXOutsideSensor
+    }
+
+    fun getNativeY(isWithinSensor: Boolean): Float {
+        return if (isWithinSensor) nativeYWithinSensor else nativeYOutsideSensor
+    }
+}
+
+private fun genPositiveTestCases(
+    motionEventAction: Int,
+    previousPointerOnSensorId: Int,
+    currentPointers: List<TestPointer>,
+    expectedInteractionEvent: InteractionEvent,
+    expectedPointerOnSensorId: Int
+): List<SinglePointerTouchProcessorTest.TestCase> {
+    val scaleFactors = listOf(0.75f, 1f, 1.5f)
+    val orientations =
+        listOf(
+            ROTATION_0_INPUTS,
+            ROTATION_90_INPUTS,
+            ROTATION_180_INPUTS,
+            ROTATION_270_INPUTS,
+        )
+    return scaleFactors.flatMap { scaleFactor ->
+        orientations.map { orientation ->
+            val overlayParams = orientation.toOverlayParams(scaleFactor)
+
+            val pointerProperties =
+                currentPointers
+                    .map { pointer ->
+                        val pp = MotionEvent.PointerProperties()
+                        pp.id = pointer.id
+                        pp
+                    }
+                    .toList()
+
+            val pointerCoords =
+                currentPointers
+                    .map { pointer ->
+                        val pc = MotionEvent.PointerCoords()
+                        pc.x = orientation.getNativeX(pointer.onSensor) * scaleFactor
+                        pc.y = orientation.getNativeY(pointer.onSensor) * scaleFactor
+                        pc.touchMinor = NATIVE_MINOR * scaleFactor
+                        pc.touchMajor = NATIVE_MAJOR * scaleFactor
+                        pc.orientation = orientation.nativeOrientation
+                        pc
+                    }
+                    .toList()
+
+            val event =
+                MOTION_EVENT.copy(
+                    action = motionEventAction,
+                    pointerProperties = pointerProperties,
+                    pointerCoords = pointerCoords
+                )
+
+            val expectedTouchDataPointer =
+                currentPointers.find { it.id == expectedPointerOnSensorId }
+                    ?: currentPointers.find { it.id == previousPointerOnSensorId }
+                        ?: currentPointers[0]
+            val expectedTouchData =
+                if (motionEventAction != MotionEvent.ACTION_CANCEL) {
+                    NORMALIZED_TOUCH_DATA.copy(
+                        pointerId = expectedTouchDataPointer.id,
+                        x = ROTATION_0_INPUTS.getNativeX(expectedTouchDataPointer.onSensor),
+                        y = ROTATION_0_INPUTS.getNativeY(expectedTouchDataPointer.onSensor)
+                    )
+                } else {
+                    NormalizedTouchData()
+                }
+
+            val expected =
+                TouchProcessorResult.ProcessedTouch(
+                    event = expectedInteractionEvent,
+                    pointerOnSensorId = expectedPointerOnSensorId,
+                    touchData = expectedTouchData,
+                )
+            SinglePointerTouchProcessorTest.TestCase(
+                event = event,
+                currentPointers = currentPointers,
+                previousPointerOnSensorId = previousPointerOnSensorId,
+                overlayParams = overlayParams,
+                expected = expected,
+            )
+        }
+    }
+}
+
+private fun obtainMotionEvent(
+    action: Int,
+    pointerId: Int,
+    x: Float,
+    y: Float,
+    minor: Float,
+    major: Float,
+    orientation: Float,
+    time: Long,
+    gestureStart: Long,
+): MotionEvent {
+    val pp = PointerProperties()
+    pp.id = pointerId
+    val pc = MotionEvent.PointerCoords()
+    pc.x = x
+    pc.y = y
+    pc.touchMinor = minor
+    pc.touchMajor = major
+    pc.orientation = orientation
+    return obtainMotionEvent(action, arrayOf(pp), arrayOf(pc), time, gestureStart)
+}
+
+private fun obtainMotionEvent(
+    action: Int,
+    pointerProperties: Array<MotionEvent.PointerProperties>,
+    pointerCoords: Array<MotionEvent.PointerCoords>,
+    time: Long,
+    gestureStart: Long,
+): MotionEvent {
+    return MotionEvent.obtain(
+        gestureStart /* downTime */,
+        time /* eventTime */,
+        action /* action */,
+        pointerCoords.size /* pointerCount */,
+        pointerProperties /* pointerProperties */,
+        pointerCoords /* pointerCoords */,
+        0 /* metaState */,
+        0 /* buttonState */,
+        1f /* xPrecision */,
+        1f /* yPrecision */,
+        0 /* deviceId */,
+        0 /* edgeFlags */,
+        0 /* source */,
+        0 /* flags */
+    )
+}
+
+private fun MotionEvent.copy(
+    action: Int = this.action,
+    pointerId: Int = this.getPointerId(0),
+    x: Float = this.rawX,
+    y: Float = this.rawY,
+    minor: Float = this.touchMinor,
+    major: Float = this.touchMajor,
+    orientation: Float = this.orientation,
+    time: Long = this.eventTime,
+    gestureStart: Long = this.downTime,
+) = obtainMotionEvent(action, pointerId, x, y, minor, major, orientation, time, gestureStart)
+
+private fun MotionEvent.copy(
+    action: Int = this.action,
+    pointerProperties: List<MotionEvent.PointerProperties>,
+    pointerCoords: List<MotionEvent.PointerCoords>,
+    time: Long = this.eventTime,
+    gestureStart: Long = this.downTime
+) =
+    obtainMotionEvent(
+        action,
+        pointerProperties.toTypedArray(),
+        pointerCoords.toTypedArray(),
+        time,
+        gestureStart
+    )
+
+private fun Rect.scaled(scaleFactor: Float) = Rect(this).apply { scale(scaleFactor) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
index 4d138b4..5d622ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
@@ -122,9 +122,24 @@
 
     @Test
     @DisableFlags(Flags.FLAG_AUDIO_SHARING_QS_DIALOG_IMPROVEMENT)
+    fun testOnClick_connectedAudioSharingMediaDevice_flagOff_previewOn_createDialog() {
+        with(kosmos) {
+            testScope.runTest {
+                whenever(BluetoothUtils.isAudioSharingPreviewEnabled(any())).thenReturn(true)
+                bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+                actionInteractorImpl.onClick(connectedAudioSharingMediaDeviceItem, dialog)
+                verify(dialogTransitionAnimator)
+                    .showFromDialog(any(), any(), eq(null), anyBoolean())
+            }
+        }
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_AUDIO_SHARING_QS_DIALOG_IMPROVEMENT)
     fun testOnClick_connectedAudioSharingMediaDevice_flagOff_shouldLaunchSettings() {
         with(kosmos) {
             testScope.runTest {
+                whenever(BluetoothUtils.isAudioSharingPreviewEnabled(any())).thenReturn(false)
                 bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
                 whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
                 actionInteractorImpl.onClick(connectedAudioSharingMediaDeviceItem, dialog)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 2c17181..bfbdc50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -24,9 +24,9 @@
 import android.os.Looper
 import android.os.PatternMatcher
 import android.os.UserHandle
-import androidx.test.filters.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
 import com.android.systemui.dump.DumpManager
@@ -40,8 +40,9 @@
 import junit.framework.Assert.assertSame
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.advanceUntilIdle
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -68,39 +69,28 @@
         val DEFAULT_PERMISSION: String? = null
 
         fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+
         const val TEST_ACTION = "TEST_ACTION"
         const val TEST_SCHEME = "TEST_SCHEME"
         const val TEST_PATH = "TEST_PATH"
         const val TEST_TYPE = "test/type"
     }
 
-    @Mock
-    private lateinit var mockContext: Context
-    @Mock
-    private lateinit var mockUBRUser0: UserBroadcastDispatcher
-    @Mock
-    private lateinit var mockUBRUser1: UserBroadcastDispatcher
-    @Mock
-    private lateinit var broadcastReceiver: BroadcastReceiver
-    @Mock
-    private lateinit var broadcastReceiverOther: BroadcastReceiver
-    @Mock
-    private lateinit var intentFilter: IntentFilter
-    @Mock
-    private lateinit var intentFilterOther: IntentFilter
-    @Mock
-    private lateinit var mockHandler: Handler
-    @Mock
-    private lateinit var logger: BroadcastDispatcherLogger
-    @Mock
-    private lateinit var userTracker: UserTracker
-    @Mock
-    private lateinit var removalPendingStore: PendingRemovalStore
+    @Mock private lateinit var mockContext: Context
+    @Mock private lateinit var mockUBRUser0: UserBroadcastDispatcher
+    @Mock private lateinit var mockUBRUser1: UserBroadcastDispatcher
+    @Mock private lateinit var broadcastReceiver: BroadcastReceiver
+    @Mock private lateinit var broadcastReceiverOther: BroadcastReceiver
+    @Mock private lateinit var intentFilter: IntentFilter
+    @Mock private lateinit var intentFilterOther: IntentFilter
+    @Mock private lateinit var mockHandler: Handler
+    @Mock private lateinit var logger: BroadcastDispatcherLogger
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var removalPendingStore: PendingRemovalStore
 
     private lateinit var mainExecutor: Executor
 
-    @Captor
-    private lateinit var argumentCaptor: ArgumentCaptor<ReceiverData>
+    @Captor private lateinit var argumentCaptor: ArgumentCaptor<ReceiverData>
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var broadcastDispatcher: BroadcastDispatcher
@@ -112,7 +102,8 @@
         mainExecutor = FakeExecutor(FakeSystemClock())
         `when`(mockContext.mainExecutor).thenReturn(mainExecutor)
 
-        broadcastDispatcher = TestBroadcastDispatcher(
+        broadcastDispatcher =
+            TestBroadcastDispatcher(
                 mockContext,
                 mainExecutor,
                 testableLooper.looper,
@@ -121,7 +112,8 @@
                 logger,
                 userTracker,
                 removalPendingStore,
-                mapOf(0 to mockUBRUser0, 1 to mockUBRUser1))
+                mapOf(0 to mockUBRUser0, 1 to mockUBRUser1),
+            )
 
         // These should be valid filters
         `when`(intentFilter.countActions()).thenReturn(1)
@@ -131,10 +123,18 @@
 
     @Test
     fun testAddingReceiverToCorrectUBR() {
-        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
-                mockHandler, user0)
         broadcastDispatcher.registerReceiverWithHandler(
-                broadcastReceiverOther, intentFilterOther, mockHandler, user1)
+            broadcastReceiver,
+            intentFilter,
+            mockHandler,
+            user0,
+        )
+        broadcastDispatcher.registerReceiverWithHandler(
+            broadcastReceiverOther,
+            intentFilterOther,
+            mockHandler,
+            user1,
+        )
 
         testableLooper.processAllMessages()
 
@@ -152,7 +152,11 @@
     fun testAddingReceiverToCorrectUBR_executor() {
         broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mainExecutor, user0)
         broadcastDispatcher.registerReceiver(
-                broadcastReceiverOther, intentFilterOther, mainExecutor, user1)
+            broadcastReceiverOther,
+            intentFilterOther,
+            mainExecutor,
+            user1,
+        )
 
         testableLooper.processAllMessages()
 
@@ -169,7 +173,10 @@
     @Test
     fun testAddReceiverDefaultFlag_handler() {
         broadcastDispatcher.registerReceiverWithHandler(
-                broadcastReceiver, intentFilter, mockHandler)
+            broadcastReceiver,
+            intentFilter,
+            mockHandler,
+        )
         testableLooper.processAllMessages()
 
         verify(mockUBRUser0).registerReceiver(capture(argumentCaptor), eq(DEFAULT_FLAG))
@@ -183,7 +190,11 @@
         val flag = 3
 
         broadcastDispatcher.registerReceiverWithHandler(
-                broadcastReceiver, intentFilter, mockHandler, flags = flag)
+            broadcastReceiver,
+            intentFilter,
+            mockHandler,
+            flags = flag,
+        )
         testableLooper.processAllMessages()
 
         verify(mockUBRUser0).registerReceiver(capture(argumentCaptor), eq(flag))
@@ -212,7 +223,7 @@
             broadcastReceiver,
             intentFilter,
             flags = flag,
-            permission = permission
+            permission = permission,
         )
         testableLooper.processAllMessages()
 
@@ -250,10 +261,18 @@
 
     @Test
     fun testRemovingReceiversRemovesFromAllUBR() {
-        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
-                mockHandler, user0)
-        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
-                mockHandler, user1)
+        broadcastDispatcher.registerReceiverWithHandler(
+            broadcastReceiver,
+            intentFilter,
+            mockHandler,
+            user0,
+        )
+        broadcastDispatcher.registerReceiverWithHandler(
+            broadcastReceiver,
+            intentFilter,
+            mockHandler,
+            user1,
+        )
 
         broadcastDispatcher.unregisterReceiver(broadcastReceiver)
 
@@ -265,10 +284,18 @@
 
     @Test
     fun testRemoveReceiverFromUser() {
-        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
-                mockHandler, user0)
-        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
-                mockHandler, user1)
+        broadcastDispatcher.registerReceiverWithHandler(
+            broadcastReceiver,
+            intentFilter,
+            mockHandler,
+            user0,
+        )
+        broadcastDispatcher.registerReceiverWithHandler(
+            broadcastReceiver,
+            intentFilter,
+            mockHandler,
+            user1,
+        )
 
         broadcastDispatcher.unregisterReceiverForUser(broadcastReceiver, user0)
 
@@ -282,13 +309,17 @@
     fun testRegisterCurrentAsActualUser() {
         `when`(userTracker.userId).thenReturn(user1.identifier)
 
-        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
-                mockHandler, UserHandle.CURRENT)
+        broadcastDispatcher.registerReceiverWithHandler(
+            broadcastReceiver,
+            intentFilter,
+            mockHandler,
+            UserHandle.CURRENT,
+        )
 
         testableLooper.processAllMessages()
 
-        verify(mockUBRUser1).registerReceiver(
-                capture(argumentCaptor), eq(Context.RECEIVER_EXPORTED))
+        verify(mockUBRUser1)
+            .registerReceiver(capture(argumentCaptor), eq(Context.RECEIVER_EXPORTED))
         assertSame(broadcastReceiver, argumentCaptor.value.receiver)
     }
 
@@ -300,41 +331,38 @@
 
     @Test(expected = IllegalArgumentException::class)
     fun testFilterMustNotContainDataScheme() {
-        val testFilter = IntentFilter(TEST_ACTION).apply {
-            addDataScheme(TEST_SCHEME)
-        }
+        val testFilter = IntentFilter(TEST_ACTION).apply { addDataScheme(TEST_SCHEME) }
         broadcastDispatcher.registerReceiver(broadcastReceiver, testFilter)
     }
 
     @Test(expected = IllegalArgumentException::class)
     fun testFilterMustNotContainDataAuthority() {
-        val testFilter = IntentFilter(TEST_ACTION).apply {
-            addDataAuthority(mock(IntentFilter.AuthorityEntry::class.java))
-        }
+        val testFilter =
+            IntentFilter(TEST_ACTION).apply {
+                addDataAuthority(mock(IntentFilter.AuthorityEntry::class.java))
+            }
         broadcastDispatcher.registerReceiver(broadcastReceiver, testFilter)
     }
 
     @Test(expected = IllegalArgumentException::class)
     fun testFilterMustNotContainDataPath() {
-        val testFilter = IntentFilter(TEST_ACTION).apply {
-            addDataPath(TEST_PATH, PatternMatcher.PATTERN_LITERAL)
-        }
+        val testFilter =
+            IntentFilter(TEST_ACTION).apply {
+                addDataPath(TEST_PATH, PatternMatcher.PATTERN_LITERAL)
+            }
         broadcastDispatcher.registerReceiver(broadcastReceiver, testFilter)
     }
 
     @Test(expected = IllegalArgumentException::class)
     fun testFilterMustNotContainDataType() {
-        val testFilter = IntentFilter(TEST_ACTION).apply {
-            addDataType(TEST_TYPE)
-        }
+        val testFilter = IntentFilter(TEST_ACTION).apply { addDataType(TEST_TYPE) }
         broadcastDispatcher.registerReceiver(broadcastReceiver, testFilter)
     }
 
     @Test(expected = IllegalArgumentException::class)
     fun testFilterMustNotSetPriority() {
-        val testFilter = IntentFilter(TEST_ACTION).apply {
-            priority = IntentFilter.SYSTEM_HIGH_PRIORITY
-        }
+        val testFilter =
+            IntentFilter(TEST_ACTION).apply { priority = IntentFilter.SYSTEM_HIGH_PRIORITY }
         broadcastDispatcher.registerReceiver(broadcastReceiver, testFilter)
     }
 
@@ -366,12 +394,14 @@
 
         val inOrderUser0 = inOrder(mockUBRUser0, removalPendingStore)
         inOrderUser0.verify(mockUBRUser0).unregisterReceiver(broadcastReceiver)
-        inOrderUser0.verify(removalPendingStore)
+        inOrderUser0
+            .verify(removalPendingStore)
             .clearPendingRemoval(broadcastReceiver, UserHandle.USER_ALL)
 
         val inOrderUser1 = inOrder(mockUBRUser1, removalPendingStore)
         inOrderUser1.verify(mockUBRUser1).unregisterReceiver(broadcastReceiver)
-        inOrderUser1.verify(removalPendingStore)
+        inOrderUser1
+            .verify(removalPendingStore)
             .clearPendingRemoval(broadcastReceiver, UserHandle.USER_ALL)
     }
 
@@ -385,21 +415,21 @@
 
         val inOrderUser1 = inOrder(mockUBRUser1, removalPendingStore)
         inOrderUser1.verify(mockUBRUser1).unregisterReceiver(broadcastReceiver)
-        inOrderUser1.verify(removalPendingStore)
+        inOrderUser1
+            .verify(removalPendingStore)
             .clearPendingRemoval(broadcastReceiver, user1.identifier)
     }
 
     @Test
-    fun testBroadcastFlow() = runBlockingTest {
-        val flow = broadcastDispatcher.broadcastFlow(intentFilter, user1) { intent, receiver ->
-            intent to receiver
-        }
+    fun testBroadcastFlow() = runTest(UnconfinedTestDispatcher()) {
+        val flow =
+            broadcastDispatcher.broadcastFlow(intentFilter, user1) { intent, receiver ->
+                intent to receiver
+            }
 
         // Collect the values into collectedValues.
         val collectedValues = mutableListOf<Pair<Intent, BroadcastReceiver>>()
-        val job = launch {
-            flow.collect { collectedValues.add(it) }
-        }
+        val job = launch { flow.collect { collectedValues.add(it) } }
 
         testableLooper.processAllMessages()
         verify(mockUBRUser1).registerReceiver(capture(argumentCaptor), eq(DEFAULT_FLAG))
@@ -436,17 +466,18 @@
         logger: BroadcastDispatcherLogger,
         userTracker: UserTracker,
         removalPendingStore: PendingRemovalStore,
-        var mockUBRMap: Map<Int, UserBroadcastDispatcher>
-    ) : BroadcastDispatcher(
-        context,
-        mainExecutor,
-        backgroundRunningLooper,
-        backgroundRunningExecutor,
-        dumpManager,
-        logger,
-        userTracker,
-        removalPendingStore
-    ) {
+        var mockUBRMap: Map<Int, UserBroadcastDispatcher>,
+    ) :
+        BroadcastDispatcher(
+            context,
+            mainExecutor,
+            backgroundRunningLooper,
+            backgroundRunningExecutor,
+            dumpManager,
+            logger,
+            userTracker,
+            removalPendingStore,
+        ) {
         override fun createUBRForUser(userId: Int): UserBroadcastDispatcher {
             return mockUBRMap.getOrDefault(userId, mock(UserBroadcastDispatcher::class.java))
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
index 4ca84c58..50fad3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.communal.data.db.CommunalDatabase
 import com.android.systemui.communal.data.db.CommunalWidgetDao
 import com.android.systemui.communal.proto.toCommunalHubState
+import com.android.systemui.communal.shared.model.SpanValue
 import com.android.systemui.lifecycle.InstantTaskExecutorRule
 import com.google.common.truth.Truth.assertThat
 import java.io.File
@@ -120,21 +121,32 @@
                     componentName = "com.android.fakePackage1/fakeWidget1",
                     rank = 0,
                     userSerialNumber = 0,
+                    spanY = SpanValue.Responsive(1),
                 ),
                 FakeWidgetMetadata(
                     widgetId = 12,
                     componentName = "com.android.fakePackage2/fakeWidget2",
                     rank = 1,
                     userSerialNumber = 0,
+                    spanY = SpanValue.Responsive(2),
                 ),
                 FakeWidgetMetadata(
                     widgetId = 13,
                     componentName = "com.android.fakePackage3/fakeWidget3",
                     rank = 2,
                     userSerialNumber = 10,
+                    spanY = SpanValue.Responsive(3),
                 ),
             )
-            .onEach { dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber) }
+            .onEach {
+                dao.addWidget(
+                    widgetId = it.widgetId,
+                    componentName = it.componentName,
+                    rank = it.rank,
+                    userSerialNumber = it.userSerialNumber,
+                    spanY = it.spanY,
+                )
+            }
     }
 
     private fun getBackupDataInputStream(): BackupDataInputStream {
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 edc8c83..d31e466 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
@@ -23,6 +23,9 @@
 import com.android.systemui.communal.data.db.CommunalDatabase
 import com.android.systemui.communal.data.db.CommunalWidgetDao
 import com.android.systemui.communal.nano.CommunalHubState
+import com.android.systemui.communal.shared.model.SpanValue
+import com.android.systemui.communal.shared.model.toFixed
+import com.android.systemui.communal.shared.model.toResponsive
 import com.android.systemui.lifecycle.InstantTaskExecutorRule
 import com.google.common.truth.Correspondence
 import com.google.common.truth.Truth.assertThat
@@ -71,22 +74,25 @@
                     componentName = "com.android.fakePackage1/fakeWidget1",
                     rank = 0,
                     userSerialNumber = 0,
+                    spanY = SpanValue.Responsive(1),
                 ),
                 FakeWidgetMetadata(
                     widgetId = 12,
                     componentName = "com.android.fakePackage2/fakeWidget2",
                     rank = 1,
                     userSerialNumber = 0,
+                    spanY = SpanValue.Responsive(2),
                 ),
                 FakeWidgetMetadata(
                     widgetId = 13,
                     componentName = "com.android.fakePackage3/fakeWidget3",
                     rank = 2,
                     userSerialNumber = 10,
+                    spanY = SpanValue.Responsive(3),
                 ),
             )
         expectedWidgets.forEach {
-            dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber)
+            dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber, it.spanY)
         }
 
         // Get communal hub state
@@ -150,6 +156,7 @@
         val componentName: String,
         val rank: Int,
         val userSerialNumber: Int,
+        val spanY: SpanValue,
     )
 
     companion object {
@@ -163,7 +170,9 @@
                     actual?.widgetId == expected?.widgetId &&
                         actual?.componentName == expected?.componentName &&
                         actual?.rank == expected?.rank &&
-                        actual?.userSerialNumber == expected?.userSerialNumber
+                        actual?.userSerialNumber == expected?.userSerialNumber &&
+                        actual?.spanY == expected?.spanY?.toFixed()?.value &&
+                        actual?.spanYNew == expected?.spanY?.toResponsive()?.value
                 },
                 "represents",
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
index 7d5a334..1466e32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
@@ -22,6 +22,8 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.shared.model.SpanValue
+import com.android.systemui.communal.shared.model.toResponsive
 import com.android.systemui.lifecycle.InstantTaskExecutorRule
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
@@ -173,6 +175,49 @@
         databaseV4.verifyWidgetsV4(fakeWidgetsV3.map { it.getV4() })
     }
 
+    @Test
+    fun migrate4To5_addNewSpanYColumn() {
+        val databaseV4 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 4)
+
+        val fakeWidgetsV4 =
+            listOf(
+                FakeCommunalWidgetItemV4(
+                    widgetId = 1,
+                    componentName = "test_widget_1",
+                    itemId = 11,
+                    userSerialNumber = 0,
+                    spanY = 3,
+                ),
+                FakeCommunalWidgetItemV4(
+                    widgetId = 2,
+                    componentName = "test_widget_2",
+                    itemId = 12,
+                    userSerialNumber = 10,
+                    spanY = 6,
+                ),
+                FakeCommunalWidgetItemV4(
+                    widgetId = 3,
+                    componentName = "test_widget_3",
+                    itemId = 13,
+                    userSerialNumber = 0,
+                    spanY = 0,
+                ),
+            )
+        databaseV4.insertWidgetsV4(fakeWidgetsV4)
+
+        databaseV4.verifyWidgetsV4(fakeWidgetsV4)
+
+        val databaseV5 =
+            migrationTestHelper.runMigrationsAndValidate(
+                name = DATABASE_NAME,
+                version = 5,
+                validateDroppedTables = false,
+                CommunalDatabase.MIGRATION_4_5,
+            )
+
+        databaseV5.verifyWidgetsV5(fakeWidgetsV4.map { it.getV5() })
+    }
+
     private fun SupportSQLiteDatabase.insertWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
         widgets.forEach { widget ->
             execSQL(
@@ -198,6 +243,24 @@
         }
     }
 
+    private fun SupportSQLiteDatabase.insertWidgetsV4(widgets: List<FakeCommunalWidgetItemV4>) {
+        widgets.forEach { widget ->
+            execSQL(
+                "INSERT INTO communal_widget_table(" +
+                    "widget_id, " +
+                    "component_name, " +
+                    "item_id, " +
+                    "user_serial_number, " +
+                    "span_y) " +
+                    "VALUES(${widget.widgetId}, " +
+                    "'${widget.componentName}', " +
+                    "${widget.itemId}, " +
+                    "${widget.userSerialNumber}," +
+                    "${widget.spanY})"
+            )
+        }
+    }
+
     private fun SupportSQLiteDatabase.verifyWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
         val cursor = query("SELECT * FROM communal_widget_table")
         assertThat(cursor.moveToFirst()).isTrue()
@@ -270,6 +333,27 @@
         assertThat(cursor.isAfterLast).isTrue()
     }
 
+    private fun SupportSQLiteDatabase.verifyWidgetsV5(widgets: List<FakeCommunalWidgetItemV5>) {
+        val cursor = query("SELECT * FROM communal_widget_table")
+        assertThat(cursor.moveToFirst()).isTrue()
+
+        widgets.forEach { widget ->
+            assertThat(cursor.getInt(cursor.getColumnIndex("widget_id"))).isEqualTo(widget.widgetId)
+            assertThat(cursor.getString(cursor.getColumnIndex("component_name")))
+                .isEqualTo(widget.componentName)
+            assertThat(cursor.getInt(cursor.getColumnIndex("item_id"))).isEqualTo(widget.itemId)
+            assertThat(cursor.getInt(cursor.getColumnIndex("user_serial_number")))
+                .isEqualTo(widget.userSerialNumber)
+            assertThat(cursor.getInt(cursor.getColumnIndex("span_y"))).isEqualTo(widget.spanY)
+            assertThat(cursor.getInt(cursor.getColumnIndex("span_y_new")))
+                .isEqualTo(widget.spanYNew)
+
+            cursor.moveToNext()
+        }
+
+        assertThat(cursor.isAfterLast).isTrue()
+    }
+
     private fun SupportSQLiteDatabase.insertRanks(ranks: List<FakeCommunalItemRank>) {
         ranks.forEach { rank ->
             execSQL("INSERT INTO communal_item_rank_table(rank) VALUES(${rank.rank})")
@@ -334,6 +418,27 @@
         val spanY: Int,
     )
 
+    private fun FakeCommunalWidgetItemV4.getV5(): FakeCommunalWidgetItemV5 {
+        val spanYFixed = SpanValue.Fixed(spanY)
+        return FakeCommunalWidgetItemV5(
+            widgetId = widgetId,
+            componentName = componentName,
+            itemId = itemId,
+            userSerialNumber = userSerialNumber,
+            spanY = spanYFixed.value,
+            spanYNew = spanYFixed.toResponsive().value,
+        )
+    }
+
+    private data class FakeCommunalWidgetItemV5(
+        val widgetId: Int,
+        val componentName: String,
+        val itemId: Int,
+        val userSerialNumber: Int,
+        val spanY: Int,
+        val spanYNew: Int,
+    )
+
     private data class FakeCommunalItemRank(val rank: Int)
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index 2312bbd..2acb775 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -22,7 +22,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.nano.CommunalHubState
-import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.SpanValue
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.lifecycle.InstantTaskExecutorRule
 import com.google.common.truth.Truth.assertThat
@@ -68,12 +68,13 @@
     @Test
     fun addWidget_readValueInDb() =
         testScope.runTest {
-            val (widgetId, provider, rank, userSerialNumber) = widgetInfo1
+            val (widgetId, provider, rank, userSerialNumber, spanY) = widgetInfo1
             communalWidgetDao.addWidget(
                 widgetId = widgetId,
                 provider = provider,
                 rank = rank,
                 userSerialNumber = userSerialNumber,
+                spanY = spanY,
             )
             val entry = communalWidgetDao.getWidgetByIdNow(id = 1)
             assertThat(entry).isEqualTo(communalWidgetItemEntry1)
@@ -82,12 +83,13 @@
     @Test
     fun deleteWidget_notInDb_returnsFalse() =
         testScope.runTest {
-            val (widgetId, provider, rank, userSerialNumber) = widgetInfo1
+            val (widgetId, provider, rank, userSerialNumber, spanY) = widgetInfo1
             communalWidgetDao.addWidget(
                 widgetId = widgetId,
                 provider = provider,
                 rank = rank,
                 userSerialNumber = userSerialNumber,
+                spanY = spanY,
             )
             assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse()
         }
@@ -98,12 +100,13 @@
             val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
             widgetsToAdd.forEach {
-                val (widgetId, provider, rank, userSerialNumber) = it
+                val (widgetId, provider, rank, userSerialNumber, spanY) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
                     rank = rank,
                     userSerialNumber = userSerialNumber,
+                    spanY = spanY,
                 )
             }
             assertThat(widgets())
@@ -126,11 +129,12 @@
             // Add widgets one by one without specifying rank
             val widgetsToAdd = listOf(widgetInfo1, widgetInfo2, widgetInfo3)
             widgetsToAdd.forEach {
-                val (widgetId, provider, _, userSerialNumber) = it
+                val (widgetId, provider, _, userSerialNumber, spanY) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
                     userSerialNumber = userSerialNumber,
+                    spanY = spanY,
                 )
             }
 
@@ -153,12 +157,13 @@
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
 
             widgetsToAdd.forEach {
-                val (widgetId, provider, rank, userSerialNumber) = it
+                val (widgetId, provider, rank, userSerialNumber, spanY) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
                     rank = rank,
                     userSerialNumber = userSerialNumber,
+                    spanY = spanY,
                 )
             }
             assertThat(widgets())
@@ -180,12 +185,13 @@
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
 
             widgetsToAdd.forEach {
-                val (widgetId, provider, rank, userSerialNumber) = it
+                val (widgetId, provider, rank, userSerialNumber, spanY) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
                     rank = rank,
                     userSerialNumber = userSerialNumber,
+                    spanY = spanY,
                 )
             }
             assertThat(widgets())
@@ -217,12 +223,13 @@
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
 
             existingWidgets.forEach {
-                val (widgetId, provider, rank, userSerialNumber) = it
+                val (widgetId, provider, rank, userSerialNumber, spanY) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
                     rank = rank,
                     userSerialNumber = userSerialNumber,
+                    spanY = spanY,
                 )
             }
             assertThat(widgets())
@@ -242,6 +249,7 @@
                 provider = ComponentName("pk_name", "cls_name_4"),
                 rank = 1,
                 userSerialNumber = 0,
+                spanY = SpanValue.Responsive(1),
             )
 
             val newRankEntry = CommunalItemRank(uid = 4L, rank = 1)
@@ -253,6 +261,7 @@
                     itemId = 4L,
                     userSerialNumber = 0,
                     spanY = 3,
+                    spanYNew = 1,
                 )
             assertThat(widgets())
                 .containsExactly(
@@ -279,21 +288,21 @@
                 provider = ComponentName("pkg_name", "cls_name_1"),
                 rank = 0,
                 userSerialNumber = 0,
-                spanY = CommunalContentSize.FULL.span,
+                spanY = SpanValue.Responsive(1),
             )
             communalWidgetDao.addWidget(
                 widgetId = 2,
                 provider = ComponentName("pkg_name", "cls_name_2"),
                 rank = 1,
                 userSerialNumber = 0,
-                spanY = CommunalContentSize.HALF.span,
+                spanY = SpanValue.Responsive(2),
             )
             communalWidgetDao.addWidget(
                 widgetId = 3,
                 provider = ComponentName("pkg_name", "cls_name_3"),
                 rank = 2,
                 userSerialNumber = 0,
-                spanY = CommunalContentSize.THIRD.span,
+                spanY = SpanValue.Fixed(3),
             )
 
             // Verify that the widgets have the correct spanY values
@@ -306,7 +315,8 @@
                         componentName = "pkg_name/cls_name_1",
                         itemId = 1L,
                         userSerialNumber = 0,
-                        spanY = CommunalContentSize.FULL.span,
+                        spanY = 3,
+                        spanYNew = 1,
                     ),
                     CommunalItemRank(uid = 2L, rank = 1),
                     CommunalWidgetItem(
@@ -315,7 +325,8 @@
                         componentName = "pkg_name/cls_name_2",
                         itemId = 2L,
                         userSerialNumber = 0,
-                        spanY = CommunalContentSize.HALF.span,
+                        spanY = 6,
+                        spanYNew = 2,
                     ),
                     CommunalItemRank(uid = 3L, rank = 2),
                     CommunalWidgetItem(
@@ -324,7 +335,8 @@
                         componentName = "pkg_name/cls_name_3",
                         itemId = 3L,
                         userSerialNumber = 0,
-                        spanY = CommunalContentSize.THIRD.span,
+                        spanY = 3,
+                        spanYNew = 1,
                     ),
                 )
                 .inOrder()
@@ -352,7 +364,8 @@
                         componentName = fakeWidget.componentName,
                         itemId = rank.uid,
                         userSerialNumber = fakeWidget.userSerialNumber,
-                        spanY = 3,
+                        spanY = fakeWidget.spanY.coerceAtLeast(3),
+                        spanYNew = fakeWidget.spanYNew.coerceAtLeast(1),
                     )
                 expected[rank] = widget
             }
@@ -366,6 +379,7 @@
             provider = metadata.provider,
             rank = rank ?: metadata.rank,
             userSerialNumber = metadata.userSerialNumber,
+            spanY = metadata.spanY,
         )
     }
 
@@ -374,6 +388,7 @@
         val provider: ComponentName,
         val rank: Int,
         val userSerialNumber: Int,
+        val spanY: SpanValue,
     )
 
     companion object {
@@ -383,6 +398,7 @@
                 provider = ComponentName("pk_name", "cls_name_1"),
                 rank = 0,
                 userSerialNumber = 0,
+                spanY = SpanValue.Responsive(1),
             )
         val widgetInfo2 =
             FakeWidgetMetadata(
@@ -390,6 +406,7 @@
                 provider = ComponentName("pk_name", "cls_name_2"),
                 rank = 1,
                 userSerialNumber = 0,
+                spanY = SpanValue.Responsive(1),
             )
         val widgetInfo3 =
             FakeWidgetMetadata(
@@ -397,6 +414,7 @@
                 provider = ComponentName("pk_name", "cls_name_3"),
                 rank = 2,
                 userSerialNumber = 10,
+                spanY = SpanValue.Responsive(1),
             )
         val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.rank)
         val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.rank)
@@ -409,6 +427,7 @@
                 itemId = communalItemRankEntry1.uid,
                 userSerialNumber = widgetInfo1.userSerialNumber,
                 spanY = 3,
+                spanYNew = 1,
             )
         val communalWidgetItemEntry2 =
             CommunalWidgetItem(
@@ -418,6 +437,7 @@
                 itemId = communalItemRankEntry2.uid,
                 userSerialNumber = widgetInfo2.userSerialNumber,
                 spanY = 3,
+                spanYNew = 1,
             )
         val communalWidgetItemEntry3 =
             CommunalWidgetItem(
@@ -427,6 +447,7 @@
                 itemId = communalItemRankEntry3.uid,
                 userSerialNumber = widgetInfo3.userSerialNumber,
                 spanY = 3,
+                spanYNew = 1,
             )
         val fakeState =
             CommunalHubState().apply {
@@ -437,12 +458,14 @@
                                 componentName = "pk_name/fake_widget_1"
                                 rank = 1
                                 userSerialNumber = 0
+                                spanY = 3
                             },
                             CommunalHubState.CommunalWidgetItem().apply {
                                 widgetId = 2
                                 componentName = "pk_name/fake_widget_2"
                                 rank = 2
                                 userSerialNumber = 10
+                                spanYNew = 1
                             },
                         )
                         .toTypedArray()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
deleted file mode 100644
index ba578a3..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.doze;
-
-import static com.android.systemui.doze.DozeMachine.State.DOZE;
-import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
-import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
-import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
-import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
-import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
-import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING;
-import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE;
-import static com.android.systemui.doze.DozeMachine.State.FINISH;
-import static com.android.systemui.doze.DozeMachine.State.INITIALIZED;
-import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
-import static com.android.systemui.utils.os.FakeHandler.Mode.QUEUEING;
-
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.os.Looper;
-import android.view.Display;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.biometrics.UdfpsController;
-import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
-import com.android.systemui.util.wakelock.WakeLockFake;
-import com.android.systemui.utils.os.FakeHandler;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import javax.inject.Provider;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class DozeScreenStateTest extends SysuiTestCase {
-
-    private DozeServiceFake mServiceFake;
-    private FakeHandler mHandlerFake;
-    @Mock
-    private DozeHost mDozeHost;
-    @Mock
-    private DozeParameters mDozeParameters;
-    private WakeLockFake mWakeLock;
-    private DozeScreenState mScreen;
-    @Mock
-    private Provider<UdfpsController> mUdfpsControllerProvider;
-    @Mock
-    private AuthController mAuthController;
-    @Mock
-    private UdfpsController mUdfpsController;
-    @Mock
-    private DozeLog mDozeLog;
-    @Mock
-    private DozeScreenBrightness mDozeScreenBrightness;
-    @Mock
-    private SelectedUserInteractor mSelectedUserInteractor;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
-        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
-        when(mUdfpsControllerProvider.get()).thenReturn(mUdfpsController);
-        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
-        when(mUdfpsController.isFingerDown()).thenReturn(false);
-
-        mServiceFake = new DozeServiceFake();
-        mHandlerFake = new FakeHandler(Looper.getMainLooper());
-        mWakeLock = new WakeLockFake();
-        mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters,
-                mWakeLock, mAuthController, mUdfpsControllerProvider, mDozeLog,
-                mDozeScreenBrightness, mSelectedUserInteractor);
-    }
-
-    @Test
-    public void testScreen_offInDoze() {
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE);
-
-        assertEquals(Display.STATE_OFF, mServiceFake.screenState);
-    }
-
-    @Test
-    public void testScreen_onInAod() {
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-
-        assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
-    }
-
-    @Test
-    public void testScreen_onInPulse() {
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE);
-
-        mScreen.transitionTo(DOZE, DOZE_REQUEST_PULSE);
-        mScreen.transitionTo(DOZE_REQUEST_PULSE, DOZE_PULSING);
-
-        assertEquals(Display.STATE_ON, mServiceFake.screenState);
-    }
-
-    @Test
-    public void testScreen_offInRequestPulseWithoutAoD() {
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE);
-
-        mScreen.transitionTo(DOZE, DOZE_REQUEST_PULSE);
-
-        assertEquals(Display.STATE_OFF, mServiceFake.screenState);
-    }
-
-    @Test
-    public void testScreen_offInRequestPulseWithAoD() {
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-
-        mScreen.transitionTo(DOZE, DOZE_REQUEST_PULSE);
-
-        assertEquals(Display.STATE_OFF, mServiceFake.screenState);
-    }
-
-    @Test
-    public void testScreen_onInDockedAod() {
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD_DOCKED);
-
-        assertEquals(Display.STATE_ON, mServiceFake.screenState);
-    }
-
-    @Test
-    public void test_initialScreenStatePostedToHandler() {
-        mHandlerFake.setMode(QUEUEING);
-
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mServiceFake.screenStateSet = false;
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-
-        assertFalse(mServiceFake.screenStateSet);
-
-        mHandlerFake.dispatchQueuedMessages();
-
-        assertTrue(mServiceFake.screenStateSet);
-        assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
-    }
-
-    @Test
-    public void test_noScreenStateSetAfterFinish() {
-        mHandlerFake.setMode(QUEUEING);
-
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-        mScreen.transitionTo(DOZE_AOD, FINISH);
-
-        mServiceFake.screenStateSet = false;
-
-        mHandlerFake.dispatchQueuedMessages();
-
-        assertFalse(mServiceFake.screenStateSet);
-    }
-
-    @Test
-    public void test_holdsWakeLockWhenGoingToLowPowerDelayed() {
-        // Transition to low power mode will be delayed to let
-        // animations play at 60 fps.
-        when(mDozeParameters.shouldDelayDisplayDozeTransition()).thenReturn(true);
-        mHandlerFake.setMode(QUEUEING);
-
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mHandlerFake.dispatchQueuedMessages();
-
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-        assertThat(mWakeLock.isHeld(), is(true));
-
-        mHandlerFake.dispatchQueuedMessages();
-        assertThat(mWakeLock.isHeld(), is(false));
-    }
-
-    @Test
-    public void test_releasesWakeLock_abortingLowPowerDelayed() {
-        // Transition to low power mode will be delayed to let
-        // animations play at 60 fps.
-        when(mDozeParameters.shouldDelayDisplayDozeTransition()).thenReturn(true);
-        mHandlerFake.setMode(QUEUEING);
-
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mHandlerFake.dispatchQueuedMessages();
-
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-        assertThat(mWakeLock.isHeld(), is(true));
-        mScreen.transitionTo(DOZE_AOD, FINISH);
-
-        assertThat(mWakeLock.isHeld(), is(false));
-    }
-
-    @Test
-    public void test_animatesPausing() {
-        ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
-        doAnswer(invocation -> null).when(mDozeHost).prepareForGentleSleep(captor.capture());
-        mHandlerFake.setMode(QUEUEING);
-
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD_PAUSING);
-        mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
-
-        mHandlerFake.dispatchQueuedMessages();
-        verify(mDozeHost).prepareForGentleSleep(eq(captor.getValue()));
-        captor.getValue().run();
-        assertEquals(Display.STATE_OFF, mServiceFake.screenState);
-    }
-
-    @Test
-    public void test_animatesOff() {
-        ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
-        doAnswer(invocation -> null).when(mDozeHost).prepareForGentleSleep(captor.capture());
-        mHandlerFake.setMode(QUEUEING);
-
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-        mScreen.transitionTo(DOZE_AOD, DOZE);
-
-        mHandlerFake.dispatchQueuedMessages();
-        verify(mDozeHost).prepareForGentleSleep(eq(captor.getValue()));
-        captor.getValue().run();
-        assertEquals(Display.STATE_OFF, mServiceFake.screenState);
-    }
-
-    @Test
-    public void testDelayEnterDozeScreenState_whenUdfpsFingerDown() {
-        // GIVEN AOD is initialized
-        when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
-        mHandlerFake.setMode(QUEUEING);
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mHandlerFake.dispatchQueuedMessages();
-
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-
-        // WHEN udfps is activated (fingerDown)
-        when(mUdfpsController.isFingerDown()).thenReturn(true);
-        mHandlerFake.dispatchQueuedMessages();
-
-        // THEN the display screen state doesn't immediately change
-        assertEquals(Display.STATE_ON, mServiceFake.screenState);
-
-        // WHEN udfpsController finger is no longer down and the queued messages are run
-        when(mUdfpsController.isFingerDown()).thenReturn(false);
-        mHandlerFake.dispatchQueuedMessages();
-
-        // THEN the display screen state will change
-        assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
-    }
-
-    @Test
-    public void testDelayExitPulsingScreenState_whenUdfpsFingerDown() {
-        // GIVEN we're pulsing
-        when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
-        mHandlerFake.setMode(QUEUEING);
-        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
-        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-        mScreen.transitionTo(DOZE_AOD, DOZE_REQUEST_PULSE);
-        mScreen.transitionTo(DOZE_REQUEST_PULSE, DOZE_PULSING);
-        mScreen.transitionTo(DOZE_PULSING, DOZE_PULSE_DONE);
-        mHandlerFake.dispatchQueuedMessages();
-
-        // WHEN udfps is activated while are transitioning back to DOZE_AOD
-        mScreen.transitionTo(DOZE_PULSE_DONE, DOZE_AOD);
-        when(mUdfpsController.isFingerDown()).thenReturn(true);
-        mHandlerFake.dispatchQueuedMessages();
-
-        // THEN the display screen state doesn't immediately change
-        assertEquals(Display.STATE_ON, mServiceFake.screenState);
-
-        // WHEN udfpsController finger is no longer down and the queued messages are run
-        when(mUdfpsController.isFingerDown()).thenReturn(false);
-        mHandlerFake.dispatchQueuedMessages();
-
-        // THEN the display screen state will change
-        assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
-    }
-
-    @Test
-    public void authCallbackRemovedOnDestroy() {
-        mScreen.destroy();
-
-        verify(mAuthController).removeCallback(anyObject());
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
deleted file mode 100644
index f706cf6..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
+++ /dev/null
@@ -1,360 +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.keyboard.shortcut.ui.viewmodel
-
-import android.content.Context
-import android.content.Context.INPUT_SERVICE
-import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS
-import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER
-import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE
-import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
-import android.hardware.input.fakeInputManager
-import android.os.SystemClock
-import android.view.KeyEvent.ACTION_DOWN
-import android.view.KeyEvent.KEYCODE_A
-import android.view.KeyEvent.META_CTRL_ON
-import android.view.KeyEvent.META_META_ON
-import androidx.compose.ui.input.key.KeyEvent
-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.keyboard.shortcut.shared.model.ShortcutCategoryType
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
-import com.android.systemui.keyboard.shortcut.shortcutCustomizationViewModelFactory
-import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
-import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.res.R
-import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.settings.userTracker
-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
-import org.mockito.kotlin.any
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class ShortcutCustomizationViewModelTest : SysuiTestCase() {
-
-    private val mockUserContext: Context = mock()
-    private val kosmos =
-        Kosmos().also {
-            it.testCase = this
-            it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
-        }
-    private val testScope = kosmos.testScope
-    private val inputManager = kosmos.fakeInputManager.inputManager
-    private val helper = kosmos.shortcutHelperTestHelper
-    private val viewModel = kosmos.shortcutCustomizationViewModelFactory.create()
-
-    @Before
-    fun setup() {
-        helper.showFromActivity()
-        whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager)
-    }
-
-    @Test
-    fun uiState_inactiveByDefault() {
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-
-            assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
-        }
-    }
-
-    @Test
-    fun uiState_correctlyUpdatedWhenAddShortcutCustomizationIsRequested() {
-        testScope.runTest {
-            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-
-            assertThat(uiState).isEqualTo(expectedStandardAddShortcutUiState)
-        }
-    }
-
-    @Test
-    fun uiState_correctlyUpdatedWhenDeleteShortcutCustomizationIsRequested() {
-        testScope.runTest {
-            viewModel.onShortcutCustomizationRequested(standardDeleteShortcutRequest)
-            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-
-            assertThat(uiState).isEqualTo(expectedStandardDeleteShortcutUiState)
-        }
-    }
-
-    @Test
-    fun uiState_consumedOnAddDialogShown() {
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            viewModel.onDialogShown()
-
-            assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).isDialogShowing)
-                .isTrue()
-        }
-    }
-
-    @Test
-    fun uiState_consumedOnDeleteDialogShown() {
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-            viewModel.onShortcutCustomizationRequested(standardDeleteShortcutRequest)
-            viewModel.onDialogShown()
-
-            assertThat(
-                    (uiState as ShortcutCustomizationUiState.DeleteShortcutDialog).isDialogShowing
-                )
-                .isTrue()
-        }
-    }
-
-    @Test
-    fun uiState_inactiveAfterDialogIsDismissed() {
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            viewModel.onDialogShown()
-            viewModel.onDialogDismissed()
-            assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
-        }
-    }
-
-    @Test
-    fun uiState_pressedKeys_emptyByDefault() {
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
-                .isEmpty()
-        }
-    }
-
-    @Test
-    fun uiState_becomeInactiveAfterSuccessfullySettingShortcut() {
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-            whenever(inputManager.addCustomInputGesture(any()))
-                .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS)
-
-            openAddShortcutDialogAndSetShortcut()
-
-            assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
-        }
-    }
-
-    @Test
-    fun uiState_errorMessage_isEmptyByDefault() {
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-            viewModel.onShortcutCustomizationRequested(allAppsShortcutCustomizationRequest)
-            viewModel.onDialogShown()
-
-            assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
-                .isEmpty()
-        }
-    }
-
-    @Test
-    fun uiState_errorMessage_isKeyCombinationInUse_whenKeyCombinationAlreadyExists() {
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-            whenever(inputManager.addCustomInputGesture(any()))
-                .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS)
-
-            openAddShortcutDialogAndSetShortcut()
-
-            assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
-                .isEqualTo(
-                    context.getString(
-                        R.string.shortcut_customizer_key_combination_in_use_error_message
-                    )
-                )
-        }
-    }
-
-    @Test
-    fun uiState_errorMessage_isKeyCombinationInUse_whenKeyCombinationIsReserved() {
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-            whenever(inputManager.addCustomInputGesture(any()))
-                .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE)
-
-            openAddShortcutDialogAndSetShortcut()
-
-            assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
-                .isEqualTo(
-                    context.getString(
-                        R.string.shortcut_customizer_key_combination_in_use_error_message
-                    )
-                )
-        }
-    }
-
-    @Test
-    fun uiState_errorMessage_isGenericError_whenErrorIsUnknown() {
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-            whenever(inputManager.addCustomInputGesture(any()))
-                .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER)
-
-            openAddShortcutDialogAndSetShortcut()
-
-            assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
-                .isEqualTo(context.getString(R.string.shortcut_customizer_generic_error_message))
-        }
-    }
-
-    @Test
-    fun onKeyPressed_handlesKeyEvents_whereActionKeyIsAlsoPressed() {
-        testScope.runTest {
-            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            val isHandled = viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
-
-            assertThat(isHandled).isTrue()
-        }
-    }
-
-    @Test
-    fun onKeyPressed_doesNotHandleKeyEvents_whenActionKeyIsNotAlsoPressed() {
-        testScope.runTest {
-            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            val isHandled = viewModel.onKeyPressed(keyDownEventWithoutActionKeyPressed)
-
-            assertThat(isHandled).isFalse()
-        }
-    }
-
-    @Test
-    fun onKeyPressed_convertsKeyEventsAndUpdatesUiStatesPressedKey() {
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
-            viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
-
-            // Note that Action Key is excluded as it's already displayed on the UI
-            assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
-                .containsExactly(ShortcutKey.Text("Ctrl"), ShortcutKey.Text("A"))
-        }
-    }
-
-    @Test
-    fun uiState_pressedKeys_resetsToEmptyListAfterDialogIsDismissedAndReopened() {
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
-            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
-            viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
-
-            // Note that Action Key is excluded as it's already displayed on the UI
-            assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
-                .containsExactly(ShortcutKey.Text("Ctrl"), ShortcutKey.Text("A"))
-
-            // Close the dialog and show it again
-            viewModel.onDialogDismissed()
-            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
-                .isEmpty()
-        }
-    }
-
-    private suspend fun openAddShortcutDialogAndSetShortcut() {
-        viewModel.onShortcutCustomizationRequested(allAppsShortcutCustomizationRequest)
-        viewModel.onDialogShown()
-
-        viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
-        viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
-
-        viewModel.onSetShortcut()
-    }
-
-    private val keyDownEventWithoutActionKeyPressed =
-        KeyEvent(
-            android.view.KeyEvent(
-                /* downTime = */ SystemClock.uptimeMillis(),
-                /* eventTime = */ SystemClock.uptimeMillis(),
-                /* action = */ ACTION_DOWN,
-                /* code = */ KEYCODE_A,
-                /* repeat = */ 0,
-                /* metaState = */ META_CTRL_ON,
-            )
-        )
-
-    private val keyDownEventWithActionKeyPressed =
-        KeyEvent(
-            android.view.KeyEvent(
-                /* downTime = */ SystemClock.uptimeMillis(),
-                /* eventTime = */ SystemClock.uptimeMillis(),
-                /* action = */ ACTION_DOWN,
-                /* code = */ KEYCODE_A,
-                /* repeat = */ 0,
-                /* metaState = */ META_CTRL_ON or META_META_ON,
-            )
-        )
-
-    private val keyUpEventWithActionKeyPressed =
-        KeyEvent(
-            android.view.KeyEvent(
-                /* downTime = */ SystemClock.uptimeMillis(),
-                /* eventTime = */ SystemClock.uptimeMillis(),
-                /* action = */ ACTION_DOWN,
-                /* code = */ KEYCODE_A,
-                /* repeat = */ 0,
-                /* metaState = */ 0,
-            )
-        )
-
-    private val standardAddShortcutRequest =
-        ShortcutCustomizationRequestInfo.Add(
-            label = "Standard shortcut",
-            categoryType = ShortcutCategoryType.System,
-            subCategoryLabel = "Standard subcategory",
-        )
-
-    private val standardDeleteShortcutRequest =
-        ShortcutCustomizationRequestInfo.Delete(
-            label = "Standard shortcut",
-            categoryType = ShortcutCategoryType.System,
-            subCategoryLabel = "Standard subcategory",
-        )
-
-    private val allAppsShortcutCustomizationRequest =
-        ShortcutCustomizationRequestInfo.Add(
-            label = "Open apps list",
-            categoryType = ShortcutCategoryType.System,
-            subCategoryLabel = "System controls",
-        )
-
-    private val expectedStandardAddShortcutUiState =
-        ShortcutCustomizationUiState.AddShortcutDialog(
-            shortcutLabel = "Standard shortcut",
-            defaultCustomShortcutModifierKey =
-                ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
-            isDialogShowing = false,
-        )
-
-    private val expectedStandardDeleteShortcutUiState =
-        ShortcutCustomizationUiState.DeleteShortcutDialog(isDialogShowing = false)
-}
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
deleted file mode 100644
index feae901..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ /dev/null
@@ -1,422 +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.keyboard.shortcut.ui.viewmodel
-
-import android.app.role.RoleManager
-import android.app.role.mockRoleManager
-import android.view.KeyEvent
-import android.view.KeyboardShortcutGroup
-import android.view.KeyboardShortcutInfo
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Tv
-import androidx.compose.material.icons.filled.VerticalSplit
-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.coroutines.collectValues
-import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
-import com.android.systemui.keyboard.shortcut.shared.model.shortcut
-import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
-import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
-import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
-import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
-import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
-import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
-import com.android.systemui.keyboard.shortcut.shortcutHelperViewModel
-import com.android.systemui.keyboard.shortcut.ui.model.IconSource
-import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
-import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
-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.settings.FakeUserTracker
-import com.android.systemui.settings.fakeUserTracker
-import com.android.systemui.settings.userTracker
-import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-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)
-class ShortcutHelperViewModelTest : SysuiTestCase() {
-
-    private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
-    private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
-    private val fakeCurrentAppsSource = FakeKeyboardShortcutGroupsSource()
-
-    private val kosmos =
-        Kosmos().also {
-            it.testCase = this
-            it.testDispatcher = UnconfinedTestDispatcher()
-            it.shortcutHelperSystemShortcutsSource = fakeSystemSource
-            it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
-            it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
-            it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
-            it.shortcutHelperCurrentAppShortcutsSource = fakeCurrentAppsSource
-            it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { context })
-        }
-
-    private val testScope = kosmos.testScope
-    private val testHelper = kosmos.shortcutHelperTestHelper
-    private val sysUiState = kosmos.sysUiState
-    private val fakeUserTracker = kosmos.fakeUserTracker
-    private val mockRoleManager = kosmos.mockRoleManager
-    private val viewModel = kosmos.shortcutHelperViewModel
-
-    @Before
-    fun setUp() {
-        fakeSystemSource.setGroups(TestShortcuts.systemGroups)
-        fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
-        fakeCurrentAppsSource.setGroups(TestShortcuts.currentAppGroups)
-    }
-
-    @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_falseAfterCloseSystemDialogs() =
-        testScope.runTest {
-            val shouldShow by collectLastValue(viewModel.shouldShow)
-
-            testHelper.showFromActivity()
-            testHelper.hideThroughCloseSystemDialogs()
-
-            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()
-        }
-
-    @Test
-    fun shortcutsUiState_inactiveByDefault() =
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutsUiState)
-
-            assertThat(uiState).isEqualTo(ShortcutsUiState.Inactive)
-        }
-
-    @Test
-    fun shortcutsUiState_featureActive_emitsActive() =
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutsUiState)
-
-            testHelper.showFromActivity()
-
-            assertThat(uiState).isInstanceOf(ShortcutsUiState.Active::class.java)
-        }
-
-    @Test
-    fun shortcutsUiState_noCurrentAppCategory_defaultSelectedCategoryIsSystem() =
-        testScope.runTest {
-            fakeCurrentAppsSource.setGroups(emptyList())
-
-            val uiState by collectLastValue(viewModel.shortcutsUiState)
-
-            testHelper.showFromActivity()
-
-            val activeUiState = uiState as ShortcutsUiState.Active
-            assertThat(activeUiState.defaultSelectedCategory).isEqualTo(System)
-        }
-
-    @Test
-    fun shortcutsUiState_currentAppCategoryPresent_currentAppIsDefaultSelected() =
-        testScope.runTest {
-            val uiState by collectLastValue(viewModel.shortcutsUiState)
-
-            testHelper.showFromActivity()
-
-            val activeUiState = uiState as ShortcutsUiState.Active
-            assertThat(activeUiState.defaultSelectedCategory)
-                .isEqualTo(CurrentApp(TestShortcuts.currentAppPackageName))
-        }
-
-    @Test
-    fun shortcutsUiState_currentAppIsLauncher_defaultSelectedCategoryIsSystem() =
-        testScope.runTest {
-            whenever(
-                    mockRoleManager.getRoleHoldersAsUser(
-                        RoleManager.ROLE_HOME,
-                        fakeUserTracker.userHandle,
-                    )
-                )
-                .thenReturn(listOf(TestShortcuts.currentAppPackageName))
-            val uiState by collectLastValue(viewModel.shortcutsUiState)
-
-            testHelper.showFromActivity()
-
-            val activeUiState = uiState as ShortcutsUiState.Active
-            assertThat(activeUiState.defaultSelectedCategory).isEqualTo(System)
-        }
-
-    @Test
-    fun shortcutsUiState_userTypedQuery_filtersMatchingShortcutLabels() =
-        testScope.runTest {
-            fakeSystemSource.setGroups(
-                groupWithShortcutLabels("first Foo shortcut1", "first bar shortcut1"),
-                groupWithShortcutLabels(
-                    "second foO shortcut2",
-                    "second bar shortcut2",
-                    groupLabel = SECOND_SIMPLE_GROUP_LABEL,
-                ),
-            )
-            fakeMultiTaskingSource.setGroups(
-                groupWithShortcutLabels("third FoO shortcut1", "third bar shortcut1")
-            )
-            val uiState by collectLastValue(viewModel.shortcutsUiState)
-
-            testHelper.showFromActivity()
-            viewModel.onSearchQueryChanged("foo")
-
-            val activeUiState = uiState as ShortcutsUiState.Active
-            assertThat(activeUiState.shortcutCategories)
-                .containsExactly(
-                    ShortcutCategoryUi(
-                        label = "System",
-                        iconSource = IconSource(imageVector = Icons.Default.Tv),
-                        shortcutCategory =
-                            ShortcutCategory(
-                                System,
-                                subCategoryWithShortcutLabels("first Foo shortcut1"),
-                                subCategoryWithShortcutLabels(
-                                    "second foO shortcut2",
-                                    subCategoryLabel = SECOND_SIMPLE_GROUP_LABEL,
-                                ),
-                            ),
-                    ),
-                    ShortcutCategoryUi(
-                        label = "Multitasking",
-                        iconSource = IconSource(imageVector = Icons.Default.VerticalSplit),
-                        shortcutCategory =
-                            ShortcutCategory(
-                                MultiTasking,
-                                subCategoryWithShortcutLabels("third FoO shortcut1"),
-                            ),
-                    ),
-                )
-        }
-
-    @Test
-    fun shortcutsUiState_userTypedQuery_noMatch_returnsEmptyList() =
-        testScope.runTest {
-            fakeSystemSource.setGroups(
-                groupWithShortcutLabels("first Foo shortcut1", "first bar shortcut1"),
-                groupWithShortcutLabels("second foO shortcut2", "second bar shortcut2"),
-            )
-            fakeMultiTaskingSource.setGroups(
-                groupWithShortcutLabels("third FoO shortcut1", "third bar shortcut1")
-            )
-            val uiState by collectLastValue(viewModel.shortcutsUiState)
-
-            testHelper.showFromActivity()
-            viewModel.onSearchQueryChanged("unmatched query")
-
-            val activeUiState = uiState as ShortcutsUiState.Active
-            assertThat(activeUiState.shortcutCategories).isEmpty()
-        }
-
-    @Test
-    fun shortcutsUiState_userTypedQuery_noMatch_returnsNullDefaultSelectedCategory() =
-        testScope.runTest {
-            fakeSystemSource.setGroups(
-                groupWithShortcutLabels("first Foo shortcut1", "first bar shortcut1"),
-                groupWithShortcutLabels("second foO shortcut2", "second bar shortcut2"),
-            )
-            fakeMultiTaskingSource.setGroups(
-                groupWithShortcutLabels("third FoO shortcut1", "third bar shortcut1")
-            )
-            val uiState by collectLastValue(viewModel.shortcutsUiState)
-
-            testHelper.showFromActivity()
-            viewModel.onSearchQueryChanged("unmatched query")
-
-            val activeUiState = uiState as ShortcutsUiState.Active
-            assertThat(activeUiState.defaultSelectedCategory).isNull()
-        }
-
-    @Test
-    fun shortcutsUiState_userTypedQuery_changesDefaultSelectedCategoryToFirstMatchingCategory() =
-        testScope.runTest {
-            fakeSystemSource.setGroups(groupWithShortcutLabels("first shortcut"))
-            fakeMultiTaskingSource.setGroups(groupWithShortcutLabels("second shortcut"))
-            val uiState by collectLastValue(viewModel.shortcutsUiState)
-
-            testHelper.showFromActivity()
-            viewModel.onSearchQueryChanged("second")
-
-            val activeUiState = uiState as ShortcutsUiState.Active
-            assertThat(activeUiState.defaultSelectedCategory).isEqualTo(MultiTasking)
-        }
-
-    @Test
-    fun shortcutsUiState_userTypedQuery_multipleCategoriesMatch_currentAppIsDefaultSelected() =
-        testScope.runTest {
-            fakeSystemSource.setGroups(groupWithShortcutLabels("first shortcut"))
-            fakeMultiTaskingSource.setGroups(groupWithShortcutLabels("second shortcut"))
-            fakeCurrentAppsSource.setGroups(groupWithShortcutLabels("third shortcut"))
-            val uiState by collectLastValue(viewModel.shortcutsUiState)
-
-            testHelper.showFromActivity()
-            viewModel.onSearchQueryChanged("shortcut")
-
-            val activeUiState = uiState as ShortcutsUiState.Active
-            assertThat(activeUiState.defaultSelectedCategory).isInstanceOf(CurrentApp::class.java)
-        }
-
-    private fun groupWithShortcutLabels(
-        vararg shortcutLabels: String,
-        groupLabel: String = FIRST_SIMPLE_GROUP_LABEL,
-    ) =
-        KeyboardShortcutGroup(groupLabel, shortcutLabels.map { simpleShortcutInfo(it) }).apply {
-            packageName = "test.package.name"
-        }
-
-    private fun simpleShortcutInfo(label: String) =
-        KeyboardShortcutInfo(label, KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON)
-
-    private fun subCategoryWithShortcutLabels(
-        vararg shortcutLabels: String,
-        subCategoryLabel: String = FIRST_SIMPLE_GROUP_LABEL,
-    ) =
-        ShortcutSubCategory(
-            label = subCategoryLabel,
-            shortcuts = shortcutLabels.map { simpleShortcut(it) },
-        )
-
-    private fun simpleShortcut(label: String) =
-        shortcut(label) {
-            command {
-                key("Ctrl")
-                key("A")
-            }
-        }
-
-    companion object {
-        private const val FIRST_SIMPLE_GROUP_LABEL = "simple group 1"
-        private const val SECOND_SIMPLE_GROUP_LABEL = "simple group 2"
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 929b0aa..67e03e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -273,6 +273,12 @@
                     "${Contract.AUTHORITY}." +
                     Contract.FlagsTable.TABLE_NAME
             )
+        assertThat(underTest.getType(Contract.RuntimeValuesTable.URI))
+            .isEqualTo(
+                "vnd.android.cursor.dir/vnd." +
+                    "${Contract.AUTHORITY}." +
+                    Contract.RuntimeValuesTable.TABLE_NAME
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index b3cccea..194f456 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -277,7 +277,9 @@
                 () -> mSelectedUserInteractor,
                 mUserTracker,
                 mKosmos.getNotificationShadeWindowModel(),
-                mKosmos::getCommunalInteractor);
+                mSecureSettings,
+                mKosmos::getCommunalInteractor,
+                mKosmos.getShadeLayoutParams());
         mFeatureFlags = new FakeFeatureFlags();
         mSetFlagsRule.disableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR);
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index bf4ef50..7ba797c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -59,6 +59,7 @@
 import android.os.PowerExemptionManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.service.notification.StatusBarNotification;
 import android.testing.TestableLooper;
@@ -242,10 +243,14 @@
         mLocalMediaManager = spy(mMediaSwitchingController.mLocalMediaManager);
         when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
         mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager;
+
         mMediaSwitchingController.mInputRouteManager =
                 new InputRouteManager(mContext, mAudioManager);
         mInputRouteManager = spy(mMediaSwitchingController.mInputRouteManager);
         mMediaSwitchingController.mInputRouteManager = mInputRouteManager;
+        when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
+                .thenReturn(new AudioDeviceInfo[0]);
+
         MediaDescription.Builder builder = new MediaDescription.Builder();
         builder.setTitle(TEST_SONG);
         builder.setSubtitle(TEST_ARTIST);
@@ -483,11 +488,11 @@
         verify(mMediaDevice2, never()).setRangeZone(anyInt());
     }
 
+    @DisableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
     @Test
     public void onDeviceListUpdate_verifyDeviceListCallback() {
         // This test relies on mMediaSwitchingController.start being called while the selected
-        // device
-        // list has exactly one item, and that item's id is:
+        // device list has exactly one item, and that item's id is:
         // - Different from both ids in mMediaDevices.
         // - Different from the id of the route published by the device under test (usually the
         //   built-in speakers).
@@ -511,16 +516,54 @@
 
         assertThat(devices.containsAll(mMediaDevices)).isTrue();
         assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+        // There should be 2 non-MediaDevice items: the "Speakers & Display" title, and the "Connect
+        // a device" button.
         assertThat(mMediaSwitchingController.getMediaItemList().size())
                 .isEqualTo(mMediaDevices.size() + 2);
         verify(mCb).onDeviceListChanged();
     }
 
+    @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+    @Test
+    public void onDeviceListUpdate_verifyDeviceListCallback_inputRouting() {
+        // This test relies on mMediaSwitchingController.start being called while the selected
+        // device list has exactly one item, and that item's id is:
+        // - Different from both ids in mMediaDevices.
+        // - Different from the id of the route published by the device under test (usually the
+        //   built-in speakers).
+        // So mock the selected device to respect these two preconditions.
+        MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
+        when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
+        doReturn(List.of(mockSelectedMediaDevice))
+                .when(mLocalMediaManager)
+                .getSelectedMediaDevice();
+
+        mMediaSwitchingController.start(mCb);
+        reset(mCb);
+
+        mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+        final List<MediaDevice> devices = new ArrayList<>();
+        for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
+            if (item.getMediaDevice().isPresent()) {
+                devices.add(item.getMediaDevice().get());
+            }
+        }
+
+        assertThat(devices.containsAll(mMediaDevices)).isTrue();
+        assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+        // When input routing is enabled, there should be 4 non-MediaDevice items: one for
+        // the "Output" title, one for the "Speakers & Displays" title, one for the "Connect a
+        // device" button, and one for the "Input" title.
+        assertThat(mMediaSwitchingController.getMediaItemList().size())
+                .isEqualTo(mMediaDevices.size() + 4);
+        verify(mCb).onDeviceListChanged();
+    }
+
+    @DisableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
     @Test
     public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize() {
         // This test relies on mMediaSwitchingController.start being called while the selected
-        // device
-        // list has exactly one item, and that item's id is:
+        // device list has exactly one item, and that item's id is:
         // - Different from both ids in mMediaDevices.
         // - Different from the id of the route published by the device under test (usually the
         //   built-in speakers).
@@ -547,6 +590,7 @@
 
         assertThat(devices.containsAll(mMediaDevices)).isTrue();
         assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+        // There should be 1 non-MediaDevice item: the "Speakers & Display" title.
         assertThat(mMediaSwitchingController.getMediaItemList().size())
                 .isEqualTo(mMediaDevices.size() + 1);
         verify(mCb).onDeviceListChanged();
@@ -554,6 +598,45 @@
 
     @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
     @Test
+    public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize_inputRouting() {
+        // This test relies on mMediaSwitchingController.start being called while the selected
+        // device list has exactly one item, and that item's id is:
+        // - Different from both ids in mMediaDevices.
+        // - Different from the id of the route published by the device under test (usually the
+        //   built-in speakers).
+        // So mock the selected device to respect these two preconditions.
+        MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
+        when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
+        doReturn(List.of(mockSelectedMediaDevice))
+                .when(mLocalMediaManager)
+                .getSelectedMediaDevice();
+
+        when(mMediaDevice1.getFeatures())
+                .thenReturn(ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
+        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
+        mMediaSwitchingController.start(mCb);
+        reset(mCb);
+
+        mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+        final List<MediaDevice> devices = new ArrayList<>();
+        for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
+            if (item.getMediaDevice().isPresent()) {
+                devices.add(item.getMediaDevice().get());
+            }
+        }
+
+        assertThat(devices.containsAll(mMediaDevices)).isTrue();
+        assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+        // When input routing is enabled, there should be 3 non-MediaDevice items: one for
+        // the "Output" title, one for the "Speakers & Displays" title, and one for the "Input"
+        // title.
+        assertThat(mMediaSwitchingController.getMediaItemList().size())
+                .isEqualTo(mMediaDevices.size() + 3);
+        verify(mCb).onDeviceListChanged();
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+    @Test
     public void onInputDeviceListUpdate_verifyDeviceListCallback() {
         AudioDeviceInfo[] audioDeviceInfos = {};
         when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
@@ -1384,4 +1467,66 @@
         verify(mInputRouteManager, never()).selectDevice(outputMediaDevice);
         verify(mLocalMediaManager, atLeastOnce()).connectDevice(outputMediaDevice);
     }
+
+    @DisableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+    @Test
+    public void connectDeviceButton_presentAtAllTimesForNonGroupOutputs() {
+        mMediaSwitchingController.start(mCb);
+        reset(mCb);
+
+        // Mock the selected output device.
+        doReturn(Collections.singletonList(mMediaDevice1))
+                .when(mLocalMediaManager)
+                .getSelectedMediaDevice();
+
+        // Verify that there is initially one "Connect a device" button present.
+        assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
+
+        // Change the selected device, and verify that there is still one "Connect a device" button
+        // present.
+        doReturn(Collections.singletonList(mMediaDevice2))
+                .when(mLocalMediaManager)
+                .getSelectedMediaDevice();
+        mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+        assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+    @Test
+    public void connectDeviceButton_presentAtAllTimesForNonGroupOutputs_inputRoutingEnabled() {
+        mMediaSwitchingController.start(mCb);
+        reset(mCb);
+
+        // Mock the selected output device.
+        doReturn(Collections.singletonList(mMediaDevice1))
+                .when(mLocalMediaManager)
+                .getSelectedMediaDevice();
+
+        // Mock the selected input media device.
+        final MediaDevice selectedInputMediaDevice = mock(MediaDevice.class);
+        doReturn(selectedInputMediaDevice).when(mInputRouteManager).getSelectedInputDevice();
+
+        // Verify that there is initially one "Connect a device" button present.
+        assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
+
+        // Change the selected device, and verify that there is still one "Connect a device" button
+        // present.
+        doReturn(Collections.singletonList(mMediaDevice2))
+                .when(mLocalMediaManager)
+                .getSelectedMediaDevice();
+        mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+        assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
+    }
+
+    private int getNumberOfConnectDeviceButtons() {
+        int numberOfConnectDeviceButtons = 0;
+        for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
+            if (item.getMediaItemType() == MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE) {
+                numberOfConnectDeviceButtons++;
+            }
+        }
+        return numberOfConnectDeviceButtons;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index bc3c0d9..d59a404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -49,6 +49,7 @@
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.views.NavigationBar;
 import com.android.systemui.recents.OverviewProxyService;
@@ -56,7 +57,7 @@
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.AutoHideControllerStore;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -89,6 +90,11 @@
 
     private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+
+    private final AutoHideControllerStore mAutoHideControllerStore =
+            mKosmos.getAutoHideControllerStore();
+
     @Mock
     private CommandQueue mCommandQueue;
     @Mock
@@ -113,7 +119,7 @@
                         mTaskbarDelegate,
                         mNavigationBarFactory,
                         mock(DumpManager.class),
-                        mock(AutoHideController.class),
+                        mAutoHideControllerStore,
                         mock(LightBarController.class),
                         TaskStackChangeListeners.getTestInstance(),
                         Optional.of(mock(Pip.class)),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index d88b758..266cb51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -181,11 +181,9 @@
 
     @Test
     @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
-    fun handlesShortcut_metaCtrlN() {
+    fun handlesShortcut_keyGestureTypeOpenNotes() {
         val gestureEvent =
             KeyGestureEvent.Builder()
-                .setKeycodes(intArrayOf(KeyEvent.KEYCODE_N))
-                .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
                 .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES)
                 .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
                 .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
index 2db5e83..d058484 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
@@ -28,13 +28,14 @@
 import android.widget.TextView
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.qs.QSTileView
+import com.android.systemui.res.R
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import java.util.Arrays
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -45,7 +46,6 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
-import java.util.Arrays
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -62,16 +62,13 @@
 
     private lateinit var dialog: TileRequestDialog
 
-    @Mock
-    private lateinit var ugm: IUriGrantsManager
+    @Mock private lateinit var ugm: IUriGrantsManager
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         // Create in looper so we can make sure that the tile is fully updated
-        TestableLooper.get(this).runWithLooper {
-            dialog = TileRequestDialog(mContext)
-        }
+        TestableLooper.get(this).runWithLooper { dialog = TileRequestDialog(mContext) }
     }
 
     @After
@@ -84,7 +81,7 @@
     @Test
     fun setTileData_hasCorrectViews() {
         val icon = Icon.createWithResource(mContext, R.drawable.cloud)
-        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+        val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
         dialog.setTileData(tileData, ugm)
         dialog.show()
@@ -99,7 +96,7 @@
     @Test
     fun setTileData_hasCorrectAppName() {
         val icon = Icon.createWithResource(mContext, R.drawable.cloud)
-        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+        val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
         dialog.setTileData(tileData, ugm)
         dialog.show()
@@ -112,7 +109,7 @@
     @Test
     fun setTileData_hasCorrectLabel() {
         val icon = Icon.createWithResource(mContext, R.drawable.cloud)
-        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+        val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
         dialog.setTileData(tileData, ugm)
         dialog.show()
@@ -127,7 +124,7 @@
     @Test
     fun setTileData_hasIcon() {
         val icon = Icon.createWithResource(mContext, R.drawable.cloud)
-        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+        val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
         dialog.setTileData(tileData, ugm)
         dialog.show()
@@ -141,7 +138,7 @@
 
     @Test
     fun setTileData_nullIcon_hasIcon() {
-        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, null, PACKAGE)
+        val tileData = TileData(UID, APP_NAME, LABEL, null, PACKAGE)
 
         dialog.setTileData(tileData, ugm)
         dialog.show()
@@ -156,7 +153,7 @@
     @Test
     fun setTileData_hasNoStateDescription() {
         val icon = Icon.createWithResource(mContext, R.drawable.cloud)
-        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+        val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
         dialog.setTileData(tileData, ugm)
         dialog.show()
@@ -172,7 +169,7 @@
     @Test
     fun setTileData_tileNotClickable() {
         val icon = Icon.createWithResource(mContext, R.drawable.cloud)
-        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+        val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
         dialog.setTileData(tileData, ugm)
         dialog.show()
@@ -189,7 +186,7 @@
     @Test
     fun setTileData_tileHasCorrectContentDescription() {
         val icon = Icon.createWithResource(mContext, R.drawable.cloud)
-        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+        val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
         dialog.setTileData(tileData, ugm)
         dialog.show()
@@ -206,20 +203,14 @@
     fun uriIconLoadSuccess_correctIcon() {
         val tintColor = Color.BLACK
         val icon = Mockito.mock(Icon::class.java)
-        val drawable = context.getDrawable(R.drawable.cloud)!!.apply {
-            setTint(tintColor)
-        }
+        val drawable = context.getDrawable(R.drawable.cloud)!!.apply { setTint(tintColor) }
         whenever(icon.loadDrawable(any())).thenReturn(drawable)
-        whenever(icon.loadDrawableCheckingUriGrant(
-            any(),
-            eq(ugm),
-            anyInt(),
-            anyString())
-        ).thenReturn(drawable)
+        whenever(icon.loadDrawableCheckingUriGrant(any(), eq(ugm), anyInt(), anyString()))
+            .thenReturn(drawable)
 
         val size = 100
 
-        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+        val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
         dialog.setTileData(tileData, ugm)
         dialog.show()
@@ -231,9 +222,7 @@
         val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
         val tile = content.getChildAt(1) as QSTileView
 
-        val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply {
-            setTint(tintColor)
-        }
+        val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply { setTint(tintColor) }
 
         assertThat(areDrawablesEqual(iconDrawable, drawable, size)).isTrue()
     }
@@ -242,20 +231,14 @@
     fun uriIconLoadFail_defaultIcon() {
         val tintColor = Color.BLACK
         val icon = Mockito.mock(Icon::class.java)
-        val drawable = context.getDrawable(R.drawable.cloud)!!.apply {
-            setTint(tintColor)
-        }
+        val drawable = context.getDrawable(R.drawable.cloud)!!.apply { setTint(tintColor) }
         whenever(icon.loadDrawable(any())).thenReturn(drawable)
-        whenever(icon.loadDrawableCheckingUriGrant(
-            any(),
-            eq(ugm),
-            anyInt(),
-            anyString())
-        ).thenReturn(null)
+        whenever(icon.loadDrawableCheckingUriGrant(any(), eq(ugm), anyInt(), anyString()))
+            .thenReturn(null)
 
         val size = 100
 
-        val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
+        val tileData = TileData(UID, APP_NAME, LABEL, icon, PACKAGE)
 
         dialog.setTileData(tileData, ugm)
         dialog.show()
@@ -267,13 +250,9 @@
         val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
         val tile = content.getChildAt(1) as QSTileView
 
-        val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply {
-            setTint(tintColor)
-        }
+        val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply { setTint(tintColor) }
 
-        val defaultIcon = context.getDrawable(DEFAULT_ICON)!!.apply {
-            setTint(tintColor)
-        }
+        val defaultIcon = context.getDrawable(DEFAULT_ICON)!!.apply { setTint(tintColor) }
 
         assertThat(areDrawablesEqual(iconDrawable, defaultIcon, size)).isTrue()
     }
@@ -308,4 +287,3 @@
     b.getPixels(bPix, 0, w, 0, 0, w, h)
     return Arrays.equals(aPix, bPix)
 }
-
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
deleted file mode 100644
index 89ec687..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt
+++ /dev/null
@@ -1,471 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.external
-
-import android.app.IUriGrantsManager
-import android.app.StatusBarManager
-import android.content.ComponentName
-import android.content.DialogInterface
-import android.graphics.drawable.Icon
-import android.os.RemoteException
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.InstanceId
-import com.android.internal.statusbar.IAddTileResultCallback
-import com.android.systemui.InstanceIdSequenceFake
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.qs.QSHost
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.commandline.CommandRegistry
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
-import com.google.common.truth.Truth.assertThat
-import java.util.function.Consumer
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyString
-import org.mockito.Mockito.atLeastOnce
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class TileServiceRequestControllerTest : SysuiTestCase() {
-
-    companion object {
-        private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls")
-        private const val TEST_APP_NAME = "App"
-        private const val TEST_LABEL = "Label"
-        private const val TEST_UID = 12345
-    }
-
-    @Mock
-    private lateinit var tileRequestDialog: TileRequestDialog
-    @Mock
-    private lateinit var qsHost: QSHost
-    @Mock
-    private lateinit var commandRegistry: CommandRegistry
-    @Mock
-    private lateinit var commandQueue: CommandQueue
-    @Mock
-    private lateinit var logger: TileRequestDialogEventLogger
-    @Mock
-    private lateinit var icon: Icon
-    @Mock
-    private lateinit var ugm: IUriGrantsManager
-
-    private val instanceIdSequence = InstanceIdSequenceFake(1_000)
-    private lateinit var controller: TileServiceRequestController
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        `when`(logger.newInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
-
-        // Tile not present by default
-        `when`(qsHost.indexOf(anyString())).thenReturn(-1)
-
-        controller = TileServiceRequestController(
-                qsHost,
-                commandQueue,
-                commandRegistry,
-                logger,
-                ugm,
-        ) {
-            tileRequestDialog
-        }
-
-        controller.init()
-    }
-
-    @Test
-    fun requestTileAdd_dataIsPassedToDialog() {
-        controller.requestTileAdd(
-                TEST_UID,
-                TEST_COMPONENT,
-                TEST_APP_NAME,
-                TEST_LABEL,
-                icon,
-                Callback(),
-        )
-
-        verify(tileRequestDialog).setTileData(
-                TileRequestDialog.TileData(
-                        TEST_UID,
-                        TEST_APP_NAME,
-                        TEST_LABEL,
-                        icon,
-                        TEST_COMPONENT.packageName,
-                ),
-                ugm,
-        )
-    }
-
-    @Test
-    fun tileAlreadyAdded_correctResult() {
-        `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
-
-        val callback = Callback()
-        controller.requestTileAdd(
-                TEST_UID,
-                TEST_COMPONENT,
-                TEST_APP_NAME,
-                TEST_LABEL,
-                icon,
-                callback,
-        )
-
-        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
-        verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
-    }
-
-    @Test
-    fun tileAlreadyAdded_logged() {
-        `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
-
-        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
-
-        verify(logger).logTileAlreadyAdded(eq<String>(TEST_COMPONENT.packageName), any())
-        verify(logger, never()).logDialogShown(anyString(), any())
-        verify(logger, never()).logUserResponse(anyInt(), anyString(), any())
-    }
-
-    @Test
-    fun showAllUsers_set() {
-        controller.requestTileAdd(
-                TEST_UID,
-                TEST_COMPONENT,
-                TEST_APP_NAME,
-                TEST_LABEL,
-                icon,
-                Callback(),
-        )
-        verify(tileRequestDialog).setShowForAllUsers(true)
-    }
-
-    @Test
-    fun cancelOnTouchOutside_set() {
-        controller.requestTileAdd(
-                TEST_UID,
-                TEST_COMPONENT,
-                TEST_APP_NAME,
-                TEST_LABEL,
-                icon,
-                Callback(),
-        )
-        verify(tileRequestDialog).setCanceledOnTouchOutside(true)
-    }
-
-    @Test
-    fun dialogShown_logged() {
-        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
-
-        verify(logger).logDialogShown(eq<String>(TEST_COMPONENT.packageName), any())
-    }
-
-    @Test
-    fun cancelListener_dismissResult() {
-        val cancelListenerCaptor =
-                ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
-
-        val callback = Callback()
-        controller.requestTileAdd(
-                TEST_UID,
-                TEST_COMPONENT,
-                TEST_APP_NAME,
-                TEST_LABEL,
-                icon,
-                callback,
-        )
-        verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
-
-        cancelListenerCaptor.value.onCancel(tileRequestDialog)
-        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED)
-        verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
-    }
-
-    @Test
-    fun dialogCancelled_logged() {
-        val cancelListenerCaptor =
-                ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
-
-        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
-        val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
-
-        verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
-        verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
-
-        cancelListenerCaptor.value.onCancel(tileRequestDialog)
-        verify(logger).logUserResponse(
-                StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED,
-                TEST_COMPONENT.packageName,
-                instanceId
-        )
-    }
-
-    @Test
-    fun positiveActionListener_tileAddedResult() {
-        val clickListenerCaptor =
-                ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
-
-        val callback = Callback()
-        controller.requestTileAdd(
-                TEST_UID,
-                TEST_COMPONENT,
-                TEST_APP_NAME,
-                TEST_LABEL,
-                icon,
-                callback,
-        )
-        verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
-
-        clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
-
-        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.ADD_TILE)
-        verify(qsHost).addTile(TEST_COMPONENT, /* end */ true)
-    }
-
-    @Test
-    fun tileAdded_logged() {
-        val clickListenerCaptor =
-                ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
-
-        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
-        val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
-
-        verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
-        verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
-
-        clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
-        verify(logger).logUserResponse(
-                StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED,
-                TEST_COMPONENT.packageName,
-                instanceId
-        )
-    }
-
-    @Test
-    fun negativeActionListener_tileNotAddedResult() {
-        val clickListenerCaptor =
-                ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
-
-        val callback = Callback()
-        controller.requestTileAdd(
-                TEST_UID,
-                TEST_COMPONENT,
-                TEST_APP_NAME,
-                TEST_LABEL,
-                icon,
-                callback,
-        )
-        verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor))
-
-        clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE)
-
-        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DONT_ADD_TILE)
-        verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
-    }
-
-    @Test
-    fun tileNotAdded_logged() {
-        val clickListenerCaptor =
-                ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
-
-        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
-        val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
-
-        verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor))
-        verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
-
-        clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE)
-        verify(logger).logUserResponse(
-                StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
-                TEST_COMPONENT.packageName,
-                instanceId
-        )
-    }
-
-    @Test
-    fun commandQueueCallback_registered() {
-        verify(commandQueue).addCallback(any())
-    }
-
-    @Test
-    fun commandQueueCallback_dataPassedToDialog() {
-        val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
-        verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
-
-        captor.value.requestAddTile(
-                TEST_UID,
-                TEST_COMPONENT,
-                TEST_APP_NAME,
-                TEST_LABEL,
-                icon,
-                Callback(),
-        )
-
-        verify(tileRequestDialog).setTileData(
-                TileRequestDialog.TileData(
-                        TEST_UID,
-                        TEST_APP_NAME,
-                        TEST_LABEL,
-                        icon,
-                        TEST_COMPONENT.packageName,
-                ),
-                ugm,
-        )
-    }
-
-    @Test
-    fun commandQueueCallback_callbackCalled() {
-        `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
-        val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
-        verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
-        val c = Callback()
-
-        captor.value.requestAddTile(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, c)
-
-        assertThat(c.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
-    }
-
-    @Test
-    fun interfaceThrowsRemoteException_doesntCrash() {
-        val cancelListenerCaptor =
-                ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
-        val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
-        verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
-
-        val callback = object : IAddTileResultCallback.Stub() {
-            override fun onTileRequest(p0: Int) {
-                throw RemoteException()
-            }
-        }
-        captor.value.requestAddTile(
-                TEST_UID,
-                TEST_COMPONENT,
-                TEST_APP_NAME,
-                TEST_LABEL,
-                icon,
-                callback,
-        )
-        verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
-
-        cancelListenerCaptor.value.onCancel(tileRequestDialog)
-    }
-
-    @Test
-    fun testDismissDialogResponse() {
-        val dismissListenerCaptor =
-            ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
-
-        val callback = Callback()
-        controller.requestTileAdd(
-                TEST_UID,
-                TEST_COMPONENT,
-                TEST_APP_NAME,
-                TEST_LABEL,
-                icon,
-                callback,
-        )
-        verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
-
-        dismissListenerCaptor.value.onDismiss(tileRequestDialog)
-        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED)
-    }
-
-    @Test
-    fun addTileAndThenDismissSendsOnlyAddTile() {
-        // After clicking, the dialog is dismissed. This tests that only one response
-        // is sent (the first one)
-        val dismissListenerCaptor =
-            ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
-        val clickListenerCaptor =
-            ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
-
-        val callback = Callback()
-        controller.requestTileAdd(
-                TEST_UID,
-                TEST_COMPONENT,
-                TEST_APP_NAME,
-                TEST_LABEL,
-                icon,
-                callback,
-        )
-        verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
-        verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
-
-        clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
-        dismissListenerCaptor.value.onDismiss(tileRequestDialog)
-
-        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.ADD_TILE)
-        assertThat(callback.timesCalled).isEqualTo(1)
-    }
-
-    @Test
-    fun cancelAndThenDismissSendsOnlyOnce() {
-        // After cancelling, the dialog is dismissed. This tests that only one response
-        // is sent.
-        val dismissListenerCaptor =
-            ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
-        val cancelListenerCaptor =
-            ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
-
-        val callback = Callback()
-        controller.requestTileAdd(
-                TEST_UID,
-                TEST_COMPONENT,
-                TEST_APP_NAME,
-                TEST_LABEL,
-                icon,
-                callback,
-        )
-        verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
-        verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
-
-        cancelListenerCaptor.value.onCancel(tileRequestDialog)
-        dismissListenerCaptor.value.onDismiss(tileRequestDialog)
-
-        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED)
-        assertThat(callback.timesCalled).isEqualTo(1)
-    }
-
-    private class Callback : IAddTileResultCallback.Stub(), Consumer<Int> {
-        var lastAccepted: Int? = null
-            private set
-
-        var timesCalled = 0
-            private set
-
-        override fun accept(t: Int) {
-            lastAccepted = t
-            timesCalled++
-        }
-
-        override fun onTileRequest(r: Int) {
-            accept(r)
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOff.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOff.kt
new file mode 100644
index 0000000..82e24771
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTestComposeOff.kt
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import android.app.IUriGrantsManager
+import android.app.StatusBarManager
+import android.content.ComponentName
+import android.content.DialogInterface
+import android.graphics.drawable.Icon
+import android.os.RemoteException
+import android.platform.test.annotations.DisableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
+import com.android.internal.statusbar.IAddTileResultCallback
+import com.android.systemui.InstanceIdSequenceFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.external.ui.dialog.tileRequestDialogComposeDelegateFactory
+import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@DisableFlags(value = [QSComposeFragment.FLAG_NAME, DualShade.FLAG_NAME])
+class TileServiceRequestControllerTestComposeOff : SysuiTestCase() {
+
+    companion object {
+        private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls")
+        private const val TEST_APP_NAME = "App"
+        private const val TEST_LABEL = "Label"
+        private const val TEST_UID = 12345
+    }
+
+    private val kosmos = testKosmos()
+
+    @Mock private lateinit var tileRequestDialog: TileRequestDialog
+    @Mock private lateinit var qsHost: QSHost
+    @Mock private lateinit var commandRegistry: CommandRegistry
+    @Mock private lateinit var commandQueue: CommandQueue
+    @Mock private lateinit var logger: TileRequestDialogEventLogger
+    @Mock private lateinit var icon: Icon
+    @Mock private lateinit var ugm: IUriGrantsManager
+
+    private val instanceIdSequence = InstanceIdSequenceFake(1_000)
+    private lateinit var controller: TileServiceRequestController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        `when`(logger.newInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
+
+        // Tile not present by default
+        `when`(qsHost.indexOf(anyString())).thenReturn(-1)
+
+        controller =
+            TileServiceRequestController(
+                qsHost,
+                commandQueue,
+                commandRegistry,
+                logger,
+                ugm,
+                kosmos.tileRequestDialogComposeDelegateFactory,
+            ) {
+                tileRequestDialog
+            }
+
+        controller.init()
+    }
+
+    @Test
+    fun requestTileAdd_dataIsPassedToDialog() {
+        controller.requestTileAdd(
+            TEST_UID,
+            TEST_COMPONENT,
+            TEST_APP_NAME,
+            TEST_LABEL,
+            icon,
+            Callback(),
+        )
+
+        verify(tileRequestDialog)
+            .setTileData(
+                TileData(TEST_UID, TEST_APP_NAME, TEST_LABEL, icon, TEST_COMPONENT.packageName),
+                ugm,
+            )
+    }
+
+    @Test
+    fun tileAlreadyAdded_correctResult() {
+        `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
+
+        val callback = Callback()
+        controller.requestTileAdd(
+            TEST_UID,
+            TEST_COMPONENT,
+            TEST_APP_NAME,
+            TEST_LABEL,
+            icon,
+            callback,
+        )
+
+        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
+        verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
+    }
+
+    @Test
+    fun tileAlreadyAdded_logged() {
+        `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
+
+        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+
+        verify(logger).logTileAlreadyAdded(eq<String>(TEST_COMPONENT.packageName), any())
+        verify(logger, never()).logDialogShown(anyString(), any())
+        verify(logger, never()).logUserResponse(anyInt(), anyString(), any())
+    }
+
+    @Test
+    fun showAllUsers_set() {
+        controller.requestTileAdd(
+            TEST_UID,
+            TEST_COMPONENT,
+            TEST_APP_NAME,
+            TEST_LABEL,
+            icon,
+            Callback(),
+        )
+        verify(tileRequestDialog).setShowForAllUsers(true)
+    }
+
+    @Test
+    fun cancelOnTouchOutside_set() {
+        controller.requestTileAdd(
+            TEST_UID,
+            TEST_COMPONENT,
+            TEST_APP_NAME,
+            TEST_LABEL,
+            icon,
+            Callback(),
+        )
+        verify(tileRequestDialog).setCanceledOnTouchOutside(true)
+    }
+
+    @Test
+    fun dialogShown_logged() {
+        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+
+        verify(logger).logDialogShown(eq<String>(TEST_COMPONENT.packageName), any())
+    }
+
+    @Test
+    fun cancelListener_dismissResult() {
+        val cancelListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+
+        val callback = Callback()
+        controller.requestTileAdd(
+            TEST_UID,
+            TEST_COMPONENT,
+            TEST_APP_NAME,
+            TEST_LABEL,
+            icon,
+            callback,
+        )
+        verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
+
+        cancelListenerCaptor.value.onCancel(tileRequestDialog)
+        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED)
+        verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
+    }
+
+    @Test
+    fun dialogCancelled_logged() {
+        val cancelListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+
+        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+        val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
+
+        verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
+        verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
+
+        cancelListenerCaptor.value.onCancel(tileRequestDialog)
+        verify(logger)
+            .logUserResponse(
+                StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED,
+                TEST_COMPONENT.packageName,
+                instanceId,
+            )
+    }
+
+    @Test
+    fun positiveActionListener_tileAddedResult() {
+        val clickListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+
+        val callback = Callback()
+        controller.requestTileAdd(
+            TEST_UID,
+            TEST_COMPONENT,
+            TEST_APP_NAME,
+            TEST_LABEL,
+            icon,
+            callback,
+        )
+        verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
+
+        clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
+
+        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.ADD_TILE)
+        verify(qsHost).addTile(TEST_COMPONENT, /* end */ true)
+    }
+
+    @Test
+    fun tileAdded_logged() {
+        val clickListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+
+        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+        val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
+
+        verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
+        verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
+
+        clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
+        verify(logger)
+            .logUserResponse(
+                StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED,
+                TEST_COMPONENT.packageName,
+                instanceId,
+            )
+    }
+
+    @Test
+    fun negativeActionListener_tileNotAddedResult() {
+        val clickListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+
+        val callback = Callback()
+        controller.requestTileAdd(
+            TEST_UID,
+            TEST_COMPONENT,
+            TEST_APP_NAME,
+            TEST_LABEL,
+            icon,
+            callback,
+        )
+        verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor))
+
+        clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE)
+
+        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DONT_ADD_TILE)
+        verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean())
+    }
+
+    @Test
+    fun tileNotAdded_logged() {
+        val clickListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+
+        controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {}
+        val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId)
+
+        verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor))
+        verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId)
+
+        clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE)
+        verify(logger)
+            .logUserResponse(
+                StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
+                TEST_COMPONENT.packageName,
+                instanceId,
+            )
+    }
+
+    @Test
+    fun commandQueueCallback_registered() {
+        verify(commandQueue).addCallback(any())
+    }
+
+    @Test
+    fun commandQueueCallback_dataPassedToDialog() {
+        val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
+        verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
+
+        captor.value.requestAddTile(
+            TEST_UID,
+            TEST_COMPONENT,
+            TEST_APP_NAME,
+            TEST_LABEL,
+            icon,
+            Callback(),
+        )
+
+        verify(tileRequestDialog)
+            .setTileData(
+                TileData(TEST_UID, TEST_APP_NAME, TEST_LABEL, icon, TEST_COMPONENT.packageName),
+                ugm,
+            )
+    }
+
+    @Test
+    fun commandQueueCallback_callbackCalled() {
+        `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2)
+        val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
+        verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
+        val c = Callback()
+
+        captor.value.requestAddTile(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, c)
+
+        assertThat(c.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED)
+    }
+
+    @Test
+    fun interfaceThrowsRemoteException_doesntCrash() {
+        val cancelListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+        val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
+        verify(commandQueue, atLeastOnce()).addCallback(capture(captor))
+
+        val callback =
+            object : IAddTileResultCallback.Stub() {
+                override fun onTileRequest(p0: Int) {
+                    throw RemoteException()
+                }
+            }
+        captor.value.requestAddTile(
+            TEST_UID,
+            TEST_COMPONENT,
+            TEST_APP_NAME,
+            TEST_LABEL,
+            icon,
+            callback,
+        )
+        verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
+
+        cancelListenerCaptor.value.onCancel(tileRequestDialog)
+    }
+
+    @Test
+    fun testDismissDialogResponse() {
+        val dismissListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
+
+        val callback = Callback()
+        controller.requestTileAdd(
+            TEST_UID,
+            TEST_COMPONENT,
+            TEST_APP_NAME,
+            TEST_LABEL,
+            icon,
+            callback,
+        )
+        verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
+
+        dismissListenerCaptor.value.onDismiss(tileRequestDialog)
+        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED)
+    }
+
+    @Test
+    fun addTileAndThenDismissSendsOnlyAddTile() {
+        // After clicking, the dialog is dismissed. This tests that only one response
+        // is sent (the first one)
+        val dismissListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
+        val clickListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java)
+
+        val callback = Callback()
+        controller.requestTileAdd(
+            TEST_UID,
+            TEST_COMPONENT,
+            TEST_APP_NAME,
+            TEST_LABEL,
+            icon,
+            callback,
+        )
+        verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor))
+        verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
+
+        clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE)
+        dismissListenerCaptor.value.onDismiss(tileRequestDialog)
+
+        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.ADD_TILE)
+        assertThat(callback.timesCalled).isEqualTo(1)
+    }
+
+    @Test
+    fun cancelAndThenDismissSendsOnlyOnce() {
+        // After cancelling, the dialog is dismissed. This tests that only one response
+        // is sent.
+        val dismissListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java)
+        val cancelListenerCaptor =
+            ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java)
+
+        val callback = Callback()
+        controller.requestTileAdd(
+            TEST_UID,
+            TEST_COMPONENT,
+            TEST_APP_NAME,
+            TEST_LABEL,
+            icon,
+            callback,
+        )
+        verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor))
+        verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor))
+
+        cancelListenerCaptor.value.onCancel(tileRequestDialog)
+        dismissListenerCaptor.value.onDismiss(tileRequestDialog)
+
+        assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED)
+        assertThat(callback.timesCalled).isEqualTo(1)
+    }
+
+    private class Callback : IAddTileResultCallback.Stub(), Consumer<Int> {
+        var lastAccepted: Int? = null
+            private set
+
+        var timesCalled = 0
+            private set
+
+        override fun accept(t: Int) {
+            lastAccepted = t
+            timesCalled++
+        }
+
+        override fun onTileRequest(r: Int) {
+            accept(r)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
index fac5ecb..f23553e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.click
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithContentDescription
 import androidx.compose.ui.test.performClick
@@ -73,7 +74,7 @@
     }
 
     @Test
-    fun toggleIconTile_shouldBeLarge() {
+    fun toggleIconTileWithA11yAction_shouldBeLarge() {
         var tiles by mutableStateOf(TestEditTiles)
         val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
         composeRule.setContent {
@@ -89,7 +90,7 @@
     }
 
     @Test
-    fun toggleLargeTile_shouldBeIcon() {
+    fun toggleLargeTileWithA11yAction_shouldBeIcon() {
         var tiles by mutableStateOf(TestEditTiles)
         val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
         composeRule.setContent {
@@ -105,7 +106,7 @@
     }
 
     @Test
-    fun resizedLarge_shouldBeIcon() {
+    fun tapOnIconResizingHandle_shouldBeLarge() {
         var tiles by mutableStateOf(TestEditTiles)
         val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
         composeRule.setContent {
@@ -116,12 +117,32 @@
         composeRule
             .onNodeWithContentDescription("tileA")
             .performClick() // Select
-            .performTouchInput { // Resize down
-                swipeRight()
+            .performTouchInput { // Tap on resizing handle
+                click(centerRight)
             }
         composeRule.waitForIdle()
 
-        assertThat(tiles.find { it.tile.tileSpec.spec == "tileA" }?.width).isEqualTo(1)
+        assertThat(tiles.find { it.tile.tileSpec.spec == "tileA" }?.width).isEqualTo(2)
+    }
+
+    @Test
+    fun tapOnLargeResizingHandle_shouldBeIcon() {
+        var tiles by mutableStateOf(TestEditTiles)
+        val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
+        composeRule.setContent {
+            EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
+        }
+        composeRule.waitForIdle()
+
+        composeRule
+            .onNodeWithContentDescription("tileD_large")
+            .performClick() // Select
+            .performTouchInput { // Tap on resizing handle
+                click(centerRight)
+            }
+        composeRule.waitForIdle()
+
+        assertThat(tiles.find { it.tile.tileSpec.spec == "tileD_large" }?.width).isEqualTo(1)
     }
 
     @Test
@@ -134,6 +155,26 @@
         composeRule.waitForIdle()
 
         composeRule
+            .onNodeWithContentDescription("tileA")
+            .performClick() // Select
+            .performTouchInput { // Resize up
+                swipeRight(startX = right, endX = right * 2)
+            }
+        composeRule.waitForIdle()
+
+        assertThat(tiles.find { it.tile.tileSpec.spec == "tileA" }?.width).isEqualTo(2)
+    }
+
+    @Test
+    fun resizedLarge_shouldBeIcon() {
+        var tiles by mutableStateOf(TestEditTiles)
+        val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
+        composeRule.setContent {
+            EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
+        }
+        composeRule.waitForIdle()
+
+        composeRule
             .onNodeWithContentDescription("tileD_large")
             .performClick() // Select
             .performTouchInput { // Resize down
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index afff485..a17f100 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -36,6 +36,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.media.projection.StopReason;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -154,7 +155,7 @@
         PendingIntent stopIntent = Mockito.mock(PendingIntent.class);
 
         mController.startCountdown(0, 0, startIntent, stopIntent);
-        mController.stopRecording();
+        mController.stopRecording(StopReason.STOP_UNKNOWN);
 
         assertFalse(mController.isStarting());
         assertFalse(mController.isRecording());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index f7059e2..a64ff321 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
 import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModelFactory
 import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.qs.flags.QsInCompose
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
@@ -70,8 +71,8 @@
         mSetFlagsRule.setFlagsParameterization(flags)
     }
 
-    val viewId by lazy {
-        if (QSComposeFragment.isEnabled) {
+    private val viewId by lazy {
+        if (QsInCompose.isEnabled) {
             R.id.brightness_dialog_slider
         } else {
             R.id.brightness_mirror_container
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 859f84e..e7fb470c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -33,6 +33,7 @@
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB_ON_MOBILE
 import com.android.systemui.Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.ambient.touch.TouchHandler
@@ -630,6 +631,7 @@
             }
         }
 
+    @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
     @Test
     fun onTouchEvent_shadeInteracting_movesNotDispatched() =
         with(kosmos) {
@@ -686,6 +688,7 @@
             }
         }
 
+    @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
     @Test
     fun onTouchEvent_bouncerInteracting_movesNotDispatched() =
         with(kosmos) {
@@ -718,6 +721,19 @@
             }
         }
 
+    @EnableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+    @Test
+    fun onTouchEvent_onLockscreenAndGlanceableHubV2_touchIgnored() =
+        with(kosmos) {
+            testScope.runTest {
+                // On lockscreen.
+                goToScene(CommunalScenes.Blank)
+
+                assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+                verify(containerView, never()).onTouchEvent(DOWN_EVENT)
+            }
+        }
+
     @Test
     fun disposeView_destroysTouchMonitor() {
         clearInvocations(touchMonitor)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
deleted file mode 100644
index da0029f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ /dev/null
@@ -1,1668 +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.statusbar;
-
-import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
-import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
-import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
-import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
-import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
-import static android.view.View.GONE;
-import static android.view.View.VISIBLE;
-
-import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
-import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
-import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ADAPTIVE_AUTH;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
-import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_OFF;
-import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_TURNING_ON;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-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.anyObject;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.Intent;
-import android.content.pm.UserInfo;
-import android.graphics.Color;
-import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricSourceType;
-import android.os.BatteryManager;
-import android.os.RemoteException;
-import android.testing.TestableLooper;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.TrustGrantFlags;
-import com.android.settingslib.fuelgauge.BatteryStatus;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.keyguard.KeyguardIndication;
-import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
-import com.android.systemui.res.R;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.text.NumberFormat;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class KeyguardIndicationControllerTest extends KeyguardIndicationControllerBaseTest {
-    @Test
-    public void afterFaceLockout_skipShowingFaceNotRecognized() {
-        createController();
-        onFaceLockoutError("lockout");
-        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "lockout");
-        clearInvocations(mRotateTextViewController);
-
-        // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED (face fail)
-        mKeyguardUpdateMonitorCallback.onBiometricHelp(
-                BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
-                "Face not recognized",
-                BiometricSourceType.FACE);
-        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); // no updated message
-    }
-
-    @Test
-    public void createController_setIndicationAreaAgain_destroysPreviousRotateTextViewController() {
-        // GIVEN a controller with a mocked rotate text view controlller
-        final KeyguardIndicationRotateTextViewController mockedRotateTextViewController =
-                mock(KeyguardIndicationRotateTextViewController.class);
-        createController();
-        mController.mRotateTextViewController = mockedRotateTextViewController;
-
-        // WHEN a new indication area is set
-        mController.setIndicationArea(mIndicationArea);
-
-        // THEN the previous rotateTextViewController is destroyed
-        verify(mockedRotateTextViewController).destroy();
-    }
-
-    @Test
-    public void createController_addsAlignmentListener() {
-        createController();
-
-        verify(mDockManager).addAlignmentStateListener(
-                any(DockManager.AlignmentStateListener.class));
-    }
-
-    @Test
-    public void onAlignmentStateChanged_showsSlowChargingIndication() {
-        createController();
-        verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
-        mController.setVisible(true);
-
-        mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_POOR);
-        mTestableLooper.processAllMessages();
-
-        verifyIndicationMessage(INDICATION_TYPE_ALIGNMENT,
-                mContext.getResources().getString(R.string.dock_alignment_slow_charging));
-        assertThat(mKeyguardIndicationCaptor.getValue().getTextColor().getDefaultColor())
-                .isEqualTo(mContext.getColor(R.color.misalignment_text_color));
-    }
-
-    @Test
-    public void onAlignmentStateChanged_showsNotChargingIndication() {
-        createController();
-        verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
-        mController.setVisible(true);
-
-        mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
-        mTestableLooper.processAllMessages();
-
-        verifyIndicationMessage(INDICATION_TYPE_ALIGNMENT,
-                mContext.getResources().getString(R.string.dock_alignment_not_charging));
-        assertThat(mKeyguardIndicationCaptor.getValue().getTextColor().getDefaultColor())
-                .isEqualTo(mContext.getColor(R.color.misalignment_text_color));
-    }
-
-    @FlakyTest(bugId = 279944472)
-    @Test
-    public void onAlignmentStateChanged_whileDozing_showsSlowChargingIndication() {
-        createController();
-        verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
-        mController.setVisible(true);
-        mStatusBarStateListener.onDozingChanged(true);
-
-        mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_POOR);
-        mTestableLooper.processAllMessages();
-
-        assertThat(mTextView.getText()).isEqualTo(
-                mContext.getResources().getString(R.string.dock_alignment_slow_charging));
-        assertThat(mTextView.getCurrentTextColor()).isEqualTo(
-                mContext.getColor(R.color.misalignment_text_color));
-    }
-
-    @Test
-    public void onAlignmentStateChanged_whileDozing_showsNotChargingIndication() {
-        createController();
-        verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
-        mController.setVisible(true);
-        mStatusBarStateListener.onDozingChanged(true);
-
-        mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
-        mTestableLooper.processAllMessages();
-
-        assertThat(mTextView.getText()).isEqualTo(
-                mContext.getResources().getString(R.string.dock_alignment_not_charging));
-        assertThat(mTextView.getCurrentTextColor()).isEqualTo(
-                mContext.getColor(R.color.misalignment_text_color));
-    }
-
-    @Test
-    public void disclosure_unmanaged() {
-        createController();
-        mController.setVisible(true);
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
-        when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(false);
-        reset(mRotateTextViewController);
-
-        sendUpdateDisclosureBroadcast();
-        mExecutor.runAllReady();
-
-        verifyHideIndication(INDICATION_TYPE_DISCLOSURE);
-    }
-
-    @Test
-    public void disclosure_deviceOwner_noOrganizationName() {
-        createController();
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
-        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
-        sendUpdateDisclosureBroadcast();
-        mController.setVisible(true);
-        mExecutor.runAllReady();
-
-        verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric);
-    }
-
-    @Test
-    public void disclosure_orgOwnedDeviceWithManagedProfile_noOrganizationName() {
-        createController();
-        mController.setVisible(true);
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
-        when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList(
-                new UserInfo(10, /* name */ null, /* flags */ FLAG_MANAGED_PROFILE)));
-        when(mDevicePolicyManager.getOrganizationNameForUser(eq(10))).thenReturn(null);
-        sendUpdateDisclosureBroadcast();
-        mExecutor.runAllReady();
-
-        verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric);
-    }
-
-    @Test
-    public void disclosure_deviceOwner_withOrganizationName() {
-        createController();
-        mController.setVisible(true);
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
-        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
-        sendUpdateDisclosureBroadcast();
-        mExecutor.runAllReady();
-
-        verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureWithOrganization);
-    }
-
-    @Test
-    public void disclosure_orgOwnedDeviceWithManagedProfile_withOrganizationName() {
-        createController();
-        mController.setVisible(true);
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true);
-        when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList(
-                new UserInfo(10, /* name */ null, FLAG_MANAGED_PROFILE)));
-        when(mDevicePolicyManager.getOrganizationNameForUser(eq(10))).thenReturn(ORGANIZATION_NAME);
-        sendUpdateDisclosureBroadcast();
-        mExecutor.runAllReady();
-
-        verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureWithOrganization);
-    }
-
-    @Test
-    public void disclosure_updateOnTheFly() {
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
-        createController();
-        mController.setVisible(true);
-
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
-        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
-        sendUpdateDisclosureBroadcast();
-        mExecutor.runAllReady();
-
-        verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric);
-        reset(mRotateTextViewController);
-
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
-        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
-        sendUpdateDisclosureBroadcast();
-        mExecutor.runAllReady();
-
-        verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureWithOrganization);
-        reset(mRotateTextViewController);
-
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
-        sendUpdateDisclosureBroadcast();
-        mExecutor.runAllReady();
-
-        verifyHideIndication(INDICATION_TYPE_DISCLOSURE);
-    }
-
-    @Test
-    public void disclosure_deviceOwner_financedDeviceWithOrganizationName() {
-        createController();
-        mController.setVisible(true);
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
-        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
-        when(mDevicePolicyManager.isFinancedDevice()).thenReturn(true);
-        // TODO(b/259908270): remove
-        when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
-                .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
-        sendUpdateDisclosureBroadcast();
-        mExecutor.runAllReady();
-        mController.setVisible(true);
-
-        verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mFinancedDisclosureWithOrganization);
-    }
-
-    @Test
-    public void transientIndication_holdsWakeLock_whenDozing() {
-        // GIVEN animations are enabled and text is visible
-        mTextView.setAnimationsEnabled(true);
-        createController();
-        mController.setVisible(true);
-
-        // WHEN transient text is shown
-        mStatusBarStateListener.onDozingChanged(true);
-        mController.showTransientIndication(TEST_STRING_RES);
-
-        // THEN wake lock is held while the animation is running
-        assertTrue("WakeLock expected: HELD, was: RELEASED", mWakeLock.isHeld());
-    }
-
-    @Test
-    public void transientIndication_releasesWakeLock_whenDozing() {
-        // GIVEN animations aren't enabled
-        mTextView.setAnimationsEnabled(false);
-        createController();
-        mController.setVisible(true);
-
-        // WHEN we show the transient indication
-        mStatusBarStateListener.onDozingChanged(true);
-        mController.showTransientIndication(TEST_STRING_RES);
-
-        // THEN wake lock is RELEASED, not held
-        assertFalse("WakeLock expected: RELEASED, was: HELD", mWakeLock.isHeld());
-    }
-
-    @Test
-    public void transientIndication_visibleWhenDozing() {
-        createController();
-        mController.setVisible(true);
-
-        mStatusBarStateListener.onDozingChanged(true);
-        mController.showTransientIndication(TEST_STRING_RES);
-
-        assertThat(mTextView.getText()).isEqualTo(
-                mContext.getResources().getString(TEST_STRING_RES));
-        assertThat(mTextView.getCurrentTextColor()).isEqualTo(Color.WHITE);
-        assertThat(mTextView.getAlpha()).isEqualTo(1f);
-    }
-
-    @Test
-    public void transientIndication_visibleWhenDozing_unlessSwipeUp_fromHelp() {
-        createController();
-        String message = "A message";
-
-        mController.setVisible(true);
-        mController.getKeyguardCallback().onBiometricHelp(
-                BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message,
-                BiometricSourceType.FACE);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message);
-        reset(mRotateTextViewController);
-        mStatusBarStateListener.onDozingChanged(true);
-
-        assertThat(mTextView.getText()).isNotEqualTo(message);
-    }
-
-    @Test
-    public void transientIndication_visibleWhenWokenUp() {
-        createController();
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        final String message = "helpMsg";
-
-        // GIVEN screen is off
-        when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_OFF);
-
-        // WHEN fingeprint help message received
-        mController.setVisible(true);
-        mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
-                message, BiometricSourceType.FINGERPRINT);
-
-        // THEN message isn't shown right away
-        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
-
-        // WHEN the screen turns on
-        mScreenObserver.onScreenTurnedOn();
-        mTestableLooper.processAllMessages();
-
-        // THEN the message is shown
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message);
-    }
-
-    @Test
-    public void onBiometricHelp_coEx_faceFailure() {
-        createController();
-
-        // GIVEN unlocking with fingerprint is possible and allowed
-        fingerprintUnlockIsPossibleAndAllowed();
-
-        String message = "A message";
-        mController.setVisible(true);
-
-        // WHEN there's a face not recognized message
-        mController.getKeyguardCallback().onBiometricHelp(
-                BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
-                message,
-                BiometricSourceType.FACE);
-
-        // THEN show sequential messages such as: 'face not recognized' and
-        // 'try fingerprint instead'
-        verifyIndicationMessage(
-                INDICATION_TYPE_BIOMETRIC_MESSAGE,
-                mContext.getString(R.string.keyguard_face_failed));
-        verifyIndicationMessage(
-                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
-                mContext.getString(R.string.keyguard_suggest_fingerprint));
-    }
-
-    @Test
-    public void onBiometricHelp_coEx_faceUnavailable() {
-        createController();
-
-        // GIVEN unlocking with fingerprint is possible and allowed
-        fingerprintUnlockIsPossibleAndAllowed();
-
-        String message = "A message";
-        mController.setVisible(true);
-
-        // WHEN there's a face unavailable message
-        mController.getKeyguardCallback().onBiometricHelp(
-                BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
-                message,
-                BiometricSourceType.FACE);
-
-        // THEN show sequential messages such as: 'face unlock unavailable' and
-        // 'try fingerprint instead'
-        verifyIndicationMessage(
-                INDICATION_TYPE_BIOMETRIC_MESSAGE,
-                message);
-        verifyIndicationMessage(
-                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
-                mContext.getString(R.string.keyguard_suggest_fingerprint));
-    }
-
-
-    @Test
-    public void onBiometricHelp_coEx_faceUnavailable_fpNotAllowed() {
-        createController();
-
-        // GIVEN unlocking with fingerprint is possible but not allowed
-        setupFingerprintUnlockPossible(true);
-        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed())
-                .thenReturn(false);
-
-        String message = "A message";
-        mController.setVisible(true);
-
-        // WHEN there's a face unavailable message
-        mController.getKeyguardCallback().onBiometricHelp(
-                BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
-                message,
-                BiometricSourceType.FACE);
-
-        // THEN show sequential messages such as: 'face unlock unavailable' and
-        // 'try fingerprint instead'
-        verifyIndicationMessage(
-                INDICATION_TYPE_BIOMETRIC_MESSAGE,
-                message);
-        verifyIndicationMessage(
-                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
-                mContext.getString(R.string.keyguard_unlock));
-    }
-
-    @Test
-    public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() {
-        createController();
-
-        // GIVEN face has already unlocked the device
-        when(mKeyguardUpdateMonitor.isCurrentUserUnlockedWithFace()).thenReturn(true);
-
-        String message = "A message";
-        mController.setVisible(true);
-
-        // WHEN there's a fingerprint not recognized message
-        mController.getKeyguardCallback().onBiometricHelp(
-                BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
-                message,
-                BiometricSourceType.FINGERPRINT);
-
-        // THEN show sequential messages such as: 'Unlocked by face' and
-        // 'Swipe up to open'
-        verifyIndicationMessage(
-                INDICATION_TYPE_BIOMETRIC_MESSAGE,
-                mContext.getString(R.string.keyguard_face_successful_unlock));
-        verifyIndicationMessage(
-                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
-                mContext.getString(R.string.keyguard_unlock));
-    }
-
-    @Test
-    public void onBiometricHelp_coEx_fpFailure_trustAgentAlreadyUnlocked() {
-        createController();
-
-        // GIVEN trust agent has already unlocked the device
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
-
-        String message = "A message";
-        mController.setVisible(true);
-
-        // WHEN there's a fingerprint not recognized message
-        mController.getKeyguardCallback().onBiometricHelp(
-                BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
-                message,
-                BiometricSourceType.FINGERPRINT);
-
-        // THEN show sequential messages such as: 'Kept unlocked by TrustAgent' and
-        // 'Swipe up to open'
-        verifyIndicationMessage(
-                INDICATION_TYPE_BIOMETRIC_MESSAGE,
-                mContext.getString(R.string.keyguard_indication_trust_unlocked));
-        verifyIndicationMessage(
-                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
-                mContext.getString(R.string.keyguard_unlock));
-    }
-
-    @Test
-    public void onBiometricHelp_coEx_fpFailure_trustAgentUnlocked_emptyTrustGrantedMessage() {
-        createController();
-
-        // GIVEN trust agent has already unlocked the device & trust granted message is empty
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
-        mController.showTrustGrantedMessage(false, "");
-
-        String message = "A message";
-        mController.setVisible(true);
-
-        // WHEN there's a fingerprint not recognized message
-        mController.getKeyguardCallback().onBiometricHelp(
-                BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
-                message,
-                BiometricSourceType.FINGERPRINT);
-
-        // THEN show action to unlock (ie: 'Swipe up to open')
-        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
-        verifyIndicationMessage(
-                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
-                mContext.getString(R.string.keyguard_unlock));
-    }
-
-    @Test
-    public void transientIndication_visibleWhenDozing_unlessSwipeUp_fromError() {
-        createController();
-        String message = mContext.getString(R.string.keyguard_unlock);
-
-        mController.setVisible(true);
-        mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT,
-                "A message", BiometricSourceType.FACE);
-
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message);
-        mStatusBarStateListener.onDozingChanged(true);
-
-        assertThat(mTextView.getText()).isNotEqualTo(message);
-    }
-
-    @Test
-    public void transientIndication_visibleWhenDozing_ignoresFingerprintErrorMsg() {
-        createController();
-        mController.setVisible(true);
-        reset(mRotateTextViewController);
-
-        // WHEN a fingerprint error user cancelled message is received
-        mController.getKeyguardCallback().onBiometricError(
-                BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED, "foo",
-                BiometricSourceType.FINGERPRINT);
-
-        // THEN no message is shown
-        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
-        verifyNoMessage(INDICATION_TYPE_TRANSIENT);
-    }
-
-    @Test
-    public void transientIndication_swipeUpToRetry() {
-        createController();
-        String message = mContext.getString(R.string.keyguard_retry);
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(false);
-
-        mController.setVisible(true);
-        mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT,
-                "A message", BiometricSourceType.FACE);
-
-        verify(mStatusBarKeyguardViewManager).setKeyguardMessage(eq(message), any(), any());
-    }
-
-    @Test
-    public void transientIndication_swipeUpToRetry_faceAuthenticated() {
-        createController();
-        String message = mContext.getString(R.string.keyguard_retry);
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.isFaceEnabledAndEnrolled()).thenReturn(true);
-
-        mController.setVisible(true);
-        mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT,
-                "A message", BiometricSourceType.FACE);
-
-        verify(mStatusBarKeyguardViewManager, never()).setKeyguardMessage(
-                eq(message), any(), any());
-    }
-
-    @Test
-    public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() {
-        createController();
-        fingerprintUnlockIsPossibleAndAllowed();
-        String message = "A message";
-
-        mController.setVisible(true);
-        mController.getKeyguardCallback().onBiometricError(
-                FACE_ERROR_TIMEOUT, message, BiometricSourceType.FACE);
-        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
-    }
-
-    @Test
-    public void sendFaceHelpMessages_fingerprintEnrolled() {
-        createController();
-        mController.mCoExAcquisitionMsgIdsToShowCallback.accept(
-                Set.of(
-                        BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED,
-                        BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED
-                )
-        );
-
-        // GIVEN unlocking with fingerprint is possible and allowed
-        fingerprintUnlockIsPossibleAndAllowed();
-
-        // WHEN help messages received that are allowed to show
-        final String helpString = "helpString";
-        final int[] msgIds = new int[]{
-                BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED,
-                BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED
-        };
-        Set<CharSequence> messages = new HashSet<>();
-        for (int msgId : msgIds) {
-            final String message = helpString + msgId;
-            messages.add(message);
-            mKeyguardUpdateMonitorCallback.onBiometricHelp(
-                    msgId, message, BiometricSourceType.FACE);
-        }
-
-        // THEN FACE_ACQUIRED_MOUTH_COVERING_DETECTED and DARK_GLASSES help messages shown
-        verifyIndicationMessages(INDICATION_TYPE_BIOMETRIC_MESSAGE,
-                messages);
-    }
-
-    @Test
-    public void doNotSendMostFaceHelpMessages_fingerprintEnrolled() {
-        createController();
-
-        // GIVEN unlocking with fingerprint is possible and allowed
-        fingerprintUnlockIsPossibleAndAllowed();
-
-        // WHEN help messages received that aren't supposed to show
-        final String helpString = "helpString";
-        final int[] msgIds = new int[]{
-                BiometricFaceConstants.FACE_ACQUIRED_FACE_OBSCURED,
-                BiometricFaceConstants.FACE_ACQUIRED_TOO_RIGHT,
-                BiometricFaceConstants.FACE_ACQUIRED_TOO_LEFT,
-                BiometricFaceConstants.FACE_ACQUIRED_TOO_HIGH,
-                BiometricFaceConstants.FACE_ACQUIRED_TOO_LOW,
-                BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT
-        };
-        for (int msgId : msgIds) {
-            mKeyguardUpdateMonitorCallback.onBiometricHelp(
-                    msgId,  helpString + msgId, BiometricSourceType.FACE);
-        }
-
-        // THEN no messages shown
-        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
-    }
-
-    @Test
-    public void sendAllFaceHelpMessages_fingerprintNotEnrolled() {
-        createController();
-
-        // GIVEN fingerprint NOT possible
-        fingerprintUnlockIsNotPossible();
-
-        // WHEN help messages received
-        final Set<CharSequence> helpStrings = new HashSet<>();
-        final String helpString = "helpString";
-        final int[] msgIds = new int[]{
-                BiometricFaceConstants.FACE_ACQUIRED_FACE_OBSCURED,
-                BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED,
-                BiometricFaceConstants.FACE_ACQUIRED_TOO_RIGHT,
-                BiometricFaceConstants.FACE_ACQUIRED_TOO_LEFT,
-                BiometricFaceConstants.FACE_ACQUIRED_TOO_HIGH,
-                BiometricFaceConstants.FACE_ACQUIRED_TOO_LOW,
-                BiometricFaceConstants.FACE_ACQUIRED_TOO_BRIGHT
-        };
-        for (int msgId : msgIds) {
-            final String numberedHelpString = helpString + msgId;
-            mKeyguardUpdateMonitorCallback.onBiometricHelp(
-                    msgId,  numberedHelpString, BiometricSourceType.FACE);
-            helpStrings.add(numberedHelpString);
-        }
-
-        // THEN message shown for each call
-        verifyIndicationMessages(INDICATION_TYPE_BIOMETRIC_MESSAGE, helpStrings);
-    }
-
-    @Test
-    public void sendTooDarkFaceHelpMessages_onTimeout_noFpEnrolled() {
-        createController();
-
-        // GIVEN fingerprint not possible
-        fingerprintUnlockIsNotPossible();
-
-        // WHEN help message received and deferred message is valid
-        final String helpString = "helpMsg";
-        when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString);
-        when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true);
-        mKeyguardUpdateMonitorCallback.onBiometricHelp(
-                BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK,
-                helpString,
-                BiometricSourceType.FACE
-        );
-
-        // THEN help message not shown yet
-        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
-
-        // WHEN face timeout error received
-        mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_TIMEOUT, "face timeout",
-                BiometricSourceType.FACE);
-
-        // THEN the low light message shows with suggestion to swipe up to unlock
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, helpString);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
-                mContext.getString(R.string.keyguard_unlock));
-    }
-
-    @Test
-    public void sendTooDarkFaceHelpMessages_onTimeout_fingerprintEnrolled() {
-        createController();
-
-        // GIVEN unlocking with fingerprint is possible and allowed
-        fingerprintUnlockIsPossibleAndAllowed();
-
-        // WHEN help message received and deferredMessage is valid
-        final String helpString = "helpMsg";
-        when(mFaceHelpMessageDeferral.getDeferredMessage()).thenReturn(helpString);
-        when(mFaceHelpMessageDeferral.shouldDefer(FACE_ACQUIRED_TOO_DARK)).thenReturn(true);
-        mKeyguardUpdateMonitorCallback.onBiometricHelp(
-                BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK,
-                helpString,
-                BiometricSourceType.FACE
-        );
-
-        // THEN help message not shown yet
-        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
-
-        // WHEN face timeout error received
-        mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_TIMEOUT, "face timeout",
-                BiometricSourceType.FACE);
-
-        // THEN the low light message shows and suggests trying fingerprint
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, helpString);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
-                mContext.getString(R.string.keyguard_suggest_fingerprint));
-    }
-
-    @Test
-    public void indicationAreaHidden_untilBatteryInfoArrives() {
-        createController();
-        // level of -1 indicates missing info
-        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_UNKNOWN,
-                -1 /* level */, BatteryManager.BATTERY_PLUGGED_WIRELESS, 100 /* health */,
-                0 /* maxChargingWattage */, true /* present */);
-
-        mController.setVisible(true);
-        mStatusBarStateListener.onDozingChanged(true);
-        reset(mIndicationArea);
-
-        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
-        // VISIBLE is always called first
-        verify(mIndicationArea).setVisibility(VISIBLE);
-        verify(mIndicationArea).setVisibility(GONE);
-    }
-
-    @Test
-    public void onRefreshBatteryInfo_computesChargingTime() throws RemoteException {
-        createController();
-        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
-                80 /* level */, BatteryManager.BATTERY_PLUGGED_WIRELESS, 100 /* health */,
-                0 /* maxChargingWattage */, true /* present */);
-
-        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
-        verify(mIBatteryStats).computeChargeTimeRemaining();
-    }
-
-    @Test
-    public void onRefreshBatteryInfo_computesChargingTime_onlyWhenCharging()
-            throws RemoteException {
-        createController();
-        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
-                80 /* level */, 0 /* plugged */, 100 /* health */,
-                0 /* maxChargingWattage */, true /* present */);
-
-        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
-        verify(mIBatteryStats, never()).computeChargeTimeRemaining();
-    }
-
-    /**
-     * Regression test.
-     * We should not make calls to the system_process when updating the doze state.
-     */
-    @Test
-    public void setDozing_noIBatteryCalls() throws RemoteException {
-        createController();
-        mController.setVisible(true);
-        mStatusBarStateListener.onDozingChanged(true);
-        mStatusBarStateListener.onDozingChanged(false);
-        verify(mIBatteryStats, never()).computeChargeTimeRemaining();
-    }
-
-    @Test
-    public void registersKeyguardStateCallback() {
-        createController();
-        verify(mKeyguardStateController).addCallback(any());
-    }
-
-    @Test
-    public void unlockMethodCache_listenerUpdatesPluggedIndication() {
-        createController();
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
-        mController.setPowerPluggedIn(true);
-        mController.setVisible(true);
-
-        verifyIndicationMessage(
-                INDICATION_TYPE_TRUST,
-                mContext.getString(R.string.keyguard_indication_trust_unlocked));
-    }
-
-    @Test
-    public void onRefreshBatteryInfo_chargingWithLongLife_presentChargingLimited() {
-        createController();
-        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
-                80 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
-                BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE, 0 /* maxChargingWattage */,
-                true /* present */);
-
-        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
-        mController.setVisible(true);
-
-        verifyIndicationMessage(
-                INDICATION_TYPE_BATTERY,
-                mContext.getString(
-                        R.string.keyguard_plugged_in_charging_limited,
-                        NumberFormat.getPercentInstance().format(80 / 100f)));
-    }
-
-    @Test
-    public void onRefreshBatteryInfo_fullChargedWithLongLife_presentChargingLimited() {
-        createController();
-        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
-                100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
-                BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE, 0 /* maxChargingWattage */,
-                true /* present */);
-
-        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
-        mController.setVisible(true);
-
-        verifyIndicationMessage(
-                INDICATION_TYPE_BATTERY,
-                mContext.getString(
-                        R.string.keyguard_plugged_in_charging_limited,
-                        NumberFormat.getPercentInstance().format(100 / 100f)));
-    }
-
-    @Test
-    public void onRefreshBatteryInfo_fullChargedWithoutLongLife_presentCharged() {
-        createController();
-        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
-                100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
-                BatteryManager.CHARGING_POLICY_DEFAULT, 0 /* maxChargingWattage */,
-                true /* present */);
-
-        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
-        mController.setVisible(true);
-
-        verifyIndicationMessage(
-                INDICATION_TYPE_BATTERY,
-                mContext.getString(R.string.keyguard_charged));
-    }
-
-    @Test
-    public void onRefreshBatteryInfo_dozing_dischargingWithLongLife_presentBatteryPercentage() {
-        createController();
-        mController.setVisible(true);
-        BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_DISCHARGING,
-                90 /* level */, 0 /* plugged */, BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE,
-                0 /* maxChargingWattage */, true /* present */);
-
-        mController.getKeyguardCallback().onRefreshBatteryInfo(status);
-        mStatusBarStateListener.onDozingChanged(true);
-
-        String percentage = NumberFormat.getPercentInstance().format(90 / 100f);
-        assertThat(mTextView.getText()).isEqualTo(percentage);
-    }
-
-    @Test
-    public void onRequireUnlockForNfc_showsRequireUnlockForNfcIndication() {
-        createController();
-        mController.setVisible(true);
-        String message = mContext.getString(R.string.require_unlock_for_nfc);
-        mController.getKeyguardCallback().onRequireUnlockForNfc();
-
-        verifyTransientMessage(message);
-    }
-
-    @Test
-    public void testEmptyOwnerInfoHidesIndicationArea() {
-        createController();
-
-        // GIVEN the owner info is set to an empty string & keyguard is showing
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mLockPatternUtils.getDeviceOwnerInfo()).thenReturn("");
-
-        // WHEN asked to update the indication area
-        mController.setVisible(true);
-        mExecutor.runAllReady();
-
-        // THEN the owner info should be hidden
-        verifyHideIndication(INDICATION_TYPE_OWNER_INFO);
-    }
-
-    @Test
-    public void testOnKeyguardShowingChanged_notShowing_resetsMessages() {
-        createController();
-
-        // GIVEN keyguard isn't showing
-        when(mKeyguardStateController.isShowing()).thenReturn(false);
-
-        // WHEN keyguard showing changed called
-        mKeyguardStateControllerCallback.onKeyguardShowingChanged();
-
-        // THEN messages are reset
-        verify(mRotateTextViewController).clearMessages();
-        assertThat(mTextView.getText()).isEqualTo("");
-    }
-
-    @Test
-    public void testOnKeyguardShowingChanged_showing_updatesPersistentMessages() {
-        createController();
-        mController.setVisible(true);
-        mExecutor.runAllReady();
-        reset(mRotateTextViewController);
-
-        // GIVEN keyguard is showing and not dozing
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
-        mController.setVisible(true);
-        mExecutor.runAllReady();
-        reset(mRotateTextViewController);
-
-        // WHEN keyguard showing changed called
-        mKeyguardStateControllerCallback.onKeyguardShowingChanged();
-        mExecutor.runAllReady();
-
-        // THEN persistent messages are updated (in this case, most messages are hidden since
-        // no info is provided) - verify that this happens
-        verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_DISCLOSURE);
-        verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_OWNER_INFO);
-        verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_BATTERY);
-        verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_TRUST);
-        verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_ALIGNMENT);
-        verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_LOGOUT);
-    }
-
-    @Test
-    public void onTrustGrantedMessageDoesNotShowUntilTrustGranted() {
-        createController();
-        mController.setVisible(true);
-        reset(mRotateTextViewController);
-
-        // GIVEN a trust granted message but trust isn't granted
-        final String trustGrantedMsg = "testing trust granted message";
-        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
-                false, false, new TrustGrantFlags(0), trustGrantedMsg);
-
-        verifyHideIndication(INDICATION_TYPE_TRUST);
-
-        // WHEN trust is granted
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
-        mKeyguardUpdateMonitorCallback.onTrustChanged(getCurrentUser());
-
-        // THEN verify the trust granted message shows
-        verifyIndicationMessage(
-                INDICATION_TYPE_TRUST,
-                trustGrantedMsg);
-    }
-
-    @Test
-    public void onTrustGrantedMessageShowsOnTrustGranted() {
-        createController();
-        mController.setVisible(true);
-
-        // GIVEN trust is granted
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
-
-        // WHEN the showTrustGranted method is called
-        final String trustGrantedMsg = "testing trust granted message";
-        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
-                false, false, new TrustGrantFlags(0), trustGrantedMsg);
-
-        // THEN verify the trust granted message shows
-        verifyIndicationMessage(
-                INDICATION_TYPE_TRUST,
-                trustGrantedMsg);
-    }
-
-    @Test
-    public void onTrustGrantedMessage_nullMessage_showsDefaultMessage() {
-        createController();
-        mController.setVisible(true);
-
-        // GIVEN trust is granted
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
-
-        // WHEN the showTrustGranted method is called with a null message
-        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
-                false, false, new TrustGrantFlags(0), null);
-
-        // THEN verify the default trust granted message shows
-        verifyIndicationMessage(
-                INDICATION_TYPE_TRUST,
-                getContext().getString(R.string.keyguard_indication_trust_unlocked));
-    }
-
-    @Test
-    public void onTrustGrantedMessage_emptyString_showsNoMessage() {
-        createController();
-        mController.setVisible(true);
-
-        // GIVEN trust is granted
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
-
-        // WHEN the showTrustGranted method is called with an EMPTY string
-        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
-                false, false, new TrustGrantFlags(0), "");
-
-        // THEN verify NO trust message is shown
-        verifyNoMessage(INDICATION_TYPE_TRUST);
-    }
-
-    @Test
-    public void coEx_faceSuccess_showsPressToOpen() {
-        // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, no a11y enabled
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
-                .thenReturn(true);
-        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
-        when(mAccessibilityManager.isEnabled()).thenReturn(false);
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        createController();
-        mController.setVisible(true);
-
-        // WHEN face auth succeeds
-        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
-        mController.getKeyguardCallback().onBiometricAuthenticated(0,
-                BiometricSourceType.FACE, false);
-
-        // THEN 'face unlocked' then 'press unlock icon to open' message show
-        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
-        String pressToOpen = mContext.getString(R.string.keyguard_unlock_press);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, pressToOpen);
-    }
-
-    @Test
-    public void coEx_faceSuccess_touchExplorationEnabled_showsFaceUnlockedSwipeToOpen() {
-        // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y enabled
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
-                .thenReturn(true);
-        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
-        when(mAccessibilityManager.isEnabled()).thenReturn(true);
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
-        createController();
-        mController.setVisible(true);
-
-        // WHEN face authenticated
-        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
-        mController.getKeyguardCallback().onBiometricAuthenticated(0,
-                BiometricSourceType.FACE, false);
-
-        // THEN show 'face unlocked' and 'swipe up to open' messages
-        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
-        String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
-    }
-
-    @Test
-    public void coEx_faceSuccess_a11yEnabled_showsFaceUnlockedSwipeToOpen() {
-        // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y is enabled
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
-                .thenReturn(true);
-        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
-        when(mAccessibilityManager.isEnabled()).thenReturn(true);
-        createController();
-        mController.setVisible(true);
-
-        // WHEN face auth is successful
-        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
-        mController.getKeyguardCallback().onBiometricAuthenticated(0,
-                BiometricSourceType.FACE, false);
-
-        // THEN show 'face unlocked' and 'swipe up to open' messages
-        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
-        String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
-    }
-
-    @Test
-    public void faceOnly_faceSuccess_showsFaceUnlockedSwipeToOpen() {
-        // GIVEN bouncer isn't showing, can skip bouncer, no udfps supported
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
-                .thenReturn(true);
-        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
-        createController();
-        mController.setVisible(true);
-
-        // WHEN face auth is successful
-        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
-        mController.getKeyguardCallback().onBiometricAuthenticated(0,
-                BiometricSourceType.FACE, false);
-
-        // THEN show 'face unlocked' and 'swipe up to open' messages
-        String unlockedByFace = mContext.getString(R.string.keyguard_face_successful_unlock);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, unlockedByFace);
-        String swipeUpToOpen = mContext.getString(R.string.keyguard_unlock);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, swipeUpToOpen);
-    }
-
-    @Test
-    public void udfpsOnly_a11yEnabled_showsSwipeToOpen() {
-        // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y is enabled
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
-                .thenReturn(true);
-        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
-        when(mAccessibilityManager.isEnabled()).thenReturn(true);
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
-        createController();
-        mController.setVisible(true);
-
-        // WHEN showActionToUnlock
-        mController.showActionToUnlock();
-
-        // THEN show 'swipe up to open' message
-        String swipeToOpen = mContext.getString(R.string.keyguard_unlock);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen);
-    }
-
-    @Test
-    public void udfpsOnly_showsPressToOpen() {
-        // GIVEN bouncer isn't showing, udfps is supported, a11y is NOT enabled, can skip bouncer
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
-                .thenReturn(true);
-        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
-        when(mAccessibilityManager.isEnabled()).thenReturn(false);
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        createController();
-        mController.setVisible(true);
-
-        // WHEN showActionToUnlock
-        mController.showActionToUnlock();
-
-        // THEN show 'press unlock icon to open' message
-        String pressToOpen = mContext.getString(R.string.keyguard_unlock_press);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, pressToOpen);
-    }
-
-    @Test
-    public void canSkipBouncer_noSecurity_showSwipeToUnlockHint() {
-        // GIVEN bouncer isn't showing, can skip bouncer, no security (udfps isn't supported,
-        // face wasn't authenticated)
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
-                .thenReturn(true);
-        when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
-        createController();
-        mController.setVisible(true);
-
-        // WHEN showActionToUnlock
-        mController.showActionToUnlock();
-
-        // THEN show 'swipe up to open' message
-        String swipeToOpen = mContext.getString(R.string.keyguard_unlock);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen);
-    }
-
-    @Test
-    public void cannotSkipBouncer_showSwipeToUnlockHint() {
-        // GIVEN bouncer isn't showing and cannot skip bouncer
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
-                .thenReturn(false);
-        createController();
-        mController.setVisible(true);
-
-        // WHEN showActionToUnlock
-        mController.showActionToUnlock();
-
-        // THEN show 'swipe up to open' message
-        String swipeToOpen = mContext.getString(R.string.keyguard_unlock);
-        verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen);
-    }
-
-    @Test
-    public void faceOnAcquired_processFrame() {
-        createController();
-
-        // WHEN face sends an acquired message
-        final int acquireInfo = 1;
-        mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo);
-
-        // THEN face help message deferral should process the acquired frame
-        verify(mFaceHelpMessageDeferral).processFrame(acquireInfo);
-    }
-
-    @Test
-    public void fingerprintOnAcquired_noProcessFrame() {
-        createController();
-
-        // WHEN fingerprint sends an acquired message
-        mKeyguardUpdateMonitorCallback.onBiometricAcquired(BiometricSourceType.FINGERPRINT, 1);
-
-        // THEN face help message deferral should NOT process any acquired frames
-        verify(mFaceHelpMessageDeferral, never()).processFrame(anyInt());
-    }
-
-    @Test
-    public void onBiometricHelp_fingerprint_faceHelpMessageDeferralDoesNothing() {
-        createController();
-
-        // WHEN fingerprint sends an onBiometricHelp
-        mKeyguardUpdateMonitorCallback.onBiometricHelp(
-                1,
-                "placeholder",
-                BiometricSourceType.FINGERPRINT);
-
-        // THEN face help message deferral is NOT: reset, updated, or checked for shouldDefer
-        verify(mFaceHelpMessageDeferral, never()).reset();
-        verify(mFaceHelpMessageDeferral, never()).updateMessage(anyInt(), anyString());
-        verify(mFaceHelpMessageDeferral, never()).shouldDefer(anyInt());
-    }
-
-    @Test
-    public void onBiometricFailed_resetFaceHelpMessageDeferral() {
-        createController();
-
-        // WHEN face sends an onBiometricAuthFailed
-        mKeyguardUpdateMonitorCallback.onBiometricAuthFailed(BiometricSourceType.FACE);
-
-        // THEN face help message deferral is reset
-        verify(mFaceHelpMessageDeferral).reset();
-    }
-
-    @Test
-    public void onBiometricError_resetFaceHelpMessageDeferral() {
-        createController();
-
-        // WHEN face has an error
-        mKeyguardUpdateMonitorCallback.onBiometricError(4, "string",
-                BiometricSourceType.FACE);
-
-        // THEN face help message deferral is reset
-        verify(mFaceHelpMessageDeferral).reset();
-    }
-
-    @Test
-    public void onBiometricHelp_faceAcquiredInfo_faceHelpMessageDeferral() {
-        createController();
-
-        // WHEN face sends an onBiometricHelp BIOMETRIC_HELP_FACE_NOT_RECOGNIZED
-        final int msgId = 1;
-        final String helpString = "test";
-        mKeyguardUpdateMonitorCallback.onBiometricHelp(
-                msgId,
-                "test",
-                BiometricSourceType.FACE);
-
-        // THEN face help message deferral is NOT reset and message IS updated
-        verify(mFaceHelpMessageDeferral, never()).reset();
-        verify(mFaceHelpMessageDeferral).updateMessage(msgId, helpString);
-    }
-
-
-    @Test
-    public void onBiometricError_faceLockedOutFirstTime_showsThePassedInMessage() {
-        createController();
-        onFaceLockoutError("first lockout");
-
-        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "first lockout");
-    }
-
-    @Test
-    public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() {
-        createController();
-        fingerprintUnlockIsPossibleAndAllowed();
-        onFaceLockoutError("first lockout");
-
-        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
-                mContext.getString(R.string.keyguard_suggest_fingerprint));
-    }
-
-    @Test
-    public void onBiometricError_faceLockedOutFirstTimeAndFpNotAllowed_showsDefaultFollowup() {
-        createController();
-        fingerprintUnlockIsNotPossible();
-        onFaceLockoutError("first lockout");
-
-        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
-                mContext.getString(R.string.keyguard_unlock));
-    }
-
-    @Test
-    public void onBiometricError_faceLockedOutSecondTimeInSession_showsUnavailableMessage() {
-        createController();
-        onFaceLockoutError("first lockout");
-        clearInvocations(mRotateTextViewController);
-
-        onFaceLockoutError("second lockout");
-
-        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
-                mContext.getString(R.string.keyguard_face_unlock_unavailable));
-    }
-
-    @Test
-    public void onBiometricError_faceLockedOutSecondTimeOnBouncer_showsUnavailableMessage() {
-        createController();
-        onFaceLockoutError("first lockout");
-        clearInvocations(mRotateTextViewController);
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
-
-        onFaceLockoutError("second lockout");
-
-        verify(mStatusBarKeyguardViewManager)
-                .setKeyguardMessage(
-                        eq(mContext.getString(R.string.keyguard_face_unlock_unavailable)),
-                        any(),
-                        any()
-                );
-    }
-
-    @Test
-    public void onBiometricError_faceLockedOutSecondTimeButUdfpsActive_showsNoMessage() {
-        createController();
-        onFaceLockoutError("first lockout");
-        clearInvocations(mRotateTextViewController);
-
-        when(mAuthController.isUdfpsFingerDown()).thenReturn(true);
-        onFaceLockoutError("second lockout");
-
-        verifyNoMoreInteractions(mRotateTextViewController);
-    }
-
-    @Test
-    public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() {
-        createController();
-        fingerprintUnlockIsPossibleAndAllowed();
-        onFaceLockoutError("first lockout");
-        clearInvocations(mRotateTextViewController);
-
-        onFaceLockoutError("second lockout");
-
-        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
-                mContext.getString(R.string.keyguard_suggest_fingerprint));
-    }
-
-    @Test
-    public void onBiometricError_faceLockedOutAgainAndFpNotAllowed_showsDefaultFollowup() {
-        createController();
-        fingerprintUnlockIsNotPossible();
-        onFaceLockoutError("first lockout");
-        clearInvocations(mRotateTextViewController);
-
-        onFaceLockoutError("second lockout");
-
-        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
-                mContext.getString(R.string.keyguard_unlock));
-    }
-
-    @Test
-    public void onBiometricError_whenFaceLockoutReset_onLockOutError_showsPassedInMessage() {
-        createController();
-        onFaceLockoutError("first lockout");
-        clearInvocations(mRotateTextViewController);
-        when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(false);
-        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE);
-
-        onFaceLockoutError("second lockout");
-
-        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "second lockout");
-    }
-
-    @Test
-    public void onFpLockoutStateChanged_whenFpIsLockedOut_showsPersistentMessage() {
-        createController();
-        mController.setVisible(true);
-        when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(true);
-
-        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
-
-        verifyIndicationShown(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
-                mContext.getString(R.string.keyguard_unlock));
-    }
-
-    @Test
-    public void onFpLockoutStateChanged_whenFpIsNotLockedOut_showsPersistentMessage() {
-        createController();
-        mController.setVisible(true);
-        clearInvocations(mRotateTextViewController);
-        when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(false);
-
-        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
-
-        verifyHideIndication(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE);
-    }
-
-    @Test
-    public void onVisibilityChange_showsPersistentMessage_ifFpIsLockedOut() {
-        createController();
-        mController.setVisible(false);
-        when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(true);
-        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
-        clearInvocations(mRotateTextViewController);
-
-        mController.setVisible(true);
-
-        verifyIndicationShown(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
-                mContext.getString(R.string.keyguard_unlock));
-    }
-
-    @Test
-    public void onBiometricError_whenFaceIsLocked_onMultipleLockOutErrors_showUnavailableMessage() {
-        createController();
-        onFaceLockoutError("first lockout");
-        clearInvocations(mRotateTextViewController);
-        when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true);
-        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE);
-
-        onFaceLockoutError("second lockout");
-
-        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
-                mContext.getString(R.string.keyguard_face_unlock_unavailable));
-    }
-
-    @Test
-    public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsNotAvailable_showsMessage() {
-        createController();
-        screenIsTurningOn();
-        fingerprintUnlockIsNotPossible();
-
-        onFaceLockoutError("lockout error");
-        verifyNoMoreInteractions(mRotateTextViewController);
-
-        mScreenObserver.onScreenTurnedOn();
-
-        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
-                "lockout error");
-        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
-                mContext.getString(R.string.keyguard_unlock));
-    }
-
-    @Test
-    public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
-        createController();
-        screenIsTurningOn();
-        fingerprintUnlockIsPossibleAndAllowed();
-
-        onFaceLockoutError("lockout error");
-        verifyNoMoreInteractions(mRotateTextViewController);
-
-        mScreenObserver.onScreenTurnedOn();
-
-        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
-                "lockout error");
-        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
-                mContext.getString(R.string.keyguard_suggest_fingerprint));
-    }
-
-    @Test
-    public void faceErrorMessageDroppedBecauseFingerprintMessageShowing() {
-        createController();
-        mController.setVisible(true);
-        mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
-                "fp not recognized", BiometricSourceType.FINGERPRINT);
-        clearInvocations(mRotateTextViewController);
-
-        onFaceLockoutError("lockout");
-        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
-    }
-
-    @Test
-    public void faceUnlockedMessageShowsEvenWhenFingerprintMessageShowing() {
-        createController();
-        mController.setVisible(true);
-        mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
-                "fp not recognized", BiometricSourceType.FINGERPRINT);
-        clearInvocations(mRotateTextViewController);
-
-        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
-                .thenReturn(true);
-        mController.getKeyguardCallback().onBiometricAuthenticated(0,
-                BiometricSourceType.FACE, false);
-        verifyIndicationMessage(
-                INDICATION_TYPE_BIOMETRIC_MESSAGE,
-                mContext.getString(R.string.keyguard_face_successful_unlock));
-    }
-
-    @Test
-    public void trustGrantedMessageShowsEvenWhenFingerprintMessageShowing() {
-        createController();
-        mController.setVisible(true);
-        mController.getKeyguardCallback().onBiometricHelp(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
-                "fp not recognized", BiometricSourceType.FINGERPRINT);
-        clearInvocations(mRotateTextViewController);
-
-        // GIVEN trust is granted
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
-
-        // WHEN the showTrustGranted method is called
-        final String trustGrantedMsg = "testing trust granted message after fp message";
-        mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
-                false, false, new TrustGrantFlags(0), trustGrantedMsg);
-
-        // THEN verify the trust granted message shows
-        verifyIndicationMessage(
-                INDICATION_TYPE_TRUST,
-                trustGrantedMsg);
-    }
-
-    @Test
-    public void updateAdaptiveAuthMessage_whenNotLockedByAdaptiveAuth_doesNotShowMsg() {
-        // When the device is not locked by adaptive auth
-        when(mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(getCurrentUser()))
-                .thenReturn(false);
-        createController();
-        mController.setVisible(true);
-
-        // Verify that the adaptive auth message does not show
-        verifyNoMessage(INDICATION_TYPE_ADAPTIVE_AUTH);
-    }
-
-    @Test
-    public void updateAdaptiveAuthMessage_whenLockedByAdaptiveAuth_cannotSkipBouncer_showsMsg() {
-        // When the device is locked by adaptive auth, and the user cannot skip bouncer
-        when(mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(getCurrentUser()))
-                .thenReturn(true);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser())).thenReturn(false);
-        createController();
-        mController.setVisible(true);
-
-        // Verify that the adaptive auth message shows
-        String message = mContext.getString(R.string.keyguard_indication_after_adaptive_auth_lock);
-        verifyIndicationMessage(INDICATION_TYPE_ADAPTIVE_AUTH, message);
-    }
-
-    @Test
-    public void updateAdaptiveAuthMessage_whenLockedByAdaptiveAuth_canSkipBouncer_doesNotShowMsg() {
-        createController();
-        mController.setVisible(true);
-
-        // When the device is locked by adaptive auth, but the device unlocked state changes and the
-        // user can skip bouncer
-        when(mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(getCurrentUser()))
-                .thenReturn(true);
-        when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser())).thenReturn(true);
-        mKeyguardStateControllerCallback.onUnlockedChanged();
-
-        // Verify that the adaptive auth message does not show
-        verifyNoMessage(INDICATION_TYPE_ADAPTIVE_AUTH);
-    }
-
-    private void screenIsTurningOn() {
-        when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_TURNING_ON);
-    }
-
-    private void sendUpdateDisclosureBroadcast() {
-        mBroadcastReceiver.onReceive(mContext, new Intent());
-    }
-
-    private void verifyIndicationMessages(int type, Set<CharSequence> messages) {
-        verify(mRotateTextViewController, times(messages.size())).updateIndication(eq(type),
-                mKeyguardIndicationCaptor.capture(), anyBoolean());
-        List<KeyguardIndication> kis = mKeyguardIndicationCaptor.getAllValues();
-
-        for (KeyguardIndication ki : kis) {
-            final CharSequence msg = ki.getMessage();
-            assertTrue(messages.contains(msg)); // check message is shown
-            messages.remove(msg);
-        }
-        assertThat(messages.size()).isEqualTo(0); // check that all messages accounted for (removed)
-    }
-
-    private void verifyIndicationMessage(int type, String message) {
-        verify(mRotateTextViewController).updateIndication(eq(type),
-                mKeyguardIndicationCaptor.capture(), anyBoolean());
-        assertThat(mKeyguardIndicationCaptor.getValue().getMessage())
-                .isEqualTo(message);
-    }
-
-    private void verifyHideIndication(int type) {
-        if (type == INDICATION_TYPE_TRANSIENT) {
-            verify(mRotateTextViewController).hideTransient();
-            verify(mRotateTextViewController, never()).showTransient(anyString());
-        } else {
-            verify(mRotateTextViewController).hideIndication(type);
-            verify(mRotateTextViewController, never()).updateIndication(eq(type),
-                    anyObject(), anyBoolean());
-        }
-    }
-
-    private void verifyTransientMessage(String message) {
-        verify(mRotateTextViewController).showTransient(eq(message));
-    }
-
-    private void fingerprintUnlockIsNotPossible() {
-        setupFingerprintUnlockPossible(false);
-    }
-
-    private void fingerprintUnlockIsPossibleAndAllowed() {
-        setupFingerprintUnlockPossible(true);
-        when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(true);
-    }
-
-    private void setupFingerprintUnlockPossible(boolean possible) {
-        when(mKeyguardUpdateMonitor
-                .isUnlockWithFingerprintPossible(getCurrentUser()))
-                .thenReturn(possible);
-    }
-
-    private int getCurrentUser() {
-        return mCurrentUserId;
-    }
-
-    private void onFaceLockoutError(String errMsg) {
-        mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_LOCKOUT_PERMANENT,
-                errMsg,
-                BiometricSourceType.FACE);
-    }
-}
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 382b307..2371ccc 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
@@ -1,11 +1,9 @@
 package com.android.systemui.statusbar.notification
 
-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
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import org.junit.Assert.assertEquals
@@ -148,7 +146,6 @@
     }
 
     @Test
-    @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun getCornerRadii_radius_maxed_to_height() {
         whenever(targetView.height).thenReturn(10)
         roundable.requestRoundness(1f, 1f, SOURCE1)
@@ -157,7 +154,6 @@
     }
 
     @Test
-    @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun getCornerRadii_topRadius_maxed_to_height() {
         whenever(targetView.height).thenReturn(5)
         roundable.requestRoundness(1f, 0f, SOURCE1)
@@ -166,7 +162,6 @@
     }
 
     @Test
-    @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun getCornerRadii_bottomRadius_maxed_to_height() {
         whenever(targetView.height).thenReturn(5)
         roundable.requestRoundness(0f, 1f, SOURCE1)
@@ -175,7 +170,6 @@
     }
 
     @Test
-    @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun getCornerRadii_radii_kept() {
         whenever(targetView.height).thenReturn(100)
         roundable.requestRoundness(1f, 1f, SOURCE1)
@@ -188,16 +182,9 @@
         assertEquals("bottomCornerRadius", bottom, roundable.bottomCornerRadius)
     }
 
-    class FakeRoundable(
-        targetView: View,
-        radius: Float = MAX_RADIUS,
-    ) : Roundable {
+    class FakeRoundable(targetView: View, radius: Float = MAX_RADIUS) : Roundable {
         override val roundableState =
-            RoundableState(
-                targetView = targetView,
-                roundable = this,
-                maxRadius = radius,
-            )
+            RoundableState(targetView = targetView, roundable = this, maxRadius = radius)
 
         override val clipHeight: Int
             get() = roundableState.targetView.height
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 61d14b7..58864f6 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
@@ -76,7 +76,7 @@
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.FakeEventLog;
 import com.android.systemui.util.settings.FakeGlobalSettings;
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 f870200..28577b3 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
@@ -61,7 +61,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.FakeSystemClock;
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 d8a23d6..5aee929 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
@@ -68,6 +68,7 @@
 import com.android.systemui.statusbar.notification.FeedbackIcon;
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
@@ -394,7 +395,7 @@
         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
         AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
         row.setAboveShelfChangedListener(listener);
-        row.setPinned(true);
+        row.setPinnedStatus(PinnedStatus.PinnedBySystem);
         verify(listener).onAboveShelfStateChanged(true);
     }
 
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 723c0d7..2d35ea5 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
@@ -65,11 +65,11 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.notificationLockscreenUserManager
 import com.android.systemui.statusbar.policy.deviceProvisionedController
-import com.android.systemui.statusbar.policy.headsUpManager
 import com.android.systemui.testKosmos
 import com.android.systemui.util.kotlin.JavaAdapter
 import com.android.systemui.wmshell.BubblesManager
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 d2350bc..11b19f9 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
@@ -103,7 +103,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.AvalancheController;
+import com.android.systemui.statusbar.notification.headsup.AvalancheController;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor;
 
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 c9f8463..6912eda 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
@@ -198,7 +198,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -334,7 +334,7 @@
     @Mock private CentralSurfacesCommandQueueCallbacks mCentralSurfacesCommandQueueCallbacks;
     @Mock private PluginManager mPluginManager;
     @Mock private ViewMediatorCallback mViewMediatorCallback;
-    @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+    @Mock private ShadeTouchableRegionManager mShadeTouchableRegionManager;
     @Mock private PluginDependencyProvider mPluginDependencyProvider;
     @Mock private ExtensionController mExtensionController;
     @Mock private UserInfoControllerImpl mUserInfoControllerImpl;
@@ -617,7 +617,7 @@
                 mKeyguardIndicationController,
                 mDemoModeController,
                 mNotificationShadeDepthControllerLazy,
-                mStatusBarTouchableRegionManager,
+                mShadeTouchableRegionManager,
                 mBrightnessSliderFactory,
                 mScreenOffAnimationController,
                 mWallpaperController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 69efa87..abdd797 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -33,6 +33,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.systemui.Flags as AconfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.flags.FeatureFlags
@@ -44,6 +45,7 @@
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.StatusBarLongPressGestureDetector
+import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
@@ -60,12 +62,14 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.view.ViewUtil
 import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
 import java.util.Optional
 import javax.inject.Provider
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
@@ -98,6 +102,7 @@
     @Mock private lateinit var shadeLogger: ShadeLogger
     @Mock private lateinit var viewUtil: ViewUtil
     @Mock private lateinit var mStatusBarLongPressGestureDetector: StatusBarLongPressGestureDetector
+    @Mock private lateinit var statusBarTouchShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy
     private lateinit var statusBarWindowStateController: StatusBarWindowStateController
 
     private lateinit var view: PhoneStatusBarView
@@ -329,6 +334,30 @@
     }
 
     @Test
+    @EnableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    fun onTouch_actionDown_propagatesToDisplayPolicy() {
+        controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+
+        verify(statusBarTouchShadeDisplayPolicy).onStatusBarTouched(eq(mContext.displayId))
+    }
+
+    @Test
+    @EnableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    fun onTouch_actionUp_notPropagatesToDisplayPolicy() {
+        controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0))
+
+        verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any())
+    }
+
+    @Test
+    @DisableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    fun onTouch_shadeWindowGoesAroundDisabled_notPropagatesToDisplayPolicy() {
+        controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+
+        verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any())
+    }
+
+    @Test
     fun shadeIsExpandedOnStatusIconMouseClick() {
         val view = createViewMock()
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -402,6 +431,7 @@
                 mStatusOverlayHoverListenerFactory,
                 fakeDarkIconDispatcher,
                 statusBarContentInsetsProviderStore,
+                Lazy { statusBarTouchShadeDisplayPolicy },
             )
             .create(view)
             .also { it.init() }
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 cace60c..2f30b74 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
@@ -95,7 +95,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
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 d1e4f64..3e24fbe 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
@@ -68,6 +68,7 @@
 import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
 import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization;
 import com.android.systemui.statusbar.phone.ui.DarkIconManager;
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeHomeStatusBarViewBinder;
@@ -155,9 +156,9 @@
                 any(StatusBarWindowStateListener.class));
     }
 
-    @Test
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void testDisableNone() {
+  @Test
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void testDisableNone() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -166,9 +167,9 @@
         assertEquals(View.VISIBLE, getClockView().getVisibility());
     }
 
-    @Test
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void testDisableSystemInfo_systemAnimationIdle_doesHide() {
+  @Test
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void testDisableSystemInfo_systemAnimationIdle_doesHide() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
@@ -184,9 +185,9 @@
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
-    @Test
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() {
+  @Test
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() {
         // GIVEN the status bar hides the system info via disable flags, while there is no event
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
@@ -214,9 +215,9 @@
         assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
     }
 
-    @Test
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() {
+  @Test
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() {
         // GIVEN the status bar hides the system info via disable flags, while there is no event
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
@@ -231,9 +232,9 @@
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
-    @Test
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() {
+  @Test
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() {
         // GIVEN the status bar is not disabled
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
@@ -247,9 +248,9 @@
         assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
     }
 
-    @Test
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() {
+  @Test
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() {
         // GIVEN the status bar is not disabled
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
@@ -271,9 +272,9 @@
         assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
     }
 
-    @Test
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void testDisableNotifications() {
+  @Test
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void testDisableNotifications() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
@@ -307,9 +308,9 @@
         assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
     }
 
-    @Test
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void testDisableClock() {
+  @Test
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void testDisableClock() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_CLOCK, 0, false);
@@ -343,10 +344,10 @@
         assertEquals(View.VISIBLE, getClockView().getVisibility());
     }
 
-    @Test
-    @DisableSceneContainer
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void disable_shadeOpenAndShouldHide_everythingHidden() {
+  @Test
+  @DisableSceneContainer
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void disable_shadeOpenAndShouldHide_everythingHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         // WHEN the shade is open and configured to hide the status bar icons
@@ -361,10 +362,10 @@
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
-    @Test
-    @DisableSceneContainer
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void disable_shadeOpenButNotShouldHide_everythingShown() {
+  @Test
+  @DisableSceneContainer
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void disable_shadeOpenButNotShouldHide_everythingShown() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         // WHEN the shade is open but *not* configured to hide the status bar icons
@@ -379,11 +380,11 @@
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
-    /** Regression test for b/279790651. */
-    @Test
-    @DisableSceneContainer
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() {
+  /** Regression test for b/279790651. */
+  @Test
+  @DisableSceneContainer
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         // WHEN the shade is open and configured to hide the status bar icons
@@ -409,9 +410,9 @@
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
-    @Test
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void disable_notTransitioningToOccluded_everythingShown() {
+  @Test
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void disable_notTransitioningToOccluded_everythingShown() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mCollapsedStatusBarViewModel.isTransitioningFromLockscreenToOccluded().setValue(false);
@@ -424,10 +425,10 @@
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
-    @Test
-    @DisableSceneContainer
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void disable_isTransitioningToOccluded_everythingHidden() {
+  @Test
+  @DisableSceneContainer
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void disable_isTransitioningToOccluded_everythingHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mCollapsedStatusBarViewModel.isTransitioningFromLockscreenToOccluded().setValue(true);
@@ -440,10 +441,10 @@
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
-    @Test
-    @DisableSceneContainer
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() {
+  @Test
+  @DisableSceneContainer
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         // WHEN the transition is occurring
@@ -472,9 +473,13 @@
         assertEquals(View.GONE, getUserChipView().getVisibility());
     }
 
-    @Test
-    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
-    public void disable_noOngoingCall_chipHidden() {
+  @Test
+  @DisableFlags({
+    FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+    StatusBarRootModernization.FLAG_NAME,
+    StatusBarChipsModernization.FLAG_NAME
+  })
+  public void disable_noOngoingCall_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         when(mOngoingCallController.hasOngoingCall()).thenReturn(false);
@@ -484,9 +489,13 @@
         assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
-    public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() {
+  @Test
+  @DisableFlags({
+    FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+    StatusBarRootModernization.FLAG_NAME,
+    StatusBarChipsModernization.FLAG_NAME
+  })
+  public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
@@ -497,9 +506,13 @@
         assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
-    @Test
-    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
-    public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() {
+  @Test
+  @DisableFlags({
+    FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+    StatusBarRootModernization.FLAG_NAME,
+    StatusBarChipsModernization.FLAG_NAME
+  })
+  public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
@@ -510,9 +523,13 @@
         assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
-    public void disable_hasOngoingCallButAlsoHun_chipHidden() {
+  @Test
+  @DisableFlags({
+    FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+    StatusBarRootModernization.FLAG_NAME,
+    StatusBarChipsModernization.FLAG_NAME
+  })
+  public void disable_hasOngoingCallButAlsoHun_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
@@ -523,9 +540,13 @@
         assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
-    public void disable_ongoingCallEnded_chipHidden() {
+  @Test
+  @DisableFlags({
+    FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+    StatusBarRootModernization.FLAG_NAME,
+    StatusBarChipsModernization.FLAG_NAME
+  })
+  public void disable_ongoingCallEnded_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         // Ongoing call started
@@ -547,9 +568,13 @@
         assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
-    public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() {
+  @Test
+  @DisableFlags({
+    FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+    StatusBarRootModernization.FLAG_NAME,
+    StatusBarChipsModernization.FLAG_NAME
+  })
+  public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         // Enable animations for testing so that we can verify we still aren't animating
         fragment.enableAnimationsForTesting();
@@ -564,9 +589,13 @@
         assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
-    @Test
-    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
-    public void screenSharingChipsDisabled_ignoresNewCallback() {
+  @Test
+  @DisableFlags({
+    FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+    StatusBarRootModernization.FLAG_NAME,
+    StatusBarChipsModernization.FLAG_NAME
+  })
+  public void screenSharingChipsDisabled_ignoresNewCallback() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         // WHEN there *is* an ongoing call via old callback
@@ -597,10 +626,10 @@
         assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void noOngoingActivity_chipHidden() {
+  @Test
+  @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void noOngoingActivity_chipHidden() {
         resumeAndGetFragment();
 
         // TODO(b/332662551): We *should* be able to just set a value on
@@ -615,10 +644,10 @@
         assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void hasPrimaryOngoingActivity_primaryChipDisplayedAndNotificationIconsHidden() {
+  @Test
+  @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void hasPrimaryOngoingActivity_primaryChipDisplayedAndNotificationIconsHidden() {
         resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -630,12 +659,14 @@
         assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
-    @Test
-    @EnableFlags({
-            FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
-            StatusBarNotifChips.FLAG_NAME,
-            StatusBarRootModernization.FLAG_NAME})
-    public void hasPrimaryOngoingActivity_viewsUnchangedWhenRootModernizationFlagOn() {
+  @Test
+  @EnableFlags({
+    FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
+    StatusBarNotifChips.FLAG_NAME,
+    StatusBarRootModernization.FLAG_NAME,
+    StatusBarChipsModernization.FLAG_NAME
+  })
+  public void hasPrimaryOngoingActivity_viewsUnchangedWhenRootModernizationFlagOn() {
         resumeAndGetFragment();
 
         assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
@@ -658,10 +689,14 @@
         assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
     }
 
-    @Test
-    @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
-    public void hasSecondaryOngoingActivity_butNotifsFlagOff_secondaryChipHidden() {
+  @Test
+  @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+  @DisableFlags({
+    StatusBarNotifChips.FLAG_NAME,
+    StatusBarRootModernization.FLAG_NAME,
+    StatusBarChipsModernization.FLAG_NAME
+  })
+  public void hasSecondaryOngoingActivity_butNotifsFlagOff_secondaryChipHidden() {
         resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -672,10 +707,10 @@
         assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() {
+  @Test
+  @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() {
         resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -687,10 +722,14 @@
         assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
-    @Test
-    @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
-    public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_notifsFlagOff() {
+  @Test
+  @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+  @DisableFlags({
+    StatusBarNotifChips.FLAG_NAME,
+    StatusBarRootModernization.FLAG_NAME,
+    StatusBarChipsModernization.FLAG_NAME
+  })
+  public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_notifsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -704,10 +743,10 @@
         assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_notifsFlagOn() {
+  @Test
+  @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_notifsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -722,10 +761,14 @@
         assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
-    public void hasOngoingActivityButAlsoHun_chipHidden_notifsFlagOff() {
+  @Test
+  @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+  @DisableFlags({
+    StatusBarNotifChips.FLAG_NAME,
+    StatusBarRootModernization.FLAG_NAME,
+    StatusBarChipsModernization.FLAG_NAME
+  })
+  public void hasOngoingActivityButAlsoHun_chipHidden_notifsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -739,10 +782,10 @@
         assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void hasOngoingActivitiesButAlsoHun_chipsHidden_notifsFlagOn() {
+  @Test
+  @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void hasOngoingActivitiesButAlsoHun_chipsHidden_notifsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
@@ -757,10 +800,14 @@
         assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
-    public void primaryOngoingActivityEnded_chipHidden_notifsFlagOff() {
+  @Test
+  @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+  @DisableFlags({
+    StatusBarNotifChips.FLAG_NAME,
+    StatusBarRootModernization.FLAG_NAME,
+    StatusBarChipsModernization.FLAG_NAME
+  })
+  public void primaryOngoingActivityEnded_chipHidden_notifsFlagOff() {
         resumeAndGetFragment();
 
         // Ongoing activity started
@@ -780,10 +827,10 @@
         assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void primaryOngoingActivityEnded_chipHidden_notifsFlagOn() {
+  @Test
+  @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void primaryOngoingActivityEnded_chipHidden_notifsFlagOn() {
         resumeAndGetFragment();
 
         // Ongoing activity started
@@ -803,10 +850,10 @@
         assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void secondaryOngoingActivityEnded_chipHidden() {
+  @Test
+  @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void secondaryOngoingActivityEnded_chipHidden() {
         resumeAndGetFragment();
 
         // Secondary ongoing activity started
@@ -826,10 +873,14 @@
         assertEquals(View.GONE, getSecondaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
-    public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOff() {
+  @Test
+  @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+  @DisableFlags({
+    StatusBarNotifChips.FLAG_NAME,
+    StatusBarRootModernization.FLAG_NAME,
+    StatusBarChipsModernization.FLAG_NAME
+  })
+  public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         // Enable animations for testing so that we can verify we still aren't animating
         fragment.enableAnimationsForTesting();
@@ -845,10 +896,10 @@
         assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
-    @Test
-    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOn() {
+  @Test
+  @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         // Enable animations for testing so that we can verify we still aren't animating
         fragment.enableAnimationsForTesting();
@@ -864,10 +915,14 @@
         assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
-    @Test
-    @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
-    public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOff() {
+  @Test
+  @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+  @DisableFlags({
+    StatusBarNotifChips.FLAG_NAME,
+    StatusBarRootModernization.FLAG_NAME,
+    StatusBarChipsModernization.FLAG_NAME
+  })
+  public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         // WHEN there *is* an ongoing call via old callback
@@ -897,10 +952,10 @@
         assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOn() {
+  @Test
+  @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         // WHEN there *is* an ongoing call via old callback
@@ -931,10 +986,10 @@
         assertEquals(View.VISIBLE, getSecondaryOngoingActivityChipView().getVisibility());
     }
 
-    @Test
-    @EnableSceneContainer
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void isHomeStatusBarAllowedByScene_false_everythingHidden() {
+  @Test
+  @EnableSceneContainer
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void isHomeStatusBarAllowedByScene_false_everythingHidden() {
         resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onIsHomeStatusBarAllowedBySceneChanged(false);
@@ -945,10 +1000,10 @@
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
-    @Test
-    @EnableSceneContainer
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void isHomeStatusBarAllowedByScene_true_everythingShown() {
+  @Test
+  @EnableSceneContainer
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void isHomeStatusBarAllowedByScene_true_everythingShown() {
         resumeAndGetFragment();
 
         mCollapsedStatusBarViewBinder.getListener().onIsHomeStatusBarAllowedBySceneChanged(true);
@@ -959,10 +1014,10 @@
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
-    @Test
-    @EnableSceneContainer
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void disable_isHomeStatusBarAllowedBySceneFalse_disableValuesIgnored() {
+  @Test
+  @EnableSceneContainer
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void disable_isHomeStatusBarAllowedBySceneFalse_disableValuesIgnored() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         // WHEN the scene doesn't allow the status bar
@@ -977,10 +1032,10 @@
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
-    @Test
-    @EnableSceneContainer
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void disable_isHomeStatusBarAllowedBySceneTrue_disableValuesUsed() {
+  @Test
+  @EnableSceneContainer
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void disable_isHomeStatusBarAllowedBySceneTrue_disableValuesUsed() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         // WHEN the scene does allow the status bar
@@ -995,10 +1050,10 @@
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
-    @Test
-    @DisableSceneContainer
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void isHomeStatusBarAllowedByScene_sceneContainerDisabled_valueNotUsed() {
+  @Test
+  @DisableSceneContainer
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void isHomeStatusBarAllowedByScene_sceneContainerDisabled_valueNotUsed() {
         resumeAndGetFragment();
 
         // Even if the scene says to hide the home status bar
@@ -1010,9 +1065,9 @@
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
-    @Test
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void disable_isDozing_clockAndSystemInfoVisible() {
+  @Test
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void disable_isDozing_clockAndSystemInfoVisible() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mStatusBarStateController.isDozing()).thenReturn(true);
 
@@ -1022,9 +1077,9 @@
         assertEquals(View.VISIBLE, getClockView().getVisibility());
     }
 
-    @Test
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void disable_NotDozing_clockAndSystemInfoVisible() {
+  @Test
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void disable_NotDozing_clockAndSystemInfoVisible() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mStatusBarStateController.isDozing()).thenReturn(false);
 
@@ -1034,9 +1089,9 @@
         assertEquals(View.VISIBLE, getClockView().getVisibility());
     }
 
-    @Test
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void disable_headsUpShouldBeVisibleTrue_clockDisabled() {
+  @Test
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void disable_headsUpShouldBeVisibleTrue_clockDisabled() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
 
@@ -1045,9 +1100,9 @@
         assertEquals(View.GONE, getClockView().getVisibility());
     }
 
-    @Test
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() {
+  @Test
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false);
 
@@ -1098,10 +1153,10 @@
         assertFalse(contains);
     }
 
-    @Test
-    @DisableSceneContainer
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void testStatusBarIcons_hiddenThroughoutCameraLaunch() {
+  @Test
+  @DisableSceneContainer
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void testStatusBarIcons_hiddenThroughoutCameraLaunch() {
         final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         mockSecureCameraLaunch(fragment, true /* launched */);
@@ -1121,10 +1176,10 @@
         assertEquals(View.VISIBLE, getClockView().getVisibility());
     }
 
-    @Test
-    @DisableSceneContainer
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() {
+  @Test
+  @DisableSceneContainer
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() {
         final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         // WHEN a transition to dream has started
@@ -1158,9 +1213,9 @@
         assertEquals(View.VISIBLE, getClockView().getVisibility());
     }
 
-    @Test
-    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
-    public void testStatusBarIcons_lockscreenToDreamTransitionButNotDreaming_iconsVisible() {
+  @Test
+  @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME})
+  public void testStatusBarIcons_lockscreenToDreamTransitionButNotDreaming_iconsVisible() {
         final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         // WHEN a transition to dream has started but we're *not* dreaming
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index a213c85..1d4b8e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -58,6 +58,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 
 import kotlin.sequences.Sequence;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
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 aad8b4b..df561ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -141,6 +141,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.notification.interruption.AvalancheProvider;
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
@@ -154,7 +155,6 @@
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -162,6 +162,7 @@
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.FakeEventLog;
 import com.android.systemui.util.settings.FakeGlobalSettings;
+import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.settings.SystemSettings;
 import com.android.systemui.util.time.SystemClock;
 import com.android.wm.shell.Flags;
@@ -442,7 +443,9 @@
                 () -> mSelectedUserInteractor,
                 mUserTracker,
                 mNotificationShadeWindowModel,
-                mKosmos::getCommunalInteractor
+                new FakeSettings(),
+                mKosmos::getCommunalInteractor,
+                mKosmos.getShadeLayoutParams()
         );
         mNotificationShadeWindowController.fetchWindowRootView();
         mNotificationShadeWindowController.attach();
@@ -2575,78 +2578,6 @@
 
     @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
     @Test
-    public void testEventLogging_bubbleBar_dragBarLeft() {
-        mBubbleProperties.mIsBubbleBarEnabled = true;
-        mPositioner.setIsLargeScreen(true);
-        mPositioner.setBubbleBarLocation(BubbleBarLocation.RIGHT);
-        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
-        mBubbleController.registerBubbleStateListener(bubbleStateListener);
-
-        mEntryListener.onEntryAdded(mRow);
-        assertBarMode();
-
-        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
-                BubbleBarLocation.UpdateSource.DRAG_BAR);
-
-        verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR);
-    }
-
-    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
-    @Test
-    public void testEventLogging_bubbleBar_dragBarRight() {
-        mBubbleProperties.mIsBubbleBarEnabled = true;
-        mPositioner.setIsLargeScreen(true);
-        mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
-        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
-        mBubbleController.registerBubbleStateListener(bubbleStateListener);
-
-        mEntryListener.onEntryAdded(mRow);
-        assertBarMode();
-
-        mBubbleController.setBubbleBarLocation(BubbleBarLocation.RIGHT,
-                BubbleBarLocation.UpdateSource.DRAG_BAR);
-
-        verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR);
-    }
-
-    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
-    @Test
-    public void testEventLogging_bubbleBar_dragBubbleLeft() {
-        mBubbleProperties.mIsBubbleBarEnabled = true;
-        mPositioner.setIsLargeScreen(true);
-        mPositioner.setBubbleBarLocation(BubbleBarLocation.RIGHT);
-        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
-        mBubbleController.registerBubbleStateListener(bubbleStateListener);
-
-        mEntryListener.onEntryAdded(mRow);
-        assertBarMode();
-
-        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
-                BubbleBarLocation.UpdateSource.DRAG_BUBBLE);
-
-        verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE);
-    }
-
-    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
-    @Test
-    public void testEventLogging_bubbleBar_dragBubbleRight() {
-        mBubbleProperties.mIsBubbleBarEnabled = true;
-        mPositioner.setIsLargeScreen(true);
-        mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
-        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
-        mBubbleController.registerBubbleStateListener(bubbleStateListener);
-
-        mEntryListener.onEntryAdded(mRow);
-        assertBarMode();
-
-        mBubbleController.setBubbleBarLocation(BubbleBarLocation.RIGHT,
-                BubbleBarLocation.UpdateSource.DRAG_BUBBLE);
-
-        verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE);
-    }
-
-    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
-    @Test
     public void testEventLogging_bubbleBar_expandAndCollapse() {
         mBubbleProperties.mIsBubbleBarEnabled = true;
         mPositioner.setIsLargeScreen(true);
diff --git a/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt
index 5686764..74330c1 100644
--- a/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/res/ResourcesKosmos.kt
@@ -18,5 +18,8 @@
 
 import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
+import org.mockito.Mockito.mock
 
 var Kosmos.mainResources: Resources by Kosmos.Fixture { applicationContext.resources }
+
+var Kosmos.mockResources: Resources by Kosmos.Fixture { mock(Resources::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/IUriGrantsManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/IUriGrantsManagerKosmos.kt
new file mode 100644
index 0000000..003777a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/app/IUriGrantsManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.app
+
+import android.app.IUriGrantsManager
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.iUriGrantsManager by Kosmos.Fixture { mock<IUriGrantsManager>() }
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 153a8be..3e44364 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -118,6 +118,7 @@
                     android.net.platform.flags.Flags.class,
                     android.os.Flags.class,
                     android.service.controls.flags.Flags.class,
+                    android.service.quickaccesswallet.Flags.class,
                     com.android.internal.telephony.flags.Flags.class,
                     com.android.server.notification.Flags.class,
                     com.android.systemui.Flags.class);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 5063140..0ba7c85 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -62,6 +62,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
 import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -74,7 +75,6 @@
 import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.policy.ZenModeController
 import com.android.systemui.statusbar.window.StatusBarWindowController
@@ -173,7 +173,7 @@
     interface Bindings {
         @Binds
         fun bindStatusBarStateController(
-            sysuiStatusBarStateController: SysuiStatusBarStateController,
+            sysuiStatusBarStateController: SysuiStatusBarStateController
         ): StatusBarStateController
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MotionEventHelper.java b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/MotionEventHelper.java
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MotionEventHelper.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/MotionEventHelper.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/TestableWindowManager.java
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/TestableWindowManager.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/TestableWindowManager.java
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt
new file mode 100644
index 0000000..a6e7133
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.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.activity.data.repository
+
+import android.app.activityManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.log.core.Logger
+import kotlinx.coroutines.flow.MutableStateFlow
+
+val Kosmos.activityManagerRepository by Kosmos.Fixture { FakeActivityManagerRepository() }
+
+val Kosmos.realActivityManagerRepository by
+    Kosmos.Fixture { ActivityManagerRepositoryImpl(testDispatcher, activityManager) }
+
+class FakeActivityManagerRepository : ActivityManagerRepository {
+    private val uidFlows = mutableMapOf<Int, MutableList<MutableStateFlow<Boolean>>>()
+
+    var startingIsAppVisibleValue = false
+
+    override fun createIsAppVisibleFlow(
+        creationUid: Int,
+        logger: Logger,
+        identifyingLogTag: String,
+    ): MutableStateFlow<Boolean> {
+        val newFlow = MutableStateFlow(startingIsAppVisibleValue)
+        uidFlows.computeIfAbsent(creationUid) { mutableListOf() }.add(newFlow)
+        return newFlow
+    }
+
+    fun setIsAppVisible(uid: Int, isAppVisible: Boolean) {
+        uidFlows[uid]?.forEach { stateFlow -> stateFlow.value = isAppVisible }
+    }
+}
+
+val ActivityManagerRepository.fake
+    get() = this as FakeActivityManagerRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
index e2fc44f..eb0aee4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.animation
 
+import android.animation.Animator
 import java.util.function.Consumer
 import org.junit.rules.RuleChain
 import org.junit.rules.TestRule
@@ -71,6 +72,22 @@
     }
 
     /**
+     * This is similar to [advanceTimeBy] but it expects to reach the end of an animation. This call
+     * may produce 2 frames for the last animation frame and end animation callback.
+     *
+     * @param durationMs the duration that is greater than or equal to the animation duration.
+     */
+    fun advanceAnimationDuration(durationMs: Long) {
+        advanceTimeBy(durationMs)
+        if (Animator.isPostNotifyEndListenerEnabled()) {
+            // If the post-end-callback is enabled, the AnimatorListener#onAnimationEnd will be
+            // called on the next frame of last animation frame. So trigger additional doFrame to
+            // ensure the end callback method is called (by android.animation.AnimatorTestRule).
+            advanceTimeBy(0)
+        }
+    }
+
+    /**
      * Returns the current time in milliseconds tracked by the AnimationHandlers. Note that this is
      * a different time than the time tracked by {@link SystemClock}.
      */
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
index 4f4d1da..e0c0fbd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
@@ -16,12 +16,14 @@
 
 package com.android.systemui.bluetooth.qsdialog
 
+import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
 
 val Kosmos.audioSharingInteractor: AudioSharingInteractor by
     Kosmos.Fixture {
         AudioSharingInteractorImpl(
+            applicationContext,
             localBluetoothManager,
             bluetoothTileDialogAudioSharingRepository,
             testDispatcher,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index e0d1d16..7254154 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
 import com.android.systemui.haptics.msdl.bouncerHapticPlayer
 import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardDismissActionInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardMediaKeyInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -63,6 +64,7 @@
         bouncerHapticPlayer = bouncerHapticPlayer,
         keyguardMediaKeyInteractor = keyguardMediaKeyInteractor,
         bouncerActionButtonInteractor = bouncerActionButtonInteractor,
+        keyguardDismissActionInteractor = keyguardDismissActionInteractor,
     )
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt
index e36ad42..35e85bb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.brightness.domain.interactor.brightnessPolicyEnforcementInteractor
 import com.android.systemui.brightness.domain.interactor.screenBrightnessInteractor
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
@@ -33,6 +34,7 @@
                     hapticsViewModelFactory = sliderHapticsViewModelFactory,
                     brightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor,
                     supportsMirroring = allowsMirroring,
+                    falsingInteractor = falsingInteractor,
                     brightnessWarningToast = brightnessWarningToast,
                 )
             }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt
index 5485f79..5da6c7b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.data.repository
 
 import android.app.admin.devicePolicyManager
+import android.content.res.mainResources
 import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.kosmos.Kosmos
@@ -27,6 +28,7 @@
     Kosmos.Fixture {
         CommunalSettingsRepositoryImpl(
             bgDispatcher = testDispatcher,
+            resources = mainResources,
             featureFlagsClassic = featureFlagsClassic,
             secureSettings = fakeSettings,
             broadcastDispatcher = broadcastDispatcher,
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 3b175853de7..1f7f3bc 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
@@ -55,7 +55,7 @@
         rank: Int = 0,
         category: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
         userId: Int = 0,
-        spanY: Int = CommunalContentSize.HALF.span,
+        spanY: Int = CommunalContentSize.FixedSize.HALF.span,
     ) {
         fakeDatabase[appWidgetId] =
             CommunalWidgetContentModel.Available(
@@ -87,7 +87,7 @@
                 componentName = ComponentName.unflattenFromString(componentName)!!,
                 icon = icon,
                 user = UserHandle(userId),
-                spanY = CommunalContentSize.HALF.span,
+                spanY = CommunalContentSize.FixedSize.HALF.span,
             )
         updateListFromDatabase()
     }
@@ -143,7 +143,7 @@
                 appWidgetId = id,
                 providerInfo = providerInfo,
                 rank = rank,
-                spanY = CommunalContentSize.HALF.span,
+                spanY = CommunalContentSize.FixedSize.HALF.span,
             )
         updateListFromDatabase()
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt
index 65b18c1..5b940f9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt
@@ -16,10 +16,11 @@
 
 package com.android.systemui.display.data.repository
 
+import android.content.testableContext
 import com.android.systemui.kosmos.Kosmos
 
 val Kosmos.fakeDisplayWindowPropertiesRepository by
-    Kosmos.Fixture { FakeDisplayWindowPropertiesRepository() }
+    Kosmos.Fixture { FakeDisplayWindowPropertiesRepository(testableContext) }
 
 var Kosmos.displayWindowPropertiesRepository: DisplayWindowPropertiesRepository by
     Kosmos.Fixture { fakeDisplayWindowPropertiesRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 78ea700..3fc60e3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -48,6 +48,7 @@
 /** Fake [DisplayRepository] implementation for testing. */
 class FakeDisplayRepository @Inject constructor() : DisplayRepository {
     private val flow = MutableStateFlow<Set<Display>>(emptySet())
+    private val displayIdFlow = MutableStateFlow<Set<Int>>(emptySet())
     private val pendingDisplayFlow =
         MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1)
     private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 0)
@@ -57,13 +58,19 @@
         addDisplay(display(type, id = displayId))
     }
 
+    suspend fun addDisplays(vararg displays: Display) {
+        displays.forEach { addDisplay(it) }
+    }
+
     suspend fun addDisplay(display: Display) {
         flow.value += display
+        displayIdFlow.value += display.displayId
         displayAdditionEventFlow.emit(display)
     }
 
     suspend fun removeDisplay(displayId: Int) {
         flow.value = flow.value.filter { it.displayId != displayId }.toSet()
+        displayIdFlow.value = displayIdFlow.value.filter { it != displayId }.toSet()
         displayRemovalEventFlow.emit(displayId)
     }
 
@@ -79,10 +86,13 @@
     override val displays: StateFlow<Set<Display>>
         get() = flow
 
+    override val displayIds: StateFlow<Set<Int>>
+        get() = displayIdFlow
+
     override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
         get() = pendingDisplayFlow
 
-    val _defaultDisplayOff: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    private val _defaultDisplayOff: MutableStateFlow<Boolean> = MutableStateFlow(false)
     override val defaultDisplayOff: Flow<Boolean>
         get() = _defaultDisplayOff.asStateFlow()
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
index 7fd9276..534ded5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
@@ -16,11 +16,16 @@
 
 package com.android.systemui.display.data.repository
 
+import android.content.Context
+import android.view.Display
 import com.android.systemui.display.shared.model.DisplayWindowProperties
 import com.google.common.collect.HashBasedTable
+import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
 
-class FakeDisplayWindowPropertiesRepository : DisplayWindowPropertiesRepository {
+class FakeDisplayWindowPropertiesRepository(private val context: Context) :
+    DisplayWindowPropertiesRepository {
 
     private val properties = HashBasedTable.create<Int, Int, DisplayWindowProperties>()
 
@@ -29,13 +34,26 @@
             ?: DisplayWindowProperties(
                     displayId = displayId,
                     windowType = windowType,
-                    context = mock(),
+                    context = contextWithDisplayId(context, displayId),
                     windowManager = mock(),
                     layoutInflater = mock(),
                 )
                 .also { properties.put(displayId, windowType, it) }
     }
 
+    private fun contextWithDisplayId(context: Context, displayId: Int): Context {
+        val newDisplay = displayWithId(context.display, displayId)
+        return spy(context) {
+            on { getDisplayId() } doReturn displayId
+            on { display } doReturn newDisplay
+            on { displayNoVerify } doReturn newDisplay
+        }
+    }
+
+    private fun displayWithId(display: Display, displayId: Int): Display {
+        return spy(display) { on { getDisplayId() } doReturn displayId }
+    }
+
     /** Sets an instance, just for testing purposes. */
     fun insert(instance: DisplayWindowProperties) {
         properties.put(instance.displayId, instance.windowType, instance)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractorKosmos.kt
new file mode 100644
index 0000000..c2466bb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/domain/interactor/DisplayWindowPropertiesInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.display.domain.interactor
+
+import com.android.systemui.display.data.repository.displayWindowPropertiesRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.displayWindowPropertiesInteractor by
+    Kosmos.Fixture { DisplayWindowPropertiesInteractorImpl(displayWindowPropertiesRepository) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeServiceFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/doze/DozeServiceFake.java
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeServiceFake.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/doze/DozeServiceFake.java
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt
index b24b3ad..71746b5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dreams.ui.viewmodel
 
+import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -23,6 +24,7 @@
 val Kosmos.dreamUserActionsViewModel by
     Kosmos.Fixture {
         DreamUserActionsViewModel(
+            communalInteractor = communalInteractor,
             deviceUnlockedInteractor = deviceUnlockedInteractor,
             shadeInteractor = shadeInteractor,
         )
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
index 721c0b8..1828da5 100644
--- 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
@@ -22,6 +22,7 @@
 import android.hardware.input.fakeInputManager
 import android.view.windowManager
 import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.keyboard.shortcut.data.repository.CustomInputGesturesRepository
 import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository
 import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository
 import com.android.systemui.keyboard.shortcut.data.repository.InputGestureMaps
@@ -55,10 +56,10 @@
     Kosmos.Fixture { AppCategoriesShortcutsSource(windowManager, testDispatcher) }
 
 var Kosmos.shortcutHelperSystemShortcutsSource: KeyboardShortcutGroupsSource by
-    Kosmos.Fixture { SystemShortcutsSource(mainResources) }
+    Kosmos.Fixture { SystemShortcutsSource(mainResources, fakeInputManager.inputManager) }
 
 var Kosmos.shortcutHelperMultiTaskingShortcutsSource: KeyboardShortcutGroupsSource by
-    Kosmos.Fixture { MultitaskingShortcutsSource(mainResources) }
+    Kosmos.Fixture { MultitaskingShortcutsSource(mainResources, applicationContext) }
 
 val Kosmos.shortcutHelperStateRepository by
     Kosmos.Fixture {
@@ -72,7 +73,9 @@
     }
 
 var Kosmos.shortcutHelperInputShortcutsSource: KeyboardShortcutGroupsSource by
-    Kosmos.Fixture { InputShortcutsSource(mainResources, windowManager) }
+    Kosmos.Fixture {
+        InputShortcutsSource(mainResources, windowManager, fakeInputManager.inputManager)
+    }
 
 var Kosmos.shortcutHelperCurrentAppShortcutsSource: KeyboardShortcutGroupsSource by
     Kosmos.Fixture { CurrentAppShortcutsSource(windowManager) }
@@ -102,21 +105,23 @@
         )
     }
 
-val Kosmos.inputGestureMaps by
-        Kosmos.Fixture {
-            InputGestureMaps(applicationContext)
-        }
+val Kosmos.inputGestureMaps by Kosmos.Fixture { InputGestureMaps(applicationContext) }
+
+val Kosmos.customInputGesturesRepository by Kosmos.Fixture {
+    CustomInputGesturesRepository(userTracker, testDispatcher)
+}
 
 val Kosmos.customShortcutCategoriesRepository by
     Kosmos.Fixture {
         CustomShortcutCategoriesRepository(
             shortcutHelperStateRepository,
-            userTracker,
             applicationCoroutineScope,
             testDispatcher,
             shortcutCategoriesUtils,
             applicationContext,
-            inputGestureMaps
+            inputGestureMaps,
+            customInputGesturesRepository,
+            fakeInputManager.inputManager
         )
     }
 
@@ -144,7 +149,10 @@
 
 val Kosmos.shortcutHelperCategoriesInteractor by
     Kosmos.Fixture {
-        ShortcutHelperCategoriesInteractor(defaultShortcutCategoriesRepository) {
+        ShortcutHelperCategoriesInteractor(
+            context = applicationContext,
+            defaultShortcutCategoriesRepository,
+        ) {
             customShortcutCategoriesRepository
         }
     }
@@ -152,6 +160,7 @@
 val Kosmos.shortcutHelperViewModel by
     Kosmos.Fixture {
         ShortcutHelperViewModel(
+            applicationContext,
             mockRoleManager,
             userTracker,
             applicationCoroutineScope,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
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 bd841ab..09f5fd7 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
@@ -17,14 +17,12 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.keyguard.logging.KeyguardLogger
-import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -38,8 +36,6 @@
             dismissInteractor = keyguardDismissInteractor,
             applicationScope = testScope.backgroundScope,
             deviceUnlockedInteractor = { deviceUnlockedInteractor },
-            powerInteractor = powerInteractor,
-            alternateBouncerInteractor = alternateBouncerInteractor,
             shadeInteractor = { shadeInteractor },
             keyguardInteractor = { keyguardInteractor },
             sceneInteractor = { sceneInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
index ace1157..339210c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
@@ -38,8 +38,8 @@
             primaryBouncerInteractor = primaryBouncerInteractor,
             selectedUserInteractor = selectedUserInteractor,
             dismissCallbackRegistry = dismissCallbackRegistry,
-            trustRepository = trustRepository,
             alternateBouncerInteractor = alternateBouncerInteractor,
+            trustRepository = trustRepository,
             powerInteractor = powerInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
index 007d229..f88ed07 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
@@ -16,18 +16,24 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import com.android.internal.widget.lockPatternUtils
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
 
 val Kosmos.keyguardEnabledInteractor by
     Kosmos.Fixture {
         KeyguardEnabledInteractor(
             applicationCoroutineScope,
+            testDispatcher,
             keyguardRepository,
             biometricSettingsRepository,
-            keyguardDismissTransitionInteractor,
+            selectedUserInteractor,
+            lockPatternUtils,
+            { keyguardDismissTransitionInteractor },
             internalTransitionInteractor = internalKeyguardTransitionInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt
index 39236c7..2423949 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt
@@ -24,5 +24,6 @@
         KeyguardLockWhileAwakeInteractor(
             biometricSettingsRepository = biometricSettingsRepository,
             keyguardEnabledInteractor = keyguardEnabledInteractor,
+            keyguardServiceLockNowInteractor = keyguardServiceLockNowInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt
new file mode 100644
index 0000000..29335c5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+
+val Kosmos.keyguardServiceLockNowInteractor by
+    Kosmos.Fixture { KeyguardServiceLockNowInteractor(backgroundScope = testScope) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
index 63e168d..4aa132c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
@@ -41,5 +41,7 @@
             lockPatternUtils,
             fakeSettings,
             selectedUserInteractor,
+            keyguardEnabledInteractor,
+            keyguardServiceLockNowInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
index b5e6f75..c0b39b1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.content.applicationContext
 import android.content.res.mainResources
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
@@ -28,12 +29,13 @@
 val Kosmos.keyguardClockViewModel by
     Kosmos.Fixture {
         KeyguardClockViewModel(
+            context = applicationContext,
             keyguardClockInteractor = keyguardClockInteractor,
             applicationScope = applicationCoroutineScope,
             aodNotificationIconViewModel = notificationIconContainerAlwaysOnDisplayViewModel,
             shadeInteractor = shadeInteractor,
             systemBarUtils = systemBarUtilsProxy,
             configurationInteractor = configurationInteractor,
-            resources = mainResources
+            resources = mainResources,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelKosmos.kt
index a25b29fd..2311c0a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 
 val Kosmos.lockscreenUserActionsViewModel by Fixture {
@@ -27,5 +28,6 @@
         deviceEntryInteractor = deviceEntryInteractor,
         communalInteractor = communalInteractor,
         shadeInteractor = shadeInteractor,
+        occlusionInteractor = sceneContainerOcclusionInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 3d60abf..41cfcea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -66,6 +66,7 @@
 import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeLayoutParams
 import com.android.systemui.shade.domain.interactor.shadeModeInteractor
 import com.android.systemui.shade.shadeController
 import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
@@ -78,6 +79,7 @@
 import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.statusbar.phone.fakeAutoHideControllerStore
 import com.android.systemui.statusbar.phone.keyguardBypassController
 import com.android.systemui.statusbar.phone.scrimController
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
@@ -132,6 +134,8 @@
     val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
     val statusBarStateController by lazy { kosmos.statusBarStateController }
     val statusBarModePerDisplayRepository by lazy { kosmos.fakeStatusBarModePerDisplayRepository }
+    val shadeLayoutParams by lazy { kosmos.shadeLayoutParams }
+    val autoHideControllerStore by lazy { kosmos.fakeAutoHideControllerStore }
     val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
     val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig }
     val sceneInteractor by lazy { kosmos.sceneInteractor }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt
index d631f92..e6256a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.mediaprojection.data.repository
 
 import android.app.ActivityManager
+import android.media.projection.StopReason
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -28,7 +29,7 @@
 
     var stopProjectingInvoked = false
 
-    override suspend fun stopProjecting() {
+    override suspend fun stopProjecting(@StopReason stopReason: Int) {
         stopProjectingInvoked = true
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt
index 8aa7a03..d5637cb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/FakeMediaRouterRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.mediarouter.data.repository
 
+import android.media.projection.StopReason
 import com.android.systemui.statusbar.policy.CastDevice
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -25,7 +26,7 @@
     var lastStoppedDevice: CastDevice? = null
         private set
 
-    override fun stopCasting(device: CastDevice) {
+    override fun stopCasting(device: CastDevice, @StopReason stopReason: Int) {
         lastStoppedDevice = device
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
index 562980d..06822a6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
@@ -19,10 +19,12 @@
 import com.android.internal.logging.InstanceId
 import com.android.systemui.animation.Expandable
 import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.TileDetailsViewModel
 
 class FakeQSTile(var user: Int, var available: Boolean = true) : QSTile {
     private var tileSpec: String? = null
     var destroyed = false
+    var hasDetailsViewModel: Boolean = true
     private var state = QSTile.State()
     val callbacks = mutableListOf<QSTile.Callback>()
 
@@ -91,6 +93,13 @@
         return false
     }
 
+    override fun getDetailsViewModel(): FakeTileDetailsViewModel? {
+        if (hasDetailsViewModel) {
+            return FakeTileDetailsViewModel(tileSpec)
+        }
+        return null
+    }
+
     fun changeState(newState: QSTile.State) {
         state = newState
         callbacks.forEach { it.onStateChanged(state) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt
new file mode 100644
index 0000000..555f019
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.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
+
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import com.android.systemui.plugins.qs.TileDetailsViewModel
+
+class FakeTileDetailsViewModel(var tileSpec: String?) : TileDetailsViewModel() {
+    private var _clickOnSettingsButton = 0
+
+    @Composable
+    override fun GetContentView() {
+        Text(
+            text = "Fake details content",
+            textAlign = TextAlign.Center,
+            fontWeight = FontWeight.ExtraBold,
+        )
+    }
+
+    override fun clickOnSettingsButton() {
+        _clickOnSettingsButton++
+    }
+
+    override fun getTitle(): String {
+        return tileSpec ?: " Fake title"
+    }
+
+    override fun getSubTitle(): String {
+        return tileSpec ?: "Fake sub title"
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt
new file mode 100644
index 0000000..0bf801b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.content.applicationContext
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.qs.external.tileServiceRequestControllerBuilder
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+
+val Kosmos.qsHostAdapter by
+    Kosmos.Fixture {
+        QSHostAdapter(
+            currentTilesInteractor,
+            applicationContext,
+            tileServiceRequestControllerBuilder,
+            applicationCoroutineScope,
+            dumpManager,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index 45d5b38..c574463 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -18,10 +18,12 @@
 
 import android.content.res.mainResources
 import androidx.lifecycle.LifecycleCoroutineScope
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.media.controls.ui.view.qqsMediaHost
 import com.android.systemui.media.controls.ui.view.qsMediaHost
 import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
@@ -57,7 +59,9 @@
                     configurationInteractor,
                     largeScreenHeaderHelper,
                     tileSquishinessInteractor,
+                    falsingInteractor,
                     inFirstPageViewModel,
+                    logcatTableLogBuffer(this@Fixture),
                     mediaInRowInLandscapeViewModelFactory,
                     qqsMediaHost,
                     qsMediaHost,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServiceRequestControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServiceRequestControllerKosmos.kt
new file mode 100644
index 0000000..2966234
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServiceRequestControllerKosmos.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 com.android.app.iUriGrantsManager
+import com.android.internal.logging.uiEventLoggerFake
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.external.ui.dialog.tileRequestDialogComposeDelegateFactory
+import com.android.systemui.qs.instanceIdSequenceFake
+import com.android.systemui.qs.qsHostAdapter
+import com.android.systemui.statusbar.commandQueue
+import com.android.systemui.statusbar.commandline.commandRegistry
+import org.mockito.kotlin.mock
+
+val Kosmos.tileServiceRequestControllerBuilder by
+    Kosmos.Fixture {
+        TileServiceRequestController.Builder(
+            commandQueue,
+            commandRegistry,
+            iUriGrantsManager,
+            tileRequestDialogComposeDelegateFactory,
+        )
+    }
+
+val Kosmos.tileServiceRequestController by
+    Kosmos.Fixture {
+        TileServiceRequestController(
+            qsHostAdapter,
+            commandQueue,
+            commandRegistry,
+            TileRequestDialogEventLogger(uiEventLoggerFake, instanceIdSequenceFake),
+            iUriGrantsManager,
+            tileRequestDialogComposeDelegateFactory,
+            { mock() },
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/FakeTileRequestDialogComposeDelegateFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/FakeTileRequestDialogComposeDelegateFactory.kt
new file mode 100644
index 0000000..1e0ebe4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/FakeTileRequestDialogComposeDelegateFactory.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.ui.dialog
+
+import android.content.DialogInterface
+import com.android.systemui.qs.external.TileData
+import org.mockito.Answers
+import org.mockito.kotlin.mock
+
+class FakeTileRequestDialogComposeDelegateFactory : TileRequestDialogComposeDelegate.Factory {
+    lateinit var tileData: TileData
+    lateinit var clickListener: DialogInterface.OnClickListener
+
+    override fun create(
+        tileData: TileData,
+        dialogListener: DialogInterface.OnClickListener,
+    ): TileRequestDialogComposeDelegate {
+        this.tileData = tileData
+        this.clickListener = dialogListener
+        return mock(defaultAnswer = Answers.RETURNS_MOCKS)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegateKosmos.kt
new file mode 100644
index 0000000..030af61
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegateKosmos.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.qs.external.ui.dialog
+
+import android.content.DialogInterface
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.external.TileData
+import com.android.systemui.qs.external.ui.viewmodel.tileRequestDialogViewModelFactory
+import com.android.systemui.statusbar.phone.systemUIDialogFactory
+
+var Kosmos.tileRequestDialogComposeDelegateFactory by
+    Kosmos.Fixture<TileRequestDialogComposeDelegate.Factory> {
+        object : TileRequestDialogComposeDelegate.Factory {
+            override fun create(
+                tiledata: TileData,
+                dialogListener: DialogInterface.OnClickListener,
+            ): TileRequestDialogComposeDelegate {
+                return TileRequestDialogComposeDelegate(
+                    systemUIDialogFactory,
+                    tileRequestDialogViewModelFactory,
+                    tiledata,
+                    dialogListener,
+                )
+            }
+        }
+    }
+
+val TileRequestDialogComposeDelegate.Factory.fake: FakeTileRequestDialogComposeDelegateFactory
+    get() = this as FakeTileRequestDialogComposeDelegateFactory
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelKosmos.kt
new file mode 100644
index 0000000..7b1797d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelKosmos.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.qs.external.ui.viewmodel
+
+import android.content.Context
+import com.android.app.iUriGrantsManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.qs.external.TileData
+
+val Kosmos.tileRequestDialogViewModelFactory by
+    Kosmos.Fixture {
+        object : TileRequestDialogViewModel.Factory {
+            override fun create(
+                dialogContext: Context,
+                tileData: TileData,
+            ): TileRequestDialogViewModel {
+                return TileRequestDialogViewModel(
+                    iUriGrantsManager,
+                    testDispatcher,
+                    dialogContext,
+                    tileData,
+                )
+            }
+        }
+    }
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 b5a6bf1..6fe860c 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,12 +19,14 @@
 import com.android.systemui.haptics.msdl.tileHapticsViewModelFactoryProvider
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
+import com.android.systemui.qs.panels.ui.viewmodel.detailsViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.infiniteGridViewModelFactory
 
 val Kosmos.infiniteGridLayout by
     Kosmos.Fixture {
         InfiniteGridLayout(
+            detailsViewModel,
             iconTilesViewModel,
             infiniteGridViewModelFactory,
             tileHapticsViewModelFactoryProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelKosmos.kt
new file mode 100644
index 0000000..dc22905
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.qs.pipeline.domain.interactor.currentTilesInteractor
+
+val Kosmos.detailsViewModel by Kosmos.Fixture { DetailsViewModel(currentTilesInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt
new file mode 100644
index 0000000..b8d3ff4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.editModeButtonViewModelFactory by
+    Kosmos.Fixture {
+        object : EditModeButtonViewModel.Factory {
+            override fun create(): EditModeButtonViewModel {
+                return EditModeButtonViewModel(editModeViewModel, falsingInteractor)
+            }
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
index 2e80293..5c71ba2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.development.ui.viewmodel.buildNumberViewModelFactory
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.panels.domain.interactor.paginatedGridInteractor
@@ -28,5 +29,7 @@
             paginatedGridInteractor,
             inFirstPageViewModel,
             buildNumberViewModelFactory,
+            editModeButtonViewModelFactory,
+            falsingInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt
index a908765..de9f629 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.util.mockito.mock
 
 val Kosmos.qsTileViewModelAdaperFactory by
@@ -28,6 +29,7 @@
                     applicationCoroutineScope,
                     mock(),
                     qsTileViewModel,
+                    testDispatcher,
                 )
             }
         }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
index ce103ec..6afc0d80 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModelFactory
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.ui.viewmodel.detailsViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModelFactory
 import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
@@ -34,6 +35,7 @@
                     supportsBrightnessMirroring,
                     tileGridViewModel,
                     editModeViewModel,
+                    detailsViewModel,
                 )
             }
         }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 3300c96..0eca818 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -13,6 +13,7 @@
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.FakeOverlay
+import com.android.systemui.scene.ui.composable.SceneContainerTransitions
 import com.android.systemui.scene.ui.viewmodel.SceneContainerHapticsViewModel
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
@@ -60,6 +61,7 @@
     SceneContainerConfig(
         sceneKeys = sceneKeys,
         initialSceneKey = initialSceneKey,
+        transitions = SceneContainerTransitions,
         overlayKeys = overlayKeys,
         navigationDistances = navigationDistances,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt
new file mode 100644
index 0000000..5c91dc8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.view
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.mockShadeRootView by Kosmos.Fixture { mock<WindowRootView>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt
index 30b4763..4c9e174 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/screenrecord/data/repository/FakeScreenRecordRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.screenrecord.data.repository
 
+import android.media.projection.StopReason
 import com.android.systemui.screenrecord.data.model.ScreenRecordModel
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -25,7 +26,7 @@
 
     var stopRecordingInvoked = false
 
-    override suspend fun stopRecording() {
+    override suspend fun stopRecording(@StopReason stopReason: Int) {
         stopRecordingInvoked = true
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
new file mode 100644
index 0000000..7488397d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.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.shade.data.repository
+
+import android.view.Display
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.display.ShadeDisplayPolicy
+import com.android.systemui.shade.display.SpecificDisplayIdPolicy
+
+val Kosmos.defaultShadeDisplayPolicy: ShadeDisplayPolicy by
+    Kosmos.Fixture { SpecificDisplayIdPolicy(Display.DEFAULT_DISPLAY) }
+
+val Kosmos.shadeDisplaysRepository: MutableShadeDisplaysRepository by
+    Kosmos.Fixture {
+        ShadeDisplaysRepositoryImpl(
+            defaultPolicy = defaultShadeDisplayPolicy,
+            bgScope = testScope.backgroundScope,
+        )
+    }
+
+val Kosmos.fakeShadeDisplaysRepository: FakeShadeDisplayRepository by
+    Kosmos.Fixture { FakeShadeDisplayRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
new file mode 100644
index 0000000..db4df38
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.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.shade.domain.interactor
+
+import android.content.mockedContext
+import android.view.mockWindowManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.ui.view.mockShadeRootView
+import com.android.systemui.shade.ShadeWindowLayoutParams
+import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
+import java.util.Optional
+
+val Kosmos.shadeLayoutParams by Kosmos.Fixture {
+    ShadeWindowLayoutParams.create(mockedContext)
+}
+val Kosmos.shadeDisplaysInteractor by
+    Kosmos.Fixture {
+        ShadeDisplaysInteractor(
+            Optional.of(mockShadeRootView),
+            fakeShadeDisplaysRepository,
+            mockedContext,
+            shadeLayoutParams,
+            mockWindowManager,
+            testScope.backgroundScope,
+            testScope.backgroundScope.coroutineContext,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt
index 7097d31..694bb6e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModelFactory
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor
 import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
 
 val Kosmos.shadeSceneContentViewModel: ShadeSceneContentViewModel by Fixture {
@@ -35,6 +36,7 @@
         brightnessMirrorViewModelFactory = brightnessMirrorViewModelFactory,
         mediaCarouselInteractor = mediaCarouselInteractor,
         shadeInteractor = shadeInteractor,
+        disableFlagsInteractor = disableFlagsInteractor,
         footerActionsViewModelFactory = footerActionsViewModelFactory,
         footerActionsController = footerActionsController,
         unfoldTransitionInteractor = unfoldTransitionInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt
index fcd14d8..d8d4d2b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorKosmos.kt
@@ -20,12 +20,14 @@
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.statusBarChipsLogger
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
+import com.android.systemui.statusbar.phone.ongoingcall.domain.interactor.ongoingCallInteractor
 
 val Kosmos.callChipInteractor: CallChipInteractor by
     Kosmos.Fixture {
         CallChipInteractor(
             scope = applicationCoroutineScope,
             repository = ongoingCallRepository,
+            ongoingCallInteractor = ongoingCallInteractor,
             logger = statusBarChipsLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorFactoryKosmos.kt
new file mode 100644
index 0000000..1c095e1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorFactoryKosmos.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.statusbar.chips.notification.domain.interactor
+
+import com.android.systemui.activity.data.repository.activityManagerRepository
+import com.android.systemui.activity.data.repository.fake
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
+
+val Kosmos.singleNotificationChipInteractorFactory: SingleNotificationChipInteractor.Factory by
+    Kosmos.Fixture {
+        SingleNotificationChipInteractor.Factory { startingModel ->
+            SingleNotificationChipInteractor(
+                startingModel,
+                activityManagerRepository.fake,
+                logBuffer = statusBarChipsLogger,
+            )
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorKosmos.kt
index 74c7611..03e9f3d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorKosmos.kt
@@ -17,6 +17,16 @@
 package com.android.systemui.statusbar.chips.notification.domain.interactor
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 
 val Kosmos.statusBarNotificationChipsInteractor: StatusBarNotificationChipsInteractor by
-    Kosmos.Fixture { StatusBarNotificationChipsInteractor() }
+    Kosmos.Fixture {
+        StatusBarNotificationChipsInteractor(
+            testScope.backgroundScope,
+            activeNotificationsInteractor,
+            singleNotificationChipInteractorFactory,
+            logBuffer = statusBarChipsLogger,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
index 68b28ad..4bcce86 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
@@ -19,13 +19,8 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
-import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 
 val Kosmos.notifChipsViewModel: NotifChipsViewModel by
     Kosmos.Fixture {
-        NotifChipsViewModel(
-            applicationCoroutineScope,
-            activeNotificationsInteractor,
-            statusBarNotificationChipsInteractor,
-        )
+        NotifChipsViewModel(applicationCoroutineScope, statusBarNotificationChipsInteractor)
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt
index 9197dcd..2d88ae8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.core
 
 import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.phone.AutoHideController
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStatePerDisplayRepository
 import kotlinx.coroutines.CoroutineScope
@@ -36,6 +37,7 @@
         statusBarModeRepository: StatusBarModePerDisplayRepository,
         statusBarInitializer: StatusBarInitializer,
         statusBarWindowController: StatusBarWindowController,
+        autoHideController: AutoHideController,
     ): StatusBarOrchestrator =
         mock<StatusBarOrchestrator>().also { createdOrchestrators[displayId] = it }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index 28edae7..8c37bd7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.statusbar.data.repository.statusBarModeRepository
 import com.android.systemui.statusbar.mockNotificationRemoteInputManager
 import com.android.systemui.statusbar.phone.mockAutoHideController
+import com.android.systemui.statusbar.phone.multiDisplayAutoHideControllerStore
 import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStatePerDisplayRepository
 import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore
 import com.android.systemui.statusbar.window.fakeStatusBarWindowController
@@ -50,9 +51,9 @@
             fakeStatusBarInitializer,
             fakeStatusBarWindowController,
             applicationCoroutineScope.coroutineContext,
+            mockAutoHideController,
             mockDemoModeController,
             mockPluginDependencyProvider,
-            mockAutoHideController,
             mockNotificationRemoteInputManager,
             { mockNotificationShadeWindowViewController },
             mockShadeSurface,
@@ -80,6 +81,7 @@
             statusBarInitializerStore,
             statusBarWindowControllerStore,
             statusBarInitializerStore,
+            multiDisplayAutoHideControllerStore,
             privacyDotWindowControllerStore,
             lightBarControllerStore,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 2ec8016..c6ae15d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -29,7 +29,6 @@
     key: String,
     groupKey: String? = null,
     whenTime: Long = 0L,
-    isPromoted: Boolean = false,
     isAmbient: Boolean = false,
     isRowDismissed: Boolean = false,
     isSilent: Boolean = false,
@@ -53,7 +52,6 @@
         key = key,
         groupKey = groupKey,
         whenTime = whenTime,
-        isPromoted = isPromoted,
         isAmbient = isAmbient,
         isRowDismissed = isRowDismissed,
         isSilent = isSilent,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt
index 980d65f..7de22d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt
@@ -16,13 +16,27 @@
 
 package com.android.systemui.statusbar.notification.data.repository
 
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus
 import kotlinx.coroutines.flow.MutableStateFlow
 
 class FakeHeadsUpRowRepository(override val key: String, override val elementKey: Any = Any()) :
     HeadsUpRowRepository {
-    constructor(key: String, isPinned: Boolean) : this(key = key) {
-        this.isPinned.value = isPinned
+    constructor(
+        key: String,
+        elementKey: Any = Any(),
+        isPinned: Boolean,
+    ) : this(key = key, elementKey = elementKey) {
+        this.pinnedStatus.value = if (isPinned) PinnedStatus.PinnedBySystem else PinnedStatus.NotPinned
     }
 
-    override val isPinned: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    constructor(
+        key: String,
+        elementKey: Any = Any(),
+        pinnedStatus: PinnedStatus,
+    ) : this(key = key, elementKey = elementKey) {
+        this.pinnedStatus.value = pinnedStatus
+    }
+
+    override val pinnedStatus: MutableStateFlow<PinnedStatus> =
+        MutableStateFlow(PinnedStatus.NotPinned)
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
index 067193f..f7acae9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
@@ -19,13 +19,8 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.notification.collection.provider.sectionStyleProvider
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
-import com.android.systemui.statusbar.notification.promoted.promotedNotificationsProvider
 
 val Kosmos.renderNotificationListInteractor by
     Kosmos.Fixture {
-        RenderNotificationListInteractor(
-            activeNotificationListRepository,
-            sectionStyleProvider,
-            promotedNotificationsProvider,
-        )
+        RenderNotificationListInteractor(activeNotificationListRepository, sectionStyleProvider)
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerKosmos.kt
new file mode 100644
index 0000000..de9485d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.statusbar.notification.headsup
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.headsUpManager by Fixture { mock<HeadsUpManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt
new file mode 100644
index 0000000..88caf6e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.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.statusbar.notification.promoted
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+class FakePromotedNotificationsProvider : PromotedNotificationsProvider {
+    val promotedEntries = mutableSetOf<NotificationEntry>()
+
+    override fun shouldPromote(entry: NotificationEntry): Boolean {
+        return promotedEntries.contains(entry)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
new file mode 100644
index 0000000..5e9f12b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.promotedNotificationContentExtractor by
+    Kosmos.Fixture {
+        PromotedNotificationContentExtractor(
+            promotedNotificationsProvider,
+            applicationContext,
+            promotedNotificationLogger,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLoggerKosmos.kt
new file mode 100644
index 0000000..2805d1e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+val Kosmos.promotedNotificationLogger by
+    Kosmos.Fixture { PromotedNotificationLogger(logcatLogBuffer("PromotedNotifLog")) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
index a7aa0b4..580f617 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
@@ -18,4 +18,5 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.promotedNotificationsProvider by Kosmos.Fixture { PromotedNotificationsProviderImpl() }
+var Kosmos.promotedNotificationsProvider: PromotedNotificationsProvider by
+    Kosmos.Fixture { PromotedNotificationsProviderImpl() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 31d3908..2d3f68f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.media.controls.util.MediaFeatureFlag
 import com.android.systemui.media.dialog.MediaOutputDialogManager
 import com.android.systemui.plugins.ActivityStarter
@@ -60,9 +61,13 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
 import com.android.systemui.statusbar.notification.icon.IconBuilder
 import com.android.systemui.statusbar.notification.icon.IconManager
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProviderImpl
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.CoordinateOnClickListener
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener
@@ -77,7 +82,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.SmartActionInflaterImpl
 import com.android.systemui.statusbar.policy.SmartReplyConstants
 import com.android.systemui.statusbar.policy.SmartReplyInflaterImpl
@@ -221,6 +225,17 @@
                 Mockito.mock(ConversationNotificationManager::class.java, STUB_ONLY),
             )
 
+        val promotedNotificationsProvider = PromotedNotificationsProviderImpl()
+        val promotedNotificationLog = logcatLogBuffer("PromotedNotifLog")
+        val promotedNotificationLogger = PromotedNotificationLogger(promotedNotificationLog)
+
+        val promotedNotificationContentExtractor =
+            PromotedNotificationContentExtractor(
+                promotedNotificationsProvider,
+                context,
+                promotedNotificationLogger,
+            )
+
         mContentBinder =
             if (NotificationRowContentBinderRefactor.isEnabled)
                 NotificationRowContentBinderImpl(
@@ -231,6 +246,7 @@
                     smartReplyStateInflater,
                     notifLayoutInflaterFactoryProvider,
                     Mockito.mock(HeadsUpStyleProvider::class.java, STUB_ONLY),
+                    promotedNotificationContentExtractor,
                     Mockito.mock(NotificationRowContentBinderLogger::class.java, STUB_ONLY),
                 )
             else
@@ -243,6 +259,7 @@
                     smartReplyStateInflater,
                     notifLayoutInflaterFactoryProvider,
                     Mockito.mock(HeadsUpStyleProvider::class.java, STUB_ONLY),
+                    promotedNotificationContentExtractor,
                     Mockito.mock(NotificationRowContentBinderLogger::class.java, STUB_ONLY),
                 )
         mContentBinder.setInflateSynchronously(true)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt
index 1e3897b..ec54c33 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl
 import com.android.systemui.statusbar.notification.collection.notifCollection
 import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
-import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
 
 var Kosmos.onUserInteractionCallback: OnUserInteractionCallback by
     Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmKosmos.kt
index e20ce27..a5c4bfd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmKosmos.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.policy.AvalancheController
+import com.android.systemui.statusbar.notification.headsup.AvalancheController
 import com.android.systemui.util.mockito.mock
 
 var Kosmos.stackScrollAlgorithmSectionProvider by Fixture {
@@ -29,6 +29,4 @@
     mock<StackScrollAlgorithm.BypassController>()
 }
 
-var Kosmos.avalancheController by Fixture {
-    mock<AvalancheController>()
-}
+var Kosmos.avalancheController by Fixture { mock<AvalancheController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 7fbf4e4..d1619b7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
@@ -71,6 +72,8 @@
         shadeInteractor = shadeInteractor,
         notificationStackAppearanceInteractor = notificationStackAppearanceInteractor,
         alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+        alternateBouncerToPrimaryBouncerTransitionViewModel =
+            alternateBouncerToPrimaryBouncerTransitionViewModel,
         aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
         aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
         aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
index 9c3f510..e972c2c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
-import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
 
 val Kosmos.windowRootViewVisibilityInteractor by Fixture {
     WindowRootViewVisibilityInteractor(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt
index 090ce31..b99e93a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt
@@ -16,9 +16,49 @@
 
 package com.android.systemui.statusbar.phone
 
+import android.content.Context
+import android.os.Handler
+import android.view.Display
+import android.view.IWindowManager
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.fakeDisplayWindowPropertiesRepository
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.Mockito.mock
 
-val Kosmos.mockAutoHideController by Kosmos.Fixture { mock<AutoHideController>() }
+val Kosmos.mockAutoHideController: AutoHideController by
+    Kosmos.Fixture { mock(AutoHideController::class.java) }
 
-var Kosmos.autoHideController by Kosmos.Fixture { mockAutoHideController }
+val Kosmos.fakeAutoHideControllerFactory by Kosmos.Fixture { FakeAutoHideControllerFactory() }
+
+val Kosmos.multiDisplayAutoHideControllerStore by
+    Kosmos.Fixture {
+        MultiDisplayAutoHideControllerStore(
+            applicationCoroutineScope,
+            displayRepository,
+            fakeDisplayWindowPropertiesRepository,
+            fakeAutoHideControllerFactory,
+        )
+    }
+
+val Kosmos.fakeAutoHideControllerStore by Kosmos.Fixture { FakeAutoHideControllerStore() }
+
+class FakeAutoHideControllerFactory :
+    AutoHideControllerImpl.Factory(mock(Handler::class.java), mock(IWindowManager::class.java)) {
+
+    override fun create(context: Context): AutoHideControllerImpl {
+        return mock(AutoHideControllerImpl::class.java)
+    }
+}
+
+class FakeAutoHideControllerStore : AutoHideControllerStore {
+
+    private val perDisplayMocks = mutableMapOf<Int, AutoHideController>()
+
+    override val defaultDisplay: AutoHideController
+        get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+    override fun forDisplay(displayId: Int): AutoHideController {
+        return perDisplayMocks.computeIfAbsent(displayId) { mock(AutoHideController::class.java) }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
index 4a6757d..f86824a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
@@ -26,10 +26,10 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.shade.domain.interactor.shadeLockscreenInteractor
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
 import com.android.systemui.statusbar.notificationShadeWindowController
 import com.android.systemui.statusbar.policy.batteryController
 import com.android.systemui.statusbar.policy.deviceProvisionedController
-import com.android.systemui.statusbar.policy.headsUpManager
 import com.android.systemui.statusbar.pulseExpansionHandler
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManagerKosmos.kt
new file mode 100644
index 0000000..5b7f23b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManagerKosmos.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.statusbar.phone
+
+import android.content.applicationContext
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
+import com.android.systemui.statusbar.notificationShadeWindowController
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.shadeTouchableRegionManager by
+    Kosmos.Fixture {
+        ShadeTouchableRegionManager(
+            applicationContext,
+            notificationShadeWindowController,
+            configurationController,
+            headsUpManager,
+            shadeInteractor,
+            { sceneInteractor },
+            JavaAdapter(testScope.backgroundScope),
+            mock<UnlockedScreenOffAnimationController>(),
+            primaryBouncerInteractor,
+            alternateBouncerInteractor,
+            communalSceneInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
index c6684af..6083414 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.statusbar.commandQueue
 import com.android.systemui.statusbar.notification.collection.provider.launchFullScreenIntentProvider
 import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
 import com.android.systemui.statusbar.notification.notificationTransitionAnimatorControllerProvider
 import com.android.systemui.statusbar.notification.row.onUserInteractionCallback
 import com.android.systemui.statusbar.notificationClickNotifier
@@ -43,7 +44,6 @@
 import com.android.systemui.statusbar.notificationPresenter
 import com.android.systemui.statusbar.notificationRemoteInputManager
 import com.android.systemui.statusbar.notificationShadeWindowController
-import com.android.systemui.statusbar.policy.headsUpManager
 import com.android.systemui.statusbar.policy.keyguardStateController
 import com.android.systemui.wmshell.bubblesManager
 import java.util.Optional
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt
deleted file mode 100644
index 87ea147..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt
+++ /dev/null
@@ -1,49 +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.statusbar.phone
-
-import android.content.applicationContext
-import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
-import com.android.systemui.communal.domain.interactor.communalSceneInteractor
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.statusbar.notificationShadeWindowController
-import com.android.systemui.statusbar.policy.configurationController
-import com.android.systemui.statusbar.policy.headsUpManager
-import com.android.systemui.util.kotlin.JavaAdapter
-import com.android.systemui.util.mockito.mock
-import org.mockito.Mockito.mock
-
-var Kosmos.statusBarTouchableRegionManager by
-    Kosmos.Fixture {
-        StatusBarTouchableRegionManager(
-            applicationContext,
-            notificationShadeWindowController,
-            configurationController,
-            headsUpManager,
-            shadeInteractor,
-            { sceneInteractor },
-            JavaAdapter(testScope.backgroundScope),
-            mock<UnlockedScreenOffAnimationController>(),
-            primaryBouncerInteractor,
-            alternateBouncerInteractor,
-            communalSceneInteractor,
-        )
-    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.kt
new file mode 100644
index 0000000..51fb36f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorKosmos.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.statusbar.phone.ongoingcall.domain.interactor
+
+import com.android.systemui.activity.data.repository.activityManagerRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+
+val Kosmos.ongoingCallInteractor: OngoingCallInteractor by
+    Kosmos.Fixture {
+      OngoingCallInteractor(
+          scope = applicationCoroutineScope,
+          activeNotificationsInteractor = activeNotificationsInteractor,
+          activityManagerRepository = activityManagerRepository,
+          logBuffer = logcatLogBuffer("OngoingCallInteractorKosmos"),
+      )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index 03e4c89..eb17237 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
 import com.android.systemui.statusbar.events.domain.interactor.systemStatusEventAnimationInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.phone.domain.interactor.lightsOutInteractor
 import com.android.systemui.statusbar.pipeline.shared.domain.interactor.collapsedStatusBarInteractor
 
@@ -35,6 +36,7 @@
             collapsedStatusBarInteractor,
             lightsOutInteractor,
             activeNotificationsInteractor,
+            headsUpNotificationInteractor,
             keyguardTransitionInteractor,
             keyguardInteractor,
             sceneInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt
index 2df0c7a5..da6b2ae 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeCastController.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy
 
+import android.media.projection.StopReason
 import java.io.PrintWriter
 
 class FakeCastController : CastController {
@@ -45,7 +46,7 @@
 
     override fun startCasting(device: CastDevice?) {}
 
-    override fun stopCasting(device: CastDevice?) {
+    override fun stopCasting(device: CastDevice?, @StopReason stopReason: Int) {
         lastStoppedDevice = device
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/HeadsUpManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/HeadsUpManagerKosmos.kt
deleted file mode 100644
index a4db00c..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/HeadsUpManagerKosmos.kt
+++ /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.
- */
-
-package com.android.systemui.statusbar.policy
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
-
-var Kosmos.headsUpManager by Fixture { mock<HeadsUpManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/FakeVelocityTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/FakeVelocityTracker.kt
new file mode 100644
index 0000000..f12089a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/FakeVelocityTracker.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.touchpad.ui.gesture
+
+import android.view.MotionEvent
+import com.android.systemui.touchpad.tutorial.ui.gesture.Velocity
+import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker
+
+class FakeVelocityTracker : VelocityTracker {
+
+    private var fakeVelocity = Velocity(0f)
+
+    override fun calculateVelocity(): Velocity {
+        return fakeVelocity
+    }
+
+    override fun accept(event: MotionEvent) {}
+
+    fun setVelocity(velocity: Velocity) {
+        fakeVelocity = velocity
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/VelocityTrackerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/VelocityTrackerKosmos.kt
new file mode 100644
index 0000000..3b61e21
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/ui/gesture/VelocityTrackerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.touchpad.ui.gesture
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.fakeVelocityTracker: FakeVelocityTracker by Kosmos.Fixture { FakeVelocityTracker() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java
index 2249bc0..857dc85 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckerCastController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.media.projection.StopReason;
 import android.testing.LeakCheck;
 
 import com.android.systemui.statusbar.policy.CastController;
@@ -51,7 +52,7 @@
     }
 
     @Override
-    public void stopCasting(CastDevice device) {
+    public void stopCasting(CastDevice device, @StopReason int stopReason) {
 
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorKosmos.kt
new file mode 100644
index 0000000..ddd0014
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogSafetyWarningInteractorKosmos.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.volume.dialog.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.volumeDialogSafetyWarningInteractor: VolumeDialogSafetyWarningInteractor by
+    Kosmos.Fixture {
+        VolumeDialogSafetyWarningInteractor(
+            volumeDialogStateInteractor,
+            volumeDialogVisibilityInteractor,
+        )
+    }
diff --git a/packages/Vcn/TEST_MAPPING b/packages/Vcn/TEST_MAPPING
index bde88fe..9722a83 100644
--- a/packages/Vcn/TEST_MAPPING
+++ b/packages/Vcn/TEST_MAPPING
@@ -1,4 +1,12 @@
 {
+  "presubmit": [
+    {
+      "name": "FrameworksVcnTests"
+    },
+    {
+      "name": "CtsVcnTestCases"
+    }
+  ],
   "postsubmit": [
     {
       "name": "FrameworksVcnTests"
diff --git a/packages/Vcn/flags/Android.bp b/packages/Vcn/flags/Android.bp
new file mode 100644
index 0000000..3943c6f
--- /dev/null
+++ b/packages/Vcn/flags/Android.bp
@@ -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 {
+    default_team: "trendy_team_enigma",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aconfig_declarations {
+    name: "android.net.vcn.flags-aconfig",
+    package: "android.net.vcn",
+    container: "com.android.tethering",
+    exportable: true,
+    srcs: [
+        "flags.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "android.net.vcn.flags-aconfig-java-export",
+    aconfig_declarations: "android.net.vcn.flags-aconfig",
+    mode: "exported",
+    min_sdk_version: "35",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/core/java/android/net/vcn/flags.aconfig b/packages/Vcn/flags/flags.aconfig
similarity index 100%
rename from core/java/android/net/vcn/flags.aconfig
rename to packages/Vcn/flags/flags.aconfig
diff --git a/packages/Vcn/framework-b/Android.bp b/packages/Vcn/framework-b/Android.bp
index be64bb1..c312116 100644
--- a/packages/Vcn/framework-b/Android.bp
+++ b/packages/Vcn/framework-b/Android.bp
@@ -19,6 +19,18 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+filegroup {
+    name: "vcn-utils-platform-sources",
+    srcs: [
+        "src/android/net/vcn/persistablebundleutils/**/*.java",
+        "src/android/net/vcn/util/**/*.java",
+    ],
+    path: "src",
+    visibility: [
+        "//frameworks/base", // For VpnProfile.java and Vpn.java
+    ],
+}
+
 java_defaults {
     name: "framework-connectivity-b-defaults",
     sdk_version: "module_current",
@@ -27,7 +39,25 @@
 
     srcs: [
         "src/**/*.java",
+        "src/**/*.aidl",
     ],
+
+    libs: [
+        "android.net.ipsec.ike.stubs.module_lib",
+        "app-compat-annotations",
+        "framework-wifi.stubs.module_lib",
+        "unsupportedappusage",
+    ],
+    static_libs: [
+        //TODO:375213246 Use a non-exported flag lib when VCN is in mainline
+        "android.net.vcn.flags-aconfig-java-export",
+    ],
+    aidl: {
+        include_dirs: [
+            // For connectivity-framework classes such as Network.aidl, NetworkCapabilities.aidl
+            "packages/modules/Connectivity/framework/aidl-export",
+        ],
+    },
 }
 
 java_sdk_library {
@@ -36,9 +66,72 @@
         "framework-connectivity-b-defaults",
     ],
 
+    //TODO: b/375213246 Use "framework-connectivity-jarjar-rules" when VCN is
+    // in mainline
+    jarjar_rules: "framework-vcn-jarjar-rules.txt",
+
     permitted_packages: [
+        "android.net",
+        "android.net.vcn",
+        "com.android.server.vcn.util",
+
+    ],
+    api_packages: [
+        "android.net",
         "android.net.vcn",
     ],
 
-    // TODO: b/375213246 Expose this library to Tethering module
+    // Allow VCN APIs to reference APIs in IKE and Connectivity
+    stub_only_libs: [
+        "android.net.ipsec.ike.stubs.module_lib",
+        "framework-connectivity.stubs.module_lib",
+    ],
+
+    // To use non-jarjard names of utilities such as android.util.IndentingPrintWriter
+    impl_only_libs: [
+        "framework-connectivity-pre-jarjar",
+    ],
+
+    aconfig_declarations: [
+        //TODO:375213246 Use a non-exported flag lib when VCN is in mainline
+        "android.net.vcn.flags-aconfig-java-export",
+    ],
+
+    impl_library_visibility: [
+        // Using for test only
+        "//cts/tests/netlegacy22.api",
+        "//cts/tests/tests/vcn",
+        "//external/sl4a:__subpackages__",
+        "//frameworks/base/core/tests/bandwidthtests",
+        "//frameworks/base/core/tests/benchmarks",
+        "//frameworks/base/core/tests/utillib",
+        "//frameworks/base/services/tests/VpnTests",
+        "//frameworks/base/tests/vcn",
+        "//frameworks/opt/telephony/tests/telephonytests",
+        "//packages/modules/CaptivePortalLogin/tests",
+        "//packages/modules/Connectivity/staticlibs/testutils",
+        "//packages/modules/Connectivity/staticlibs/tests:__subpackages__",
+        "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
+        "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/Connectivity/thread/tests:__subpackages__",
+        "//packages/modules/IPsec/tests/iketests",
+        "//packages/modules/NetworkStack",
+        "//packages/modules/NetworkStack/tests:__subpackages__",
+        "//packages/modules/Wifi/service/tests/wifitests",
+    ],
+
+    apex_available: [
+        // TODO: b/374174952 Remove it when VCN modularization is released
+        "//apex_available:platform",
+
+        "com.android.tethering",
+    ],
+}
+
+java_library {
+    name: "framework-connectivity-b-pre-jarjar",
+    defaults: ["framework-connectivity-b-defaults"],
+    libs: [
+        "framework-connectivity-pre-jarjar",
+    ],
 }
diff --git a/packages/Vcn/framework-b/api/current.txt b/packages/Vcn/framework-b/api/current.txt
index d802177..831b741 100644
--- a/packages/Vcn/framework-b/api/current.txt
+++ b/packages/Vcn/framework-b/api/current.txt
@@ -1 +1,123 @@
 // Signature format: 2.0
+package android.net.vcn {
+
+  public final class VcnCellUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
+    method public int getCbs();
+    method public int getDun();
+    method public int getIms();
+    method public int getInternet();
+    method public int getMms();
+    method @NonNull public java.util.Set<java.lang.String> getOperatorPlmnIds();
+    method public int getOpportunistic();
+    method public int getRcs();
+    method public int getRoaming();
+    method @NonNull public java.util.Set<java.lang.Integer> getSimSpecificCarrierIds();
+  }
+
+  public static final class VcnCellUnderlyingNetworkTemplate.Builder {
+    ctor public VcnCellUnderlyingNetworkTemplate.Builder();
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate build();
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setCbs(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setDun(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setIms(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setInternet(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMetered(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setMms(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOperatorPlmnIds(@NonNull java.util.Set<java.lang.String>);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setOpportunistic(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRcs(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setRoaming(int);
+    method @NonNull public android.net.vcn.VcnCellUnderlyingNetworkTemplate.Builder setSimSpecificCarrierIds(@NonNull java.util.Set<java.lang.Integer>);
+  }
+
+  public final class VcnConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
+    method @NonNull public java.util.Set<java.lang.Integer> getRestrictedUnderlyingNetworkTransports();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR;
+  }
+
+  public static final class VcnConfig.Builder {
+    ctor public VcnConfig.Builder(@NonNull android.content.Context);
+    method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig);
+    method @NonNull public android.net.vcn.VcnConfig build();
+    method @NonNull public android.net.vcn.VcnConfig.Builder setRestrictedUnderlyingNetworkTransports(@NonNull java.util.Set<java.lang.Integer>);
+  }
+
+  public final class VcnGatewayConnectionConfig {
+    method @NonNull public int[] getExposedCapabilities();
+    method @NonNull public String getGatewayConnectionName();
+    method @IntRange(from=0x500) public int getMaxMtu();
+    method public int getMinUdpPort4500NatTimeoutSeconds();
+    method @NonNull public long[] getRetryIntervalsMillis();
+    method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
+    method public boolean hasGatewayOption(int);
+    method @FlaggedApi("android.net.vcn.safe_mode_config") public boolean isSafeModeEnabled();
+    field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET = -1; // 0xffffffff
+    field public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; // 0x0
+  }
+
+  public static final class VcnGatewayConnectionConfig.Builder {
+    ctor public VcnGatewayConnectionConfig.Builder(@NonNull String, @NonNull android.net.ipsec.ike.IkeTunnelConnectionParams);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addGatewayOption(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeGatewayOption(int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]);
+    method @FlaggedApi("android.net.vcn.safe_mode_config") @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setSafeModeEnabled(boolean);
+    method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>);
+  }
+
+  public class VcnManager {
+    method @RequiresPermission("carrier privileges") public void clearVcnConfig(@NonNull android.os.ParcelUuid) throws java.io.IOException;
+    method @NonNull public java.util.List<android.os.ParcelUuid> getConfiguredSubscriptionGroups();
+    method public void registerVcnStatusCallback(@NonNull android.os.ParcelUuid, @NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnStatusCallback);
+    method @RequiresPermission("carrier privileges") public void setVcnConfig(@NonNull android.os.ParcelUuid, @NonNull android.net.vcn.VcnConfig) throws java.io.IOException;
+    method public void unregisterVcnStatusCallback(@NonNull android.net.vcn.VcnManager.VcnStatusCallback);
+    field public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1; // 0x1
+    field public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0; // 0x0
+    field public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2; // 0x2
+    field public static final int VCN_STATUS_CODE_ACTIVE = 2; // 0x2
+    field public static final int VCN_STATUS_CODE_INACTIVE = 1; // 0x1
+    field public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0; // 0x0
+    field public static final int VCN_STATUS_CODE_SAFE_MODE = 3; // 0x3
+  }
+
+  public abstract static class VcnManager.VcnStatusCallback {
+    ctor public VcnManager.VcnStatusCallback();
+    method public abstract void onGatewayConnectionError(@NonNull String, int, @Nullable Throwable);
+    method public abstract void onStatusChanged(int);
+  }
+
+  public abstract class VcnUnderlyingNetworkTemplate {
+    method public int getMetered();
+    method public int getMinEntryDownstreamBandwidthKbps();
+    method public int getMinEntryUpstreamBandwidthKbps();
+    method public int getMinExitDownstreamBandwidthKbps();
+    method public int getMinExitUpstreamBandwidthKbps();
+    field public static final int MATCH_ANY = 0; // 0x0
+    field public static final int MATCH_FORBIDDEN = 2; // 0x2
+    field public static final int MATCH_REQUIRED = 1; // 0x1
+  }
+
+  public final class VcnWifiUnderlyingNetworkTemplate extends android.net.vcn.VcnUnderlyingNetworkTemplate {
+    method @NonNull public java.util.Set<java.lang.String> getSsids();
+  }
+
+  public static final class VcnWifiUnderlyingNetworkTemplate.Builder {
+    ctor public VcnWifiUnderlyingNetworkTemplate.Builder();
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate build();
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMetered(int);
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinDownstreamBandwidthKbps(int, int);
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setMinUpstreamBandwidthKbps(int, int);
+    method @NonNull public android.net.vcn.VcnWifiUnderlyingNetworkTemplate.Builder setSsids(@NonNull java.util.Set<java.lang.String>);
+  }
+
+}
+
diff --git a/packages/Vcn/framework-b/api/module-lib-current.txt b/packages/Vcn/framework-b/api/module-lib-current.txt
index d802177..8961b28 100644
--- a/packages/Vcn/framework-b/api/module-lib-current.txt
+++ b/packages/Vcn/framework-b/api/module-lib-current.txt
@@ -1 +1,28 @@
 // Signature format: 2.0
+package android.net {
+
+  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class ConnectivityFrameworkInitializerBaklava {
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static void registerServiceWrappers();
+  }
+
+}
+
+package android.net.vcn {
+
+  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public final class VcnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int describeContents();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public long getApplicableRedactions();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public int getMinUdpPort4500NatTimeoutSeconds();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.TransportInfo makeCopy(long);
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnTransportInfo> CREATOR;
+  }
+
+  @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public static final class VcnTransportInfo.Builder {
+    ctor @FlaggedApi("android.net.vcn.mainline_vcn_module_api") public VcnTransportInfo.Builder();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo build();
+    method @FlaggedApi("android.net.vcn.mainline_vcn_module_api") @NonNull public android.net.vcn.VcnTransportInfo.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
+  }
+
+}
+
diff --git a/packages/Vcn/framework-b/api/system-current.txt b/packages/Vcn/framework-b/api/system-current.txt
index d802177..9c5a677 100644
--- a/packages/Vcn/framework-b/api/system-current.txt
+++ b/packages/Vcn/framework-b/api/system-current.txt
@@ -1 +1,23 @@
 // Signature format: 2.0
+package android.net.vcn {
+
+  public class VcnManager {
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void addVcnNetworkPolicyChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.vcn.VcnNetworkPolicyResult applyVcnNetworkPolicy(@NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void removeVcnNetworkPolicyChangeListener(@NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener);
+  }
+
+  public static interface VcnManager.VcnNetworkPolicyChangeListener {
+    method public void onPolicyChanged();
+  }
+
+  public final class VcnNetworkPolicyResult implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
+    method public boolean isTeardownRequested();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnNetworkPolicyResult> CREATOR;
+  }
+
+}
+
diff --git a/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt b/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt
new file mode 100644
index 0000000..757043b
--- /dev/null
+++ b/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt
@@ -0,0 +1,2 @@
+rule android.net.vcn.persistablebundleutils.** android.net.vcn.module.repackaged.persistablebundleutils.@1
+rule android.net.vcn.util.** android.net.vcn.module.repackaged.util.@1
\ No newline at end of file
diff --git a/core/java/android/net/ConnectivityFrameworkInitializerBaklava.java b/packages/Vcn/framework-b/src/android/net/ConnectivityFrameworkInitializerBaklava.java
similarity index 100%
rename from core/java/android/net/ConnectivityFrameworkInitializerBaklava.java
rename to packages/Vcn/framework-b/src/android/net/ConnectivityFrameworkInitializerBaklava.java
diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/packages/Vcn/framework-b/src/android/net/vcn/IVcnManagementService.aidl
similarity index 100%
rename from core/java/android/net/vcn/IVcnManagementService.aidl
rename to packages/Vcn/framework-b/src/android/net/vcn/IVcnManagementService.aidl
diff --git a/core/java/android/net/vcn/IVcnStatusCallback.aidl b/packages/Vcn/framework-b/src/android/net/vcn/IVcnStatusCallback.aidl
similarity index 100%
rename from core/java/android/net/vcn/IVcnStatusCallback.aidl
rename to packages/Vcn/framework-b/src/android/net/vcn/IVcnStatusCallback.aidl
diff --git a/core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl b/packages/Vcn/framework-b/src/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl
similarity index 100%
rename from core/java/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl
rename to packages/Vcn/framework-b/src/android/net/vcn/IVcnUnderlyingNetworkPolicyListener.aidl
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
new file mode 100644
index 0000000..ded9415
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java
@@ -0,0 +1,740 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.getMatchCriteriaString;
+import static android.net.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
+import static android.net.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
+import static android.net.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
+import static android.net.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.NetworkCapabilities;
+import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.os.PersistableBundle;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This class represents a configuration for a network template class of underlying cellular
+ * networks.
+ *
+ * <p>See {@link VcnUnderlyingNetworkTemplate}
+ */
+public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate {
+    private static final String ALLOWED_NETWORK_PLMN_IDS_KEY = "mAllowedNetworkPlmnIds";
+    @NonNull private final Set<String> mAllowedNetworkPlmnIds;
+    private static final String ALLOWED_SPECIFIC_CARRIER_IDS_KEY = "mAllowedSpecificCarrierIds";
+    @NonNull private final Set<Integer> mAllowedSpecificCarrierIds;
+
+    private static final String ROAMING_MATCH_KEY = "mRoamingMatchCriteria";
+    private static final int DEFAULT_ROAMING_MATCH_CRITERIA = MATCH_ANY;
+    private final int mRoamingMatchCriteria;
+
+    private static final String OPPORTUNISTIC_MATCH_KEY = "mOpportunisticMatchCriteria";
+    private static final int DEFAULT_OPPORTUNISTIC_MATCH_CRITERIA = MATCH_ANY;
+    private final int mOpportunisticMatchCriteria;
+
+    private static final String CAPABILITIES_MATCH_CRITERIA_KEY = "mCapabilitiesMatchCriteria";
+    @NonNull private final Map<Integer, Integer> mCapabilitiesMatchCriteria;
+
+    private static final Map<Integer, Integer> CAPABILITIES_MATCH_CRITERIA_DEFAULT;
+
+    static {
+        Map<Integer, Integer> capsMatchCriteria = new HashMap<>();
+        capsMatchCriteria.put(NET_CAPABILITY_CBS, MATCH_ANY);
+        capsMatchCriteria.put(NET_CAPABILITY_DUN, MATCH_ANY);
+        capsMatchCriteria.put(NET_CAPABILITY_IMS, MATCH_ANY);
+        capsMatchCriteria.put(NET_CAPABILITY_INTERNET, MATCH_REQUIRED);
+        capsMatchCriteria.put(NET_CAPABILITY_MMS, MATCH_ANY);
+        capsMatchCriteria.put(NET_CAPABILITY_RCS, MATCH_ANY);
+
+        CAPABILITIES_MATCH_CRITERIA_DEFAULT = Collections.unmodifiableMap(capsMatchCriteria);
+    }
+
+    private VcnCellUnderlyingNetworkTemplate(
+            int meteredMatchCriteria,
+            int minEntryUpstreamBandwidthKbps,
+            int minExitUpstreamBandwidthKbps,
+            int minEntryDownstreamBandwidthKbps,
+            int minExitDownstreamBandwidthKbps,
+            Set<String> allowedNetworkPlmnIds,
+            Set<Integer> allowedSpecificCarrierIds,
+            int roamingMatchCriteria,
+            int opportunisticMatchCriteria,
+            Map<Integer, Integer> capabilitiesMatchCriteria) {
+        super(
+                NETWORK_PRIORITY_TYPE_CELL,
+                meteredMatchCriteria,
+                minEntryUpstreamBandwidthKbps,
+                minExitUpstreamBandwidthKbps,
+                minEntryDownstreamBandwidthKbps,
+                minExitDownstreamBandwidthKbps);
+        mAllowedNetworkPlmnIds = new ArraySet<>(allowedNetworkPlmnIds);
+        mAllowedSpecificCarrierIds = new ArraySet<>(allowedSpecificCarrierIds);
+        mRoamingMatchCriteria = roamingMatchCriteria;
+        mOpportunisticMatchCriteria = opportunisticMatchCriteria;
+        mCapabilitiesMatchCriteria = new HashMap<>(capabilitiesMatchCriteria);
+
+        validate();
+    }
+
+    /** @hide */
+    @Override
+    protected void validate() {
+        super.validate();
+        validatePlmnIds(mAllowedNetworkPlmnIds);
+        validateCapabilitiesMatchCriteria(mCapabilitiesMatchCriteria);
+        Objects.requireNonNull(mAllowedSpecificCarrierIds, "matchingCarrierIds is null");
+        validateMatchCriteria(mRoamingMatchCriteria, "mRoamingMatchCriteria");
+        validateMatchCriteria(mOpportunisticMatchCriteria, "mOpportunisticMatchCriteria");
+    }
+
+    private static void validatePlmnIds(Set<String> matchingOperatorPlmnIds) {
+        Objects.requireNonNull(matchingOperatorPlmnIds, "matchingOperatorPlmnIds is null");
+
+        // A valid PLMN is a concatenation of MNC and MCC, and thus consists of 5 or 6 decimal
+        // digits.
+        for (String id : matchingOperatorPlmnIds) {
+            if ((id.length() == 5 || id.length() == 6) && id.matches("[0-9]+")) {
+                continue;
+            } else {
+                throw new IllegalArgumentException("Found invalid PLMN ID: " + id);
+            }
+        }
+    }
+
+    private static void validateCapabilitiesMatchCriteria(
+            Map<Integer, Integer> capabilitiesMatchCriteria) {
+        Objects.requireNonNull(capabilitiesMatchCriteria, "capabilitiesMatchCriteria is null");
+
+        boolean requiredCapabilityFound = false;
+        for (Map.Entry<Integer, Integer> entry : capabilitiesMatchCriteria.entrySet()) {
+            final int capability = entry.getKey();
+            final int matchCriteria = entry.getValue();
+
+            Preconditions.checkArgument(
+                    CAPABILITIES_MATCH_CRITERIA_DEFAULT.containsKey(capability),
+                    "NetworkCapability " + capability + "out of range");
+            validateMatchCriteria(matchCriteria, "capability " + capability);
+
+            requiredCapabilityFound |= (matchCriteria == MATCH_REQUIRED);
+        }
+
+        if (!requiredCapabilityFound) {
+            throw new IllegalArgumentException("No required capabilities found");
+        }
+    }
+
+    /** @hide */
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public static VcnCellUnderlyingNetworkTemplate fromPersistableBundle(
+            @NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle is null");
+
+        final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY);
+
+        final int minEntryUpstreamBandwidthKbps =
+                in.getInt(MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+        final int minExitUpstreamBandwidthKbps =
+                in.getInt(MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+        final int minEntryDownstreamBandwidthKbps =
+                in.getInt(MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+        final int minExitDownstreamBandwidthKbps =
+                in.getInt(MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+
+        final PersistableBundle plmnIdsBundle =
+                in.getPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY);
+        Objects.requireNonNull(plmnIdsBundle, "plmnIdsBundle is null");
+        final Set<String> allowedNetworkPlmnIds =
+                new ArraySet<String>(
+                        PersistableBundleUtils.toList(plmnIdsBundle, STRING_DESERIALIZER));
+
+        final PersistableBundle specificCarrierIdsBundle =
+                in.getPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY);
+        Objects.requireNonNull(specificCarrierIdsBundle, "specificCarrierIdsBundle is null");
+        final Set<Integer> allowedSpecificCarrierIds =
+                new ArraySet<Integer>(
+                        PersistableBundleUtils.toList(
+                                specificCarrierIdsBundle, INTEGER_DESERIALIZER));
+
+        final PersistableBundle capabilitiesMatchCriteriaBundle =
+                in.getPersistableBundle(CAPABILITIES_MATCH_CRITERIA_KEY);
+        final Map<Integer, Integer> capabilitiesMatchCriteria;
+        if (capabilitiesMatchCriteriaBundle == null) {
+            capabilitiesMatchCriteria = CAPABILITIES_MATCH_CRITERIA_DEFAULT;
+        } else {
+            capabilitiesMatchCriteria =
+                    PersistableBundleUtils.toMap(
+                            capabilitiesMatchCriteriaBundle,
+                            INTEGER_DESERIALIZER,
+                            INTEGER_DESERIALIZER);
+        }
+
+        final int roamingMatchCriteria = in.getInt(ROAMING_MATCH_KEY);
+        final int opportunisticMatchCriteria = in.getInt(OPPORTUNISTIC_MATCH_KEY);
+
+        return new VcnCellUnderlyingNetworkTemplate(
+                meteredMatchCriteria,
+                minEntryUpstreamBandwidthKbps,
+                minExitUpstreamBandwidthKbps,
+                minEntryDownstreamBandwidthKbps,
+                minExitDownstreamBandwidthKbps,
+                allowedNetworkPlmnIds,
+                allowedSpecificCarrierIds,
+                roamingMatchCriteria,
+                opportunisticMatchCriteria,
+                capabilitiesMatchCriteria);
+    }
+
+    /** @hide */
+    @Override
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public PersistableBundle toPersistableBundle() {
+        final PersistableBundle result = super.toPersistableBundle();
+
+        final PersistableBundle plmnIdsBundle =
+                PersistableBundleUtils.fromList(
+                        new ArrayList<>(mAllowedNetworkPlmnIds), STRING_SERIALIZER);
+        result.putPersistableBundle(ALLOWED_NETWORK_PLMN_IDS_KEY, plmnIdsBundle);
+
+        final PersistableBundle specificCarrierIdsBundle =
+                PersistableBundleUtils.fromList(
+                        new ArrayList<>(mAllowedSpecificCarrierIds), INTEGER_SERIALIZER);
+        result.putPersistableBundle(ALLOWED_SPECIFIC_CARRIER_IDS_KEY, specificCarrierIdsBundle);
+
+        final PersistableBundle capabilitiesMatchCriteriaBundle =
+                PersistableBundleUtils.fromMap(
+                        mCapabilitiesMatchCriteria, INTEGER_SERIALIZER, INTEGER_SERIALIZER);
+        result.putPersistableBundle(
+                CAPABILITIES_MATCH_CRITERIA_KEY, capabilitiesMatchCriteriaBundle);
+
+        result.putInt(ROAMING_MATCH_KEY, mRoamingMatchCriteria);
+        result.putInt(OPPORTUNISTIC_MATCH_KEY, mOpportunisticMatchCriteria);
+
+        return result;
+    }
+
+    /**
+     * Retrieve the matching operator PLMN IDs, or an empty set if any PLMN ID is acceptable.
+     *
+     * @see Builder#setOperatorPlmnIds(Set)
+     */
+    @NonNull
+    public Set<String> getOperatorPlmnIds() {
+        return Collections.unmodifiableSet(mAllowedNetworkPlmnIds);
+    }
+
+    /**
+     * Retrieve the matching sim specific carrier IDs, or an empty set if any sim specific carrier
+     * ID is acceptable.
+     *
+     * @see Builder#setSimSpecificCarrierIds(Set)
+     */
+    @NonNull
+    public Set<Integer> getSimSpecificCarrierIds() {
+        return Collections.unmodifiableSet(mAllowedSpecificCarrierIds);
+    }
+
+    /**
+     * Return the matching criteria for roaming networks.
+     *
+     * @see Builder#setRoaming(int)
+     */
+    @MatchCriteria
+    public int getRoaming() {
+        return mRoamingMatchCriteria;
+    }
+
+    /**
+     * Return the matching criteria for opportunistic cellular subscriptions.
+     *
+     * @see Builder#setOpportunistic(int)
+     */
+    @MatchCriteria
+    public int getOpportunistic() {
+        return mOpportunisticMatchCriteria;
+    }
+
+    /**
+     * Returns the matching criteria for CBS networks.
+     *
+     * @see Builder#setCbs(int)
+     */
+    @MatchCriteria
+    public int getCbs() {
+        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_CBS);
+    }
+
+    /**
+     * Returns the matching criteria for DUN networks.
+     *
+     * @see Builder#setDun(int)
+     */
+    @MatchCriteria
+    public int getDun() {
+        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_DUN);
+    }
+    /**
+     * Returns the matching criteria for IMS networks.
+     *
+     * @see Builder#setIms(int)
+     */
+    @MatchCriteria
+    public int getIms() {
+        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_IMS);
+    }
+    /**
+     * Returns the matching criteria for INTERNET networks.
+     *
+     * @see Builder#setInternet(int)
+     */
+    @MatchCriteria
+    public int getInternet() {
+        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_INTERNET);
+    }
+    /**
+     * Returns the matching criteria for MMS networks.
+     *
+     * @see Builder#setMms(int)
+     */
+    @MatchCriteria
+    public int getMms() {
+        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_MMS);
+    }
+
+    /**
+     * Returns the matching criteria for RCS networks.
+     *
+     * @see Builder#setRcs(int)
+     */
+    @MatchCriteria
+    public int getRcs() {
+        return mCapabilitiesMatchCriteria.get(NET_CAPABILITY_RCS);
+    }
+
+    /** @hide */
+    @Override
+    public Map<Integer, Integer> getCapabilitiesMatchCriteria() {
+        return Collections.unmodifiableMap(new HashMap<>(mCapabilitiesMatchCriteria));
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                super.hashCode(),
+                mAllowedNetworkPlmnIds,
+                mAllowedSpecificCarrierIds,
+                mCapabilitiesMatchCriteria,
+                mRoamingMatchCriteria,
+                mOpportunisticMatchCriteria);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (!super.equals(other)) {
+            return false;
+        }
+
+        if (!(other instanceof VcnCellUnderlyingNetworkTemplate)) {
+            return false;
+        }
+
+        final VcnCellUnderlyingNetworkTemplate rhs = (VcnCellUnderlyingNetworkTemplate) other;
+        return Objects.equals(mAllowedNetworkPlmnIds, rhs.mAllowedNetworkPlmnIds)
+                && Objects.equals(mAllowedSpecificCarrierIds, rhs.mAllowedSpecificCarrierIds)
+                && Objects.equals(mCapabilitiesMatchCriteria, rhs.mCapabilitiesMatchCriteria)
+                && mRoamingMatchCriteria == rhs.mRoamingMatchCriteria
+                && mOpportunisticMatchCriteria == rhs.mOpportunisticMatchCriteria;
+    }
+
+    /** @hide */
+    @Override
+    void dumpTransportSpecificFields(IndentingPrintWriter pw) {
+        if (!mAllowedNetworkPlmnIds.isEmpty()) {
+            pw.println("mAllowedNetworkPlmnIds: " + mAllowedNetworkPlmnIds);
+        }
+        if (!mAllowedNetworkPlmnIds.isEmpty()) {
+            pw.println("mAllowedSpecificCarrierIds: " + mAllowedSpecificCarrierIds);
+        }
+        pw.println("mCapabilitiesMatchCriteria: " + mCapabilitiesMatchCriteria);
+        if (mRoamingMatchCriteria != DEFAULT_ROAMING_MATCH_CRITERIA) {
+            pw.println("mRoamingMatchCriteria: " + getMatchCriteriaString(mRoamingMatchCriteria));
+        }
+        if (mOpportunisticMatchCriteria != DEFAULT_OPPORTUNISTIC_MATCH_CRITERIA) {
+            pw.println(
+                    "mOpportunisticMatchCriteria: "
+                            + getMatchCriteriaString(mOpportunisticMatchCriteria));
+        }
+    }
+
+    /** This class is used to incrementally build VcnCellUnderlyingNetworkTemplate objects. */
+    public static final class Builder {
+        private int mMeteredMatchCriteria = DEFAULT_METERED_MATCH_CRITERIA;
+
+        @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
+        @NonNull private final Set<Integer> mAllowedSpecificCarrierIds = new ArraySet<>();
+        @NonNull private final Map<Integer, Integer> mCapabilitiesMatchCriteria = new HashMap<>();
+
+        private int mRoamingMatchCriteria = DEFAULT_ROAMING_MATCH_CRITERIA;
+        private int mOpportunisticMatchCriteria = DEFAULT_OPPORTUNISTIC_MATCH_CRITERIA;
+
+        private int mMinEntryUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+        private int mMinExitUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+        private int mMinEntryDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+        private int mMinExitDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+
+        /** Construct a Builder object. */
+        public Builder() {
+            mCapabilitiesMatchCriteria.putAll(CAPABILITIES_MATCH_CRITERIA_DEFAULT);
+        }
+
+        /**
+         * Set the matching criteria for metered networks.
+         *
+         * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one
+         * without NET_CAPABILITY_NOT_METERED). A template where setMetered(MATCH_FORBIDDEN) will
+         * only match a network that is not metered (one with NET_CAPABILITY_NOT_METERED).
+         *
+         * @param matchCriteria the matching criteria for metered networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED
+         */
+        // The matching getter is defined in the super class. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMetered()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setMetered(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setMetered");
+
+            mMeteredMatchCriteria = matchCriteria;
+            return this;
+        }
+
+        /**
+         * Set operator PLMN IDs with which a network can match this template.
+         *
+         * <p>This is used to distinguish cases where roaming agreements may dictate a different
+         * priority from a partner's networks.
+         *
+         * @param operatorPlmnIds the matching operator PLMN IDs in String. Network with one of the
+         *     matching PLMN IDs can match this template. If the set is empty, any PLMN ID will
+         *     match. The default is an empty set. A valid PLMN is a concatenation of MNC and MCC,
+         *     and thus consists of 5 or 6 decimal digits.
+         * @see SubscriptionInfo#getMccString()
+         * @see SubscriptionInfo#getMncString()
+         */
+        @NonNull
+        public Builder setOperatorPlmnIds(@NonNull Set<String> operatorPlmnIds) {
+            validatePlmnIds(operatorPlmnIds);
+
+            mAllowedNetworkPlmnIds.clear();
+            mAllowedNetworkPlmnIds.addAll(operatorPlmnIds);
+            return this;
+        }
+
+        /**
+         * Set sim specific carrier IDs with which a network can match this template.
+         *
+         * @param simSpecificCarrierIds the matching sim specific carrier IDs. Network with one of
+         *     the sim specific carrier IDs can match this template. If the set is empty, any
+         *     carrier ID will match. The default is an empty set.
+         * @see TelephonyManager#getSimSpecificCarrierId()
+         */
+        @NonNull
+        public Builder setSimSpecificCarrierIds(@NonNull Set<Integer> simSpecificCarrierIds) {
+            Objects.requireNonNull(simSpecificCarrierIds, "simSpecificCarrierIds is null");
+
+            mAllowedSpecificCarrierIds.clear();
+            mAllowedSpecificCarrierIds.addAll(simSpecificCarrierIds);
+            return this;
+        }
+
+        /**
+         * Set the matching criteria for roaming networks.
+         *
+         * <p>A template where setRoaming(MATCH_REQUIRED) will only match roaming networks (one
+         * without NET_CAPABILITY_NOT_ROAMING). A template where setRoaming(MATCH_FORBIDDEN) will
+         * only match a network that is not roaming (one with NET_CAPABILITY_NOT_ROAMING).
+         *
+         * @param matchCriteria the matching criteria for roaming networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING
+         */
+        @NonNull
+        public Builder setRoaming(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setRoaming");
+
+            mRoamingMatchCriteria = matchCriteria;
+            return this;
+        }
+
+        /**
+         * Set the matching criteria for opportunistic cellular subscriptions.
+         *
+         * @param matchCriteria the matching criteria for opportunistic cellular subscriptions.
+         *     Defaults to {@link #MATCH_ANY}.
+         * @see SubscriptionManager#setOpportunistic(boolean, int)
+         */
+        @NonNull
+        public Builder setOpportunistic(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setOpportunistic");
+
+            mOpportunisticMatchCriteria = matchCriteria;
+            return this;
+        }
+
+        /**
+         * Set the minimum upstream bandwidths that this template will match.
+         *
+         * <p>This template will not match a network that does not provide at least the bandwidth
+         * passed as the entry bandwidth, except in the case that the network is selected as the VCN
+         * Gateway Connection's underlying network, where it will continue to match until the
+         * bandwidth drops under the exit bandwidth.
+         *
+         * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
+         * invalid case where a network fulfills the entry criteria, but at the same time fails the
+         * exit criteria.
+         *
+         * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
+         * {@link NetworkCapabilities}. The provided estimates will be used without modification.
+         *
+         * @param minEntryUpstreamBandwidthKbps the minimum accepted upstream bandwidth for networks
+         *     that ARE NOT the already-selected underlying network, or {@code 0} to disable this
+         *     requirement. Disabled by default.
+         * @param minExitUpstreamBandwidthKbps the minimum accepted upstream bandwidth for a network
+         *     that IS the already-selected underlying network, or {@code 0} to disable this
+         *     requirement. Disabled by default.
+         * @return this {@link Builder} instance, for chaining
+         */
+        @NonNull
+        // The getter for the two integers are separated, and in the superclass. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMinEntryUpstreamBandwidthKbps()} and {@link
+        // VcnUnderlyingNetworkTemplate#getMinExitUpstreamBandwidthKbps()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setMinUpstreamBandwidthKbps(
+                int minEntryUpstreamBandwidthKbps, int minExitUpstreamBandwidthKbps) {
+            validateMinBandwidthKbps(minEntryUpstreamBandwidthKbps, minExitUpstreamBandwidthKbps);
+
+            mMinEntryUpstreamBandwidthKbps = minEntryUpstreamBandwidthKbps;
+            mMinExitUpstreamBandwidthKbps = minExitUpstreamBandwidthKbps;
+
+            return this;
+        }
+
+        /**
+         * Set the minimum upstream bandwidths that this template will match.
+         *
+         * <p>This template will not match a network that does not provide at least the bandwidth
+         * passed as the entry bandwidth, except in the case that the network is selected as the VCN
+         * Gateway Connection's underlying network, where it will continue to match until the
+         * bandwidth drops under the exit bandwidth.
+         *
+         * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
+         * invalid case where a network fulfills the entry criteria, but at the same time fails the
+         * exit criteria.
+         *
+         * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
+         * {@link NetworkCapabilities}. The provided estimates will be used without modification.
+         *
+         * @param minEntryDownstreamBandwidthKbps the minimum accepted downstream bandwidth for
+         *     networks that ARE NOT the already-selected underlying network, or {@code 0} to
+         *     disable this requirement. Disabled by default.
+         * @param minExitDownstreamBandwidthKbps the minimum accepted downstream bandwidth for a
+         *     network that IS the already-selected underlying network, or {@code 0} to disable this
+         *     requirement. Disabled by default.
+         * @return this {@link Builder} instance, for chaining
+         */
+        @NonNull
+        // The getter for the two integers are separated, and in the superclass. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMinEntryDownstreamBandwidthKbps()} and {@link
+        // VcnUnderlyingNetworkTemplate#getMinExitDownstreamBandwidthKbps()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setMinDownstreamBandwidthKbps(
+                int minEntryDownstreamBandwidthKbps, int minExitDownstreamBandwidthKbps) {
+            validateMinBandwidthKbps(
+                    minEntryDownstreamBandwidthKbps, minExitDownstreamBandwidthKbps);
+
+            mMinEntryDownstreamBandwidthKbps = minEntryDownstreamBandwidthKbps;
+            mMinExitDownstreamBandwidthKbps = minExitDownstreamBandwidthKbps;
+
+            return this;
+        }
+
+        /**
+         * Sets the matching criteria for CBS networks.
+         *
+         * <p>A template where {@code setCbs(MATCH_REQUIRED)} is called will only match CBS networks
+         * (ones with NET_CAPABILITY_CBS). A template where {@code setCbs(MATCH_FORBIDDEN)} is
+         * called will only match networks that do not support CBS (ones without
+         * NET_CAPABILITY_CBS).
+         *
+         * @param matchCriteria the matching criteria for CBS networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_CBS
+         */
+        @NonNull
+        public Builder setCbs(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setCbs");
+
+            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_CBS, matchCriteria);
+            return this;
+        }
+
+        /**
+         * Sets the matching criteria for DUN networks.
+         *
+         * <p>A template where {@code setDun(MATCH_REQUIRED)} is called will only match DUN networks
+         * (ones with NET_CAPABILITY_DUN). A template where {@code setDun(MATCH_FORBIDDEN)} is
+         * called will only match networks that do not support DUN (ones without
+         * NET_CAPABILITY_DUN).
+         *
+         * @param matchCriteria the matching criteria for DUN networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_DUN
+         */
+        @NonNull
+        public Builder setDun(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setDun");
+
+            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_DUN, matchCriteria);
+            return this;
+        }
+
+        /**
+         * Sets the matching criteria for IMS networks.
+         *
+         * <p>A template where {@code setIms(MATCH_REQUIRED)} is called will only match IMS networks
+         * (ones with NET_CAPABILITY_IMS). A template where {@code setIms(MATCH_FORBIDDEN)} is
+         * called will only match networks that do not support IMS (ones without
+         * NET_CAPABILITY_IMS).
+         *
+         * @param matchCriteria the matching criteria for IMS networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_IMS
+         */
+        @NonNull
+        public Builder setIms(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setIms");
+
+            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_IMS, matchCriteria);
+            return this;
+        }
+
+        /**
+         * Sets the matching criteria for INTERNET networks.
+         *
+         * <p>A template where {@code setInternet(MATCH_REQUIRED)} is called will only match
+         * INTERNET networks (ones with NET_CAPABILITY_INTERNET). A template where {@code
+         * setInternet(MATCH_FORBIDDEN)} is called will only match networks that do not support
+         * INTERNET (ones without NET_CAPABILITY_INTERNET).
+         *
+         * @param matchCriteria the matching criteria for INTERNET networks. Defaults to {@link
+         *     #MATCH_REQUIRED}.
+         * @see NetworkCapabilities#NET_CAPABILITY_INTERNET
+         */
+        @NonNull
+        public Builder setInternet(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setInternet");
+
+            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_INTERNET, matchCriteria);
+            return this;
+        }
+
+        /**
+         * Sets the matching criteria for MMS networks.
+         *
+         * <p>A template where {@code setMms(MATCH_REQUIRED)} is called will only match MMS networks
+         * (ones with NET_CAPABILITY_MMS). A template where {@code setMms(MATCH_FORBIDDEN)} is
+         * called will only match networks that do not support MMS (ones without
+         * NET_CAPABILITY_MMS).
+         *
+         * @param matchCriteria the matching criteria for MMS networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_MMS
+         */
+        @NonNull
+        public Builder setMms(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setMms");
+
+            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_MMS, matchCriteria);
+            return this;
+        }
+
+        /**
+         * Sets the matching criteria for RCS networks.
+         *
+         * <p>A template where {@code setRcs(MATCH_REQUIRED)} is called will only match RCS networks
+         * (ones with NET_CAPABILITY_RCS). A template where {@code setRcs(MATCH_FORBIDDEN)} is
+         * called will only match networks that do not support RCS (ones without
+         * NET_CAPABILITY_RCS).
+         *
+         * @param matchCriteria the matching criteria for RCS networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_RCS
+         */
+        @NonNull
+        public Builder setRcs(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setRcs");
+
+            mCapabilitiesMatchCriteria.put(NET_CAPABILITY_RCS, matchCriteria);
+            return this;
+        }
+
+        /** Build the VcnCellUnderlyingNetworkTemplate. */
+        @NonNull
+        public VcnCellUnderlyingNetworkTemplate build() {
+            return new VcnCellUnderlyingNetworkTemplate(
+                    mMeteredMatchCriteria,
+                    mMinEntryUpstreamBandwidthKbps,
+                    mMinExitUpstreamBandwidthKbps,
+                    mMinEntryDownstreamBandwidthKbps,
+                    mMinExitDownstreamBandwidthKbps,
+                    mAllowedNetworkPlmnIds,
+                    mAllowedSpecificCarrierIds,
+                    mRoamingMatchCriteria,
+                    mOpportunisticMatchCriteria,
+                    mCapabilitiesMatchCriteria);
+        }
+    }
+}
diff --git a/core/java/android/net/vcn/VcnConfig.aidl b/packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.aidl
similarity index 100%
rename from core/java/android/net/vcn/VcnConfig.aidl
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.aidl
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.java
new file mode 100644
index 0000000..0d0efb2
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnConfig.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
+import static android.net.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This class represents a configuration for a Virtual Carrier Network.
+ *
+ * <p>Each {@link VcnGatewayConnectionConfig} instance added represents a connection that will be
+ * brought up on demand based on active {@link NetworkRequest}(s).
+ *
+ * @see VcnManager for more information on the Virtual Carrier Network feature
+ */
+public final class VcnConfig implements Parcelable {
+    @NonNull private static final String TAG = VcnConfig.class.getSimpleName();
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"TRANSPORT_"},
+            value = {
+                NetworkCapabilities.TRANSPORT_CELLULAR,
+                NetworkCapabilities.TRANSPORT_WIFI,
+            })
+    @Target({ElementType.TYPE_USE})
+    public @interface VcnUnderlyingNetworkTransport {}
+
+    private static final Set<Integer> ALLOWED_TRANSPORTS = new ArraySet<>();
+
+    static {
+        ALLOWED_TRANSPORTS.add(TRANSPORT_WIFI);
+        ALLOWED_TRANSPORTS.add(TRANSPORT_CELLULAR);
+        ALLOWED_TRANSPORTS.add(TRANSPORT_TEST);
+    }
+
+    private static final String PACKAGE_NAME_KEY = "mPackageName";
+    @NonNull private final String mPackageName;
+
+    private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs";
+    @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs;
+
+    private static final Set<Integer> RESTRICTED_TRANSPORTS_DEFAULT =
+            Collections.singleton(TRANSPORT_WIFI);
+    private static final String RESTRICTED_TRANSPORTS_KEY = "mRestrictedTransports";
+    @NonNull private final Set<Integer> mRestrictedTransports;
+
+    private static final String IS_TEST_MODE_PROFILE_KEY = "mIsTestModeProfile";
+    private final boolean mIsTestModeProfile;
+
+    private VcnConfig(
+            @NonNull String packageName,
+            @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs,
+            @NonNull Set<Integer> restrictedTransports,
+            boolean isTestModeProfile) {
+        mPackageName = packageName;
+        mGatewayConnectionConfigs =
+                Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs));
+        mRestrictedTransports = Collections.unmodifiableSet(new ArraySet<>(restrictedTransports));
+        mIsTestModeProfile = isTestModeProfile;
+
+        validate();
+    }
+
+    /**
+     * Deserializes a VcnConfig from a PersistableBundle.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public VcnConfig(@NonNull PersistableBundle in) {
+        mPackageName = in.getString(PACKAGE_NAME_KEY);
+
+        final PersistableBundle gatewayConnectionConfigsBundle =
+                in.getPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY);
+        mGatewayConnectionConfigs =
+                new ArraySet<>(
+                        PersistableBundleUtils.toList(
+                                gatewayConnectionConfigsBundle, VcnGatewayConnectionConfig::new));
+
+        final PersistableBundle restrictedTransportsBundle =
+                in.getPersistableBundle(RESTRICTED_TRANSPORTS_KEY);
+        if (restrictedTransportsBundle == null) {
+            // RESTRICTED_TRANSPORTS_KEY was added in U and does not exist in VcnConfigs created in
+            // older platforms
+            mRestrictedTransports = RESTRICTED_TRANSPORTS_DEFAULT;
+        } else {
+            mRestrictedTransports =
+                    new ArraySet<Integer>(
+                            PersistableBundleUtils.toList(
+                                    restrictedTransportsBundle, INTEGER_DESERIALIZER));
+        }
+
+        mIsTestModeProfile = in.getBoolean(IS_TEST_MODE_PROFILE_KEY);
+
+        validate();
+    }
+
+    private void validate() {
+        Objects.requireNonNull(mPackageName, "packageName was null");
+        Preconditions.checkCollectionNotEmpty(
+                mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty");
+
+        final Iterator<Integer> iterator = mRestrictedTransports.iterator();
+        while (iterator.hasNext()) {
+            final int transport = iterator.next();
+            if (!ALLOWED_TRANSPORTS.contains(transport)) {
+                iterator.remove();
+                Log.w(
+                        TAG,
+                        "Found invalid transport "
+                                + transport
+                                + " which might be from a new version of VcnConfig");
+            }
+
+            if (transport == TRANSPORT_TEST && !mIsTestModeProfile) {
+                throw new IllegalArgumentException(
+                        "Found TRANSPORT_TEST in a non-test-mode profile");
+            }
+        }
+    }
+
+    /**
+     * Retrieve the package name of the provisioning app.
+     *
+     * @hide
+     */
+    @NonNull
+    public String getProvisioningPackageName() {
+        return mPackageName;
+    }
+
+    /** Retrieves the set of configured GatewayConnection(s). */
+    @NonNull
+    public Set<VcnGatewayConnectionConfig> getGatewayConnectionConfigs() {
+        return Collections.unmodifiableSet(mGatewayConnectionConfigs);
+    }
+
+    /**
+     * Retrieve the transports that will be restricted by the VCN.
+     *
+     * @see Builder#setRestrictedUnderlyingNetworkTransports(Set)
+     */
+    @NonNull
+    public Set<@VcnUnderlyingNetworkTransport Integer> getRestrictedUnderlyingNetworkTransports() {
+        return Collections.unmodifiableSet(mRestrictedTransports);
+    }
+
+    /**
+     * Returns whether or not this VcnConfig is restricted to test networks.
+     *
+     * @hide
+     */
+    public boolean isTestModeProfile() {
+        return mIsTestModeProfile;
+    }
+
+    /**
+     * Serializes this object to a PersistableBundle.
+     *
+     * @hide
+     */
+    @NonNull
+    public PersistableBundle toPersistableBundle() {
+        final PersistableBundle result = new PersistableBundle();
+
+        result.putString(PACKAGE_NAME_KEY, mPackageName);
+
+        final PersistableBundle gatewayConnectionConfigsBundle =
+                PersistableBundleUtils.fromList(
+                        new ArrayList<>(mGatewayConnectionConfigs),
+                        VcnGatewayConnectionConfig::toPersistableBundle);
+        result.putPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY, gatewayConnectionConfigsBundle);
+
+        final PersistableBundle restrictedTransportsBundle =
+                PersistableBundleUtils.fromList(
+                        new ArrayList<>(mRestrictedTransports), INTEGER_SERIALIZER);
+        result.putPersistableBundle(RESTRICTED_TRANSPORTS_KEY, restrictedTransportsBundle);
+
+        result.putBoolean(IS_TEST_MODE_PROFILE_KEY, mIsTestModeProfile);
+
+        return result;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mPackageName, mGatewayConnectionConfigs, mRestrictedTransports, mIsTestModeProfile);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (!(other instanceof VcnConfig)) {
+            return false;
+        }
+
+        final VcnConfig rhs = (VcnConfig) other;
+        return mPackageName.equals(rhs.mPackageName)
+                && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs)
+                && mRestrictedTransports.equals(rhs.mRestrictedTransports)
+                && mIsTestModeProfile == rhs.mIsTestModeProfile;
+    }
+
+    // Parcelable methods
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeParcelable(toPersistableBundle(), flags);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<VcnConfig> CREATOR =
+            new Parcelable.Creator<VcnConfig>() {
+                @NonNull
+                public VcnConfig createFromParcel(Parcel in) {
+                    return new VcnConfig((PersistableBundle) in.readParcelable(null, android.os.PersistableBundle.class));
+                }
+
+                @NonNull
+                public VcnConfig[] newArray(int size) {
+                    return new VcnConfig[size];
+                }
+            };
+
+    /** This class is used to incrementally build {@link VcnConfig} objects. */
+    public static final class Builder {
+        @NonNull private final String mPackageName;
+
+        @NonNull
+        private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>();
+
+        @NonNull private final Set<Integer> mRestrictedTransports = new ArraySet<>();
+
+        private boolean mIsTestModeProfile = false;
+
+        public Builder(@NonNull Context context) {
+            Objects.requireNonNull(context, "context was null");
+
+            mPackageName = context.getOpPackageName();
+            mRestrictedTransports.addAll(RESTRICTED_TRANSPORTS_DEFAULT);
+        }
+
+        /**
+         * Adds a configuration for an individual gateway connection.
+         *
+         * @param gatewayConnectionConfig the configuration for an individual gateway connection
+         * @return this {@link Builder} instance, for chaining
+         * @throws IllegalArgumentException if a VcnGatewayConnectionConfig has already been set for
+         *     this {@link VcnConfig} with the same GatewayConnection name (as returned via {@link
+         *     VcnGatewayConnectionConfig#getGatewayConnectionName()}).
+         */
+        @NonNull
+        public Builder addGatewayConnectionConfig(
+                @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) {
+            Objects.requireNonNull(gatewayConnectionConfig, "gatewayConnectionConfig was null");
+
+            for (final VcnGatewayConnectionConfig vcnGatewayConnectionConfig :
+                    mGatewayConnectionConfigs) {
+                if (vcnGatewayConnectionConfig
+                        .getGatewayConnectionName()
+                        .equals(gatewayConnectionConfig.getGatewayConnectionName())) {
+                    throw new IllegalArgumentException(
+                            "GatewayConnection for specified name already exists");
+                }
+            }
+
+            mGatewayConnectionConfigs.add(gatewayConnectionConfig);
+            return this;
+        }
+
+        private void validateRestrictedTransportsOrThrow(Set<Integer> restrictedTransports) {
+            Objects.requireNonNull(restrictedTransports, "transports was null");
+
+            for (int transport : restrictedTransports) {
+                if (!ALLOWED_TRANSPORTS.contains(transport)) {
+                    throw new IllegalArgumentException("Invalid transport " + transport);
+                }
+            }
+        }
+
+        /**
+         * Sets transports that will be restricted by the VCN.
+         *
+         * <p>In general, apps will not be able to bind to, or use a restricted network. In other
+         * words, unless the network type is marked restricted, any app can opt to use underlying
+         * networks, instead of through the VCN.
+         *
+         * @param transports transports that will be restricted by VCN. Networks that include any of
+         *     the transports will be marked as restricted. {@link
+         *     NetworkCapabilities#TRANSPORT_WIFI} is marked restricted by default.
+         * @return this {@link Builder} instance, for chaining
+         * @throws IllegalArgumentException if the input contains unsupported transport types.
+         * @see NetworkCapabilities#NET_CAPABILITY_NOT_RESTRICTED
+         */
+        @NonNull
+        public Builder setRestrictedUnderlyingNetworkTransports(
+                @NonNull Set<@VcnUnderlyingNetworkTransport Integer> transports) {
+            validateRestrictedTransportsOrThrow(transports);
+
+            mRestrictedTransports.clear();
+            mRestrictedTransports.addAll(transports);
+            return this;
+        }
+
+        /**
+         * Restricts this VcnConfig to matching with test networks (only).
+         *
+         * <p>This method is for testing only, and must not be used by apps. Calling {@link
+         * VcnManager#setVcnConfig(ParcelUuid, VcnConfig)} with a VcnConfig where test-network usage
+         * is enabled will require the MANAGE_TEST_NETWORKS permission.
+         *
+         * @return this {@link Builder} instance, for chaining
+         * @hide
+         */
+        @NonNull
+        public Builder setIsTestModeProfile() {
+            mIsTestModeProfile = true;
+            return this;
+        }
+
+        /**
+         * Builds and validates the VcnConfig.
+         *
+         * @return an immutable VcnConfig instance
+         */
+        @NonNull
+        public VcnConfig build() {
+            return new VcnConfig(
+                    mPackageName,
+                    mGatewayConnectionConfigs,
+                    mRestrictedTransports,
+                    mIsTestModeProfile);
+        }
+    }
+}
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/VcnGatewayConnectionConfig.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnGatewayConnectionConfig.java
new file mode 100644
index 0000000..067144e
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -0,0 +1,876 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
+import static android.net.vcn.Flags.FLAG_MAINLINE_VCN_MODULE_API;
+import static android.net.vcn.Flags.FLAG_SAFE_MODE_CONFIG;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
+import android.net.vcn.persistablebundleutils.TunnelConnectionParamsUtils;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.os.PersistableBundle;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class represents a configuration for a connection to a Virtual Carrier Network gateway.
+ *
+ * <p>Each VcnGatewayConnectionConfig represents a single logical connection to a carrier gateway,
+ * and may provide one or more telephony services (as represented by network capabilities). Each
+ * gateway is expected to provide mobility for a given session as the device roams across {@link
+ * Network}s.
+ *
+ * <p>A VCN connection based on this configuration will be brought up dynamically based on device
+ * settings, and filed NetworkRequests. Underlying Networks must provide INTERNET connectivity, and
+ * must be part of the subscription group under which this configuration is registered (see {@link
+ * VcnManager#setVcnConfig}).
+ *
+ * <p>As an abstraction of a cellular network, services that can be provided by a VCN network are
+ * limited to services provided by cellular networks:
+ *
+ * <ul>
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_MMS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_SUPL}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_DUN}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_FOTA}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_IMS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_CBS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_IA}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_RCS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_XCAP}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_EIMS}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_INTERNET}
+ *   <li>{@link NetworkCapabilities#NET_CAPABILITY_MCX}
+ * </ul>
+ */
+public final class VcnGatewayConnectionConfig {
+    /**
+     * Minimum NAT timeout not set.
+     *
+     * <p>When the timeout is not set, the device will automatically choose a keepalive interval and
+     * may reduce the keepalive frequency for power-optimization.
+     */
+    @FlaggedApi(FLAG_MAINLINE_VCN_MODULE_API)
+    // This constant does not represent a minimum value. It indicates the value is not configured.
+    @SuppressLint("MinMaxConstant")
+    public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET = -1;
+
+    /** @hide */
+    public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS = 120;
+
+    // TODO: Use MIN_MTU_V6 once it is public, @hide
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int MIN_MTU_V6 = 1280;
+
+    /**
+     * The set of allowed capabilities for exposed capabilities.
+     *
+     * @hide
+     */
+    public static final Set<Integer> ALLOWED_CAPABILITIES;
+
+    static {
+        Set<Integer> allowedCaps = new ArraySet<>();
+        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_MMS);
+        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_SUPL);
+        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_DUN);
+        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_FOTA);
+        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_IMS);
+        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_CBS);
+        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_IA);
+        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_RCS);
+        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_XCAP);
+        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_EIMS);
+        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        allowedCaps.add(NetworkCapabilities.NET_CAPABILITY_MCX);
+
+        ALLOWED_CAPABILITIES = Collections.unmodifiableSet(allowedCaps);
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"NET_CAPABILITY_"},
+            value = {
+                NetworkCapabilities.NET_CAPABILITY_MMS,
+                NetworkCapabilities.NET_CAPABILITY_SUPL,
+                NetworkCapabilities.NET_CAPABILITY_DUN,
+                NetworkCapabilities.NET_CAPABILITY_FOTA,
+                NetworkCapabilities.NET_CAPABILITY_IMS,
+                NetworkCapabilities.NET_CAPABILITY_CBS,
+                NetworkCapabilities.NET_CAPABILITY_IA,
+                NetworkCapabilities.NET_CAPABILITY_RCS,
+                NetworkCapabilities.NET_CAPABILITY_XCAP,
+                NetworkCapabilities.NET_CAPABILITY_EIMS,
+                NetworkCapabilities.NET_CAPABILITY_INTERNET,
+                NetworkCapabilities.NET_CAPABILITY_MCX,
+            })
+    public @interface VcnSupportedCapability {}
+
+    /**
+     * Perform mobility update to attempt recovery from suspected data stalls.
+     *
+     * <p>If set, the gateway connection will monitor the data stall detection of the VCN network.
+     * When there is a suspected data stall, the gateway connection will attempt recovery by
+     * performing a mobility update on the underlying IKE session.
+     */
+    public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"VCN_GATEWAY_OPTION_"},
+            value = {
+                VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY,
+            })
+    public @interface VcnGatewayOption {}
+
+    private static final Set<Integer> ALLOWED_GATEWAY_OPTIONS = new ArraySet<>();
+
+    static {
+        ALLOWED_GATEWAY_OPTIONS.add(VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY);
+    }
+
+    private static final int DEFAULT_MAX_MTU = 1500;
+
+    /**
+     * The maximum number of retry intervals that may be specified.
+     *
+     * <p>Limited to ensure an upper bound on config sizes.
+     */
+    private static final int MAX_RETRY_INTERVAL_COUNT = 10;
+
+    /**
+     * The minimum allowable repeating retry interval
+     *
+     * <p>To ensure the device is not constantly being woken up, this retry interval MUST be greater
+     * than this value.
+     *
+     * @see {@link Builder#setRetryIntervalsMillis()}
+     */
+    private static final long MINIMUM_REPEATING_RETRY_INTERVAL_MS = TimeUnit.MINUTES.toMillis(15);
+
+    private static final long[] DEFAULT_RETRY_INTERVALS_MS =
+            new long[] {
+                TimeUnit.SECONDS.toMillis(1),
+                TimeUnit.SECONDS.toMillis(2),
+                TimeUnit.SECONDS.toMillis(5),
+                TimeUnit.SECONDS.toMillis(30),
+                TimeUnit.MINUTES.toMillis(1),
+                TimeUnit.MINUTES.toMillis(5),
+                TimeUnit.MINUTES.toMillis(15)
+            };
+
+    /** @hide */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static final List<VcnUnderlyingNetworkTemplate> DEFAULT_UNDERLYING_NETWORK_TEMPLATES =
+            new ArrayList<>();
+
+    static {
+        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
+                new VcnCellUnderlyingNetworkTemplate.Builder()
+                        .setOpportunistic(MATCH_REQUIRED)
+                        .build());
+
+        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
+                new VcnWifiUnderlyingNetworkTemplate.Builder()
+                        .build());
+
+        DEFAULT_UNDERLYING_NETWORK_TEMPLATES.add(
+                new VcnCellUnderlyingNetworkTemplate.Builder()
+                        .build());
+    }
+
+    private static final String GATEWAY_CONNECTION_NAME_KEY = "mGatewayConnectionName";
+    @NonNull private final String mGatewayConnectionName;
+
+    private static final String TUNNEL_CONNECTION_PARAMS_KEY = "mTunnelConnectionParams";
+    @NonNull private IkeTunnelConnectionParams mTunnelConnectionParams;
+
+    private static final String EXPOSED_CAPABILITIES_KEY = "mExposedCapabilities";
+    @NonNull private final SortedSet<Integer> mExposedCapabilities;
+
+    /** @hide */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static final String UNDERLYING_NETWORK_TEMPLATES_KEY = "mUnderlyingNetworkTemplates";
+
+    @NonNull private final List<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkTemplates;
+
+    private static final String MAX_MTU_KEY = "mMaxMtu";
+    private final int mMaxMtu;
+
+    private static final String RETRY_INTERVAL_MS_KEY = "mRetryIntervalsMs";
+    @NonNull private final long[] mRetryIntervalsMs;
+
+    private static final String MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS_KEY =
+            "mMinUdpPort4500NatTimeoutSeconds";
+    private final int mMinUdpPort4500NatTimeoutSeconds;
+
+    private static final String IS_SAFE_MODE_DISABLED_KEY = "mIsSafeModeDisabled";
+    private final boolean mIsSafeModeDisabled;
+
+    private static final String GATEWAY_OPTIONS_KEY = "mGatewayOptions";
+    @NonNull private final Set<Integer> mGatewayOptions;
+
+    /** Builds a VcnGatewayConnectionConfig with the specified parameters. */
+    private VcnGatewayConnectionConfig(
+            @NonNull String gatewayConnectionName,
+            @NonNull IkeTunnelConnectionParams tunnelConnectionParams,
+            @NonNull Set<Integer> exposedCapabilities,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull long[] retryIntervalsMs,
+            @IntRange(from = MIN_MTU_V6) int maxMtu,
+            @NonNull int minUdpPort4500NatTimeoutSeconds,
+            boolean isSafeModeDisabled,
+            @NonNull Set<Integer> gatewayOptions) {
+        mGatewayConnectionName = gatewayConnectionName;
+        mTunnelConnectionParams = tunnelConnectionParams;
+        mExposedCapabilities = new TreeSet(exposedCapabilities);
+        mRetryIntervalsMs = retryIntervalsMs;
+        mMaxMtu = maxMtu;
+        mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds;
+        mGatewayOptions = Collections.unmodifiableSet(new ArraySet(gatewayOptions));
+        mIsSafeModeDisabled = isSafeModeDisabled;
+
+        mUnderlyingNetworkTemplates = new ArrayList<>(underlyingNetworkTemplates);
+        if (mUnderlyingNetworkTemplates.isEmpty()) {
+            mUnderlyingNetworkTemplates.addAll(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
+        }
+
+        validate();
+    }
+
+    // Null check MUST be done for all new fields added to VcnGatewayConnectionConfig, to avoid
+    // crashes when parsing PersistableBundle built on old platforms.
+    /** @hide */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public VcnGatewayConnectionConfig(@NonNull PersistableBundle in) {
+        final PersistableBundle tunnelConnectionParamsBundle =
+                in.getPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY);
+        Objects.requireNonNull(
+                tunnelConnectionParamsBundle, "tunnelConnectionParamsBundle was null");
+
+        final PersistableBundle exposedCapsBundle =
+                in.getPersistableBundle(EXPOSED_CAPABILITIES_KEY);
+        mGatewayConnectionName = in.getString(GATEWAY_CONNECTION_NAME_KEY);
+        mTunnelConnectionParams =
+                TunnelConnectionParamsUtils.fromPersistableBundle(tunnelConnectionParamsBundle);
+        mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
+                exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
+
+        final PersistableBundle networkTemplatesBundle =
+                in.getPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY);
+
+        if (networkTemplatesBundle == null) {
+            // UNDERLYING_NETWORK_TEMPLATES_KEY was added in Android T. Thus
+            // VcnGatewayConnectionConfig created on old platforms will not have this data and will
+            // be assigned with the default value
+            mUnderlyingNetworkTemplates = new ArrayList<>(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
+        } else {
+            mUnderlyingNetworkTemplates =
+                    PersistableBundleUtils.toList(
+                            networkTemplatesBundle,
+                            VcnUnderlyingNetworkTemplate::fromPersistableBundle);
+        }
+
+        final PersistableBundle gatewayOptionsBundle = in.getPersistableBundle(GATEWAY_OPTIONS_KEY);
+
+        if (gatewayOptionsBundle == null) {
+            // GATEWAY_OPTIONS_KEY was added in Android U. Thus VcnGatewayConnectionConfig created
+            // on old platforms will not have this data and will be assigned with the default value
+            mGatewayOptions = Collections.emptySet();
+        } else {
+            mGatewayOptions =
+                    new ArraySet<>(
+                            PersistableBundleUtils.toList(
+                                    gatewayOptionsBundle,
+                                    PersistableBundleUtils.INTEGER_DESERIALIZER));
+        }
+
+        mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY);
+        mMaxMtu = in.getInt(MAX_MTU_KEY);
+        mMinUdpPort4500NatTimeoutSeconds =
+                in.getInt(
+                        MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS_KEY,
+                        MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET);
+        mIsSafeModeDisabled = in.getBoolean(IS_SAFE_MODE_DISABLED_KEY);
+
+        validate();
+    }
+
+    private void validate() {
+        Objects.requireNonNull(mGatewayConnectionName, "gatewayConnectionName was null");
+        Objects.requireNonNull(mTunnelConnectionParams, "tunnel connection parameter was null");
+
+        Preconditions.checkArgument(
+                mExposedCapabilities != null && !mExposedCapabilities.isEmpty(),
+                "exposedCapsBundle was null or empty");
+        for (Integer cap : getAllExposedCapabilities()) {
+            checkValidCapability(cap);
+        }
+
+        validateNetworkTemplateList(mUnderlyingNetworkTemplates);
+        Objects.requireNonNull(mRetryIntervalsMs, "retryIntervalsMs was null");
+        validateRetryInterval(mRetryIntervalsMs);
+
+        Preconditions.checkArgument(
+                mMaxMtu >= MIN_MTU_V6, "maxMtu must be at least IPv6 min MTU (1280)");
+
+        Preconditions.checkArgument(
+                mMinUdpPort4500NatTimeoutSeconds == MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET
+                        || mMinUdpPort4500NatTimeoutSeconds
+                                >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
+                "minUdpPort4500NatTimeoutSeconds must be at least 120s");
+
+        for (int option : mGatewayOptions) {
+            validateGatewayOption(option);
+        }
+    }
+
+    private static void checkValidCapability(int capability) {
+        Preconditions.checkArgument(
+                ALLOWED_CAPABILITIES.contains(capability),
+                "NetworkCapability " + capability + "out of range");
+    }
+
+    private static void validateRetryInterval(@Nullable long[] retryIntervalsMs) {
+        Preconditions.checkArgument(
+                retryIntervalsMs != null
+                        && retryIntervalsMs.length > 0
+                        && retryIntervalsMs.length <= MAX_RETRY_INTERVAL_COUNT,
+                "retryIntervalsMs was null, empty or exceed max interval count");
+
+        final long repeatingInterval = retryIntervalsMs[retryIntervalsMs.length - 1];
+        if (repeatingInterval < MINIMUM_REPEATING_RETRY_INTERVAL_MS) {
+            throw new IllegalArgumentException(
+                    "Repeating retry interval was too short, must be a minimum of 15 minutes: "
+                            + repeatingInterval);
+        }
+    }
+
+    private static void validateNetworkTemplateList(
+            List<VcnUnderlyingNetworkTemplate> networkPriorityRules) {
+        Objects.requireNonNull(networkPriorityRules, "networkPriorityRules is null");
+
+        Set<VcnUnderlyingNetworkTemplate> existingRules = new ArraySet<>();
+        for (VcnUnderlyingNetworkTemplate rule : networkPriorityRules) {
+            Objects.requireNonNull(rule, "Found null value VcnUnderlyingNetworkTemplate");
+            if (!existingRules.add(rule)) {
+                throw new IllegalArgumentException("Found duplicate VcnUnderlyingNetworkTemplate");
+            }
+        }
+    }
+
+    private static void validateGatewayOption(int option) {
+        if (!ALLOWED_GATEWAY_OPTIONS.contains(option)) {
+            throw new IllegalArgumentException("Invalid vcn gateway option: " + option);
+        }
+    }
+
+    /**
+     * Returns the configured Gateway Connection name.
+     *
+     * <p>This name is used by the configuring apps to distinguish between
+     * VcnGatewayConnectionConfigs configured on a single {@link VcnConfig}. This will be used as
+     * the identifier in VcnStatusCallback invocations.
+     *
+     * @see VcnManager.VcnStatusCallback#onGatewayConnectionError
+     */
+    @NonNull
+    public String getGatewayConnectionName() {
+        return mGatewayConnectionName;
+    }
+
+    /**
+     * Returns tunnel connection parameters.
+     *
+     * @hide
+     */
+    @NonNull
+    public IkeTunnelConnectionParams getTunnelConnectionParams() {
+        return mTunnelConnectionParams;
+    }
+
+    /**
+     * Returns all exposed capabilities.
+     *
+     * <p>The returned integer-value capabilities will not contain duplicates, and will be sorted in
+     * ascending numerical order.
+     *
+     * @see Builder#addExposedCapability(int)
+     * @see Builder#removeExposedCapability(int)
+     */
+    @NonNull
+    public int[] getExposedCapabilities() {
+        // Sorted set guarantees ordering
+        final int[] caps = new int[mExposedCapabilities.size()];
+
+        int i = 0;
+        for (int c : mExposedCapabilities) {
+            caps[i++] = c;
+        }
+
+        return caps;
+    }
+
+    /**
+     * Returns all exposed capabilities.
+     *
+     * <p>Left to prevent the need to make major changes while changes are actively in flight.
+     *
+     * @deprecated use getExposedCapabilities() instead
+     * @hide
+     */
+    @Deprecated
+    @NonNull
+    public Set<Integer> getAllExposedCapabilities() {
+        return Collections.unmodifiableSet(mExposedCapabilities);
+    }
+
+    /**
+     * Retrieve the VcnUnderlyingNetworkTemplate list, or a default list if it is not configured.
+     *
+     * @see Builder#setVcnUnderlyingNetworkPriorities(List)
+     */
+    @NonNull
+    public List<VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities() {
+        return new ArrayList<>(mUnderlyingNetworkTemplates);
+    }
+
+    /**
+     * Retrieves the configured retry intervals.
+     *
+     * @see Builder#setRetryIntervalsMillis(long[])
+     */
+    @NonNull
+    public long[] getRetryIntervalsMillis() {
+        return Arrays.copyOf(mRetryIntervalsMs, mRetryIntervalsMs.length);
+    }
+
+    /**
+     * Retrieves the maximum MTU allowed for this Gateway Connection.
+     *
+     * @see Builder#setMaxMtu(int)
+     */
+    @IntRange(from = MIN_MTU_V6)
+    public int getMaxMtu() {
+        return mMaxMtu;
+    }
+
+    /**
+     * Retrieves the maximum supported IKEv2/IPsec NATT keepalive timeout.
+     *
+     * @see Builder#setMinUdpPort4500NatTimeoutSeconds(int)
+     */
+    public int getMinUdpPort4500NatTimeoutSeconds() {
+        return mMinUdpPort4500NatTimeoutSeconds;
+    }
+
+    /**
+     * Check whether safe mode is enabled
+     *
+     * @see Builder#setSafeModeEnabled(boolean)
+     */
+    @FlaggedApi(FLAG_SAFE_MODE_CONFIG)
+    public boolean isSafeModeEnabled() {
+        return !mIsSafeModeDisabled;
+    }
+
+    /**
+     * Checks if the given VCN gateway option is enabled.
+     *
+     * @param option the option to check.
+     * @throws IllegalArgumentException if the provided option is invalid.
+     * @see Builder#addGatewayOption(int)
+     * @see Builder#removeGatewayOption(int)
+     */
+    public boolean hasGatewayOption(@VcnGatewayOption int option) {
+        validateGatewayOption(option);
+        return mGatewayOptions.contains(option);
+    }
+
+    /**
+     * Converts this config to a PersistableBundle.
+     *
+     * @hide
+     */
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public PersistableBundle toPersistableBundle() {
+        final PersistableBundle result = new PersistableBundle();
+
+        final PersistableBundle tunnelConnectionParamsBundle =
+                TunnelConnectionParamsUtils.toPersistableBundle(mTunnelConnectionParams);
+        final PersistableBundle exposedCapsBundle =
+                PersistableBundleUtils.fromList(
+                        new ArrayList<>(mExposedCapabilities),
+                        PersistableBundleUtils.INTEGER_SERIALIZER);
+        final PersistableBundle networkTemplatesBundle =
+                PersistableBundleUtils.fromList(
+                        mUnderlyingNetworkTemplates,
+                        VcnUnderlyingNetworkTemplate::toPersistableBundle);
+        final PersistableBundle gatewayOptionsBundle =
+                PersistableBundleUtils.fromList(
+                        new ArrayList<>(mGatewayOptions),
+                        PersistableBundleUtils.INTEGER_SERIALIZER);
+
+        result.putString(GATEWAY_CONNECTION_NAME_KEY, mGatewayConnectionName);
+        result.putPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY, tunnelConnectionParamsBundle);
+        result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle);
+        result.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, networkTemplatesBundle);
+        result.putPersistableBundle(GATEWAY_OPTIONS_KEY, gatewayOptionsBundle);
+        result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs);
+        result.putInt(MAX_MTU_KEY, mMaxMtu);
+        result.putInt(MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS_KEY, mMinUdpPort4500NatTimeoutSeconds);
+        result.putBoolean(IS_SAFE_MODE_DISABLED_KEY, mIsSafeModeDisabled);
+
+        return result;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mGatewayConnectionName,
+                mTunnelConnectionParams,
+                mExposedCapabilities,
+                mUnderlyingNetworkTemplates,
+                Arrays.hashCode(mRetryIntervalsMs),
+                mMaxMtu,
+                mMinUdpPort4500NatTimeoutSeconds,
+                mIsSafeModeDisabled,
+                mGatewayOptions);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (!(other instanceof VcnGatewayConnectionConfig)) {
+            return false;
+        }
+
+        final VcnGatewayConnectionConfig rhs = (VcnGatewayConnectionConfig) other;
+        return mGatewayConnectionName.equals(rhs.mGatewayConnectionName)
+                && mTunnelConnectionParams.equals(rhs.mTunnelConnectionParams)
+                && mExposedCapabilities.equals(rhs.mExposedCapabilities)
+                && mUnderlyingNetworkTemplates.equals(rhs.mUnderlyingNetworkTemplates)
+                && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs)
+                && mMaxMtu == rhs.mMaxMtu
+                && mMinUdpPort4500NatTimeoutSeconds == rhs.mMinUdpPort4500NatTimeoutSeconds
+                && mIsSafeModeDisabled == rhs.mIsSafeModeDisabled
+                && mGatewayOptions.equals(rhs.mGatewayOptions);
+    }
+
+    /**
+     * This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects.
+     */
+    public static final class Builder {
+        @NonNull private final String mGatewayConnectionName;
+        @NonNull private final IkeTunnelConnectionParams mTunnelConnectionParams;
+        @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet();
+
+        @NonNull
+        private final List<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkTemplates =
+                new ArrayList<>(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
+
+        @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
+        private int mMaxMtu = DEFAULT_MAX_MTU;
+        private int mMinUdpPort4500NatTimeoutSeconds = MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET;
+        private boolean mIsSafeModeDisabled = false;
+
+        @NonNull private final Set<Integer> mGatewayOptions = new ArraySet<>();
+
+        // TODO: (b/175829816) Consider VCN-exposed capabilities that may be transport dependent.
+        //       Consider the case where the VCN might only expose MMS on WiFi, but defer to MMS
+        //       when on Cell.
+
+        /**
+         * Construct a Builder object.
+         *
+         * @param gatewayConnectionName the String GatewayConnection name for this
+         *     VcnGatewayConnectionConfig. Each VcnGatewayConnectionConfig within a {@link
+         *     VcnConfig} must be given a unique name. This name is used by the caller to
+         *     distinguish between VcnGatewayConnectionConfigs configured on a single {@link
+         *     VcnConfig}. This will be used as the identifier in VcnStatusCallback invocations.
+         * @param tunnelConnectionParams the IKE tunnel connection configuration
+         * @throws IllegalArgumentException if the provided IkeTunnelConnectionParams is not
+         *     configured to support MOBIKE
+         * @see IkeTunnelConnectionParams
+         * @see VcnManager.VcnStatusCallback#onGatewayConnectionError
+         */
+        public Builder(
+                @NonNull String gatewayConnectionName,
+                @NonNull IkeTunnelConnectionParams tunnelConnectionParams) {
+            Objects.requireNonNull(gatewayConnectionName, "gatewayConnectionName was null");
+            Objects.requireNonNull(tunnelConnectionParams, "tunnelConnectionParams was null");
+            if (!tunnelConnectionParams.getIkeSessionParams().hasIkeOption(IKE_OPTION_MOBIKE)) {
+                throw new IllegalArgumentException(
+                        "MOBIKE must be configured for the provided IkeSessionParams");
+            }
+
+            mGatewayConnectionName = gatewayConnectionName;
+            mTunnelConnectionParams = tunnelConnectionParams;
+        }
+
+        /**
+         * Add a capability that this VCN Gateway Connection will support.
+         *
+         * @param exposedCapability the app-facing capability to be exposed by this VCN Gateway
+         *     Connection (i.e., the capabilities that this VCN Gateway Connection will support).
+         * @return this {@link Builder} instance, for chaining
+         * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
+         *     Connection
+         */
+        @NonNull
+        public Builder addExposedCapability(@VcnSupportedCapability int exposedCapability) {
+            checkValidCapability(exposedCapability);
+
+            mExposedCapabilities.add(exposedCapability);
+            return this;
+        }
+
+        /**
+         * Remove a capability that this VCN Gateway Connection will support.
+         *
+         * @param exposedCapability the app-facing capability to not be exposed by this VCN Gateway
+         *     Connection (i.e., the capabilities that this VCN Gateway Connection will support)
+         * @return this {@link Builder} instance, for chaining
+         * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
+         *     Connection
+         */
+        @NonNull
+        @SuppressLint("BuilderSetStyle") // For consistency with NetCaps.Builder add/removeCap
+        public Builder removeExposedCapability(@VcnSupportedCapability int exposedCapability) {
+            checkValidCapability(exposedCapability);
+
+            mExposedCapabilities.remove(exposedCapability);
+            return this;
+        }
+
+        /**
+         * Set the list of templates to match underlying networks against, in high-to-low priority
+         * order.
+         *
+         * <p>To select the VCN underlying network, the VCN connection will go through all the
+         * network candidates and return a network matching the highest priority rule.
+         *
+         * <p>If multiple networks match the same rule, the VCN will prefer an already-selected
+         * network as opposed to a new/unselected network. However, if both are new/unselected
+         * networks, a network will be chosen arbitrarily amongst the networks matching the highest
+         * priority rule.
+         *
+         * <p>If all networks fail to match the rules provided, a carrier-owned underlying network
+         * will still be selected (if available, at random if necessary).
+         *
+         * @param underlyingNetworkTemplates a list of unique VcnUnderlyingNetworkTemplates that are
+         *     ordered from most to least preferred, or an empty list to use the default
+         *     prioritization. The default network prioritization order is Opportunistic cellular,
+         *     Carrier WiFi and then Macro cellular.
+         * @return this {@link Builder} instance, for chaining
+         */
+        @NonNull
+        public Builder setVcnUnderlyingNetworkPriorities(
+                @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) {
+            validateNetworkTemplateList(underlyingNetworkTemplates);
+
+            mUnderlyingNetworkTemplates.clear();
+
+            if (underlyingNetworkTemplates.isEmpty()) {
+                mUnderlyingNetworkTemplates.addAll(DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
+            } else {
+                mUnderlyingNetworkTemplates.addAll(underlyingNetworkTemplates);
+            }
+
+            return this;
+        }
+
+        /**
+         * Set the retry interval between VCN establishment attempts upon successive failures.
+         *
+         * <p>The last retry interval will be repeated until safe mode is entered, or a connection
+         * is successfully established, at which point the retry timers will be reset. For power
+         * reasons, the last (repeated) retry interval MUST be at least 15 minutes.
+         *
+         * <p>Retry intervals MAY be subject to system power saving modes. That is to say that if
+         * the system enters a power saving mode, the retry may not occur until the device leaves
+         * the specified power saving mode. Intervals are sequential, and intervals will NOT be
+         * skipped if system power saving results in delaying retries (even if it exceed multiple
+         * retry intervals).
+         *
+         * <p>Each Gateway Connection will retry according to the retry intervals configured, but if
+         * safe mode is enabled, all Gateway Connection(s) will be disabled.
+         *
+         * @param retryIntervalsMs an array of between 1 and 10 millisecond intervals after which
+         *     the VCN will attempt to retry a session initiation. The last (repeating) retry
+         *     interval must be at least 15 minutes. Defaults to: {@code [1s, 2s, 5s, 30s, 1m, 5m,
+         *     15m]}
+         * @return this {@link Builder} instance, for chaining
+         * @see VcnManager for additional discussion on fail-safe mode
+         */
+        @NonNull
+        public Builder setRetryIntervalsMillis(@NonNull long[] retryIntervalsMs) {
+            validateRetryInterval(retryIntervalsMs);
+
+            mRetryIntervalsMs = retryIntervalsMs;
+            return this;
+        }
+
+        /**
+         * Sets the maximum MTU allowed for this VCN Gateway Connection.
+         *
+         * <p>This MTU is applied to the VCN Gateway Connection exposed Networks, and represents the
+         * MTU of the virtualized network.
+         *
+         * <p>The system may reduce the MTU below the maximum specified based on signals such as the
+         * MTU of the underlying networks (and adjusted for Gateway Connection overhead).
+         *
+         * @param maxMtu the maximum MTU allowed for this Gateway Connection. Must be greater than
+         *     the IPv6 minimum MTU of 1280. Defaults to 1500.
+         * @return this {@link Builder} instance, for chaining
+         */
+        @NonNull
+        public Builder setMaxMtu(@IntRange(from = MIN_MTU_V6) int maxMtu) {
+            Preconditions.checkArgument(
+                    maxMtu >= MIN_MTU_V6, "maxMtu must be at least IPv6 min MTU (1280)");
+
+            mMaxMtu = maxMtu;
+            return this;
+        }
+
+        /**
+         * Sets the maximum supported IKEv2/IPsec NATT keepalive timeout.
+         *
+         * <p>This is used as a power-optimization hint for other IKEv2/IPsec use cases (e.g. VPNs,
+         * or IWLAN) to reduce the necessary keepalive frequency, thus conserving power and data.
+         *
+         * @param minUdpPort4500NatTimeoutSeconds the maximum keepalive timeout supported by the VCN
+         *     Gateway Connection, generally the minimum duration a NAT mapping is cached on the VCN
+         *     Gateway; or {@link MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET} to clear this value.
+         * @return this {@link Builder} instance, for chaining
+         */
+        @NonNull
+        public Builder setMinUdpPort4500NatTimeoutSeconds(
+                @IntRange(from = MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS)
+                        int minUdpPort4500NatTimeoutSeconds) {
+            if (Flags.mainlineVcnModuleApi()) {
+                Preconditions.checkArgument(
+                        minUdpPort4500NatTimeoutSeconds == MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET
+                                || minUdpPort4500NatTimeoutSeconds
+                                        >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
+                        "Timeout must be at least 120s or MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET");
+            } else {
+                Preconditions.checkArgument(
+                        minUdpPort4500NatTimeoutSeconds >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
+                        "Timeout must be at least 120s");
+            }
+
+            mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds;
+            return this;
+        }
+
+        /**
+         * Enables the specified VCN gateway option.
+         *
+         * @param option the option to be enabled
+         * @return this {@link Builder} instance, for chaining
+         * @throws IllegalArgumentException if the provided option is invalid
+         */
+        @NonNull
+        public Builder addGatewayOption(@VcnGatewayOption int option) {
+            validateGatewayOption(option);
+            mGatewayOptions.add(option);
+            return this;
+        }
+
+        /**
+         * Resets (disables) the specified VCN gateway option.
+         *
+         * @param option the option to be disabled
+         * @return this {@link Builder} instance, for chaining
+         * @throws IllegalArgumentException if the provided option is invalid
+         */
+        @NonNull
+        public Builder removeGatewayOption(@VcnGatewayOption int option) {
+            validateGatewayOption(option);
+            mGatewayOptions.remove(option);
+            return this;
+        }
+
+        /**
+         * Enable/disable safe mode
+         *
+         * <p>If a VCN fails to provide connectivity within a system-provided timeout, it will enter
+         * safe mode. In safe mode, the VCN Network will be torn down and the system will restore
+         * connectivity by allowing underlying cellular or WiFi networks to be used as default. At
+         * the same time, VCN will continue to retry until it succeeds.
+         *
+         * <p>When safe mode is disabled and VCN connection fails to provide connectivity, end users
+         * might not have connectivity, and may not have access to carrier-owned underlying
+         * networks.
+         *
+         * @param enabled whether safe mode should be enabled. Defaults to {@code true}
+         */
+        @FlaggedApi(FLAG_SAFE_MODE_CONFIG)
+        @NonNull
+        public Builder setSafeModeEnabled(boolean enabled) {
+            mIsSafeModeDisabled = !enabled;
+            return this;
+        }
+
+        /**
+         * Builds and validates the VcnGatewayConnectionConfig.
+         *
+         * @return an immutable VcnGatewayConnectionConfig instance
+         */
+        @NonNull
+        public VcnGatewayConnectionConfig build() {
+            return new VcnGatewayConnectionConfig(
+                    mGatewayConnectionName,
+                    mTunnelConnectionParams,
+                    mExposedCapabilities,
+                    mUnderlyingNetworkTemplates,
+                    mRetryIntervalsMs,
+                    mMaxMtu,
+                    mMinUdpPort4500NatTimeoutSeconds,
+                    mIsSafeModeDisabled,
+                    mGatewayOptions);
+        }
+    }
+}
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/VcnManager.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnManager.java
new file mode 100644
index 0000000..594bbb8
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnManager.java
@@ -0,0 +1,772 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.net.module.util.BinderUtils;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * VcnManager publishes APIs for applications to configure and manage Virtual Carrier Networks.
+ *
+ * <p>A VCN creates a virtualization layer to allow carriers to aggregate heterogeneous physical
+ * networks, unifying them as a single carrier network. This enables infrastructure flexibility on
+ * the part of carriers without impacting user connectivity, abstracting the physical network
+ * technologies as an implementation detail of their public network.
+ *
+ * <p>Each VCN virtualizes a carrier's network by building tunnels to a carrier's core network over
+ * carrier-managed physical links and supports a IP mobility layer to ensure seamless transitions
+ * between the underlying networks. Each VCN is configured based on a Subscription Group (see {@link
+ * android.telephony.SubscriptionManager}) and aggregates all networks that are brought up based on
+ * a profile or suggestion in the specified Subscription Group.
+ *
+ * <p>The VCN can be configured to expose one or more {@link android.net.Network}(s), each with
+ * different capabilities, allowing for APN virtualization.
+ *
+ * <p>If a tunnel fails to connect, or otherwise encounters a fatal error, the VCN will attempt to
+ * reestablish the connection. If the tunnel still has not reconnected after a system-determined
+ * timeout, the VCN Safe Mode (see below) will be entered.
+ *
+ * <p>The VCN Safe Mode ensures that users (and carriers) have a fallback to restore system
+ * connectivity to update profiles, diagnose issues, contact support, or perform other remediation
+ * tasks. In Safe Mode, the system will allow underlying cellular networks to be used as default.
+ * Additionally, during Safe Mode, the VCN will continue to retry the connections, and will
+ * automatically exit Safe Mode if all active tunnels connect successfully.
+ *
+ * <p>Apps targeting Android 15 or newer should check the existence of {@link
+ * PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION} before querying the service. If the feature is
+ * absent, {@link Context#getSystemService} may return null.
+ */
+@SystemService(VcnManager.VCN_MANAGEMENT_SERVICE_STRING)
+@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+public class VcnManager {
+    @NonNull private static final String TAG = VcnManager.class.getSimpleName();
+
+    // TODO: b/366598445: Expose and use Context.VCN_MANAGEMENT_SERVICE
+    /** @hide */
+    public static final String VCN_MANAGEMENT_SERVICE_STRING = "vcn_management";
+
+    /**
+     * Key for WiFi entry RSSI thresholds
+     *
+     * <p>The VCN will only migrate to a Carrier WiFi network that has a signal strength greater
+     * than, or equal to this threshold.
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY =
+            "vcn_network_selection_wifi_entry_rssi_threshold";
+
+    /**
+     * Key for WiFi entry RSSI thresholds
+     *
+     * <p>If the VCN's selected Carrier WiFi network has a signal strength less than this threshold,
+     * the VCN will attempt to migrate away from the Carrier WiFi network.
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY =
+            "vcn_network_selection_wifi_exit_rssi_threshold";
+
+    /**
+     * Key for the interval to poll IpSecTransformState for packet loss monitoring
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY =
+            "vcn_network_selection_poll_ipsec_state_interval_seconds";
+
+    /**
+     * Key for the threshold of IPSec packet loss rate
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY =
+            "vcn_network_selection_ipsec_packet_loss_percent_threshold";
+
+    /**
+     * Key for detecting unusually large increases in IPsec packet sequence numbers.
+     *
+     * <p>If the sequence number increases by more than this value within a second, it may indicate
+     * an intentional leap on the server's downlink. To avoid false positives, the packet loss
+     * detector will suppress loss reporting.
+     *
+     * <p>By default, there's no maximum limit enforced, prioritizing detection of lossy networks.
+     * To reduce false positives, consider setting an appropriate maximum threshold.
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY =
+            "vcn_network_selection_max_seq_num_increase_per_second";
+
+    /**
+     * Key for the list of timeouts in minute to stop penalizing an underlying network candidate
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY =
+            "vcn_network_selection_penalty_timeout_minutes_list";
+
+    // TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz
+
+    /**
+     * Key for transports that need to be marked as restricted by the VCN
+     *
+     * <p>Defaults to TRANSPORT_WIFI if the config does not exist
+     *
+     * @hide
+     */
+    public static final String VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY =
+            "vcn_restricted_transports";
+
+    /**
+     * Key for number of seconds to wait before entering safe mode
+     *
+     * <p>A VcnGatewayConnection will enter safe mode when it takes over the configured timeout to
+     * enter {@link ConnectedState}.
+     *
+     * <p>Defaults to 30, unless overridden by carrier config
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY =
+            "vcn_safe_mode_timeout_seconds_key";
+
+    /**
+     * Key for maximum number of parallel SAs for tunnel aggregation
+     *
+     * <p>If set to a value > 1, multiple tunnels will be set up, and inbound traffic will be
+     * aggregated over the various tunnels.
+     *
+     * <p>Defaults to 1, unless overridden by carrier config
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY =
+            "vcn_tunnel_aggregation_sa_count_max";
+
+    /** List of Carrier Config options to extract from Carrier Config bundles. @hide */
+    @NonNull
+    public static final String[] VCN_RELATED_CARRIER_CONFIG_KEYS =
+            new String[] {
+                VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
+                VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
+                VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
+                VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+                VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
+                VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
+                VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
+                VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY,
+                VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY,
+            };
+
+    private static final Map<
+                    VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder>
+            REGISTERED_POLICY_LISTENERS = new ConcurrentHashMap<>();
+
+    @NonNull private final Context mContext;
+    @NonNull private final IVcnManagementService mService;
+
+    /**
+     * Construct an instance of VcnManager within an application context.
+     *
+     * @param ctx the application context for this manager
+     * @param service the VcnManagementService binder backing this manager
+     *
+     * @hide
+     */
+    public VcnManager(@NonNull Context ctx, @NonNull IVcnManagementService service) {
+        mContext = requireNonNull(ctx, "missing context");
+        mService = requireNonNull(service, "missing service");
+    }
+
+    /**
+     * Get all currently registered VcnNetworkPolicyChangeListeners for testing purposes.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @NonNull
+    public static Map<VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder>
+            getAllPolicyListeners() {
+        return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS);
+    }
+
+    /**
+     * Sets the VCN configuration for a given subscription group.
+     *
+     * <p>An app that has carrier privileges for any of the subscriptions in the given group may set
+     * a VCN configuration. If a configuration already exists for the given subscription group, it
+     * will be overridden. Any active VCN(s) may be forced to restart to use the new configuration.
+     *
+     * <p>This API is ONLY permitted for callers running as the primary user.
+     *
+     * @param subscriptionGroup the subscription group that the configuration should be applied to
+     * @param config the configuration parameters for the VCN
+     * @throws SecurityException if the caller does not have carrier privileges for the provided
+     *     subscriptionGroup, or is not running as the primary user
+     * @throws IOException if the configuration failed to be saved and persisted to disk. This may
+     *     occur due to temporary disk errors, or more permanent conditions such as a full disk.
+     */
+    @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant
+    public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config)
+            throws IOException {
+        requireNonNull(subscriptionGroup, "subscriptionGroup was null");
+        requireNonNull(config, "config was null");
+
+        try {
+            mService.setVcnConfig(subscriptionGroup, config, mContext.getOpPackageName());
+        } catch (ServiceSpecificException e) {
+            throw new IOException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Clears the VCN configuration for a given subscription group.
+     *
+     * <p>An app that has carrier privileges for any of the subscriptions in the given group may
+     * clear a VCN configuration. This API is ONLY permitted for callers running as the primary
+     * user. Any active VCN associated with this configuration will be torn down.
+     *
+     * @param subscriptionGroup the subscription group that the configuration should be applied to
+     * @throws SecurityException if the caller does not have carrier privileges, is not the owner of
+     *     the associated configuration, or is not running as the primary user
+     * @throws IOException if the configuration failed to be cleared from disk. This may occur due
+     *     to temporary disk errors, or more permanent conditions such as a full disk.
+     */
+    @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant
+    public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) throws IOException {
+        requireNonNull(subscriptionGroup, "subscriptionGroup was null");
+
+        try {
+            mService.clearVcnConfig(subscriptionGroup, mContext.getOpPackageName());
+        } catch (ServiceSpecificException e) {
+            throw new IOException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Retrieves the list of Subscription Groups for which a VCN Configuration has been set.
+     *
+     * <p>The returned list will include only subscription groups for which an associated {@link
+     * VcnConfig} exists, and the app is either:
+     *
+     * <ul>
+     *   <li>Carrier privileged for that subscription group, or
+     *   <li>Is the provisioning package of the config
+     * </ul>
+     *
+     * @throws SecurityException if the caller is not running as the primary user
+     */
+    @NonNull
+    public List<ParcelUuid> getConfiguredSubscriptionGroups() {
+        try {
+            return mService.getConfiguredSubscriptionGroups(mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    // TODO(b/180537630): remove all VcnUnderlyingNetworkPolicyListener refs once Telephony is using
+    // the new VcnNetworkPolicyChangeListener API
+    /**
+     * VcnUnderlyingNetworkPolicyListener is the interface through which internal system components
+     * can register to receive updates for VCN-underlying Network policies from the System Server.
+     *
+     * @hide
+     */
+    public interface VcnUnderlyingNetworkPolicyListener extends VcnNetworkPolicyChangeListener {}
+
+    /**
+     * Add a listener for VCN-underlying network policy updates.
+     *
+     * @param executor the Executor that will be used for invoking all calls to the specified
+     *     Listener
+     * @param listener the VcnUnderlyingNetworkPolicyListener to be added
+     * @throws SecurityException if the caller does not have the required permission
+     * @throws IllegalStateException if the specified VcnUnderlyingNetworkPolicyListener is already
+     *     registered
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public void addVcnUnderlyingNetworkPolicyListener(
+            @NonNull Executor executor, @NonNull VcnUnderlyingNetworkPolicyListener listener) {
+        addVcnNetworkPolicyChangeListener(executor, listener);
+    }
+
+    /**
+     * Remove the specified VcnUnderlyingNetworkPolicyListener from VcnManager.
+     *
+     * <p>If the specified listener is not currently registered, this is a no-op.
+     *
+     * @param listener the VcnUnderlyingNetworkPolicyListener that will be removed
+     * @hide
+     */
+    public void removeVcnUnderlyingNetworkPolicyListener(
+            @NonNull VcnUnderlyingNetworkPolicyListener listener) {
+        removeVcnNetworkPolicyChangeListener(listener);
+    }
+
+    /**
+     * Queries the underlying network policy for a network with the given parameters.
+     *
+     * <p>Prior to a new NetworkAgent being registered, or upon notification that Carrier VCN policy
+     * may have changed via {@link VcnUnderlyingNetworkPolicyListener#onPolicyChanged()}, a Network
+     * Provider MUST poll for the updated Network policy based on that Network's capabilities and
+     * properties.
+     *
+     * @param networkCapabilities the NetworkCapabilities to be used in determining the Network
+     *     policy for this Network.
+     * @param linkProperties the LinkProperties to be used in determining the Network policy for
+     *     this Network.
+     * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
+     * @return the VcnUnderlyingNetworkPolicy to be used for this Network.
+     * @hide
+     */
+    @NonNull
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(
+            @NonNull NetworkCapabilities networkCapabilities,
+            @NonNull LinkProperties linkProperties) {
+        requireNonNull(networkCapabilities, "networkCapabilities must not be null");
+        requireNonNull(linkProperties, "linkProperties must not be null");
+
+        try {
+            return mService.getUnderlyingNetworkPolicy(networkCapabilities, linkProperties);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * VcnNetworkPolicyChangeListener is the interface through which internal system components
+     * (e.g. Network Factories) can register to receive updates for VCN-underlying Network policies
+     * from the System Server.
+     *
+     * <p>Any Network Factory that brings up Networks capable of being VCN-underlying Networks
+     * should register a VcnNetworkPolicyChangeListener. VcnManager will then use this listener to
+     * notify the registrant when VCN Network policies change. Upon receiving this signal, the
+     * listener must check {@link VcnManager} for the current Network policy result for each of its
+     * Networks via {@link #applyVcnNetworkPolicy(NetworkCapabilities, LinkProperties)}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface VcnNetworkPolicyChangeListener {
+        /**
+         * Notifies the implementation that the VCN's underlying Network policy has changed.
+         *
+         * <p>After receiving this callback, implementations should get the current {@link
+         * VcnNetworkPolicyResult} via {@link #applyVcnNetworkPolicy(NetworkCapabilities,
+         * LinkProperties)}.
+         */
+        void onPolicyChanged();
+    }
+
+    /**
+     * Add a listener for VCN-underlying Network policy updates.
+     *
+     * <p>A {@link VcnNetworkPolicyChangeListener} is eligible to begin receiving callbacks once it
+     * is registered. No callbacks are guaranteed upon registration.
+     *
+     * @param executor the Executor that will be used for invoking all calls to the specified
+     *     Listener
+     * @param listener the VcnNetworkPolicyChangeListener to be added
+     * @throws SecurityException if the caller does not have the required permission
+     * @throws IllegalStateException if the specified VcnNetworkPolicyChangeListener is already
+     *     registered
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public void addVcnNetworkPolicyChangeListener(
+            @NonNull Executor executor, @NonNull VcnNetworkPolicyChangeListener listener) {
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(listener, "listener must not be null");
+
+        VcnUnderlyingNetworkPolicyListenerBinder binder =
+                new VcnUnderlyingNetworkPolicyListenerBinder(executor, listener);
+        if (REGISTERED_POLICY_LISTENERS.putIfAbsent(listener, binder) != null) {
+            throw new IllegalStateException("listener is already registered with VcnManager");
+        }
+
+        try {
+            mService.addVcnUnderlyingNetworkPolicyListener(binder);
+        } catch (RemoteException e) {
+            REGISTERED_POLICY_LISTENERS.remove(listener);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove the specified VcnNetworkPolicyChangeListener from VcnManager.
+     *
+     * <p>If the specified listener is not currently registered, this is a no-op.
+     *
+     * @param listener the VcnNetworkPolicyChangeListener that will be removed
+     * @throws SecurityException if the caller does not have the required permission
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public void removeVcnNetworkPolicyChangeListener(
+            @NonNull VcnNetworkPolicyChangeListener listener) {
+        requireNonNull(listener, "listener must not be null");
+
+        VcnUnderlyingNetworkPolicyListenerBinder binder =
+                REGISTERED_POLICY_LISTENERS.remove(listener);
+        if (binder == null) {
+            return;
+        }
+
+        try {
+            mService.removeVcnUnderlyingNetworkPolicyListener(binder);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Applies the network policy for a {@link android.net.Network} with the given parameters.
+     *
+     * <p>Prior to a new NetworkAgent being registered, or upon notification that Carrier VCN policy
+     * may have changed via {@link VcnNetworkPolicyChangeListener#onPolicyChanged()}, a Network
+     * Provider MUST poll for the updated Network policy based on that Network's capabilities and
+     * properties.
+     *
+     * @param networkCapabilities the NetworkCapabilities to be used in determining the Network
+     *     policy result for this Network.
+     * @param linkProperties the LinkProperties to be used in determining the Network policy result
+     *     for this Network.
+     * @throws SecurityException if the caller does not have the required permission
+     * @return the {@link VcnNetworkPolicyResult} to be used for this Network.
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public VcnNetworkPolicyResult applyVcnNetworkPolicy(
+            @NonNull NetworkCapabilities networkCapabilities,
+            @NonNull LinkProperties linkProperties) {
+        requireNonNull(networkCapabilities, "networkCapabilities must not be null");
+        requireNonNull(linkProperties, "linkProperties must not be null");
+
+        final VcnUnderlyingNetworkPolicy policy =
+                getUnderlyingNetworkPolicy(networkCapabilities, linkProperties);
+        return new VcnNetworkPolicyResult(
+                policy.isTeardownRequested(), policy.getMergedNetworkCapabilities());
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        VCN_STATUS_CODE_NOT_CONFIGURED,
+        VCN_STATUS_CODE_INACTIVE,
+        VCN_STATUS_CODE_ACTIVE,
+        VCN_STATUS_CODE_SAFE_MODE
+    })
+    public @interface VcnStatusCode {}
+
+    /**
+     * Value indicating that the VCN for the subscription group is not configured, or that the
+     * callback is not privileged for the subscription group.
+     */
+    public static final int VCN_STATUS_CODE_NOT_CONFIGURED = 0;
+
+    /**
+     * Value indicating that the VCN for the subscription group is inactive.
+     *
+     * <p>A VCN is inactive if a {@link VcnConfig} is present for the subscription group, but the
+     * provisioning package is not privileged.
+     */
+    public static final int VCN_STATUS_CODE_INACTIVE = 1;
+
+    /**
+     * Value indicating that the VCN for the subscription group is active.
+     *
+     * <p>A VCN is active if a {@link VcnConfig} is present for the subscription, the provisioning
+     * package is privileged, and the VCN is not in Safe Mode. In other words, a VCN is considered
+     * active while it is connecting, fully connected, and disconnecting.
+     */
+    public static final int VCN_STATUS_CODE_ACTIVE = 2;
+
+    /**
+     * Value indicating that the VCN for the subscription group is in Safe Mode.
+     *
+     * <p>A VCN will be put into Safe Mode if any of the gateway connections were unable to
+     * establish a connection within a system-determined timeout (while underlying networks were
+     * available).
+     */
+    public static final int VCN_STATUS_CODE_SAFE_MODE = 3;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        VCN_ERROR_CODE_INTERNAL_ERROR,
+        VCN_ERROR_CODE_CONFIG_ERROR,
+        VCN_ERROR_CODE_NETWORK_ERROR
+    })
+    public @interface VcnErrorCode {}
+
+    /**
+     * Value indicating that an internal failure occurred in this Gateway Connection.
+     */
+    public static final int VCN_ERROR_CODE_INTERNAL_ERROR = 0;
+
+    /**
+     * Value indicating that an error with this Gateway Connection's configuration occurred.
+     *
+     * <p>For example, this error code will be returned after authentication failures.
+     */
+    public static final int VCN_ERROR_CODE_CONFIG_ERROR = 1;
+
+    /**
+     * Value indicating that a Network error occurred with this Gateway Connection.
+     *
+     * <p>For example, this error code will be returned if an underlying {@link android.net.Network}
+     * for this Gateway Connection is lost, or if an error occurs while resolving the connection
+     * endpoint address.
+     */
+    public static final int VCN_ERROR_CODE_NETWORK_ERROR = 2;
+
+    /**
+     * VcnStatusCallback is the interface for Carrier apps to receive updates for their VCNs.
+     *
+     * <p>VcnStatusCallbacks may be registered before {@link VcnConfig}s are provided for a
+     * subscription group.
+     */
+    public abstract static class VcnStatusCallback {
+        private VcnStatusCallbackBinder mCbBinder;
+
+        /**
+         * Invoked when status of the VCN for this callback's subscription group changes.
+         *
+         * @param statusCode the code for the status change encountered by this {@link
+         *     VcnStatusCallback}'s subscription group. This value will be one of VCN_STATUS_CODE_*.
+         */
+        public abstract void onStatusChanged(@VcnStatusCode int statusCode);
+
+        /**
+         * Invoked when a VCN Gateway Connection corresponding to this callback's subscription group
+         * encounters an error.
+         *
+         * @param gatewayConnectionName the String GatewayConnection name for the GatewayConnection
+         *     encountering an error. This will match the name for exactly one {@link
+         *     VcnGatewayConnectionConfig} for the {@link VcnConfig} configured for this callback's
+         *     subscription group
+         * @param errorCode the code to indicate the error that occurred. This value will be one of
+         *     VCN_ERROR_CODE_*.
+         * @param detail Throwable to provide additional information about the error, or {@code
+         *     null} if none
+         */
+        public abstract void onGatewayConnectionError(
+                @NonNull String gatewayConnectionName,
+                @VcnErrorCode int errorCode,
+                @Nullable Throwable detail);
+    }
+
+    /**
+     * Registers the given callback to receive status updates for the specified subscription.
+     *
+     * <p>Callbacks can be registered for a subscription before {@link VcnConfig}s are set for it.
+     *
+     * <p>A {@link VcnStatusCallback} may only be registered for one subscription at a time. {@link
+     * VcnStatusCallback}s may be reused once unregistered.
+     *
+     * <p>A {@link VcnStatusCallback} will only be invoked if the registering package has carrier
+     * privileges for the specified subscription at the time of invocation.
+     *
+     * <p>A {@link VcnStatusCallback} is eligible to begin receiving callbacks once it is registered
+     * and there is a VCN active for its specified subscription group (this may happen after the
+     * callback is registered).
+     *
+     * <p>{@link VcnStatusCallback#onStatusChanged(int)} will be invoked on registration with the
+     * current status for the specified subscription group's VCN. If the registrant is not
+     * privileged for this subscription group, {@link #VCN_STATUS_CODE_NOT_CONFIGURED} will be
+     * returned.
+     *
+     * @param subscriptionGroup The subscription group to match for callbacks
+     * @param executor The {@link Executor} to be used for invoking callbacks
+     * @param callback The VcnStatusCallback to be registered
+     * @throws IllegalStateException if callback is currently registered with VcnManager
+     */
+    public void registerVcnStatusCallback(
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull Executor executor,
+            @NonNull VcnStatusCallback callback) {
+        requireNonNull(subscriptionGroup, "subscriptionGroup must not be null");
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        synchronized (callback) {
+            if (callback.mCbBinder != null) {
+                throw new IllegalStateException("callback is already registered with VcnManager");
+            }
+            callback.mCbBinder = new VcnStatusCallbackBinder(executor, callback);
+
+            try {
+                mService.registerVcnStatusCallback(
+                        subscriptionGroup, callback.mCbBinder, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                callback.mCbBinder = null;
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregisters the given callback.
+     *
+     * <p>Once unregistered, the callback will stop receiving status updates for the subscription it
+     * was registered with.
+     *
+     * @param callback The callback to be unregistered
+     */
+    public void unregisterVcnStatusCallback(@NonNull VcnStatusCallback callback) {
+        requireNonNull(callback, "callback must not be null");
+
+        synchronized (callback) {
+            if (callback.mCbBinder == null) {
+                // no Binder attached to this callback, so it's not currently registered
+                return;
+            }
+
+            try {
+                mService.unregisterVcnStatusCallback(callback.mCbBinder);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            } finally {
+                callback.mCbBinder = null;
+            }
+        }
+    }
+
+    /**
+     * Binder wrapper for added VcnNetworkPolicyChangeListeners to receive signals from System
+     * Server.
+     *
+     * @hide
+     */
+    private static class VcnUnderlyingNetworkPolicyListenerBinder
+            extends IVcnUnderlyingNetworkPolicyListener.Stub {
+        @NonNull private final Executor mExecutor;
+        @NonNull private final VcnNetworkPolicyChangeListener mListener;
+
+        private VcnUnderlyingNetworkPolicyListenerBinder(
+                Executor executor, VcnNetworkPolicyChangeListener listener) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        @Override
+        public void onPolicyChanged() {
+            BinderUtils.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> mListener.onPolicyChanged()));
+        }
+    }
+
+    /**
+     * Binder wrapper for VcnStatusCallbacks to receive signals from VcnManagementService.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class VcnStatusCallbackBinder extends IVcnStatusCallback.Stub {
+        @NonNull private final Executor mExecutor;
+        @NonNull private final VcnStatusCallback mCallback;
+
+        public VcnStatusCallbackBinder(
+                @NonNull Executor executor, @NonNull VcnStatusCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onVcnStatusChanged(@VcnStatusCode int statusCode) {
+            BinderUtils.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> mCallback.onStatusChanged(statusCode)));
+        }
+
+        // TODO(b/180521637): use ServiceSpecificException for safer Exception 'parceling'
+        @Override
+        public void onGatewayConnectionError(
+                @NonNull String gatewayConnectionName,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage) {
+            final Throwable cause = createThrowableByClassName(exceptionClass, exceptionMessage);
+
+            BinderUtils.withCleanCallingIdentity(
+                    () ->
+                            mExecutor.execute(
+                                    () ->
+                                            mCallback.onGatewayConnectionError(
+                                                    gatewayConnectionName, errorCode, cause)));
+        }
+
+        private static Throwable createThrowableByClassName(
+                @Nullable String className, @Nullable String message) {
+            if (className == null) {
+                return null;
+            }
+
+            try {
+                Class<?> c = Class.forName(className);
+                return (Throwable) c.getConstructor(String.class).newInstance(message);
+            } catch (ReflectiveOperationException | ClassCastException e) {
+                return new RuntimeException(className + ": " + message);
+            }
+        }
+    }
+}
diff --git a/core/java/android/net/vcn/VcnNetworkPolicyResult.aidl b/packages/Vcn/framework-b/src/android/net/vcn/VcnNetworkPolicyResult.aidl
similarity index 100%
rename from core/java/android/net/vcn/VcnNetworkPolicyResult.aidl
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnNetworkPolicyResult.aidl
diff --git a/core/java/android/net/vcn/VcnNetworkPolicyResult.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnNetworkPolicyResult.java
similarity index 100%
rename from core/java/android/net/vcn/VcnNetworkPolicyResult.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnNetworkPolicyResult.java
diff --git a/core/java/android/net/vcn/VcnTransportInfo.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java
similarity index 100%
rename from core/java/android/net/vcn/VcnTransportInfo.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnTransportInfo.java
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.aidl b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkPolicy.aidl
similarity index 100%
rename from core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.aidl
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkPolicy.aidl
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkPolicy.java
similarity index 100%
rename from core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkPolicy.java
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
similarity index 100%
rename from core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkTemplate.java
similarity index 100%
rename from core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java
rename to packages/Vcn/framework-b/src/android/net/vcn/VcnUnderlyingNetworkTemplate.java
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java b/packages/Vcn/framework-b/src/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
new file mode 100644
index 0000000..770a8c1
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
+import static android.net.vcn.util.PersistableBundleUtils.STRING_SERIALIZER;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.NetworkCapabilities;
+import android.net.vcn.VcnUnderlyingNetworkTemplate.MatchCriteria;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.os.PersistableBundle;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This class represents a configuration for a network template class of underlying Carrier WiFi
+ * networks.
+ *
+ * <p>See {@link VcnUnderlyingNetworkTemplate}
+ */
+public final class VcnWifiUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate {
+    private static final String SSIDS_KEY = "mSsids";
+    @Nullable private final Set<String> mSsids;
+
+    private VcnWifiUnderlyingNetworkTemplate(
+            int meteredMatchCriteria,
+            int minEntryUpstreamBandwidthKbps,
+            int minExitUpstreamBandwidthKbps,
+            int minEntryDownstreamBandwidthKbps,
+            int minExitDownstreamBandwidthKbps,
+            Set<String> ssids) {
+        super(
+                NETWORK_PRIORITY_TYPE_WIFI,
+                meteredMatchCriteria,
+                minEntryUpstreamBandwidthKbps,
+                minExitUpstreamBandwidthKbps,
+                minEntryDownstreamBandwidthKbps,
+                minExitDownstreamBandwidthKbps);
+        mSsids = new ArraySet<>(ssids);
+
+        validate();
+    }
+
+    /** @hide */
+    @Override
+    protected void validate() {
+        super.validate();
+        validateSsids(mSsids);
+    }
+
+    private static void validateSsids(Set<String> ssids) {
+        Objects.requireNonNull(ssids, "ssids is null");
+
+        for (String ssid : ssids) {
+            Objects.requireNonNull(ssid, "found null value ssid");
+        }
+    }
+
+    /** @hide */
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public static VcnWifiUnderlyingNetworkTemplate fromPersistableBundle(
+            @NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle is null");
+
+        final int meteredMatchCriteria = in.getInt(METERED_MATCH_KEY);
+
+        final int minEntryUpstreamBandwidthKbps =
+                in.getInt(MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+        final int minExitUpstreamBandwidthKbps =
+                in.getInt(MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+        final int minEntryDownstreamBandwidthKbps =
+                in.getInt(MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+        final int minExitDownstreamBandwidthKbps =
+                in.getInt(MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS_KEY, DEFAULT_MIN_BANDWIDTH_KBPS);
+
+        final PersistableBundle ssidsBundle = in.getPersistableBundle(SSIDS_KEY);
+        Objects.requireNonNull(ssidsBundle, "ssidsBundle is null");
+        final Set<String> ssids =
+                new ArraySet<String>(
+                        PersistableBundleUtils.toList(ssidsBundle, STRING_DESERIALIZER));
+        return new VcnWifiUnderlyingNetworkTemplate(
+                meteredMatchCriteria,
+                minEntryUpstreamBandwidthKbps,
+                minExitUpstreamBandwidthKbps,
+                minEntryDownstreamBandwidthKbps,
+                minExitDownstreamBandwidthKbps,
+                ssids);
+    }
+
+    /** @hide */
+    @Override
+    @NonNull
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public PersistableBundle toPersistableBundle() {
+        final PersistableBundle result = super.toPersistableBundle();
+
+        final PersistableBundle ssidsBundle =
+                PersistableBundleUtils.fromList(new ArrayList<>(mSsids), STRING_SERIALIZER);
+        result.putPersistableBundle(SSIDS_KEY, ssidsBundle);
+
+        return result;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), mSsids);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (!super.equals(other)) {
+            return false;
+        }
+
+        if (!(other instanceof VcnWifiUnderlyingNetworkTemplate)) {
+            return false;
+        }
+
+        final VcnWifiUnderlyingNetworkTemplate rhs = (VcnWifiUnderlyingNetworkTemplate) other;
+        return mSsids.equals(rhs.mSsids);
+    }
+
+    /** @hide */
+    @Override
+    void dumpTransportSpecificFields(IndentingPrintWriter pw) {
+        if (!mSsids.isEmpty()) {
+            pw.println("mSsids: " + mSsids);
+        }
+    }
+
+    /**
+     * Retrieve the matching SSIDs, or an empty set if any SSID is acceptable.
+     *
+     * @see Builder#setSsids(Set)
+     */
+    @NonNull
+    public Set<String> getSsids() {
+        return Collections.unmodifiableSet(mSsids);
+    }
+
+    /** @hide */
+    @Override
+    public Map<Integer, Integer> getCapabilitiesMatchCriteria() {
+        return Collections.singletonMap(NET_CAPABILITY_INTERNET, MATCH_REQUIRED);
+    }
+
+    /** This class is used to incrementally build VcnWifiUnderlyingNetworkTemplate objects. */
+    public static final class Builder {
+        private int mMeteredMatchCriteria = MATCH_ANY;
+        @NonNull private final Set<String> mSsids = new ArraySet<>();
+
+        private int mMinEntryUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+        private int mMinExitUpstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+        private int mMinEntryDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+        private int mMinExitDownstreamBandwidthKbps = DEFAULT_MIN_BANDWIDTH_KBPS;
+
+        /** Construct a Builder object. */
+        public Builder() {}
+
+        /**
+         * Set the matching criteria for metered networks.
+         *
+         * <p>A template where setMetered(MATCH_REQUIRED) will only match metered networks (one
+         * without NET_CAPABILITY_NOT_METERED). A template where setMetered(MATCH_FORBIDDEN) will
+         * only match a network that is not metered (one with NET_CAPABILITY_NOT_METERED).
+         *
+         * @param matchCriteria the matching criteria for metered networks. Defaults to {@link
+         *     #MATCH_ANY}.
+         * @see NetworkCapabilities#NET_CAPABILITY_NOT_METERED
+         */
+        // The matching getter is defined in the super class. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMetered()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setMetered(@MatchCriteria int matchCriteria) {
+            validateMatchCriteria(matchCriteria, "setMetered");
+
+            mMeteredMatchCriteria = matchCriteria;
+            return this;
+        }
+
+        /**
+         * Set the SSIDs with which a network can match this priority rule.
+         *
+         * @param ssids the matching SSIDs. Network with one of the matching SSIDs can match this
+         *     priority rule. If the set is empty, any SSID will match. The default is an empty set.
+         */
+        @NonNull
+        public Builder setSsids(@NonNull Set<String> ssids) {
+            validateSsids(ssids);
+
+            mSsids.clear();
+            mSsids.addAll(ssids);
+            return this;
+        }
+
+        /**
+         * Set the minimum upstream bandwidths that this template will match.
+         *
+         * <p>This template will not match a network that does not provide at least the bandwidth
+         * passed as the entry bandwidth, except in the case that the network is selected as the VCN
+         * Gateway Connection's underlying network, where it will continue to match until the
+         * bandwidth drops under the exit bandwidth.
+         *
+         * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
+         * invalid case where a network fulfills the entry criteria, but at the same time fails the
+         * exit criteria.
+         *
+         * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
+         * {@link NetworkCapabilities}. The provided estimates will be used without modification.
+         *
+         * @param minEntryUpstreamBandwidthKbps the minimum accepted upstream bandwidth for networks
+         *     that ARE NOT the already-selected underlying network, or {@code 0} to disable this
+         *     requirement. Disabled by default.
+         * @param minExitUpstreamBandwidthKbps the minimum accepted upstream bandwidth for a network
+         *     that IS the already-selected underlying network, or {@code 0} to disable this
+         *     requirement. Disabled by default.
+         * @return this {@link Builder} instance, for chaining
+         */
+        @NonNull
+        // The getter for the two integers are separated, and in the superclass. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMinEntryUpstreamBandwidthKbps()} and {@link
+        // VcnUnderlyingNetworkTemplate#getMinExitUpstreamBandwidthKbps()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setMinUpstreamBandwidthKbps(
+                int minEntryUpstreamBandwidthKbps, int minExitUpstreamBandwidthKbps) {
+            validateMinBandwidthKbps(minEntryUpstreamBandwidthKbps, minExitUpstreamBandwidthKbps);
+
+            mMinEntryUpstreamBandwidthKbps = minEntryUpstreamBandwidthKbps;
+            mMinExitUpstreamBandwidthKbps = minExitUpstreamBandwidthKbps;
+
+            return this;
+        }
+
+        /**
+         * Set the minimum upstream bandwidths that this template will match.
+         *
+         * <p>This template will not match a network that does not provide at least the bandwidth
+         * passed as the entry bandwidth, except in the case that the network is selected as the VCN
+         * Gateway Connection's underlying network, where it will continue to match until the
+         * bandwidth drops under the exit bandwidth.
+         *
+         * <p>The entry criteria MUST be greater than, or equal to the exit criteria to avoid the
+         * invalid case where a network fulfills the entry criteria, but at the same time fails the
+         * exit criteria.
+         *
+         * <p>Estimated bandwidth of a network is provided by the transport layer, and reported in
+         * {@link NetworkCapabilities}. The provided estimates will be used without modification.
+         *
+         * @param minEntryDownstreamBandwidthKbps the minimum accepted downstream bandwidth for
+         *     networks that ARE NOT the already-selected underlying network, or {@code 0} to
+         *     disable this requirement. Disabled by default.
+         * @param minExitDownstreamBandwidthKbps the minimum accepted downstream bandwidth for a
+         *     network that IS the already-selected underlying network, or {@code 0} to disable this
+         *     requirement. Disabled by default.
+         * @return this {@link Builder} instance, for chaining
+         */
+        @NonNull
+        // The getter for the two integers are separated, and in the superclass. Please see {@link
+        // VcnUnderlyingNetworkTemplate#getMinEntryDownstreamBandwidthKbps()} and {@link
+        // VcnUnderlyingNetworkTemplate#getMinExitDownstreamBandwidthKbps()}
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setMinDownstreamBandwidthKbps(
+                int minEntryDownstreamBandwidthKbps, int minExitDownstreamBandwidthKbps) {
+            validateMinBandwidthKbps(
+                    minEntryDownstreamBandwidthKbps, minExitDownstreamBandwidthKbps);
+
+            mMinEntryDownstreamBandwidthKbps = minEntryDownstreamBandwidthKbps;
+            mMinExitDownstreamBandwidthKbps = minExitDownstreamBandwidthKbps;
+
+            return this;
+        }
+
+        /** Build the VcnWifiUnderlyingNetworkTemplate. */
+        @NonNull
+        public VcnWifiUnderlyingNetworkTemplate build() {
+            return new VcnWifiUnderlyingNetworkTemplate(
+                    mMeteredMatchCriteria,
+                    mMinEntryUpstreamBandwidthKbps,
+                    mMinExitUpstreamBandwidthKbps,
+                    mMinEntryDownstreamBandwidthKbps,
+                    mMinExitDownstreamBandwidthKbps,
+                    mSsids);
+        }
+    }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/CertUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/CertUtils.java
similarity index 100%
rename from core/java/android/net/vcn/persistablebundleutils/CertUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/CertUtils.java
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
new file mode 100644
index 0000000..48c1b25
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/ChildSaProposalUtils.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides utility methods to convert ChildSaProposal to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class ChildSaProposalUtils extends SaProposalUtilsBase {
+    /** Serializes a ChildSaProposal to a PersistableBundle. */
+    @NonNull
+    public static PersistableBundle toPersistableBundle(ChildSaProposal proposal) {
+        return SaProposalUtilsBase.toPersistableBundle(proposal);
+    }
+
+    /** Constructs a ChildSaProposal by deserializing a PersistableBundle. */
+    @NonNull
+    public static ChildSaProposal fromPersistableBundle(@NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle was null");
+
+        final ChildSaProposal.Builder builder = new ChildSaProposal.Builder();
+
+        final PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY);
+        Objects.requireNonNull(encryptionBundle, "Encryption algo bundle was null");
+        final List<EncryptionAlgoKeyLenPair> encryptList =
+                PersistableBundleUtils.toList(encryptionBundle, EncryptionAlgoKeyLenPair::new);
+        for (EncryptionAlgoKeyLenPair t : encryptList) {
+            builder.addEncryptionAlgorithm(t.encryptionAlgo, t.keyLen);
+        }
+
+        final int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY);
+        Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array was null");
+        for (int algo : integrityAlgoIdArray) {
+            builder.addIntegrityAlgorithm(algo);
+        }
+
+        final int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY);
+        Objects.requireNonNull(dhGroupArray, "DH Group array was null");
+        for (int dh : dhGroupArray) {
+            builder.addDhGroup(dh);
+        }
+
+        return builder.build();
+    }
+}
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java
new file mode 100644
index 0000000..dc1ee36
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/EapSessionConfigUtils.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.net.eap.EapSessionConfig;
+import android.net.eap.EapSessionConfig.EapAkaConfig;
+import android.net.eap.EapSessionConfig.EapAkaPrimeConfig;
+import android.net.eap.EapSessionConfig.EapMethodConfig;
+import android.net.eap.EapSessionConfig.EapMsChapV2Config;
+import android.net.eap.EapSessionConfig.EapSimConfig;
+import android.net.eap.EapSessionConfig.EapTtlsConfig;
+import android.net.eap.EapSessionConfig.EapUiccConfig;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.Objects;
+
+/**
+ * Provides utility methods to convert EapSessionConfig to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class EapSessionConfigUtils {
+    private static final String EAP_ID_KEY = "EAP_ID_KEY";
+    private static final String EAP_SIM_CONFIG_KEY = "EAP_SIM_CONFIG_KEY";
+    private static final String EAP_TTLS_CONFIG_KEY = "EAP_TTLS_CONFIG_KEY";
+    private static final String EAP_AKA_CONFIG_KEY = "EAP_AKA_CONFIG_KEY";
+    private static final String EAP_MSCHAP_V2_CONFIG_KEY = "EAP_MSCHAP_V2_CONFIG_KEY";
+    private static final String EAP_AKA_PRIME_CONFIG_KEY = "EAP_AKA_PRIME_CONFIG_KEY";
+
+    /** Serializes an EapSessionConfig to a PersistableBundle. */
+    @NonNull
+    public static PersistableBundle toPersistableBundle(@NonNull EapSessionConfig config) {
+        final PersistableBundle result = new PersistableBundle();
+
+        result.putPersistableBundle(
+                EAP_ID_KEY, PersistableBundleUtils.fromByteArray(config.getEapIdentity()));
+
+        if (config.getEapSimConfig() != null) {
+            result.putPersistableBundle(
+                    EAP_SIM_CONFIG_KEY,
+                    EapSimConfigUtils.toPersistableBundle(config.getEapSimConfig()));
+        }
+
+        if (config.getEapTtlsConfig() != null) {
+            result.putPersistableBundle(
+                    EAP_TTLS_CONFIG_KEY,
+                    EapTtlsConfigUtils.toPersistableBundle(config.getEapTtlsConfig()));
+        }
+
+        if (config.getEapAkaConfig() != null) {
+            result.putPersistableBundle(
+                    EAP_AKA_CONFIG_KEY,
+                    EapAkaConfigUtils.toPersistableBundle(config.getEapAkaConfig()));
+        }
+
+        if (config.getEapMsChapV2Config() != null) {
+            result.putPersistableBundle(
+                    EAP_MSCHAP_V2_CONFIG_KEY,
+                    EapMsChapV2ConfigUtils.toPersistableBundle(config.getEapMsChapV2Config()));
+        }
+
+        if (config.getEapAkaPrimeConfig() != null) {
+            result.putPersistableBundle(
+                    EAP_AKA_PRIME_CONFIG_KEY,
+                    EapAkaPrimeConfigUtils.toPersistableBundle(config.getEapAkaPrimeConfig()));
+        }
+
+        return result;
+    }
+
+    /** Constructs an EapSessionConfig by deserializing a PersistableBundle. */
+    @NonNull
+    public static EapSessionConfig fromPersistableBundle(@NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle was null");
+
+        final EapSessionConfig.Builder builder = new EapSessionConfig.Builder();
+
+        final PersistableBundle eapIdBundle = in.getPersistableBundle(EAP_ID_KEY);
+        Objects.requireNonNull(eapIdBundle, "EAP ID was null");
+        builder.setEapIdentity(PersistableBundleUtils.toByteArray(eapIdBundle));
+
+        final PersistableBundle simBundle = in.getPersistableBundle(EAP_SIM_CONFIG_KEY);
+        if (simBundle != null) {
+            EapSimConfigUtils.setBuilderByReadingPersistableBundle(simBundle, builder);
+        }
+
+        final PersistableBundle ttlsBundle = in.getPersistableBundle(EAP_TTLS_CONFIG_KEY);
+        if (ttlsBundle != null) {
+            EapTtlsConfigUtils.setBuilderByReadingPersistableBundle(ttlsBundle, builder);
+        }
+
+        final PersistableBundle akaBundle = in.getPersistableBundle(EAP_AKA_CONFIG_KEY);
+        if (akaBundle != null) {
+            EapAkaConfigUtils.setBuilderByReadingPersistableBundle(akaBundle, builder);
+        }
+
+        final PersistableBundle msChapV2Bundle = in.getPersistableBundle(EAP_MSCHAP_V2_CONFIG_KEY);
+        if (msChapV2Bundle != null) {
+            EapMsChapV2ConfigUtils.setBuilderByReadingPersistableBundle(msChapV2Bundle, builder);
+        }
+
+        final PersistableBundle akaPrimeBundle = in.getPersistableBundle(EAP_AKA_PRIME_CONFIG_KEY);
+        if (akaPrimeBundle != null) {
+            EapAkaPrimeConfigUtils.setBuilderByReadingPersistableBundle(akaPrimeBundle, builder);
+        }
+
+        return builder.build();
+    }
+
+    private static class EapMethodConfigUtils {
+        private static final String METHOD_TYPE = "METHOD_TYPE";
+
+        /** Serializes an EapMethodConfig to a PersistableBundle. */
+        @NonNull
+        public static PersistableBundle toPersistableBundle(@NonNull EapMethodConfig config) {
+            final PersistableBundle result = new PersistableBundle();
+            result.putInt(METHOD_TYPE, config.getMethodType());
+            return result;
+        }
+    }
+
+    private static class EapUiccConfigUtils extends EapMethodConfigUtils {
+        static final String SUB_ID_KEY = "SUB_ID_KEY";
+        static final String APP_TYPE_KEY = "APP_TYPE_KEY";
+
+        @NonNull
+        protected static PersistableBundle toPersistableBundle(@NonNull EapUiccConfig config) {
+            final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config);
+            result.putInt(SUB_ID_KEY, config.getSubId());
+            result.putInt(APP_TYPE_KEY, config.getAppType());
+
+            return result;
+        }
+    }
+
+    private static final class EapSimConfigUtils extends EapUiccConfigUtils {
+        @NonNull
+        public static PersistableBundle toPersistableBundle(EapSimConfig config) {
+            return EapUiccConfigUtils.toPersistableBundle(config);
+        }
+
+        public static void setBuilderByReadingPersistableBundle(
+                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+            builder.setEapSimConfig(in.getInt(SUB_ID_KEY), in.getInt(APP_TYPE_KEY));
+        }
+    }
+
+    private static class EapAkaConfigUtils extends EapUiccConfigUtils {
+        @NonNull
+        public static PersistableBundle toPersistableBundle(@NonNull EapAkaConfig config) {
+            return EapUiccConfigUtils.toPersistableBundle(config);
+        }
+
+        public static void setBuilderByReadingPersistableBundle(
+                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+            builder.setEapAkaConfig(in.getInt(SUB_ID_KEY), in.getInt(APP_TYPE_KEY));
+        }
+    }
+
+    private static final class EapAkaPrimeConfigUtils extends EapAkaConfigUtils {
+        private static final String NETWORK_NAME_KEY = "NETWORK_NAME_KEY";
+        private static final String ALL_MISMATCHED_NETWORK_KEY = "ALL_MISMATCHED_NETWORK_KEY";
+
+        @NonNull
+        public static PersistableBundle toPersistableBundle(@NonNull EapAkaPrimeConfig config) {
+            final PersistableBundle result = EapUiccConfigUtils.toPersistableBundle(config);
+            result.putString(NETWORK_NAME_KEY, config.getNetworkName());
+            result.putBoolean(ALL_MISMATCHED_NETWORK_KEY, config.allowsMismatchedNetworkNames());
+
+            return result;
+        }
+
+        public static void setBuilderByReadingPersistableBundle(
+                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+            builder.setEapAkaPrimeConfig(
+                    in.getInt(SUB_ID_KEY),
+                    in.getInt(APP_TYPE_KEY),
+                    in.getString(NETWORK_NAME_KEY),
+                    in.getBoolean(ALL_MISMATCHED_NETWORK_KEY));
+        }
+    }
+
+    private static final class EapMsChapV2ConfigUtils extends EapMethodConfigUtils {
+        private static final String USERNAME_KEY = "USERNAME_KEY";
+        private static final String PASSWORD_KEY = "PASSWORD_KEY";
+
+        @NonNull
+        public static PersistableBundle toPersistableBundle(@NonNull EapMsChapV2Config config) {
+            final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config);
+            result.putString(USERNAME_KEY, config.getUsername());
+            result.putString(PASSWORD_KEY, config.getPassword());
+
+            return result;
+        }
+
+        public static void setBuilderByReadingPersistableBundle(
+                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+            builder.setEapMsChapV2Config(in.getString(USERNAME_KEY), in.getString(PASSWORD_KEY));
+        }
+    }
+
+    private static final class EapTtlsConfigUtils extends EapMethodConfigUtils {
+        private static final String TRUST_CERT_KEY = "TRUST_CERT_KEY";
+        private static final String EAP_SESSION_CONFIG_KEY = "EAP_SESSION_CONFIG_KEY";
+
+        @NonNull
+        public static PersistableBundle toPersistableBundle(@NonNull EapTtlsConfig config) {
+            final PersistableBundle result = EapMethodConfigUtils.toPersistableBundle(config);
+            try {
+                if (config.getServerCaCert() != null) {
+                    final PersistableBundle caBundle =
+                            PersistableBundleUtils.fromByteArray(
+                                    config.getServerCaCert().getEncoded());
+                    result.putPersistableBundle(TRUST_CERT_KEY, caBundle);
+                }
+            } catch (CertificateEncodingException e) {
+                throw new IllegalStateException("Fail to encode the certificate");
+            }
+
+            result.putPersistableBundle(
+                    EAP_SESSION_CONFIG_KEY,
+                    EapSessionConfigUtils.toPersistableBundle(config.getInnerEapSessionConfig()));
+
+            return result;
+        }
+
+        public static void setBuilderByReadingPersistableBundle(
+                @NonNull PersistableBundle in, @NonNull EapSessionConfig.Builder builder) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+
+            final PersistableBundle caBundle = in.getPersistableBundle(TRUST_CERT_KEY);
+            X509Certificate caCert = null;
+            if (caBundle != null) {
+                caCert =
+                        CertUtils.certificateFromByteArray(
+                                PersistableBundleUtils.toByteArray(caBundle));
+            }
+
+            final PersistableBundle eapSessionConfigBundle =
+                    in.getPersistableBundle(EAP_SESSION_CONFIG_KEY);
+            Objects.requireNonNull(eapSessionConfigBundle, "Inner EAP Session Config was null");
+            final EapSessionConfig eapSessionConfig =
+                    EapSessionConfigUtils.fromPersistableBundle(eapSessionConfigBundle);
+
+            builder.setEapTtlsConfig(caCert, eapSessionConfig);
+        }
+    }
+}
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java
new file mode 100644
index 0000000..6e8616f
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeIdentificationUtils.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.net.InetAddresses;
+import android.net.ipsec.ike.IkeDerAsn1DnIdentification;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIdentification;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeIpv6AddrIdentification;
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.util.Objects;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * Abstract utility class to convert IkeIdentification to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class IkeIdentificationUtils {
+    private static final String ID_TYPE_KEY = "ID_TYPE_KEY";
+
+    private static final String DER_ASN1_DN_KEY = "DER_ASN1_DN_KEY";
+    private static final String FQDN_KEY = "FQDN_KEY";
+    private static final String KEY_ID_KEY = "KEY_ID_KEY";
+    private static final String IP4_ADDRESS_KEY = "IP4_ADDRESS_KEY";
+    private static final String IP6_ADDRESS_KEY = "IP6_ADDRESS_KEY";
+    private static final String RFC822_ADDRESS_KEY = "RFC822_ADDRESS_KEY";
+
+    private static final int ID_TYPE_DER_ASN1_DN = 1;
+    private static final int ID_TYPE_FQDN = 2;
+    private static final int ID_TYPE_IPV4_ADDR = 3;
+    private static final int ID_TYPE_IPV6_ADDR = 4;
+    private static final int ID_TYPE_KEY_ID = 5;
+    private static final int ID_TYPE_RFC822_ADDR = 6;
+
+    /** Serializes an IkeIdentification to a PersistableBundle. */
+    @NonNull
+    public static PersistableBundle toPersistableBundle(@NonNull IkeIdentification ikeId) {
+        if (ikeId instanceof IkeDerAsn1DnIdentification) {
+            final PersistableBundle result = createPersistableBundle(ID_TYPE_DER_ASN1_DN);
+            IkeDerAsn1DnIdentification id = (IkeDerAsn1DnIdentification) ikeId;
+            result.putPersistableBundle(
+                    DER_ASN1_DN_KEY,
+                    PersistableBundleUtils.fromByteArray(id.derAsn1Dn.getEncoded()));
+            return result;
+        } else if (ikeId instanceof IkeFqdnIdentification) {
+            final PersistableBundle result = createPersistableBundle(ID_TYPE_FQDN);
+            IkeFqdnIdentification id = (IkeFqdnIdentification) ikeId;
+            result.putString(FQDN_KEY, id.fqdn);
+            return result;
+        } else if (ikeId instanceof IkeIpv4AddrIdentification) {
+            final PersistableBundle result = createPersistableBundle(ID_TYPE_IPV4_ADDR);
+            IkeIpv4AddrIdentification id = (IkeIpv4AddrIdentification) ikeId;
+            result.putString(IP4_ADDRESS_KEY, id.ipv4Address.getHostAddress());
+            return result;
+        } else if (ikeId instanceof IkeIpv6AddrIdentification) {
+            final PersistableBundle result = createPersistableBundle(ID_TYPE_IPV6_ADDR);
+            IkeIpv6AddrIdentification id = (IkeIpv6AddrIdentification) ikeId;
+            result.putString(IP6_ADDRESS_KEY, id.ipv6Address.getHostAddress());
+            return result;
+        } else if (ikeId instanceof IkeKeyIdIdentification) {
+            final PersistableBundle result = createPersistableBundle(ID_TYPE_KEY_ID);
+            IkeKeyIdIdentification id = (IkeKeyIdIdentification) ikeId;
+            result.putPersistableBundle(KEY_ID_KEY, PersistableBundleUtils.fromByteArray(id.keyId));
+            return result;
+        } else if (ikeId instanceof IkeRfc822AddrIdentification) {
+            final PersistableBundle result = createPersistableBundle(ID_TYPE_RFC822_ADDR);
+            IkeRfc822AddrIdentification id = (IkeRfc822AddrIdentification) ikeId;
+            result.putString(RFC822_ADDRESS_KEY, id.rfc822Name);
+            return result;
+        } else {
+            throw new IllegalStateException("Unrecognized IkeIdentification subclass");
+        }
+    }
+
+    private static PersistableBundle createPersistableBundle(int idType) {
+        final PersistableBundle result = new PersistableBundle();
+        result.putInt(ID_TYPE_KEY, idType);
+        return result;
+    }
+
+    /** Constructs an IkeIdentification by deserializing a PersistableBundle. */
+    @NonNull
+    public static IkeIdentification fromPersistableBundle(@NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle was null");
+        int idType = in.getInt(ID_TYPE_KEY);
+        switch (idType) {
+            case ID_TYPE_DER_ASN1_DN:
+                final PersistableBundle dnBundle = in.getPersistableBundle(DER_ASN1_DN_KEY);
+                Objects.requireNonNull(dnBundle, "ASN1 DN was null");
+                return new IkeDerAsn1DnIdentification(
+                        new X500Principal(PersistableBundleUtils.toByteArray(dnBundle)));
+            case ID_TYPE_FQDN:
+                return new IkeFqdnIdentification(in.getString(FQDN_KEY));
+            case ID_TYPE_IPV4_ADDR:
+                final String v4AddressStr = in.getString(IP4_ADDRESS_KEY);
+                Objects.requireNonNull(v4AddressStr, "IPv4 address was null");
+                return new IkeIpv4AddrIdentification(
+                        (Inet4Address) InetAddresses.parseNumericAddress(v4AddressStr));
+            case ID_TYPE_IPV6_ADDR:
+                final String v6AddressStr = in.getString(IP6_ADDRESS_KEY);
+                Objects.requireNonNull(v6AddressStr, "IPv6 address was null");
+                return new IkeIpv6AddrIdentification(
+                        (Inet6Address) InetAddresses.parseNumericAddress(v6AddressStr));
+            case ID_TYPE_KEY_ID:
+                final PersistableBundle keyIdBundle = in.getPersistableBundle(KEY_ID_KEY);
+                Objects.requireNonNull(in, "Key ID was null");
+                return new IkeKeyIdIdentification(PersistableBundleUtils.toByteArray(keyIdBundle));
+            case ID_TYPE_RFC822_ADDR:
+                return new IkeRfc822AddrIdentification(in.getString(RFC822_ADDRESS_KEY));
+            default:
+                throw new IllegalStateException("Unrecognized IKE ID type: " + idType);
+        }
+    }
+}
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
new file mode 100644
index 0000000..b590148
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSaProposalUtils.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides utility methods to convert IkeSaProposal to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class IkeSaProposalUtils extends SaProposalUtilsBase {
+    private static final String PRF_KEY = "PRF_KEY";
+
+    /** Serializes an IkeSaProposal to a PersistableBundle. */
+    @NonNull
+    public static PersistableBundle toPersistableBundle(IkeSaProposal proposal) {
+        final PersistableBundle result = SaProposalUtilsBase.toPersistableBundle(proposal);
+
+        final int[] prfArray =
+                proposal.getPseudorandomFunctions().stream().mapToInt(i -> i).toArray();
+        result.putIntArray(PRF_KEY, prfArray);
+
+        return result;
+    }
+
+    /** Constructs an IkeSaProposal by deserializing a PersistableBundle. */
+    @NonNull
+    public static IkeSaProposal fromPersistableBundle(@NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle was null");
+
+        final IkeSaProposal.Builder builder = new IkeSaProposal.Builder();
+
+        final PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY);
+        Objects.requireNonNull(encryptionBundle, "Encryption algo bundle was null");
+        final List<EncryptionAlgoKeyLenPair> encryptList =
+                PersistableBundleUtils.toList(encryptionBundle, EncryptionAlgoKeyLenPair::new);
+        for (EncryptionAlgoKeyLenPair t : encryptList) {
+            builder.addEncryptionAlgorithm(t.encryptionAlgo, t.keyLen);
+        }
+
+        final int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY);
+        Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array was null");
+        for (int algo : integrityAlgoIdArray) {
+            builder.addIntegrityAlgorithm(algo);
+        }
+
+        final int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY);
+        Objects.requireNonNull(dhGroupArray, "DH Group array was null");
+        for (int dh : dhGroupArray) {
+            builder.addDhGroup(dh);
+        }
+
+        final int[] prfArray = in.getIntArray(PRF_KEY);
+        Objects.requireNonNull(prfArray, "PRF array was null");
+        for (int prf : prfArray) {
+            builder.addPseudorandomFunction(prf);
+        }
+
+        return builder.build();
+    }
+}
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
new file mode 100644
index 0000000..aefac2e8
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+import android.net.eap.EapSessionConfig;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeSessionParams.ConfigRequestIpv4PcscfServer;
+import android.net.ipsec.ike.IkeSessionParams.ConfigRequestIpv6PcscfServer;
+import android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig;
+import android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignLocalConfig;
+import android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignRemoteConfig;
+import android.net.ipsec.ike.IkeSessionParams.IkeAuthEapConfig;
+import android.net.ipsec.ike.IkeSessionParams.IkeAuthPskConfig;
+import android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.os.PersistableBundle;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.InetAddress;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Abstract utility class to convert IkeSessionParams to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class IkeSessionParamsUtils {
+    private static final String TAG = IkeSessionParamsUtils.class.getSimpleName();
+
+    private static final String SERVER_HOST_NAME_KEY = "SERVER_HOST_NAME_KEY";
+    private static final String SA_PROPOSALS_KEY = "SA_PROPOSALS_KEY";
+    private static final String LOCAL_ID_KEY = "LOCAL_ID_KEY";
+    private static final String REMOTE_ID_KEY = "REMOTE_ID_KEY";
+    private static final String LOCAL_AUTH_KEY = "LOCAL_AUTH_KEY";
+    private static final String REMOTE_AUTH_KEY = "REMOTE_AUTH_KEY";
+    private static final String CONFIG_REQUESTS_KEY = "CONFIG_REQUESTS_KEY";
+    private static final String RETRANS_TIMEOUTS_KEY = "RETRANS_TIMEOUTS_KEY";
+    private static final String HARD_LIFETIME_SEC_KEY = "HARD_LIFETIME_SEC_KEY";
+    private static final String SOFT_LIFETIME_SEC_KEY = "SOFT_LIFETIME_SEC_KEY";
+    private static final String DPD_DELAY_SEC_KEY = "DPD_DELAY_SEC_KEY";
+    private static final String NATT_KEEPALIVE_DELAY_SEC_KEY = "NATT_KEEPALIVE_DELAY_SEC_KEY";
+    private static final String IKE_OPTIONS_KEY = "IKE_OPTIONS_KEY";
+    private static final String IP_VERSION_KEY = "IP_VERSION_KEY";
+    private static final String ENCAP_TYPE_KEY = "ENCAP_TYPE_KEY";
+    // TODO: add DSCP_KEY and IS_IKE_FRAGMENT_SUPPORTED_KEY.
+
+    // TODO: b/243181760 Use the IKE API when they are exposed
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static final int IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION = 6;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static final int IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES = 7;
+
+    private static final Set<Integer> IKE_OPTIONS = new ArraySet<>();
+
+    static {
+        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID);
+        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH);
+        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_MOBIKE);
+        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500);
+        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT);
+        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY);
+        IKE_OPTIONS.add(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION);
+        IKE_OPTIONS.add(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES);
+        IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_AUTOMATIC_KEEPALIVE_ON_OFF);
+    }
+
+    /**
+     * Check if an IKE option is supported in the IPsec module installed on the device
+     *
+     * <p>This method ensures caller to safely access options that are added between dessert
+     * releases.
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static boolean isIkeOptionValid(int option) {
+        try {
+            new IkeSessionParams.Builder().addIkeOption(option);
+            return true;
+        } catch (IllegalArgumentException e) {
+            Log.d(TAG, "Option not supported; discarding: " + option);
+            return false;
+        }
+    }
+
+    /** Serializes an IkeSessionParams to a PersistableBundle. */
+    @NonNull
+    public static PersistableBundle toPersistableBundle(@NonNull IkeSessionParams params) {
+        if (params.getNetwork() != null || params.getIke3gppExtension() != null) {
+            throw new IllegalStateException(
+                    "Cannot convert a IkeSessionParams with a caller configured network or with"
+                            + " 3GPP extension enabled");
+        }
+
+        final PersistableBundle result = new PersistableBundle();
+
+        result.putString(SERVER_HOST_NAME_KEY, params.getServerHostname());
+
+        final PersistableBundle saProposalBundle =
+                PersistableBundleUtils.fromList(
+                        params.getSaProposals(), IkeSaProposalUtils::toPersistableBundle);
+        result.putPersistableBundle(SA_PROPOSALS_KEY, saProposalBundle);
+
+        result.putPersistableBundle(
+                LOCAL_ID_KEY,
+                IkeIdentificationUtils.toPersistableBundle(params.getLocalIdentification()));
+        result.putPersistableBundle(
+                REMOTE_ID_KEY,
+                IkeIdentificationUtils.toPersistableBundle(params.getRemoteIdentification()));
+
+        result.putPersistableBundle(
+                LOCAL_AUTH_KEY, AuthConfigUtils.toPersistableBundle(params.getLocalAuthConfig()));
+        result.putPersistableBundle(
+                REMOTE_AUTH_KEY, AuthConfigUtils.toPersistableBundle(params.getRemoteAuthConfig()));
+
+        final List<ConfigRequest> reqList = new ArrayList<>();
+        for (IkeConfigRequest req : params.getConfigurationRequests()) {
+            reqList.add(new ConfigRequest(req));
+        }
+        final PersistableBundle configReqListBundle =
+                PersistableBundleUtils.fromList(reqList, ConfigRequest::toPersistableBundle);
+        result.putPersistableBundle(CONFIG_REQUESTS_KEY, configReqListBundle);
+
+        result.putIntArray(RETRANS_TIMEOUTS_KEY, params.getRetransmissionTimeoutsMillis());
+        result.putInt(HARD_LIFETIME_SEC_KEY, params.getHardLifetimeSeconds());
+        result.putInt(SOFT_LIFETIME_SEC_KEY, params.getSoftLifetimeSeconds());
+        result.putInt(DPD_DELAY_SEC_KEY, params.getDpdDelaySeconds());
+        result.putInt(NATT_KEEPALIVE_DELAY_SEC_KEY, params.getNattKeepAliveDelaySeconds());
+        result.putInt(IP_VERSION_KEY, params.getIpVersion());
+        result.putInt(ENCAP_TYPE_KEY, params.getEncapType());
+
+        final List<Integer> enabledIkeOptions = new ArrayList<>();
+
+        try {
+            // TODO: b/328844044: Ideally this code should gate the behavior by checking the
+            // com.android.ipsec.flags.enabled_ike_options_api flag but that flag is not accessible
+            // right now. We should either update the code when the flag is accessible or remove the
+            // legacy behavior after VIC SDK finalization
+            enabledIkeOptions.addAll(params.getIkeOptions());
+        } catch (Exception e) {
+            // getIkeOptions throws. It means the API is not available
+            enabledIkeOptions.clear();
+            for (int option : IKE_OPTIONS) {
+                if (isIkeOptionValid(option) && params.hasIkeOption(option)) {
+                    enabledIkeOptions.add(option);
+                }
+            }
+        }
+
+        final int[] optionArray = enabledIkeOptions.stream().mapToInt(i -> i).toArray();
+        result.putIntArray(IKE_OPTIONS_KEY, optionArray);
+
+        return result;
+    }
+
+    /** Constructs an IkeSessionParams by deserializing a PersistableBundle. */
+    @NonNull
+    public static IkeSessionParams fromPersistableBundle(@NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle is null");
+
+        final IkeSessionParams.Builder builder = new IkeSessionParams.Builder();
+
+        builder.setServerHostname(in.getString(SERVER_HOST_NAME_KEY));
+
+        PersistableBundle proposalBundle = in.getPersistableBundle(SA_PROPOSALS_KEY);
+        Objects.requireNonNull(in, "SA Proposals was null");
+        List<IkeSaProposal> saProposals =
+                PersistableBundleUtils.toList(
+                        proposalBundle, IkeSaProposalUtils::fromPersistableBundle);
+        for (IkeSaProposal proposal : saProposals) {
+            builder.addSaProposal(proposal);
+        }
+
+        builder.setLocalIdentification(
+                IkeIdentificationUtils.fromPersistableBundle(
+                        in.getPersistableBundle(LOCAL_ID_KEY)));
+        builder.setRemoteIdentification(
+                IkeIdentificationUtils.fromPersistableBundle(
+                        in.getPersistableBundle(REMOTE_ID_KEY)));
+
+        AuthConfigUtils.setBuilderByReadingPersistableBundle(
+                in.getPersistableBundle(LOCAL_AUTH_KEY),
+                in.getPersistableBundle(REMOTE_AUTH_KEY),
+                builder);
+
+        builder.setRetransmissionTimeoutsMillis(in.getIntArray(RETRANS_TIMEOUTS_KEY));
+        builder.setLifetimeSeconds(
+                in.getInt(HARD_LIFETIME_SEC_KEY), in.getInt(SOFT_LIFETIME_SEC_KEY));
+        builder.setDpdDelaySeconds(in.getInt(DPD_DELAY_SEC_KEY));
+        builder.setNattKeepAliveDelaySeconds(in.getInt(NATT_KEEPALIVE_DELAY_SEC_KEY));
+        builder.setIpVersion(in.getInt(IP_VERSION_KEY));
+        builder.setEncapType(in.getInt(ENCAP_TYPE_KEY));
+
+        final PersistableBundle configReqListBundle = in.getPersistableBundle(CONFIG_REQUESTS_KEY);
+        Objects.requireNonNull(configReqListBundle, "Config request list was null");
+        final List<ConfigRequest> reqList =
+                PersistableBundleUtils.toList(configReqListBundle, ConfigRequest::new);
+        for (ConfigRequest req : reqList) {
+            switch (req.type) {
+                case ConfigRequest.IPV4_P_CSCF_ADDRESS:
+                    if (req.address == null) {
+                        builder.addPcscfServerRequest(AF_INET);
+                    } else {
+                        builder.addPcscfServerRequest(req.address);
+                    }
+                    break;
+                case ConfigRequest.IPV6_P_CSCF_ADDRESS:
+                    if (req.address == null) {
+                        builder.addPcscfServerRequest(AF_INET6);
+                    } else {
+                        builder.addPcscfServerRequest(req.address);
+                    }
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Unrecognized config request type: " + req.type);
+            }
+        }
+
+        // Clear IKE Options that are by default enabled
+        for (int option : IKE_OPTIONS) {
+            if (isIkeOptionValid(option)) {
+                builder.removeIkeOption(option);
+            }
+        }
+
+        final int[] optionArray = in.getIntArray(IKE_OPTIONS_KEY);
+        for (int option : optionArray) {
+            if (isIkeOptionValid(option)) {
+                builder.addIkeOption(option);
+            }
+        }
+
+        return builder.build();
+    }
+
+    private static final class AuthConfigUtils {
+        private static final int IKE_AUTH_METHOD_PSK = 1;
+        private static final int IKE_AUTH_METHOD_PUB_KEY_SIGNATURE = 2;
+        private static final int IKE_AUTH_METHOD_EAP = 3;
+
+        private static final String AUTH_METHOD_KEY = "AUTH_METHOD_KEY";
+
+        @NonNull
+        public static PersistableBundle toPersistableBundle(@NonNull IkeAuthConfig authConfig) {
+            if (authConfig instanceof IkeAuthPskConfig) {
+                IkeAuthPskConfig config = (IkeAuthPskConfig) authConfig;
+                return IkeAuthPskConfigUtils.toPersistableBundle(
+                        config, createPersistableBundle(IKE_AUTH_METHOD_PSK));
+            } else if (authConfig instanceof IkeAuthDigitalSignLocalConfig) {
+                IkeAuthDigitalSignLocalConfig config = (IkeAuthDigitalSignLocalConfig) authConfig;
+                return IkeAuthDigitalSignConfigUtils.toPersistableBundle(
+                        config, createPersistableBundle(IKE_AUTH_METHOD_PUB_KEY_SIGNATURE));
+            } else if (authConfig instanceof IkeAuthDigitalSignRemoteConfig) {
+                IkeAuthDigitalSignRemoteConfig config = (IkeAuthDigitalSignRemoteConfig) authConfig;
+                return IkeAuthDigitalSignConfigUtils.toPersistableBundle(
+                        config, createPersistableBundle(IKE_AUTH_METHOD_PUB_KEY_SIGNATURE));
+            } else if (authConfig instanceof IkeAuthEapConfig) {
+                IkeAuthEapConfig config = (IkeAuthEapConfig) authConfig;
+                return IkeAuthEapConfigUtils.toPersistableBundle(
+                        config, createPersistableBundle(IKE_AUTH_METHOD_EAP));
+            } else {
+                throw new IllegalStateException("Invalid IkeAuthConfig subclass");
+            }
+        }
+
+        private static PersistableBundle createPersistableBundle(int type) {
+            final PersistableBundle result = new PersistableBundle();
+            result.putInt(AUTH_METHOD_KEY, type);
+            return result;
+        }
+
+        public static void setBuilderByReadingPersistableBundle(
+                @NonNull PersistableBundle localAuthBundle,
+                @NonNull PersistableBundle remoteAuthBundle,
+                @NonNull IkeSessionParams.Builder builder) {
+            Objects.requireNonNull(localAuthBundle, "localAuthBundle was null");
+            Objects.requireNonNull(remoteAuthBundle, "remoteAuthBundle was null");
+
+            final int localMethodType = localAuthBundle.getInt(AUTH_METHOD_KEY);
+            final int remoteMethodType = remoteAuthBundle.getInt(AUTH_METHOD_KEY);
+            switch (localMethodType) {
+                case IKE_AUTH_METHOD_PSK:
+                    if (remoteMethodType != IKE_AUTH_METHOD_PSK) {
+                        throw new IllegalArgumentException(
+                                "Expect remote auth method to be PSK based, but was "
+                                        + remoteMethodType);
+                    }
+                    IkeAuthPskConfigUtils.setBuilderByReadingPersistableBundle(
+                            localAuthBundle, remoteAuthBundle, builder);
+                    return;
+                case IKE_AUTH_METHOD_PUB_KEY_SIGNATURE:
+                    if (remoteMethodType != IKE_AUTH_METHOD_PUB_KEY_SIGNATURE) {
+                        throw new IllegalArgumentException(
+                                "Expect remote auth method to be digital signature based, but was "
+                                        + remoteMethodType);
+                    }
+                    IkeAuthDigitalSignConfigUtils.setBuilderByReadingPersistableBundle(
+                            localAuthBundle, remoteAuthBundle, builder);
+                    return;
+                case IKE_AUTH_METHOD_EAP:
+                    if (remoteMethodType != IKE_AUTH_METHOD_PUB_KEY_SIGNATURE) {
+                        throw new IllegalArgumentException(
+                                "When using EAP for local authentication, expect remote auth"
+                                        + " method to be digital signature based, but was "
+                                        + remoteMethodType);
+                    }
+                    IkeAuthEapConfigUtils.setBuilderByReadingPersistableBundle(
+                            localAuthBundle, remoteAuthBundle, builder);
+                    return;
+                default:
+                    throw new IllegalArgumentException(
+                            "Invalid EAP method type " + localMethodType);
+            }
+        }
+    }
+
+    private static final class IkeAuthPskConfigUtils {
+        private static final String PSK_KEY = "PSK_KEY";
+
+        @NonNull
+        public static PersistableBundle toPersistableBundle(
+                @NonNull IkeAuthPskConfig config, @NonNull PersistableBundle result) {
+            result.putPersistableBundle(
+                    PSK_KEY, PersistableBundleUtils.fromByteArray(config.getPsk()));
+            return result;
+        }
+
+        public static void setBuilderByReadingPersistableBundle(
+                @NonNull PersistableBundle localAuthBundle,
+                @NonNull PersistableBundle remoteAuthBundle,
+                @NonNull IkeSessionParams.Builder builder) {
+            Objects.requireNonNull(localAuthBundle, "localAuthBundle was null");
+            Objects.requireNonNull(remoteAuthBundle, "remoteAuthBundle was null");
+
+            final PersistableBundle localPskBundle = localAuthBundle.getPersistableBundle(PSK_KEY);
+            final PersistableBundle remotePskBundle =
+                    remoteAuthBundle.getPersistableBundle(PSK_KEY);
+            Objects.requireNonNull(localAuthBundle, "Local PSK was null");
+            Objects.requireNonNull(remoteAuthBundle, "Remote PSK was null");
+
+            final byte[] localPsk = PersistableBundleUtils.toByteArray(localPskBundle);
+            final byte[] remotePsk = PersistableBundleUtils.toByteArray(remotePskBundle);
+            if (!Arrays.equals(localPsk, remotePsk)) {
+                throw new IllegalArgumentException("Local PSK and remote PSK are different");
+            }
+            builder.setAuthPsk(localPsk);
+        }
+    }
+
+    private static class IkeAuthDigitalSignConfigUtils {
+        private static final String END_CERT_KEY = "END_CERT_KEY";
+        private static final String INTERMEDIATE_CERTS_KEY = "INTERMEDIATE_CERTS_KEY";
+        private static final String PRIVATE_KEY_KEY = "PRIVATE_KEY_KEY";
+        private static final String TRUST_CERT_KEY = "TRUST_CERT_KEY";
+
+        @NonNull
+        public static PersistableBundle toPersistableBundle(
+                @NonNull IkeAuthDigitalSignLocalConfig config, @NonNull PersistableBundle result) {
+            try {
+                result.putPersistableBundle(
+                        END_CERT_KEY,
+                        PersistableBundleUtils.fromByteArray(
+                                config.getClientEndCertificate().getEncoded()));
+
+                final List<X509Certificate> certList = config.getIntermediateCertificates();
+                final List<byte[]> encodedCertList = new ArrayList<>(certList.size());
+                for (X509Certificate cert : certList) {
+                    encodedCertList.add(cert.getEncoded());
+                }
+
+                final PersistableBundle certsBundle =
+                        PersistableBundleUtils.fromList(
+                                encodedCertList, PersistableBundleUtils::fromByteArray);
+                result.putPersistableBundle(INTERMEDIATE_CERTS_KEY, certsBundle);
+            } catch (CertificateEncodingException e) {
+                throw new IllegalArgumentException("Fail to encode certificate");
+            }
+
+            // TODO: b/170670506 Consider putting PrivateKey in Android KeyStore
+            result.putPersistableBundle(
+                    PRIVATE_KEY_KEY,
+                    PersistableBundleUtils.fromByteArray(config.getPrivateKey().getEncoded()));
+            return result;
+        }
+
+        @NonNull
+        public static PersistableBundle toPersistableBundle(
+                @NonNull IkeAuthDigitalSignRemoteConfig config, @NonNull PersistableBundle result) {
+            try {
+                X509Certificate caCert = config.getRemoteCaCert();
+                if (caCert != null) {
+                    result.putPersistableBundle(
+                            TRUST_CERT_KEY,
+                            PersistableBundleUtils.fromByteArray(caCert.getEncoded()));
+                }
+            } catch (CertificateEncodingException e) {
+                throw new IllegalArgumentException("Fail to encode the certificate");
+            }
+
+            return result;
+        }
+
+        public static void setBuilderByReadingPersistableBundle(
+                @NonNull PersistableBundle localAuthBundle,
+                @NonNull PersistableBundle remoteAuthBundle,
+                @NonNull IkeSessionParams.Builder builder) {
+            Objects.requireNonNull(localAuthBundle, "localAuthBundle was null");
+            Objects.requireNonNull(remoteAuthBundle, "remoteAuthBundle was null");
+
+            // Deserialize localAuth
+            final PersistableBundle endCertBundle =
+                    localAuthBundle.getPersistableBundle(END_CERT_KEY);
+            Objects.requireNonNull(endCertBundle, "End cert was null");
+            final byte[] encodedCert = PersistableBundleUtils.toByteArray(endCertBundle);
+            final X509Certificate endCert = CertUtils.certificateFromByteArray(encodedCert);
+
+            final PersistableBundle certsBundle =
+                    localAuthBundle.getPersistableBundle(INTERMEDIATE_CERTS_KEY);
+            Objects.requireNonNull(certsBundle, "Intermediate certs was null");
+            final List<byte[]> encodedCertList =
+                    PersistableBundleUtils.toList(certsBundle, PersistableBundleUtils::toByteArray);
+            final List<X509Certificate> certList = new ArrayList<>(encodedCertList.size());
+            for (byte[] encoded : encodedCertList) {
+                certList.add(CertUtils.certificateFromByteArray(encoded));
+            }
+
+            final PersistableBundle privateKeyBundle =
+                    localAuthBundle.getPersistableBundle(PRIVATE_KEY_KEY);
+            Objects.requireNonNull(privateKeyBundle, "PrivateKey bundle was null");
+            final PrivateKey privateKey =
+                    CertUtils.privateKeyFromByteArray(
+                            PersistableBundleUtils.toByteArray(privateKeyBundle));
+
+            // Deserialize remoteAuth
+            final PersistableBundle trustCertBundle =
+                    remoteAuthBundle.getPersistableBundle(TRUST_CERT_KEY);
+
+            X509Certificate caCert = null;
+            if (trustCertBundle != null) {
+                final byte[] encodedCaCert = PersistableBundleUtils.toByteArray(trustCertBundle);
+                caCert = CertUtils.certificateFromByteArray(encodedCaCert);
+            }
+
+            builder.setAuthDigitalSignature(caCert, endCert, certList, privateKey);
+        }
+    }
+
+    private static final class IkeAuthEapConfigUtils {
+        private static final String EAP_CONFIG_KEY = "EAP_CONFIG_KEY";
+
+        @NonNull
+        public static PersistableBundle toPersistableBundle(
+                @NonNull IkeAuthEapConfig config, @NonNull PersistableBundle result) {
+            result.putPersistableBundle(
+                    EAP_CONFIG_KEY,
+                    EapSessionConfigUtils.toPersistableBundle(config.getEapConfig()));
+            return result;
+        }
+
+        public static void setBuilderByReadingPersistableBundle(
+                @NonNull PersistableBundle localAuthBundle,
+                @NonNull PersistableBundle remoteAuthBundle,
+                @NonNull IkeSessionParams.Builder builder) {
+            // Deserialize localAuth
+            final PersistableBundle eapBundle =
+                    localAuthBundle.getPersistableBundle(EAP_CONFIG_KEY);
+            Objects.requireNonNull(eapBundle, "EAP Config was null");
+            final EapSessionConfig eapConfig =
+                    EapSessionConfigUtils.fromPersistableBundle(eapBundle);
+
+            // Deserialize remoteAuth
+            final PersistableBundle trustCertBundle =
+                    remoteAuthBundle.getPersistableBundle(
+                            IkeAuthDigitalSignConfigUtils.TRUST_CERT_KEY);
+
+            X509Certificate serverCaCert = null;
+            if (trustCertBundle != null) {
+                final byte[] encodedCaCert = PersistableBundleUtils.toByteArray(trustCertBundle);
+                serverCaCert = CertUtils.certificateFromByteArray(encodedCaCert);
+            }
+            builder.setAuthEap(serverCaCert, eapConfig);
+        }
+    }
+
+    private static final class ConfigRequest {
+        private static final int IPV4_P_CSCF_ADDRESS = 1;
+        private static final int IPV6_P_CSCF_ADDRESS = 2;
+
+        private static final String TYPE_KEY = "type";
+        private static final String ADDRESS_KEY = "address";
+
+        public final int type;
+
+        // Null when it is an empty request
+        @Nullable public final InetAddress address;
+
+        ConfigRequest(IkeConfigRequest config) {
+            if (config instanceof ConfigRequestIpv4PcscfServer) {
+                type = IPV4_P_CSCF_ADDRESS;
+                address = ((ConfigRequestIpv4PcscfServer) config).getAddress();
+            } else if (config instanceof ConfigRequestIpv6PcscfServer) {
+                type = IPV6_P_CSCF_ADDRESS;
+                address = ((ConfigRequestIpv6PcscfServer) config).getAddress();
+            } else {
+                throw new IllegalStateException("Unknown TunnelModeChildConfigRequest");
+            }
+        }
+
+        ConfigRequest(PersistableBundle in) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+
+            type = in.getInt(TYPE_KEY);
+
+            String addressStr = in.getString(ADDRESS_KEY);
+            if (addressStr == null) {
+                address = null;
+            } else {
+                address = InetAddresses.parseNumericAddress(addressStr);
+            }
+        }
+
+        @NonNull
+        public PersistableBundle toPersistableBundle() {
+            final PersistableBundle result = new PersistableBundle();
+
+            result.putInt(TYPE_KEY, type);
+            if (address != null) {
+                result.putString(ADDRESS_KEY, address.getHostAddress());
+            }
+
+            return result;
+        }
+    }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java
similarity index 100%
rename from core/java/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/IkeTrafficSelectorUtils.java
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
new file mode 100644
index 0000000..469966a
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/SaProposalUtilsBase.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import android.annotation.NonNull;
+import android.net.ipsec.ike.SaProposal;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.os.PersistableBundle;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Abstract utility class to convert SaProposal to/from PersistableBundle.
+ *
+ * @hide
+ */
+abstract class SaProposalUtilsBase {
+    static final String ENCRYPT_ALGO_KEY = "ENCRYPT_ALGO_KEY";
+    static final String INTEGRITY_ALGO_KEY = "INTEGRITY_ALGO_KEY";
+    static final String DH_GROUP_KEY = "DH_GROUP_KEY";
+
+    static class EncryptionAlgoKeyLenPair {
+        private static final String ALGO_KEY = "ALGO_KEY";
+        private static final String KEY_LEN_KEY = "KEY_LEN_KEY";
+
+        public final int encryptionAlgo;
+        public final int keyLen;
+
+        EncryptionAlgoKeyLenPair(int encryptionAlgo, int keyLen) {
+            this.encryptionAlgo = encryptionAlgo;
+            this.keyLen = keyLen;
+        }
+
+        EncryptionAlgoKeyLenPair(PersistableBundle in) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+
+            this.encryptionAlgo = in.getInt(ALGO_KEY);
+            this.keyLen = in.getInt(KEY_LEN_KEY);
+        }
+
+        public PersistableBundle toPersistableBundle() {
+            final PersistableBundle result = new PersistableBundle();
+
+            result.putInt(ALGO_KEY, encryptionAlgo);
+            result.putInt(KEY_LEN_KEY, keyLen);
+
+            return result;
+        }
+    }
+
+    /**
+     * Serializes common info of a SaProposal to a PersistableBundle.
+     *
+     * @hide
+     */
+    @NonNull
+    static PersistableBundle toPersistableBundle(SaProposal proposal) {
+        final PersistableBundle result = new PersistableBundle();
+
+        final List<EncryptionAlgoKeyLenPair> encryptAlgoKeyLenPairs = new ArrayList<>();
+        for (Pair<Integer, Integer> pair : proposal.getEncryptionAlgorithms()) {
+            encryptAlgoKeyLenPairs.add(new EncryptionAlgoKeyLenPair(pair.first, pair.second));
+        }
+        final PersistableBundle encryptionBundle =
+                PersistableBundleUtils.fromList(
+                        encryptAlgoKeyLenPairs, EncryptionAlgoKeyLenPair::toPersistableBundle);
+        result.putPersistableBundle(ENCRYPT_ALGO_KEY, encryptionBundle);
+
+        final int[] integrityAlgoIdArray =
+                proposal.getIntegrityAlgorithms().stream().mapToInt(i -> i).toArray();
+        result.putIntArray(INTEGRITY_ALGO_KEY, integrityAlgoIdArray);
+
+        final int[] dhGroupArray = proposal.getDhGroups().stream().mapToInt(i -> i).toArray();
+        result.putIntArray(DH_GROUP_KEY, dhGroupArray);
+
+        return result;
+    }
+}
diff --git a/core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
similarity index 100%
rename from core/java/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
rename to packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelConnectionParamsUtils.java
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java
new file mode 100644
index 0000000..3f4ba34
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/persistablebundleutils/TunnelModeChildSessionParamsUtils.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.persistablebundleutils;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Address;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DhcpServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DnsServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Netmask;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6Address;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.os.PersistableBundle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides utility methods to convert TunnelModeChildSessionParams to/from PersistableBundle.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = Visibility.PRIVATE)
+public final class TunnelModeChildSessionParamsUtils {
+    private static final String TAG = TunnelModeChildSessionParamsUtils.class.getSimpleName();
+
+    private static final String INBOUND_TS_KEY = "INBOUND_TS_KEY";
+    private static final String OUTBOUND_TS_KEY = "OUTBOUND_TS_KEY";
+    private static final String SA_PROPOSALS_KEY = "SA_PROPOSALS_KEY";
+    private static final String HARD_LIFETIME_SEC_KEY = "HARD_LIFETIME_SEC_KEY";
+    private static final String SOFT_LIFETIME_SEC_KEY = "SOFT_LIFETIME_SEC_KEY";
+    private static final String CONFIG_REQUESTS_KEY = "CONFIG_REQUESTS_KEY";
+
+    private static class ConfigRequest {
+        private static final int TYPE_IPV4_ADDRESS = 1;
+        private static final int TYPE_IPV6_ADDRESS = 2;
+        private static final int TYPE_IPV4_DNS = 3;
+        private static final int TYPE_IPV6_DNS = 4;
+        private static final int TYPE_IPV4_DHCP = 5;
+        private static final int TYPE_IPV4_NETMASK = 6;
+
+        private static final String TYPE_KEY = "type";
+        private static final String VALUE_KEY = "address";
+        private static final String IP6_PREFIX_LEN = "ip6PrefixLen";
+
+        private static final int PREFIX_LEN_UNUSED = -1;
+
+        public final int type;
+        public final int ip6PrefixLen;
+
+        // Null when it is an empty request
+        @Nullable public final InetAddress address;
+
+        ConfigRequest(TunnelModeChildConfigRequest config) {
+            int prefixLen = PREFIX_LEN_UNUSED;
+
+            if (config instanceof ConfigRequestIpv4Address) {
+                type = TYPE_IPV4_ADDRESS;
+                address = ((ConfigRequestIpv4Address) config).getAddress();
+            } else if (config instanceof ConfigRequestIpv6Address) {
+                type = TYPE_IPV6_ADDRESS;
+                address = ((ConfigRequestIpv6Address) config).getAddress();
+                if (address != null) {
+                    prefixLen = ((ConfigRequestIpv6Address) config).getPrefixLength();
+                }
+            } else if (config instanceof ConfigRequestIpv4DnsServer) {
+                type = TYPE_IPV4_DNS;
+                address = null;
+            } else if (config instanceof ConfigRequestIpv6DnsServer) {
+                type = TYPE_IPV6_DNS;
+                address = null;
+            } else if (config instanceof ConfigRequestIpv4DhcpServer) {
+                type = TYPE_IPV4_DHCP;
+                address = null;
+            } else if (config instanceof ConfigRequestIpv4Netmask) {
+                type = TYPE_IPV4_NETMASK;
+                address = null;
+            } else {
+                throw new IllegalStateException("Unknown TunnelModeChildConfigRequest");
+            }
+
+            ip6PrefixLen = prefixLen;
+        }
+
+        ConfigRequest(PersistableBundle in) {
+            Objects.requireNonNull(in, "PersistableBundle was null");
+
+            type = in.getInt(TYPE_KEY);
+            ip6PrefixLen = in.getInt(IP6_PREFIX_LEN);
+
+            String addressStr = in.getString(VALUE_KEY);
+            if (addressStr == null) {
+                address = null;
+            } else {
+                address = InetAddresses.parseNumericAddress(addressStr);
+            }
+        }
+
+        @NonNull
+        public PersistableBundle toPersistableBundle() {
+            final PersistableBundle result = new PersistableBundle();
+
+            result.putInt(TYPE_KEY, type);
+            result.putInt(IP6_PREFIX_LEN, ip6PrefixLen);
+
+            if (address != null) {
+                result.putString(VALUE_KEY, address.getHostAddress());
+            }
+
+            return result;
+        }
+    }
+
+    /** Serializes a TunnelModeChildSessionParams to a PersistableBundle. */
+    @NonNull
+    public static PersistableBundle toPersistableBundle(
+            @NonNull TunnelModeChildSessionParams params) {
+        final PersistableBundle result = new PersistableBundle();
+
+        final PersistableBundle saProposalBundle =
+                PersistableBundleUtils.fromList(
+                        params.getSaProposals(), ChildSaProposalUtils::toPersistableBundle);
+        result.putPersistableBundle(SA_PROPOSALS_KEY, saProposalBundle);
+
+        final PersistableBundle inTsBundle =
+                PersistableBundleUtils.fromList(
+                        params.getInboundTrafficSelectors(),
+                        IkeTrafficSelectorUtils::toPersistableBundle);
+        result.putPersistableBundle(INBOUND_TS_KEY, inTsBundle);
+
+        final PersistableBundle outTsBundle =
+                PersistableBundleUtils.fromList(
+                        params.getOutboundTrafficSelectors(),
+                        IkeTrafficSelectorUtils::toPersistableBundle);
+        result.putPersistableBundle(OUTBOUND_TS_KEY, outTsBundle);
+
+        result.putInt(HARD_LIFETIME_SEC_KEY, params.getHardLifetimeSeconds());
+        result.putInt(SOFT_LIFETIME_SEC_KEY, params.getSoftLifetimeSeconds());
+
+        final List<ConfigRequest> reqList = new ArrayList<>();
+        for (TunnelModeChildConfigRequest req : params.getConfigurationRequests()) {
+            reqList.add(new ConfigRequest(req));
+        }
+        final PersistableBundle configReqListBundle =
+                PersistableBundleUtils.fromList(reqList, ConfigRequest::toPersistableBundle);
+        result.putPersistableBundle(CONFIG_REQUESTS_KEY, configReqListBundle);
+
+        return result;
+    }
+
+    private static List<IkeTrafficSelector> getTsFromPersistableBundle(
+            PersistableBundle in, String key) {
+        PersistableBundle tsBundle = in.getPersistableBundle(key);
+        Objects.requireNonNull(tsBundle, "Value for key " + key + " was null");
+        return PersistableBundleUtils.toList(
+                tsBundle, IkeTrafficSelectorUtils::fromPersistableBundle);
+    }
+
+    /** Constructs a TunnelModeChildSessionParams by deserializing a PersistableBundle. */
+    @NonNull
+    public static TunnelModeChildSessionParams fromPersistableBundle(
+            @NonNull PersistableBundle in) {
+        Objects.requireNonNull(in, "PersistableBundle was null");
+
+        final TunnelModeChildSessionParams.Builder builder =
+                new TunnelModeChildSessionParams.Builder();
+
+        final PersistableBundle proposalBundle = in.getPersistableBundle(SA_PROPOSALS_KEY);
+        Objects.requireNonNull(proposalBundle, "SA proposal was null");
+        final List<ChildSaProposal> proposals =
+                PersistableBundleUtils.toList(
+                        proposalBundle, ChildSaProposalUtils::fromPersistableBundle);
+        for (ChildSaProposal p : proposals) {
+            builder.addSaProposal(p);
+        }
+
+        for (IkeTrafficSelector ts : getTsFromPersistableBundle(in, INBOUND_TS_KEY)) {
+            builder.addInboundTrafficSelectors(ts);
+        }
+
+        for (IkeTrafficSelector ts : getTsFromPersistableBundle(in, OUTBOUND_TS_KEY)) {
+            builder.addOutboundTrafficSelectors(ts);
+        }
+
+        builder.setLifetimeSeconds(
+                in.getInt(HARD_LIFETIME_SEC_KEY), in.getInt(SOFT_LIFETIME_SEC_KEY));
+        final PersistableBundle configReqListBundle = in.getPersistableBundle(CONFIG_REQUESTS_KEY);
+        Objects.requireNonNull(configReqListBundle, "Config request list was null");
+        final List<ConfigRequest> reqList =
+                PersistableBundleUtils.toList(configReqListBundle, ConfigRequest::new);
+
+        boolean hasIpv4AddressReq = false;
+        boolean hasIpv4NetmaskReq = false;
+        for (ConfigRequest req : reqList) {
+            switch (req.type) {
+                case ConfigRequest.TYPE_IPV4_ADDRESS:
+                    hasIpv4AddressReq = true;
+                    if (req.address == null) {
+                        builder.addInternalAddressRequest(AF_INET);
+                    } else {
+                        builder.addInternalAddressRequest((Inet4Address) req.address);
+                    }
+                    break;
+                case ConfigRequest.TYPE_IPV6_ADDRESS:
+                    if (req.address == null) {
+                        builder.addInternalAddressRequest(AF_INET6);
+                    } else {
+                        builder.addInternalAddressRequest(
+                                (Inet6Address) req.address, req.ip6PrefixLen);
+                    }
+                    break;
+                case ConfigRequest.TYPE_IPV4_NETMASK:
+                    // Do not need to set netmask because it will be automatically set by the
+                    // builder when an IPv4 internal address request is set.
+                    hasIpv4NetmaskReq = true;
+                    break;
+                case ConfigRequest.TYPE_IPV4_DNS:
+                    if (req.address != null) {
+                        Log.w(TAG, "Requesting a specific IPv4 DNS server is unsupported");
+                    }
+                    builder.addInternalDnsServerRequest(AF_INET);
+                    break;
+                case ConfigRequest.TYPE_IPV6_DNS:
+                    if (req.address != null) {
+                        Log.w(TAG, "Requesting a specific IPv6 DNS server is unsupported");
+                    }
+                    builder.addInternalDnsServerRequest(AF_INET6);
+                    break;
+                case ConfigRequest.TYPE_IPV4_DHCP:
+                    if (req.address != null) {
+                        Log.w(TAG, "Requesting a specific IPv4 DHCP server is unsupported");
+                    }
+                    builder.addInternalDhcpServerRequest(AF_INET);
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Unrecognized config request type: " + req.type);
+            }
+        }
+
+        if (hasIpv4AddressReq != hasIpv4NetmaskReq) {
+            Log.w(
+                    TAG,
+                    String.format(
+                            "Expect IPv4 address request and IPv4 netmask request either both"
+                                + " exist or both absent, but found hasIpv4AddressReq exists? %b,"
+                                + " hasIpv4AddressReq exists? %b, ",
+                            hasIpv4AddressReq, hasIpv4NetmaskReq));
+        }
+
+        return builder.build();
+    }
+}
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/util/LogUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/util/LogUtils.java
new file mode 100644
index 0000000..742aa76
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/util/LogUtils.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.util;
+
+import android.annotation.Nullable;
+import android.os.ParcelUuid;
+
+import com.android.net.module.util.HexDump;
+
+/** @hide */
+public class LogUtils {
+    /**
+     * Returns the hash of the subscription group in hexadecimal format.
+     *
+     * @return the hexadecimal encoded string if uuid was non-null, else {@code null}
+     */
+    @Nullable
+    public static String getHashedSubscriptionGroup(@Nullable ParcelUuid uuid) {
+        if (uuid == null) {
+            return null;
+        }
+
+        return HexDump.toHexString(uuid.hashCode());
+    }
+}
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/util/MtuUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/util/MtuUtils.java
new file mode 100644
index 0000000..c3123bc
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/util/MtuUtils.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.util;
+
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_3DES;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CTR;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_CHACHA20_POLY1305;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_CMAC_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_NONE;
+
+import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
+
+import static java.lang.Math.max;
+import static java.util.Collections.unmodifiableMap;
+
+import android.annotation.NonNull;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.util.ArrayMap;
+import android.util.Pair;
+import android.util.Slog;
+
+import java.util.List;
+import java.util.Map;
+
+/** @hide */
+public class MtuUtils {
+    private static final String TAG = MtuUtils.class.getSimpleName();
+    /**
+     * Max ESP overhead possible
+     *
+     * <p>60 (Outer IPv4 + options) + 8 (UDP encap) + 4 (SPI) + 4 (Seq) + 2 (Pad Length + Next
+     * Header). Note: Payload data, Pad Length and Next Header will need to be padded to be multiple
+     * of the block size of a cipher, and at the same time be aligned on a 4-byte boundary.
+     */
+    private static final int GENERIC_ESP_OVERHEAD_MAX_V4 = 78;
+
+    /**
+     * Max ESP overhead possible
+     *
+     * <p>40 (Outer IPv6) + 4 (SPI) + 4 (Seq) + 2 (Pad Length + Next Header). Note: Payload data,
+     * Pad Length and Next Header will need to be padded to be multiple of the block size of a
+     * cipher, and at the same time be aligned on a 4-byte boundary.
+     */
+    private static final int GENERIC_ESP_OVERHEAD_MAX_V6 = 50;
+
+    /** Maximum overheads of authentication algorithms, keyed on IANA-defined constants */
+    private static final Map<Integer, Integer> AUTH_ALGORITHM_OVERHEAD;
+
+    static {
+        final Map<Integer, Integer> map = new ArrayMap<>();
+        map.put(INTEGRITY_ALGORITHM_NONE, 0);
+        map.put(INTEGRITY_ALGORITHM_HMAC_SHA1_96, 12);
+        map.put(INTEGRITY_ALGORITHM_AES_XCBC_96, 12);
+        map.put(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128, 32);
+        map.put(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192, 48);
+        map.put(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256, 64);
+        map.put(INTEGRITY_ALGORITHM_AES_CMAC_96, 12);
+
+        AUTH_ALGORITHM_OVERHEAD = unmodifiableMap(map);
+    }
+
+    /** Maximum overheads of encryption algorithms, keyed on IANA-defined constants */
+    private static final Map<Integer, Integer> CRYPT_ALGORITHM_OVERHEAD;
+
+    static {
+        final Map<Integer, Integer> map = new ArrayMap<>();
+        map.put(ENCRYPTION_ALGORITHM_3DES, 15); // 8 (IV) + 7 (Max pad)
+        map.put(ENCRYPTION_ALGORITHM_AES_CBC, 31); // 16 (IV) + 15 (Max pad)
+        map.put(ENCRYPTION_ALGORITHM_AES_CTR, 11); // 8 (IV) + 3 (Max pad)
+
+        CRYPT_ALGORITHM_OVERHEAD = unmodifiableMap(map);
+    }
+
+    /** Maximum overheads of combined mode algorithms, keyed on IANA-defined constants */
+    private static final Map<Integer, Integer> AUTHCRYPT_ALGORITHM_OVERHEAD;
+
+    static {
+        final Map<Integer, Integer> map = new ArrayMap<>();
+        map.put(ENCRYPTION_ALGORITHM_AES_GCM_8, 19); // 8 (IV) + 3 (Max pad) + 8 (ICV)
+        map.put(ENCRYPTION_ALGORITHM_AES_GCM_12, 23); // 8 (IV) + 3 (Max pad) + 12 (ICV)
+        map.put(ENCRYPTION_ALGORITHM_AES_GCM_16, 27); // 8 (IV) + 3 (Max pad) + 16 (ICV)
+        map.put(ENCRYPTION_ALGORITHM_CHACHA20_POLY1305, 27); // 8 (IV) + 3 (Max pad) + 16 (ICV)
+
+        AUTHCRYPT_ALGORITHM_OVERHEAD = unmodifiableMap(map);
+    }
+
+    /**
+     * Calculates the MTU of the inner interface based on the parameters provided
+     *
+     * <p>The MTU of the inner interface will be the minimum of the following:
+     *
+     * <ul>
+     *   <li>The MTU of the outer interface, minus the greatest ESP overhead (based on proposed
+     *       algorithms).
+     *   <li>The maximum MTU as provided in the arguments.
+     * </ul>
+     */
+    public static int getMtu(
+            @NonNull List<ChildSaProposal> childProposals,
+            int maxMtu,
+            int underlyingMtu,
+            boolean isIpv4) {
+        if (underlyingMtu <= 0) {
+            return IPV6_MIN_MTU;
+        }
+
+        int maxAuthOverhead = 0;
+        int maxCryptOverhead = 0;
+        int maxAuthCryptOverhead = 0;
+
+        for (ChildSaProposal proposal : childProposals) {
+            for (Pair<Integer, Integer> encryptionAlgoPair : proposal.getEncryptionAlgorithms()) {
+                final int algo = encryptionAlgoPair.first;
+
+                if (AUTHCRYPT_ALGORITHM_OVERHEAD.containsKey(algo)) {
+                    maxAuthCryptOverhead =
+                            max(maxAuthCryptOverhead, AUTHCRYPT_ALGORITHM_OVERHEAD.get(algo));
+                    continue;
+                } else if (CRYPT_ALGORITHM_OVERHEAD.containsKey(algo)) {
+                    maxCryptOverhead = max(maxCryptOverhead, CRYPT_ALGORITHM_OVERHEAD.get(algo));
+                    continue;
+                }
+
+                Slog.wtf(TAG, "Unknown encryption algorithm requested: " + algo);
+                return IPV6_MIN_MTU;
+            }
+
+            for (int algo : proposal.getIntegrityAlgorithms()) {
+                if (AUTH_ALGORITHM_OVERHEAD.containsKey(algo)) {
+                    maxAuthOverhead = max(maxAuthOverhead, AUTH_ALGORITHM_OVERHEAD.get(algo));
+                    continue;
+                }
+
+                Slog.wtf(TAG, "Unknown integrity algorithm requested: " + algo);
+                return IPV6_MIN_MTU;
+            }
+        }
+
+        final int genericEspOverheadMax =
+                isIpv4 ? GENERIC_ESP_OVERHEAD_MAX_V4 : GENERIC_ESP_OVERHEAD_MAX_V6;
+
+        // Return minimum of maxMtu, and the adjusted MTUs based on algorithms.
+        final int combinedModeMtu = underlyingMtu - maxAuthCryptOverhead - genericEspOverheadMax;
+        final int normalModeMtu =
+                underlyingMtu - maxCryptOverhead - maxAuthOverhead - genericEspOverheadMax;
+        return Math.min(Math.min(maxMtu, combinedModeMtu), normalModeMtu);
+    }
+}
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/util/OneWayBoolean.java b/packages/Vcn/framework-b/src/android/net/vcn/util/OneWayBoolean.java
new file mode 100644
index 0000000..a7ef67b
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/util/OneWayBoolean.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.util;
+
+/**
+ * OneWayBoolean is an abstraction for a boolean that MUST only ever be flipped from false to true
+ *
+ * <p>This class allows the providing of a guarantee that a flag will never be flipped back after
+ * being set.
+ *
+ * @hide
+ */
+public class OneWayBoolean {
+    private boolean mValue = false;
+
+    /** Get boolean value. */
+    public boolean getValue() {
+        return mValue;
+    }
+
+    /** Sets the value to true. */
+    public void setTrue() {
+        mValue = true;
+    }
+}
diff --git a/packages/Vcn/framework-b/src/android/net/vcn/util/PersistableBundleUtils.java b/packages/Vcn/framework-b/src/android/net/vcn/util/PersistableBundleUtils.java
new file mode 100644
index 0000000..8a687d8
--- /dev/null
+++ b/packages/Vcn/framework-b/src/android/net/vcn/util/PersistableBundleUtils.java
@@ -0,0 +1,582 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ParcelUuid;
+import android.os.PersistableBundle;
+
+import com.android.net.module.util.HexDump;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.TreeSet;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/** @hide */
+public class PersistableBundleUtils {
+    private static final String LIST_KEY_FORMAT = "LIST_ITEM_%d";
+    private static final String COLLECTION_SIZE_KEY = "COLLECTION_LENGTH";
+    private static final String MAP_KEY_FORMAT = "MAP_KEY_%d";
+    private static final String MAP_VALUE_FORMAT = "MAP_VALUE_%d";
+
+    private static final String PARCEL_UUID_KEY = "PARCEL_UUID";
+    private static final String BYTE_ARRAY_KEY = "BYTE_ARRAY_KEY";
+    private static final String INTEGER_KEY = "INTEGER_KEY";
+    private static final String STRING_KEY = "STRING_KEY";
+
+    /**
+     * Functional interface to convert an object of the specified type to a PersistableBundle.
+     *
+     * @param <T> the type of the source object
+     */
+    public interface Serializer<T> {
+        /**
+         * Converts this object to a PersistableBundle.
+         *
+         * @return the PersistableBundle representation of this object
+         */
+        PersistableBundle toPersistableBundle(T obj);
+    }
+
+    /**
+     * Functional interface used to create an object of the specified type from a PersistableBundle.
+     *
+     * @param <T> the type of the resultant object
+     */
+    public interface Deserializer<T> {
+        /**
+         * Creates an instance of specified type from a PersistableBundle representation.
+         *
+         * @param in the PersistableBundle representation
+         * @return an instance of the specified type
+         */
+        T fromPersistableBundle(PersistableBundle in);
+    }
+
+    /** Serializer to convert an integer to a PersistableBundle. */
+    public static final Serializer<Integer> INTEGER_SERIALIZER =
+            (i) -> {
+                final PersistableBundle result = new PersistableBundle();
+                result.putInt(INTEGER_KEY, i);
+                return result;
+            };
+
+    /** Deserializer to convert a PersistableBundle to an integer. */
+    public static final Deserializer<Integer> INTEGER_DESERIALIZER =
+            (bundle) -> {
+                Objects.requireNonNull(bundle, "PersistableBundle is null");
+                return bundle.getInt(INTEGER_KEY);
+            };
+
+    /** Serializer to convert s String to a PersistableBundle. */
+    public static final Serializer<String> STRING_SERIALIZER =
+            (i) -> {
+                final PersistableBundle result = new PersistableBundle();
+                result.putString(STRING_KEY, i);
+                return result;
+            };
+
+    /** Deserializer to convert a PersistableBundle to a String. */
+    public static final Deserializer<String> STRING_DESERIALIZER =
+            (bundle) -> {
+                Objects.requireNonNull(bundle, "PersistableBundle is null");
+                return bundle.getString(STRING_KEY);
+            };
+
+    /**
+     * Converts a ParcelUuid to a PersistableBundle.
+     *
+     * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
+     * PersistableBundle object.
+     *
+     * @param uuid a ParcelUuid instance to persist
+     * @return the PersistableBundle instance
+     */
+    public static PersistableBundle fromParcelUuid(ParcelUuid uuid) {
+        final PersistableBundle result = new PersistableBundle();
+
+        result.putString(PARCEL_UUID_KEY, uuid.toString());
+
+        return result;
+    }
+
+    /**
+     * Converts from a PersistableBundle to a ParcelUuid.
+     *
+     * @param bundle the PersistableBundle containing the ParcelUuid
+     * @return the ParcelUuid instance
+     */
+    public static ParcelUuid toParcelUuid(PersistableBundle bundle) {
+        return ParcelUuid.fromString(bundle.getString(PARCEL_UUID_KEY));
+    }
+
+    /**
+     * Converts from a list of Persistable objects to a single PersistableBundle.
+     *
+     * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
+     * PersistableBundle object.
+     *
+     * @param <T> the type of the objects to convert to the PersistableBundle
+     * @param in the list of objects to be serialized into a PersistableBundle
+     * @param serializer an implementation of the {@link Serializer} functional interface that
+     *     converts an object of type T to a PersistableBundle
+     */
+    @NonNull
+    public static <T> PersistableBundle fromList(
+            @NonNull List<T> in, @NonNull Serializer<T> serializer) {
+        final PersistableBundle result = new PersistableBundle();
+
+        result.putInt(COLLECTION_SIZE_KEY, in.size());
+        for (int i = 0; i < in.size(); i++) {
+            final String key = String.format(LIST_KEY_FORMAT, i);
+            result.putPersistableBundle(key, serializer.toPersistableBundle(in.get(i)));
+        }
+        return result;
+    }
+
+    /**
+     * Converts from a PersistableBundle to a list of objects.
+     *
+     * @param <T> the type of the objects to convert from a PersistableBundle
+     * @param in the PersistableBundle containing the persisted list
+     * @param deserializer an implementation of the {@link Deserializer} functional interface that
+     *     builds the relevant type of objects.
+     */
+    @NonNull
+    public static <T> List<T> toList(
+            @NonNull PersistableBundle in, @NonNull Deserializer<T> deserializer) {
+        final int listLength = in.getInt(COLLECTION_SIZE_KEY);
+        final ArrayList<T> result = new ArrayList<>(listLength);
+
+        for (int i = 0; i < listLength; i++) {
+            final String key = String.format(LIST_KEY_FORMAT, i);
+            final PersistableBundle item = in.getPersistableBundle(key);
+
+            result.add(deserializer.fromPersistableBundle(item));
+        }
+        return result;
+    }
+
+    // TODO: b/170513329 Delete #fromByteArray and #toByteArray once BaseBundle#putByteArray and
+    // BaseBundle#getByteArray are exposed.
+
+    /**
+     * Converts a byte array to a PersistableBundle.
+     *
+     * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
+     * PersistableBundle object.
+     *
+     * @param array a byte array instance to persist
+     * @return the PersistableBundle instance
+     */
+    public static PersistableBundle fromByteArray(byte[] array) {
+        final PersistableBundle result = new PersistableBundle();
+
+        result.putString(BYTE_ARRAY_KEY, HexDump.toHexString(array));
+
+        return result;
+    }
+
+    /**
+     * Converts from a PersistableBundle to a byte array.
+     *
+     * @param bundle the PersistableBundle containing the byte array
+     * @return the byte array instance
+     */
+    public static byte[] toByteArray(PersistableBundle bundle) {
+        Objects.requireNonNull(bundle, "PersistableBundle is null");
+
+        String hex = bundle.getString(BYTE_ARRAY_KEY);
+        if (hex == null || hex.length() % 2 != 0) {
+            throw new IllegalArgumentException("PersistableBundle contains invalid byte array");
+        }
+
+        return HexDump.hexStringToByteArray(hex);
+    }
+
+    /**
+     * Converts from a Map of Persistable objects to a single PersistableBundle.
+     *
+     * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
+     * PersistableBundle object.
+     *
+     * @param <K> the type of the map-key to convert to the PersistableBundle
+     * @param <V> the type of the map-value to convert to the PersistableBundle
+     * @param in the Map of objects implementing the {@link Persistable} interface
+     * @param keySerializer an implementation of the {@link Serializer} functional interface that
+     *     converts a map-key of type T to a PersistableBundle
+     * @param valueSerializer an implementation of the {@link Serializer} functional interface that
+     *     converts a map-value of type E to a PersistableBundle
+     */
+    @NonNull
+    public static <K, V> PersistableBundle fromMap(
+            @NonNull Map<K, V> in,
+            @NonNull Serializer<K> keySerializer,
+            @NonNull Serializer<V> valueSerializer) {
+        final PersistableBundle result = new PersistableBundle();
+
+        result.putInt(COLLECTION_SIZE_KEY, in.size());
+        int i = 0;
+        for (Entry<K, V> entry : in.entrySet()) {
+            final String keyKey = String.format(MAP_KEY_FORMAT, i);
+            final String valueKey = String.format(MAP_VALUE_FORMAT, i);
+            result.putPersistableBundle(keyKey, keySerializer.toPersistableBundle(entry.getKey()));
+            result.putPersistableBundle(
+                    valueKey, valueSerializer.toPersistableBundle(entry.getValue()));
+
+            i++;
+        }
+
+        return result;
+    }
+
+    /**
+     * Converts from a PersistableBundle to a Map of objects.
+     *
+     * <p>In an attempt to preserve ordering, the returned map will be a LinkedHashMap. However, the
+     * guarantees on the ordering can only ever be as strong as the map that was serialized in
+     * {@link fromMap()}. If the initial map that was serialized had no ordering guarantees, the
+     * deserialized map similarly may be of a non-deterministic order.
+     *
+     * @param <K> the type of the map-key to convert from a PersistableBundle
+     * @param <V> the type of the map-value to convert from a PersistableBundle
+     * @param in the PersistableBundle containing the persisted Map
+     * @param keyDeserializer an implementation of the {@link Deserializer} functional interface
+     *     that builds the relevant type of map-key.
+     * @param valueDeserializer an implementation of the {@link Deserializer} functional interface
+     *     that builds the relevant type of map-value.
+     * @return An instance of the parsed map as a LinkedHashMap (in an attempt to preserve
+     *     ordering).
+     */
+    @NonNull
+    public static <K, V> LinkedHashMap<K, V> toMap(
+            @NonNull PersistableBundle in,
+            @NonNull Deserializer<K> keyDeserializer,
+            @NonNull Deserializer<V> valueDeserializer) {
+        final int mapSize = in.getInt(COLLECTION_SIZE_KEY);
+        final LinkedHashMap<K, V> result = new LinkedHashMap<>(mapSize);
+
+        for (int i = 0; i < mapSize; i++) {
+            final String keyKey = String.format(MAP_KEY_FORMAT, i);
+            final String valueKey = String.format(MAP_VALUE_FORMAT, i);
+            final PersistableBundle keyBundle = in.getPersistableBundle(keyKey);
+            final PersistableBundle valueBundle = in.getPersistableBundle(valueKey);
+
+            final K key = keyDeserializer.fromPersistableBundle(keyBundle);
+            final V value = valueDeserializer.fromPersistableBundle(valueBundle);
+            result.put(key, value);
+        }
+        return result;
+    }
+
+    /**
+     * Converts a PersistableBundle into a disk-stable byte array format
+     *
+     * @param bundle the PersistableBundle to be converted to a disk-stable format
+     * @return the byte array representation of the PersistableBundle
+     */
+    @Nullable
+    public static byte[] toDiskStableBytes(@NonNull PersistableBundle bundle) throws IOException {
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        bundle.writeToStream(outputStream);
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * Converts from a disk-stable byte array format to a PersistableBundle
+     *
+     * @param bytes the disk-stable byte array
+     * @return the PersistableBundle parsed from this byte array.
+     */
+    public static PersistableBundle fromDiskStableBytes(@NonNull byte[] bytes) throws IOException {
+        final ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+        return PersistableBundle.readFromStream(inputStream);
+    }
+
+    /**
+     * Ensures safe reading and writing of {@link PersistableBundle}s to and from disk.
+     *
+     * <p>This class will enforce exclusion between reads and writes using the standard semantics of
+     * a ReadWriteLock. Specifically, concurrent readers ARE allowed, but reads/writes from/to the
+     * file are mutually exclusive. In other words, for an unbounded number n, the acceptable states
+     * are n readers, OR 1 writer (but not both).
+     */
+    public static class LockingReadWriteHelper {
+        private final ReadWriteLock mDiskLock = new ReentrantReadWriteLock();
+        private final String mPath;
+
+        public LockingReadWriteHelper(@NonNull String path) {
+            mPath = Objects.requireNonNull(path, "fileName was null");
+        }
+
+        /**
+         * Reads the {@link PersistableBundle} from the disk.
+         *
+         * @return the PersistableBundle, if the file existed, or null otherwise
+         */
+        @Nullable
+        public PersistableBundle readFromDisk() throws IOException {
+            try {
+                mDiskLock.readLock().lock();
+                final File file = new File(mPath);
+                if (!file.exists()) {
+                    return null;
+                }
+
+                try (FileInputStream fis = new FileInputStream(file)) {
+                    return PersistableBundle.readFromStream(fis);
+                }
+            } finally {
+                mDiskLock.readLock().unlock();
+            }
+        }
+
+        /**
+         * Writes a {@link PersistableBundle} to disk.
+         *
+         * @param bundle the {@link PersistableBundle} to write to disk
+         */
+        public void writeToDisk(@NonNull PersistableBundle bundle) throws IOException {
+            Objects.requireNonNull(bundle, "bundle was null");
+
+            try {
+                mDiskLock.writeLock().lock();
+                final File file = new File(mPath);
+                if (!file.exists()) {
+                    file.getParentFile().mkdirs();
+                }
+
+                try (FileOutputStream fos = new FileOutputStream(file)) {
+                    bundle.writeToStream(fos);
+                }
+            } finally {
+                mDiskLock.writeLock().unlock();
+            }
+        }
+    }
+
+    /**
+     * Returns a copy of the persistable bundle with only the specified keys
+     *
+     * <p>This allows for holding minimized copies for memory-saving purposes.
+     */
+    @NonNull
+    public static PersistableBundle minimizeBundle(
+            @NonNull PersistableBundle bundle, String... keys) {
+        final PersistableBundle minimized = new PersistableBundle();
+
+        if (bundle == null) {
+            return minimized;
+        }
+
+        for (String key : keys) {
+            if (bundle.containsKey(key)) {
+                final Object value = bundle.get(key);
+                if (value == null) {
+                    continue;
+                }
+
+                if (value instanceof Boolean) {
+                    minimized.putBoolean(key, (Boolean) value);
+                } else if (value instanceof boolean[]) {
+                    minimized.putBooleanArray(key, (boolean[]) value);
+                } else if (value instanceof Double) {
+                    minimized.putDouble(key, (Double) value);
+                } else if (value instanceof double[]) {
+                    minimized.putDoubleArray(key, (double[]) value);
+                } else if (value instanceof Integer) {
+                    minimized.putInt(key, (Integer) value);
+                } else if (value instanceof int[]) {
+                    minimized.putIntArray(key, (int[]) value);
+                } else if (value instanceof Long) {
+                    minimized.putLong(key, (Long) value);
+                } else if (value instanceof long[]) {
+                    minimized.putLongArray(key, (long[]) value);
+                } else if (value instanceof String) {
+                    minimized.putString(key, (String) value);
+                } else if (value instanceof String[]) {
+                    minimized.putStringArray(key, (String[]) value);
+                } else if (value instanceof PersistableBundle) {
+                    minimized.putPersistableBundle(key, (PersistableBundle) value);
+                } else {
+                    continue;
+                }
+            }
+        }
+
+        return minimized;
+    }
+
+    /** Builds a stable hashcode */
+    public static int getHashCode(@Nullable PersistableBundle bundle) {
+        if (bundle == null) {
+            return -1;
+        }
+
+        int iterativeHashcode = 0;
+        TreeSet<String> treeSet = new TreeSet<>(bundle.keySet());
+        for (String key : treeSet) {
+            Object val = bundle.get(key);
+            if (val instanceof PersistableBundle) {
+                iterativeHashcode =
+                        Objects.hash(iterativeHashcode, key, getHashCode((PersistableBundle) val));
+            } else {
+                iterativeHashcode = Objects.hash(iterativeHashcode, key, val);
+            }
+        }
+
+        return iterativeHashcode;
+    }
+
+    /** Checks for persistable bundle equality */
+    public static boolean isEqual(
+            @Nullable PersistableBundle left, @Nullable PersistableBundle right) {
+        // Check for pointer equality & null equality
+        if (Objects.equals(left, right)) {
+            return true;
+        }
+
+        // If only one of the two is null, but not the other, not equal by definition.
+        if (Objects.isNull(left) != Objects.isNull(right)) {
+            return false;
+        }
+
+        if (!left.keySet().equals(right.keySet())) {
+            return false;
+        }
+
+        for (String key : left.keySet()) {
+            Object leftVal = left.get(key);
+            Object rightVal = right.get(key);
+
+            // Check for equality
+            if (Objects.equals(leftVal, rightVal)) {
+                continue;
+            } else if (Objects.isNull(leftVal) != Objects.isNull(rightVal)) {
+                // If only one of the two is null, but not the other, not equal by definition.
+                return false;
+            } else if (!Objects.equals(leftVal.getClass(), rightVal.getClass())) {
+                // If classes are different, not equal by definition.
+                return false;
+            }
+            if (leftVal instanceof PersistableBundle) {
+                if (!isEqual((PersistableBundle) leftVal, (PersistableBundle) rightVal)) {
+                    return false;
+                }
+            } else if (leftVal.getClass().isArray()) {
+                if (leftVal instanceof boolean[]) {
+                    if (!Arrays.equals((boolean[]) leftVal, (boolean[]) rightVal)) {
+                        return false;
+                    }
+                } else if (leftVal instanceof double[]) {
+                    if (!Arrays.equals((double[]) leftVal, (double[]) rightVal)) {
+                        return false;
+                    }
+                } else if (leftVal instanceof int[]) {
+                    if (!Arrays.equals((int[]) leftVal, (int[]) rightVal)) {
+                        return false;
+                    }
+                } else if (leftVal instanceof long[]) {
+                    if (!Arrays.equals((long[]) leftVal, (long[]) rightVal)) {
+                        return false;
+                    }
+                } else if (!Arrays.equals((Object[]) leftVal, (Object[]) rightVal)) {
+                    return false;
+                }
+            } else {
+                if (!Objects.equals(leftVal, rightVal)) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Wrapper class around PersistableBundles to allow equality comparisons
+     *
+     * <p>This class exposes the minimal getters to retrieve values.
+     */
+    public static class PersistableBundleWrapper {
+        @NonNull private final PersistableBundle mBundle;
+
+        public PersistableBundleWrapper(@NonNull PersistableBundle bundle) {
+            mBundle = Objects.requireNonNull(bundle, "Bundle was null");
+        }
+
+        /**
+         * Retrieves the integer associated with the provided key.
+         *
+         * @param key the string key to query
+         * @param defaultValue the value to return if key does not exist
+         * @return the int value, or the default
+         */
+        public int getInt(String key, int defaultValue) {
+            return mBundle.getInt(key, defaultValue);
+        }
+
+        /**
+         * Returns the value associated with the given key, or null if no mapping of the desired
+         * type exists for the given key or a null value is explicitly associated with the key.
+         *
+         * @param key a String, or null
+         * @param defaultValue the value to return if key does not exist
+         * @return an int[] value, or null
+         */
+        @Nullable
+        public int[] getIntArray(@Nullable String key, @Nullable int[] defaultValue) {
+            final int[] value = mBundle.getIntArray(key);
+            return value == null ? defaultValue : value;
+        }
+
+        @Override
+        public int hashCode() {
+            return getHashCode(mBundle);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof PersistableBundleWrapper)) {
+                return false;
+            }
+
+            final PersistableBundleWrapper other = (PersistableBundleWrapper) obj;
+
+            return isEqual(mBundle, other.mBundle);
+        }
+
+        @Override
+        public String toString() {
+            return mBundle.toString();
+        }
+    }
+}
diff --git a/packages/Vcn/service-b/Android.bp b/packages/Vcn/service-b/Android.bp
index a462297..c1a1ee7 100644
--- a/packages/Vcn/service-b/Android.bp
+++ b/packages/Vcn/service-b/Android.bp
@@ -19,6 +19,46 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+filegroup {
+    name: "vcn-location-sources",
+    srcs: select(release_flag("RELEASE_MOVE_VCN_TO_MAINLINE"), {
+        true: [
+            "vcn-location-flag/module/com/android/server/vcn/VcnLocation.java",
+        ],
+        default: [
+            "vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java",
+        ],
+    }),
+    visibility: ["//frameworks/base/services/core"],
+}
+
+// Do not static include this lib in VCN because these files exist in
+// both service-connectivity.jar and framework.jar
+// TODO: b/374174952 After VCN moves to Connectivity/ and the modularization is done
+// this lib can be removed and "service-connectivity-b-pre-jarjar" can include
+// "service-connectivity-pre-jarjar"
+java_library {
+    name: "connectivity-utils-service-vcn-internal",
+    sdk_version: "module_current",
+    min_sdk_version: "30",
+    srcs: [
+        ":framework-connectivity-shared-srcs",
+    ],
+    libs: [
+        "framework-annotations-lib",
+        "unsupportedappusage",
+    ],
+    visibility: [
+        "//visibility:private",
+    ],
+    apex_available: [
+        // TODO: b/374174952 Remove it when VCN modularization is released
+        "//apex_available:platform",
+
+        "com.android.tethering",
+    ],
+}
+
 java_library {
     name: "service-connectivity-b-pre-jarjar",
     sdk_version: "system_server_current",
@@ -29,8 +69,33 @@
         "src/**/*.java",
     ],
 
-    // TODO: b/375213246 Expose this library to Tethering module
+    libs: [
+        "android.net.ipsec.ike.stubs.module_lib",
+        "connectivity-utils-service-vcn-internal",
+        "framework-annotations-lib",
+        "framework-connectivity-pre-jarjar",
+        "framework-connectivity-t-pre-jarjar",
+        "framework-connectivity-b-pre-jarjar",
+        "framework-wifi.stubs.module_lib",
+        "keepanno-annotations",
+        "modules-utils-statemachine",
+        "unsupportedappusage",
+    ],
+
+    // TODO: b/374174952 Dynamically include these libs when VCN
+    // modularization is released
+    static_libs: [
+        "net-utils-service-vcn",
+        "modules-utils-handlerexecutor",
+    ],
+
     visibility: [
         "//frameworks/base/services",
     ],
+    apex_available: [
+        // TODO: b/374174952 Remove it when VCN modularization is released
+        "//apex_available:platform",
+
+        "com.android.tethering",
+    ],
 }
diff --git a/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java b/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java
new file mode 100644
index 0000000..02c8ce4
--- /dev/null
+++ b/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+
+/**
+ * Service initializer for VCN. This is called by system server to create a new instance of
+ * VcnManagementService.
+ */
+// This class is reflectively invoked from SystemServer and ConnectivityServiceInitializer.
+// Without this annotation, this class will be treated as unused class and be removed during build
+// time.
+@UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+public final class ConnectivityServiceInitializerB extends SystemService {
+    private static final String TAG = ConnectivityServiceInitializerB.class.getSimpleName();
+    private final VcnManagementService mVcnManagementService;
+
+    public ConnectivityServiceInitializerB(Context context) {
+        super(context);
+        mVcnManagementService = VcnManagementService.create(context);
+    }
+
+    @Override
+    public void onStart() {
+        if (mVcnManagementService != null) {
+            Log.i(TAG, "Registering " + Context.VCN_MANAGEMENT_SERVICE);
+            publishBinderService(
+                    Context.VCN_MANAGEMENT_SERVICE,
+                    mVcnManagementService,
+                    /* allowIsolated= */ false);
+        }
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (mVcnManagementService != null && phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+            Log.i(TAG, "Starting " + Context.VCN_MANAGEMENT_SERVICE);
+            mVcnManagementService.systemReady();
+        }
+    }
+}
diff --git a/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java b/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java
new file mode 100644
index 0000000..26db6a9
--- /dev/null
+++ b/packages/Vcn/service-b/src/com/android/server/VcnManagementService.java
@@ -0,0 +1,1547 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.Manifest.permission.DUMP;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnGatewayConnectionConfig.ALLOWED_CAPABILITIES;
+import static android.net.vcn.VcnManager.VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY;
+import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
+import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE;
+import static android.net.vcn.VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED;
+import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
+import static android.telephony.SubscriptionManager.isValidSubscriptionId;
+
+import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.vcn.IVcnManagementService;
+import android.net.vcn.IVcnStatusCallback;
+import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
+import android.net.vcn.VcnConfig;
+import android.net.vcn.VcnManager.VcnErrorCode;
+import android.net.vcn.VcnManager.VcnStatusCode;
+import android.net.vcn.VcnUnderlyingNetworkPolicy;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+import android.net.wifi.WifiInfo;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ParcelUuid;
+import android.os.PersistableBundle;
+import android.os.Process;
+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;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.net.module.util.BinderUtils;
+import com.android.net.module.util.HandlerUtils;
+import com.android.net.module.util.LocationPermissionChecker;
+import com.android.net.module.util.PermissionUtils;
+import com.android.server.vcn.TelephonySubscriptionTracker;
+import com.android.server.vcn.Vcn;
+import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.VcnNetworkProvider;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * VcnManagementService manages Virtual Carrier Network profiles and lifecycles.
+ *
+ * <pre>The internal structure of the VCN Management subsystem is as follows:
+ *
+ * +-------------------------+ 1:1                                +--------------------------------+
+ * |  VcnManagementService   | ------------ Creates ------------> |  TelephonySubscriptionManager  |
+ * |                         |                                    |                                |
+ * |   Manages configs and   |                                    | Tracks subscriptions, carrier  |
+ * | Vcn instance lifecycles | <--- Notifies of subscription & -- | privilege changes, caches maps |
+ * +-------------------------+      carrier privilege changes     +--------------------------------+
+ *      | 1:N          ^
+ *      |              |
+ *      |              +-------------------------------+
+ *      +---------------+                              |
+ *                      |                              |
+ *         Creates when config present,                |
+ *        subscription group active, and               |
+ *      providing app is carrier privileged     Notifies of safe
+ *                      |                      mode state changes
+ *                      v                              |
+ * +-----------------------------------------------------------------------+
+ * |                                  Vcn                                  |
+ * |                                                                       |
+ * |       Manages GatewayConnection lifecycles based on fulfillable       |
+ * |                NetworkRequest(s) and overall safe-mode                |
+ * +-----------------------------------------------------------------------+
+ *                      | 1:N                          ^
+ *              Creates to fulfill                     |
+ *           NetworkRequest(s), tears   Notifies of VcnGatewayConnection
+ *          down when no longer needed   teardown (e.g. Network reaped)
+ *                      |                 and safe-mode timer changes
+ *                      v                              |
+ * +-----------------------------------------------------------------------+
+ * |                          VcnGatewayConnection                         |
+ * |                                                                       |
+ * |       Manages a single (IKEv2) tunnel session and NetworkAgent,       |
+ * |  handles mobility events, (IPsec) Tunnel setup and safe-mode timers   |
+ * +-----------------------------------------------------------------------+
+ *                      | 1:1                          ^
+ *                      |                              |
+ *          Creates upon instantiation      Notifies of changes in
+ *                      |                 selected underlying network
+ *                      |                     or its properties
+ *                      v                              |
+ * +-----------------------------------------------------------------------+
+ * |                       UnderlyingNetworkController                     |
+ * |                                                                       |
+ * | Manages lifecycle of underlying physical networks, filing requests to |
+ * | bring them up, and releasing them as they become no longer necessary  |
+ * +-----------------------------------------------------------------------+
+ * </pre>
+ *
+ * @hide
+ */
+// TODO(b/180451994): ensure all incoming + outgoing calls have a cleared calling identity
+public class VcnManagementService extends IVcnManagementService.Stub {
+    @NonNull private static final String TAG = VcnManagementService.class.getSimpleName();
+    @NonNull private static final String CONTEXT_ATTRIBUTION_TAG = "VCN";
+
+    private static final long DUMP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(5);
+    private static final int LOCAL_LOG_LINE_COUNT = 512;
+
+    private static final Set<Integer> RESTRICTED_TRANSPORTS_DEFAULT =
+            Collections.singleton(TRANSPORT_WIFI);
+
+    // Public for use in all other VCN classes
+    @NonNull public static final LocalLog LOCAL_LOG = new LocalLog(LOCAL_LOG_LINE_COUNT);
+
+    public static final boolean VDBG = false; // STOPSHIP: if true
+
+    // The system path is copied from Environment.getDataSystemDirectory
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final String VCN_CONFIG_FILE =
+            new File(Environment.getDataDirectory(), "system/vcn/configs.xml").getPath();
+
+    // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
+
+    /* Binder context for this service */
+    @NonNull private final Context mContext;
+    @NonNull private final Dependencies mDeps;
+
+    @NonNull private final Looper mLooper;
+    @NonNull private final Handler mHandler;
+    @NonNull private final VcnNetworkProvider mNetworkProvider;
+    @NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb;
+    @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
+    @NonNull private final BroadcastReceiver mVcnBroadcastReceiver;
+
+    @NonNull
+    private final TrackingNetworkCallback mTrackingNetworkCallback = new TrackingNetworkCallback();
+
+    @GuardedBy("mLock")
+    @NonNull
+    private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>();
+
+    @GuardedBy("mLock")
+    @NonNull
+    private final Map<ParcelUuid, Vcn> mVcns = new ArrayMap<>();
+
+    @GuardedBy("mLock")
+    @NonNull
+    private TelephonySubscriptionSnapshot mLastSnapshot =
+            TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT;
+
+    @NonNull private final Object mLock = new Object();
+
+    @NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper;
+
+    @GuardedBy("mLock")
+    @NonNull
+    private final Map<IBinder, PolicyListenerBinderDeath> mRegisteredPolicyListeners =
+            new ArrayMap<>();
+
+    @GuardedBy("mLock")
+    @NonNull
+    private final Map<IBinder, VcnStatusCallbackInfo> mRegisteredStatusCallbacks = new ArrayMap<>();
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    VcnManagementService(@NonNull Context context, @NonNull Dependencies deps) {
+        mContext =
+                requireNonNull(context, "Missing context")
+                        .createAttributionContext(CONTEXT_ATTRIBUTION_TAG);
+        mDeps = requireNonNull(deps, "Missing dependencies");
+
+        mLooper = mDeps.getLooper();
+        mHandler = new Handler(mLooper);
+        mNetworkProvider = new VcnNetworkProvider(mContext, mLooper);
+        mTelephonySubscriptionTrackerCb = new VcnSubscriptionTrackerCallback();
+        mTelephonySubscriptionTracker = mDeps.newTelephonySubscriptionTracker(
+                mContext, mLooper, mTelephonySubscriptionTrackerCb);
+
+        mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE);
+
+        mVcnBroadcastReceiver = new VcnBroadcastReceiver();
+
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+        intentFilter.addDataScheme("package");
+        mContext.registerReceiver(
+                mVcnBroadcastReceiver, intentFilter, null /* broadcastPermission */, mHandler);
+
+        // Run on handler to ensure I/O does not block system server startup
+        mHandler.post(() -> {
+            PersistableBundle configBundle = null;
+            try {
+                configBundle = mConfigDiskRwHelper.readFromDisk();
+            } catch (IOException e1) {
+                logErr("Failed to read configs from disk; retrying", e1);
+
+                // Retry immediately. The IOException may have been transient.
+                try {
+                    configBundle = mConfigDiskRwHelper.readFromDisk();
+                } catch (IOException e2) {
+                    logWtf("Failed to read configs from disk", e2);
+                    return;
+                }
+            }
+
+            if (configBundle != null) {
+                final Map<ParcelUuid, VcnConfig> configs =
+                        PersistableBundleUtils.toMap(
+                                configBundle,
+                                PersistableBundleUtils::toParcelUuid,
+                                VcnConfig::new);
+
+                synchronized (mLock) {
+                    for (Entry<ParcelUuid, VcnConfig> entry : configs.entrySet()) {
+                        // Ensure no new configs are overwritten; a carrier app may have added a new
+                        // config.
+                        if (!mConfigs.containsKey(entry.getKey())) {
+                            mConfigs.put(entry.getKey(), entry.getValue());
+                        }
+                    }
+
+                    // Re-evaluate subscriptions, and start/stop VCNs. This starts with an empty
+                    // snapshot, and therefore safe even before telephony subscriptions are loaded.
+                    mTelephonySubscriptionTrackerCb.onNewSnapshot(mLastSnapshot);
+                }
+            }
+        });
+    }
+
+    // Package-visibility for SystemServer to create instances.
+    static VcnManagementService create(@NonNull Context context) {
+        return new VcnManagementService(context, new Dependencies());
+    }
+
+    /** External dependencies used by VcnManagementService, for injection in tests */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        private HandlerThread mHandlerThread;
+
+        /** Retrieves a looper for the VcnManagementService */
+        public Looper getLooper() {
+            if (mHandlerThread == null) {
+                synchronized (this) {
+                    if (mHandlerThread == null) {
+                        mHandlerThread = new HandlerThread(TAG);
+                        mHandlerThread.start();
+                    }
+                }
+            }
+            return mHandlerThread.getLooper();
+        }
+
+        /** Creates a new VcnInstance using the provided configuration */
+        public TelephonySubscriptionTracker newTelephonySubscriptionTracker(
+                @NonNull Context context,
+                @NonNull Looper looper,
+                @NonNull TelephonySubscriptionTrackerCallback callback) {
+            return new TelephonySubscriptionTracker(context, new Handler(looper), callback);
+        }
+
+        /**
+         * Retrieves the caller's UID
+         *
+         * <p>This call MUST be made before calling {@link Binder#clearCallingIdentity}, otherwise
+         * this will not work properly.
+         *
+         * @return
+         */
+        public int getBinderCallingUid() {
+            return Binder.getCallingUid();
+        }
+
+        /**
+         * Creates and returns a new {@link PersistableBundle.LockingReadWriteHelper}
+         *
+         * @param path the file path to read/write from/to.
+         * @return the {@link PersistableBundleUtils.LockingReadWriteHelper} instance
+         */
+        public PersistableBundleUtils.LockingReadWriteHelper
+                newPersistableBundleLockingReadWriteHelper(@NonNull String path) {
+            return new PersistableBundleUtils.LockingReadWriteHelper(path);
+        }
+
+        /** Creates a new VcnContext */
+        public VcnContext newVcnContext(
+                @NonNull Context context,
+                @NonNull Looper looper,
+                @NonNull VcnNetworkProvider vcnNetworkProvider,
+                boolean isInTestMode) {
+            return new VcnContext(context, looper, vcnNetworkProvider, isInTestMode);
+        }
+
+        /** Creates a new Vcn instance using the provided configuration */
+        public Vcn newVcn(
+                @NonNull VcnContext vcnContext,
+                @NonNull ParcelUuid subscriptionGroup,
+                @NonNull VcnConfig config,
+                @NonNull TelephonySubscriptionSnapshot snapshot,
+                @NonNull VcnCallback vcnCallback) {
+            return new Vcn(vcnContext, subscriptionGroup, config, snapshot, vcnCallback);
+        }
+
+        /** Gets the subId indicated by the given {@link WifiInfo}. */
+        public int getSubIdForWifiInfo(@NonNull WifiInfo wifiInfo) {
+            return wifiInfo.getSubscriptionId();
+        }
+
+        /** Creates a new LocationPermissionChecker for the provided Context. */
+        public LocationPermissionChecker newLocationPermissionChecker(@NonNull Context context) {
+            return new LocationPermissionChecker(context);
+        }
+
+        /** Gets transports that need to be marked as restricted by the VCN from CarrierConfig */
+        // TODO: b/262269892 This method was created to perform experiments before the relevant API
+        // was exposed. Now it is obsolete and should be removed.
+        @VisibleForTesting(visibility = Visibility.PRIVATE)
+        public Set<Integer> getRestrictedTransportsFromCarrierConfig(
+                ParcelUuid subGrp, TelephonySubscriptionSnapshot lastSnapshot) {
+            if (!Build.isDebuggable()) {
+                return RESTRICTED_TRANSPORTS_DEFAULT;
+            }
+
+            final PersistableBundleWrapper carrierConfig =
+                    lastSnapshot.getCarrierConfigForSubGrp(subGrp);
+            if (carrierConfig == null) {
+                return RESTRICTED_TRANSPORTS_DEFAULT;
+            }
+
+            final int[] defaultValue =
+                    RESTRICTED_TRANSPORTS_DEFAULT.stream().mapToInt(i -> i).toArray();
+            final int[] restrictedTransportsArray =
+                    carrierConfig.getIntArray(
+                            VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
+                            defaultValue);
+
+            // Convert to a boxed set
+            final Set<Integer> restrictedTransports = new ArraySet<>();
+            for (int transport : restrictedTransportsArray) {
+                restrictedTransports.add(transport);
+            }
+            return restrictedTransports;
+        }
+
+        /** Gets the transports that need to be marked as restricted by the VCN */
+        public Set<Integer> getRestrictedTransports(
+                ParcelUuid subGrp,
+                TelephonySubscriptionSnapshot lastSnapshot,
+                VcnConfig vcnConfig) {
+            final Set<Integer> restrictedTransports = new ArraySet<>();
+            restrictedTransports.addAll(vcnConfig.getRestrictedUnderlyingNetworkTransports());
+
+            // TODO: b/262269892 Remove the ability to configure restricted transports
+            // via CarrierConfig
+            restrictedTransports.addAll(
+                    getRestrictedTransportsFromCarrierConfig(subGrp, lastSnapshot));
+
+            return restrictedTransports;
+        }
+    }
+
+    /** Notifies the VcnManagementService that external dependencies can be set up. */
+    public void systemReady() {
+        mNetworkProvider.register();
+        mContext.getSystemService(ConnectivityManager.class)
+                .registerNetworkCallback(
+                        new NetworkRequest.Builder().clearCapabilities().build(),
+                        mTrackingNetworkCallback);
+        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) {
+            throw new IllegalStateException(
+                    "Calling identity was System Server. Was Binder calling identity cleared?");
+        }
+
+        final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+        final UserManager userManager = mContext.getSystemService(UserManager.class);
+
+        BinderUtils.withCleanCallingIdentity(
+                () -> {
+                    if (!Objects.equals(userManager.getMainUser(), userHandle)) {
+                        throw new SecurityException(
+                                "VcnManagementService can only be used by callers running as"
+                                        + " the main user");
+                    }
+                });
+    }
+
+    private void enforceCallingUserAndCarrierPrivilege(
+            ParcelUuid subscriptionGroup, String pkgName) {
+        // Only apps running in the primary (system) user are allowed to configure the VCN. This is
+        // in line with Telephony's behavior with regards to binding to a Carrier App provided
+        // CarrierConfigService.
+        enforcePrimaryUser();
+
+        // TODO (b/172619301): Check based on events propagated from CarrierPrivilegesTracker
+        final SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class);
+        final List<SubscriptionInfo> subscriptionInfos = new ArrayList<>();
+        BinderUtils.withCleanCallingIdentity(
+                () -> {
+                    List<SubscriptionInfo> subsInGroup =
+                            subMgr.getSubscriptionsInGroup(subscriptionGroup);
+                    if (subsInGroup == null) {
+                        logWtf("Received null from getSubscriptionsInGroup");
+                        subsInGroup = Collections.emptyList();
+                    }
+                    subscriptionInfos.addAll(subsInGroup);
+                });
+
+        for (SubscriptionInfo info : subscriptionInfos) {
+            final TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class)
+                    .createForSubscriptionId(info.getSubscriptionId());
+
+            // Check subscription is active first; much cheaper/faster check, and an app (currently)
+            // cannot be carrier privileged for inactive subscriptions.
+            final int simSlotIndex = info.getSimSlotIndex();
+            final boolean isValidSlotIndex =
+                    simSlotIndex >= 0 && simSlotIndex < telMgr.getActiveModemCount();
+            if (isValidSlotIndex
+                    && telMgr.checkCarrierPrivilegesForPackage(pkgName)
+                            == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+                // TODO (b/173717728): Allow configuration for inactive, but manageable
+                // subscriptions.
+                // TODO (b/173718661): Check for whole subscription groups at a time.
+                return;
+            }
+        }
+
+        throw new SecurityException(
+                "Carrier privilege required for subscription group to set VCN Config");
+    }
+
+    private void enforceManageTestNetworksForTestMode(@NonNull VcnConfig vcnConfig) {
+        if (vcnConfig.isTestModeProfile()) {
+            mContext.enforceCallingPermission(
+                    android.Manifest.permission.MANAGE_TEST_NETWORKS,
+                    "Test-mode require the MANAGE_TEST_NETWORKS permission");
+        }
+    }
+
+    private boolean isActiveSubGroup(
+            @NonNull ParcelUuid subGrp, @NonNull TelephonySubscriptionSnapshot snapshot) {
+        if (subGrp == null || snapshot == null) {
+            return false;
+        }
+
+        return Objects.equals(subGrp, snapshot.getActiveDataSubscriptionGroup());
+    }
+
+    private class VcnBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
+
+            switch (action) {
+                case Intent.ACTION_PACKAGE_ADDED: // Fallthrough
+                case Intent.ACTION_PACKAGE_REPLACED: // Fallthrough
+                case Intent.ACTION_PACKAGE_REMOVED:
+                    // Reevaluate subscriptions
+                    mTelephonySubscriptionTracker.handleSubscriptionsChanged();
+
+                    break;
+                case Intent.ACTION_PACKAGE_FULLY_REMOVED:
+                case Intent.ACTION_PACKAGE_DATA_CLEARED:
+                    final String pkgName = intent.getData().getSchemeSpecificPart();
+
+                    if (pkgName == null || pkgName.isEmpty()) {
+                        logWtf("Package name was empty or null for intent with action" + action);
+                        return;
+                    }
+
+                    // Clear configs for the packages that had data cleared, or removed.
+                    synchronized (mLock) {
+                        final List<ParcelUuid> toRemove = new ArrayList<>();
+                        for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
+                            if (pkgName.equals(entry.getValue().getProvisioningPackageName())) {
+                                toRemove.add(entry.getKey());
+                            }
+                        }
+
+                        for (ParcelUuid subGrp : toRemove) {
+                            stopAndClearVcnConfigInternalLocked(subGrp);
+                        }
+
+                        if (!toRemove.isEmpty()) {
+                            writeConfigsToDiskLocked();
+                        }
+                    }
+
+                    break;
+                default:
+                    Slog.wtf(TAG, "received unexpected intent: " + action);
+            }
+        }
+    }
+
+    private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback {
+        /**
+         * Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker}
+         *
+         * <p>Start any unstarted VCN instances
+         *
+         * @hide
+         */
+        public void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
+            // Startup VCN instances
+            synchronized (mLock) {
+                final TelephonySubscriptionSnapshot oldSnapshot = mLastSnapshot;
+                mLastSnapshot = snapshot;
+                logInfo("new snapshot: " + mLastSnapshot);
+
+                // Start any VCN instances as necessary
+                for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
+                    final ParcelUuid subGrp = entry.getKey();
+
+                    // TODO(b/193687515): Support multiple VCNs active at the same time
+                    if (snapshot.packageHasPermissionsForSubscriptionGroup(
+                                    subGrp, entry.getValue().getProvisioningPackageName())
+                            && isActiveSubGroup(subGrp, snapshot)) {
+                        if (!mVcns.containsKey(subGrp)) {
+                            startVcnLocked(subGrp, entry.getValue());
+                        }
+
+                        // Cancel any scheduled teardowns for active subscriptions
+                        mHandler.removeCallbacksAndMessages(mVcns.get(subGrp));
+                    }
+                }
+
+                boolean needNotifyAllPolicyListeners = false;
+                // Schedule teardown of any VCN instances that have lost carrier privileges (after a
+                // delay)
+                for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
+                    final ParcelUuid subGrp = entry.getKey();
+                    final VcnConfig config = mConfigs.get(subGrp);
+
+                    final boolean isActiveSubGrp = isActiveSubGroup(subGrp, snapshot);
+                    final boolean isValidActiveDataSubIdNotInVcnSubGrp =
+                            isValidSubscriptionId(snapshot.getActiveDataSubscriptionId())
+                                    && !isActiveSubGroup(subGrp, snapshot);
+
+                    // TODO(b/193687515): Support multiple VCNs active at the same time
+                    if (config == null
+                            || !snapshot.packageHasPermissionsForSubscriptionGroup(
+                                    subGrp, config.getProvisioningPackageName())
+                            || !isActiveSubGrp) {
+                        final ParcelUuid uuidToTeardown = subGrp;
+                        final Vcn instanceToTeardown = entry.getValue();
+
+                        // TODO(b/193687515): Support multiple VCNs active at the same time
+                        // If directly switching to a subscription not in the current group,
+                        // teardown immediately to prevent other subscription's network from being
+                        // outscored by the VCN. Otherwise, teardown after a delay to ensure that
+                        // SIM profile switches do not trigger the VCN to cycle.
+                        final long teardownDelayMs =
+                                isValidActiveDataSubIdNotInVcnSubGrp
+                                        ? 0
+                                        : CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS;
+                        mHandler.postDelayed(() -> {
+                            synchronized (mLock) {
+                                // Guard against case where this is run after a old instance was
+                                // torn down, and a new instance was started. Verify to ensure
+                                // correct instance is torn down. This could happen as a result of a
+                                // Carrier App manually removing/adding a VcnConfig.
+                                if (mVcns.get(uuidToTeardown) == instanceToTeardown) {
+                                    stopVcnLocked(uuidToTeardown);
+
+                                    // TODO(b/181789060): invoke asynchronously after Vcn notifies
+                                    // through VcnCallback
+                                    notifyAllPermissionedStatusCallbacksLocked(
+                                            uuidToTeardown, VCN_STATUS_CODE_INACTIVE);
+                                }
+                            }
+                        }, instanceToTeardown, teardownDelayMs);
+                    } else {
+                        // If this VCN's status has not changed, update it with the new snapshot
+                        entry.getValue().updateSubscriptionSnapshot(mLastSnapshot);
+                        needNotifyAllPolicyListeners |=
+                                !Objects.equals(
+                                        oldSnapshot.getCarrierConfigForSubGrp(subGrp),
+                                        mLastSnapshot.getCarrierConfigForSubGrp(subGrp));
+                    }
+                }
+
+                final Map<ParcelUuid, Set<Integer>> oldSubGrpMappings =
+                        getSubGroupToSubIdMappings(oldSnapshot);
+                final Map<ParcelUuid, Set<Integer>> currSubGrpMappings =
+                        getSubGroupToSubIdMappings(mLastSnapshot);
+                if (!currSubGrpMappings.equals(oldSubGrpMappings)) {
+                    garbageCollectAndWriteVcnConfigsLocked();
+                    needNotifyAllPolicyListeners = true;
+                }
+
+                if (needNotifyAllPolicyListeners) {
+                    notifyAllPolicyListenersLocked();
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private Map<ParcelUuid, Set<Integer>> getSubGroupToSubIdMappings(
+            @NonNull TelephonySubscriptionSnapshot snapshot) {
+        final Map<ParcelUuid, Set<Integer>> subGrpMappings = new ArrayMap<>();
+        for (ParcelUuid subGrp : mVcns.keySet()) {
+            subGrpMappings.put(subGrp, snapshot.getAllSubIdsInGroup(subGrp));
+        }
+        return subGrpMappings;
+    }
+
+    @GuardedBy("mLock")
+    private void stopVcnLocked(@NonNull ParcelUuid uuidToTeardown) {
+        logInfo("Stopping VCN config for subGrp: " + uuidToTeardown);
+
+        // Remove in 2 steps. Make sure teardownAsync is triggered before removing from the map.
+        final Vcn vcnToTeardown = mVcns.get(uuidToTeardown);
+        if (vcnToTeardown == null) {
+            return;
+        }
+
+        vcnToTeardown.teardownAsynchronously();
+        mVcns.remove(uuidToTeardown);
+
+        // Now that the VCN is removed, notify all registered listeners to refresh their
+        // UnderlyingNetworkPolicy.
+        notifyAllPolicyListenersLocked();
+    }
+
+    @GuardedBy("mLock")
+    private void notifyAllPolicyListenersLocked() {
+        for (final PolicyListenerBinderDeath policyListener : mRegisteredPolicyListeners.values()) {
+            BinderUtils.withCleanCallingIdentity(() -> {
+                try {
+                    policyListener.mListener.onPolicyChanged();
+                } catch (RemoteException e) {
+                    logDbg("VcnStatusCallback threw on VCN status change", e);
+                }
+            });
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void notifyAllPermissionedStatusCallbacksLocked(
+            @NonNull ParcelUuid subGroup, @VcnStatusCode int statusCode) {
+        for (final VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
+            if (isCallbackPermissioned(cbInfo, subGroup)) {
+                BinderUtils.withCleanCallingIdentity(() -> {
+                    try {
+                        cbInfo.mCallback.onVcnStatusChanged(statusCode);
+                    } catch (RemoteException e) {
+                        logDbg("VcnStatusCallback threw on VCN status change", e);
+                    }
+                });
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
+        logInfo("Starting VCN config for subGrp: " + subscriptionGroup);
+
+        // TODO(b/193687515): Support multiple VCNs active at the same time
+        if (!mVcns.isEmpty()) {
+            // Only one VCN supported at a time; teardown all others before starting new one
+            for (ParcelUuid uuidToTeardown : mVcns.keySet()) {
+                stopVcnLocked(uuidToTeardown);
+            }
+        }
+
+        final VcnCallbackImpl vcnCallback = new VcnCallbackImpl(subscriptionGroup);
+
+        final VcnContext vcnContext =
+                mDeps.newVcnContext(
+                        mContext, mLooper, mNetworkProvider, config.isTestModeProfile());
+        final Vcn newInstance =
+                mDeps.newVcn(vcnContext, subscriptionGroup, config, mLastSnapshot, vcnCallback);
+        mVcns.put(subscriptionGroup, newInstance);
+
+        // Now that a new VCN has started, notify all registered listeners to refresh their
+        // UnderlyingNetworkPolicy.
+        notifyAllPolicyListenersLocked();
+
+        // TODO(b/181789060): invoke asynchronously after Vcn notifies through VcnCallback
+        notifyAllPermissionedStatusCallbacksLocked(subscriptionGroup, VCN_STATUS_CODE_ACTIVE);
+    }
+
+    @GuardedBy("mLock")
+    private void startOrUpdateVcnLocked(
+            @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
+        logDbg("Starting or updating VCN config for subGrp: " + subscriptionGroup);
+
+        if (mVcns.containsKey(subscriptionGroup)) {
+            final Vcn vcn = mVcns.get(subscriptionGroup);
+            vcn.updateConfig(config);
+            notifyAllPolicyListenersLocked();
+        } else {
+            // TODO(b/193687515): Support multiple VCNs active at the same time
+            if (isActiveSubGroup(subscriptionGroup, mLastSnapshot)) {
+                startVcnLocked(subscriptionGroup, config);
+            }
+        }
+    }
+
+    /**
+     * Sets a VCN config for a given subscription group.
+     *
+     * <p>Implements the IVcnManagementService Binder interface.
+     */
+    @Override
+    public void setVcnConfig(
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull VcnConfig config,
+            @NonNull String opPkgName) {
+        requireNonNull(subscriptionGroup, "subscriptionGroup was null");
+        requireNonNull(config, "config was null");
+        requireNonNull(opPkgName, "opPkgName was null");
+        if (!config.getProvisioningPackageName().equals(opPkgName)) {
+            throw new IllegalArgumentException("Mismatched caller and VcnConfig creator");
+        }
+        logInfo("VCN config updated for subGrp: " + subscriptionGroup);
+
+        mContext.getSystemService(AppOpsManager.class)
+                .checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName());
+        enforceManageTestNetworksForTestMode(config);
+        enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName);
+
+        BinderUtils.withCleanCallingIdentity(() -> {
+            synchronized (mLock) {
+                mConfigs.put(subscriptionGroup, config);
+                startOrUpdateVcnLocked(subscriptionGroup, config);
+
+                writeConfigsToDiskLocked();
+            }
+        });
+    }
+
+    private void enforceCarrierPrivilegeOrProvisioningPackage(
+            @NonNull ParcelUuid subscriptionGroup, @NonNull String pkg) {
+        // Only apps running in the primary (system) user are allowed to configure the VCN. This is
+        // in line with Telephony's behavior with regards to binding to a Carrier App provided
+        // CarrierConfigService.
+        enforcePrimaryUser();
+
+        if (isProvisioningPackageForConfig(subscriptionGroup, pkg)) {
+            return;
+        }
+
+        // Must NOT be called from cleared binder identity, since this checks user calling identity
+        enforceCallingUserAndCarrierPrivilege(subscriptionGroup, pkg);
+    }
+
+    private boolean isProvisioningPackageForConfig(
+            @NonNull ParcelUuid subscriptionGroup, @NonNull String pkg) {
+        // Try-finally to return early if matching owned subscription found.
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                final VcnConfig config = mConfigs.get(subscriptionGroup);
+                if (config != null && pkg.equals(config.getProvisioningPackageName())) {
+                    return true;
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        return false;
+    }
+
+    /**
+     * Clears the VcnManagementService for a given subscription group.
+     *
+     * <p>Implements the IVcnManagementService Binder interface.
+     */
+    @Override
+    public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull String opPkgName) {
+        requireNonNull(subscriptionGroup, "subscriptionGroup was null");
+        requireNonNull(opPkgName, "opPkgName was null");
+        logInfo("VCN config cleared for subGrp: " + subscriptionGroup);
+
+        mContext.getSystemService(AppOpsManager.class)
+                .checkPackage(mDeps.getBinderCallingUid(), opPkgName);
+        enforceCarrierPrivilegeOrProvisioningPackage(subscriptionGroup, opPkgName);
+
+        BinderUtils.withCleanCallingIdentity(() -> {
+            synchronized (mLock) {
+                stopAndClearVcnConfigInternalLocked(subscriptionGroup);
+                writeConfigsToDiskLocked();
+            }
+        });
+    }
+
+    private void stopAndClearVcnConfigInternalLocked(@NonNull ParcelUuid subscriptionGroup) {
+        mConfigs.remove(subscriptionGroup);
+        final boolean vcnExists = mVcns.containsKey(subscriptionGroup);
+
+        stopVcnLocked(subscriptionGroup);
+
+        if (vcnExists) {
+            // TODO(b/181789060): invoke asynchronously after Vcn notifies through
+            // VcnCallback
+            notifyAllPermissionedStatusCallbacksLocked(
+                    subscriptionGroup, VCN_STATUS_CODE_NOT_CONFIGURED);
+        }
+    }
+
+    private void garbageCollectAndWriteVcnConfigsLocked() {
+        final SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class);
+        final Set<ParcelUuid> subGroups = mLastSnapshot.getAllSubscriptionGroups();
+
+        boolean shouldWrite = false;
+
+        final Iterator<ParcelUuid> configsIterator = mConfigs.keySet().iterator();
+        while (configsIterator.hasNext()) {
+            final ParcelUuid subGrp = configsIterator.next();
+
+            if (!subGroups.contains(subGrp)) {
+                // Trim subGrps with no more subscriptions; must have moved to another subGrp
+                logDbg("Garbage collect VcnConfig for group=" + subGrp);
+                configsIterator.remove();
+                shouldWrite = true;
+            }
+        }
+
+        if (shouldWrite) {
+            writeConfigsToDiskLocked();
+        }
+    }
+
+    /**
+     * Retrieves the list of subscription groups with configured VcnConfigs
+     *
+     * <p>Limited to subscription groups for which the caller had configured.
+     *
+     * <p>Implements the IVcnManagementService Binder interface.
+     */
+    @Override
+    @NonNull
+    public List<ParcelUuid> getConfiguredSubscriptionGroups(@NonNull String opPkgName) {
+        requireNonNull(opPkgName, "opPkgName was null");
+
+        mContext.getSystemService(AppOpsManager.class)
+                .checkPackage(mDeps.getBinderCallingUid(), opPkgName);
+        enforcePrimaryUser();
+
+        final List<ParcelUuid> result = new ArrayList<>();
+        synchronized (mLock) {
+            for (ParcelUuid subGrp : mConfigs.keySet()) {
+                if (mLastSnapshot.packageHasPermissionsForSubscriptionGroup(subGrp, opPkgName)
+                        || isProvisioningPackageForConfig(subGrp, opPkgName)) {
+                    result.add(subGrp);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    @GuardedBy("mLock")
+    private void writeConfigsToDiskLocked() {
+        try {
+            PersistableBundle bundle =
+                    PersistableBundleUtils.fromMap(
+                            mConfigs,
+                            PersistableBundleUtils::fromParcelUuid,
+                            VcnConfig::toPersistableBundle);
+            mConfigDiskRwHelper.writeToDisk(bundle);
+        } catch (IOException e) {
+            logErr("Failed to save configs to disk", e);
+            throw new ServiceSpecificException(0, "Failed to save configs");
+        }
+    }
+
+    /** Get current configuration list for testing purposes */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    Map<ParcelUuid, VcnConfig> getConfigs() {
+        synchronized (mLock) {
+            return Collections.unmodifiableMap(mConfigs);
+        }
+    }
+
+    /** Get current VCNs for testing purposes */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public Map<ParcelUuid, Vcn> getAllVcns() {
+        synchronized (mLock) {
+            return Collections.unmodifiableMap(mVcns);
+        }
+    }
+
+    /** Get current VcnStatusCallbacks for testing purposes. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public Map<IBinder, VcnStatusCallbackInfo> getAllStatusCallbacks() {
+        synchronized (mLock) {
+            return Collections.unmodifiableMap(mRegisteredStatusCallbacks);
+        }
+    }
+
+    /** Binder death recipient used to remove a registered policy listener. */
+    private class PolicyListenerBinderDeath implements Binder.DeathRecipient {
+        @NonNull private final IVcnUnderlyingNetworkPolicyListener mListener;
+
+        PolicyListenerBinderDeath(@NonNull IVcnUnderlyingNetworkPolicyListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            Log.e(TAG, "app died without removing VcnUnderlyingNetworkPolicyListener");
+            removeVcnUnderlyingNetworkPolicyListener(mListener);
+        }
+    }
+
+    /** Adds the provided listener for receiving VcnUnderlyingNetworkPolicy updates. */
+    @Override
+    public void addVcnUnderlyingNetworkPolicyListener(
+            @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
+        requireNonNull(listener, "listener was null");
+
+        PermissionUtils.enforceAnyPermissionOf(
+                mContext,
+                android.Manifest.permission.NETWORK_FACTORY,
+                android.Manifest.permission.MANAGE_TEST_NETWORKS);
+
+        BinderUtils.withCleanCallingIdentity(() -> {
+            PolicyListenerBinderDeath listenerBinderDeath = new PolicyListenerBinderDeath(listener);
+
+            synchronized (mLock) {
+                mRegisteredPolicyListeners.put(listener.asBinder(), listenerBinderDeath);
+
+                try {
+                    listener.asBinder().linkToDeath(listenerBinderDeath, 0 /* flags */);
+                } catch (RemoteException e) {
+                    // Remote binder already died - cleanup registered Listener
+                    listenerBinderDeath.binderDied();
+                }
+            }
+        });
+    }
+
+    /** Removes the provided listener from receiving VcnUnderlyingNetworkPolicy updates. */
+    @Override
+    public void removeVcnUnderlyingNetworkPolicyListener(
+            @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
+        requireNonNull(listener, "listener was null");
+
+        PermissionUtils.enforceAnyPermissionOf(
+                mContext,
+                android.Manifest.permission.NETWORK_FACTORY,
+                android.Manifest.permission.MANAGE_TEST_NETWORKS);
+
+        BinderUtils.withCleanCallingIdentity(() -> {
+            synchronized (mLock) {
+                PolicyListenerBinderDeath listenerBinderDeath =
+                        mRegisteredPolicyListeners.remove(listener.asBinder());
+
+                if (listenerBinderDeath != null) {
+                    listener.asBinder().unlinkToDeath(listenerBinderDeath, 0 /* flags */);
+                }
+            }
+        });
+    }
+
+    private ParcelUuid getSubGroupForNetworkCapabilities(
+            @NonNull NetworkCapabilities networkCapabilities) {
+        ParcelUuid subGrp = null;
+        final TelephonySubscriptionSnapshot snapshot;
+
+        // Always access mLastSnapshot under lock. Technically this can be treated as a volatile
+        // but for consistency and safety, always access under lock.
+        synchronized (mLock) {
+            snapshot = mLastSnapshot;
+        }
+
+        // If multiple subscription IDs exist, they MUST all point to the same subscription
+        // group. Otherwise undefined behavior may occur.
+        for (int subId : networkCapabilities.getSubscriptionIds()) {
+            // Verify that all subscriptions point to the same group
+            if (subGrp != null && !subGrp.equals(snapshot.getGroupForSubId(subId))) {
+                logWtf("Got multiple subscription groups for a single network");
+            }
+
+            subGrp = snapshot.getGroupForSubId(subId);
+        }
+
+        return subGrp;
+    }
+
+    /**
+     * Gets the UnderlyingNetworkPolicy as determined by the provided NetworkCapabilities and
+     * LinkProperties.
+     */
+    @NonNull
+    @Override
+    public VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(
+            @NonNull NetworkCapabilities networkCapabilities,
+            @NonNull LinkProperties linkProperties) {
+        requireNonNull(networkCapabilities, "networkCapabilities was null");
+        requireNonNull(linkProperties, "linkProperties was null");
+
+        PermissionUtils.enforceAnyPermissionOf(
+                mContext,
+                android.Manifest.permission.NETWORK_FACTORY,
+                android.Manifest.permission.MANAGE_TEST_NETWORKS);
+
+        final boolean isUsingManageTestNetworks =
+                mContext.checkCallingOrSelfPermission(android.Manifest.permission.NETWORK_FACTORY)
+                        != PackageManager.PERMISSION_GRANTED;
+
+        if (isUsingManageTestNetworks && !networkCapabilities.hasTransport(TRANSPORT_TEST)) {
+            throw new IllegalStateException(
+                    "NetworkCapabilities must be for Test Network if using permission"
+                            + " MANAGE_TEST_NETWORKS");
+        }
+
+        return BinderUtils.withCleanCallingIdentity(() -> {
+            // Defensive copy in case this call is in-process and the given NetworkCapabilities
+            // mutates
+            final NetworkCapabilities ncCopy = new NetworkCapabilities(networkCapabilities);
+
+            final ParcelUuid subGrp = getSubGroupForNetworkCapabilities(ncCopy);
+            boolean isVcnManagedNetwork = false;
+            boolean isRestricted = false;
+            synchronized (mLock) {
+                final Vcn vcn = mVcns.get(subGrp);
+                final VcnConfig vcnConfig = mConfigs.get(subGrp);
+                if (vcn != null && vcnConfig != null) {
+                    if (vcn.getStatus() == VCN_STATUS_CODE_ACTIVE) {
+                        isVcnManagedNetwork = true;
+                    }
+
+                    final Set<Integer> restrictedTransports = mDeps.getRestrictedTransports(
+                            subGrp, mLastSnapshot, vcnConfig);
+                    for (int restrictedTransport : restrictedTransports) {
+                        if (ncCopy.hasTransport(restrictedTransport)) {
+                            if (restrictedTransport == TRANSPORT_CELLULAR
+                                    || restrictedTransport == TRANSPORT_TEST) {
+                                // For cell or test network, only mark it as restricted when
+                                // the VCN is in active mode.
+                                isRestricted |= (vcn.getStatus() == VCN_STATUS_CODE_ACTIVE);
+                            } else {
+                                isRestricted = true;
+                                break;
+                            }
+                        }
+                    }
+                } else if (vcn != null && vcnConfig == null) {
+                    logWtf("Vcn instance exists but VcnConfig does not for " + subGrp);
+                }
+            }
+
+            final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder(ncCopy);
+
+            if (isVcnManagedNetwork) {
+                ncBuilder.removeCapability(
+                        NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+            } else {
+                ncBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+            }
+
+            if (isRestricted) {
+                ncBuilder.removeCapability(
+                        NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+            }
+
+            final NetworkCapabilities result = ncBuilder.build();
+            final VcnUnderlyingNetworkPolicy policy = new VcnUnderlyingNetworkPolicy(
+                    mTrackingNetworkCallback
+                            .requiresRestartForImmutableCapabilityChanges(result, linkProperties),
+                    result);
+
+            logVdbg("getUnderlyingNetworkPolicy() called for caps: " + networkCapabilities
+                        + "; and lp: " + linkProperties + "; result = " + policy);
+            return policy;
+        });
+    }
+
+    /** Binder death recipient used to remove registered VcnStatusCallbacks. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    class VcnStatusCallbackInfo implements Binder.DeathRecipient {
+        @NonNull final ParcelUuid mSubGroup;
+        @NonNull final IVcnStatusCallback mCallback;
+        @NonNull final String mPkgName;
+        final int mUid;
+
+        private VcnStatusCallbackInfo(
+                @NonNull ParcelUuid subGroup,
+                @NonNull IVcnStatusCallback callback,
+                @NonNull String pkgName,
+                int uid) {
+            mSubGroup = subGroup;
+            mCallback = callback;
+            mPkgName = pkgName;
+            mUid = uid;
+        }
+
+        @Override
+        public void binderDied() {
+            Log.e(TAG, "app died without unregistering VcnStatusCallback");
+            unregisterVcnStatusCallback(mCallback);
+        }
+    }
+
+    private boolean isCallbackPermissioned(
+            @NonNull VcnStatusCallbackInfo cbInfo, @NonNull ParcelUuid subgroup) {
+        if (!subgroup.equals(cbInfo.mSubGroup)) {
+            return false;
+        }
+
+        if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(subgroup, cbInfo.mPkgName)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /** Registers the provided callback for receiving VCN status updates. */
+    @Override
+    public void registerVcnStatusCallback(
+            @NonNull ParcelUuid subGroup,
+            @NonNull IVcnStatusCallback callback,
+            @NonNull String opPkgName) {
+        final int callingUid = mDeps.getBinderCallingUid();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            requireNonNull(subGroup, "subGroup must not be null");
+            requireNonNull(callback, "callback must not be null");
+            requireNonNull(opPkgName, "opPkgName must not be null");
+
+            mContext.getSystemService(AppOpsManager.class).checkPackage(callingUid, opPkgName);
+
+            final IBinder cbBinder = callback.asBinder();
+            final VcnStatusCallbackInfo cbInfo =
+                    new VcnStatusCallbackInfo(subGroup, callback, opPkgName, callingUid);
+
+            try {
+                cbBinder.linkToDeath(cbInfo, 0 /* flags */);
+            } catch (RemoteException e) {
+                // Remote binder already died - don't add to mRegisteredStatusCallbacks and exit
+                return;
+            }
+
+            synchronized (mLock) {
+                if (mRegisteredStatusCallbacks.containsKey(cbBinder)) {
+                    throw new IllegalStateException(
+                            "Attempting to register a callback that is already in use");
+                }
+
+                mRegisteredStatusCallbacks.put(cbBinder, cbInfo);
+
+                // now that callback is registered, send it the VCN's current status
+                final VcnConfig vcnConfig = mConfigs.get(subGroup);
+                final Vcn vcn = mVcns.get(subGroup);
+                final int vcnStatus =
+                        vcn == null ? VCN_STATUS_CODE_NOT_CONFIGURED : vcn.getStatus();
+                final int resultStatus;
+                if (vcnConfig == null || !isCallbackPermissioned(cbInfo, subGroup)) {
+                    resultStatus = VCN_STATUS_CODE_NOT_CONFIGURED;
+                } else if (vcn == null) {
+                    resultStatus = VCN_STATUS_CODE_INACTIVE;
+                } else if (vcnStatus == VCN_STATUS_CODE_ACTIVE
+                        || vcnStatus == VCN_STATUS_CODE_SAFE_MODE) {
+                    resultStatus = vcnStatus;
+                } else {
+                    logWtf("Unknown VCN status: " + vcnStatus);
+                    resultStatus = VCN_STATUS_CODE_NOT_CONFIGURED;
+                }
+
+                try {
+                    cbInfo.mCallback.onVcnStatusChanged(resultStatus);
+                } catch (RemoteException e) {
+                    logDbg("VcnStatusCallback threw on VCN status change", e);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /** Unregisters the provided callback from receiving future VCN status updates. */
+    @Override
+    public void unregisterVcnStatusCallback(@NonNull IVcnStatusCallback callback) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            requireNonNull(callback, "callback must not be null");
+
+            final IBinder cbBinder = callback.asBinder();
+            synchronized (mLock) {
+                VcnStatusCallbackInfo cbInfo = mRegisteredStatusCallbacks.remove(cbBinder);
+
+                if (cbInfo != null) {
+                    cbBinder.unlinkToDeath(cbInfo, 0 /* flags */);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setLastSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
+        mLastSnapshot = Objects.requireNonNull(snapshot);
+    }
+
+    private void logVdbg(String msg) {
+        if (VDBG) {
+            Slog.v(TAG, msg);
+        }
+    }
+
+    private void logDbg(String msg) {
+        Slog.d(TAG, msg);
+    }
+
+    private void logDbg(String msg, Throwable tr) {
+        Slog.d(TAG, msg, tr);
+    }
+
+    private void logInfo(String msg) {
+        Slog.i(TAG, msg);
+        LOCAL_LOG.log("[INFO] [" + TAG + "] " + msg);
+    }
+
+    private void logInfo(String msg, Throwable tr) {
+        Slog.i(TAG, msg, tr);
+        LOCAL_LOG.log("[INFO] [" + TAG + "] " + msg + tr);
+    }
+
+    private void logErr(String msg) {
+        Slog.e(TAG, msg);
+        LOCAL_LOG.log("[ERR] [" + TAG + "] " + msg);
+    }
+
+    private void logErr(String msg, Throwable tr) {
+        Slog.e(TAG, msg, tr);
+        LOCAL_LOG.log("[ERR ] [" + TAG + "] " + msg + tr);
+    }
+
+    private void logWtf(String msg) {
+        Slog.wtf(TAG, msg);
+        LOCAL_LOG.log("[WTF] [" + TAG + "] " + msg);
+    }
+
+    private void logWtf(String msg, Throwable tr) {
+        Slog.wtf(TAG, msg, tr);
+        LOCAL_LOG.log("[WTF ] [" + TAG + "] " + msg + tr);
+    }
+
+    /**
+     * Dumps the state of the VcnManagementService for logging and debugging purposes.
+     *
+     * <p>PII and credentials MUST NEVER be dumped here.
+     */
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "| ");
+
+        // Post to handler thread to prevent ConcurrentModificationExceptions, and avoid lock-hell.
+        HandlerUtils.runWithScissorsForDump(
+                mHandler,
+                () -> {
+                    mNetworkProvider.dump(pw);
+                    pw.println();
+
+                    mTrackingNetworkCallback.dump(pw);
+                    pw.println();
+
+                    synchronized (mLock) {
+                        mLastSnapshot.dump(pw);
+                        pw.println();
+
+                        pw.println("mConfigs:");
+                        pw.increaseIndent();
+                        for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
+                            pw.println(
+                                    entry.getKey()
+                                            + ": "
+                                            + entry.getValue().getProvisioningPackageName());
+                        }
+                        pw.decreaseIndent();
+                        pw.println();
+
+                        pw.println("mVcns:");
+                        pw.increaseIndent();
+                        for (Vcn vcn : mVcns.values()) {
+                            vcn.dump(pw);
+                        }
+                        pw.decreaseIndent();
+                        pw.println();
+                    }
+
+                    pw.println("Local log:");
+                    pw.increaseIndent();
+                    LOCAL_LOG.dump(pw);
+                    pw.decreaseIndent();
+                    pw.println();
+                },
+                DUMP_TIMEOUT_MILLIS);
+    }
+
+    // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService
+    /** Callback for Vcn signals sent up to VcnManagementService. */
+    public interface VcnCallback {
+        /** Called by a Vcn to signal that its safe mode status has changed. */
+        void onSafeModeStatusChanged(boolean isInSafeMode);
+
+        /** Called by a Vcn to signal that an error occurred. */
+        void onGatewayConnectionError(
+                @NonNull String gatewayConnectionName,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage);
+    }
+
+    /**
+     * TrackingNetworkCallback tracks all active networks
+     *
+     * <p>This is used to ensure that no underlying networks have immutable capabilities changed
+     * without requiring a Network restart.
+     */
+    private class TrackingNetworkCallback extends ConnectivityManager.NetworkCallback {
+        private final Object mLockObject = new Object();
+        private final Map<Network, NetworkCapabilities> mCaps = new ArrayMap<>();
+        private final Map<Network, LinkProperties> mLinkProperties = new ArrayMap<>();
+
+        @Override
+        public void onCapabilitiesChanged(Network network, NetworkCapabilities caps) {
+            synchronized (mLockObject) {
+                mCaps.put(network, caps);
+            }
+        }
+
+        @Override
+        public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
+            synchronized (mLockObject) {
+                mLinkProperties.put(network, lp);
+            }
+        }
+
+        @Override
+        public void onLost(Network network) {
+            synchronized (mLockObject) {
+                mCaps.remove(network);
+                mLinkProperties.remove(network);
+            }
+        }
+
+        private Set<Integer> getNonTestTransportTypes(NetworkCapabilities caps) {
+            final Set<Integer> transportTypes = new ArraySet<>();
+            for (int t : caps.getTransportTypes()) {
+                transportTypes.add(t);
+            }
+            return transportTypes;
+        }
+
+        private boolean hasSameTransportsAndCapabilities(
+                NetworkCapabilities caps, NetworkCapabilities capsOther) {
+            if (!Objects.equals(
+                    getNonTestTransportTypes(caps), getNonTestTransportTypes(capsOther))) {
+                return false;
+            }
+
+            for (int capability : ALLOWED_CAPABILITIES) {
+                if (caps.hasCapability(capability) != capsOther.hasCapability(capability)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private boolean requiresRestartForImmutableCapabilityChanges(
+                NetworkCapabilities caps, LinkProperties lp) {
+            if (caps.getSubscriptionIds() == null) {
+                return false;
+            }
+
+            synchronized (mLockObject) {
+                // Search for an existing network (using interfce names)
+                // TODO: Get network from NetworkFactory (if exists) for this match.
+                for (Entry<Network, LinkProperties> lpEntry : mLinkProperties.entrySet()) {
+                    if (lp.getInterfaceName() != null
+                            && !lp.getInterfaceName().isEmpty()
+                            && Objects.equals(
+                                    lp.getInterfaceName(), lpEntry.getValue().getInterfaceName())) {
+                        return mCaps.get(lpEntry.getKey())
+                                        .hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                                != caps.hasCapability(NET_CAPABILITY_NOT_RESTRICTED);
+                    }
+                }
+            }
+
+            // If no network found, by definition does not need restart.
+            return false;
+        }
+
+        /** Dumps the state of this snapshot for logging and debugging purposes. */
+        public void dump(IndentingPrintWriter pw) {
+            pw.println("TrackingNetworkCallback:");
+            pw.increaseIndent();
+
+            pw.println("mCaps:");
+            pw.increaseIndent();
+            synchronized (mCaps) {
+                for (Entry<Network, NetworkCapabilities> entry : mCaps.entrySet()) {
+                    pw.println(entry.getKey() + ": " + entry.getValue());
+                }
+            }
+            pw.decreaseIndent();
+            pw.println();
+
+            pw.decreaseIndent();
+        }
+    }
+
+    /** VcnCallbackImpl for Vcn signals sent up to VcnManagementService. */
+    private class VcnCallbackImpl implements VcnCallback {
+        @NonNull private final ParcelUuid mSubGroup;
+
+        private VcnCallbackImpl(@NonNull final ParcelUuid subGroup) {
+            mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
+        }
+
+        @Override
+        public void onSafeModeStatusChanged(boolean isInSafeMode) {
+            synchronized (mLock) {
+                // Ignore if this subscription group doesn't exist anymore
+                if (!mVcns.containsKey(mSubGroup)) {
+                    return;
+                }
+
+                final int status =
+                        isInSafeMode ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
+
+                notifyAllPolicyListenersLocked();
+                notifyAllPermissionedStatusCallbacksLocked(mSubGroup, status);
+            }
+        }
+
+        @Override
+        public void onGatewayConnectionError(
+                @NonNull String gatewayConnectionName,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage) {
+            synchronized (mLock) {
+                // Ignore if this subscription group doesn't exist anymore
+                if (!mVcns.containsKey(mSubGroup)) {
+                    return;
+                }
+
+                // Notify all registered StatusCallbacks for this subGroup
+                for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
+                    if (isCallbackPermissioned(cbInfo, mSubGroup)) {
+                        BinderUtils.withCleanCallingIdentity(() -> {
+                            try {
+                                cbInfo.mCallback.onGatewayConnectionError(
+                                        gatewayConnectionName,
+                                        errorCode,
+                                        exceptionClass,
+                                        exceptionMessage);
+                            } catch (RemoteException e) {
+                                logDbg("VcnStatusCallback threw on VCN status change", e);
+                            }
+                        });
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java b/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java
new file mode 100644
index 0000000..b448f75
--- /dev/null
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -0,0 +1,599 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn;
+
+import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.vcn.VcnManager;
+import android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.modules.utils.HandlerExecutor;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * TelephonySubscriptionTracker provides a caching layer for tracking active subscription groups.
+ *
+ * <p>This class performs two roles:
+ *
+ * <ol>
+ *   <li>De-noises subscription changes by ensuring that only changes in active and ready
+ *       subscription groups are acted upon
+ *   <li>Caches mapping between subIds and subscription groups
+ * </ol>
+ *
+ * <p>An subscription group is active and ready if any of its contained subIds has had BOTH the
+ * {@link CarrierConfigManager#isConfigForIdentifiedCarrier()} return true, AND the subscription is
+ * listed as active per SubscriptionManager#getAllSubscriptionInfoList().
+ *
+ * <p>Note that due to the asynchronous nature of callbacks and broadcasts, the output of this class
+ * is (only) eventually consistent.
+ *
+ * @hide
+ */
+public class TelephonySubscriptionTracker extends BroadcastReceiver {
+    @NonNull private static final String TAG = TelephonySubscriptionTracker.class.getSimpleName();
+    private static final boolean LOG_DBG = false; // STOPSHIP if true
+
+    @NonNull private final Context mContext;
+    @NonNull private final Handler mHandler;
+    @NonNull private final TelephonySubscriptionTrackerCallback mCallback;
+    @NonNull private final Dependencies mDeps;
+
+    @NonNull private final TelephonyManager mTelephonyManager;
+    @NonNull private final SubscriptionManager mSubscriptionManager;
+    @Nullable private final CarrierConfigManager mCarrierConfigManager;
+
+    @NonNull private final ActiveDataSubscriptionIdListener mActiveDataSubIdListener;
+
+    // TODO (Android T+): Add ability to handle multiple subIds per slot.
+    @NonNull private final Map<Integer, Integer> mReadySubIdsBySlotId = new HashMap<>();
+
+    @NonNull
+    private final Map<Integer, PersistableBundleWrapper> mSubIdToCarrierConfigMap = new HashMap<>();
+
+    @NonNull private final OnSubscriptionsChangedListener mSubscriptionChangedListener;
+
+    @NonNull
+    private final List<CarrierPrivilegesCallback> mCarrierPrivilegesCallbacks = new ArrayList<>();
+
+    @NonNull private TelephonySubscriptionSnapshot mCurrentSnapshot;
+
+    @NonNull
+    private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
+            (int logicalSlotIndex, int subscriptionId, int carrierId, int specificCarrierId) ->
+                    handleActionCarrierConfigChanged(logicalSlotIndex, subscriptionId);
+
+
+    public TelephonySubscriptionTracker(
+            @NonNull Context context,
+            @NonNull Handler handler,
+            @NonNull TelephonySubscriptionTrackerCallback callback) {
+        this(context, handler, callback, new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    TelephonySubscriptionTracker(
+            @NonNull Context context,
+            @NonNull Handler handler,
+            @NonNull TelephonySubscriptionTrackerCallback callback,
+            @NonNull Dependencies deps) {
+        mContext = Objects.requireNonNull(context, "Missing context");
+        mHandler = Objects.requireNonNull(handler, "Missing handler");
+        mCallback = Objects.requireNonNull(callback, "Missing callback");
+        mDeps = Objects.requireNonNull(deps, "Missing deps");
+
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+        mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
+        mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
+        mActiveDataSubIdListener = new ActiveDataSubscriptionIdListener();
+
+        mSubscriptionChangedListener =
+                new OnSubscriptionsChangedListener() {
+                    @Override
+                    public void onSubscriptionsChanged() {
+                        handleSubscriptionsChanged();
+                    }
+                };
+    }
+
+    /**
+     * Registers the receivers, and starts tracking subscriptions.
+     *
+     * <p>Must always be run on the VcnManagementService thread.
+     */
+    public void register() {
+        final HandlerExecutor executor = new HandlerExecutor(mHandler);
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_MULTI_SIM_CONFIG_CHANGED);
+
+        mContext.registerReceiver(this, filter, null, mHandler);
+        mSubscriptionManager.addOnSubscriptionsChangedListener(
+                executor, mSubscriptionChangedListener);
+        mTelephonyManager.registerTelephonyCallback(executor, mActiveDataSubIdListener);
+        if (mCarrierConfigManager != null) {
+            mCarrierConfigManager.registerCarrierConfigChangeListener(executor,
+                    mCarrierConfigChangeListener);
+        }
+
+        registerCarrierPrivilegesCallbacks();
+    }
+
+    private void registerCarrierPrivilegesCallbacks() {
+        final HandlerExecutor executor = new HandlerExecutor(mHandler);
+        final int modemCount = mTelephonyManager.getActiveModemCount();
+        try {
+            for (int i = 0; i < modemCount; i++) {
+                CarrierPrivilegesCallback carrierPrivilegesCallback =
+                        new CarrierPrivilegesCallback() {
+                            @Override
+                            public void onCarrierPrivilegesChanged(
+                                    @NonNull Set<String> privilegedPackageNames,
+                                    @NonNull Set<Integer> privilegedUids) {
+                                // Re-trigger the synchronous check (which is also very cheap due
+                                // to caching in CarrierPrivilegesTracker). This allows consistency
+                                // with the onSubscriptionsChangedListener and broadcasts.
+                                handleSubscriptionsChanged();
+                            }
+                        };
+
+                mTelephonyManager.registerCarrierPrivilegesCallback(
+                        i, executor, carrierPrivilegesCallback);
+                mCarrierPrivilegesCallbacks.add(carrierPrivilegesCallback);
+            }
+        } catch (IllegalArgumentException e) {
+            Slog.wtf(TAG, "Encounted exception registering carrier privileges listeners", e);
+        }
+    }
+
+    /**
+     * Unregisters the receivers, and stops tracking subscriptions.
+     *
+     * <p>Must always be run on the VcnManagementService thread.
+     */
+    public void unregister() {
+        mContext.unregisterReceiver(this);
+        mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionChangedListener);
+        mTelephonyManager.unregisterTelephonyCallback(mActiveDataSubIdListener);
+        if (mCarrierConfigManager != null) {
+            mCarrierConfigManager.unregisterCarrierConfigChangeListener(
+                    mCarrierConfigChangeListener);
+        }
+
+        unregisterCarrierPrivilegesCallbacks();
+    }
+
+    private void unregisterCarrierPrivilegesCallbacks() {
+        for (CarrierPrivilegesCallback carrierPrivilegesCallback :
+                mCarrierPrivilegesCallbacks) {
+            mTelephonyManager.unregisterCarrierPrivilegesCallback(carrierPrivilegesCallback);
+        }
+        mCarrierPrivilegesCallbacks.clear();
+    }
+
+    /**
+     * Handles subscription changes, correlating available subscriptions and loaded carrier configs
+     *
+     * <p>The subscription change listener is registered with a HandlerExecutor backed by mHandler,
+     * so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking.
+     */
+    public void handleSubscriptionsChanged() {
+        final Map<ParcelUuid, Set<String>> privilegedPackages = new HashMap<>();
+        final Map<Integer, SubscriptionInfo> newSubIdToInfoMap = new HashMap<>();
+
+        final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList();
+        if (allSubs == null) {
+            return; // Telephony crashed; no way to verify subscriptions.
+        }
+
+        // If allSubs is empty, no subscriptions exist. Cache will be cleared by virtue of no active
+        // subscriptions
+        for (SubscriptionInfo subInfo : allSubs) {
+            if (subInfo.getGroupUuid() == null) {
+                continue;
+            }
+
+            // Build subId -> subGrp cache
+            newSubIdToInfoMap.put(subInfo.getSubscriptionId(), subInfo);
+
+            // Update subscription groups that are both ready, and active. For a group to be
+            // considered active, both of the following must be true:
+            //
+            // 1. A final CARRIER_CONFIG_CHANGED (where config is for an identified carrier)
+            // broadcast must have been received for the subId
+            // 2. A active subscription (is loaded into a SIM slot) must be part of the subscription
+            // group.
+            if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX
+                    && mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) {
+                final TelephonyManager subIdSpecificTelephonyManager =
+                        mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId());
+
+                final ParcelUuid subGroup = subInfo.getGroupUuid();
+                final Set<String> pkgs =
+                        privilegedPackages.getOrDefault(subGroup, new ArraySet<>());
+                pkgs.addAll(subIdSpecificTelephonyManager.getPackagesWithCarrierPrivileges());
+
+                privilegedPackages.put(subGroup, pkgs);
+            }
+        }
+
+        final TelephonySubscriptionSnapshot newSnapshot =
+                new TelephonySubscriptionSnapshot(
+                        mDeps.getActiveDataSubscriptionId(),
+                        newSubIdToInfoMap,
+                        mSubIdToCarrierConfigMap,
+                        privilegedPackages);
+
+        // If snapshot was meaningfully updated, fire the callback
+        if (!newSnapshot.equals(mCurrentSnapshot)) {
+            mCurrentSnapshot = newSnapshot;
+            mHandler.post(
+                    () -> {
+                        mCallback.onNewSnapshot(newSnapshot);
+                    });
+        }
+    }
+
+    /**
+     * Broadcast receiver for ACTION_MULTI_SIM_CONFIG_CHANGED
+     *
+     * <p>The broadcast receiver is registered with mHandler, so callbacks & broadcasts are all
+     * serialized on mHandler, avoiding the need for locking.
+     */
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        switch (intent.getAction()) {
+            case ACTION_MULTI_SIM_CONFIG_CHANGED:
+                handleActionMultiSimConfigChanged(context, intent);
+                break;
+            default:
+                Slog.v(TAG, "Unknown intent received with action: " + intent.getAction());
+        }
+    }
+
+    private void handleActionMultiSimConfigChanged(Context context, Intent intent) {
+        unregisterCarrierPrivilegesCallbacks();
+
+        // Clear invalid slotIds from the mReadySubIdsBySlotId map.
+        final int modemCount = mTelephonyManager.getActiveModemCount();
+        final Iterator<Integer> slotIdIterator = mReadySubIdsBySlotId.keySet().iterator();
+        while (slotIdIterator.hasNext()) {
+            final int slotId = slotIdIterator.next();
+
+            if (slotId >= modemCount) {
+                slotIdIterator.remove();
+            }
+        }
+
+        registerCarrierPrivilegesCallbacks();
+        handleSubscriptionsChanged();
+    }
+
+    private void handleActionCarrierConfigChanged(int slotId, int subId) {
+        if (slotId == INVALID_SIM_SLOT_INDEX) {
+            return;
+        }
+
+        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+            // Get only configs as needed to save memory.
+            PersistableBundle carrierConfig = new PersistableBundle();
+            try {
+                carrierConfig =
+                        mCarrierConfigManager.getConfigForSubId(
+                                subId, VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS);
+
+            } catch (RuntimeException exception) {
+                Slog.w(TAG, "CarrierConfigLoader is not available.");
+            }
+
+            if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) {
+                mReadySubIdsBySlotId.put(slotId, subId);
+
+                if (!carrierConfig.isEmpty()) {
+                    mSubIdToCarrierConfigMap.put(subId,
+                            new PersistableBundleWrapper(carrierConfig));
+                }
+                handleSubscriptionsChanged();
+            }
+        } else {
+            final Integer oldSubid = mReadySubIdsBySlotId.remove(slotId);
+            if (oldSubid != null) {
+                mSubIdToCarrierConfigMap.remove(oldSubid);
+            }
+            handleSubscriptionsChanged();
+        }
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setReadySubIdsBySlotId(Map<Integer, Integer> readySubIdsBySlotId) {
+        mReadySubIdsBySlotId.clear();
+        mReadySubIdsBySlotId.putAll(readySubIdsBySlotId);
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setSubIdToCarrierConfigMap(
+            Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap) {
+        mSubIdToCarrierConfigMap.clear();
+        mSubIdToCarrierConfigMap.putAll(subIdToCarrierConfigMap);
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    Map<Integer, Integer> getReadySubIdsBySlotId() {
+        return Collections.unmodifiableMap(mReadySubIdsBySlotId);
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    Map<Integer, PersistableBundleWrapper> getSubIdToCarrierConfigMap() {
+        return Collections.unmodifiableMap(mSubIdToCarrierConfigMap);
+    }
+
+    /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */
+    public static class TelephonySubscriptionSnapshot {
+        private final int mActiveDataSubId;
+        private final Map<Integer, SubscriptionInfo> mSubIdToInfoMap;
+        private final Map<Integer, PersistableBundleWrapper> mSubIdToCarrierConfigMap;
+        private final Map<ParcelUuid, Set<String>> mPrivilegedPackages;
+
+        public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT =
+                new TelephonySubscriptionSnapshot(
+                        INVALID_SUBSCRIPTION_ID,
+                        Collections.emptyMap(),
+                        Collections.emptyMap(),
+                        Collections.emptyMap());
+
+        @VisibleForTesting(visibility = Visibility.PRIVATE)
+        TelephonySubscriptionSnapshot(
+                int activeDataSubId,
+                @NonNull Map<Integer, SubscriptionInfo> subIdToInfoMap,
+                @NonNull Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap,
+                @NonNull Map<ParcelUuid, Set<String>> privilegedPackages) {
+            mActiveDataSubId = activeDataSubId;
+            Objects.requireNonNull(subIdToInfoMap, "subIdToInfoMap was null");
+            Objects.requireNonNull(privilegedPackages, "privilegedPackages was null");
+            Objects.requireNonNull(subIdToCarrierConfigMap, "subIdToCarrierConfigMap was null");
+
+            mSubIdToInfoMap =
+                    Collections.unmodifiableMap(
+                            new HashMap<Integer, SubscriptionInfo>(subIdToInfoMap));
+            mSubIdToCarrierConfigMap =
+                    Collections.unmodifiableMap(
+                            new HashMap<Integer, PersistableBundleWrapper>(
+                                    subIdToCarrierConfigMap));
+
+            final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>();
+            for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) {
+                unmodifiableInnerSets.put(
+                        entry.getKey(), Collections.unmodifiableSet(entry.getValue()));
+            }
+            mPrivilegedPackages = Collections.unmodifiableMap(unmodifiableInnerSets);
+        }
+
+        /** Returns the active subscription ID. May be INVALID_SUBSCRIPTION_ID */
+        public int getActiveDataSubscriptionId() {
+            return mActiveDataSubId;
+        }
+
+        /** Returns the active subscription group */
+        @Nullable
+        public ParcelUuid getActiveDataSubscriptionGroup() {
+            final SubscriptionInfo info = mSubIdToInfoMap.get(getActiveDataSubscriptionId());
+            if (info == null) {
+                return null;
+            }
+
+            return info.getGroupUuid();
+        }
+
+        /** Returns the active subscription groups */
+        @NonNull
+        public Set<ParcelUuid> getActiveSubscriptionGroups() {
+            return mPrivilegedPackages.keySet();
+        }
+
+        /** Returns all subscription groups */
+        @NonNull
+        public Set<ParcelUuid> getAllSubscriptionGroups() {
+            final Set<ParcelUuid> subGroups = new ArraySet<>();
+            for (SubscriptionInfo subInfo : mSubIdToInfoMap.values()) {
+                subGroups.add(subInfo.getGroupUuid());
+            }
+
+            return subGroups;
+        }
+
+        /** Checks if the provided package is carrier privileged for the specified sub group. */
+        public boolean packageHasPermissionsForSubscriptionGroup(
+                @NonNull ParcelUuid subGrp, @NonNull String packageName) {
+            final Set<String> privilegedPackages = mPrivilegedPackages.get(subGrp);
+
+            return privilegedPackages != null && privilegedPackages.contains(packageName);
+        }
+
+        /** Returns the Subscription Group for a given subId. */
+        @Nullable
+        public ParcelUuid getGroupForSubId(int subId) {
+            return mSubIdToInfoMap.containsKey(subId)
+                    ? mSubIdToInfoMap.get(subId).getGroupUuid()
+                    : null;
+        }
+
+        /**
+         * Returns all the subIds in a given group, including available, but inactive subscriptions.
+         */
+        @NonNull
+        public Set<Integer> getAllSubIdsInGroup(ParcelUuid subGrp) {
+            final Set<Integer> subIds = new ArraySet<>();
+
+            for (Entry<Integer, SubscriptionInfo> entry : mSubIdToInfoMap.entrySet()) {
+                if (subGrp.equals(entry.getValue().getGroupUuid())) {
+                    subIds.add(entry.getKey());
+                }
+            }
+
+            return subIds;
+        }
+
+        /** Checks if the requested subscription is opportunistic */
+        @NonNull
+        public boolean isOpportunistic(int subId) {
+            return mSubIdToInfoMap.containsKey(subId)
+                    ? mSubIdToInfoMap.get(subId).isOpportunistic()
+                    : false;
+        }
+
+        /**
+         * Retrieves a carrier config for a subscription in the provided group.
+         *
+         * <p>This method will prioritize non-opportunistic subscriptions, but will use the a
+         * carrier config for an opportunistic subscription if no other subscriptions are found.
+         */
+        @Nullable
+        public PersistableBundleWrapper getCarrierConfigForSubGrp(@NonNull ParcelUuid subGrp) {
+            PersistableBundleWrapper result = null;
+
+            for (int subId : getAllSubIdsInGroup(subGrp)) {
+                final PersistableBundleWrapper config = mSubIdToCarrierConfigMap.get(subId);
+                if (config != null) {
+                    result = config;
+
+                    // Attempt to use (any) non-opportunistic subscription. If this subscription is
+                    // opportunistic, continue and try to find a non-opportunistic subscription,
+                    // using the opportunistic ones as a last resort.
+                    if (!isOpportunistic(subId)) {
+                        return config;
+                    }
+                }
+            }
+
+            return result;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(
+                    mActiveDataSubId,
+                    mSubIdToInfoMap,
+                    mSubIdToCarrierConfigMap,
+                    mPrivilegedPackages);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof TelephonySubscriptionSnapshot)) {
+                return false;
+            }
+
+            final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj;
+
+            return mActiveDataSubId == other.mActiveDataSubId
+                    && mSubIdToInfoMap.equals(other.mSubIdToInfoMap)
+                    && mSubIdToCarrierConfigMap.equals(other.mSubIdToCarrierConfigMap)
+                    && mPrivilegedPackages.equals(other.mPrivilegedPackages);
+        }
+
+        /** Dumps the state of this snapshot for logging and debugging purposes. */
+        public void dump(IndentingPrintWriter pw) {
+            pw.println("TelephonySubscriptionSnapshot:");
+            pw.increaseIndent();
+
+            pw.println("mActiveDataSubId: " + mActiveDataSubId);
+            pw.println("mSubIdToInfoMap: " + mSubIdToInfoMap);
+            pw.println("mSubIdToCarrierConfigMap: " + mSubIdToCarrierConfigMap);
+            pw.println("mPrivilegedPackages: " + mPrivilegedPackages);
+
+            pw.decreaseIndent();
+        }
+
+        @Override
+        public String toString() {
+            return "TelephonySubscriptionSnapshot{ "
+                    + "mActiveDataSubId=" + mActiveDataSubId
+                    + ", mSubIdToInfoMap=" + mSubIdToInfoMap
+                    + ", mSubIdToCarrierConfigMap=" + mSubIdToCarrierConfigMap
+                    + ", mPrivilegedPackages=" + mPrivilegedPackages
+                    + " }";
+        }
+    }
+
+    /**
+     * Interface for listening to changes in subscriptions
+     *
+     * @see TelephonySubscriptionTracker
+     */
+    public interface TelephonySubscriptionTrackerCallback {
+        /**
+         * Called when subscription information changes, and a new subscription snapshot was taken
+         *
+         * @param snapshot the snapshot of subscription information.
+         */
+        void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot);
+    }
+
+    private class ActiveDataSubscriptionIdListener extends TelephonyCallback
+            implements TelephonyCallback.ActiveDataSubscriptionIdListener {
+        @Override
+        public void onActiveDataSubscriptionIdChanged(int subId) {
+            handleSubscriptionsChanged();
+        }
+    }
+
+    /** External static dependencies for test injection */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        /** Checks if the given bundle is for an identified carrier */
+        public boolean isConfigForIdentifiedCarrier(PersistableBundle bundle) {
+            return CarrierConfigManager.isConfigForIdentifiedCarrier(bundle);
+        }
+
+        /** Gets the active Subscription ID */
+        public int getActiveDataSubscriptionId() {
+            return SubscriptionManager.getActiveDataSubscriptionId();
+        }
+    }
+}
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
new file mode 100644
index 0000000..2524d0e
--- /dev/null
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
@@ -0,0 +1,785 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
+import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE;
+import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
+
+import static com.android.server.VcnManagementService.LOCAL_LOG;
+import static com.android.server.VcnManagementService.VDBG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.net.Uri;
+import android.net.vcn.VcnConfig;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnManager.VcnErrorCode;
+import android.net.vcn.util.LogUtils;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.provider.Settings;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.modules.utils.HandlerExecutor;
+import com.android.server.VcnManagementService.VcnCallback;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Represents an single instance of a VCN.
+ *
+ * <p>Each Vcn instance manages all {@link VcnGatewayConnection}(s) for a given subscription group,
+ * including per-capability networks, network selection, and multi-homing.
+ *
+ * @hide
+ */
+public class Vcn extends Handler {
+    private static final String TAG = Vcn.class.getSimpleName();
+
+    private static final int VCN_LEGACY_SCORE_INT = 52;
+
+    private static final List<Integer> CAPS_REQUIRING_MOBILE_DATA =
+            Arrays.asList(NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN);
+
+    private static final int MSG_EVENT_BASE = 0;
+    private static final int MSG_CMD_BASE = 100;
+
+    // Copied from Settings.Global.MOBILE_DATA
+    private static final String SETTINGS_GLOBAL_MOBILE_DATA_STRING = "mobile_data";
+
+    /**
+     * A carrier app updated the configuration.
+     *
+     * <p>Triggers update of config, re-evaluating all active and underlying networks.
+     *
+     * @param obj VcnConfig
+     */
+    private static final int MSG_EVENT_CONFIG_UPDATED = MSG_EVENT_BASE;
+
+    /**
+     * A NetworkRequest was added or updated.
+     *
+     * <p>Triggers an evaluation of all active networks, bringing up a new one if necessary.
+     *
+     * @param obj NetworkRequest
+     */
+    private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1;
+
+    /**
+     * The TelephonySubscriptionSnapshot tracked by VcnManagementService has changed.
+     *
+     * <p>This updated snapshot should be cached locally and passed to all VcnGatewayConnections.
+     *
+     * @param obj TelephonySubscriptionSnapshot
+     */
+    private static final int MSG_EVENT_SUBSCRIPTIONS_CHANGED = MSG_EVENT_BASE + 2;
+
+    /**
+     * A GatewayConnection owned by this VCN quit.
+     *
+     * @param obj VcnGatewayConnectionConfig
+     */
+    private static final int MSG_EVENT_GATEWAY_CONNECTION_QUIT = MSG_EVENT_BASE + 3;
+
+    /**
+     * Triggers reevaluation of safe mode conditions.
+     *
+     * <p>Upon entering safe mode, the VCN will only provide gateway connections opportunistically,
+     * leaving the underlying networks marked as NOT_VCN_MANAGED.
+     *
+     * <p>Any VcnGatewayConnection in safe mode will result in the entire Vcn instance being put
+     * into safe mode. Upon receiving this message, the Vcn MUST query all VcnGatewayConnections to
+     * determine if any are in safe mode.
+     */
+    private static final int MSG_EVENT_SAFE_MODE_STATE_CHANGED = MSG_EVENT_BASE + 4;
+
+    /**
+     * Triggers reevaluation of mobile data enabled conditions.
+     *
+     * <p>Upon this notification, the VCN will check if any of the underlying subIds have mobile
+     * data enabled. If not, the VCN will restart any GatewayConnections providing INTERNET or DUN
+     * with the current mobile data toggle status.
+     */
+    private static final int MSG_EVENT_MOBILE_DATA_TOGGLED = MSG_EVENT_BASE + 5;
+
+    /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
+    private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
+
+    @NonNull private final VcnContext mVcnContext;
+    @NonNull private final ParcelUuid mSubscriptionGroup;
+    @NonNull private final Dependencies mDeps;
+    @NonNull private final VcnNetworkRequestListener mRequestListener;
+    @NonNull private final VcnCallback mVcnCallback;
+    @NonNull private final VcnContentResolver mContentResolver;
+    @NonNull private final ContentObserver mMobileDataSettingsObserver;
+
+    @NonNull
+    private final Map<Integer, VcnUserMobileDataStateListener> mMobileDataStateListeners =
+            new ArrayMap<>();
+
+    /**
+     * Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs.
+     *
+     * <p>Due to potential for race conditions, VcnGatewayConnections MUST only be created and added
+     * to this map in {@link #handleNetworkRequested(NetworkRequest, int, int)}, when a VCN receives
+     * a NetworkRequest that matches a VcnGatewayConnectionConfig for this VCN's VcnConfig.
+     *
+     * <p>A VcnGatewayConnection instance MUST NEVER overwrite an existing instance - otherwise
+     * there is potential for a orphaned VcnGatewayConnection instance that does not get properly
+     * shut down.
+     *
+     * <p>Due to potential for race conditions, VcnGatewayConnections MUST only be removed from this
+     * map once they have finished tearing down, which is reported to this VCN via {@link
+     * VcnGatewayStatusCallback#onQuit()}. Once this is done, all NetworkRequests are retrieved from
+     * the NetworkProvider so that another VcnGatewayConnectionConfig can match the
+     * previously-matched request.
+     */
+    // TODO(b/182533200): remove the invariant on VcnGatewayConnection lifecycles
+    @NonNull
+    private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
+            new HashMap<>();
+
+    @NonNull private VcnConfig mConfig;
+    @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
+
+    /**
+     * The current status of this Vcn instance
+     *
+     * <p>The value will be {@link VCN_STATUS_CODE_ACTIVE} while all VcnGatewayConnections are in
+     * good standing, {@link VCN_STATUS_CODE_SAFE_MODE} if any VcnGatewayConnections are in safe
+     * mode, and {@link VCN_STATUS_CODE_INACTIVE} once a teardown has been commanded.
+     */
+    // Accessed from different threads, but always under lock in VcnManagementService
+    private volatile int mCurrentStatus = VCN_STATUS_CODE_ACTIVE;
+
+    private boolean mIsMobileDataEnabled = false;
+
+    public Vcn(
+            @NonNull VcnContext vcnContext,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull VcnConfig config,
+            @NonNull TelephonySubscriptionSnapshot snapshot,
+            @NonNull VcnCallback vcnCallback) {
+        this(vcnContext, subscriptionGroup, config, snapshot, vcnCallback, new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public Vcn(
+            @NonNull VcnContext vcnContext,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull VcnConfig config,
+            @NonNull TelephonySubscriptionSnapshot snapshot,
+            @NonNull VcnCallback vcnCallback,
+            @NonNull Dependencies deps) {
+        super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
+        mVcnContext = vcnContext;
+        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+        mVcnCallback = Objects.requireNonNull(vcnCallback, "Missing vcnCallback");
+        mDeps = Objects.requireNonNull(deps, "Missing deps");
+        mRequestListener = new VcnNetworkRequestListener();
+        mContentResolver = mDeps.newVcnContentResolver(mVcnContext);
+        mMobileDataSettingsObserver = new VcnMobileDataContentObserver(this /* handler */);
+
+        // TODO: b/364740845: Replace it with DataEnabledListener
+        final Uri uri = Settings.Global.getUriFor(SETTINGS_GLOBAL_MOBILE_DATA_STRING);
+        mContentResolver.registerContentObserver(
+                uri, true /* notifyForDescendants */, mMobileDataSettingsObserver);
+
+        mConfig = Objects.requireNonNull(config, "Missing config");
+        mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
+
+        // Update mIsMobileDataEnabled before starting handling of NetworkRequests.
+        mIsMobileDataEnabled = getMobileDataStatus();
+
+        // Register mobile data state listeners.
+        updateMobileDataStateListeners();
+
+        // Register to receive cached and future NetworkRequests
+        mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
+    }
+
+    /** Asynchronously updates the configuration and triggers a re-evaluation of Networks */
+    public void updateConfig(@NonNull VcnConfig config) {
+        Objects.requireNonNull(config, "Missing config");
+
+        sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config));
+    }
+
+    /** Asynchronously updates the Subscription snapshot for this VCN. */
+    public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
+        Objects.requireNonNull(snapshot, "Missing snapshot");
+
+        sendMessage(obtainMessage(MSG_EVENT_SUBSCRIPTIONS_CHANGED, snapshot));
+    }
+
+    /** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */
+    public void teardownAsynchronously() {
+        sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN));
+    }
+
+    /** Synchronously retrieves the current status code. */
+    public int getStatus() {
+        return mCurrentStatus;
+    }
+
+    /** Sets the status of this VCN */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public void setStatus(int status) {
+        mCurrentStatus = status;
+    }
+
+    /** Get current Gateways for testing purposes */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public Set<VcnGatewayConnection> getVcnGatewayConnections() {
+        return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values()));
+    }
+
+    /** Get current Configs and Gateways for testing purposes */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public Map<VcnGatewayConnectionConfig, VcnGatewayConnection>
+            getVcnGatewayConnectionConfigMap() {
+        return Collections.unmodifiableMap(new HashMap<>(mVcnGatewayConnections));
+    }
+
+    private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener {
+        @Override
+        public void onNetworkRequested(@NonNull NetworkRequest request) {
+            Objects.requireNonNull(request, "Missing request");
+
+            sendMessage(obtainMessage(MSG_EVENT_NETWORK_REQUESTED, request));
+        }
+    }
+
+    @Override
+    public void handleMessage(@NonNull Message msg) {
+        if (mCurrentStatus != VCN_STATUS_CODE_ACTIVE
+                && mCurrentStatus != VCN_STATUS_CODE_SAFE_MODE) {
+            return;
+        }
+
+        switch (msg.what) {
+            case MSG_EVENT_CONFIG_UPDATED:
+                handleConfigUpdated((VcnConfig) msg.obj);
+                break;
+            case MSG_EVENT_NETWORK_REQUESTED:
+                handleNetworkRequested((NetworkRequest) msg.obj);
+                break;
+            case MSG_EVENT_SUBSCRIPTIONS_CHANGED:
+                handleSubscriptionsChanged((TelephonySubscriptionSnapshot) msg.obj);
+                break;
+            case MSG_EVENT_GATEWAY_CONNECTION_QUIT:
+                handleGatewayConnectionQuit((VcnGatewayConnectionConfig) msg.obj);
+                break;
+            case MSG_EVENT_SAFE_MODE_STATE_CHANGED:
+                handleSafeModeStatusChanged();
+                break;
+            case MSG_EVENT_MOBILE_DATA_TOGGLED:
+                handleMobileDataToggled();
+                break;
+            case MSG_CMD_TEARDOWN:
+                handleTeardown();
+                break;
+            default:
+                logWtf("Unknown msg.what: " + msg.what);
+        }
+    }
+
+    private void handleConfigUpdated(@NonNull VcnConfig config) {
+        // TODO: Add a dump function in VcnConfig that omits PII. Until then, use hashCode()
+        logDbg("Config updated: old = " + mConfig.hashCode() + "; new = " + config.hashCode());
+
+        mConfig = config;
+
+        // Teardown any GatewayConnections whose configs have been removed and get all current
+        // requests
+        for (final Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
+                mVcnGatewayConnections.entrySet()) {
+            final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
+            final VcnGatewayConnection gatewayConnection = entry.getValue();
+
+            // GatewayConnectionConfigs must match exactly (otherwise authentication or
+            // connection details may have changed).
+            if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) {
+                if (gatewayConnection == null) {
+                    logWtf("Found gatewayConnectionConfig without GatewayConnection");
+                } else {
+                    logInfo(
+                            "Config updated, restarting gateway "
+                                    + gatewayConnection.getLogPrefix());
+                    gatewayConnection.teardownAsynchronously();
+                }
+            }
+        }
+
+        // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be
+        // satisfied start a new GatewayConnection)
+        mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
+    }
+
+    private void handleTeardown() {
+        logDbg("Tearing down");
+        mVcnContext.getVcnNetworkProvider().unregisterListener(mRequestListener);
+
+        for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
+            gatewayConnection.teardownAsynchronously();
+        }
+
+        // Unregister MobileDataStateListeners
+        for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) {
+            getTelephonyManager().unregisterTelephonyCallback(listener);
+        }
+        mMobileDataStateListeners.clear();
+
+        mCurrentStatus = VCN_STATUS_CODE_INACTIVE;
+    }
+
+    private void handleSafeModeStatusChanged() {
+        logVdbg("VcnGatewayConnection safe mode status changed");
+        boolean hasSafeModeGatewayConnection = false;
+
+        // If any VcnGatewayConnection is in safe mode, mark the entire VCN as being in safe mode
+        for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
+            if (gatewayConnection.isInSafeMode()) {
+                hasSafeModeGatewayConnection = true;
+                break;
+            }
+        }
+
+        final int oldStatus = mCurrentStatus;
+        mCurrentStatus =
+                hasSafeModeGatewayConnection ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
+        if (oldStatus != mCurrentStatus) {
+            mVcnCallback.onSafeModeStatusChanged(hasSafeModeGatewayConnection);
+            logInfo(
+                    "Safe mode "
+                            + (mCurrentStatus == VCN_STATUS_CODE_SAFE_MODE ? "entered" : "exited"));
+        }
+    }
+
+    private void handleNetworkRequested(@NonNull NetworkRequest request) {
+        logVdbg("Received request " + request);
+
+        // If preexisting VcnGatewayConnection(s) satisfy request, return
+        for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
+            if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
+                logVdbg("Request already satisfied by existing VcnGatewayConnection: " + request);
+                return;
+            }
+        }
+
+        // If any supported (but not running) VcnGatewayConnection(s) can satisfy request, bring it
+        // up
+        for (VcnGatewayConnectionConfig gatewayConnectionConfig :
+                mConfig.getGatewayConnectionConfigs()) {
+            if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
+                if (getExposedCapabilitiesForMobileDataState(gatewayConnectionConfig).isEmpty()) {
+                    // Skip; this network does not provide any services if mobile data is disabled.
+                    continue;
+                }
+
+                // This should never happen, by virtue of checking for the above check for
+                // pre-existing VcnGatewayConnections that satisfy a given request, but if state
+                // that affects the satsifying of requests changes, this is theoretically possible.
+                if (mVcnGatewayConnections.containsKey(gatewayConnectionConfig)) {
+                    logWtf(
+                            "Attempted to bring up VcnGatewayConnection for config "
+                                    + "with existing VcnGatewayConnection");
+                    return;
+                }
+
+                logInfo("Bringing up new VcnGatewayConnection for request " + request);
+                final VcnGatewayConnection vcnGatewayConnection =
+                        mDeps.newVcnGatewayConnection(
+                                mVcnContext,
+                                mSubscriptionGroup,
+                                mLastSnapshot,
+                                gatewayConnectionConfig,
+                                new VcnGatewayStatusCallbackImpl(gatewayConnectionConfig),
+                                mIsMobileDataEnabled);
+                mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection);
+
+                return;
+            }
+        }
+
+        logVdbg("Request could not be fulfilled by VCN: " + request);
+    }
+
+    private Set<Integer> getExposedCapabilitiesForMobileDataState(
+            VcnGatewayConnectionConfig gatewayConnectionConfig) {
+        if (mIsMobileDataEnabled) {
+            return gatewayConnectionConfig.getAllExposedCapabilities();
+        }
+
+        final Set<Integer> exposedCapsWithoutMobileData =
+                new ArraySet<>(gatewayConnectionConfig.getAllExposedCapabilities());
+        exposedCapsWithoutMobileData.removeAll(CAPS_REQUIRING_MOBILE_DATA);
+
+        return exposedCapsWithoutMobileData;
+    }
+
+    private void handleGatewayConnectionQuit(VcnGatewayConnectionConfig config) {
+        logInfo("VcnGatewayConnection quit: " + config);
+        mVcnGatewayConnections.remove(config);
+
+        // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied
+        // start a new GatewayConnection). VCN is always alive here, courtesy of the liveness check
+        // in handleMessage()
+        mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
+    }
+
+    private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
+        mLastSnapshot = snapshot;
+
+        for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
+            gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
+        }
+
+        updateMobileDataStateListeners();
+
+        // Update the mobile data state after updating the subscription snapshot as a change in
+        // subIds for a subGroup may affect the mobile data state.
+        handleMobileDataToggled();
+    }
+
+    private void updateMobileDataStateListeners() {
+        final Set<Integer> subIdsInGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup);
+        final HandlerExecutor executor = new HandlerExecutor(this);
+
+        // Register new callbacks
+        for (int subId : subIdsInGroup) {
+            if (!mMobileDataStateListeners.containsKey(subId)) {
+                final VcnUserMobileDataStateListener listener =
+                        new VcnUserMobileDataStateListener();
+
+                getTelephonyManagerForSubid(subId).registerTelephonyCallback(executor, listener);
+                mMobileDataStateListeners.put(subId, listener);
+            }
+        }
+
+        // Unregister old callbacks
+        Iterator<Entry<Integer, VcnUserMobileDataStateListener>> iterator =
+                mMobileDataStateListeners.entrySet().iterator();
+        while (iterator.hasNext()) {
+            final Entry<Integer, VcnUserMobileDataStateListener> entry = iterator.next();
+            if (!subIdsInGroup.contains(entry.getKey())) {
+                getTelephonyManager().unregisterTelephonyCallback(entry.getValue());
+                iterator.remove();
+            }
+        }
+    }
+
+    private void handleMobileDataToggled() {
+        final boolean oldMobileDataEnabledStatus = mIsMobileDataEnabled;
+        mIsMobileDataEnabled = getMobileDataStatus();
+
+        if (oldMobileDataEnabledStatus != mIsMobileDataEnabled) {
+            // Teardown any GatewayConnections that advertise INTERNET or DUN. If they provide other
+            // services, the VcnGatewayConnections will be restarted without advertising INTERNET or
+            // DUN.
+            for (Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
+                    mVcnGatewayConnections.entrySet()) {
+                final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
+                final VcnGatewayConnection gatewayConnection = entry.getValue();
+
+                final Set<Integer> exposedCaps =
+                        gatewayConnectionConfig.getAllExposedCapabilities();
+                if (exposedCaps.contains(NET_CAPABILITY_INTERNET)
+                        || exposedCaps.contains(NET_CAPABILITY_DUN)) {
+                    if (gatewayConnection == null) {
+                        logWtf("Found gatewayConnectionConfig without" + " GatewayConnection");
+                    } else {
+                        // TODO(b/184868850): Optimize by restarting NetworkAgents without teardown.
+                        gatewayConnection.teardownAsynchronously();
+                    }
+                }
+            }
+
+            // Trigger re-evaluation of all requests; mobile data state impacts supported caps.
+            mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
+
+            logInfo("Mobile data " + (mIsMobileDataEnabled ? "enabled" : "disabled"));
+        }
+    }
+
+    private boolean getMobileDataStatus() {
+        for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
+            if (getTelephonyManagerForSubid(subId).isDataEnabled()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean isRequestSatisfiedByGatewayConnectionConfig(
+            @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
+        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
+        builder.addTransportType(TRANSPORT_CELLULAR);
+        builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        for (int cap : getExposedCapabilitiesForMobileDataState(config)) {
+            builder.addCapability(cap);
+        }
+
+        return request.canBeSatisfiedBy(builder.build());
+    }
+
+    private TelephonyManager getTelephonyManager() {
+        return mVcnContext.getContext().getSystemService(TelephonyManager.class);
+    }
+
+    private TelephonyManager getTelephonyManagerForSubid(int subid) {
+        return getTelephonyManager().createForSubscriptionId(subid);
+    }
+
+    private String getLogPrefix() {
+        return "("
+                + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
+                + "-"
+                + System.identityHashCode(this)
+                + ") ";
+    }
+
+    private void logVdbg(String msg) {
+        if (VDBG) {
+            Slog.v(TAG, getLogPrefix() + msg);
+        }
+    }
+
+    private void logDbg(String msg) {
+        Slog.d(TAG, getLogPrefix() + msg);
+    }
+
+    private void logDbg(String msg, Throwable tr) {
+        Slog.d(TAG, getLogPrefix() + msg, tr);
+    }
+
+    private void logInfo(String msg) {
+        Slog.i(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg);
+    }
+
+    private void logInfo(String msg, Throwable tr) {
+        Slog.i(TAG, getLogPrefix() + msg, tr);
+        LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg + tr);
+    }
+
+    private void logErr(String msg) {
+        Slog.e(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg);
+    }
+
+    private void logErr(String msg, Throwable tr) {
+        Slog.e(TAG, getLogPrefix() + msg, tr);
+        LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg + tr);
+    }
+
+    private void logWtf(String msg) {
+        Slog.wtf(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg);
+    }
+
+    private void logWtf(String msg, Throwable tr) {
+        Slog.wtf(TAG, getLogPrefix() + msg, tr);
+        LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg + tr);
+    }
+
+    /**
+     * Dumps the state of this Vcn for logging and debugging purposes.
+     *
+     * <p>PII and credentials MUST NEVER be dumped here.
+     *
+     * <p>This method is not thread safe and MUST run on the VCN thread.
+     */
+    public void dump(IndentingPrintWriter pw) {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        pw.println("Vcn (" + mSubscriptionGroup + "):");
+        pw.increaseIndent();
+
+        pw.println("mCurrentStatus: " + mCurrentStatus);
+        pw.println("mIsMobileDataEnabled: " + mIsMobileDataEnabled);
+        pw.println();
+
+        pw.println("mVcnGatewayConnections:");
+        pw.increaseIndent();
+        for (VcnGatewayConnection gw : mVcnGatewayConnections.values()) {
+            gw.dump(pw);
+        }
+        pw.decreaseIndent();
+        pw.println();
+
+        pw.decreaseIndent();
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public boolean isMobileDataEnabled() {
+        return mIsMobileDataEnabled;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public void setMobileDataEnabled(boolean isMobileDataEnabled) {
+        mIsMobileDataEnabled = isMobileDataEnabled;
+    }
+
+    /** Retrieves the network score for a VCN Network */
+    // Package visibility for use in VcnGatewayConnection and VcnNetworkProvider
+    static NetworkScore getNetworkScore() {
+        // TODO(b/193687515): Stop setting TRANSPORT_PRIMARY, define a TRANSPORT_VCN, and set in
+        //                    NetworkOffer/NetworkAgent.
+        return new NetworkScore.Builder()
+                .setLegacyInt(VCN_LEGACY_SCORE_INT)
+                .setTransportPrimary(true)
+                .build();
+    }
+
+    /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */
+    @VisibleForTesting(visibility = Visibility.PACKAGE)
+    public interface VcnGatewayStatusCallback {
+        /** Called by a VcnGatewayConnection to indicate that it's safe mode status has changed. */
+        void onSafeModeStatusChanged();
+
+        /** Callback by a VcnGatewayConnection to indicate that an error occurred. */
+        void onGatewayConnectionError(
+                @NonNull String gatewayConnectionName,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage);
+
+        /** Called by a VcnGatewayConnection to indicate that it has fully torn down. */
+        void onQuit();
+    }
+
+    private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
+        public final VcnGatewayConnectionConfig mGatewayConnectionConfig;
+
+        VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig) {
+            mGatewayConnectionConfig = gatewayConnectionConfig;
+        }
+
+        @Override
+        public void onQuit() {
+            sendMessage(obtainMessage(MSG_EVENT_GATEWAY_CONNECTION_QUIT, mGatewayConnectionConfig));
+        }
+
+        @Override
+        public void onSafeModeStatusChanged() {
+            sendMessage(obtainMessage(MSG_EVENT_SAFE_MODE_STATE_CHANGED));
+        }
+
+        @Override
+        public void onGatewayConnectionError(
+                @NonNull String gatewayConnectionName,
+                @VcnErrorCode int errorCode,
+                @Nullable String exceptionClass,
+                @Nullable String exceptionMessage) {
+            mVcnCallback.onGatewayConnectionError(
+                    gatewayConnectionName, errorCode, exceptionClass, exceptionMessage);
+        }
+    }
+
+    private class VcnMobileDataContentObserver extends ContentObserver {
+        private VcnMobileDataContentObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
+        }
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    class VcnUserMobileDataStateListener extends TelephonyCallback
+            implements TelephonyCallback.UserMobileDataStateListener {
+
+        @Override
+        public void onUserMobileDataStateChanged(boolean enabled) {
+            sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
+        }
+    }
+
+    /** External dependencies used by Vcn, for injection in tests */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        /** Builds a new VcnGatewayConnection */
+        public VcnGatewayConnection newVcnGatewayConnection(
+                VcnContext vcnContext,
+                ParcelUuid subscriptionGroup,
+                TelephonySubscriptionSnapshot snapshot,
+                VcnGatewayConnectionConfig connectionConfig,
+                VcnGatewayStatusCallback gatewayStatusCallback,
+                boolean isMobileDataEnabled) {
+            return new VcnGatewayConnection(
+                    vcnContext,
+                    subscriptionGroup,
+                    snapshot,
+                    connectionConfig,
+                    gatewayStatusCallback,
+                    isMobileDataEnabled);
+        }
+
+        /** Builds a new VcnContentResolver instance */
+        public VcnContentResolver newVcnContentResolver(VcnContext vcnContext) {
+            return new VcnContentResolver(vcnContext);
+        }
+    }
+
+    /** Proxy Implementation of NetworkAgent, used for testing. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class VcnContentResolver {
+        private final ContentResolver mImpl;
+
+        public VcnContentResolver(VcnContext vcnContext) {
+            mImpl = vcnContext.getContext().getContentResolver();
+        }
+
+        /** Registers the content observer */
+        public void registerContentObserver(
+                @NonNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer) {
+            mImpl.registerContentObserver(uri, notifyForDescendants, observer);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnContext.java
similarity index 100%
rename from services/core/java/com/android/server/vcn/VcnContext.java
rename to packages/Vcn/service-b/src/com/android/server/vcn/VcnContext.java
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java
new file mode 100644
index 0000000..f024b5f
--- /dev/null
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/VcnGatewayConnection.java
@@ -0,0 +1,3035 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
+import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR;
+import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import static com.android.server.VcnManagementService.LOCAL_LOG;
+import static com.android.server.VcnManagementService.VDBG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.ConnectivityDiagnosticsManager;
+import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
+import android.net.ConnectivityManager;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.IpSecManager;
+import android.net.IpSecManager.IpSecTunnelInterface;
+import android.net.IpSecManager.PolicyDirection;
+import android.net.IpSecManager.ResourceUnavailableException;
+import android.net.IpSecTransform;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.net.RouteInfo;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.Uri;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.ChildSessionParams;
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionConnectionInfo;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeInternalException;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnManager;
+import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.util.LogUtils;
+import android.net.vcn.util.MtuUtils;
+import android.net.vcn.util.OneWayBoolean;
+import android.net.wifi.WifiInfo;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.Process;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
+import com.android.modules.utils.HandlerExecutor;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
+import com.android.server.vcn.routeselection.UnderlyingNetworkController;
+import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback;
+import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * A single VCN Gateway Connection, providing a single public-facing VCN network.
+ *
+ * <p>This class handles mobility events, performs retries, and tracks safe-mode conditions.
+ *
+ * <pre>Internal state transitions are as follows:
+ *
+ * +----------------------------+                 +------------------------------+
+ * |     DisconnectedState      |    Teardown or  |      DisconnectingState      |
+ * |                            |<--no available--|                              |
+ * |       Initial state.       |    underlying   | Transitive state for tearing |
+ * +----------------------------+     networks    | tearing down an IKE session. |
+ *               |                                +------------------------------+
+ *               |                                         ^          |
+ *       Underlying Network            Teardown requested  |   Not tearing down
+ *            changed               +--or retriable error--+  and has available
+ *               |                  |      occurred           underlying network
+ *               |                  ^                                 |
+ *               v                  |                                 v
+ * +----------------------------+   |             +------------------------------+
+ * |      ConnectingState       |<----------------|      RetryTimeoutState       |
+ * |                            |   |             |                              |
+ * |    Transitive state for    |   |             |     Transitive state for     |
+ * |  starting IKE negotiation. |---+             |  handling retriable errors.  |
+ * +----------------------------+   |             +------------------------------+
+ *               |                  |
+ *          IKE session             |
+ *           negotiated             |
+ *               |                  |
+ *               v                  |
+ * +----------------------------+   ^
+ * |      ConnectedState        |   |
+ * |                            |   |
+ * |     Stable state where     |   |
+ * |  gateway connection is set |   |
+ * | up, and Android Network is |   |
+ * |         connected.         |---+
+ * +----------------------------+
+ * </pre>
+ *
+ * <p>All messages in VcnGatewayConnection <b>should</b> be enqueued using {@link
+ * #sendMessageAndAcquireWakeLock}. Careful consideration should be given to any uses of {@link
+ * #sendMessage} directly, as they are not guaranteed to be processed in a timely manner (due to the
+ * lack of WakeLocks).
+ *
+ * <p>Any attempt to remove messages from the Handler should be done using {@link
+ * #removeEqualMessages}. This is necessary to ensure that the WakeLock is correctly released when
+ * no messages remain in the Handler queue.
+ *
+ * @hide
+ */
+public class VcnGatewayConnection extends StateMachine {
+    private static final String TAG = VcnGatewayConnection.class.getSimpleName();
+
+    /** Default number of parallel SAs requested */
+    static final int TUNNEL_AGGREGATION_SA_COUNT_MAX_DEFAULT = 1;
+
+    // The returned string of
+    // TelephonyManager#getNetworkTypeName(TelephonyManager.NETWORK_TYPE_UNKNOWN)
+    private static final String NETWORK_TYPE_STRING_UNKNOWN = "UNKNOWN";
+
+    // Matches DataConnection.NETWORK_TYPE private constant, and magic string from
+    // ConnectivityManager#getNetworkTypeName()
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final String NETWORK_INFO_NETWORK_TYPE_STRING = "MOBILE";
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final String NETWORK_INFO_EXTRA_INFO = "VCN";
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0");
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final String TEARDOWN_TIMEOUT_ALARM = TAG + "_TEARDOWN_TIMEOUT_ALARM";
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final String DISCONNECT_REQUEST_ALARM = TAG + "_DISCONNECT_REQUEST_ALARM";
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final String RETRY_TIMEOUT_ALARM = TAG + "_RETRY_TIMEOUT_ALARM";
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final String SAFEMODE_TIMEOUT_ALARM = TAG + "_SAFEMODE_TIMEOUT_ALARM";
+
+    private static final int[] MERGED_CAPABILITIES =
+            new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING};
+    private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE;
+
+    private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: ";
+    private static final String DISCONNECT_REASON_UNDERLYING_NETWORK_LOST =
+            "Underlying Network lost";
+    private static final String DISCONNECT_REASON_NETWORK_AGENT_UNWANTED =
+            "NetworkAgent was unwanted";
+    private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel";
+    private static final int TOKEN_ALL = Integer.MIN_VALUE;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int TEARDOWN_TIMEOUT_SECONDS = 5;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int SAFEMODE_TIMEOUT_SECONDS = 30;
+    private static final int SAFEMODE_TIMEOUT_SECONDS_TEST_MODE = 10;
+
+    private interface EventInfo {}
+
+    /**
+     * Sent when there are changes to the underlying network (per the UnderlyingNetworkController).
+     *
+     * <p>May indicate an entirely new underlying network, OR a change in network properties.
+     *
+     * <p>Relevant in ALL states.
+     *
+     * <p>In the Connected state, this MAY indicate a mobility even occurred.
+     *
+     * @param arg1 The "all" token; this event is always applicable.
+     * @param obj @NonNull An EventUnderlyingNetworkChangedInfo instance with relevant data.
+     */
+    private static final int EVENT_UNDERLYING_NETWORK_CHANGED = 1;
+
+    private static class EventUnderlyingNetworkChangedInfo implements EventInfo {
+        @Nullable public final UnderlyingNetworkRecord newUnderlying;
+
+        EventUnderlyingNetworkChangedInfo(@Nullable UnderlyingNetworkRecord newUnderlying) {
+            this.newUnderlying = newUnderlying;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(newUnderlying);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof EventUnderlyingNetworkChangedInfo)) {
+                return false;
+            }
+
+            final EventUnderlyingNetworkChangedInfo rhs = (EventUnderlyingNetworkChangedInfo) other;
+            return Objects.equals(newUnderlying, rhs.newUnderlying);
+        }
+    }
+
+    /**
+     * Sent (delayed) to trigger an attempt to reestablish the tunnel.
+     *
+     * <p>Only relevant in the Retry-timeout state, discarded in all other states.
+     *
+     * <p>Upon receipt of this signal, the state machine will transition from the Retry-timeout
+     * state to the Connecting state.
+     *
+     * @param arg1 The "all" token; no sessions are active in the RetryTimeoutState.
+     */
+    private static final int EVENT_RETRY_TIMEOUT_EXPIRED = 2;
+
+    /**
+     * Sent when a gateway connection has been lost, either due to a IKE or child failure.
+     *
+     * <p>Relevant in all states that have an IKE session.
+     *
+     * <p>Upon receipt of this signal, the state machine will (unless loss of the session is
+     * expected) transition to the Disconnecting state, to ensure IKE session closure before
+     * retrying, or fully shutting down.
+     *
+     * @param arg1 The session token for the IKE Session that was lost, used to prevent out-of-date
+     *     signals from propagating.
+     * @param obj @NonNull An EventSessionLostInfo instance with relevant data.
+     */
+    private static final int EVENT_SESSION_LOST = 3;
+
+    private static class EventSessionLostInfo implements EventInfo {
+        @Nullable public final Exception exception;
+
+        EventSessionLostInfo(@NonNull Exception exception) {
+            this.exception = exception;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(exception);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof EventSessionLostInfo)) {
+                return false;
+            }
+
+            final EventSessionLostInfo rhs = (EventSessionLostInfo) other;
+            return Objects.equals(exception, rhs.exception);
+        }
+    }
+
+    /**
+     * Sent when an IKE session has completely closed.
+     *
+     * <p>Relevant only in the Disconnecting State, used to identify that a session being torn down
+     * was fully closed. If this event is not fired within a timely fashion, the IKE session will be
+     * forcibly terminated.
+     *
+     * <p>Upon receipt of this signal, the state machine will (unless closure of the session is
+     * expected) transition to the Disconnected or RetryTimeout states, depending on whether the
+     * GatewayConnection is being fully torn down.
+     *
+     * @param arg1 The session token for the IKE Session that was lost, used to prevent out-of-date
+     *     signals from propagating.
+     * @param obj @NonNull An EventSessionLostInfo instance with relevant data.
+     */
+    private static final int EVENT_SESSION_CLOSED = 4;
+
+    /**
+     * Sent when an IKE Child Transform was created, and should be applied to the tunnel.
+     *
+     * <p>Only relevant in the Connecting, Connected and Migrating states. This callback MUST be
+     * handled in the Connected or Migrating states, and should be deferred if necessary.
+     *
+     * @param arg1 The session token for the IKE Session that had a new child created, used to
+     *     prevent out-of-date signals from propagating.
+     * @param obj @NonNull An EventTransformCreatedInfo instance with relevant data.
+     */
+    private static final int EVENT_TRANSFORM_CREATED = 5;
+
+    private static class EventTransformCreatedInfo implements EventInfo {
+        @PolicyDirection public final int direction;
+        @NonNull public final IpSecTransform transform;
+
+        EventTransformCreatedInfo(
+                @PolicyDirection int direction, @NonNull IpSecTransform transform) {
+            this.direction = direction;
+            this.transform = Objects.requireNonNull(transform);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(direction, transform);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof EventTransformCreatedInfo)) {
+                return false;
+            }
+
+            final EventTransformCreatedInfo rhs = (EventTransformCreatedInfo) other;
+            return direction == rhs.direction && Objects.equals(transform, rhs.transform);
+        }
+    }
+
+    /**
+     * Sent when an IKE Child Session was completely opened and configured successfully.
+     *
+     * <p>Only relevant in the Connected and Migrating states.
+     *
+     * @param arg1 The session token for the IKE Session for which a child was opened and configured
+     *     successfully, used to prevent out-of-date signals from propagating.
+     * @param obj @NonNull An EventSetupCompletedInfo instance with relevant data.
+     */
+    private static final int EVENT_SETUP_COMPLETED = 6;
+
+    private static class EventSetupCompletedInfo implements EventInfo {
+        @NonNull public final VcnChildSessionConfiguration childSessionConfig;
+
+        EventSetupCompletedInfo(@NonNull VcnChildSessionConfiguration childSessionConfig) {
+            this.childSessionConfig = Objects.requireNonNull(childSessionConfig);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(childSessionConfig);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof EventSetupCompletedInfo)) {
+                return false;
+            }
+
+            final EventSetupCompletedInfo rhs = (EventSetupCompletedInfo) other;
+            return Objects.equals(childSessionConfig, rhs.childSessionConfig);
+        }
+    }
+
+    /**
+     * Sent when conditions (internal or external) require a disconnect.
+     *
+     * <p>Relevant in all states except the Disconnected state.
+     *
+     * <p>This signal is often fired with a timeout in order to prevent disconnecting during
+     * transient conditions, such as network switches. Upon the transient passing, the signal is
+     * canceled based on the disconnect reason.
+     *
+     * <p>Upon receipt of this signal, the state machine MUST tear down all active sessions, cancel
+     * any pending work items, and move to the Disconnected state.
+     *
+     * @param arg1 The "all" token; this signal is always honored.
+     * @param obj @NonNull An EventDisconnectRequestedInfo instance with relevant data.
+     */
+    private static final int EVENT_DISCONNECT_REQUESTED = 7;
+
+    private static class EventDisconnectRequestedInfo implements EventInfo {
+        /** The reason why the disconnect was requested. */
+        @NonNull public final String reason;
+
+        public final boolean shouldQuit;
+
+        EventDisconnectRequestedInfo(@NonNull String reason, boolean shouldQuit) {
+            this.reason = Objects.requireNonNull(reason);
+            this.shouldQuit = shouldQuit;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(reason, shouldQuit);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof EventDisconnectRequestedInfo)) {
+                return false;
+            }
+
+            final EventDisconnectRequestedInfo rhs = (EventDisconnectRequestedInfo) other;
+            return reason.equals(rhs.reason) && shouldQuit == rhs.shouldQuit;
+        }
+    }
+
+    /**
+     * Sent (delayed) to trigger a forcible close of an IKE session.
+     *
+     * <p>Only relevant in the Disconnecting state, discarded in all other states.
+     *
+     * <p>Upon receipt of this signal, the state machine will transition from the Disconnecting
+     * state to the Disconnected state.
+     *
+     * @param arg1 The session token for the IKE Session that is being torn down, used to prevent
+     *     out-of-date signals from propagating.
+     */
+    private static final int EVENT_TEARDOWN_TIMEOUT_EXPIRED = 8;
+
+    /**
+     * Sent when this VcnGatewayConnection is notified of a change in TelephonySubscriptions.
+     *
+     * <p>Relevant in all states.
+     *
+     * @param arg1 The "all" token; this signal is always honored.
+     */
+    // TODO(b/178426520): implement handling of this event
+    private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9;
+
+    /**
+     * Sent when this VcnGatewayConnection has entered safe mode.
+     *
+     * <p>A VcnGatewayConnection enters safe mode when it takes over {@link
+     * #SAFEMODE_TIMEOUT_SECONDS} to enter {@link ConnectedState}.
+     *
+     * <p>When a VcnGatewayConnection enters safe mode, it will fire {@link
+     * VcnGatewayStatusCallback#onEnteredSafeMode()} to notify its Vcn. The Vcn will then shut down
+     * its VcnGatewayConnectin(s).
+     *
+     * <p>Relevant in DisconnectingState, ConnectingState, ConnectedState (if the Vcn Network is not
+     * validated yet), and RetryTimeoutState.
+     *
+     * @param arg1 The "all" token; this signal is always honored.
+     */
+    private static final int EVENT_SAFE_MODE_TIMEOUT_EXCEEDED = 10;
+
+    /**
+     * Sent when an IKE has completed migration, and created updated transforms for application.
+     *
+     * <p>Only relevant in the Connected state.
+     *
+     * @param arg1 The session token for the IKE Session that completed migration, used to prevent
+     *     out-of-date signals from propagating.
+     * @param obj @NonNull An EventMigrationCompletedInfo instance with relevant data.
+     */
+    private static final int EVENT_MIGRATION_COMPLETED = 11;
+
+    private static class EventMigrationCompletedInfo implements EventInfo {
+        @NonNull public final IpSecTransform inTransform;
+        @NonNull public final IpSecTransform outTransform;
+
+        EventMigrationCompletedInfo(
+                @NonNull IpSecTransform inTransform, @NonNull IpSecTransform outTransform) {
+            this.inTransform = Objects.requireNonNull(inTransform);
+            this.outTransform = Objects.requireNonNull(outTransform);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(inTransform, outTransform);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof EventMigrationCompletedInfo)) {
+                return false;
+            }
+
+            final EventMigrationCompletedInfo rhs = (EventMigrationCompletedInfo) other;
+            return Objects.equals(inTransform, rhs.inTransform)
+                    && Objects.equals(outTransform, rhs.outTransform);
+        }
+    }
+
+    /**
+     * Sent when an IKE session connection information has changed.
+     *
+     * <p>This signal is always fired before EVENT_SETUP_COMPLETED and EVENT_MIGRATION_COMPLETED.
+     *
+     * <p>Only relevant in the Connecting and Connected state.
+     *
+     * @param arg1 The session token for the IKE Session whose connection information has changed,
+     *     used to prevent out-of-date signals from propagating.
+     * @param obj @NonNull An EventIkeConnectionInfoChangedInfo instance with relevant data.
+     */
+    private static final int EVENT_IKE_CONNECTION_INFO_CHANGED = 12;
+
+    private static class EventIkeConnectionInfoChangedInfo implements EventInfo {
+        @NonNull public final IkeSessionConnectionInfo ikeConnectionInfo;
+
+        EventIkeConnectionInfoChangedInfo(@NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
+            this.ikeConnectionInfo = ikeConnectionInfo;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(ikeConnectionInfo);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof EventIkeConnectionInfoChangedInfo)) {
+                return false;
+            }
+
+            final EventIkeConnectionInfoChangedInfo rhs = (EventIkeConnectionInfoChangedInfo) other;
+            return Objects.equals(ikeConnectionInfo, rhs.ikeConnectionInfo);
+        }
+    }
+
+    /**
+     * Sent when there is a suspected data stall on a network
+     *
+     * <p>Only relevant in the Connected state.
+     *
+     * @param arg1 The "all" token; this signal is always honored.
+     * @param obj @NonNull An EventDataStallSuspectedInfo instance with relevant data.
+     */
+    private static final int EVENT_DATA_STALL_SUSPECTED = 13;
+
+    private static class EventDataStallSuspectedInfo implements EventInfo {
+        @NonNull public final Network network;
+
+        EventDataStallSuspectedInfo(@NonNull Network network) {
+            this.network = network;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(network);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof EventDataStallSuspectedInfo)) {
+                return false;
+            }
+
+            final EventDataStallSuspectedInfo rhs = (EventDataStallSuspectedInfo) other;
+            return Objects.equals(network, rhs.network);
+        }
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @NonNull
+    final DisconnectedState mDisconnectedState = new DisconnectedState();
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @NonNull
+    final DisconnectingState mDisconnectingState = new DisconnectingState();
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @NonNull
+    final ConnectingState mConnectingState = new ConnectingState();
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @NonNull
+    final ConnectedState mConnectedState = new ConnectedState();
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @NonNull
+    final RetryTimeoutState mRetryTimeoutState = new RetryTimeoutState();
+
+    @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
+
+    @NonNull private final VcnContext mVcnContext;
+    @NonNull private final ParcelUuid mSubscriptionGroup;
+    @NonNull private final UnderlyingNetworkController mUnderlyingNetworkController;
+    @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
+    @NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback;
+    @NonNull private final Dependencies mDeps;
+
+    @NonNull
+    private final VcnUnderlyingNetworkControllerCallback mUnderlyingNetworkControllerCallback;
+
+    @NonNull private final VcnConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback;
+
+    private final boolean mIsMobileDataEnabled;
+
+    @NonNull private final IpSecManager mIpSecManager;
+    @NonNull private final ConnectivityManager mConnectivityManager;
+    @NonNull private final ConnectivityDiagnosticsManager mConnectivityDiagnosticsManager;
+
+    @Nullable private IpSecTunnelInterface mTunnelIface = null;
+
+    /**
+     * WakeLock to be held when processing messages on the Handler queue.
+     *
+     * <p>Used to prevent the device from going to sleep while there are VCN-related events to
+     * process for this VcnGatewayConnection.
+     *
+     * <p>Obtain a WakeLock when enquing messages onto the Handler queue. Once all messages in the
+     * Handler queue have been processed, the WakeLock can be released and cleared.
+     *
+     * <p>This WakeLock is also used for handling delayed messages by using WakeupMessages to send
+     * delayed messages to the Handler. When the WakeupMessage fires, it will obtain the WakeLock
+     * before enquing the delayed event to the Handler.
+     */
+    @NonNull private final VcnWakeLock mWakeLock;
+
+    /**
+     * Whether the VcnGatewayConnection is in the process of irreversibly quitting.
+     *
+     * <p>This variable is false for the lifecycle of the VcnGatewayConnection, until a command to
+     * teardown has been received. This may be flipped due to events such as the Network becoming
+     * unwanted, the owning VCN entering safe mode, or an irrecoverable internal failure.
+     *
+     * <p>WARNING: Assignments to this MUST ALWAYS (except for testing) use the or operator ("|="),
+     * otherwise the flag may be flipped back to false after having been set to true. This could
+     * lead to a case where the Vcn parent instance has commanded a teardown, but a spurious
+     * non-quitting disconnect request could flip this back to true.
+     */
+    private OneWayBoolean mIsQuitting = new OneWayBoolean();
+
+    /**
+     * Whether the VcnGatewayConnection is in safe mode.
+     *
+     * <p>Upon hitting the safe mode timeout, this will be set to {@code true}. In safe mode, this
+     * VcnGatewayConnection will continue attempting to connect, and if a successful connection is
+     * made, safe mode will be exited.
+     */
+    private boolean mIsInSafeMode = false;
+
+    /**
+     * The token used by the primary/current/active session.
+     *
+     * <p>This token MUST be updated when a new stateful/async session becomes the
+     * primary/current/active session. Example cases where the session changes are:
+     *
+     * <ul>
+     *   <li>Switching to an IKE session as the primary session
+     * </ul>
+     *
+     * <p>In the migrating state, where two sessions may be active, this value MUST represent the
+     * primary session. This is USUALLY the existing session, and is only switched to the new
+     * session when:
+     *
+     * <ul>
+     *   <li>The new session connects successfully, and becomes the primary session
+     *   <li>The existing session is lost, and the remaining (new) session becomes the primary
+     *       session
+     * </ul>
+     */
+    private int mCurrentToken = -1;
+
+    /**
+     * The number of unsuccessful attempts since the last successful connection.
+     *
+     * <p>This number MUST be incremented each time the RetryTimeout state is entered, and cleared
+     * each time the Connected state is entered.
+     */
+    private int mFailedAttempts = 0;
+
+    /**
+     * The current underlying network.
+     *
+     * <p>Set in any states, always @NonNull in all states except Disconnected, null otherwise.
+     */
+    private UnderlyingNetworkRecord mUnderlying;
+
+    /**
+     * The current IKE Session connection information
+     *
+     * <p>Set in Connected and Migrating states, always @NonNull in Connected, Migrating
+     * states, @Nullable otherwise.
+     */
+    private IkeSessionConnectionInfo mIkeConnectionInfo;
+
+    /**
+     * The active IKE session.
+     *
+     * <p>Set in Connecting or Migrating States, always @NonNull in Connecting, Connected, and
+     * Migrating states, null otherwise.
+     */
+    private VcnIkeSession mIkeSession;
+
+    /**
+     * The last known child configuration.
+     *
+     * <p>Set in Connected and Migrating states, always @NonNull in Connected, Migrating
+     * states, @Nullable otherwise.
+     */
+    private VcnChildSessionConfiguration mChildConfig;
+
+    /**
+     * The active network agent.
+     *
+     * <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable
+     * otherwise.
+     */
+    private VcnNetworkAgent mNetworkAgent;
+
+    @Nullable private WakeupMessage mTeardownTimeoutAlarm;
+    @Nullable private WakeupMessage mDisconnectRequestAlarm;
+    @Nullable private WakeupMessage mRetryTimeoutAlarm;
+    @Nullable private WakeupMessage mSafeModeTimeoutAlarm;
+
+    public VcnGatewayConnection(
+            @NonNull VcnContext vcnContext,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot snapshot,
+            @NonNull VcnGatewayConnectionConfig connectionConfig,
+            @NonNull VcnGatewayStatusCallback gatewayStatusCallback,
+            boolean isMobileDataEnabled) {
+        this(
+                vcnContext,
+                subscriptionGroup,
+                snapshot,
+                connectionConfig,
+                gatewayStatusCallback,
+                isMobileDataEnabled,
+                new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    VcnGatewayConnection(
+            @NonNull VcnContext vcnContext,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot snapshot,
+            @NonNull VcnGatewayConnectionConfig connectionConfig,
+            @NonNull VcnGatewayStatusCallback gatewayStatusCallback,
+            boolean isMobileDataEnabled,
+            @NonNull Dependencies deps) {
+        super(TAG, Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
+        mVcnContext = vcnContext;
+        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+        mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
+        mGatewayStatusCallback =
+                Objects.requireNonNull(gatewayStatusCallback, "Missing gatewayStatusCallback");
+        mIsMobileDataEnabled = isMobileDataEnabled;
+        mDeps = Objects.requireNonNull(deps, "Missing deps");
+
+        mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
+
+        mUnderlyingNetworkControllerCallback = new VcnUnderlyingNetworkControllerCallback();
+
+        mWakeLock =
+                mDeps.newWakeLock(mVcnContext.getContext(), PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
+        mUnderlyingNetworkController =
+                mDeps.newUnderlyingNetworkController(
+                        mVcnContext,
+                        mConnectionConfig,
+                        subscriptionGroup,
+                        mLastSnapshot,
+                        mUnderlyingNetworkControllerCallback);
+        mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
+        mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
+        mConnectivityDiagnosticsManager =
+                mVcnContext.getContext().getSystemService(ConnectivityDiagnosticsManager.class);
+
+        mConnectivityDiagnosticsCallback = new VcnConnectivityDiagnosticsCallback();
+
+        if (mConnectionConfig.hasGatewayOption(
+                VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY)) {
+            final NetworkRequest diagRequest =
+                    new NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build();
+            mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(
+                    diagRequest,
+                    new HandlerExecutor(new Handler(vcnContext.getLooper())),
+                    mConnectivityDiagnosticsCallback);
+        }
+
+        addState(mDisconnectedState);
+        addState(mDisconnectingState);
+        addState(mConnectingState);
+        addState(mConnectedState);
+        addState(mRetryTimeoutState);
+
+        setInitialState(mDisconnectedState);
+        setDbg(VDBG);
+        start();
+    }
+
+    /** Queries whether this VcnGatewayConnection is in safe mode. */
+    public boolean isInSafeMode() {
+        // Accessing internal state; must only be done on looper thread.
+        mVcnContext.ensureRunningOnLooperThread();
+
+        return mIsInSafeMode;
+    }
+
+    /**
+     * Asynchronously tears down this GatewayConnection, and any resources used.
+     *
+     * <p>Once torn down, this VcnTunnel CANNOT be started again.
+     */
+    public void teardownAsynchronously() {
+        logDbg("Triggering async teardown");
+        sendDisconnectRequestedAndAcquireWakelock(
+                DISCONNECT_REASON_TEARDOWN, true /* shouldQuit */);
+    }
+
+    @Override
+    protected void onQuitting() {
+        logInfo("Quitting VcnGatewayConnection");
+
+        if (mNetworkAgent != null) {
+            logWtf("NetworkAgent was non-null in onQuitting");
+            mNetworkAgent.unregister();
+            mNetworkAgent = null;
+        }
+
+        if (mIkeSession != null) {
+            logWtf("IkeSession was non-null in onQuitting");
+            mIkeSession.kill();
+            mIkeSession = null;
+        }
+
+        // No need to call setInterfaceDown(); the IpSecInterface is being fully torn down.
+        if (mTunnelIface != null) {
+            mTunnelIface.close();
+        }
+
+        releaseWakeLock();
+
+        cancelTeardownTimeoutAlarm();
+        cancelDisconnectRequestAlarm();
+        cancelRetryTimeoutAlarm();
+        cancelSafeModeAlarm();
+
+        mUnderlyingNetworkController.teardown();
+
+        mGatewayStatusCallback.onQuit();
+
+        mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
+                mConnectivityDiagnosticsCallback);
+    }
+
+    /**
+     * Notify this Gateway that subscriptions have changed.
+     *
+     * <p>This snapshot should be used to update any keepalive requests necessary for potential
+     * underlying Networks in this Gateway's subscription group.
+     */
+    public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
+        Objects.requireNonNull(snapshot, "Missing snapshot");
+        mVcnContext.ensureRunningOnLooperThread();
+
+        mLastSnapshot = snapshot;
+        mUnderlyingNetworkController.updateSubscriptionSnapshot(mLastSnapshot);
+
+        sendMessageAndAcquireWakeLock(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
+    }
+
+    private class VcnConnectivityDiagnosticsCallback extends ConnectivityDiagnosticsCallback {
+        @Override
+        public void onDataStallSuspected(ConnectivityDiagnosticsManager.DataStallReport report) {
+            mVcnContext.ensureRunningOnLooperThread();
+
+            final Network network = report.getNetwork();
+            logInfo("Data stall suspected on " + network);
+            sendMessageAndAcquireWakeLock(
+                    EVENT_DATA_STALL_SUSPECTED,
+                    TOKEN_ALL,
+                    new EventDataStallSuspectedInfo(network));
+        }
+    }
+
+    private class VcnUnderlyingNetworkControllerCallback
+            implements UnderlyingNetworkControllerCallback {
+        @Override
+        public void onSelectedUnderlyingNetworkChanged(
+                @Nullable UnderlyingNetworkRecord underlying) {
+            // TODO(b/180132994): explore safely removing this Thread check
+            mVcnContext.ensureRunningOnLooperThread();
+
+            if (!UnderlyingNetworkRecord.isSameNetwork(mUnderlying, underlying)) {
+                logInfo(
+                        "Selected underlying network changed: "
+                                + (underlying == null ? null : underlying.network));
+            }
+
+            // TODO(b/179091925): Move the delayed-message handling to BaseState
+
+            // If underlying is null, all underlying networks have been lost. Disconnect VCN after a
+            // timeout (or immediately if in airplane mode, since the device user has indicated that
+            // the radios should all be turned off).
+            if (underlying == null) {
+                if (mDeps.isAirplaneModeOn(mVcnContext)) {
+                    sendMessageAndAcquireWakeLock(
+                            EVENT_UNDERLYING_NETWORK_CHANGED,
+                            TOKEN_ALL,
+                            new EventUnderlyingNetworkChangedInfo(null));
+                    sendDisconnectRequestedAndAcquireWakelock(
+                            DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */);
+                    return;
+                }
+
+                setDisconnectRequestAlarm();
+            } else {
+                // Received a new Network so any previous alarm is irrelevant - cancel + clear it,
+                // and cancel any queued EVENT_DISCONNECT_REQUEST messages
+                cancelDisconnectRequestAlarm();
+            }
+
+            sendMessageAndAcquireWakeLock(
+                    EVENT_UNDERLYING_NETWORK_CHANGED,
+                    TOKEN_ALL,
+                    new EventUnderlyingNetworkChangedInfo(underlying));
+        }
+    }
+
+    private void acquireWakeLock() {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        if (!mIsQuitting.getValue()) {
+            mWakeLock.acquire();
+
+            logVdbg("Wakelock acquired: " + mWakeLock);
+        }
+    }
+
+    private void releaseWakeLock() {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        mWakeLock.release();
+
+        logVdbg("Wakelock released: " + mWakeLock);
+    }
+
+    /**
+     * Attempt to release mWakeLock - this can only be done if the Handler is null (meaning the
+     * StateMachine has been shutdown and thus has no business keeping the WakeLock) or if there are
+     * no more messags left to process in the Handler queue (at which point the WakeLock can be
+     * released until more messages must be processed).
+     */
+    private void maybeReleaseWakeLock() {
+        final Handler handler = getHandler();
+        if (handler == null || !handler.hasMessagesOrCallbacks()) {
+            releaseWakeLock();
+        }
+    }
+
+    @Override
+    public void sendMessage(int what) {
+        logWtf(
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what);
+    }
+
+    @Override
+    public void sendMessage(int what, Object obj) {
+        logWtf(
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, obj);
+    }
+
+    @Override
+    public void sendMessage(int what, int arg1) {
+        logWtf(
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, arg1);
+    }
+
+    @Override
+    public void sendMessage(int what, int arg1, int arg2) {
+        logWtf(
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, arg1, arg2);
+    }
+
+    @Override
+    public void sendMessage(int what, int arg1, int arg2, Object obj) {
+        logWtf(
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, arg1, arg2, obj);
+    }
+
+    @Override
+    public void sendMessage(Message msg) {
+        logWtf(
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(msg);
+    }
+
+    // TODO(b/180146061): also override and Log.wtf() other Message handling methods
+    // In mind are sendMessageDelayed(), sendMessageAtFrontOfQueue, removeMessages, and
+    // removeDeferredMessages
+
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(int what, int token) {
+        acquireWakeLock();
+        super.sendMessage(what, token);
+    }
+
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(int what, int token, EventInfo data) {
+        acquireWakeLock();
+        super.sendMessage(what, token, ARG_NOT_PRESENT, data);
+    }
+
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(int what, int token, int arg2, EventInfo data) {
+        acquireWakeLock();
+        super.sendMessage(what, token, arg2, data);
+    }
+
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(Message msg) {
+        acquireWakeLock();
+        super.sendMessage(msg);
+    }
+
+    /**
+     * Removes all messages matching the given parameters, and attempts to release mWakeLock if the
+     * Handler is empty.
+     *
+     * @param what the Message.what value to be removed
+     */
+    private void removeEqualMessages(int what) {
+        removeEqualMessages(what, null /* obj */);
+    }
+
+    /**
+     * Removes all messages matching the given parameters, and attempts to release mWakeLock if the
+     * Handler is empty.
+     *
+     * @param what the Message.what value to be removed
+     * @param obj the Message.obj to to be removed, or null if all messages matching Message.what
+     *     should be removed
+     */
+    private void removeEqualMessages(int what, @Nullable Object obj) {
+        final Handler handler = getHandler();
+        if (handler != null) {
+            handler.removeEqualMessages(what, obj);
+        }
+
+        maybeReleaseWakeLock();
+    }
+
+    private WakeupMessage createScheduledAlarm(
+            @NonNull String cmdName, Message delayedMessage, long delay) {
+        final Handler handler = getHandler();
+        if (handler == null) {
+            logWarn(
+                    "Attempted to schedule alarm after StateMachine has quit",
+                    new IllegalStateException());
+            return null; // StateMachine has already quit.
+        }
+
+        // WakeupMessage uses Handler#dispatchMessage() to immediately handle the specified Runnable
+        // at the scheduled time. dispatchMessage() immediately executes and there may be queued
+        // events that resolve the scheduled alarm pending in the queue. So, use the Runnable to
+        // place the alarm event at the end of the queue with sendMessageAndAcquireWakeLock (which
+        // guarantees the device will stay awake).
+        final WakeupMessage alarm =
+                mDeps.newWakeupMessage(
+                        mVcnContext,
+                        handler,
+                        cmdName,
+                        () -> sendMessageAndAcquireWakeLock(delayedMessage));
+        alarm.schedule(mDeps.getElapsedRealTime() + delay);
+        return alarm;
+    }
+
+    private void setTeardownTimeoutAlarm() {
+        logVdbg("Setting teardown timeout alarm; mCurrentToken: " + mCurrentToken);
+
+        // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
+        // either case, there is nothing to cancel.
+        if (mTeardownTimeoutAlarm != null) {
+            logWtf(
+                    "mTeardownTimeoutAlarm should be null before being set; mCurrentToken: "
+                            + mCurrentToken);
+        }
+
+        final Message delayedMessage = obtainMessage(EVENT_TEARDOWN_TIMEOUT_EXPIRED, mCurrentToken);
+        mTeardownTimeoutAlarm =
+                createScheduledAlarm(
+                        TEARDOWN_TIMEOUT_ALARM,
+                        delayedMessage,
+                        TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+    }
+
+    private void cancelTeardownTimeoutAlarm() {
+        logVdbg("Cancelling teardown timeout alarm; mCurrentToken: " + mCurrentToken);
+
+        if (mTeardownTimeoutAlarm != null) {
+            mTeardownTimeoutAlarm.cancel();
+            mTeardownTimeoutAlarm = null;
+        }
+
+        // Cancel any existing teardown timeouts
+        removeEqualMessages(EVENT_TEARDOWN_TIMEOUT_EXPIRED);
+    }
+
+    private void setDisconnectRequestAlarm() {
+        logVdbg(
+                "Setting alarm to disconnect due to underlying network loss;"
+                        + " mCurrentToken: "
+                        + mCurrentToken);
+
+        // Only schedule a NEW alarm if none is already set.
+        if (mDisconnectRequestAlarm != null) {
+            return;
+        }
+
+        final Message delayedMessage =
+                obtainMessage(
+                        EVENT_DISCONNECT_REQUESTED,
+                        TOKEN_ALL,
+                        0 /* arg2 */,
+                        new EventDisconnectRequestedInfo(
+                                DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */));
+        mDisconnectRequestAlarm =
+                createScheduledAlarm(
+                        DISCONNECT_REQUEST_ALARM,
+                        delayedMessage,
+                        TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS));
+    }
+
+    private void cancelDisconnectRequestAlarm() {
+        logVdbg(
+                "Cancelling alarm to disconnect due to underlying network loss;"
+                        + " mCurrentToken: "
+                        + mCurrentToken);
+
+        if (mDisconnectRequestAlarm != null) {
+            mDisconnectRequestAlarm.cancel();
+            mDisconnectRequestAlarm = null;
+        }
+
+        // Cancel any existing disconnect due to previous loss of underlying network
+        removeEqualMessages(
+                EVENT_DISCONNECT_REQUESTED,
+                new EventDisconnectRequestedInfo(
+                        DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */));
+    }
+
+    private void setRetryTimeoutAlarm(long delay) {
+        logVdbg("Setting retry alarm; mCurrentToken: " + mCurrentToken);
+
+        // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
+        // either case, there is nothing to cancel.
+        if (mRetryTimeoutAlarm != null) {
+            logWtf(
+                    "mRetryTimeoutAlarm should be null before being set; mCurrentToken: "
+                            + mCurrentToken);
+        }
+
+        final Message delayedMessage = obtainMessage(EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken);
+        mRetryTimeoutAlarm = createScheduledAlarm(RETRY_TIMEOUT_ALARM, delayedMessage, delay);
+    }
+
+    private void cancelRetryTimeoutAlarm() {
+        logVdbg("Cancel retry alarm; mCurrentToken: " + mCurrentToken);
+
+        if (mRetryTimeoutAlarm != null) {
+            mRetryTimeoutAlarm.cancel();
+            mRetryTimeoutAlarm = null;
+        }
+
+        removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setSafeModeAlarm() {
+        final boolean isFlagSafeModeConfigEnabled = mVcnContext.getFeatureFlags().safeModeConfig();
+        logVdbg("isFlagSafeModeConfigEnabled " + isFlagSafeModeConfigEnabled);
+
+        if (isFlagSafeModeConfigEnabled && !mConnectionConfig.isSafeModeEnabled()) {
+            logVdbg("setSafeModeAlarm: safe mode disabled");
+            return;
+        }
+
+        logVdbg("Setting safe mode alarm; mCurrentToken: " + mCurrentToken);
+
+        // Only schedule a NEW alarm if none is already set.
+        if (mSafeModeTimeoutAlarm != null) {
+            return;
+        }
+
+        final Message delayedMessage = obtainMessage(EVENT_SAFE_MODE_TIMEOUT_EXCEEDED, TOKEN_ALL);
+        mSafeModeTimeoutAlarm =
+                createScheduledAlarm(
+                        SAFEMODE_TIMEOUT_ALARM,
+                        delayedMessage,
+                        getSafeModeTimeoutMs(mVcnContext, mLastSnapshot, mSubscriptionGroup));
+    }
+
+    /** Gets the safe mode timeout */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static long getSafeModeTimeoutMs(
+            VcnContext vcnContext, TelephonySubscriptionSnapshot snapshot, ParcelUuid subGrp) {
+        final int defaultSeconds =
+                vcnContext.isInTestMode()
+                        ? SAFEMODE_TIMEOUT_SECONDS_TEST_MODE
+                        : SAFEMODE_TIMEOUT_SECONDS;
+
+        final PersistableBundleWrapper carrierConfig = snapshot.getCarrierConfigForSubGrp(subGrp);
+        int resultSeconds = defaultSeconds;
+
+        if (carrierConfig != null) {
+            resultSeconds =
+                    carrierConfig.getInt(
+                            VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, defaultSeconds);
+        }
+
+        return TimeUnit.SECONDS.toMillis(resultSeconds);
+    }
+
+    private void cancelSafeModeAlarm() {
+        logVdbg("Cancel safe mode alarm; mCurrentToken: " + mCurrentToken);
+
+        if (mSafeModeTimeoutAlarm != null) {
+            mSafeModeTimeoutAlarm.cancel();
+            mSafeModeTimeoutAlarm = null;
+        }
+
+        removeEqualMessages(EVENT_SAFE_MODE_TIMEOUT_EXCEEDED);
+    }
+
+    private void sessionLostWithoutCallback(int token, @Nullable Exception exception) {
+        sendMessageAndAcquireWakeLock(
+                EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
+    }
+
+    private void sessionLost(int token, @Nullable Exception exception) {
+        // Only notify mGatewayStatusCallback if the session was lost with an error. All
+        // authentication and DNS failures are sent through
+        // IkeSessionCallback.onClosedExceptionally(), which calls sessionClosed()
+        if (exception != null) {
+            mGatewayStatusCallback.onGatewayConnectionError(
+                    mConnectionConfig.getGatewayConnectionName(),
+                    VCN_ERROR_CODE_INTERNAL_ERROR,
+                    RuntimeException.class.getName(),
+                    "Received "
+                            + exception.getClass().getSimpleName()
+                            + " with message: "
+                            + exception.getMessage());
+        }
+
+        sessionLostWithoutCallback(token, exception);
+    }
+
+    private static boolean isIkeAuthFailure(@NonNull Exception exception) {
+        if (!(exception instanceof IkeProtocolException)) {
+            return false;
+        }
+
+        return ((IkeProtocolException) exception).getErrorType()
+                == ERROR_TYPE_AUTHENTICATION_FAILED;
+    }
+
+    private void notifyStatusCallbackForSessionClosed(@NonNull Exception exception) {
+        final int errorCode;
+        final String exceptionClass;
+        final String exceptionMessage;
+
+        if (isIkeAuthFailure(exception)) {
+            errorCode = VCN_ERROR_CODE_CONFIG_ERROR;
+            exceptionClass = exception.getClass().getName();
+            exceptionMessage = exception.getMessage();
+        } else if (exception instanceof IkeInternalException
+                && exception.getCause() instanceof IOException) {
+            errorCode = VCN_ERROR_CODE_NETWORK_ERROR;
+            exceptionClass = IOException.class.getName();
+            exceptionMessage = exception.getCause().getMessage();
+        } else {
+            errorCode = VCN_ERROR_CODE_INTERNAL_ERROR;
+            exceptionClass = RuntimeException.class.getName();
+            exceptionMessage =
+                    "Received "
+                            + exception.getClass().getSimpleName()
+                            + " with message: "
+                            + exception.getMessage();
+        }
+
+        logDbg(
+                "Encountered error; code="
+                        + errorCode
+                        + ", exceptionClass="
+                        + exceptionClass
+                        + ", exceptionMessage="
+                        + exceptionMessage);
+
+        mGatewayStatusCallback.onGatewayConnectionError(
+                mConnectionConfig.getGatewayConnectionName(),
+                errorCode,
+                exceptionClass,
+                exceptionMessage);
+    }
+
+    private void ikeConnectionInfoChanged(
+            int token, @NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
+        sendMessageAndAcquireWakeLock(
+                EVENT_IKE_CONNECTION_INFO_CHANGED,
+                token,
+                new EventIkeConnectionInfoChangedInfo(ikeConnectionInfo));
+    }
+
+    private void sessionClosed(int token, @Nullable Exception exception) {
+        if (exception != null) {
+            notifyStatusCallbackForSessionClosed(exception);
+        }
+
+        // SESSION_LOST MUST be sent before SESSION_CLOSED to ensure that the SM moves to the
+        // Disconnecting state.
+        sessionLostWithoutCallback(token, exception);
+        sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, token);
+    }
+
+    private void migrationCompleted(
+            int token, @NonNull IpSecTransform inTransform, @NonNull IpSecTransform outTransform) {
+        sendMessageAndAcquireWakeLock(
+                EVENT_MIGRATION_COMPLETED,
+                token,
+                new EventMigrationCompletedInfo(inTransform, outTransform));
+    }
+
+    private void childTransformCreated(
+            int token, @NonNull IpSecTransform transform, int direction) {
+        sendMessageAndAcquireWakeLock(
+                EVENT_TRANSFORM_CREATED,
+                token,
+                new EventTransformCreatedInfo(direction, transform));
+    }
+
+    private void childOpened(int token, @NonNull VcnChildSessionConfiguration childConfig) {
+        sendMessageAndAcquireWakeLock(
+                EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig));
+    }
+
+    private abstract class BaseState extends State {
+        @Override
+        public void enter() {
+            try {
+                enterState();
+            } catch (Exception e) {
+                logWtf("Uncaught exception", e);
+                sendDisconnectRequestedAndAcquireWakelock(
+                        DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
+            }
+        }
+
+        protected void enterState() throws Exception {}
+
+        /**
+         * Returns whether the given token is valid.
+         *
+         * <p>By default, States consider any and all token to be 'valid'.
+         *
+         * <p>States should override this method if they want to restrict message handling to
+         * specific tokens.
+         */
+        protected boolean isValidToken(int token) {
+            return true;
+        }
+
+        /**
+         * Top-level processMessage with safeguards to prevent crashing the System Server on non-eng
+         * builds.
+         *
+         * <p>Here be dragons: processMessage() is final to ensure that mWakeLock is released once
+         * the Handler queue is empty. Future changes (or overrides) to processMessage() to MUST
+         * ensure that mWakeLock is correctly released.
+         */
+        @Override
+        public final boolean processMessage(Message msg) {
+            final int token = msg.arg1;
+            if (!isValidToken(token)) {
+                logDbg("Message called with obsolete token: " + token + "; what: " + msg.what);
+                return HANDLED;
+            }
+
+            try {
+                processStateMsg(msg);
+            } catch (Exception e) {
+                logWtf("Uncaught exception", e);
+                sendDisconnectRequestedAndAcquireWakelock(
+                        DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
+            }
+
+            // Attempt to release the WakeLock - only possible if the Handler queue is empty
+            maybeReleaseWakeLock();
+
+            return HANDLED;
+        }
+
+        protected abstract void processStateMsg(Message msg) throws Exception;
+
+        @Override
+        public void exit() {
+            try {
+                exitState();
+            } catch (Exception e) {
+                logWtf("Uncaught exception", e);
+                sendDisconnectRequestedAndAcquireWakelock(
+                        DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
+            }
+        }
+
+        protected void exitState() throws Exception {}
+
+        protected void logUnhandledMessage(Message msg) {
+            // Log as unexpected all known messages, and log all else as unknown.
+            switch (msg.what) {
+                case EVENT_UNDERLYING_NETWORK_CHANGED: // Fallthrough
+                case EVENT_RETRY_TIMEOUT_EXPIRED: // Fallthrough
+                case EVENT_SESSION_LOST: // Fallthrough
+                case EVENT_SESSION_CLOSED: // Fallthrough
+                case EVENT_TRANSFORM_CREATED: // Fallthrough
+                case EVENT_SETUP_COMPLETED: // Fallthrough
+                case EVENT_DISCONNECT_REQUESTED: // Fallthrough
+                case EVENT_TEARDOWN_TIMEOUT_EXPIRED: // Fallthrough
+                case EVENT_SUBSCRIPTIONS_CHANGED: // Fallthrough
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: // Fallthrough
+                case EVENT_MIGRATION_COMPLETED: // Fallthrough
+                case EVENT_IKE_CONNECTION_INFO_CHANGED: // Fallthrough
+                case EVENT_DATA_STALL_SUSPECTED:
+                    logUnexpectedEvent(msg.what);
+                    break;
+                default:
+                    logWtfUnknownEvent(msg.what);
+                    break;
+            }
+        }
+
+        protected void teardownNetwork() {
+            if (mNetworkAgent != null) {
+                mNetworkAgent.unregister();
+                mNetworkAgent = null;
+            }
+        }
+
+        protected void handleDisconnectRequested(EventDisconnectRequestedInfo info) {
+            // TODO(b/180526152): notify VcnStatusCallback for Network loss
+
+            logInfo("Tearing down. Cause: " + info.reason + "; quitting = " + info.shouldQuit);
+            if (info.shouldQuit) {
+                mIsQuitting.setTrue();
+            }
+
+            teardownNetwork();
+
+            if (mIkeSession == null) {
+                // Already disconnected, go straight to DisconnectedState
+                transitionTo(mDisconnectedState);
+            } else {
+                // Still need to wait for full closure
+                transitionTo(mDisconnectingState);
+            }
+        }
+
+        protected void handleSafeModeTimeoutExceeded() {
+            mSafeModeTimeoutAlarm = null;
+            logInfo("Entering safe mode after timeout exceeded");
+
+            // Connectivity for this GatewayConnection is broken; tear down the Network.
+            teardownNetwork();
+            mIsInSafeMode = true;
+            mGatewayStatusCallback.onSafeModeStatusChanged();
+        }
+
+        protected void logUnexpectedEvent(int what) {
+            logVdbg(
+                    "Unexpected event code "
+                            + what
+                            + " in state "
+                            + this.getClass().getSimpleName());
+        }
+
+        protected void logWtfUnknownEvent(int what) {
+            logWtf("Unknown event code " + what + " in state " + this.getClass().getSimpleName());
+        }
+    }
+
+    /**
+     * State representing the a disconnected VCN tunnel.
+     *
+     * <p>This is also is the initial state.
+     */
+    private class DisconnectedState extends BaseState {
+        @Override
+        protected void enterState() {
+            if (mIsQuitting.getValue()) {
+                quitNow(); // Ignore all queued events; cleanup is complete.
+            }
+
+            if (mIkeSession != null || mNetworkAgent != null) {
+                logWtf("Active IKE Session or NetworkAgent in DisconnectedState");
+            }
+
+            cancelSafeModeAlarm();
+        }
+
+        @Override
+        protected void processStateMsg(Message msg) {
+            switch (msg.what) {
+                case EVENT_UNDERLYING_NETWORK_CHANGED:
+                    // First network found; start tunnel
+                    mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
+
+                    if (mUnderlying != null) {
+                        transitionTo(mConnectingState);
+                    }
+                    break;
+                case EVENT_DISCONNECT_REQUESTED:
+                    if (((EventDisconnectRequestedInfo) msg.obj).shouldQuit) {
+                        mIsQuitting.setTrue();
+
+                        quitNow();
+                    }
+                    break;
+                default:
+                    logUnhandledMessage(msg);
+                    break;
+            }
+        }
+
+        @Override
+        protected void exitState() {
+            // Safe to blindly set up, as it is cancelled and cleared on entering this state
+            setSafeModeAlarm();
+        }
+    }
+
+    private abstract class ActiveBaseState extends BaseState {
+        @Override
+        protected boolean isValidToken(int token) {
+            return (token == TOKEN_ALL || token == mCurrentToken);
+        }
+    }
+
+    /**
+     * Transitive state representing a VCN that is tearing down an IKE session.
+     *
+     * <p>In this state, the IKE session is in the process of being torn down. If the IKE session
+     * does not complete teardown in a timely fashion, it will be killed (forcibly closed).
+     */
+    private class DisconnectingState extends ActiveBaseState {
+        /**
+         * Whether to skip the RetryTimeoutState and go straight to the ConnectingState.
+         *
+         * <p>This is used when an underlying network change triggered a restart on a new network.
+         *
+         * <p>Reset (to false) upon exit of the DisconnectingState.
+         */
+        private boolean mSkipRetryTimeout = false;
+
+        // TODO(b/178441390): Remove this in favor of resetting retry timers on UND_NET change.
+        public void setSkipRetryTimeout(boolean shouldSkip) {
+            mSkipRetryTimeout = shouldSkip;
+        }
+
+        @Override
+        protected void enterState() throws Exception {
+            if (mIkeSession == null) {
+                logWtf("IKE session was already closed when entering Disconnecting state.");
+                sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, mCurrentToken);
+                return;
+            }
+
+            // If underlying network has already been lost, save some time and just kill the session
+            if (mUnderlying == null) {
+                // Will trigger a EVENT_SESSION_CLOSED as IkeSession shuts down.
+                mIkeSession.kill();
+                return;
+            }
+
+            mIkeSession.close();
+
+            // Safe to blindly set up, as it is cancelled and cleared on exiting this state
+            setTeardownTimeoutAlarm();
+        }
+
+        @Override
+        protected void processStateMsg(Message msg) {
+            switch (msg.what) {
+                case EVENT_UNDERLYING_NETWORK_CHANGED: // Fallthrough
+                    mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
+
+                    // If we received a new underlying network, continue.
+                    if (mUnderlying != null) {
+                        break;
+                    }
+
+                    // Fallthrough; no network exists to send IKE close session requests.
+                case EVENT_TEARDOWN_TIMEOUT_EXPIRED:
+                    // Grace period ended. Kill session, triggering EVENT_SESSION_CLOSED
+                    mIkeSession.kill();
+
+                    break;
+                case EVENT_DISCONNECT_REQUESTED:
+                    EventDisconnectRequestedInfo info = ((EventDisconnectRequestedInfo) msg.obj);
+                    if (info.shouldQuit) {
+                        mIsQuitting.setTrue();
+                    }
+
+                    teardownNetwork();
+
+                    if (info.reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) {
+                        // TODO(b/180526152): notify VcnStatusCallback for Network loss
+
+                        // Will trigger EVENT_SESSION_CLOSED immediately.
+                        mIkeSession.kill();
+                        break;
+                    }
+
+                    // Otherwise we are already in the process of shutting down.
+                    break;
+                case EVENT_SESSION_CLOSED:
+                    mIkeSession = null;
+
+                    if (!mIsQuitting.getValue() && mUnderlying != null) {
+                        transitionTo(mSkipRetryTimeout ? mConnectingState : mRetryTimeoutState);
+                    } else {
+                        teardownNetwork();
+                        transitionTo(mDisconnectedState);
+                    }
+                    break;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    handleSafeModeTimeoutExceeded();
+                    break;
+                default:
+                    logUnhandledMessage(msg);
+                    break;
+            }
+        }
+
+        @Override
+        protected void exitState() throws Exception {
+            mSkipRetryTimeout = false;
+
+            cancelTeardownTimeoutAlarm();
+        }
+    }
+
+    /**
+     * Transitive state representing a VCN that is making an primary (non-handover) connection.
+     *
+     * <p>This state starts IKE negotiation, but defers transform application & network setup to the
+     * Connected state.
+     */
+    private class ConnectingState extends ActiveBaseState {
+        @Override
+        protected void enterState() {
+            if (mIkeSession != null) {
+                logWtf("ConnectingState entered with active session");
+
+                // Attempt to recover.
+                mIkeSession.kill();
+                mIkeSession = null;
+            }
+
+            mIkeSession = buildIkeSession(mUnderlying.network);
+        }
+
+        @Override
+        protected void processStateMsg(Message msg) {
+            switch (msg.what) {
+                case EVENT_UNDERLYING_NETWORK_CHANGED:
+                    final UnderlyingNetworkRecord oldUnderlying = mUnderlying;
+                    mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
+
+                    if (oldUnderlying == null) {
+                        // This should never happen, but if it does, there's likely a nasty bug.
+                        logWtf("Old underlying network was null in connected state. Bug?");
+                    }
+
+                    // If new underlying is null, all underlying networks have been lost; disconnect
+                    if (mUnderlying == null) {
+                        transitionTo(mDisconnectingState);
+                        break;
+                    }
+
+                    if (oldUnderlying != null
+                            && mUnderlying.network.equals(oldUnderlying.network)) {
+                        break; // Only network properties have changed; continue connecting.
+                    }
+                    // Else, retry on the new network.
+
+                    // Immediately come back to the ConnectingState (skip RetryTimeout, since this
+                    // isn't a failure)
+                    mDisconnectingState.setSkipRetryTimeout(true);
+
+                    // fallthrough - disconnect, and retry on new network.
+                case EVENT_SESSION_LOST:
+                    transitionTo(mDisconnectingState);
+                    break;
+                case EVENT_SESSION_CLOSED:
+                    // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this
+                    // message may not be posted again. Defer to ensure immediate shutdown.
+                    deferMessage(msg);
+
+                    transitionTo(mDisconnectingState);
+                    break;
+                case EVENT_SETUP_COMPLETED: // fallthrough
+                case EVENT_IKE_CONNECTION_INFO_CHANGED: // fallthrough
+                case EVENT_TRANSFORM_CREATED:
+                    // Child setup complete; move to ConnectedState for NetworkAgent registration
+                    deferMessage(msg);
+                    transitionTo(mConnectedState);
+                    break;
+                case EVENT_DISCONNECT_REQUESTED:
+                    handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
+                    break;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    handleSafeModeTimeoutExceeded();
+                    break;
+                default:
+                    logUnhandledMessage(msg);
+                    break;
+            }
+        }
+    }
+
+    private abstract class ConnectedStateBase extends ActiveBaseState {
+        protected void updateNetworkAgent(
+                @NonNull IpSecTunnelInterface tunnelIface,
+                @NonNull VcnNetworkAgent agent,
+                @NonNull VcnChildSessionConfiguration childConfig,
+                @NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
+            final NetworkCapabilities caps =
+                    buildNetworkCapabilities(mConnectionConfig, mUnderlying, mIsMobileDataEnabled);
+            final LinkProperties lp =
+                    buildConnectedLinkProperties(
+                            mConnectionConfig,
+                            tunnelIface,
+                            childConfig,
+                            mUnderlying,
+                            ikeConnectionInfo);
+
+            agent.sendNetworkCapabilities(caps);
+            agent.sendLinkProperties(lp);
+
+            agent.setUnderlyingNetworks(
+                    mUnderlying == null ? null : Collections.singletonList(mUnderlying.network));
+        }
+
+        protected VcnNetworkAgent buildNetworkAgent(
+                @NonNull IpSecTunnelInterface tunnelIface,
+                @NonNull VcnChildSessionConfiguration childConfig,
+                @NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
+            final NetworkCapabilities caps =
+                    buildNetworkCapabilities(mConnectionConfig, mUnderlying, mIsMobileDataEnabled);
+            final LinkProperties lp =
+                    buildConnectedLinkProperties(
+                            mConnectionConfig,
+                            tunnelIface,
+                            childConfig,
+                            mUnderlying,
+                            ikeConnectionInfo);
+            final NetworkAgentConfig nac =
+                    new NetworkAgentConfig.Builder()
+                            .setLegacyType(ConnectivityManager.TYPE_MOBILE)
+                            .setLegacyTypeName(NETWORK_INFO_NETWORK_TYPE_STRING)
+                            .setLegacySubType(TelephonyManager.NETWORK_TYPE_UNKNOWN)
+                            .setLegacySubTypeName(NETWORK_TYPE_STRING_UNKNOWN)
+                            .setLegacyExtraInfo(NETWORK_INFO_EXTRA_INFO)
+                            .build();
+
+            final VcnNetworkAgent agent =
+                    mDeps.newNetworkAgent(
+                            mVcnContext,
+                            TAG,
+                            caps,
+                            lp,
+                            Vcn.getNetworkScore(),
+                            nac,
+                            mVcnContext.getVcnNetworkProvider(),
+                            (agentRef) -> {
+                                // Only trigger teardown if the NetworkAgent hasn't been replaced or
+                                // changed. This guards against two cases - the first where
+                                // unwanted() may be called as a result of the
+                                // NetworkAgent.unregister() call, which might trigger a teardown
+                                // instead of just a Network disconnect, as well as the case where a
+                                // new NetworkAgent replaces an old one before the unwanted() call
+                                // is processed.
+                                if (mNetworkAgent != agentRef) {
+                                    logDbg("unwanted() called on stale NetworkAgent");
+                                    return;
+                                }
+
+                                logInfo("NetworkAgent was unwanted");
+                                teardownAsynchronously();
+                            } /* networkUnwantedCallback */,
+                            (status) -> {
+                                if (mIsQuitting.getValue()) {
+                                    return; // Ignore; VcnGatewayConnection quitting or already quit
+                                }
+
+                                switch (status) {
+                                    case NetworkAgent.VALIDATION_STATUS_VALID:
+                                        clearFailedAttemptCounterAndSafeModeAlarm();
+                                        break;
+                                    case NetworkAgent.VALIDATION_STATUS_NOT_VALID:
+                                        // Trigger re-validation of underlying networks; if it
+                                        // fails, the VCN will attempt to migrate away.
+                                        if (mUnderlying != null) {
+                                            mConnectivityManager.reportNetworkConnectivity(
+                                                    mUnderlying.network,
+                                                    false /* hasConnectivity */);
+                                        }
+
+                                        // Will only set a new alarm if no safe mode alarm is
+                                        // currently scheduled.
+                                        setSafeModeAlarm();
+                                        break;
+                                    default:
+                                        logWtf(
+                                                "Unknown validation status "
+                                                        + status
+                                                        + "; ignoring");
+                                        break;
+                                }
+                            } /* validationStatusCallback */);
+
+            agent.register();
+            agent.markConnected();
+
+            return agent;
+        }
+
+        protected void clearFailedAttemptCounterAndSafeModeAlarm() {
+            mVcnContext.ensureRunningOnLooperThread();
+
+            // Validated connection, clear failed attempt counter
+            mFailedAttempts = 0;
+            cancelSafeModeAlarm();
+
+            mIsInSafeMode = false;
+            mGatewayStatusCallback.onSafeModeStatusChanged();
+        }
+
+        protected void applyTransform(
+                int token,
+                @NonNull IpSecTunnelInterface tunnelIface,
+                @NonNull Network underlyingNetwork,
+                @NonNull IpSecTransform transform,
+                int direction) {
+            if (direction != IpSecManager.DIRECTION_IN && direction != IpSecManager.DIRECTION_OUT) {
+                logWtf("Applying transform for unexpected direction: " + direction);
+            }
+
+            try {
+                tunnelIface.setUnderlyingNetwork(underlyingNetwork);
+
+                // Transforms do not need to be persisted; the IkeSession will keep them alive
+                mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
+
+                if (direction == IpSecManager.DIRECTION_IN) {
+                    mUnderlyingNetworkController.updateInboundTransform(mUnderlying, transform);
+                }
+
+                // For inbound transforms, additionally allow forwarded traffic to bridge to DUN (as
+                // needed)
+                final Set<Integer> exposedCaps = mConnectionConfig.getAllExposedCapabilities();
+                if (direction == IpSecManager.DIRECTION_IN
+                        && exposedCaps.contains(NET_CAPABILITY_DUN)) {
+                    mIpSecManager.applyTunnelModeTransform(
+                            tunnelIface, IpSecManager.DIRECTION_FWD, transform);
+                }
+            } catch (IOException | IllegalArgumentException e) {
+                logInfo("Transform application failed for network " + token, e);
+                sessionLost(token, e);
+            }
+        }
+
+        protected void setupInterface(
+                int token,
+                @NonNull IpSecTunnelInterface tunnelIface,
+                @NonNull VcnChildSessionConfiguration childConfig,
+                @Nullable VcnChildSessionConfiguration oldChildConfig) {
+            try {
+                final Set<LinkAddress> newAddrs =
+                        new ArraySet<>(childConfig.getInternalAddresses());
+                final Set<LinkAddress> existingAddrs = new ArraySet<>();
+                if (oldChildConfig != null) {
+                    existingAddrs.addAll(oldChildConfig.getInternalAddresses());
+                }
+
+                final Set<LinkAddress> toAdd = new ArraySet<>();
+                toAdd.addAll(newAddrs);
+                toAdd.removeAll(existingAddrs);
+
+                final Set<LinkAddress> toRemove = new ArraySet<>();
+                toRemove.addAll(existingAddrs);
+                toRemove.removeAll(newAddrs);
+
+                for (LinkAddress address : toAdd) {
+                    tunnelIface.addAddress(address.getAddress(), address.getPrefixLength());
+                }
+
+                for (LinkAddress address : toRemove) {
+                    tunnelIface.removeAddress(address.getAddress(), address.getPrefixLength());
+                }
+            } catch (IOException e) {
+                logInfo("Adding address to tunnel failed for token " + token, e);
+                sessionLost(token, e);
+            }
+        }
+    }
+
+    /**
+     * Stable state representing a VCN that has a functioning connection to the mobility anchor.
+     *
+     * <p>This state handles IPsec transform application (initial and rekey), NetworkAgent setup,
+     * and monitors for mobility events.
+     */
+    class ConnectedState extends ConnectedStateBase {
+        @Override
+        protected void enterState() throws Exception {
+            if (mTunnelIface == null) {
+                try {
+                    // Requires a real Network object in order to be created; doing this any earlier
+                    // means not having a real Network object, or picking an incorrect Network.
+                    mTunnelIface =
+                            mIpSecManager.createIpSecTunnelInterface(
+                                    DUMMY_ADDR, DUMMY_ADDR, mUnderlying.network);
+                } catch (IOException | ResourceUnavailableException e) {
+                    teardownAsynchronously();
+                }
+            }
+        }
+
+        @Override
+        protected void processStateMsg(Message msg) {
+            switch (msg.what) {
+                case EVENT_UNDERLYING_NETWORK_CHANGED:
+                    handleUnderlyingNetworkChanged(msg);
+                    break;
+                case EVENT_SESSION_CLOSED:
+                    // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this
+                    // message may not be posted again. Defer to ensure immediate shutdown.
+                    deferMessage(msg);
+                    transitionTo(mDisconnectingState);
+                    break;
+                case EVENT_SESSION_LOST:
+                    transitionTo(mDisconnectingState);
+                    break;
+                case EVENT_TRANSFORM_CREATED:
+                    final EventTransformCreatedInfo transformCreatedInfo =
+                            (EventTransformCreatedInfo) msg.obj;
+
+                    applyTransform(
+                            mCurrentToken,
+                            mTunnelIface,
+                            mUnderlying.network,
+                            transformCreatedInfo.transform,
+                            transformCreatedInfo.direction);
+                    break;
+                case EVENT_SETUP_COMPLETED:
+                    final VcnChildSessionConfiguration oldChildConfig = mChildConfig;
+                    mChildConfig = ((EventSetupCompletedInfo) msg.obj).childSessionConfig;
+
+                    setupInterfaceAndNetworkAgent(
+                            mCurrentToken,
+                            mTunnelIface,
+                            mChildConfig,
+                            oldChildConfig,
+                            mIkeConnectionInfo);
+
+                    // Create opportunistic child SAs; this allows SA aggregation in the downlink,
+                    // reducing lock/atomic contention in high throughput scenarios. All SAs will
+                    // share the same UDP encap socket (and keepalives) as necessary, and are
+                    // effectively free.
+                    final int parallelTunnelCount =
+                            mDeps.getParallelTunnelCount(mLastSnapshot, mSubscriptionGroup);
+                    logInfo("Parallel tunnel count: " + parallelTunnelCount);
+
+                    for (int i = 0; i < parallelTunnelCount - 1; i++) {
+                        mIkeSession.openChildSession(
+                                buildOpportunisticChildParams(),
+                                new VcnChildSessionCallback(
+                                        mCurrentToken, true /* isOpportunistic */));
+                    }
+
+                    break;
+                case EVENT_DISCONNECT_REQUESTED:
+                    handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
+                    break;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    handleSafeModeTimeoutExceeded();
+                    break;
+                case EVENT_MIGRATION_COMPLETED:
+                    final EventMigrationCompletedInfo migrationCompletedInfo =
+                            (EventMigrationCompletedInfo) msg.obj;
+
+                    handleMigrationCompleted(migrationCompletedInfo);
+                    break;
+                case EVENT_IKE_CONNECTION_INFO_CHANGED:
+                    mIkeConnectionInfo =
+                            ((EventIkeConnectionInfoChangedInfo) msg.obj).ikeConnectionInfo;
+                    break;
+                case EVENT_DATA_STALL_SUSPECTED:
+                    final Network networkWithDataStall =
+                            ((EventDataStallSuspectedInfo) msg.obj).network;
+                    handleDataStallSuspected(networkWithDataStall);
+                    break;
+                default:
+                    logUnhandledMessage(msg);
+                    break;
+            }
+        }
+
+        private void handleMigrationCompleted(EventMigrationCompletedInfo migrationCompletedInfo) {
+            logInfo("Migration completed: " + mUnderlying.network);
+
+            applyTransform(
+                    mCurrentToken,
+                    mTunnelIface,
+                    mUnderlying.network,
+                    migrationCompletedInfo.inTransform,
+                    IpSecManager.DIRECTION_IN);
+
+            applyTransform(
+                    mCurrentToken,
+                    mTunnelIface,
+                    mUnderlying.network,
+                    migrationCompletedInfo.outTransform,
+                    IpSecManager.DIRECTION_OUT);
+
+            updateNetworkAgent(mTunnelIface, mNetworkAgent, mChildConfig, mIkeConnectionInfo);
+
+            // Trigger re-validation after migration events.
+            mConnectivityManager.reportNetworkConnectivity(
+                    mNetworkAgent.getNetwork(), false /* hasConnectivity */);
+        }
+
+        private void handleUnderlyingNetworkChanged(@NonNull Message msg) {
+            final UnderlyingNetworkRecord oldUnderlying = mUnderlying;
+            mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
+
+            if (mUnderlying == null) {
+                logInfo("Underlying network lost");
+
+                // Ignored for now; a new network may be coming up. If none does, the delayed
+                // NETWORK_LOST disconnect will be fired, and tear down the session + network.
+                return;
+            }
+
+            // mUnderlying assumed non-null, given check above.
+            // If network changed, migrate. Otherwise, update any existing networkAgent.
+            if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) {
+                logInfo("Migrating to new network: " + mUnderlying.network);
+                mIkeSession.setNetwork(mUnderlying.network);
+            } else {
+                // oldUnderlying is non-null & underlying network itself has not changed
+                // (only network properties were changed).
+
+                // Network not yet set up, or child not yet connected.
+                if (mNetworkAgent != null && mChildConfig != null) {
+                    // If only network properties changed and agent is active, update properties
+                    updateNetworkAgent(
+                            mTunnelIface, mNetworkAgent, mChildConfig, mIkeConnectionInfo);
+                }
+            }
+        }
+
+        private void handleDataStallSuspected(Network networkWithDataStall) {
+            if (mUnderlying != null
+                    && mNetworkAgent != null
+                    && mNetworkAgent.getNetwork().equals(networkWithDataStall)) {
+                logInfo("Perform Mobility update to recover from suspected data stall");
+                mIkeSession.setNetwork(mUnderlying.network);
+            }
+        }
+
+        protected void setupInterfaceAndNetworkAgent(
+                int token,
+                @NonNull IpSecTunnelInterface tunnelIface,
+                @NonNull VcnChildSessionConfiguration childConfig,
+                @NonNull VcnChildSessionConfiguration oldChildConfig,
+                @NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
+            setupInterface(token, tunnelIface, childConfig, oldChildConfig);
+
+            if (mNetworkAgent == null) {
+                mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig, ikeConnectionInfo);
+            } else {
+                updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig, ikeConnectionInfo);
+
+                // mNetworkAgent not null, so the VCN Network has already been established. Clear
+                // the failed attempt counter and safe mode alarm since this transition is complete.
+                clearFailedAttemptCounterAndSafeModeAlarm();
+            }
+        }
+
+        @Override
+        protected void exitState() {
+            // Will only set a new alarm if no safe mode alarm is currently scheduled.
+            setSafeModeAlarm();
+        }
+    }
+
+    /**
+     * Transitive state representing a VCN that failed to establish a connection, and will retry.
+     *
+     * <p>This state will be exited upon a new underlying network being found, or timeout expiry.
+     */
+    class RetryTimeoutState extends ActiveBaseState {
+        @Override
+        protected void enterState() throws Exception {
+            // Reset upon entry to ConnectedState
+            mFailedAttempts++;
+
+            if (mUnderlying == null) {
+                logWtf("Underlying network was null in retry state");
+                teardownNetwork();
+                transitionTo(mDisconnectedState);
+            } else {
+                // Safe to blindly set up, as it is cancelled and cleared on exiting this state
+                setRetryTimeoutAlarm(getNextRetryIntervalsMs());
+            }
+        }
+
+        @Override
+        protected void processStateMsg(Message msg) {
+            switch (msg.what) {
+                case EVENT_UNDERLYING_NETWORK_CHANGED:
+                    final UnderlyingNetworkRecord oldUnderlying = mUnderlying;
+                    mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
+
+                    // If new underlying is null, all networks were lost; go back to disconnected.
+                    if (mUnderlying == null) {
+                        teardownNetwork();
+                        transitionTo(mDisconnectedState);
+                        return;
+                    } else if (oldUnderlying != null
+                            && mUnderlying.network.equals(oldUnderlying.network)) {
+                        // If the network has not changed, do nothing.
+                        return;
+                    }
+
+                    // Fallthrough
+                case EVENT_RETRY_TIMEOUT_EXPIRED:
+                    transitionTo(mConnectingState);
+                    break;
+                case EVENT_DISCONNECT_REQUESTED:
+                    handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
+                    break;
+                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
+                    handleSafeModeTimeoutExceeded();
+                    break;
+                default:
+                    logUnhandledMessage(msg);
+                    break;
+            }
+        }
+
+        @Override
+        public void exitState() {
+            cancelRetryTimeoutAlarm();
+        }
+
+        private long getNextRetryIntervalsMs() {
+            final int retryDelayIndex = mFailedAttempts - 1;
+            final long[] retryIntervalsMs = mConnectionConfig.getRetryIntervalsMillis();
+
+            // Repeatedly use last item in retry timeout list.
+            if (retryDelayIndex >= retryIntervalsMs.length) {
+                return retryIntervalsMs[retryIntervalsMs.length - 1];
+            }
+
+            return retryIntervalsMs[retryDelayIndex];
+        }
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static NetworkCapabilities buildNetworkCapabilities(
+            @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig,
+            @Nullable UnderlyingNetworkRecord underlying,
+            boolean isMobileDataEnabled) {
+        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
+
+        builder.addTransportType(TRANSPORT_CELLULAR);
+        builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        builder.addCapability(NET_CAPABILITY_NOT_CONGESTED);
+        builder.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
+
+        // Add exposed capabilities
+        for (int cap : gatewayConnectionConfig.getAllExposedCapabilities()) {
+            // Skip adding INTERNET or DUN if mobile data is disabled.
+            if (!isMobileDataEnabled
+                    && (cap == NET_CAPABILITY_INTERNET || cap == NET_CAPABILITY_DUN)) {
+                continue;
+            }
+
+            builder.addCapability(cap);
+        }
+
+        if (underlying != null) {
+            final NetworkCapabilities underlyingCaps = underlying.networkCapabilities;
+
+            // Mirror merged capabilities.
+            for (int cap : MERGED_CAPABILITIES) {
+                if (underlyingCaps.hasCapability(cap)) {
+                    builder.addCapability(cap);
+                }
+            }
+
+            // Set admin UIDs for ConnectivityDiagnostics use.
+            final int[] underlyingAdminUids = underlyingCaps.getAdministratorUids();
+            Arrays.sort(underlyingAdminUids); // Sort to allow contains check below.
+
+            int[] adminUids;
+            if (underlyingCaps.getOwnerUid() > 0 // No owner UID specified
+                    && 0 > Arrays.binarySearch(// Owner UID not found in admin UID list.
+                            underlyingAdminUids, underlyingCaps.getOwnerUid())) {
+                adminUids = Arrays.copyOf(underlyingAdminUids, underlyingAdminUids.length + 1);
+                adminUids[adminUids.length - 1] = underlyingCaps.getOwnerUid();
+                Arrays.sort(adminUids);
+            } else {
+                adminUids = underlyingAdminUids;
+            }
+
+            // Set owner & administrator UID
+            builder.setOwnerUid(Process.myUid());
+            adminUids = Arrays.copyOf(adminUids, adminUids.length + 1);
+            adminUids[adminUids.length - 1] = Process.myUid();
+            builder.setAdministratorUids(adminUids);
+
+            builder.setLinkUpstreamBandwidthKbps(underlyingCaps.getLinkUpstreamBandwidthKbps());
+            builder.setLinkDownstreamBandwidthKbps(underlyingCaps.getLinkDownstreamBandwidthKbps());
+
+            // Set TransportInfo for SysUI use (never parcelled out of SystemServer).
+            if (underlyingCaps.hasTransport(TRANSPORT_WIFI)
+                    && underlyingCaps.getTransportInfo() instanceof WifiInfo) {
+                final WifiInfo wifiInfo = (WifiInfo) underlyingCaps.getTransportInfo();
+                builder.setTransportInfo(
+                        new VcnTransportInfo(
+                                wifiInfo,
+                                gatewayConnectionConfig.getMinUdpPort4500NatTimeoutSeconds()));
+            } else if (underlyingCaps.hasTransport(TRANSPORT_CELLULAR)
+                    && underlyingCaps.getNetworkSpecifier() instanceof TelephonyNetworkSpecifier) {
+                final TelephonyNetworkSpecifier telNetSpecifier =
+                        (TelephonyNetworkSpecifier) underlyingCaps.getNetworkSpecifier();
+                builder.setTransportInfo(
+                        new VcnTransportInfo(
+                                telNetSpecifier.getSubscriptionId(),
+                                gatewayConnectionConfig.getMinUdpPort4500NatTimeoutSeconds()));
+            } else {
+                Slog.wtf(
+                        TAG,
+                        "Unknown transport type or missing TransportInfo/NetworkSpecifier for"
+                                + " non-null underlying network");
+            }
+            builder.setUnderlyingNetworks(List.of(underlying.network));
+        } else {
+            Slog.wtf(
+                    TAG,
+                    "No underlying network while building network capabilities",
+                    new IllegalStateException());
+        }
+
+        return builder.build();
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    LinkProperties buildConnectedLinkProperties(
+            @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig,
+            @NonNull IpSecTunnelInterface tunnelIface,
+            @NonNull VcnChildSessionConfiguration childConfig,
+            @Nullable UnderlyingNetworkRecord underlying,
+            @NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
+        final IkeTunnelConnectionParams ikeTunnelParams =
+                gatewayConnectionConfig.getTunnelConnectionParams();
+        final LinkProperties lp = new LinkProperties();
+
+        lp.setInterfaceName(tunnelIface.getInterfaceName());
+        for (LinkAddress addr : childConfig.getInternalAddresses()) {
+            lp.addLinkAddress(addr);
+        }
+        for (InetAddress addr : childConfig.getInternalDnsServers()) {
+            lp.addDnsServer(addr);
+        }
+
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null /*gateway*/,
+                null /*iface*/, RouteInfo.RTN_UNICAST));
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null /*gateway*/,
+                null /*iface*/, RouteInfo.RTN_UNICAST));
+
+        int underlyingMtu = 0;
+        if (underlying != null) {
+            final LinkProperties underlyingLp = underlying.linkProperties;
+
+            lp.setTcpBufferSizes(underlyingLp.getTcpBufferSizes());
+            underlyingMtu = underlyingLp.getMtu();
+
+            // WiFi LinkProperties uses DHCP as the sole source of MTU information, and as a result
+            // often lists MTU as 0 (see b/184678973). Use the interface MTU as retrieved by
+            // NetworkInterface APIs.
+            if (underlyingMtu == 0 && underlyingLp.getInterfaceName() != null) {
+                underlyingMtu = mDeps.getUnderlyingIfaceMtu(underlyingLp.getInterfaceName());
+            }
+        } else {
+            Slog.wtf(
+                    TAG,
+                    "No underlying network while building link properties",
+                    new IllegalStateException());
+        }
+        lp.setMtu(
+                MtuUtils.getMtu(
+                        ikeTunnelParams.getTunnelModeChildSessionParams().getSaProposals(),
+                        gatewayConnectionConfig.getMaxMtu(),
+                        underlyingMtu,
+                        ikeConnectionInfo.getLocalAddress() instanceof Inet4Address));
+
+        return lp;
+    }
+
+    private class IkeSessionCallbackImpl implements IkeSessionCallback {
+        private final int mToken;
+
+        IkeSessionCallbackImpl(int token) {
+            mToken = token;
+        }
+
+        @Override
+        public void onOpened(@NonNull IkeSessionConfiguration ikeSessionConfig) {
+            logDbg("IkeOpened for token " + mToken);
+            ikeConnectionInfoChanged(mToken, ikeSessionConfig.getIkeSessionConnectionInfo());
+        }
+
+        @Override
+        public void onClosed() {
+            logDbg("IkeClosed for token " + mToken);
+            sessionClosed(mToken, null);
+        }
+
+        @Override
+        public void onClosedExceptionally(@NonNull IkeException exception) {
+            logInfo("IkeClosedExceptionally for token " + mToken, exception);
+            sessionClosed(mToken, exception);
+        }
+
+        @Override
+        public void onError(@NonNull IkeProtocolException exception) {
+            logInfo("IkeError for token " + mToken, exception);
+            // Non-fatal, log and continue.
+        }
+
+        @Override
+        public void onIkeSessionConnectionInfoChanged(
+                @NonNull IkeSessionConnectionInfo connectionInfo) {
+            logDbg("onIkeSessionConnectionInfoChanged for token " + mToken);
+            ikeConnectionInfoChanged(mToken, connectionInfo);
+        }
+    }
+
+    /** Implementation of ChildSessionCallback, exposed for testing. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public class VcnChildSessionCallback implements ChildSessionCallback {
+        private final int mToken;
+        private final boolean mIsOpportunistic;
+
+        private boolean mIsChildOpened = false;
+
+        VcnChildSessionCallback(int token) {
+            this(token, false /* isOpportunistic */);
+        }
+
+        /**
+         * Creates a ChildSessionCallback
+         *
+         * <p>If configured as opportunistic, transforms will not report initial startup, or
+         * associated startup failures. This serves the dual purposes of ensuring that if the server
+         * does not support connection multiplexing, new child SA negotiations will be ignored, and
+         * at the same time, will notify the VCN session if a successfully negotiated opportunistic
+         * child SA is subsequently torn down, which could impact uplink traffic if the SA in use
+         * for outbound/uplink traffic is this opportunistic SA.
+         *
+         * <p>While inbound SAs can be used in parallel, the IPsec stack explicitly selects the last
+         * applied outbound transform for outbound traffic. This means that unlike inbound traffic,
+         * outbound does not benefit from these parallel SAs in the same manner.
+         */
+        VcnChildSessionCallback(int token, boolean isOpportunistic) {
+            mToken = token;
+            mIsOpportunistic = isOpportunistic;
+        }
+
+        /** Internal proxy method for injecting of mocked ChildSessionConfiguration */
+        @VisibleForTesting(visibility = Visibility.PRIVATE)
+        void onOpened(@NonNull VcnChildSessionConfiguration childConfig) {
+            logDbg("ChildOpened for token " + mToken);
+
+            if (mIsOpportunistic) {
+                logDbg("ChildOpened for opportunistic child; suppressing event message");
+                mIsChildOpened = true;
+                return;
+            }
+
+            childOpened(mToken, childConfig);
+        }
+
+        @Override
+        public void onOpened(@NonNull ChildSessionConfiguration childConfig) {
+            onOpened(new VcnChildSessionConfiguration(childConfig));
+        }
+
+        @Override
+        public void onClosed() {
+            logDbg("ChildClosed for token " + mToken);
+
+            if (mIsOpportunistic && !mIsChildOpened) {
+                logDbg("ChildClosed for unopened opportunistic child; ignoring");
+                return;
+            }
+
+            sessionLost(mToken, null);
+        }
+
+        @Override
+        public void onClosedExceptionally(@NonNull IkeException exception) {
+            logInfo("ChildClosedExceptionally for token " + mToken, exception);
+
+            if (mIsOpportunistic && !mIsChildOpened) {
+                logInfo("ChildClosedExceptionally for unopened opportunistic child; ignoring");
+                return;
+            }
+
+            sessionLost(mToken, exception);
+        }
+
+        @Override
+        public void onIpSecTransformCreated(@NonNull IpSecTransform transform, int direction) {
+            logDbg("ChildTransformCreated; Direction: " + direction + "; token " + mToken);
+            childTransformCreated(mToken, transform, direction);
+        }
+
+        @Override
+        public void onIpSecTransformsMigrated(
+                @NonNull IpSecTransform inIpSecTransform,
+                @NonNull IpSecTransform outIpSecTransform) {
+            logDbg("ChildTransformsMigrated; token " + mToken);
+            migrationCompleted(mToken, inIpSecTransform, outIpSecTransform);
+        }
+
+        @Override
+        public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) {
+            // Nothing to be done; no references to the IpSecTransform are held, and this transform
+            // will be closed by the IKE library.
+            logDbg("ChildTransformDeleted; Direction: " + direction + "; for token " + mToken);
+        }
+    }
+
+    // Used in Vcn.java, but must be public for mockito to mock this.
+    public String getLogPrefix() {
+        return "("
+                + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
+                + "-"
+                + mConnectionConfig.getGatewayConnectionName()
+                + "-"
+                + System.identityHashCode(this)
+                + ") ";
+    }
+
+    private String getTagLogPrefix() {
+        return "[ " + TAG + " " + getLogPrefix() + "]";
+    }
+
+    private void logVdbg(String msg) {
+        if (VDBG) {
+            Slog.v(TAG, getLogPrefix() + msg);
+        }
+    }
+
+    private void logDbg(String msg) {
+        Slog.d(TAG, getLogPrefix() + msg);
+    }
+
+    private void logDbg(String msg, Throwable tr) {
+        Slog.d(TAG, getLogPrefix() + msg, tr);
+    }
+
+    private void logInfo(String msg) {
+        Slog.i(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[INFO] " + getTagLogPrefix() + msg);
+    }
+
+    private void logInfo(String msg, Throwable tr) {
+        Slog.i(TAG, getLogPrefix() + msg, tr);
+        LOCAL_LOG.log("[INFO] " + getTagLogPrefix() + msg + tr);
+    }
+
+    private void logWarn(String msg) {
+        Slog.w(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[WARN] " + getTagLogPrefix() + msg);
+    }
+
+    private void logWarn(String msg, Throwable tr) {
+        Slog.w(TAG, getLogPrefix() + msg, tr);
+        LOCAL_LOG.log("[WARN] " + getTagLogPrefix() + msg + tr);
+    }
+
+    private void logErr(String msg) {
+        Slog.e(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[ERR ] " + getTagLogPrefix() + msg);
+    }
+
+    private void logErr(String msg, Throwable tr) {
+        Slog.e(TAG, getLogPrefix() + msg, tr);
+        LOCAL_LOG.log("[ERR ] " + getTagLogPrefix() + msg + tr);
+    }
+
+    private void logWtf(String msg) {
+        Slog.wtf(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[WTF ] " + msg);
+    }
+
+    private void logWtf(String msg, Throwable tr) {
+        Slog.wtf(TAG, getLogPrefix() + msg, tr);
+        LOCAL_LOG.log("[WTF ] " + msg + tr);
+    }
+
+    /**
+     * Dumps the state of this VcnGatewayConnection for logging and debugging purposes.
+     *
+     * <p>PII and credentials MUST NEVER be dumped here.
+     *
+     * <p>This method is not thread safe and MUST run on the VCN thread.
+     */
+    public void dump(IndentingPrintWriter pw) {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        pw.println("VcnGatewayConnection (" + mConnectionConfig.getGatewayConnectionName() + "):");
+        pw.increaseIndent();
+
+        pw.println(
+                "Current state: "
+                        + (getCurrentState() == null
+                                ? null
+                                : getCurrentState().getClass().getSimpleName()));
+        pw.println("mIsQuitting: " + mIsQuitting.getValue());
+        pw.println("mIsInSafeMode: " + mIsInSafeMode);
+        pw.println("mCurrentToken: " + mCurrentToken);
+        pw.println("mFailedAttempts: " + mFailedAttempts);
+        pw.println(
+                "mNetworkAgent.getNetwork(): "
+                        + (mNetworkAgent == null ? null : mNetworkAgent.getNetwork()));
+        pw.println();
+
+        mUnderlyingNetworkController.dump(pw);
+        pw.println();
+
+        if (mIkeSession == null) {
+            pw.println("mIkeSession: null");
+        } else {
+            pw.println("mIkeSession:");
+
+            // Add a try catch block in case IkeSession#dump is not thread-safe
+            try {
+                mIkeSession.dump(pw);
+            } catch (Exception e) {
+                Slog.wtf(TAG, "Failed to dump IkeSession: " + e);
+            }
+        }
+
+        pw.decreaseIndent();
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setTunnelInterface(IpSecTunnelInterface tunnelIface) {
+        mTunnelIface = tunnelIface;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    UnderlyingNetworkControllerCallback getUnderlyingNetworkControllerCallback() {
+        return mUnderlyingNetworkControllerCallback;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    ConnectivityDiagnosticsCallback getConnectivityDiagnosticsCallback() {
+        return mConnectivityDiagnosticsCallback;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    UnderlyingNetworkRecord getUnderlyingNetwork() {
+        return mUnderlying;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setUnderlyingNetwork(@Nullable UnderlyingNetworkRecord record) {
+        mUnderlying = record;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    IkeSessionConnectionInfo getIkeConnectionInfo() {
+        return mIkeConnectionInfo;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    boolean isQuitting() {
+        return mIsQuitting.getValue();
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setQuitting() {
+        mIsQuitting.setTrue();
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    VcnIkeSession getIkeSession() {
+        return mIkeSession;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setIkeSession(@Nullable VcnIkeSession session) {
+        mIkeSession = session;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    VcnNetworkAgent getNetworkAgent() {
+        return mNetworkAgent;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setNetworkAgent(@Nullable VcnNetworkAgent networkAgent) {
+        mNetworkAgent = networkAgent;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void sendDisconnectRequestedAndAcquireWakelock(String reason, boolean shouldQuit) {
+        sendMessageAndAcquireWakeLock(
+                EVENT_DISCONNECT_REQUESTED,
+                TOKEN_ALL,
+                new EventDisconnectRequestedInfo(reason, shouldQuit));
+    }
+
+    private IkeSessionParams buildIkeParams(@NonNull Network network) {
+        final IkeTunnelConnectionParams ikeTunnelConnectionParams =
+                mConnectionConfig.getTunnelConnectionParams();
+        final IkeSessionParams.Builder builder =
+                new IkeSessionParams.Builder(ikeTunnelConnectionParams.getIkeSessionParams());
+        builder.setNetwork(network);
+        return builder.build();
+    }
+
+    private ChildSessionParams buildChildParams() {
+        return mConnectionConfig.getTunnelConnectionParams().getTunnelModeChildSessionParams();
+    }
+
+    private ChildSessionParams buildOpportunisticChildParams() {
+        final ChildSessionParams baseParams =
+                mConnectionConfig.getTunnelConnectionParams().getTunnelModeChildSessionParams();
+
+        final TunnelModeChildSessionParams.Builder builder =
+                new TunnelModeChildSessionParams.Builder();
+        for (ChildSaProposal proposal : baseParams.getChildSaProposals()) {
+            builder.addChildSaProposal(proposal);
+        }
+
+        for (IkeTrafficSelector inboundSelector : baseParams.getInboundTrafficSelectors()) {
+            builder.addInboundTrafficSelectors(inboundSelector);
+        }
+
+        for (IkeTrafficSelector outboundSelector : baseParams.getOutboundTrafficSelectors()) {
+            builder.addOutboundTrafficSelectors(outboundSelector);
+        }
+
+        builder.setLifetimeSeconds(
+                baseParams.getHardLifetimeSeconds(), baseParams.getSoftLifetimeSeconds());
+
+        return builder.build();
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    VcnIkeSession buildIkeSession(@NonNull Network network) {
+        final int token = ++mCurrentToken;
+
+        return mDeps.newIkeSession(
+                mVcnContext,
+                buildIkeParams(network),
+                buildChildParams(),
+                new IkeSessionCallbackImpl(token),
+                new VcnChildSessionCallback(token));
+    }
+
+    /** External dependencies used by VcnGatewayConnection, for injection in tests */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        /** Builds a new UnderlyingNetworkController. */
+        public UnderlyingNetworkController newUnderlyingNetworkController(
+                VcnContext vcnContext,
+                VcnGatewayConnectionConfig connectionConfig,
+                ParcelUuid subscriptionGroup,
+                TelephonySubscriptionSnapshot snapshot,
+                UnderlyingNetworkControllerCallback callback) {
+            return new UnderlyingNetworkController(
+                    vcnContext, connectionConfig, subscriptionGroup, snapshot, callback);
+        }
+
+        /** Builds a new IkeSession. */
+        public VcnIkeSession newIkeSession(
+                VcnContext vcnContext,
+                IkeSessionParams ikeSessionParams,
+                ChildSessionParams childSessionParams,
+                IkeSessionCallback ikeSessionCallback,
+                ChildSessionCallback childSessionCallback) {
+            return new VcnIkeSession(
+                    vcnContext,
+                    ikeSessionParams,
+                    childSessionParams,
+                    ikeSessionCallback,
+                    childSessionCallback);
+        }
+
+        /** Builds a new WakeLock. */
+        public VcnWakeLock newWakeLock(
+                @NonNull Context context, int wakeLockFlag, @NonNull String wakeLockTag) {
+            return new VcnWakeLock(context, wakeLockFlag, wakeLockTag);
+        }
+
+        /** Builds a new WakeupMessage. */
+        public WakeupMessage newWakeupMessage(
+                @NonNull VcnContext vcnContext,
+                @NonNull Handler handler,
+                @NonNull String tag,
+                @NonNull Runnable runnable) {
+            return new WakeupMessage(vcnContext.getContext(), handler, tag, runnable);
+        }
+
+        /** Builds a new VcnNetworkAgent. */
+        public VcnNetworkAgent newNetworkAgent(
+                @NonNull VcnContext vcnContext,
+                @NonNull String tag,
+                @NonNull NetworkCapabilities caps,
+                @NonNull LinkProperties lp,
+                @NonNull NetworkScore score,
+                @NonNull NetworkAgentConfig nac,
+                @NonNull NetworkProvider provider,
+                @NonNull Consumer<VcnNetworkAgent> networkUnwantedCallback,
+                @NonNull Consumer<Integer> validationStatusCallback) {
+            return new VcnNetworkAgent(
+                    vcnContext,
+                    tag,
+                    caps,
+                    lp,
+                    score,
+                    nac,
+                    provider,
+                    networkUnwantedCallback,
+                    validationStatusCallback);
+        }
+
+        /** Checks if airplane mode is enabled. */
+        public boolean isAirplaneModeOn(@NonNull VcnContext vcnContext) {
+            return Settings.Global.getInt(vcnContext.getContext().getContentResolver(),
+                    Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+        }
+
+        /** Gets the elapsed real time since boot, in millis. */
+        public long getElapsedRealTime() {
+            return SystemClock.elapsedRealtime();
+        }
+
+        /** Gets the MTU for the given underlying interface. */
+        public int getUnderlyingIfaceMtu(String ifaceName) {
+            try {
+                final NetworkInterface underlyingIface = NetworkInterface.getByName(ifaceName);
+                return underlyingIface == null ? 0 : underlyingIface.getMTU();
+            } catch (IOException e) {
+                Slog.d(TAG, "Could not get MTU of underlying network", e);
+                return 0;
+            }
+        }
+
+        /** Gets the max number of parallel tunnels allowed for tunnel aggregation. */
+        public int getParallelTunnelCount(
+                TelephonySubscriptionSnapshot snapshot, ParcelUuid subGrp) {
+            PersistableBundleWrapper carrierConfig = snapshot.getCarrierConfigForSubGrp(subGrp);
+            int result = TUNNEL_AGGREGATION_SA_COUNT_MAX_DEFAULT;
+
+            if (carrierConfig != null) {
+                result =
+                        carrierConfig.getInt(
+                                VcnManager.VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY,
+                                TUNNEL_AGGREGATION_SA_COUNT_MAX_DEFAULT);
+            }
+
+            // Guard against tunnel count < 1
+            return Math.max(1, result);
+        }
+    }
+
+    /**
+     * Proxy implementation of Child Session Configuration, used for testing.
+     *
+     * <p>This wrapper allows mocking of the final, parcelable ChildSessionConfiguration object for
+     * testing purposes. This is the unfortunate result of mockito-inline (for mocking final
+     * classes) not working properly with system services & associated classes.
+     *
+     * <p>This class MUST EXCLUSIVELY be a passthrough, proxying calls directly to the actual
+     * ChildSessionConfiguration.
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class VcnChildSessionConfiguration {
+        private final ChildSessionConfiguration mChildConfig;
+
+        public VcnChildSessionConfiguration(ChildSessionConfiguration childConfig) {
+            mChildConfig = childConfig;
+        }
+
+        /** Retrieves the addresses to be used inside the tunnel. */
+        public List<LinkAddress> getInternalAddresses() {
+            return mChildConfig.getInternalAddresses();
+        }
+
+        /** Retrieves the DNS servers to be used inside the tunnel. */
+        public List<InetAddress> getInternalDnsServers() {
+            return mChildConfig.getInternalDnsServers();
+        }
+    }
+
+    /** Proxy implementation of IKE session, used for testing. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class VcnIkeSession {
+        private final IkeSession mImpl;
+
+        public VcnIkeSession(
+                VcnContext vcnContext,
+                IkeSessionParams ikeSessionParams,
+                ChildSessionParams childSessionParams,
+                IkeSessionCallback ikeSessionCallback,
+                ChildSessionCallback childSessionCallback) {
+            mImpl =
+                    new IkeSession(
+                            vcnContext.getContext(),
+                            ikeSessionParams,
+                            childSessionParams,
+                            new HandlerExecutor(new Handler(vcnContext.getLooper())),
+                            ikeSessionCallback,
+                            childSessionCallback);
+        }
+
+        /** Creates a new IKE Child session. */
+        public void openChildSession(
+                @NonNull ChildSessionParams childSessionParams,
+                @NonNull ChildSessionCallback childSessionCallback) {
+            mImpl.openChildSession(childSessionParams, childSessionCallback);
+        }
+
+        /** Closes an IKE session as identified by the ChildSessionCallback. */
+        public void closeChildSession(@NonNull ChildSessionCallback childSessionCallback) {
+            mImpl.closeChildSession(childSessionCallback);
+        }
+
+        /** Gracefully closes this IKE Session, waiting for remote acknowledgement. */
+        public void close() {
+            mImpl.close();
+        }
+
+        /** Forcibly kills this IKE Session, without waiting for a closure confirmation. */
+        public void kill() {
+            mImpl.kill();
+        }
+
+        /** Sets the underlying network used by the IkeSession. */
+        public void setNetwork(@NonNull Network network) {
+            mImpl.setNetwork(network);
+        }
+
+        /** Dumps the state of the IkeSession */
+        public void dump(@NonNull IndentingPrintWriter pw) {
+            mImpl.dump(pw);
+        }
+    }
+
+    /** Proxy Implementation of WakeLock, used for testing. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class VcnWakeLock {
+        private final WakeLock mImpl;
+
+        public VcnWakeLock(@NonNull Context context, int flags, @NonNull String tag) {
+            final PowerManager powerManager = context.getSystemService(PowerManager.class);
+            mImpl = powerManager.newWakeLock(flags, tag);
+            mImpl.setReferenceCounted(false /* isReferenceCounted */);
+        }
+
+        /**
+         * Acquire this WakeLock.
+         *
+         * <p>Synchronize this action to minimize locking around WakeLock use.
+         */
+        public synchronized void acquire() {
+            mImpl.acquire();
+        }
+
+        /**
+         * Release this Wakelock.
+         *
+         * <p>Synchronize this action to minimize locking around WakeLock use.
+         */
+        public synchronized void release() {
+            mImpl.release();
+        }
+    }
+
+    /** Proxy Implementation of NetworkAgent, used for testing. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class VcnNetworkAgent {
+        private final NetworkAgent mImpl;
+
+        public VcnNetworkAgent(
+                @NonNull VcnContext vcnContext,
+                @NonNull String tag,
+                @NonNull NetworkCapabilities caps,
+                @NonNull LinkProperties lp,
+                @NonNull NetworkScore score,
+                @NonNull NetworkAgentConfig nac,
+                @NonNull NetworkProvider provider,
+                @NonNull Consumer<VcnNetworkAgent> networkUnwantedCallback,
+                @NonNull Consumer<Integer> validationStatusCallback) {
+            mImpl =
+                    new NetworkAgent(
+                            vcnContext.getContext(),
+                            vcnContext.getLooper(),
+                            tag,
+                            caps,
+                            lp,
+                            score,
+                            nac,
+                            provider) {
+                        @Override
+                        public void onNetworkUnwanted() {
+                            networkUnwantedCallback.accept(VcnNetworkAgent.this);
+                        }
+
+                        @Override
+                        public void onValidationStatus(int status, @Nullable Uri redirectUri) {
+                            validationStatusCallback.accept(status);
+                        }
+                    };
+        }
+
+        /** Registers the underlying NetworkAgent */
+        public void register() {
+            mImpl.register();
+        }
+
+        /** Marks the underlying NetworkAgent as connected */
+        public void markConnected() {
+            mImpl.markConnected();
+        }
+
+        /** Unregisters the underlying NetworkAgent */
+        public void unregister() {
+            mImpl.unregister();
+        }
+
+        /** Sends new NetworkCapabilities for the underlying NetworkAgent */
+        public void sendNetworkCapabilities(@NonNull NetworkCapabilities caps) {
+            mImpl.sendNetworkCapabilities(caps);
+        }
+
+        /** Sends new LinkProperties for the underlying NetworkAgent */
+        public void sendLinkProperties(@NonNull LinkProperties lp) {
+            mImpl.sendLinkProperties(lp);
+        }
+
+        /** Sends new NetworkCapabilities for the underlying NetworkAgent */
+        public void setUnderlyingNetworks(@Nullable List<Network> underlyingNetworks) {
+            mImpl.setUnderlyingNetworks(underlyingNetworks);
+        }
+
+        /** Retrieves the Network for the underlying NetworkAgent */
+        @Nullable
+        public Network getNetwork() {
+            return mImpl.getNetwork();
+        }
+    }
+}
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
new file mode 100644
index 0000000..4552f50
--- /dev/null
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+
+import static com.android.server.VcnManagementService.VDBG;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.modules.utils.HandlerExecutor;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * VCN Network Provider routes NetworkRequests to listeners to bring up tunnels as needed.
+ *
+ * <p>The VcnNetworkProvider provides a caching layer to ensure that all listeners receive all
+ * active NetworkRequest(s), including ones that were filed prior to listener registration.
+ *
+ * @hide
+ */
+public class VcnNetworkProvider extends NetworkProvider {
+    private static final String TAG = VcnNetworkProvider.class.getSimpleName();
+
+    private final Set<NetworkRequestListener> mListeners = new ArraySet<>();
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final Dependencies mDeps;
+
+    /**
+     * Cache of NetworkRequest(s).
+     *
+     * <p>NetworkRequests are immutable once created, and therefore can be used as stable keys.
+     */
+    private final Set<NetworkRequest> mRequests = new ArraySet<>();
+
+    public VcnNetworkProvider(@NonNull Context context, @NonNull Looper looper) {
+        this(context, looper, new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public VcnNetworkProvider(
+            @NonNull Context context, @NonNull Looper looper, @NonNull Dependencies dependencies) {
+        super(
+                Objects.requireNonNull(context, "Missing context"),
+                Objects.requireNonNull(looper, "Missing looper"),
+                TAG);
+
+        mContext = context;
+        mHandler = new Handler(looper);
+        mDeps = Objects.requireNonNull(dependencies, "Missing dependencies");
+    }
+
+    /** Registers this VcnNetworkProvider and a generic network offer with ConnectivityService. */
+    public void register() {
+        mContext.getSystemService(ConnectivityManager.class).registerNetworkProvider(this);
+        mDeps.registerNetworkOffer(
+                this,
+                Vcn.getNetworkScore(), // score filter
+                buildCapabilityFilter(),
+                new HandlerExecutor(mHandler),
+                new NetworkOfferCallback() {
+                    @Override
+                    public void onNetworkNeeded(@NonNull NetworkRequest request) {
+                        handleNetworkRequested(request);
+                    }
+
+                    @Override
+                    public void onNetworkUnneeded(@NonNull NetworkRequest request) {
+                        handleNetworkRequestWithdrawn(request);
+                    }
+                });
+    }
+
+    /** Builds the filter for NetworkRequests that can be served by the VcnNetworkProvider. */
+    private NetworkCapabilities buildCapabilityFilter() {
+        final NetworkCapabilities.Builder builder =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(TRANSPORT_CELLULAR)
+                        .addCapability(NET_CAPABILITY_TRUSTED)
+                        .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                        .addCapability(NET_CAPABILITY_NOT_VPN)
+                        .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+
+        for (int cap : VcnGatewayConnectionConfig.ALLOWED_CAPABILITIES) {
+            builder.addCapability(cap);
+        }
+
+        return builder.build();
+    }
+
+    /**
+     * Registers a NetworkRequestListener with this NetworkProvider.
+     *
+     * <p>Upon registering, the provided listener will receive all cached requests.
+     */
+    @VisibleForTesting(visibility = Visibility.PACKAGE)
+    public void registerListener(@NonNull NetworkRequestListener listener) {
+        mListeners.add(listener);
+
+        // Send listener all cached requests
+        resendAllRequests(listener);
+    }
+
+    /** Unregisters the specified listener from receiving future NetworkRequests. */
+    @VisibleForTesting(visibility = Visibility.PACKAGE)
+    public void unregisterListener(@NonNull NetworkRequestListener listener) {
+        mListeners.remove(listener);
+    }
+
+    /** Sends all cached NetworkRequest(s) to the specified listener. */
+    @VisibleForTesting(visibility = Visibility.PACKAGE)
+    public void resendAllRequests(@NonNull NetworkRequestListener listener) {
+        for (NetworkRequest request : mRequests) {
+            notifyListenerForEvent(listener, request);
+        }
+    }
+
+    private void notifyListenerForEvent(
+            @NonNull NetworkRequestListener listener, @NonNull NetworkRequest request) {
+        listener.onNetworkRequested(request);
+    }
+
+    private void handleNetworkRequested(@NonNull NetworkRequest request) {
+        if (VDBG) {
+            Slog.v(TAG, "Network requested: Request = " + request);
+        }
+
+        mRequests.add(request);
+
+        // TODO(b/176939047): Intelligently route requests to prioritized VcnInstances (based on
+        // Default Data Sub, or similar)
+        for (NetworkRequestListener listener : mListeners) {
+            notifyListenerForEvent(listener, request);
+        }
+    }
+
+    private void handleNetworkRequestWithdrawn(@NonNull NetworkRequest request) {
+        if (VDBG) {
+            Slog.v(TAG, "Network request withdrawn: Request = " + request);
+        }
+
+        mRequests.remove(request);
+    }
+
+    // package-private
+    interface NetworkRequestListener {
+        void onNetworkRequested(@NonNull NetworkRequest request);
+    }
+
+    /**
+     * Dumps the state of this VcnNetworkProvider for logging and debugging purposes.
+     *
+     * <p>PII and credentials MUST NEVER be dumped here.
+     */
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("VcnNetworkProvider:");
+        pw.increaseIndent();
+
+        pw.println("mListeners:");
+        pw.increaseIndent();
+        for (NetworkRequestListener listener : mListeners) {
+            pw.println(listener);
+        }
+        pw.decreaseIndent();
+        pw.println();
+
+        pw.println("mRequests:");
+        pw.increaseIndent();
+        for (NetworkRequest request : mRequests) {
+            pw.println(request);
+        }
+        pw.decreaseIndent();
+        pw.println();
+
+        pw.decreaseIndent();
+    }
+
+    /** Proxy class for dependencies used for testing. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        /** Registers a given network offer for the given provider. */
+        public void registerNetworkOffer(
+                @NonNull VcnNetworkProvider provider,
+                @NonNull NetworkScore score,
+                @NonNull NetworkCapabilities capabilitiesFilter,
+                @NonNull Executor executor,
+                @NonNull NetworkOfferCallback callback) {
+            provider.registerNetworkOffer(score, capabilitiesFilter, executor, callback);
+        }
+    }
+}
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
new file mode 100644
index 0000000..72de613
--- /dev/null
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -0,0 +1,593 @@
+/*
+ * 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.server.vcn.routeselection;
+
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.IpSecTransformState;
+import android.net.Network;
+import android.net.vcn.VcnManager;
+import android.os.Handler;
+import android.os.OutcomeReceiver;
+import android.os.PowerManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.modules.utils.HandlerExecutor;
+import com.android.server.vcn.VcnContext;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.BitSet;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * IpSecPacketLossDetector is responsible for continuously monitoring IPsec packet loss
+ *
+ * <p>When the packet loss rate surpass the threshold, IpSecPacketLossDetector will report it to the
+ * caller
+ *
+ * <p>IpSecPacketLossDetector will start monitoring when the network being monitored is selected AND
+ * an inbound IpSecTransform has been applied to this network.
+ *
+ * <p>This class is flag gated by "network_metric_monitor" and "ipsec_tramsform_state"
+ */
+public class IpSecPacketLossDetector extends NetworkMetricMonitor {
+    private static final String TAG = IpSecPacketLossDetector.class.getSimpleName();
+
+    private static final int PACKET_LOSS_PERCENT_UNAVAILABLE = -1;
+
+    // Ignore the packet loss detection result if the expected packet number is smaller than 10.
+    // Solarwinds NPM uses 10 ICMP echos to calculate packet loss rate (as per
+    // https://thwack.solarwinds.com/products/network-performance-monitor-npm/f/forum/63829/how-is-packet-loss-calculated)
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int MIN_VALID_EXPECTED_RX_PACKET_NUM = 10;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"PACKET_LOSS_"},
+            value = {
+                PACKET_LOSS_RATE_VALID,
+                PACKET_LOSS_RATE_INVALID,
+                PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP,
+            })
+    @Target({ElementType.TYPE_USE})
+    private @interface PacketLossResultType {}
+
+    /** Indicates a valid packet loss rate is available */
+    private static final int PACKET_LOSS_RATE_VALID = 0;
+
+    /**
+     * Indicates that the detector cannot get a valid packet loss rate due to one of the following
+     * reasons:
+     *
+     * <ul>
+     *   <li>The replay window did not proceed and thus all packets might have been delivered out of
+     *       order
+     *   <li>The expected received packet number is too small and thus the detection result is not
+     *       reliable
+     *   <li>There are unexpected errors
+     * </ul>
+     */
+    private static final int PACKET_LOSS_RATE_INVALID = 1;
+
+    /**
+     * The sequence number increase is unusually large and might be caused an intentional leap on
+     * the server's downlink
+     *
+     * <p>Inbound sequence number will not always increase consecutively. During load balancing the
+     * server might add a big leap on the sequence number intentionally. In such case a high packet
+     * loss rate does not always indicate a lossy network
+     */
+    private static final int PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP = 2;
+
+    // For VoIP, losses between 5% and 10% of the total packet stream will affect the quality
+    // significantly (as per "Computer Networking for LANS to WANS: Hardware, Software and
+    // Security"). For audio and video streaming, above 10-12% packet loss is unacceptable (as per
+    // "ICTP-SDU: About PingER"). Thus choose 12% as a conservative default threshold to declare a
+    // 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
+    private static final int MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED = -1;
+
+    private long mPollIpSecStateIntervalMs;
+    private int mPacketLossRatePercentThreshold;
+    private int mMaxSeqNumIncreasePerSecond;
+
+    @NonNull private final Handler mHandler;
+    @NonNull private final PowerManager mPowerManager;
+    @NonNull private final ConnectivityManager mConnectivityManager;
+    @NonNull private final Object mCancellationToken = new Object();
+    @NonNull private final PacketLossCalculator mPacketLossCalculator;
+
+    @Nullable private IpSecTransformWrapper mInboundTransform;
+    @Nullable private IpSecTransformState mLastIpSecTransformState;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public IpSecPacketLossDetector(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkMetricMonitorCallback callback,
+            @NonNull Dependencies deps)
+            throws IllegalAccessException {
+        super(vcnContext, network, carrierConfig, callback);
+
+        Objects.requireNonNull(deps, "Missing deps");
+
+        mHandler = new Handler(getVcnContext().getLooper());
+
+        mPowerManager = getVcnContext().getContext().getSystemService(PowerManager.class);
+        mConnectivityManager =
+                getVcnContext().getContext().getSystemService(ConnectivityManager.class);
+
+        mPacketLossCalculator = deps.getPacketLossCalculator();
+
+        mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
+        mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+        mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
+
+        // Register for system broadcasts to monitor idle mode change
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        getVcnContext()
+                .getContext()
+                .registerReceiver(
+                        new BroadcastReceiver() {
+                            @Override
+                            public void onReceive(Context context, Intent intent) {
+                                if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(
+                                                intent.getAction())
+                                        && mPowerManager.isDeviceIdleMode()) {
+                                    mLastIpSecTransformState = null;
+                                }
+                            }
+                        },
+                        intentFilter,
+                        null /* broadcastPermission not required */,
+                        mHandler);
+    }
+
+    public IpSecPacketLossDetector(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkMetricMonitorCallback callback)
+            throws IllegalAccessException {
+        this(vcnContext, network, carrierConfig, callback, new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        public PacketLossCalculator getPacketLossCalculator() {
+            return new PacketLossCalculator();
+        }
+    }
+
+    private static long getPollIpSecStateIntervalMs(
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        final int seconds;
+
+        if (carrierConfig != null) {
+            seconds =
+                    carrierConfig.getInt(
+                            VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
+                            POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT);
+        } else {
+            seconds = POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT;
+        }
+
+        return TimeUnit.SECONDS.toMillis(seconds);
+    }
+
+    private static int getPacketLossRatePercentThreshold(
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        if (carrierConfig != null) {
+            return carrierConfig.getInt(
+                    VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+                    IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT);
+        }
+        return IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static int getMaxSeqNumIncreasePerSecond(@Nullable PersistableBundleWrapper carrierConfig) {
+        int maxSeqNumIncrease = MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
+        if (carrierConfig != null) {
+            maxSeqNumIncrease =
+                    carrierConfig.getInt(
+                            VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
+                            MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED);
+        }
+
+        if (maxSeqNumIncrease < MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
+            logE(TAG, "Invalid value of MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY " + maxSeqNumIncrease);
+            return MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
+        }
+
+        return maxSeqNumIncrease;
+    }
+
+    @Override
+    protected void onSelectedUnderlyingNetworkChanged() {
+        if (!isSelectedUnderlyingNetwork()) {
+            mInboundTransform = null;
+            stop();
+        }
+
+        // No action when the underlying network got selected. Wait for the inbound transform to
+        // start the monitor
+    }
+
+    @Override
+    public void setInboundTransformInternal(@NonNull IpSecTransformWrapper inboundTransform) {
+        Objects.requireNonNull(inboundTransform, "inboundTransform is null");
+
+        if (Objects.equals(inboundTransform, mInboundTransform)) {
+            return;
+        }
+
+        if (!isSelectedUnderlyingNetwork()) {
+            logWtf("setInboundTransform called but network not selected");
+            return;
+        }
+
+        // When multiple parallel inbound transforms are created, NetworkMetricMonitor will be
+        // enabled on the last one as a sample
+        mInboundTransform = inboundTransform;
+
+        if (canStart()) {
+            start();
+        }
+    }
+
+    @Override
+    public void setCarrierConfig(@Nullable PersistableBundleWrapper carrierConfig) {
+        // The already scheduled event will not be affected. The followup events will be scheduled
+        // with the new interval
+        mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
+
+        mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+        mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
+
+        if (canStart() != isStarted()) {
+            if (canStart()) {
+                start();
+            } else {
+                stop();
+            }
+        }
+    }
+
+    @Override
+    public void onLinkPropertiesOrCapabilitiesChanged() {
+        if (!isStarted()) return;
+
+        reschedulePolling();
+    }
+
+    private void reschedulePolling() {
+        mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+        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();
+        clearTransformStateAndPollingEvents();
+        mHandler.postDelayed(new PollIpSecStateRunnable(), mCancellationToken, 0L);
+    }
+
+    @Override
+    public void stop() {
+        super.stop();
+        clearTransformStateAndPollingEvents();
+    }
+
+    private void clearTransformStateAndPollingEvents() {
+        mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+        mLastIpSecTransformState = null;
+    }
+
+    @Override
+    public void close() {
+        super.close();
+
+        if (mInboundTransform != null) {
+            mInboundTransform.close();
+        }
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @Nullable
+    public IpSecTransformState getLastTransformState() {
+        return mLastIpSecTransformState;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    @Nullable
+    public IpSecTransformWrapper getInboundTransformInternal() {
+        return mInboundTransform;
+    }
+
+    private class PollIpSecStateRunnable implements Runnable {
+        @Override
+        public void run() {
+            if (!isStarted()) {
+                logWtf("Monitor stopped but PollIpSecStateRunnable not removed from Handler");
+                return;
+            }
+
+            getInboundTransformInternal()
+                    .requestIpSecTransformState(
+                            new HandlerExecutor(mHandler), new IpSecTransformStateReceiver());
+
+            // Schedule for next poll
+            mHandler.postDelayed(
+                    new PollIpSecStateRunnable(), mCancellationToken, mPollIpSecStateIntervalMs);
+        }
+    }
+
+    private class IpSecTransformStateReceiver
+            implements OutcomeReceiver<IpSecTransformState, RuntimeException> {
+        @Override
+        public void onResult(@NonNull IpSecTransformState state) {
+            getVcnContext().ensureRunningOnLooperThread();
+
+            if (!isStarted()) {
+                return;
+            }
+
+            onIpSecTransformStateReceived(state);
+        }
+
+        @Override
+        public void onError(@NonNull RuntimeException error) {
+            getVcnContext().ensureRunningOnLooperThread();
+
+            // Nothing we can do here
+            logW("TransformStateReceiver#onError " + error.toString());
+        }
+    }
+
+    private void onIpSecTransformStateReceived(@NonNull IpSecTransformState state) {
+        if (mLastIpSecTransformState == null) {
+            // This is first time to poll the state
+            mLastIpSecTransformState = state;
+            return;
+        }
+
+        final PacketLossCalculationResult calculateResult =
+                mPacketLossCalculator.getPacketLossRatePercentage(
+                        mLastIpSecTransformState,
+                        state,
+                        mMaxSeqNumIncreasePerSecond,
+                        getLogPrefix());
+
+        if (calculateResult.getResultType() == PACKET_LOSS_RATE_INVALID) {
+            return;
+        }
+
+        final String logMsg =
+                "calculateResult: "
+                        + calculateResult
+                        + "% in the past "
+                        + (state.getTimestampMillis()
+                                - mLastIpSecTransformState.getTimestampMillis())
+                        + "ms";
+
+        mLastIpSecTransformState = state;
+        if (calculateResult.getPacketLossRatePercent() < mPacketLossRatePercentThreshold) {
+            logV(logMsg);
+
+            // In both "valid" or "unusual_seq_num_leap" cases, notify that the network has passed
+            // the validation
+            onValidationResultReceivedInternal(false /* isFailed */);
+        } else {
+            logInfo(logMsg);
+
+            if (calculateResult.getResultType() == PACKET_LOSS_RATE_VALID) {
+                onValidationResultReceivedInternal(true /* isFailed */);
+            }
+
+            // In both "invalid" and "unusual_seq_num_leap" cases, trigger network validation. If
+            // validation fails, the VCN will attempt to migrate away.
+            mConnectivityManager.reportNetworkConnectivity(
+                    getNetwork(), false /* hasConnectivity */);
+        }
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class PacketLossCalculator {
+        /** Calculate the packet loss rate between two timestamps */
+        public PacketLossCalculationResult getPacketLossRatePercentage(
+                @NonNull IpSecTransformState oldState,
+                @NonNull IpSecTransformState newState,
+                int maxSeqNumIncreasePerSecond,
+                String logPrefix) {
+            logVIpSecTransform("oldState", oldState, logPrefix);
+            logVIpSecTransform("newState", newState, logPrefix);
+
+            final int replayWindowSize = oldState.getReplayBitmap().length * 8;
+            final long oldSeqHi = oldState.getRxHighestSequenceNumber();
+            final long oldSeqLow = Math.max(0L, oldSeqHi - replayWindowSize + 1);
+            final long newSeqHi = newState.getRxHighestSequenceNumber();
+            final long newSeqLow = Math.max(0L, newSeqHi - replayWindowSize + 1);
+
+            if (oldSeqHi == newSeqHi || newSeqHi < replayWindowSize) {
+                // The replay window did not proceed and all packets might have been delivered out
+                // of order
+                return PacketLossCalculationResult.invalid();
+            }
+
+            boolean isUnusualSeqNumLeap = false;
+
+            // Handle sequence number leap
+            if (maxSeqNumIncreasePerSecond != MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
+                final long timeDiffMillis =
+                        newState.getTimestampMillis() - oldState.getTimestampMillis();
+                final long maxSeqNumIncrease = timeDiffMillis * maxSeqNumIncreasePerSecond / 1000;
+
+                // Sequence numbers are unsigned 32-bit values. If maxSeqNumIncrease overflows,
+                // isUnusualSeqNumLeap can never be true.
+                if (maxSeqNumIncrease >= 0 && newSeqHi - oldSeqHi >= maxSeqNumIncrease) {
+                    isUnusualSeqNumLeap = true;
+                }
+            }
+
+            // Get the expected packet count by assuming there is no packet loss. In this case, SA
+            // should receive all packets whose sequence numbers are smaller than the lower bound of
+            // the replay window AND the packets received within the window.
+            // When the lower bound is 0, it's not possible to tell whether packet with seqNo 0 is
+            // received or not. For simplicity just assume that packet is received.
+            final long newExpectedPktCnt = newSeqLow + getPacketCntInReplayWindow(newState);
+            final long oldExpectedPktCnt = oldSeqLow + getPacketCntInReplayWindow(oldState);
+
+            final long expectedPktCntDiff = newExpectedPktCnt - oldExpectedPktCnt;
+            final long actualPktCntDiff = newState.getPacketCount() - oldState.getPacketCount();
+
+            logV(
+                    TAG,
+                    logPrefix
+                            + " expectedPktCntDiff: "
+                            + expectedPktCntDiff
+                            + " actualPktCntDiff: "
+                            + actualPktCntDiff);
+
+            if (expectedPktCntDiff < MIN_VALID_EXPECTED_RX_PACKET_NUM) {
+                // The sample size is too small to ensure a reliable detection result
+                return PacketLossCalculationResult.invalid();
+            }
+
+            if (expectedPktCntDiff < 0
+                    || expectedPktCntDiff == 0
+                    || actualPktCntDiff < 0
+                    || actualPktCntDiff > expectedPktCntDiff) {
+                logWtf(TAG, "Impossible values for expectedPktCntDiff or" + " actualPktCntDiff");
+                return PacketLossCalculationResult.invalid();
+            }
+
+            final int percent = 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+            return isUnusualSeqNumLeap
+                    ? PacketLossCalculationResult.unusualSeqNumLeap(percent)
+                    : PacketLossCalculationResult.valid(percent);
+        }
+    }
+
+    private static void logVIpSecTransform(
+            String transformTag, IpSecTransformState state, String logPrefix) {
+        final String stateString =
+                " seqNo: "
+                        + state.getRxHighestSequenceNumber()
+                        + " | pktCnt: "
+                        + state.getPacketCount()
+                        + " | pktCntInWindow: "
+                        + getPacketCntInReplayWindow(state);
+        logV(TAG, logPrefix + " " + transformTag + stateString);
+    }
+
+    /** Get the number of received packets within the replay window */
+    private static long getPacketCntInReplayWindow(@NonNull IpSecTransformState state) {
+        return BitSet.valueOf(state.getReplayBitmap()).cardinality();
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class PacketLossCalculationResult {
+        @PacketLossResultType private final int mResultType;
+        private final int mPacketLossRatePercent;
+
+        private PacketLossCalculationResult(@PacketLossResultType int type, int percent) {
+            mResultType = type;
+            mPacketLossRatePercent = percent;
+        }
+
+        /** Construct an instance that contains a valid packet loss rate */
+        public static PacketLossCalculationResult valid(int percent) {
+            return new PacketLossCalculationResult(PACKET_LOSS_RATE_VALID, percent);
+        }
+
+        /** Construct an instance indicating the inability to get a valid packet loss rate */
+        public static PacketLossCalculationResult invalid() {
+            return new PacketLossCalculationResult(
+                    PACKET_LOSS_RATE_INVALID, PACKET_LOSS_PERCENT_UNAVAILABLE);
+        }
+
+        /** Construct an instance indicating that there is an unusual sequence number leap */
+        public static PacketLossCalculationResult unusualSeqNumLeap(int percent) {
+            return new PacketLossCalculationResult(PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP, percent);
+        }
+
+        @PacketLossResultType
+        public int getResultType() {
+            return mResultType;
+        }
+
+        public int getPacketLossRatePercent() {
+            return mPacketLossRatePercent;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mResultType, mPacketLossRatePercent);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof PacketLossCalculationResult)) {
+                return false;
+            }
+
+            final PacketLossCalculationResult rhs = (PacketLossCalculationResult) other;
+            return mResultType == rhs.mResultType
+                    && mPacketLossRatePercent == rhs.mPacketLossRatePercent;
+        }
+
+        @Override
+        public String toString() {
+            return "mResultType: "
+                    + mResultType
+                    + " | mPacketLossRatePercent: "
+                    + mPacketLossRatePercent;
+        }
+    }
+}
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
new file mode 100644
index 0000000..86cee55
--- /dev/null
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -0,0 +1,279 @@
+/*
+ * 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.server.vcn.routeselection;
+
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import static com.android.server.VcnManagementService.LOCAL_LOG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.IpSecTransform;
+import android.net.IpSecTransformState;
+import android.net.Network;
+import android.os.OutcomeReceiver;
+import android.util.CloseGuard;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.VcnContext;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * NetworkMetricMonitor is responsible for managing metric monitoring and tracking validation
+ * results.
+ *
+ * <p>This class is flag gated by "network_metric_monitor"
+ */
+public abstract class NetworkMetricMonitor implements AutoCloseable {
+    private static final String TAG = NetworkMetricMonitor.class.getSimpleName();
+
+    private static final boolean VDBG = false; // STOPSHIP: if true
+
+    @NonNull private final CloseGuard mCloseGuard = new CloseGuard();
+
+    @NonNull private final VcnContext mVcnContext;
+    @NonNull private final Network mNetwork;
+    @NonNull private final NetworkMetricMonitorCallback mCallback;
+
+    private boolean mIsSelectedUnderlyingNetwork;
+    private boolean mIsStarted;
+    private boolean mIsValidationFailed;
+
+    protected NetworkMetricMonitor(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkMetricMonitorCallback callback)
+            throws IllegalAccessException {
+        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+        mNetwork = Objects.requireNonNull(network, "Missing network");
+        mCallback = Objects.requireNonNull(callback, "Missing callback");
+
+        mIsSelectedUnderlyingNetwork = false;
+        mIsStarted = false;
+        mIsValidationFailed = false;
+    }
+
+    /** Callback to notify caller of the validation result */
+    public interface NetworkMetricMonitorCallback {
+        /** Called when there is a validation result is ready */
+        void onValidationResultReceived();
+    }
+
+    /**
+     * Start monitoring
+     *
+     * <p>This method might be called on a an already started monitor for updating monitor
+     * properties (e.g. IpSecTransform, carrier config)
+     *
+     * <p>Subclasses MUST call super.start() when overriding this method
+     */
+    protected void start() {
+        mIsStarted = true;
+    }
+
+    /**
+     * Stop monitoring
+     *
+     * <p>Subclasses MUST call super.stop() when overriding this method
+     */
+    public void stop() {
+        mIsValidationFailed = false;
+        mIsStarted = false;
+    }
+
+    /** Called by the subclasses when the validation result is ready */
+    protected void onValidationResultReceivedInternal(boolean isFailed) {
+        mIsValidationFailed = isFailed;
+        mCallback.onValidationResultReceived();
+    }
+
+    /** Called when the underlying network changes to selected or unselected */
+    protected abstract void onSelectedUnderlyingNetworkChanged();
+
+    /**
+     * Mark the network being monitored selected or unselected
+     *
+     * <p>Subclasses MUST call super when overriding this method
+     */
+    public void setIsSelectedUnderlyingNetwork(boolean isSelectedUnderlyingNetwork) {
+        if (mIsSelectedUnderlyingNetwork == isSelectedUnderlyingNetwork) {
+            return;
+        }
+
+        mIsSelectedUnderlyingNetwork = isSelectedUnderlyingNetwork;
+        onSelectedUnderlyingNetworkChanged();
+    }
+
+    /** Wrapper that allows injection for testing purposes */
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public static class IpSecTransformWrapper {
+        @NonNull public final IpSecTransform ipSecTransform;
+
+        public IpSecTransformWrapper(@NonNull IpSecTransform ipSecTransform) {
+            this.ipSecTransform = ipSecTransform;
+        }
+
+        /** Poll an IpSecTransformState */
+        public void requestIpSecTransformState(
+                @NonNull Executor executor,
+                @NonNull OutcomeReceiver<IpSecTransformState, RuntimeException> callback) {
+            ipSecTransform.requestIpSecTransformState(executor, callback);
+        }
+
+        /** Close this instance and release the underlying resources */
+        public void close() {
+            ipSecTransform.close();
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(ipSecTransform);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof IpSecTransformWrapper)) {
+                return false;
+            }
+
+            final IpSecTransformWrapper other = (IpSecTransformWrapper) o;
+
+            return Objects.equals(ipSecTransform, other.ipSecTransform);
+        }
+    }
+
+    /** Set the IpSecTransform that applied to the Network being monitored */
+    public void setInboundTransform(@NonNull IpSecTransform inTransform) {
+        setInboundTransformInternal(new IpSecTransformWrapper(inTransform));
+    }
+
+    /**
+     * Set the IpSecTransform that applied to the Network being monitored *
+     *
+     * <p>Subclasses MUST call super when overriding this method
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public void setInboundTransformInternal(@NonNull IpSecTransformWrapper inTransform) {
+        // Subclasses MUST override it if they care
+    }
+
+    /** Update the carrierconfig */
+    public void setCarrierConfig(@Nullable PersistableBundleWrapper carrierConfig) {
+        // Subclasses MUST override it if they care
+    }
+
+    /** Called when LinkProperties or NetworkCapabilities have changed */
+    public void onLinkPropertiesOrCapabilitiesChanged() {
+        // Subclasses MUST override it if they care
+    }
+
+    public boolean isValidationFailed() {
+        return mIsValidationFailed;
+    }
+
+    public boolean isSelectedUnderlyingNetwork() {
+        return mIsSelectedUnderlyingNetwork;
+    }
+
+    public boolean isStarted() {
+        return mIsStarted;
+    }
+
+    @NonNull
+    public VcnContext getVcnContext() {
+        return mVcnContext;
+    }
+
+    @NonNull
+    public Network getNetwork() {
+        return mNetwork;
+    }
+
+    // Override methods for AutoCloseable. Subclasses MUST call super when overriding this method
+    @Override
+    public void close() {
+        mCloseGuard.close();
+
+        stop();
+    }
+
+    // Override #finalize() to use closeGuard for flagging that #close() was not called
+    @SuppressWarnings("Finalize")
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private String getClassName() {
+        return this.getClass().getSimpleName();
+    }
+
+    protected String getLogPrefix() {
+        return " [Network " + mNetwork + "] ";
+    }
+
+    protected void logV(String msg) {
+        if (VDBG) {
+            Slog.v(getClassName(), getLogPrefix() + msg);
+            LOCAL_LOG.log("[VERBOSE ] " + getClassName() + getLogPrefix() + msg);
+        }
+    }
+
+    protected void logInfo(String msg) {
+        Slog.i(getClassName(), getLogPrefix() + msg);
+        LOCAL_LOG.log("[INFO ] " + getClassName() + getLogPrefix() + msg);
+    }
+
+    protected void logW(String msg) {
+        Slog.w(getClassName(), getLogPrefix() + msg);
+        LOCAL_LOG.log("[WARN ] " + getClassName() + getLogPrefix() + msg);
+    }
+
+    protected void logWtf(String msg) {
+        Slog.wtf(getClassName(), getLogPrefix() + msg);
+        LOCAL_LOG.log("[WTF ] " + getClassName() + getLogPrefix() + msg);
+    }
+
+    protected static void logV(String className, String msgWithPrefix) {
+        if (VDBG) {
+            Slog.wtf(className, msgWithPrefix);
+            LOCAL_LOG.log("[VERBOSE ] " + className + msgWithPrefix);
+        }
+    }
+
+    protected static void logE(String className, String msgWithPrefix) {
+        Slog.w(className, msgWithPrefix);
+        LOCAL_LOG.log("[ERROR ] " + className + msgWithPrefix);
+    }
+
+    protected static void logWtf(String className, String msgWithPrefix) {
+        Slog.wtf(className, msgWithPrefix);
+        LOCAL_LOG.log("[WTF ] " + className + msgWithPrefix);
+    }
+}
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
new file mode 100644
index 0000000..79c4116
--- /dev/null
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.vcn.routeselection;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import static com.android.server.VcnManagementService.LOCAL_LOG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
+import android.net.vcn.VcnManager;
+import android.net.vcn.VcnUnderlyingNetworkTemplate;
+import android.net.vcn.VcnWifiUnderlyingNetworkTemplate;
+import android.os.ParcelUuid;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** @hide */
+class NetworkPriorityClassifier {
+    @NonNull private static final String TAG = NetworkPriorityClassifier.class.getSimpleName();
+    /**
+     * Minimum signal strength for a WiFi network to be eligible for switching to
+     *
+     * <p>A network that satisfies this is eligible to become the selected underlying network with
+     * no additional conditions
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT = -70;
+    /**
+     * Minimum signal strength to continue using a WiFi network
+     *
+     * <p>A network that satisfies the conditions may ONLY continue to be used if it is already
+     * selected as the underlying network. A WiFi network satisfying this condition, but NOT the
+     * prospective-network RSSI threshold CANNOT be switched to.
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74;
+
+    /**
+     * Priority for networks that VCN can fall back to.
+     *
+     * <p>If none of the network candidates are validated or match any template, VCN will fall back
+     * to any INTERNET network.
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int PRIORITY_FALLBACK = Integer.MAX_VALUE;
+
+    /**
+     * Priority for networks that cannot be selected as VCN's underlying networks.
+     *
+     * <p>VCN MUST never select a non-INTERNET network that are unvalidated or fail to match any
+     * template as the underlying network.
+     */
+    static final int PRIORITY_INVALID = -1;
+
+    /** Gives networks a priority class, based on configured VcnGatewayConnectionConfig */
+    public static int calculatePriorityClass(
+            VcnContext vcnContext,
+            UnderlyingNetworkRecord networkRecord,
+            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            ParcelUuid subscriptionGroup,
+            TelephonySubscriptionSnapshot snapshot,
+            boolean isSelected,
+            PersistableBundleWrapper carrierConfig) {
+        // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED
+
+        if (networkRecord.isBlocked) {
+            logWtf("Network blocked for System Server: " + networkRecord.network);
+            return PRIORITY_INVALID;
+        }
+
+        if (snapshot == null) {
+            logWtf("Got null snapshot");
+            return PRIORITY_INVALID;
+        }
+
+        int priorityIndex = 0;
+        for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkTemplates) {
+            if (checkMatchesPriorityRule(
+                    vcnContext,
+                    nwPriority,
+                    networkRecord,
+                    subscriptionGroup,
+                    snapshot,
+                    isSelected,
+                    carrierConfig)) {
+                return priorityIndex;
+            }
+            priorityIndex++;
+        }
+
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+        if (caps.hasCapability(NET_CAPABILITY_INTERNET)
+                || (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST))) {
+            return PRIORITY_FALLBACK;
+        }
+        return PRIORITY_INVALID;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static boolean checkMatchesPriorityRule(
+            VcnContext vcnContext,
+            VcnUnderlyingNetworkTemplate networkPriority,
+            UnderlyingNetworkRecord networkRecord,
+            ParcelUuid subscriptionGroup,
+            TelephonySubscriptionSnapshot snapshot,
+            boolean isSelected,
+            PersistableBundleWrapper carrierConfig) {
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+
+        final int meteredMatch = networkPriority.getMetered();
+        final boolean isMetered = !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
+        if (meteredMatch == MATCH_REQUIRED && !isMetered
+                || meteredMatch == MATCH_FORBIDDEN && isMetered) {
+            return false;
+        }
+
+        // Fails bandwidth requirements if either (a) less than exit threshold, or (b), not
+        // selected, but less than entry threshold
+        if (caps.getLinkUpstreamBandwidthKbps() < networkPriority.getMinExitUpstreamBandwidthKbps()
+                || (caps.getLinkUpstreamBandwidthKbps()
+                                < networkPriority.getMinEntryUpstreamBandwidthKbps()
+                        && !isSelected)) {
+            return false;
+        }
+
+        if (caps.getLinkDownstreamBandwidthKbps()
+                        < networkPriority.getMinExitDownstreamBandwidthKbps()
+                || (caps.getLinkDownstreamBandwidthKbps()
+                                < networkPriority.getMinEntryDownstreamBandwidthKbps()
+                        && !isSelected)) {
+            return false;
+        }
+
+        for (Map.Entry<Integer, Integer> entry :
+                networkPriority.getCapabilitiesMatchCriteria().entrySet()) {
+            final int cap = entry.getKey();
+            final int matchCriteria = entry.getValue();
+
+            if (matchCriteria == MATCH_REQUIRED && !caps.hasCapability(cap)) {
+                return false;
+            } else if (matchCriteria == MATCH_FORBIDDEN && caps.hasCapability(cap)) {
+                return false;
+            }
+        }
+
+        if (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST)) {
+            return true;
+        }
+
+        if (networkPriority instanceof VcnWifiUnderlyingNetworkTemplate) {
+            return checkMatchesWifiPriorityRule(
+                    (VcnWifiUnderlyingNetworkTemplate) networkPriority,
+                    networkRecord,
+                    isSelected,
+                    carrierConfig);
+        }
+
+        if (networkPriority instanceof VcnCellUnderlyingNetworkTemplate) {
+            return checkMatchesCellPriorityRule(
+                    vcnContext,
+                    (VcnCellUnderlyingNetworkTemplate) networkPriority,
+                    networkRecord,
+                    subscriptionGroup,
+                    snapshot);
+        }
+
+        logWtf(
+                "Got unknown VcnUnderlyingNetworkTemplate class: "
+                        + networkPriority.getClass().getSimpleName());
+        return false;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static boolean checkMatchesWifiPriorityRule(
+            VcnWifiUnderlyingNetworkTemplate networkPriority,
+            UnderlyingNetworkRecord networkRecord,
+            boolean isSelected,
+            PersistableBundleWrapper carrierConfig) {
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+
+        if (!caps.hasTransport(TRANSPORT_WIFI)) {
+            return false;
+        }
+
+        // TODO: Move the Network Quality check to the network metric monitor framework.
+        if (!isWifiRssiAcceptable(networkRecord, isSelected, carrierConfig)) {
+            return false;
+        }
+
+        if (!networkPriority.getSsids().isEmpty()
+                && !networkPriority.getSsids().contains(caps.getSsid())) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private static boolean isWifiRssiAcceptable(
+            UnderlyingNetworkRecord networkRecord,
+            boolean isSelected,
+            PersistableBundleWrapper carrierConfig) {
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+
+        if (isSelected && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
+            return true;
+        }
+
+        if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static boolean checkMatchesCellPriorityRule(
+            VcnContext vcnContext,
+            VcnCellUnderlyingNetworkTemplate networkPriority,
+            UnderlyingNetworkRecord networkRecord,
+            ParcelUuid subscriptionGroup,
+            TelephonySubscriptionSnapshot snapshot) {
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+
+        if (!caps.hasTransport(TRANSPORT_CELLULAR)) {
+            return false;
+        }
+
+        final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+                ((TelephonyNetworkSpecifier) caps.getNetworkSpecifier());
+        if (telephonyNetworkSpecifier == null) {
+            logWtf("Got null NetworkSpecifier");
+            return false;
+        }
+
+        final int subId = telephonyNetworkSpecifier.getSubscriptionId();
+        final TelephonyManager subIdSpecificTelephonyMgr =
+                vcnContext
+                        .getContext()
+                        .getSystemService(TelephonyManager.class)
+                        .createForSubscriptionId(subId);
+
+        if (!networkPriority.getOperatorPlmnIds().isEmpty()) {
+            final String plmnId = subIdSpecificTelephonyMgr.getNetworkOperator();
+            if (!networkPriority.getOperatorPlmnIds().contains(plmnId)) {
+                return false;
+            }
+        }
+
+        if (!networkPriority.getSimSpecificCarrierIds().isEmpty()) {
+            final int carrierId = subIdSpecificTelephonyMgr.getSimSpecificCarrierId();
+            if (!networkPriority.getSimSpecificCarrierIds().contains(carrierId)) {
+                return false;
+            }
+        }
+
+        final int roamingMatch = networkPriority.getRoaming();
+        final boolean isRoaming = !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+        if (roamingMatch == MATCH_REQUIRED && !isRoaming
+                || roamingMatch == MATCH_FORBIDDEN && isRoaming) {
+            return false;
+        }
+
+        final int opportunisticMatch = networkPriority.getOpportunistic();
+        final boolean isOpportunistic = isOpportunistic(snapshot, caps.getSubscriptionIds());
+        if (opportunisticMatch == MATCH_REQUIRED) {
+            if (!isOpportunistic) {
+                return false;
+            }
+
+            // If this carrier is the active data provider, ensure that opportunistic is only
+            // ever prioritized if it is also the active data subscription. This ensures that
+            // if an opportunistic subscription is still in the process of being switched to,
+            // or switched away from, the VCN does not attempt to continue using it against the
+            // decision made at the telephony layer. Failure to do so may result in the modem
+            // switching back and forth.
+            //
+            // Allow the following two cases:
+            // 1. Active subId is NOT in the group that this VCN is supporting
+            // 2. This opportunistic subscription is for the active subId
+            if (snapshot.getAllSubIdsInGroup(subscriptionGroup)
+                            .contains(SubscriptionManager.getActiveDataSubscriptionId())
+                    && !caps.getSubscriptionIds()
+                            .contains(SubscriptionManager.getActiveDataSubscriptionId())) {
+                return false;
+            }
+        } else if (opportunisticMatch == MATCH_FORBIDDEN && !isOpportunistic) {
+            return false;
+        }
+
+        return true;
+    }
+
+    static boolean isOpportunistic(
+            @NonNull TelephonySubscriptionSnapshot snapshot, Set<Integer> subIds) {
+        if (snapshot == null) {
+            logWtf("Got null snapshot");
+            return false;
+        }
+        for (int subId : subIds) {
+            if (snapshot.isOpportunistic(subId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static int getWifiEntryRssiThreshold(@Nullable PersistableBundleWrapper carrierConfig) {
+        if (carrierConfig != null) {
+            return carrierConfig.getInt(
+                    VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
+                    WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT);
+        }
+        return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT;
+    }
+
+    static int getWifiExitRssiThreshold(@Nullable PersistableBundleWrapper carrierConfig) {
+        if (carrierConfig != null) {
+            return carrierConfig.getInt(
+                    VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
+                    WIFI_EXIT_RSSI_THRESHOLD_DEFAULT);
+        }
+        return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
+    }
+
+    private static void logWtf(String msg) {
+        Slog.wtf(TAG, msg);
+        LOCAL_LOG.log(TAG + " WTF: " + msg);
+    }
+}
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
new file mode 100644
index 0000000..29a0762
--- /dev/null
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -0,0 +1,753 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn.routeselection;
+
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
+
+import static com.android.server.VcnManagementService.LOCAL_LOG;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiEntryRssiThreshold;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiExitRssiThreshold;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IpSecTransform;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnUnderlyingNetworkTemplate;
+import android.net.vcn.util.LogUtils;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.modules.utils.HandlerExecutor;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Tracks a set of Networks underpinning a VcnGatewayConnection.
+ *
+ * <p>A single UnderlyingNetworkController is built to serve a SINGLE VCN Gateway Connection, and
+ * MUST be torn down with the VcnGatewayConnection in order to ensure underlying networks are
+ * allowed to be reaped.
+ *
+ * @hide
+ */
+public class UnderlyingNetworkController {
+    @NonNull private static final String TAG = UnderlyingNetworkController.class.getSimpleName();
+
+    @NonNull private final VcnContext mVcnContext;
+    @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
+    @NonNull private final ParcelUuid mSubscriptionGroup;
+    @NonNull private final UnderlyingNetworkControllerCallback mCb;
+    @NonNull private final Dependencies mDeps;
+    @NonNull private final Handler mHandler;
+    @NonNull private final ConnectivityManager mConnectivityManager;
+    @NonNull private final TelephonyCallback mActiveDataSubIdListener =
+            new VcnActiveDataSubscriptionIdListener();
+
+    private final Map<Network, UnderlyingNetworkEvaluator> mUnderlyingNetworkRecords =
+            new ArrayMap<>();
+
+    @NonNull private final List<NetworkCallback> mCellBringupCallbacks = new ArrayList<>();
+    @Nullable private NetworkCallback mWifiBringupCallback;
+    @Nullable private NetworkCallback mWifiEntryRssiThresholdCallback;
+    @Nullable private NetworkCallback mWifiExitRssiThresholdCallback;
+    @Nullable private UnderlyingNetworkListener mRouteSelectionCallback;
+
+    @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
+    @Nullable private PersistableBundleWrapper mCarrierConfig;
+    private boolean mIsQuitting = false;
+
+    @Nullable private UnderlyingNetworkRecord mCurrentRecord;
+    @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress;
+
+    public UnderlyingNetworkController(
+            @NonNull VcnContext vcnContext,
+            @NonNull VcnGatewayConnectionConfig connectionConfig,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot snapshot,
+            @NonNull UnderlyingNetworkControllerCallback cb) {
+        this(vcnContext, connectionConfig, subscriptionGroup, snapshot, cb, new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    UnderlyingNetworkController(
+            @NonNull VcnContext vcnContext,
+            @NonNull VcnGatewayConnectionConfig connectionConfig,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot snapshot,
+            @NonNull UnderlyingNetworkControllerCallback cb,
+            @NonNull Dependencies deps) {
+        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+        mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
+        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+        mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
+        mCb = Objects.requireNonNull(cb, "Missing cb");
+        mDeps = Objects.requireNonNull(deps, "Missing deps");
+
+        mHandler = new Handler(mVcnContext.getLooper());
+
+        mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
+        mVcnContext
+                .getContext()
+                .getSystemService(TelephonyManager.class)
+                .registerTelephonyCallback(new HandlerExecutor(mHandler), mActiveDataSubIdListener);
+
+        mCarrierConfig = mLastSnapshot.getCarrierConfigForSubGrp(mSubscriptionGroup);
+
+        registerOrUpdateNetworkRequests();
+    }
+
+    private static class CapabilityMatchCriteria {
+        public final int capability;
+        public final int matchCriteria;
+
+        CapabilityMatchCriteria(int capability, int matchCriteria) {
+            this.capability = capability;
+            this.matchCriteria = matchCriteria;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(capability, matchCriteria);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof CapabilityMatchCriteria)) {
+                return false;
+            }
+
+            final CapabilityMatchCriteria rhs = (CapabilityMatchCriteria) other;
+            return capability == rhs.capability && matchCriteria == rhs.matchCriteria;
+        }
+    }
+
+    private static Set<Set<CapabilityMatchCriteria>> dedupAndGetCapRequirementsForCell(
+            VcnGatewayConnectionConfig connectionConfig) {
+        final Set<Set<CapabilityMatchCriteria>> dedupedCapsMatchSets = new ArraySet<>();
+
+        for (VcnUnderlyingNetworkTemplate template :
+                connectionConfig.getVcnUnderlyingNetworkPriorities()) {
+            if (template instanceof VcnCellUnderlyingNetworkTemplate) {
+                final Set<CapabilityMatchCriteria> capsMatchSet = new ArraySet<>();
+
+                for (Map.Entry<Integer, Integer> entry :
+                        ((VcnCellUnderlyingNetworkTemplate) template)
+                                .getCapabilitiesMatchCriteria()
+                                .entrySet()) {
+
+                    final int capability = entry.getKey();
+                    final int matchCriteria = entry.getValue();
+                    if (matchCriteria != MATCH_ANY) {
+                        capsMatchSet.add(new CapabilityMatchCriteria(capability, matchCriteria));
+                    }
+                }
+
+                dedupedCapsMatchSets.add(capsMatchSet);
+            }
+        }
+
+        dedupedCapsMatchSets.add(
+                Collections.singleton(
+                        new CapabilityMatchCriteria(
+                                NetworkCapabilities.NET_CAPABILITY_INTERNET, MATCH_REQUIRED)));
+        return dedupedCapsMatchSets;
+    }
+
+    private void registerOrUpdateNetworkRequests() {
+        NetworkCallback oldRouteSelectionCallback = mRouteSelectionCallback;
+        NetworkCallback oldWifiCallback = mWifiBringupCallback;
+        NetworkCallback oldWifiEntryRssiThresholdCallback = mWifiEntryRssiThresholdCallback;
+        NetworkCallback oldWifiExitRssiThresholdCallback = mWifiExitRssiThresholdCallback;
+        List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks);
+        mCellBringupCallbacks.clear();
+
+        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+            evaluator.close();
+        }
+
+        mUnderlyingNetworkRecords.clear();
+
+        // Register new callbacks. Make-before-break; always register new callbacks before removal
+        // of old callbacks
+        if (!mIsQuitting) {
+            mRouteSelectionCallback = new UnderlyingNetworkListener();
+            mConnectivityManager.registerNetworkCallback(
+                    getRouteSelectionRequest(), mRouteSelectionCallback, mHandler);
+
+            mWifiEntryRssiThresholdCallback = new NetworkBringupCallback();
+            mConnectivityManager.registerNetworkCallback(
+                    getWifiEntryRssiThresholdNetworkRequest(),
+                    mWifiEntryRssiThresholdCallback,
+                    mHandler);
+
+            mWifiExitRssiThresholdCallback = new NetworkBringupCallback();
+            mConnectivityManager.registerNetworkCallback(
+                    getWifiExitRssiThresholdNetworkRequest(),
+                    mWifiExitRssiThresholdCallback,
+                    mHandler);
+
+            mWifiBringupCallback = new NetworkBringupCallback();
+            mConnectivityManager.requestBackgroundNetwork(
+                    getWifiNetworkRequest(), mWifiBringupCallback, mHandler);
+
+            for (final int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
+                for (Set<CapabilityMatchCriteria> capsMatchCriteria :
+                        dedupAndGetCapRequirementsForCell(mConnectionConfig)) {
+                    final NetworkBringupCallback cb = new NetworkBringupCallback();
+                    mCellBringupCallbacks.add(cb);
+
+                    mConnectivityManager.requestBackgroundNetwork(
+                            getCellNetworkRequestForSubId(subId, capsMatchCriteria), cb, mHandler);
+                }
+            }
+        } else {
+            mRouteSelectionCallback = null;
+            mWifiBringupCallback = null;
+            mWifiEntryRssiThresholdCallback = null;
+            mWifiExitRssiThresholdCallback = null;
+            // mCellBringupCallbacks already cleared above.
+        }
+
+        // Unregister old callbacks (as necessary)
+        if (oldRouteSelectionCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(oldRouteSelectionCallback);
+        }
+        if (oldWifiCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(oldWifiCallback);
+        }
+        if (oldWifiEntryRssiThresholdCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(oldWifiEntryRssiThresholdCallback);
+        }
+        if (oldWifiExitRssiThresholdCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(oldWifiExitRssiThresholdCallback);
+        }
+        for (NetworkCallback cellBringupCallback : oldCellCallbacks) {
+            mConnectivityManager.unregisterNetworkCallback(cellBringupCallback);
+        }
+    }
+
+    /**
+     * Builds the Route selection request
+     *
+     * <p>This request is guaranteed to select carrier-owned, non-VCN underlying networks by virtue
+     * of a populated set of subIds as expressed in NetworkCapabilities#getSubscriptionIds(). Only
+     * carrier owned networks may be selected, as the request specifies only subIds in the VCN's
+     * subscription group, while the VCN networks are excluded by virtue of not having subIds set on
+     * the VCN-exposed networks.
+     *
+     * <p>If the VCN that this UnderlyingNetworkController belongs to is in test-mode, this will
+     * return a NetworkRequest that only matches Test Networks.
+     */
+    private NetworkRequest getRouteSelectionRequest() {
+        if (mVcnContext.isInTestMode()) {
+            return getTestNetworkRequest(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup));
+        }
+
+        return getBaseNetworkRequestBuilder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+                .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
+                .build();
+    }
+
+    private NetworkRequest.Builder getBaseWifiNetworkRequestBuilder() {
+        return getBaseNetworkRequestBuilder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup));
+    }
+
+    /**
+     * Builds the WiFi bringup request
+     *
+     * <p>This request is built specifically to match only carrier-owned WiFi networks, but is also
+     * built to ONLY keep Carrier WiFi Networks alive (but never bring them up). This is a result of
+     * the WifiNetworkFactory not advertising a list of subIds, and therefore not accepting this
+     * request. As such, it will bind to a Carrier WiFi Network that has already been brought up,
+     * but will NEVER bring up a Carrier WiFi network itself.
+     */
+    private NetworkRequest getWifiNetworkRequest() {
+        return getBaseWifiNetworkRequestBuilder().build();
+    }
+
+    /**
+     * Builds the WiFi entry threshold signal strength request
+     *
+     * <p>This request ensures that WiFi reports the crossing of the wifi entry RSSI threshold.
+     * Without this request, WiFi rate-limits, and reports signal strength changes at too slow a
+     * pace to effectively select a short-lived WiFi offload network.
+     */
+    private NetworkRequest getWifiEntryRssiThresholdNetworkRequest() {
+        return getBaseWifiNetworkRequestBuilder()
+                // Ensure wifi updates signal strengths when crossing this threshold.
+                .setSignalStrength(getWifiEntryRssiThreshold(mCarrierConfig))
+                .build();
+    }
+
+    /**
+     * Builds the WiFi exit threshold signal strength request
+     *
+     * <p>This request ensures that WiFi reports the crossing of the wifi exit RSSI threshold.
+     * Without this request, WiFi rate-limits, and reports signal strength changes at too slow a
+     * pace to effectively select away from a failing WiFi network.
+     */
+    private NetworkRequest getWifiExitRssiThresholdNetworkRequest() {
+        return getBaseWifiNetworkRequestBuilder()
+                // Ensure wifi updates signal strengths when crossing this threshold.
+                .setSignalStrength(getWifiExitRssiThreshold(mCarrierConfig))
+                .build();
+    }
+
+    /**
+     * Builds a Cellular bringup request for a given subId
+     *
+     * <p>This request is filed in order to ensure that the Telephony stack always has a
+     * NetworkRequest to bring up a VCN underlying cellular network. It is required in order to
+     * ensure that even when a VCN (appears as Cellular) satisfies the default request, Telephony
+     * will bring up additional underlying Cellular networks.
+     *
+     * <p>Since this request MUST make it to the TelephonyNetworkFactory, subIds are not specified
+     * in the NetworkCapabilities, but rather in the TelephonyNetworkSpecifier.
+     */
+    private NetworkRequest getCellNetworkRequestForSubId(
+            int subId, Set<CapabilityMatchCriteria> capsMatchCriteria) {
+        final NetworkRequest.Builder nrBuilder =
+                getBaseNetworkRequestBuilder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .setNetworkSpecifier(
+                                new TelephonyNetworkSpecifier.Builder()
+                                        .setSubscriptionId(subId)
+                                        .build());
+
+        for (CapabilityMatchCriteria capMatchCriteria : capsMatchCriteria) {
+            final int cap = capMatchCriteria.capability;
+            final int matchCriteria = capMatchCriteria.matchCriteria;
+
+            if (matchCriteria == MATCH_REQUIRED) {
+                nrBuilder.addCapability(cap);
+            } else if (matchCriteria == MATCH_FORBIDDEN) {
+                nrBuilder.addForbiddenCapability(cap);
+            }
+        }
+
+        return nrBuilder.build();
+    }
+
+    /**
+     * Builds and returns a NetworkRequest builder common to all Underlying Network requests
+     */
+    private NetworkRequest.Builder getBaseNetworkRequestBuilder() {
+        return new NetworkRequest.Builder()
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+    }
+
+    /** Builds and returns a NetworkRequest for the given subIds to match Test Networks. */
+    private NetworkRequest getTestNetworkRequest(@NonNull Set<Integer> subIds) {
+        return new NetworkRequest.Builder()
+                .clearCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+                .setSubscriptionIds(subIds)
+                .build();
+    }
+
+    /**
+     * Update this UnderlyingNetworkController's TelephonySubscriptionSnapshot.
+     *
+     * <p>Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkController to
+     * reevaluate its NetworkBringupCallbacks. This may result in NetworkRequests being registered
+     * or unregistered if the subIds mapped to the this Tracker's SubscriptionGroup change.
+     */
+    public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot newSnapshot) {
+        Objects.requireNonNull(newSnapshot, "Missing newSnapshot");
+
+        final TelephonySubscriptionSnapshot oldSnapshot = mLastSnapshot;
+        mLastSnapshot = newSnapshot;
+
+        // Update carrier config
+        mCarrierConfig = mLastSnapshot.getCarrierConfigForSubGrp(mSubscriptionGroup);
+
+        // Make sure all evaluators use the same updated TelephonySubscriptionSnapshot and carrier
+        // config to calculate their cached priority classes. For simplicity, the
+        // UnderlyingNetworkController does not listen for changes in VCN-related carrier config
+        // keys, and changes are applied at restart of the VcnGatewayConnection
+        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+            evaluator.reevaluate(
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+        }
+
+        // Only trigger re-registration if subIds in this group have changed
+        if (oldSnapshot
+                .getAllSubIdsInGroup(mSubscriptionGroup)
+                .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) {
+            reevaluateNetworks();
+            return;
+        }
+        registerOrUpdateNetworkRequests();
+    }
+
+    /**
+     * Pass the IpSecTransform of the VCN to UnderlyingNetworkController for metric monitoring
+     *
+     * <p>Caller MUST call it when IpSecTransforms have been created for VCN creation or migration
+     */
+    public void updateInboundTransform(
+            @NonNull UnderlyingNetworkRecord currentNetwork, @NonNull IpSecTransform transform) {
+        Objects.requireNonNull(currentNetwork, "currentNetwork is null");
+        Objects.requireNonNull(transform, "transform is null");
+
+        if (mCurrentRecord == null
+                || mRouteSelectionCallback == null
+                || !Objects.equals(currentNetwork.network, mCurrentRecord.network)) {
+            // The caller (VcnGatewayConnection) is out-of-dated. Ignore this call.
+            return;
+        }
+
+        mUnderlyingNetworkRecords.get(mCurrentRecord.network).setInboundTransform(transform);
+    }
+
+    /** Tears down this Tracker, and releases all underlying network requests. */
+    public void teardown() {
+        mVcnContext.ensureRunningOnLooperThread();
+        mIsQuitting = true;
+
+        // Will unregister all existing callbacks, but not register new ones due to quitting flag.
+        registerOrUpdateNetworkRequests();
+
+        mVcnContext
+                .getContext()
+                .getSystemService(TelephonyManager.class)
+                .unregisterTelephonyCallback(mActiveDataSubIdListener);
+    }
+
+    private TreeSet<UnderlyingNetworkEvaluator> getSortedUnderlyingNetworks() {
+        TreeSet<UnderlyingNetworkEvaluator> sorted =
+                new TreeSet<>(UnderlyingNetworkEvaluator.getComparator(mVcnContext));
+
+        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+            if (evaluator.getPriorityClass() != NetworkPriorityClassifier.PRIORITY_INVALID) {
+                sorted.add(evaluator);
+            }
+        }
+
+        return sorted;
+    }
+
+    private void reevaluateNetworks() {
+        if (mIsQuitting || mRouteSelectionCallback == null) {
+            return; // UnderlyingNetworkController has quit.
+        }
+
+        TreeSet<UnderlyingNetworkEvaluator> sorted = getSortedUnderlyingNetworks();
+
+        UnderlyingNetworkEvaluator candidateEvaluator = sorted.isEmpty() ? null : sorted.first();
+        UnderlyingNetworkRecord candidate =
+                candidateEvaluator == null ? null : candidateEvaluator.getNetworkRecord();
+        if (Objects.equals(mCurrentRecord, candidate)) {
+            return;
+        }
+
+        String allNetworkPriorities = "";
+        for (UnderlyingNetworkEvaluator recordEvaluator : sorted) {
+            if (!allNetworkPriorities.isEmpty()) {
+                allNetworkPriorities += ", ";
+            }
+            allNetworkPriorities +=
+                    recordEvaluator.getNetwork() + ": " + recordEvaluator.getPriorityClass();
+        }
+
+        if (!UnderlyingNetworkRecord.isSameNetwork(mCurrentRecord, candidate)) {
+            logInfo(
+                    "Selected network changed to "
+                            + (candidate == null ? null : candidate.network)
+                            + ", selected from list: "
+                            + allNetworkPriorities);
+        }
+
+        mCurrentRecord = candidate;
+        mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord);
+
+        // Need to update all evaluators to ensure the previously selected one is unselected
+        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+            evaluator.setIsSelected(
+                    candidateEvaluator == evaluator,
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+        }
+    }
+
+    /**
+     * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped.
+     *
+     * <p>NetworkBringupCallback only exists to prevent matching (VCN-managed) Networks from being
+     * reaped, and no action is taken on any events firing.
+     */
+    @VisibleForTesting
+    class NetworkBringupCallback extends NetworkCallback {}
+
+    /**
+     * RouteSelectionCallback is used to select the "best" underlying Network.
+     *
+     * <p>The "best" network is determined by ConnectivityService, which is treated as a source of
+     * truth.
+     */
+    @VisibleForTesting
+    class UnderlyingNetworkListener extends NetworkCallback {
+        UnderlyingNetworkListener() {
+            super(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO);
+        }
+
+        @Override
+        public void onAvailable(@NonNull Network network) {
+            mUnderlyingNetworkRecords.put(
+                    network,
+                    mDeps.newUnderlyingNetworkEvaluator(
+                            mVcnContext,
+                            network,
+                            mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                            mSubscriptionGroup,
+                            mLastSnapshot,
+                            mCarrierConfig,
+                            new NetworkEvaluatorCallbackImpl()));
+        }
+
+        @Override
+        public void onLost(@NonNull Network network) {
+            mUnderlyingNetworkRecords.get(network).close();
+            mUnderlyingNetworkRecords.remove(network);
+
+            reevaluateNetworks();
+        }
+
+        @Override
+        public void onCapabilitiesChanged(
+                @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
+            final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
+            if (evaluator == null) {
+                logWtf("Got capabilities change for unknown key: " + network);
+                return;
+            }
+
+            evaluator.setNetworkCapabilities(
+                    networkCapabilities,
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+
+            if (evaluator.isValid()) {
+                reevaluateNetworks();
+            }
+        }
+
+        @Override
+        public void onLinkPropertiesChanged(
+                @NonNull Network network, @NonNull LinkProperties linkProperties) {
+            final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
+            if (evaluator == null) {
+                logWtf("Got link properties change for unknown key: " + network);
+                return;
+            }
+
+            evaluator.setLinkProperties(
+                    linkProperties,
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+
+            if (evaluator.isValid()) {
+                reevaluateNetworks();
+            }
+        }
+
+        @Override
+        public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) {
+            final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
+            if (evaluator == null) {
+                logWtf("Got blocked status change for unknown key: " + network);
+                return;
+            }
+
+            evaluator.setIsBlocked(
+                    isBlocked,
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+
+            if (evaluator.isValid()) {
+                reevaluateNetworks();
+            }
+        }
+    }
+
+    @VisibleForTesting
+    class NetworkEvaluatorCallbackImpl implements NetworkEvaluatorCallback {
+        @Override
+        public void onEvaluationResultChanged() {
+            mVcnContext.ensureRunningOnLooperThread();
+            reevaluateNetworks();
+        }
+    }
+
+    private String getLogPrefix() {
+        return "("
+                + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
+                + "-"
+                + mConnectionConfig.getGatewayConnectionName()
+                + "-"
+                + System.identityHashCode(this)
+                + ") ";
+    }
+
+    private String getTagLogPrefix() {
+        return "[ " + TAG + " " + getLogPrefix() + "]";
+    }
+
+    private void logInfo(String msg) {
+        Slog.i(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[INFO] " + getTagLogPrefix() + msg);
+    }
+
+    private void logInfo(String msg, Throwable tr) {
+        Slog.i(TAG, getLogPrefix() + msg, tr);
+        LOCAL_LOG.log("[INFO] " + getTagLogPrefix() + msg + tr);
+    }
+
+    private void logWtf(String msg) {
+        Slog.wtf(TAG, msg);
+        LOCAL_LOG.log(TAG + "[WTF ] " + getTagLogPrefix() + msg);
+    }
+
+    private void logWtf(String msg, Throwable tr) {
+        Slog.wtf(TAG, msg, tr);
+        LOCAL_LOG.log(TAG + "[WTF ] " + getTagLogPrefix() + msg + tr);
+    }
+
+    /** Dumps the state of this record for logging and debugging purposes. */
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("UnderlyingNetworkController:");
+        pw.increaseIndent();
+
+        pw.println("Carrier WiFi Entry Threshold: " + getWifiEntryRssiThreshold(mCarrierConfig));
+        pw.println("Carrier WiFi Exit Threshold: " + getWifiExitRssiThreshold(mCarrierConfig));
+        pw.println(
+                "Currently selected: " + (mCurrentRecord == null ? null : mCurrentRecord.network));
+
+        pw.println("VcnUnderlyingNetworkTemplate list:");
+        pw.increaseIndent();
+        int index = 0;
+        for (VcnUnderlyingNetworkTemplate priority :
+                mConnectionConfig.getVcnUnderlyingNetworkPriorities()) {
+            pw.println("Priority index: " + index);
+            priority.dump(pw);
+            index++;
+        }
+        pw.decreaseIndent();
+        pw.println();
+
+        pw.println("Underlying networks:");
+        pw.increaseIndent();
+        if (mRouteSelectionCallback != null) {
+            for (UnderlyingNetworkEvaluator recordEvaluator : getSortedUnderlyingNetworks()) {
+                recordEvaluator.dump(pw);
+            }
+        }
+        pw.decreaseIndent();
+        pw.println();
+
+        pw.decreaseIndent();
+    }
+
+    private class VcnActiveDataSubscriptionIdListener extends TelephonyCallback
+            implements ActiveDataSubscriptionIdListener {
+        @Override
+        public void onActiveDataSubscriptionIdChanged(int subId) {
+            reevaluateNetworks();
+        }
+    }
+
+    /** Callbacks for being notified of the changes in, or to the selected underlying network. */
+    public interface UnderlyingNetworkControllerCallback {
+        /**
+         * Fired when a new underlying network is selected, or properties have changed.
+         *
+         * <p>This callback does NOT signal a mobility event.
+         *
+         * @param underlyingNetworkRecord The details of the new underlying network
+         */
+        void onSelectedUnderlyingNetworkChanged(
+                @Nullable UnderlyingNetworkRecord underlyingNetworkRecord);
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        public UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator(
+                @NonNull VcnContext vcnContext,
+                @NonNull Network network,
+                @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+                @NonNull ParcelUuid subscriptionGroup,
+                @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+                @Nullable PersistableBundleWrapper carrierConfig,
+                @NonNull NetworkEvaluatorCallback evaluatorCallback) {
+            return new UnderlyingNetworkEvaluator(
+                    vcnContext,
+                    network,
+                    underlyingNetworkTemplates,
+                    subscriptionGroup,
+                    lastSnapshot,
+                    carrierConfig,
+                    evaluatorCallback);
+        }
+    }
+}
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
new file mode 100644
index 0000000..30f4ed1
--- /dev/null
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -0,0 +1,438 @@
+/*
+ * 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.server.vcn.routeselection;
+
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import static com.android.server.VcnManagementService.LOCAL_LOG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.IpSecTransform;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.vcn.VcnManager;
+import android.net.vcn.VcnUnderlyingNetworkTemplate;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * UnderlyingNetworkEvaluator evaluates the quality and priority class of a network candidate for
+ * route selection.
+ *
+ * @hide
+ */
+public class UnderlyingNetworkEvaluator {
+    private static final String TAG = UnderlyingNetworkEvaluator.class.getSimpleName();
+
+    private static final int[] PENALTY_TIMEOUT_MINUTES_DEFAULT = new int[] {5};
+
+    @NonNull private final VcnContext mVcnContext;
+    @NonNull private final Handler mHandler;
+    @NonNull private final Object mCancellationToken = new Object();
+
+    @NonNull private final UnderlyingNetworkRecord.Builder mNetworkRecordBuilder;
+
+    @NonNull private final NetworkEvaluatorCallback mEvaluatorCallback;
+    @NonNull private final List<NetworkMetricMonitor> mMetricMonitors = new ArrayList<>();
+
+    @NonNull private final Dependencies mDependencies;
+
+    // TODO: Support back-off timeouts
+    private long mPenalizedTimeoutMs;
+
+    private boolean mIsSelected;
+    private boolean mIsPenalized;
+    private int mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public UnderlyingNetworkEvaluator(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkEvaluatorCallback evaluatorCallback,
+            @NonNull Dependencies dependencies) {
+        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+        mHandler = new Handler(mVcnContext.getLooper());
+
+        mDependencies = Objects.requireNonNull(dependencies, "Missing dependencies");
+        mEvaluatorCallback = Objects.requireNonNull(evaluatorCallback, "Missing deps");
+
+        Objects.requireNonNull(underlyingNetworkTemplates, "Missing underlyingNetworkTemplates");
+        Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+        Objects.requireNonNull(lastSnapshot, "Missing lastSnapshot");
+
+        mNetworkRecordBuilder =
+                new UnderlyingNetworkRecord.Builder(
+                        Objects.requireNonNull(network, "Missing network"));
+        mIsSelected = false;
+        mIsPenalized = false;
+        mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig);
+
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        try {
+            mMetricMonitors.add(
+                    mDependencies.newIpSecPacketLossDetector(
+                            mVcnContext,
+                            mNetworkRecordBuilder.getNetwork(),
+                            carrierConfig,
+                            new MetricMonitorCallbackImpl()));
+        } catch (IllegalAccessException e) {
+            // No action. Do not add anything to mMetricMonitors
+        }
+    }
+
+    public UnderlyingNetworkEvaluator(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkEvaluatorCallback evaluatorCallback) {
+        this(
+                vcnContext,
+                network,
+                underlyingNetworkTemplates,
+                subscriptionGroup,
+                lastSnapshot,
+                carrierConfig,
+                evaluatorCallback,
+                new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        /** Get an IpSecPacketLossDetector instance */
+        public IpSecPacketLossDetector newIpSecPacketLossDetector(
+                @NonNull VcnContext vcnContext,
+                @NonNull Network network,
+                @Nullable PersistableBundleWrapper carrierConfig,
+                @NonNull NetworkMetricMonitor.NetworkMetricMonitorCallback callback)
+                throws IllegalAccessException {
+            return new IpSecPacketLossDetector(vcnContext, network, carrierConfig, callback);
+        }
+    }
+
+    /** Callback to notify caller to reevaluate network selection */
+    public interface NetworkEvaluatorCallback {
+        /**
+         * Called when mIsPenalized changed
+         *
+         * <p>When receiving this call, UnderlyingNetworkController should reevaluate all network
+         * candidates for VCN underlying network selection
+         */
+        void onEvaluationResultChanged();
+    }
+
+    private class MetricMonitorCallbackImpl
+            implements NetworkMetricMonitor.NetworkMetricMonitorCallback {
+        public void onValidationResultReceived() {
+            mVcnContext.ensureRunningOnLooperThread();
+
+            handleValidationResult();
+        }
+    }
+
+    private void updatePriorityClass(
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        if (mNetworkRecordBuilder.isValid()) {
+            mPriorityClass =
+                    NetworkPriorityClassifier.calculatePriorityClass(
+                            mVcnContext,
+                            mNetworkRecordBuilder.build(),
+                            underlyingNetworkTemplates,
+                            subscriptionGroup,
+                            lastSnapshot,
+                            mIsSelected,
+                            carrierConfig);
+        } else {
+            mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID;
+        }
+    }
+
+    /** Get the comparator for UnderlyingNetworkEvaluator */
+    public static Comparator<UnderlyingNetworkEvaluator> getComparator(VcnContext vcnContext) {
+        return (left, right) -> {
+            if (left.mIsPenalized != right.mIsPenalized) {
+                // A penalized network should have lower priority which means a larger index
+                return left.mIsPenalized ? 1 : -1;
+            }
+
+            final int leftIndex = left.mPriorityClass;
+            final int rightIndex = right.mPriorityClass;
+
+            // In the case of networks in the same priority class, prioritize based on other
+            // criteria (eg. actively selected network, link metrics, etc)
+            if (leftIndex == rightIndex) {
+                // TODO: Improve the strategy of network selection when both UnderlyingNetworkRecord
+                // fall into the same priority class.
+                if (left.mIsSelected) {
+                    return -1;
+                }
+                if (right.mIsSelected) {
+                    return 1;
+                }
+            }
+            return Integer.compare(leftIndex, rightIndex);
+        };
+    }
+
+    private static long getPenaltyTimeoutMs(@Nullable PersistableBundleWrapper carrierConfig) {
+        final int[] timeoutMinuteList;
+
+        if (carrierConfig != null) {
+            timeoutMinuteList =
+                    carrierConfig.getIntArray(
+                            VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
+                            PENALTY_TIMEOUT_MINUTES_DEFAULT);
+        } else {
+            timeoutMinuteList = PENALTY_TIMEOUT_MINUTES_DEFAULT;
+        }
+
+        // TODO: Add the support of back-off timeouts and return the full list
+        return TimeUnit.MINUTES.toMillis(timeoutMinuteList[0]);
+    }
+
+    private void handleValidationResult() {
+        final boolean wasPenalized = mIsPenalized;
+        mIsPenalized = false;
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            mIsPenalized |= monitor.isValidationFailed();
+        }
+
+        if (wasPenalized == mIsPenalized) {
+            return;
+        }
+
+        logInfo(
+                "#handleValidationResult: wasPenalized "
+                        + wasPenalized
+                        + " mIsPenalized "
+                        + mIsPenalized);
+
+        if (mIsPenalized) {
+            mHandler.postDelayed(
+                    new ExitPenaltyBoxRunnable(), mCancellationToken, mPenalizedTimeoutMs);
+        } else {
+            // Exit the penalty box
+            mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+        }
+        mEvaluatorCallback.onEvaluationResultChanged();
+    }
+
+    public class ExitPenaltyBoxRunnable implements Runnable {
+        @Override
+        public void run() {
+            if (!mIsPenalized) {
+                logWtf("Evaluator not being penalized but ExitPenaltyBoxRunnable was scheduled");
+                return;
+            }
+
+            // TODO: There might be a future metric monitor (e.g. ping) that will require the
+            // validation to pass before exiting the penalty box.
+            mIsPenalized = false;
+            mEvaluatorCallback.onEvaluationResultChanged();
+        }
+    }
+
+    /** Set the NetworkCapabilities */
+    public void setNetworkCapabilities(
+            @NonNull NetworkCapabilities nc,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        mNetworkRecordBuilder.setNetworkCapabilities(nc);
+
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.onLinkPropertiesOrCapabilitiesChanged();
+        }
+    }
+
+    /** Set the LinkProperties */
+    public void setLinkProperties(
+            @NonNull LinkProperties lp,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        mNetworkRecordBuilder.setLinkProperties(lp);
+
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.onLinkPropertiesOrCapabilitiesChanged();
+        }
+    }
+
+    /** Set whether the network is blocked */
+    public void setIsBlocked(
+            boolean isBlocked,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        mNetworkRecordBuilder.setIsBlocked(isBlocked);
+
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+    }
+
+    /** Set whether the network is selected as VCN's underlying network */
+    public void setIsSelected(
+            boolean isSelected,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        mIsSelected = isSelected;
+
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.setIsSelectedUnderlyingNetwork(isSelected);
+        }
+    }
+
+    /**
+     * Update the last TelephonySubscriptionSnapshot and carrier config to reevaluate the network
+     */
+    public void reevaluate(
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        // The already scheduled event will not be affected. The followup events will be scheduled
+        // with the new timeout
+        mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig);
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.setCarrierConfig(carrierConfig);
+        }
+    }
+
+    /** Update the inbound IpSecTransform applied to the network */
+    public void setInboundTransform(@NonNull IpSecTransform transform) {
+        if (!mIsSelected) {
+            logWtf("setInboundTransform on an unselected evaluator");
+            return;
+        }
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.setInboundTransform(transform);
+        }
+    }
+
+    /** Close the evaluator and stop all the underlying network metric monitors */
+    public void close() {
+        mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.close();
+        }
+    }
+
+    /** Return whether this network evaluator is valid */
+    public boolean isValid() {
+        return mNetworkRecordBuilder.isValid();
+    }
+
+    /** Return the network */
+    public Network getNetwork() {
+        return mNetworkRecordBuilder.getNetwork();
+    }
+
+    /** Return the network record */
+    public UnderlyingNetworkRecord getNetworkRecord() {
+        return mNetworkRecordBuilder.build();
+    }
+
+    /** Return the priority class for network selection */
+    public int getPriorityClass() {
+        return mPriorityClass;
+    }
+
+    /** Return whether the network is being penalized */
+    public boolean isPenalized() {
+        return mIsPenalized;
+    }
+
+    /** Dump the information of this instance */
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("UnderlyingNetworkEvaluator:");
+        pw.increaseIndent();
+
+        if (mNetworkRecordBuilder.isValid()) {
+            getNetworkRecord().dump(pw);
+        } else {
+            pw.println(
+                    "UnderlyingNetworkRecord incomplete: mNetwork: "
+                            + mNetworkRecordBuilder.getNetwork());
+        }
+
+        pw.println("mIsSelected: " + mIsSelected);
+        pw.println("mPriorityClass: " + mPriorityClass);
+        pw.println("mIsPenalized: " + mIsPenalized);
+
+        pw.decreaseIndent();
+    }
+
+    private String getLogPrefix() {
+        return "[Network " + mNetworkRecordBuilder.getNetwork() + "] ";
+    }
+
+    private void logInfo(String msg) {
+        Slog.i(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[INFO ] " + TAG + getLogPrefix() + msg);
+    }
+
+    private void logWtf(String msg) {
+        Slog.wtf(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[WTF ] " + TAG + getLogPrefix() + msg);
+    }
+}
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
similarity index 100%
rename from services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
rename to packages/Vcn/service-b/src/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
diff --git a/packages/Vcn/service-b/vcn-location-flag/module/com/android/server/vcn/VcnLocation.java b/packages/Vcn/service-b/vcn-location-flag/module/com/android/server/vcn/VcnLocation.java
new file mode 100644
index 0000000..6c7d24d
--- /dev/null
+++ b/packages/Vcn/service-b/vcn-location-flag/module/com/android/server/vcn/VcnLocation.java
@@ -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.server.vcn;
+
+/**
+ * Class to represent that VCN is in a mainline module
+ *
+ * <p>This class is used to check whether VCN is in the non-updatable platform or in a mainline
+ * module.
+ */
+// When VCN is in a mainline module, this class (module/com/android/server/vcn/VcnLocation.java)
+// will be built in to the vcn-location-sources filegroup. When VCN is in the non-updatable
+// platform, platform/com/android/server/vcn/VcnLocation.java will be built in to the filegroup
+public class VcnLocation {
+    /** Indicate that VCN is the platform */
+    public static final boolean IS_VCN_IN_MAINLINE = true;
+}
diff --git a/packages/Vcn/service-b/vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java b/packages/Vcn/service-b/vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java
new file mode 100644
index 0000000..c6c82a5
--- /dev/null
+++ b/packages/Vcn/service-b/vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java
@@ -0,0 +1,32 @@
+/*
+ * 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.vcn;
+
+/**
+ * Class to represent that VCN is in the platform
+ *
+ * <p>This class is used to check whether VCN is in the non-updatable platform or in a mainline
+ * module.
+ */
+// When VCN is in a mainline module, module/com/android/server/vcn/VcnLocation.java
+// will be built in to the vcn-location-sources filegroup. When VCN is in the non-updatable
+// platform, this class (platform/com/android/server/vcn/VcnLocation.java) will be built in to the
+// filegroup
+public class VcnLocation {
+    /** Indicate that VCN is the platform */
+    public static final boolean IS_VCN_IN_MAINLINE = false;
+}
diff --git a/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml
index ca4b5d4..6ccfaf3 100644
--- a/packages/VpnDialogs/AndroidManifest.xml
+++ b/packages/VpnDialogs/AndroidManifest.xml
@@ -41,6 +41,7 @@
         <activity android:name=".PlatformVpnConfirmDialog"
                   android:theme="@*android:style/Theme.DeviceDefault.Dialog.Alert.DayNight"
                   android:noHistory="true"
+                  android:enableOnBackInvokedCallback="false"
                   android:excludeFromRecents="true"
                   android:exported="true">
         </activity>
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 0c2ce8d..66c8d0f 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -282,20 +282,12 @@
     visibility: ["//visibility:private"],
 }
 
-cc_library_host_shared {
-    name: "libravenwood_initializer",
-    defaults: ["ravenwood_jni_defaults"],
-    srcs: [
-        "runtime-jni/ravenwood_initializer.cpp",
-    ],
-}
-
 // We need this as a separate library because we need to overload the
 // sysprop symbols before libbase is loaded into the process
 cc_library_host_shared {
-    name: "libravenwood_sysprop",
+    name: "libravenwood_initializer",
     defaults: ["ravenwood_jni_defaults"],
-    srcs: ["runtime-jni/ravenwood_sysprop.cpp"],
+    srcs: ["runtime-jni/ravenwood_initializer.cpp"],
 }
 
 cc_library_host_shared {
@@ -669,7 +661,6 @@
     jni_libs: [
         // Libraries has to be loaded in the following order
         "libravenwood_initializer",
-        "libravenwood_sysprop",
         "libravenwood_runtime",
         "libandroid_runtime",
     ],
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index 9b71f80..9644a52 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -63,8 +63,6 @@
  * - Handle {@link android.platform.test.annotations.DisabledOnRavenwood}.
  */
 public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase {
-    public static final String TAG = "Ravenwood";
-
     /** Scope of a hook. */
     public enum Scope {
         Class,
@@ -133,9 +131,6 @@
 
         Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName());
 
-        // This is needed to make AndroidJUnit4ClassRunner happy.
-        InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
-
         // Hook point to allow more customization.
         runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null);
 
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java
deleted file mode 100644
index 870a10a..0000000
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java
+++ /dev/null
@@ -1,85 +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 android.platform.test.ravenwood;
-
-import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.annotation.Nullable;
-import android.app.ResourcesManager;
-import android.content.res.Resources;
-import android.view.DisplayAdjustments;
-
-import java.io.File;
-import java.util.HashMap;
-
-/**
- * Used to store various states associated with {@link RavenwoodConfig} that's inly needed
- * in junit-impl.
- *
- * We don't want to put it in junit-src to avoid having to recompile all the downstream
- * dependencies after changing this class.
- *
- * All members must be called from the runner's main thread.
- */
-public class RavenwoodConfigState {
-    private static final String TAG = "RavenwoodConfigState";
-
-    private final RavenwoodConfig mConfig;
-
-    // TODO: Move the other contexts from RavenwoodConfig to here too? They're used by
-    // RavenwoodRule too, but RavenwoodRule can probably use InstrumentationRegistry?
-    RavenwoodContext mSystemServerContext;
-
-    public RavenwoodConfigState(RavenwoodConfig config) {
-        mConfig = config;
-    }
-
-    /** Map from path -> resources. */
-    private final HashMap<File, Resources> mCachedResources = new HashMap<>();
-
-    /**
-     * Load {@link Resources} from an APK, with cache.
-     */
-    public Resources loadResources(@Nullable File apkPath) {
-        var cached = mCachedResources.get(apkPath);
-        if (cached != null) {
-            return cached;
-        }
-
-        var fileToLoad = apkPath != null ? apkPath : new File(RAVENWOOD_EMPTY_RESOURCES_APK);
-
-        assertTrue("File " + fileToLoad + " doesn't exist.", fileToLoad.isFile());
-
-        final String path = fileToLoad.getAbsolutePath();
-        final var emptyPaths = new String[0];
-
-        ResourcesManager.getInstance().initializeApplicationPaths(path, emptyPaths);
-
-        final var ret = ResourcesManager.getInstance().getResources(null, path,
-                emptyPaths, emptyPaths, emptyPaths,
-                emptyPaths, null, null,
-                new DisplayAdjustments().getCompatibilityInfo(),
-                RavenwoodRuntimeEnvironmentController.class.getClassLoader(), null);
-
-        assertNotNull(ret);
-
-        mCachedResources.put(apkPath, ret);
-        return ret;
-    }
-}
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 239c806..9eff20a 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -45,7 +45,7 @@
 import java.util.function.Supplier;
 
 public class RavenwoodContext extends RavenwoodBaseContext {
-    private static final String TAG = "Ravenwood";
+    private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
 
     private final Object mLock = new Object();
     private final String mPackageName;
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java
index 77275c4..3cb2c67 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodEnablementChecker.java
@@ -27,8 +27,6 @@
  * Calculates which tests need to be executed on Ravenwood.
  */
 public class RavenwoodEnablementChecker {
-    private static final String TAG = "RavenwoodDisablementChecker";
-
     private RavenwoodEnablementChecker() {
     }
 
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
index ec00e8f..a5d0bfd 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
@@ -15,24 +15,23 @@
  */
 package android.platform.test.ravenwood;
 
-import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicMember;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
-import static org.junit.Assert.fail;
-
-import android.annotation.Nullable;
 import android.util.Log;
+import android.util.Pair;
 
-import com.android.ravenwood.common.RavenwoodRuntimeException;
+import com.android.ravenwood.RavenwoodRuntimeNative;
 
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 
-import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
 
 /**
- * Used to store various states associated with the current test runner that's inly needed
+ * Used to store various states associated with the current test runner that's only needed
  * in junit-impl.
  *
  * We don't want to put it in junit-src to avoid having to recompile all the downstream
@@ -41,7 +40,12 @@
  * All members must be called from the runner's main thread.
  */
 public final class RavenwoodRunnerState {
-    private static final String TAG = "RavenwoodRunnerState";
+    private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
+    private static final String RAVENWOOD_RULE_ERROR =
+            "RavenwoodRule(s) are not executed in the correct order";
+
+    private static final List<Pair<RavenwoodRule, RavenwoodPropertyState>> sActiveProperties =
+            new ArrayList<>();
 
     private final RavenwoodAwareTestRunner mRunner;
 
@@ -52,207 +56,95 @@
         mRunner = runner;
     }
 
-    /**
-     * The RavenwoodConfig used to configure the current Ravenwood environment.
-     * This can either come from mConfig or mRule.
-     */
-    private RavenwoodConfig mCurrentConfig;
-    /**
-     * The RavenwoodConfig declared in the test class
-     */
-    private RavenwoodConfig mConfig;
-    /**
-     * The RavenwoodRule currently in effect, declared in the test class
-     */
-    private RavenwoodRule mRule;
-    private boolean mHasRavenwoodRule;
     private Description mMethodDescription;
 
-    public RavenwoodConfig getConfig() {
-        return mCurrentConfig;
-    }
-
     public void enterTestRunner() {
         Log.i(TAG, "enterTestRunner: " + mRunner);
-
-        mHasRavenwoodRule = hasRavenwoodRule(mRunner.mTestJavaClass);
-        mConfig = extractConfiguration(mRunner.mTestJavaClass);
-
-        if (mConfig != null) {
-            if (mHasRavenwoodRule) {
-                fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class."
-                        + " Suggest migrating to RavenwoodConfig.");
-            }
-            mCurrentConfig = mConfig;
-        } else if (!mHasRavenwoodRule) {
-            // If no RavenwoodConfig and no RavenwoodRule, use a default config
-            mCurrentConfig = new RavenwoodConfig.Builder().build();
-        }
-
-        if (mCurrentConfig != null) {
-            RavenwoodRuntimeEnvironmentController.init(mRunner);
-        }
+        RavenwoodRuntimeEnvironmentController.initForRunner();
     }
 
     public void enterTestClass() {
         Log.i(TAG, "enterTestClass: " + mRunner.mTestJavaClass.getName());
-
-        if (mCurrentConfig != null) {
-            RavenwoodRuntimeEnvironmentController.init(mRunner);
-        }
     }
 
     public void exitTestClass() {
         Log.i(TAG, "exitTestClass: " + mRunner.mTestJavaClass.getName());
-        try {
-            if (mCurrentConfig != null) {
-                RavenwoodRuntimeEnvironmentController.reset();
-            }
-        } finally {
-            mConfig = null;
-            mRule = null;
-        }
+        assertTrue(RAVENWOOD_RULE_ERROR, sActiveProperties.isEmpty());
+        RavenwoodRuntimeEnvironmentController.exitTestClass();
     }
 
     public void enterTestMethod(Description description) {
         mMethodDescription = description;
+        RavenwoodRuntimeEnvironmentController.initForMethod();
     }
 
     public void exitTestMethod() {
         mMethodDescription = null;
-        RavenwoodRuntimeEnvironmentController.reinit();
     }
 
     public void enterRavenwoodRule(RavenwoodRule rule) {
-        if (!mHasRavenwoodRule) {
-            fail("If you have a RavenwoodRule in your test, make sure the field type is"
-                    + " RavenwoodRule so Ravenwood can detect it.");
-        }
-        if (mRule != null) {
-            fail("Multiple nesting RavenwoodRule's are detected in the same class,"
-                    + " which is not supported.");
-        }
-        mRule = rule;
-        if (mCurrentConfig == null) {
-            mCurrentConfig = rule.getConfiguration();
-        }
-        RavenwoodRuntimeEnvironmentController.init(mRunner);
+        pushTestProperties(rule);
     }
 
     public void exitRavenwoodRule(RavenwoodRule rule) {
-        if (mRule != rule) {
-            fail("RavenwoodRule did not take effect.");
-        }
-        mRule = null;
+        popTestProperties(rule);
     }
 
-    /**
-     * @return a configuration from a test class, if any.
-     */
-    @Nullable
-    private static RavenwoodConfig extractConfiguration(Class<?> testClass) {
-        var field = findConfigurationField(testClass);
-        if (field == null) {
-            return null;
+    static class RavenwoodPropertyState {
+
+        final List<Pair<String, String>> mBackup;
+        final Set<String> mKeyReadable;
+        final Set<String> mKeyWritable;
+
+        RavenwoodPropertyState(RavenwoodTestProperties props) {
+            mBackup = props.mValues.keySet().stream()
+                    .map(key -> Pair.create(key, RavenwoodRuntimeNative.getSystemProperty(key)))
+                    .toList();
+            mKeyReadable = Set.copyOf(props.mKeyReadable);
+            mKeyWritable = Set.copyOf(props.mKeyWritable);
         }
 
-        try {
-            return (RavenwoodConfig) field.get(null);
-        } catch (IllegalAccessException e) {
-            throw new RavenwoodRuntimeException("Failed to fetch from the configuration field", e);
+        boolean isKeyAccessible(String key, boolean write) {
+            return write ? mKeyWritable.contains(key) : mKeyReadable.contains(key);
         }
-    }
 
-    /**
-     * @return true if the current target class (or its super classes) has any @Rule / @ClassRule
-     * fields of type RavenwoodRule.
-     *
-     * Note, this check won't detect cases where a Rule is of type
-     * {@link TestRule} and still be a {@link RavenwoodRule}. But that'll be detected at runtime
-     * as a failure, in {@link #enterRavenwoodRule}.
-     */
-    private static boolean hasRavenwoodRule(Class<?> testClass) {
-        for (var field : testClass.getDeclaredFields()) {
-            if (!field.isAnnotationPresent(Rule.class)
-                    && !field.isAnnotationPresent(ClassRule.class)) {
-                continue;
-            }
-            if (field.getType().equals(RavenwoodRule.class)) {
-                return true;
-            }
-        }
-        // JUnit supports rules as methods, so we need to check them too.
-        for (var method : testClass.getDeclaredMethods()) {
-            if (!method.isAnnotationPresent(Rule.class)
-                    && !method.isAnnotationPresent(ClassRule.class)) {
-                continue;
-            }
-            if (method.getReturnType().equals(RavenwoodRule.class)) {
-                return true;
-            }
-        }
-        // Look into the super class.
-        if (!testClass.getSuperclass().equals(Object.class)) {
-            return hasRavenwoodRule(testClass.getSuperclass());
-        }
-        return false;
-    }
-
-    /**
-     * Find and return a field with @RavenwoodConfig.Config, which must be of type
-     * RavenwoodConfig.
-     */
-    @Nullable
-    private static Field findConfigurationField(Class<?> testClass) {
-        Field foundField = null;
-
-        for (var field : testClass.getDeclaredFields()) {
-            final var hasAnot = field.isAnnotationPresent(RavenwoodConfig.Config.class);
-            final var isType = field.getType().equals(RavenwoodConfig.class);
-
-            if (hasAnot) {
-                if (isType) {
-                    // Good, use this field.
-                    if (foundField != null) {
-                        fail(String.format(
-                                "Class %s has multiple fields with %s",
-                                testClass.getCanonicalName(),
-                                "@RavenwoodConfig.Config"));
-                    }
-                    // Make sure it's static public
-                    ensureIsPublicMember(field, true);
-
-                    foundField = field;
+        void restore() {
+            mBackup.forEach(pair -> {
+                if (pair.second == null) {
+                    RavenwoodRuntimeNative.removeSystemProperty(pair.first);
                 } else {
-                    fail(String.format(
-                            "Field %s.%s has %s but type is not %s",
-                            testClass.getCanonicalName(),
-                            field.getName(),
-                            "@RavenwoodConfig.Config",
-                            "RavenwoodConfig"));
-                    return null; // unreachable
+                    RavenwoodRuntimeNative.setSystemProperty(pair.first, pair.second);
                 }
-            } else {
-                if (isType) {
-                    fail(String.format(
-                            "Field %s.%s does not have %s but type is %s",
-                            testClass.getCanonicalName(),
-                            field.getName(),
-                            "@RavenwoodConfig.Config",
-                            "RavenwoodConfig"));
-                    return null; // unreachable
-                } else {
-                    // Unrelated field, ignore.
-                    continue;
-                }
-            }
+            });
         }
-        if (foundField != null) {
-            return foundField;
+    }
+
+    private static void pushTestProperties(RavenwoodRule rule) {
+        sActiveProperties.add(Pair.create(rule, new RavenwoodPropertyState(rule.mProperties)));
+        rule.mProperties.mValues.forEach(RavenwoodRuntimeNative::setSystemProperty);
+    }
+
+    private static void popTestProperties(RavenwoodRule rule) {
+        var pair = sActiveProperties.removeLast();
+        assertNotNull(RAVENWOOD_RULE_ERROR, pair);
+        assertEquals(RAVENWOOD_RULE_ERROR, rule, pair.first);
+        pair.second.restore();
+    }
+
+    @SuppressWarnings("unused")  // Called from native code (ravenwood_sysprop.cpp)
+    private static void checkSystemPropertyAccess(String key, boolean write) {
+        if (write && RavenwoodSystemProperties.sDefaultValues.containsKey(key)) {
+            // The default core values should never be modified
+            throw new IllegalArgumentException(
+                    "Setting core system property '" + key + "' is not allowed");
         }
-        if (!testClass.getSuperclass().equals(Object.class)) {
-            return findConfigurationField(testClass.getSuperclass());
+
+        final boolean result = RavenwoodSystemProperties.isKeyAccessible(key, write)
+                || sActiveProperties.stream().anyMatch(p -> p.second.isKeyAccessible(key, write));
+
+        if (!result) {
+            throw new IllegalArgumentException((write ? "Write" : "Read")
+                    + " access to system property '" + key + "' denied via RavenwoodRule");
         }
-        return null;
     }
 }
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 979076e..930914f 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -16,8 +16,11 @@
 
 package android.platform.test.ravenwood;
 
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.UserHandle.SYSTEM;
 import static android.platform.test.ravenwood.RavenwoodSystemServer.ANDROID_PACKAGE_NAME;
 
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK;
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
@@ -25,7 +28,9 @@
 import static com.android.ravenwood.common.RavenwoodCommonUtils.parseNullableInt;
 import static com.android.ravenwood.common.RavenwoodCommonUtils.withDefault;
 
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
@@ -53,6 +58,8 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
+import android.util.Log_ravenwood;
+import android.view.DisplayAdjustments;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -62,7 +69,6 @@
 import com.android.ravenwood.RavenwoodRuntimeNative;
 import com.android.ravenwood.RavenwoodRuntimeState;
 import com.android.ravenwood.common.RavenwoodCommonUtils;
-import com.android.ravenwood.common.RavenwoodRuntimeException;
 import com.android.ravenwood.common.SneakyThrow;
 import com.android.server.LocalServices;
 import com.android.server.compat.PlatformCompat;
@@ -74,8 +80,10 @@
 import java.io.IOException;
 import java.io.PrintStream;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
@@ -85,20 +93,21 @@
 import java.util.function.Supplier;
 
 /**
- * Responsible for initializing and de-initializing the environment, according to a
- * {@link RavenwoodConfig}.
+ * Responsible for initializing and the environment.
  */
 public class RavenwoodRuntimeEnvironmentController {
-    private static final String TAG = "RavenwoodRuntimeEnvironmentController";
+    private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
 
     private RavenwoodRuntimeEnvironmentController() {
     }
 
     private static final String MAIN_THREAD_NAME = "RavenwoodMain";
     private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer";
-    private static final String RAVENWOOD_NATIVE_SYSPROP_NAME = "ravenwood_sysprop";
     private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
 
+    private static final String ANDROID_LOG_TAGS = "ANDROID_LOG_TAGS";
+    private static final String RAVENWOOD_ANDROID_LOG_TAGS = "RAVENWOOD_" + ANDROID_LOG_TAGS;
+
     /**
      * When enabled, attempt to dump all thread stacks just before we hit the
      * overall Tradefed timeout, to aid in debugging deadlocks.
@@ -113,8 +122,6 @@
 
     private static ScheduledFuture<?> sPendingTimeout;
 
-    private static long sOriginalIdentityToken = -1;
-
     /**
      * When enabled, attempt to detect uncaught exceptions from background threads.
      */
@@ -147,6 +154,10 @@
         return res;
     }
 
+    /** Map from path -> resources. */
+    private static final HashMap<File, Resources> sCachedResources = new HashMap<>();
+    private static Set<String> sAdoptedPermissions = Collections.emptySet();
+
     private static final Object sInitializationLock = new Object();
 
     @GuardedBy("sInitializationLock")
@@ -155,15 +166,16 @@
     @GuardedBy("sInitializationLock")
     private static Throwable sExceptionFromGlobalInit;
 
-    private static RavenwoodAwareTestRunner sRunner;
-    private static RavenwoodSystemProperties sProps;
-
     private static final int DEFAULT_TARGET_SDK_LEVEL = VERSION_CODES.CUR_DEVELOPMENT;
     private static final String DEFAULT_PACKAGE_NAME = "com.android.ravenwoodtests.defaultname";
 
+    private static final int sMyPid = new Random().nextInt(100, 32768);
     private static int sTargetSdkLevel;
     private static String sTestPackageName;
     private static String sTargetPackageName;
+    private static Instrumentation sInstrumentation;
+    private static final long sCallingIdentity =
+            packBinderIdentityToken(false, FIRST_APPLICATION_UID, sMyPid);
 
     /**
      * Initialize the global environment.
@@ -182,7 +194,7 @@
                     Log.e(TAG, "globalInit() failed", th);
 
                     sExceptionFromGlobalInit = th;
-                    throw th;
+                    SneakyThrow.sneakyThrow(th);
                 }
             } else {
                 // Subsequent calls. If the first call threw, just throw the same error, to prevent
@@ -197,48 +209,53 @@
         }
     }
 
-    private static void globalInitInner() {
+    private static void globalInitInner() throws IOException {
         if (RAVENWOOD_VERBOSE_LOGGING) {
             Log.v(TAG, "globalInit() called here...", new RuntimeException("NOT A CRASH"));
         }
+        if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
+            Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
+        }
 
-        // Some process-wide initialization. (maybe redirect stdout/stderr)
-        RavenwoodCommonUtils.loadJniLibrary(LIBRAVENWOOD_INITIALIZER_NAME);
+        // Some process-wide initialization:
+        // - maybe redirect stdout/stderr
+        // - override native system property functions
+        var lib = RavenwoodCommonUtils.getJniLibraryPath(LIBRAVENWOOD_INITIALIZER_NAME);
+        System.load(lib);
+        RavenwoodRuntimeNative.reloadNativeLibrary(lib);
+
+        // Redirect stdout/stdin to the Log API.
+        RuntimeInit.redirectLogStreams();
 
         dumpCommandLineArgs();
 
         // We haven't initialized liblog yet, so directly write to System.out here.
         RavenwoodCommonUtils.log(TAG, "globalInitInner()");
 
-        // Load libravenwood_sysprop before other libraries that may use SystemProperties.
-        var libProp = RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_SYSPROP_NAME);
-        System.load(libProp);
-        RavenwoodRuntimeNative.reloadNativeLibrary(libProp);
-
         // Make sure libravenwood_runtime is loaded.
         System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME));
 
+        Log_ravenwood.setLogLevels(getLogTags());
+        Log_ravenwood.onRavenwoodRuntimeNativeReady();
+
         // Do the basic set up for the android sysprops.
         RavenwoodSystemProperties.initialize();
-        setSystemProperties(null);
 
+        // Enable all log levels for native logging, until we'll have a way to change the native
+        // side log level at runtime.
         // Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()),
         // before loadFrameworkNativeCode() (which uses $ANDROID_LOG_TAGS).
-        if (RAVENWOOD_VERBOSE_LOGGING) {
-            RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging");
-            try {
-                Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
-            } catch (ErrnoException e) {
-                throw new RuntimeException(e);
-            }
+        // This would also prevent libbase from crashing the process (b/381112373) because
+        // the string format it accepts is very limited.
+        try {
+            Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
+        } catch (ErrnoException e) {
+            throw new RuntimeException(e);
         }
 
         // Make sure libandroid_runtime is loaded.
         RavenwoodNativeLoader.loadFrameworkNativeCode();
 
-        // Redirect stdout/stdin to liblog.
-        RuntimeInit.redirectLogStreams();
-
         // Touch some references early to ensure they're <clinit>'ed
         Objects.requireNonNull(Build.TYPE);
         Objects.requireNonNull(Build.VERSION.SDK);
@@ -251,6 +268,86 @@
         loadRavenwoodProperties();
 
         assertMockitoVersion();
+
+        Log.i(TAG, "TargetPackageName=" + sTargetPackageName);
+        Log.i(TAG, "TestPackageName=" + sTestPackageName);
+        Log.i(TAG, "TargetSdkLevel=" + sTargetSdkLevel);
+
+        RavenwoodRuntimeState.sUid = FIRST_APPLICATION_UID;
+        RavenwoodRuntimeState.sPid = sMyPid;
+        RavenwoodRuntimeState.sTargetSdkLevel = sTargetSdkLevel;
+
+        ServiceManager.init$ravenwood();
+        LocalServices.removeAllServicesForTest();
+
+        ActivityManager.init$ravenwood(SYSTEM.getIdentifier());
+
+        final var main = new HandlerThread(MAIN_THREAD_NAME);
+        main.start();
+        Looper.setMainLooperForTest(main.getLooper());
+
+        final boolean isSelfInstrumenting =
+                Objects.equals(sTestPackageName, sTargetPackageName);
+
+        // This will load the resources from the apk set to `resource_apk` in the build file.
+        // This is supposed to be the "target app"'s resources.
+        final Supplier<Resources> targetResourcesLoader = () -> {
+            var file = new File(RAVENWOOD_RESOURCE_APK);
+            return loadResources(file.exists() ? file : null);
+        };
+
+        // Set up test context's (== instrumentation context's) resources.
+        // If the target package name == test package name, then we use the main resources.
+        final Supplier<Resources> instResourcesLoader;
+        if (isSelfInstrumenting) {
+            instResourcesLoader = targetResourcesLoader;
+        } else {
+            instResourcesLoader = () -> {
+                var file = new File(RAVENWOOD_INST_RESOURCE_APK);
+                return loadResources(file.exists() ? file : null);
+            };
+        }
+
+        var instContext = new RavenwoodContext(
+                sTestPackageName, main, instResourcesLoader);
+        var targetContext = new RavenwoodContext(
+                sTargetPackageName, main, targetResourcesLoader);
+
+        // Set up app context.
+        var appContext = new RavenwoodContext(sTargetPackageName, main, targetResourcesLoader);
+        appContext.setApplicationContext(appContext);
+        if (isSelfInstrumenting) {
+            instContext.setApplicationContext(appContext);
+            targetContext.setApplicationContext(appContext);
+        } else {
+            // When instrumenting into another APK, the test context doesn't have an app context.
+            targetContext.setApplicationContext(appContext);
+        }
+
+        final Supplier<Resources> systemResourcesLoader = () -> loadResources(null);
+
+        var systemServerContext =
+                new RavenwoodContext(ANDROID_PACKAGE_NAME, main, systemResourcesLoader);
+
+        sInstrumentation = new Instrumentation();
+        sInstrumentation.basicInit(instContext, targetContext, null);
+        InstrumentationRegistry.registerInstance(sInstrumentation, Bundle.EMPTY);
+
+        RavenwoodSystemServer.init(systemServerContext);
+
+        initializeCompatIds();
+    }
+
+    /**
+     * Get log tags from environmental variable.
+     */
+    @Nullable
+    private static String getLogTags() {
+        var logTags = System.getenv(RAVENWOOD_ANDROID_LOG_TAGS);
+        if (logTags == null) {
+            logTags = System.getenv(ANDROID_LOG_TAGS);
+        }
+        return logTags;
     }
 
     private static void loadRavenwoodProperties() {
@@ -265,134 +362,41 @@
     }
 
     /**
-     * Initialize the environment.
+     * Partially reset and initialize before each test class invocation
      */
-    public static void init(RavenwoodAwareTestRunner runner) {
-        if (RAVENWOOD_VERBOSE_LOGGING) {
-            Log.v(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
-        }
-        if (sRunner == runner) {
-            return;
-        }
-        if (sRunner != null) {
-            reset();
-        }
-        sRunner = runner;
-        try {
-            initInner(runner.mState.getConfig());
-        } catch (Exception th) {
-            Log.e(TAG, "init() failed", th);
+    public static void initForRunner() {
+        var targetContext = sInstrumentation.getTargetContext();
+        var instContext = sInstrumentation.getContext();
+        // We need to recreate the mock UiAutomation for each test class, because sometimes tests
+        // will call Mockito.framework().clearInlineMocks() after execution.
+        sInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation());
 
-            RavenwoodCommonUtils.runIgnoringException(()-> reset());
+        // Reset some global state
+        Process_ravenwood.reset();
+        DeviceConfig_host.reset();
+        Binder.restoreCallingIdentity(sCallingIdentity);
 
-            SneakyThrow.sneakyThrow(th);
-        }
-    }
-
-    private static void initInner(RavenwoodConfig config) throws IOException {
-        if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
-            maybeThrowPendingUncaughtException(false);
-            Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
-        }
-
-        config.mTargetPackageName = sTargetPackageName;
-        config.mTestPackageName = sTestPackageName;
-        config.mTargetSdkLevel = sTargetSdkLevel;
-
-        Log.i(TAG, "TargetPackageName=" + sTargetPackageName);
-        Log.i(TAG, "TestPackageName=" + sTestPackageName);
-        Log.i(TAG, "TargetSdkLevel=" + sTargetSdkLevel);
-
-        RavenwoodRuntimeState.sUid = config.mUid;
-        RavenwoodRuntimeState.sPid = config.mPid;
-        RavenwoodRuntimeState.sTargetSdkLevel = config.mTargetSdkLevel;
-        sOriginalIdentityToken = Binder.clearCallingIdentity();
-        reinit();
-        setSystemProperties(config.mSystemProperties);
-
-        ServiceManager.init$ravenwood();
-        LocalServices.removeAllServicesForTest();
-
-        ActivityManager.init$ravenwood(config.mCurrentUser);
-
-        final var main = new HandlerThread(MAIN_THREAD_NAME);
-        main.start();
-        Looper.setMainLooperForTest(main.getLooper());
-
-        final boolean isSelfInstrumenting =
-                Objects.equals(config.mTestPackageName, config.mTargetPackageName);
-
-        // This will load the resources from the apk set to `resource_apk` in the build file.
-        // This is supposed to be the "target app"'s resources.
-        final Supplier<Resources> targetResourcesLoader = () -> {
-            var file = new File(RAVENWOOD_RESOURCE_APK);
-            return config.mState.loadResources(file.exists() ? file : null);
-        };
-
-        // Set up test context's (== instrumentation context's) resources.
-        // If the target package name == test package name, then we use the main resources.
-        final Supplier<Resources> instResourcesLoader;
-        if (isSelfInstrumenting) {
-            instResourcesLoader = targetResourcesLoader;
-        } else {
-            instResourcesLoader = () -> {
-                var file = new File(RAVENWOOD_INST_RESOURCE_APK);
-                return config.mState.loadResources(file.exists() ? file : null);
-            };
-        }
-
-        var instContext = new RavenwoodContext(
-                config.mTestPackageName, main, instResourcesLoader);
-        var targetContext = new RavenwoodContext(
-                config.mTargetPackageName, main, targetResourcesLoader);
-
-        // Set up app context.
-        var appContext = new RavenwoodContext(
-                config.mTargetPackageName, main, targetResourcesLoader);
-        appContext.setApplicationContext(appContext);
-        if (isSelfInstrumenting) {
-            instContext.setApplicationContext(appContext);
-            targetContext.setApplicationContext(appContext);
-        } else {
-            // When instrumenting into another APK, the test context doesn't have an app context.
-            targetContext.setApplicationContext(appContext);
-        }
-        config.mInstContext = instContext;
-        config.mTargetContext = targetContext;
-
-        final Supplier<Resources> systemResourcesLoader = () -> config.mState.loadResources(null);
-
-        config.mState.mSystemServerContext =
-                new RavenwoodContext(ANDROID_PACKAGE_NAME, main, systemResourcesLoader);
-
-        // Prepare other fields.
-        config.mInstrumentation = new Instrumentation();
-        config.mInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation());
-        InstrumentationRegistry.registerInstance(config.mInstrumentation, Bundle.EMPTY);
-
-        RavenwoodSystemServer.init(config);
-
-        initializeCompatIds(config);
+        SystemProperties.clearChangeCallbacksForTest();
 
         if (ENABLE_TIMEOUT_STACKS) {
             sPendingTimeout = sTimeoutExecutor.schedule(
                     RavenwoodRuntimeEnvironmentController::dumpStacks,
                     TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         }
-    }
-
-    /**
-     * Partially re-initialize after each test method invocation
-     */
-    public static void reinit() {
-        // sRunner could be null, if there was a failure in the initialization.
-        if (sRunner != null) {
-            var config = sRunner.mState.getConfig();
-            Binder.restoreCallingIdentity(packBinderIdentityToken(false, config.mUid, config.mPid));
+        if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
+            maybeThrowPendingUncaughtException(false);
         }
     }
 
-    private static void initializeCompatIds(RavenwoodConfig config) {
+    /**
+     * Partially reset and initialize before each test method invocation
+     */
+    public static void initForMethod() {
+        // TODO(b/375272444): this is a hacky workaround to ensure binder identity
+        Binder.restoreCallingIdentity(sCallingIdentity);
+    }
+
+    private static void initializeCompatIds() {
         // Set up compat-IDs for the app side.
         // TODO: Inside the system server, all the compat-IDs should be enabled,
         // Due to the `AppCompatCallbacks.install(new long[0], new long[0])` call in
@@ -400,8 +404,8 @@
 
         // Compat framework only uses the package name and the target SDK level.
         ApplicationInfo appInfo = new ApplicationInfo();
-        appInfo.packageName = config.mTargetPackageName;
-        appInfo.targetSdkVersion = config.mTargetSdkLevel;
+        appInfo.packageName = sTargetPackageName;
+        appInfo.targetSdkVersion = sTargetSdkLevel;
 
         PlatformCompat platformCompat = null;
         try {
@@ -418,65 +422,42 @@
     }
 
     /**
-     * De-initialize.
-     *
-     * Note, we call this method when init() fails too, so this method should deal with
-     * any partially-initialized states.
+     * Load {@link Resources} from an APK, with cache.
      */
-    public static void reset() {
-        if (RAVENWOOD_VERBOSE_LOGGING) {
-            Log.v(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
+    private static Resources loadResources(@Nullable File apkPath) {
+        var cached = sCachedResources.get(apkPath);
+        if (cached != null) {
+            return cached;
         }
-        if (sRunner == null) {
-            throw new RavenwoodRuntimeException("Internal error: reset() already called");
-        }
-        var config = sRunner.mState.getConfig();
-        sRunner = null;
 
+        var fileToLoad = apkPath != null ? apkPath : new File(RAVENWOOD_EMPTY_RESOURCES_APK);
+
+        assertTrue("File " + fileToLoad + " doesn't exist.", fileToLoad.isFile());
+
+        final String path = fileToLoad.getAbsolutePath();
+        final var emptyPaths = new String[0];
+
+        ResourcesManager.getInstance().initializeApplicationPaths(path, emptyPaths);
+
+        final var ret = ResourcesManager.getInstance().getResources(null, path,
+                emptyPaths, emptyPaths, emptyPaths,
+                emptyPaths, null, null,
+                new DisplayAdjustments().getCompatibilityInfo(),
+                RavenwoodRuntimeEnvironmentController.class.getClassLoader(), null);
+
+        assertNotNull(ret);
+
+        sCachedResources.put(apkPath, ret);
+        return ret;
+    }
+
+    /**
+     * A callback when a test class finishes its execution, mostly only for debugging.
+     */
+    public static void exitTestClass() {
         if (ENABLE_TIMEOUT_STACKS) {
             sPendingTimeout.cancel(false);
         }
-
-        RavenwoodSystemServer.reset(config);
-
-        InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
-        config.mInstrumentation = null;
-        if (config.mInstContext != null) {
-            ((RavenwoodContext) config.mInstContext).cleanUp();
-            config.mInstContext = null;
-        }
-        if (config.mTargetContext != null) {
-            ((RavenwoodContext) config.mTargetContext).cleanUp();
-            config.mTargetContext = null;
-        }
-        if (config.mState.mSystemServerContext != null) {
-            config.mState.mSystemServerContext.cleanUp();
-        }
-
-        if (Looper.getMainLooper() != null) {
-            Looper.getMainLooper().quit();
-        }
-        Looper.clearMainLooperForTest();
-
-        ActivityManager.reset$ravenwood();
-
-        LocalServices.removeAllServicesForTest();
-        ServiceManager.reset$ravenwood();
-
-        setSystemProperties(null);
-        if (sOriginalIdentityToken != -1) {
-            Binder.restoreCallingIdentity(sOriginalIdentityToken);
-        }
-        RavenwoodRuntimeState.reset();
-        Process_ravenwood.reset();
-        DeviceConfig_host.reset();
-
-        try {
-            ResourcesManager.setInstance(null); // Better structure needed.
-        } catch (Exception e) {
-            // AOSP-CHANGE: AOSP doesn't support resources yet.
-        }
-
         if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
             maybeThrowPendingUncaughtException(true);
         }
@@ -521,19 +502,6 @@
         }
     }
 
-    /**
-     * Set the current configuration to the actual SystemProperties.
-     */
-    private static void setSystemProperties(@Nullable RavenwoodSystemProperties systemProperties) {
-        SystemProperties.clearChangeCallbacksForTest();
-        RavenwoodRuntimeNative.clearSystemProperties();
-        if (systemProperties == null) systemProperties = new RavenwoodSystemProperties();
-        sProps = new RavenwoodSystemProperties(systemProperties, true);
-        for (var entry : systemProperties.getValues().entrySet()) {
-            RavenwoodRuntimeNative.setSystemProperty(entry.getKey(), entry.getValue());
-        }
-    }
-
     private static final String MOCKITO_ERROR = "FATAL: Unsupported Mockito detected!"
             + " Your test or its dependencies use one of the \"mockito-target-*\""
             + " modules as static library, which is unusable on host side."
@@ -558,40 +526,31 @@
 
     // TODO: use the real UiAutomation class instead of a mock
     private static UiAutomation createMockUiAutomation() {
-        final Set[] adoptedPermission = { Collections.emptySet() };
+        sAdoptedPermissions = Collections.emptySet();
         var mock = mock(UiAutomation.class, inv -> {
             HostTestUtils.onThrowMethodCalled();
             return null;
         });
         doAnswer(inv -> {
-            adoptedPermission[0] = UiAutomation.ALL_PERMISSIONS;
+            sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS;
             return null;
         }).when(mock).adoptShellPermissionIdentity();
         doAnswer(inv -> {
             if (inv.getArgument(0) == null) {
-                adoptedPermission[0] = UiAutomation.ALL_PERMISSIONS;
+                sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS;
             } else {
-                adoptedPermission[0] = Set.of(inv.getArguments());
+                sAdoptedPermissions = (Set) Set.of(inv.getArguments());
             }
             return null;
         }).when(mock).adoptShellPermissionIdentity(any());
         doAnswer(inv -> {
-            adoptedPermission[0] = Collections.emptySet();
+            sAdoptedPermissions = Collections.emptySet();
             return null;
         }).when(mock).dropShellPermissionIdentity();
-        doAnswer(inv -> adoptedPermission[0]).when(mock).getAdoptedShellPermissions();
+        doAnswer(inv -> sAdoptedPermissions).when(mock).getAdoptedShellPermissions();
         return mock;
     }
 
-    @SuppressWarnings("unused")  // Called from native code (ravenwood_sysprop.cpp)
-    private static void checkSystemPropertyAccess(String key, boolean write) {
-        boolean result = write ? sProps.isKeyWritable(key) : sProps.isKeyReadable(key);
-        if (!result) {
-            throw new IllegalArgumentException((write ? "Write" : "Read")
-                    + " access to system property '" + key + "' denied via RavenwoodConfig");
-        }
-    }
-
     private static void dumpCommandLineArgs() {
         Log.i(TAG, "JVM arguments:");
 
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
new file mode 100644
index 0000000..fac0791
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -0,0 +1,197 @@
+/*
+ * 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 static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath;
+
+import android.util.Log;
+
+import com.android.ravenwood.RavenwoodRuntimeNative;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class to manage the core default system properties of the Ravenwood environment.
+ */
+public class RavenwoodSystemProperties {
+    private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
+
+    /** We pull in properties from this file. */
+    private static final String RAVENWOOD_BUILD_PROP = "ravenwood-data/ravenwood-build.prop";
+
+    /** This is the actual build.prop we use to build the device (contents depends on lunch). */
+    private static final String DEVICE_BUILD_PROP = "ravenwood-data/build.prop";
+
+    /** The default values. */
+    static final Map<String, String> sDefaultValues = new HashMap<>();
+
+    private static final String[] PARTITIONS = {
+            "bootimage",
+            "odm",
+            "product",
+            "system",
+            "system_ext",
+            "vendor",
+            "vendor_dlkm",
+    };
+
+    static Map<String, String> readProperties(String propFile) {
+        // Use an ordered map just for cleaner dump log.
+        final Map<String, String> ret = new LinkedHashMap<>();
+        try {
+            Files.readAllLines(Path.of(propFile)).stream()
+                    .map(String::trim)
+                    .filter(s -> !s.startsWith("#"))
+                    .map(s -> s.split("\\s*=\\s*", 2))
+                    .filter(a -> a.length == 2 && a[1].length() > 0)
+                    .forEach(a -> ret.put(a[0], a[1]));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return ret;
+    }
+
+    /**
+     * Load default sysprops from {@link #RAVENWOOD_BUILD_PROP}. We also pull in
+     * certain properties from the acutual device's build.prop {@link #DEVICE_BUILD_PROP} too.
+     *
+     * More info about property file loading: system/core/init/property_service.cpp
+     * In the following logic, the only partition we would need to consider is "system",
+     * since we only read from system-build.prop
+     */
+    static void initialize() {
+        var path = getRavenwoodRuntimePath();
+        var ravenwoodProps = readProperties(path + RAVENWOOD_BUILD_PROP);
+        var deviceProps = readProperties(path + DEVICE_BUILD_PROP);
+
+        Log.i(TAG, "Default system properties:");
+        ravenwoodProps.forEach((key, origValue) -> {
+            final String value;
+
+            // If a value starts with "$$$", then this is a reference to the device-side value.
+            if (origValue.startsWith("$$$")) {
+                var deviceKey = origValue.substring(3);
+                var deviceValue = deviceProps.get(deviceKey);
+                if (deviceValue == null) {
+                    throw new RuntimeException("Failed to initialize system properties. Key '"
+                            + deviceKey + "' doesn't exist in the device side build.prop");
+                }
+                value = deviceValue;
+            } else {
+                value = origValue;
+            }
+            Log.i(TAG, key + "=" + value);
+            sDefaultValues.put(key, value);
+        });
+
+        // Copy ro.product.* and ro.build.* to all partitions, just in case
+        // We don't want to log these because these are just a lot of duplicate values
+        for (var entry : Set.copyOf(sDefaultValues.entrySet())) {
+            var key = entry.getKey();
+            if (key.startsWith("ro.product.") || key.startsWith("ro.build.")) {
+                var name = key.substring(3);
+                for (String partition : PARTITIONS) {
+                    var newKey = "ro." + partition + "." + name;
+                    if (!sDefaultValues.containsKey(newKey)) {
+                        sDefaultValues.put(newKey, entry.getValue());
+                    }
+                }
+            }
+        }
+
+        if (RAVENWOOD_VERBOSE_LOGGING) {
+            // Dump all properties for local debugging.
+            Log.v(TAG, "All system properties:");
+            for (var key : sDefaultValues.keySet().stream().sorted().toList()) {
+                Log.v(TAG, "" + key + "=" + sDefaultValues.get(key));
+            }
+        }
+
+        // Actually set the system properties
+        sDefaultValues.forEach(RavenwoodRuntimeNative::setSystemProperty);
+    }
+
+    private static boolean isKeyReadable(String key) {
+        // All writable keys are also readable
+        if (isKeyWritable(key)) return true;
+
+        final String root = getKeyRoot(key);
+
+        // This set is carefully curated to help identify situations where a test may
+        // accidentally depend on a default value of an obscure property whose owner hasn't
+        // decided how Ravenwood should behave.
+        if (root.startsWith("boot.")) return true;
+        if (root.startsWith("build.")) return true;
+        if (root.startsWith("product.")) return true;
+        if (root.startsWith("soc.")) return true;
+        if (root.startsWith("system.")) return true;
+
+        // All core values should be readable
+        if (sDefaultValues.containsKey(key)) return true;
+
+        // Hardcoded allowlist
+        return switch (key) {
+            case "gsm.version.baseband",
+                 "no.such.thing",
+                 "qemu.sf.lcd_density",
+                 "ro.bootloader",
+                 "ro.hardware",
+                 "ro.hw_timeout_multiplier",
+                 "ro.odm.build.media_performance_class",
+                 "ro.sf.lcd_density",
+                 "ro.treble.enabled",
+                 "ro.vndk.version",
+                 "ro.icu.data.path" -> true;
+            default -> false;
+        };
+    }
+
+    private static boolean isKeyWritable(String key) {
+        final String root = getKeyRoot(key);
+
+        if (root.startsWith("debug.")) return true;
+
+        // For PropertyInvalidatedCache
+        if (root.startsWith("cache_key.")) return true;
+
+        return false;
+    }
+
+    static boolean isKeyAccessible(String key, boolean write) {
+        return write ? isKeyWritable(key) : isKeyReadable(key);
+    }
+
+    /**
+     * Return the "root" of the given property key, stripping away any modifier prefix such as
+     * {@code ro.} or {@code persist.}.
+     */
+    private static String getKeyRoot(String key) {
+        if (key.startsWith("ro.")) {
+            return key.substring(3);
+        } else if (key.startsWith("persist.")) {
+            return key.substring(8);
+        } else {
+            return key;
+        }
+    }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
index 438a2bf..3346635 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
@@ -33,7 +33,7 @@
 import com.android.server.compat.PlatformCompatNative;
 import com.android.server.utils.TimingsTraceAndSlog;
 
-import java.util.List;
+import java.util.Collection;
 import java.util.Set;
 
 public class RavenwoodSystemServer {
@@ -68,27 +68,24 @@
     private static TimingsTraceAndSlog sTimings;
     private static SystemServiceManager sServiceManager;
 
-    public static void init(RavenwoodConfig config) {
+    public static void init(Context systemServerContext) {
         // Always start PlatformCompat, regardless of the requested services.
         // PlatformCompat is not really a SystemService, so it won't receive boot phases / etc.
         // This initialization code is copied from SystemServer.java.
-        PlatformCompat platformCompat = new PlatformCompat(config.mState.mSystemServerContext);
+        PlatformCompat platformCompat = new PlatformCompat(systemServerContext);
         ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE, platformCompat);
         ServiceManager.addService(Context.PLATFORM_COMPAT_NATIVE_SERVICE,
                 new PlatformCompatNative(platformCompat));
 
-        // Avoid overhead if no services required
-        if (config.mServicesRequired.isEmpty()) return;
-
         sStartedServices = new ArraySet<>();
         sTimings = new TimingsTraceAndSlog();
-        sServiceManager = new SystemServiceManager(config.mState.mSystemServerContext);
+        sServiceManager = new SystemServiceManager(systemServerContext);
         sServiceManager.setStartInfo(false,
                 SystemClock.elapsedRealtime(),
                 SystemClock.uptimeMillis());
         LocalServices.addService(SystemServiceManager.class, sServiceManager);
 
-        startServices(config.mServicesRequired);
+        startServices(sKnownServices.keySet());
         sServiceManager.sealStartedServices();
 
         // TODO: expand to include additional boot phases when relevant
@@ -96,7 +93,7 @@
         sServiceManager.startBootPhase(sTimings, SystemService.PHASE_BOOT_COMPLETED);
     }
 
-    public static void reset(RavenwoodConfig config) {
+    public static void reset() {
         // TODO: consider introducing shutdown boot phases
 
         LocalServices.removeServiceForTest(SystemServiceManager.class);
@@ -105,7 +102,7 @@
         sStartedServices = null;
     }
 
-    private static void startServices(List<Class<?>> serviceClasses) {
+    private static void startServices(Collection<Class<?>> serviceClasses) {
         for (Class<?> serviceClass : serviceClasses) {
             // Quietly ignore duplicate requests if service already started
             if (sStartedServices.contains(serviceClass)) continue;
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
index 7870585..c8b10d2 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -41,7 +41,7 @@
  * `/tmp/Ravenwood-stats_[TEST-MODULE=NAME]_latest.csv`.
  */
 public class RavenwoodTestStats {
-    private static final String TAG = "RavenwoodTestStats";
+    private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
     private static final String HEADER = "Module,Class,OuterClass,Passed,Failed,Skipped";
 
     private static RavenwoodTestStats sInstance;
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java
index 31a1416..f3688d6 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java
@@ -35,7 +35,7 @@
 import org.junit.runners.model.TestClass;
 
 abstract class RavenwoodAwareTestRunnerBase extends Runner implements Filterable, Orderable {
-    private static final String TAG = "Ravenwood";
+    public static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
 
     boolean mRealRunnerTakesRunnerBuilder = false;
 
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index 7ca9239..3ed0f50 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -15,21 +15,13 @@
  */
 package android.platform.test.ravenwood;
 
-import static android.os.Process.FIRST_APPLICATION_UID;
-import static android.os.UserHandle.SYSTEM;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Instrumentation;
-import android.content.Context;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * @deprecated This class will be removed. Reach out to g/ravenwood if you need any features in it.
@@ -45,37 +37,10 @@
     public @interface Config {
     }
 
-    private static final int NOBODY_UID = 9999;
-
-    private static final AtomicInteger sNextPid = new AtomicInteger(100);
-
-    int mCurrentUser = SYSTEM.getIdentifier();
-
-    /**
-     * Unless the test author requests differently, run as "nobody", and give each collection of
-     * tests its own unique PID.
-     */
-    int mUid = FIRST_APPLICATION_UID;
-    int mPid = sNextPid.getAndIncrement();
-
-    String mTestPackageName;
-    String mTargetPackageName;
-
-    int mTargetSdkLevel;
-
-    final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
-
-    final List<Class<?>> mServicesRequired = new ArrayList<>();
-
-    volatile Context mInstContext;
-    volatile Context mTargetContext;
-    volatile Instrumentation mInstrumentation;
-
     /**
      * Stores internal states / methods associated with this config that's only needed in
      * junit-impl.
      */
-    final RavenwoodConfigState mState = new RavenwoodConfigState(this);
     private RavenwoodConfig() {
     }
 
@@ -159,34 +124,11 @@
             return this;
         }
 
-        Builder setSystemPropertyImmutableReal(@NonNull String key,
-                @Nullable Object value) {
-            mConfig.mSystemProperties.setValue(key, value);
-            mConfig.mSystemProperties.setAccessReadOnly(key);
-            return this;
-        }
-
-        Builder setSystemPropertyMutableReal(@NonNull String key,
-                @Nullable Object value) {
-            mConfig.mSystemProperties.setValue(key, value);
-            mConfig.mSystemProperties.setAccessReadWrite(key);
-            return this;
-        }
-
         /**
-         * Configure the set of system services that are required for this test to operate.
-         *
-         * For example, passing {@code android.hardware.SerialManager.class} as an argument will
-         * ensure that the underlying service is created, initialized, and ready to use for the
-         * duration of the test. The {@code SerialManager} instance can be obtained via
-         * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and
-         * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}.
+         * @deprecated no longer used. All supported services are available.
          */
+        @Deprecated
         public Builder setServicesRequired(@NonNull Class<?>... services) {
-            mConfig.mServicesRequired.clear();
-            for (Class<?> service : services) {
-                mConfig.mServicesRequired.add(service);
-            }
             return this;
         }
 
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 5681a90..d8cde0e 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -41,7 +41,7 @@
  */
 @Deprecated
 public final class RavenwoodRule implements TestRule {
-    private static final String TAG = "RavenwoodRule";
+    private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
 
     static final boolean IS_ON_RAVENWOOD = RavenwoodCommonUtils.isOnRavenwood();
 
@@ -92,19 +92,11 @@
         }
     }
 
-    private final RavenwoodConfig mConfiguration;
-
-    public RavenwoodRule() {
-        mConfiguration = new RavenwoodConfig.Builder().build();
-    }
-
-    private RavenwoodRule(RavenwoodConfig config) {
-        mConfiguration = config;
-    }
+    final RavenwoodTestProperties mProperties = new RavenwoodTestProperties();
 
     public static class Builder {
-        private final RavenwoodConfig.Builder mBuilder =
-                new RavenwoodConfig.Builder();
+
+        private final RavenwoodRule mRule = new RavenwoodRule();
 
         public Builder() {
         }
@@ -152,7 +144,8 @@
          * Has no effect on non-Ravenwood environments.
          */
         public Builder setSystemPropertyImmutable(@NonNull String key, @Nullable Object value) {
-            mBuilder.setSystemPropertyImmutableReal(key, value);
+            mRule.mProperties.setValue(key, value);
+            mRule.mProperties.setAccessReadOnly(key);
             return this;
         }
 
@@ -167,26 +160,21 @@
          * Has no effect on non-Ravenwood environments.
          */
         public Builder setSystemPropertyMutable(@NonNull String key, @Nullable Object value) {
-            mBuilder.setSystemPropertyMutableReal(key, value);
+            mRule.mProperties.setValue(key, value);
+            mRule.mProperties.setAccessReadWrite(key);
             return this;
         }
 
         /**
-         * Configure the set of system services that are required for this test to operate.
-         *
-         * For example, passing {@code android.hardware.SerialManager.class} as an argument will
-         * ensure that the underlying service is created, initialized, and ready to use for the
-         * duration of the test. The {@code SerialManager} instance can be obtained via
-         * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and
-         * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}.
+         * @deprecated no longer used. All supported services are available.
          */
+        @Deprecated
         public Builder setServicesRequired(@NonNull Class<?>... services) {
-            mBuilder.setServicesRequired(services);
             return this;
         }
 
         public RavenwoodRule build() {
-            return new RavenwoodRule(mBuilder.build());
+            return mRule;
         }
     }
 
@@ -205,6 +193,12 @@
         return IS_ON_RAVENWOOD;
     }
 
+    private static void ensureOnRavenwood(String featureName) {
+        if (!IS_ON_RAVENWOOD) {
+            throw new RuntimeException(featureName + " is only supported on Ravenwood.");
+        }
+    }
+
     /**
      * @deprecated Use
      * {@code androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getContext()}
@@ -227,7 +221,7 @@
 
     @Override
     public Statement apply(Statement base, Description description) {
-        if (!RavenwoodConfig.isOnRavenwood()) {
+        if (!IS_ON_RAVENWOOD) {
             return base;
         }
         return new Statement() {
@@ -254,6 +248,40 @@
         return System.currentTimeMillis();
     }
 
+    /**
+     * Equivalent to setting the ANDROID_LOG_TAGS environmental variable.
+     *
+     * See https://developer.android.com/tools/logcat#filteringOutput for the string format.
+     *
+     * NOTE: this works only on Ravenwood.
+     */
+    public static void setAndroidLogTags(@Nullable String androidLogTags) {
+        ensureOnRavenwood("RavenwoodRule.setAndroidLogTags()");
+        try {
+            Class<?> logRavenwoodClazz = Class.forName("android.util.Log_ravenwood");
+            var setter = logRavenwoodClazz.getMethod("setLogLevels", String.class);
+            setter.invoke(null, androidLogTags);
+        } catch (ReflectiveOperationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Set a log level for a given tag. Pass NULL to {@code tag} to change the default level.
+     *
+     * NOTE: this works only on Ravenwood.
+     */
+    public static void setLogLevel(@Nullable String tag, int level) {
+        ensureOnRavenwood("RavenwoodRule.setLogLevel()");
+        try {
+            Class<?> logRavenwoodClazz = Class.forName("android.util.Log_ravenwood");
+            var setter = logRavenwoodClazz.getMethod("setLogLevel", String.class, int.class);
+            setter.invoke(null, tag, level);
+        } catch (ReflectiveOperationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     // Below are internal to ravenwood. Don't use them from normal tests...
 
     public static class RavenwoodPrivate {
@@ -296,8 +324,4 @@
     public static RavenwoodPrivate private$ravenwood() {
         return sRavenwoodPrivate;
     }
-
-    RavenwoodConfig getConfiguration() {
-        return mConfiguration;
-    }
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
deleted file mode 100644
index 9bd376a..0000000
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ /dev/null
@@ -1,248 +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 android.platform.test.ravenwood;
-
-import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
-import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath;
-
-import android.util.Log;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Set;
-
-public class RavenwoodSystemProperties {
-    private static final String TAG = "RavenwoodSystemProperties";
-
-    /** We pull in propeties from this file. */
-    private static final String RAVENWOOD_BUILD_PROP = "ravenwood-data/ravenwood-build.prop";
-
-    /** This is the actual build.prop we use to build the device (contents depends on lunch). */
-    private static final String DEVICE_BUILD_PROP = "ravenwood-data/build.prop";
-
-    /** The default values. */
-    private static final Map<String, String> sDefaultValues = new HashMap<>();
-
-    private static final String[] PARTITIONS = {
-            "bootimage",
-            "odm",
-            "product",
-            "system",
-            "system_ext",
-            "vendor",
-            "vendor_dlkm",
-    };
-
-    static Map<String, String> readProperties(String propFile) {
-        // Use an ordered map just for cleaner dump log.
-        final Map<String, String> ret = new LinkedHashMap<>();
-        try {
-            Files.readAllLines(Path.of(propFile)).stream()
-                    .map(String::trim)
-                    .filter(s -> !s.startsWith("#"))
-                    .map(s -> s.split("\\s*=\\s*", 2))
-                    .filter(a -> a.length == 2 && a[1].length() > 0)
-                    .forEach(a -> ret.put(a[0], a[1]));
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-        return ret;
-    }
-
-    /**
-     * Load default sysprops from {@link #RAVENWOOD_BUILD_PROP}. We also pull in
-     * certain properties from the acutual device's build.prop {@link #DEVICE_BUILD_PROP} too.
-     *
-     * More info about property file loading: system/core/init/property_service.cpp
-     * In the following logic, the only partition we would need to consider is "system",
-     * since we only read from system-build.prop
-     */
-    static void initialize() {
-        var path = getRavenwoodRuntimePath();
-        var ravenwoodProps = readProperties(path + RAVENWOOD_BUILD_PROP);
-        var deviceProps = readProperties(path + DEVICE_BUILD_PROP);
-
-        Log.i(TAG, "Default system properties:");
-        ravenwoodProps.forEach((key, origValue) -> {
-            final String value;
-
-            // If a value starts with "$$$", then this is a reference to the device-side value.
-            if (origValue.startsWith("$$$")) {
-                var deviceKey = origValue.substring(3);
-                var deviceValue = deviceProps.get(deviceKey);
-                if (deviceValue == null) {
-                    throw new RuntimeException("Failed to initialize system properties. Key '"
-                             + deviceKey + "' doesn't exist in the device side build.prop");
-                }
-                value = deviceValue;
-            } else {
-                value = origValue;
-            }
-            Log.i(TAG, key + "=" + value);
-            sDefaultValues.put(key, value);
-        });
-
-        // Copy ro.product.* and ro.build.* to all partitions, just in case
-        // We don't want to log these because these are just a lot of duplicate values
-        for (var entry : Set.copyOf(sDefaultValues.entrySet())) {
-            var key = entry.getKey();
-            if (key.startsWith("ro.product.") || key.startsWith("ro.build.")) {
-                var name = key.substring(3);
-                for (String partition : PARTITIONS) {
-                    var newKey = "ro." + partition + "." + name;
-                    if (!sDefaultValues.containsKey(newKey)) {
-                        sDefaultValues.put(newKey, entry.getValue());
-                    }
-                }
-            }
-        }
-        if (RAVENWOOD_VERBOSE_LOGGING) {
-            // Dump all properties for local debugging.
-            Log.v(TAG, "All system properties:");
-            for (var key : sDefaultValues.keySet().stream().sorted().toList()) {
-                Log.v(TAG, "" + key + "=" + sDefaultValues.get(key));
-            }
-        }
-    }
-
-    private volatile boolean mIsImmutable;
-
-    private final Map<String, String> mValues = new HashMap<>();
-
-    /** Set of additional keys that should be considered readable */
-    private final Set<String> mKeyReadable = new HashSet<>();
-
-    /** Set of additional keys that should be considered writable */
-    private final Set<String> mKeyWritable = new HashSet<>();
-
-    public RavenwoodSystemProperties() {
-        mValues.putAll(sDefaultValues);
-    }
-
-    /** Copy constructor */
-    public RavenwoodSystemProperties(RavenwoodSystemProperties source, boolean immutable) {
-        mKeyReadable.addAll(source.mKeyReadable);
-        mKeyWritable.addAll(source.mKeyWritable);
-        mValues.putAll(source.mValues);
-        mIsImmutable = immutable;
-    }
-
-    public Map<String, String> getValues() {
-        return new HashMap<>(mValues);
-    }
-
-    public boolean isKeyReadable(String key) {
-        final String root = getKeyRoot(key);
-
-        if (root.startsWith("debug.")) return true;
-
-        // This set is carefully curated to help identify situations where a test may
-        // accidentally depend on a default value of an obscure property whose owner hasn't
-        // decided how Ravenwood should behave.
-        if (root.startsWith("boot.")) return true;
-        if (root.startsWith("build.")) return true;
-        if (root.startsWith("product.")) return true;
-        if (root.startsWith("soc.")) return true;
-        if (root.startsWith("system.")) return true;
-
-        // For PropertyInvalidatedCache
-        if (root.startsWith("cache_key.")) return true;
-
-        switch (key) {
-            case "gsm.version.baseband":
-            case "no.such.thing":
-            case "qemu.sf.lcd_density":
-            case "ro.bootloader":
-            case "ro.debuggable":
-            case "ro.hardware":
-            case "ro.hw_timeout_multiplier":
-            case "ro.odm.build.media_performance_class":
-            case "ro.sf.lcd_density":
-            case "ro.treble.enabled":
-            case "ro.vndk.version":
-            case "ro.icu.data.path":
-                return true;
-        }
-
-        return mKeyReadable.contains(key);
-    }
-
-    public boolean isKeyWritable(String key) {
-        final String root = getKeyRoot(key);
-
-        if (root.startsWith("debug.")) return true;
-
-        // For PropertyInvalidatedCache
-        if (root.startsWith("cache_key.")) return true;
-
-        return mKeyWritable.contains(key);
-    }
-
-    private void ensureNotImmutable() {
-        if (mIsImmutable) {
-            throw new RuntimeException("Unable to update immutable instance");
-        }
-    }
-
-    public void setValue(String key, Object value) {
-        ensureNotImmutable();
-
-        final String valueString = (value == null) ? null : String.valueOf(value);
-        if ((valueString == null) || valueString.isEmpty()) {
-            mValues.remove(key);
-        } else {
-            mValues.put(key, valueString);
-        }
-    }
-
-    public void setAccessNone(String key) {
-        ensureNotImmutable();
-        mKeyReadable.remove(key);
-        mKeyWritable.remove(key);
-    }
-
-    public void setAccessReadOnly(String key) {
-        ensureNotImmutable();
-        mKeyReadable.add(key);
-        mKeyWritable.remove(key);
-    }
-
-    public void setAccessReadWrite(String key) {
-        ensureNotImmutable();
-        mKeyReadable.add(key);
-        mKeyWritable.add(key);
-    }
-
-    /**
-     * Return the "root" of the given property key, stripping away any modifier prefix such as
-     * {@code ro.} or {@code persist.}.
-     */
-    private static String getKeyRoot(String key) {
-        if (key.startsWith("ro.")) {
-            return key.substring(3);
-        } else if (key.startsWith("persist.")) {
-            return key.substring(8);
-        } else {
-            return key;
-        }
-    }
-}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodTestProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodTestProperties.java
new file mode 100644
index 0000000..66a26b5
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodTestProperties.java
@@ -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 android.platform.test.ravenwood;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class to store system properties defined by tests.
+ */
+public class RavenwoodTestProperties {
+    final Map<String, String> mValues = new HashMap<>();
+
+    /** Set of additional keys that should be considered readable */
+    final Set<String> mKeyReadable = new HashSet<>();
+
+    /** Set of additional keys that should be considered writable */
+    final Set<String> mKeyWritable = new HashSet<>();
+
+    public void setValue(String key, Object value) {
+        final String valueString = (value == null) ? null : String.valueOf(value);
+        if ((valueString == null) || valueString.isEmpty()) {
+            mValues.remove(key);
+        } else {
+            mValues.put(key, valueString);
+        }
+    }
+
+    public void setAccessNone(String key) {
+        mKeyReadable.remove(key);
+        mKeyWritable.remove(key);
+    }
+
+    public void setAccessReadOnly(String key) {
+        mKeyReadable.add(key);
+        mKeyWritable.remove(key);
+    }
+
+    public void setAccessReadWrite(String key) {
+        mKeyReadable.add(key);
+        mKeyWritable.add(key);
+    }
+}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index b4b75178..8d9bcd5 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -31,8 +31,6 @@
  * This is only used when a real device-side test has Ravenizer enabled.
  */
 public class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase {
-    private static final String TAG = "Ravenwood";
-
     private static class NopRule implements TestRule {
         @Override
         public Statement apply(Statement base, Description description) {
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java
deleted file mode 100644
index 7d3d8b9..0000000
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java
+++ /dev/null
@@ -1,22 +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 android.platform.test.ravenwood;
-
-/** Stub class. The actual implementation is in junit-impl-src. */
-public class RavenwoodConfigState {
-    public RavenwoodConfigState(RavenwoodConfig config) {
-    }
-}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index 2a04d44..a967a3f 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -33,7 +33,7 @@
 import java.util.function.Supplier;
 
 public class RavenwoodCommonUtils {
-    private static final String TAG = "RavenwoodCommonUtils";
+    public static final String TAG = "Ravenwood";
 
     private RavenwoodCommonUtils() {
     }
diff --git a/ravenwood/runtime-helper-src/framework/android/util/Log_host.java b/ravenwood/runtime-helper-src/framework/android/util/Log_host.java
deleted file mode 100644
index c85bd23..0000000
--- a/ravenwood/runtime-helper-src/framework/android/util/Log_host.java
+++ /dev/null
@@ -1,84 +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 android.util;
-
-import android.util.Log.Level;
-
-import com.android.internal.os.RuntimeInit;
-import com.android.ravenwood.common.RavenwoodCommonUtils;
-
-import java.io.PrintStream;
-
-/**
- * Ravenwood "native substitution" class for {@link android.util.Log}.
- *
- * {@link android.util.Log} already uses the actual native code and doesn't use this class.
- * In order to switch to this Java implementation, uncomment the @RavenwoodNativeSubstitutionClass
- * annotation on {@link android.util.Log}.
- */
-public class Log_host {
-
-    public static boolean isLoggable(String tag, @Level int level) {
-        return true;
-    }
-
-    public static int println_native(int bufID, int priority, String tag, String msg) {
-        if (priority < Log.INFO && !RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING) {
-            return msg.length(); // No verbose logging.
-        }
-        final String buffer;
-        switch (bufID) {
-            case Log.LOG_ID_MAIN: buffer = "main"; break;
-            case Log.LOG_ID_RADIO: buffer = "radio"; break;
-            case Log.LOG_ID_EVENTS: buffer = "event"; break;
-            case Log.LOG_ID_SYSTEM: buffer = "system"; break;
-            case Log.LOG_ID_CRASH: buffer = "crash"; break;
-            default: buffer = "buf:" + bufID; break;
-        }
-
-        final String prio;
-        switch (priority) {
-            case Log.VERBOSE: prio = "V"; break;
-            case Log.DEBUG: prio = "D"; break;
-            case Log.INFO: prio = "I"; break;
-            case Log.WARN: prio = "W"; break;
-            case Log.ERROR: prio = "E"; break;
-            case Log.ASSERT: prio = "A"; break;
-            default: prio = "prio:" + priority; break;
-        }
-
-        for (String s : msg.split("\\n")) {
-            getRealOut().println(String.format("logd: [%s] %s %s: %s", buffer, prio, tag, s));
-        }
-        return msg.length();
-    }
-
-    public static int logger_entry_max_payload_native() {
-        return 4068; // [ravenwood] This is what people use in various places.
-    }
-
-    /**
-     * Return the "real" {@code System.out} if it's been swapped by {@code RavenwoodRuleImpl}, so
-     * that we don't end up in a recursive loop.
-     */
-    private static PrintStream getRealOut() {
-        if (RuntimeInit.sOut$ravenwood != null) {
-            return RuntimeInit.sOut$ravenwood;
-        } else {
-            return System.out;
-        }
-    }
-}
diff --git a/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java b/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java
new file mode 100644
index 0000000..7ab9cda
--- /dev/null
+++ b/ravenwood/runtime-helper-src/framework/android/util/Log_ravenwood.java
@@ -0,0 +1,204 @@
+/*
+ * 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 android.util;
+
+import android.annotation.Nullable;
+import android.util.Log.Level;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.RuntimeInit;
+import com.android.ravenwood.RavenwoodRuntimeNative;
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
+import java.io.PrintStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Ravenwood "native substitution" class for {@link android.util.Log}.
+ *
+ * {@link android.util.Log} already uses the actual native code and doesn't use this class.
+ * In order to switch to this Java implementation, uncomment the @RavenwoodNativeSubstitutionClass
+ * annotation on {@link android.util.Log}.
+ */
+public class Log_ravenwood {
+
+    private static final SimpleDateFormat sTimestampFormat =
+            new SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US);
+
+    private static final Object sLock = new Object();
+
+    @GuardedBy("sLock")
+    private static int sDefaultLogLevel;
+
+    @GuardedBy("sLock")
+    private static final Map<String, Integer> sTagLogLevels = new HashMap<>();
+
+    /**
+     * Used by {@link android.platform.test.ravenwood.RavenwoodRule#setAndroidLogTags(String)}
+     * via reflections.
+     */
+    public static void setLogLevels(String androidLogTags) {
+        var map = parseLogLevels(androidLogTags);
+
+        synchronized (sLock) {
+            sTagLogLevels.clear();
+            sTagLogLevels.putAll(map);
+
+            var def = map.get("*");
+            sDefaultLogLevel = def != null ? def : Log.VERBOSE;
+        }
+    }
+
+    private static Map<String, Integer> parseLogLevels(String androidLogTags) {
+        final Map<String, Integer> ret = new HashMap<>();
+
+        if (androidLogTags == null) {
+            return ret;
+        }
+
+        String[] tagPairs = androidLogTags.trim().split("\\s+");
+        for (String tagPair : tagPairs) {
+            String[] parts = tagPair.split(":");
+            if (parts.length == 2) {
+                String tag = parts[0];
+                try {
+                    int priority = switch (parts[1].toUpperCase(Locale.ROOT)) {
+                        case "V": yield Log.VERBOSE;
+                        case "D": yield Log.DEBUG;
+                        case "I": yield Log.INFO;
+                        case "W": yield Log.WARN;
+                        case "E": yield Log.ERROR;
+                        case "F": yield Log.ERROR + 1; // Not used in the java side.
+                        case "S": yield Integer.MAX_VALUE; // Silent
+                        default: throw new IllegalArgumentException(
+                                "Invalid priority level for tag: " + tag);
+                    };
+
+                    ret.put(tag, priority);
+                } catch (IllegalArgumentException e) {
+                    System.err.println(e.getMessage());
+                }
+            } else {
+                System.err.println("Invalid tag format: " + tagPair);
+            }
+        }
+
+        return ret;
+    }
+
+    /**
+     * Used by {@link android.platform.test.ravenwood.RavenwoodRule#setLogLevel(String, int)}
+     * via reflections. Pass NULL to {@code tag} to set the default level.
+     */
+    public static void setLogLevel(@Nullable String tag, int level) {
+        synchronized (sLock) {
+            if (tag == null) {
+                sDefaultLogLevel = level;
+            } else {
+                sTagLogLevels.put(tag, level);
+            }
+        }
+    }
+
+    /**
+     * Replaces {@link Log#isLoggable}.
+     */
+    public static boolean isLoggable(String tag, @Level int priority) {
+        synchronized (sLock) {
+            var threshold = sTagLogLevels.get(tag);
+            if (threshold == null) {
+                threshold = sDefaultLogLevel;
+            }
+            return priority >= threshold;
+        }
+    }
+
+    public static int println_native(int bufID, int priority, String tag, String msg) {
+        if (!isLoggable(tag, priority)) {
+            return msg.length();
+        }
+
+        final String prio;
+        switch (priority) {
+            case Log.VERBOSE: prio = "V"; break;
+            case Log.DEBUG: prio = "D"; break;
+            case Log.INFO: prio = "I"; break;
+            case Log.WARN: prio = "W"; break;
+            case Log.ERROR: prio = "E"; break;
+            case Log.ASSERT: prio = "A"; break;
+            default: prio = "prio:" + priority; break;
+        }
+
+        String leading =  sTimestampFormat.format(new Date())
+                + " %-6d %-6d %s %-8s: ".formatted(getPid(), getTid(), prio, tag);
+        var out = getRealOut();
+        for (String s : msg.split("\\n")) {
+            out.print(leading);
+            out.println(s);
+        }
+        return msg.length();
+    }
+
+    public static int logger_entry_max_payload_native() {
+        return 4068; // [ravenwood] This is what people use in various places.
+    }
+
+    /**
+     * Return the "real" {@code System.out} if it's been swapped by {@code RavenwoodRuleImpl}, so
+     * that we don't end up in a recursive loop.
+     */
+    private static PrintStream getRealOut() {
+        if (RuntimeInit.sOut$ravenwood != null) {
+            return RuntimeInit.sOut$ravenwood;
+        } else {
+            return System.out;
+        }
+    }
+
+    /**
+     * PID. We need to use a JNI method to get it, but JNI isn't initially ready.
+     * Call {@link #onRavenwoodRuntimeNativeReady} to signal when JNI is ready, at which point
+     * we set this field.
+     * (We don't want to call native methods that may not be fully initialized even with a
+     * try-catch, because partially initialized JNI methods could crash the process.)
+     */
+    private static volatile int sPid = 0;
+
+    private static ThreadLocal<Integer> sTid =
+            ThreadLocal.withInitial(RavenwoodRuntimeNative::gettid);
+
+    /**
+     * Call it when {@link RavenwoodRuntimeNative} is usable.
+     */
+    public static void onRavenwoodRuntimeNativeReady() {
+        sPid = RavenwoodRuntimeNative.getpid();
+    }
+
+    private static int getPid() {
+        return sPid;
+    }
+
+    private static int getTid() {
+        if (sPid == 0) {
+            return 0; // Native methods not ready yet.
+        }
+        return sTid.get();
+    }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index 7b940b4..acbcdf1 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -56,7 +56,13 @@
 
     public static native boolean setSystemProperty(String key, String value);
 
-    public static native void clearSystemProperties();
+    public static native boolean removeSystemProperty(String key);
+
+    public static void clearSystemProperties() {
+        removeSystemProperty(null);
+    }
+
+    public static native int getpid();
 
     public static native int gettid();
 
diff --git a/ravenwood/runtime-jni/jni_helper.h b/ravenwood/runtime-jni/jni_helper.h
index 561fb3b..25d7519 100644
--- a/ravenwood/runtime-jni/jni_helper.h
+++ b/ravenwood/runtime-jni/jni_helper.h
@@ -26,6 +26,7 @@
 constexpr const char* kCommonUtils = "com/android/ravenwood/common/RavenwoodCommonUtils";
 constexpr const char* kRuntimeEnvController =
         "android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController";
+constexpr const char* kRunnerState = "android/platform/test/ravenwood/RavenwoodRunnerState";
 constexpr const char* kRuntimeNative = "com/android/ravenwood/RavenwoodRuntimeNative";
 
 // We have to explicitly decode the string to real UTF-8, because when using GetStringUTFChars
diff --git a/ravenwood/runtime-jni/ravenwood_initializer.cpp b/ravenwood/runtime-jni/ravenwood_initializer.cpp
index 89fb7c3..dbbc345 100644
--- a/ravenwood/runtime-jni/ravenwood_initializer.cpp
+++ b/ravenwood/runtime-jni/ravenwood_initializer.cpp
@@ -14,16 +14,174 @@
  * limitations under the License.
  */
 
- /*
-  * This file is compiled into a single SO file, which we load at the very first.
-  * We can do process-wide initialization here.
-  */
+/*
+ * This file is compiled into a single SO file, which we load at the very first.
+ * We can do process-wide initialization here.
+ * Please be aware that all symbols defined in this SO file will be reloaded
+ * as `RTLD_GLOBAL`, so make sure all functions are static except those we EXPLICITLY
+ * want to expose and override globally.
+ */
 
+#include <dlfcn.h>
 #include <fcntl.h>
-#include <unistd.h>
+
+#include <set>
 
 #include "jni_helper.h"
 
+// Implement a rudimentary system properties data store
+
+#define PROP_VALUE_MAX 92
+
+namespace {
+
+struct prop_info {
+    std::string key;
+    mutable std::string value;
+    mutable uint32_t serial;
+
+    prop_info(const char* key, const char* value) : key(key), value(value), serial(0) {}
+};
+
+struct prop_info_cmp {
+    using is_transparent = void;
+    bool operator()(const prop_info& lhs, const prop_info& rhs) {
+        return lhs.key < rhs.key;
+    }
+    bool operator()(std::string_view lhs, const prop_info& rhs) {
+        return lhs < rhs.key;
+    }
+    bool operator()(const prop_info& lhs, std::string_view rhs) {
+        return lhs.key < rhs;
+    }
+};
+
+} // namespace
+
+static auto& g_properties_lock = *new std::mutex;
+static auto& g_properties = *new std::set<prop_info, prop_info_cmp>;
+
+static bool property_set(const char* key, const char* value) {
+    if (key == nullptr || *key == '\0') return false;
+    if (value == nullptr) value = "";
+    bool read_only = !strncmp(key, "ro.", 3);
+    if (!read_only && strlen(value) >= PROP_VALUE_MAX) return false;
+
+    std::lock_guard lock(g_properties_lock);
+    auto [it, success] = g_properties.emplace(key, value);
+    if (read_only) return success;
+    if (!success) {
+        it->value = value;
+        ++it->serial;
+    }
+    return true;
+}
+
+template <typename Func>
+static void property_get(const char* key, Func callback) {
+    std::lock_guard lock(g_properties_lock);
+    auto it = g_properties.find(key);
+    if (it != g_properties.end()) {
+        callback(*it);
+    }
+}
+
+// Redefine the __system_property_XXX functions here so we can perform
+// logging and access checks for all sysprops in native code.
+
+static void check_system_property_access(const char* key, bool write);
+
+extern "C" {
+
+int __system_property_set(const char* key, const char* value) {
+    check_system_property_access(key, true);
+    return property_set(key, value) ? 0 : -1;
+}
+
+int __system_property_get(const char* key, char* value) {
+    check_system_property_access(key, false);
+    *value = '\0';
+    property_get(key, [&](const prop_info& info) {
+        snprintf(value, PROP_VALUE_MAX, "%s", info.value.c_str());
+    });
+    return strlen(value);
+}
+
+const prop_info* __system_property_find(const char* key) {
+    check_system_property_access(key, false);
+    const prop_info* pi = nullptr;
+    property_get(key, [&](const prop_info& info) { pi = &info; });
+    return pi;
+}
+
+void __system_property_read_callback(const prop_info* pi,
+                                     void (*callback)(void*, const char*, const char*, uint32_t),
+                                     void* cookie) {
+    std::lock_guard lock(g_properties_lock);
+    callback(cookie, pi->key.c_str(), pi->value.c_str(), pi->serial);
+}
+
+} // extern "C"
+
+// ---- JNI ----
+
+static JavaVM* gVM = nullptr;
+static jclass gRunnerState = nullptr;
+static jmethodID gCheckSystemPropertyAccess;
+
+static void reloadNativeLibrary(JNIEnv* env, jclass, jstring javaPath) {
+    ScopedUtfChars path(env, javaPath);
+    // Force reload ourselves as global
+    dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL | RTLD_NOLOAD);
+}
+
+// Call back into Java code to check property access
+static void check_system_property_access(const char* key, bool write) {
+    if (gVM != nullptr && gRunnerState != nullptr) {
+        JNIEnv* env;
+        if (gVM->GetEnv((void**)&env, JNI_VERSION_1_4) >= 0) {
+            ALOGI("%s access to system property '%s'", write ? "Write" : "Read", key);
+            env->CallStaticVoidMethod(gRunnerState, gCheckSystemPropertyAccess,
+                                      env->NewStringUTF(key), write ? JNI_TRUE : JNI_FALSE);
+            return;
+        }
+    }
+    // Not on JVM thread, abort
+    LOG_ALWAYS_FATAL("Access to system property '%s' on non-JVM threads is not allowed.", key);
+}
+
+static jstring getSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
+    ScopedUtfChars key(env, javaKey);
+    jstring value = nullptr;
+    property_get(key.c_str(),
+                 [&](const prop_info& info) { value = env->NewStringUTF(info.value.c_str()); });
+    return value;
+}
+
+static jboolean setSystemProperty(JNIEnv* env, jclass, jstring javaKey, jstring javaValue) {
+    ScopedUtfChars key(env, javaKey);
+    ScopedUtfChars value(env, javaValue);
+    return property_set(key.c_str(), value.c_str()) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean removeSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
+    std::lock_guard lock(g_properties_lock);
+
+    if (javaKey == nullptr) {
+        g_properties.clear();
+        return JNI_TRUE;
+    } else {
+        ScopedUtfChars key(env, javaKey);
+        auto it = g_properties.find(key);
+        if (it != g_properties.end()) {
+            g_properties.erase(it);
+            return JNI_TRUE;
+        } else {
+            return JNI_FALSE;
+        }
+    }
+}
+
 static void maybeRedirectLog() {
     auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
     if (ravenwoodLogOut == NULL) {
@@ -42,9 +200,30 @@
     dup2(ttyFd, 2);
 }
 
+static const JNINativeMethod sMethods[] = {
+        {"reloadNativeLibrary", "(Ljava/lang/String;)V", (void*)reloadNativeLibrary},
+        {"getSystemProperty", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getSystemProperty},
+        {"setSystemProperty", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)setSystemProperty},
+        {"removeSystemProperty", "(Ljava/lang/String;)Z", (void*)removeSystemProperty},
+};
+
 extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
     ALOGI("%s: JNI_OnLoad", __FILE__);
 
     maybeRedirectLog();
+
+    JNIEnv* env = GetJNIEnvOrDie(vm);
+    gVM = vm;
+
+    // Fetch several references for future use
+    gRunnerState = FindGlobalClassOrDie(env, kRunnerState);
+    gCheckSystemPropertyAccess =
+            GetStaticMethodIDOrDie(env, gRunnerState, "checkSystemPropertyAccess",
+                                   "(Ljava/lang/String;Z)V");
+
+    // Expose raw property methods as JNI methods
+    jint res = jniRegisterNativeMethods(env, kRuntimeNative, sMethods, NELEM(sMethods));
+    if (res < 0) return -1;
+
     return JNI_VERSION_1_4;
 }
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index c1993f6..bab4c7e 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -174,6 +174,9 @@
     throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0));
 }
 
+static jint Linux_getpid(JNIEnv* env, jobject) {
+    return getpid();
+}
 
 static jint Linux_gettid(JNIEnv* env, jobject) {
     // gettid(2() was added in glibc 2.30 but Android uses an older version in prebuilt.
@@ -196,6 +199,7 @@
     { "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
     { "nOpen", "(Ljava/lang/String;II)I", (void*)Linux_open },
     { "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv },
+    { "getpid", "()I", (void*)Linux_getpid },
     { "gettid", "()I", (void*)Linux_gettid },
 };
 
diff --git a/ravenwood/runtime-jni/ravenwood_sysprop.cpp b/ravenwood/runtime-jni/ravenwood_sysprop.cpp
deleted file mode 100644
index aafc426..0000000
--- a/ravenwood/runtime-jni/ravenwood_sysprop.cpp
+++ /dev/null
@@ -1,187 +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.
- */
-
-#include <dlfcn.h>
-
-#include <set>
-
-#include "jni_helper.h"
-
-// Implement a rudimentary system properties data store
-
-#define PROP_VALUE_MAX 92
-
-namespace {
-
-struct prop_info {
-    std::string key;
-    mutable std::string value;
-    mutable uint32_t serial;
-
-    prop_info(const char* key, const char* value) : key(key), value(value), serial(0) {}
-};
-
-struct prop_info_cmp {
-    using is_transparent = void;
-    bool operator()(const prop_info& lhs, const prop_info& rhs) {
-        return lhs.key < rhs.key;
-    }
-    bool operator()(std::string_view lhs, const prop_info& rhs) {
-        return lhs < rhs.key;
-    }
-    bool operator()(const prop_info& lhs, std::string_view rhs) {
-        return lhs.key < rhs;
-    }
-};
-
-} // namespace
-
-static auto& g_properties_lock = *new std::mutex;
-static auto& g_properties = *new std::set<prop_info, prop_info_cmp>;
-
-static bool property_set(const char* key, const char* value) {
-    if (key == nullptr || *key == '\0') return false;
-    if (value == nullptr) value = "";
-    bool read_only = !strncmp(key, "ro.", 3);
-    if (!read_only && strlen(value) >= PROP_VALUE_MAX) return false;
-
-    std::lock_guard lock(g_properties_lock);
-    auto [it, success] = g_properties.emplace(key, value);
-    if (read_only) return success;
-    if (!success) {
-        it->value = value;
-        ++it->serial;
-    }
-    return true;
-}
-
-template <typename Func>
-static void property_get(const char* key, Func callback) {
-    std::lock_guard lock(g_properties_lock);
-    auto it = g_properties.find(key);
-    if (it != g_properties.end()) {
-        callback(*it);
-    }
-}
-
-// Redefine the __system_property_XXX functions here so we can perform
-// logging and access checks for all sysprops in native code.
-
-static void check_system_property_access(const char* key, bool write);
-
-extern "C" {
-
-int __system_property_set(const char* key, const char* value) {
-    check_system_property_access(key, true);
-    return property_set(key, value) ? 0 : -1;
-}
-
-int __system_property_get(const char* key, char* value) {
-    check_system_property_access(key, false);
-    *value = '\0';
-    property_get(key, [&](const prop_info& info) {
-        snprintf(value, PROP_VALUE_MAX, "%s", info.value.c_str());
-    });
-    return strlen(value);
-}
-
-const prop_info* __system_property_find(const char* key) {
-    check_system_property_access(key, false);
-    const prop_info* pi = nullptr;
-    property_get(key, [&](const prop_info& info) { pi = &info; });
-    return pi;
-}
-
-void __system_property_read_callback(const prop_info* pi,
-                                     void (*callback)(void*, const char*, const char*, uint32_t),
-                                     void* cookie) {
-    std::lock_guard lock(g_properties_lock);
-    callback(cookie, pi->key.c_str(), pi->value.c_str(), pi->serial);
-}
-
-} // extern "C"
-
-// ---- JNI ----
-
-static JavaVM* gVM = nullptr;
-static jclass gEnvController = nullptr;
-static jmethodID gCheckSystemPropertyAccess;
-
-static void reloadNativeLibrary(JNIEnv* env, jclass, jstring javaPath) {
-    ScopedUtfChars path(env, javaPath);
-    // Force reload ourselves as global
-    dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL | RTLD_NOLOAD);
-}
-
-// Call back into Java code to check property access
-static void check_system_property_access(const char* key, bool write) {
-    if (gVM != nullptr && gEnvController != nullptr) {
-        JNIEnv* env;
-        if (gVM->GetEnv((void**)&env, JNI_VERSION_1_4) >= 0) {
-            ALOGI("%s access to system property '%s'", write ? "Write" : "Read", key);
-            env->CallStaticVoidMethod(gEnvController, gCheckSystemPropertyAccess,
-                                      env->NewStringUTF(key), write ? JNI_TRUE : JNI_FALSE);
-            return;
-        }
-    }
-    // Not on JVM thread, abort
-    LOG_ALWAYS_FATAL("Access to system property '%s' on non-JVM threads is not allowed.", key);
-}
-
-static jstring getSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
-    ScopedUtfChars key(env, javaKey);
-    jstring value = nullptr;
-    property_get(key.c_str(),
-                 [&](const prop_info& info) { value = env->NewStringUTF(info.value.c_str()); });
-    return value;
-}
-
-static jboolean setSystemProperty(JNIEnv* env, jclass, jstring javaKey, jstring javaValue) {
-    ScopedUtfChars key(env, javaKey);
-    ScopedUtfChars value(env, javaValue);
-    return property_set(key.c_str(), value.c_str()) ? JNI_TRUE : JNI_FALSE;
-}
-
-static void clearSystemProperties(JNIEnv*, jclass) {
-    std::lock_guard lock(g_properties_lock);
-    g_properties.clear();
-}
-
-static const JNINativeMethod sMethods[] = {
-        {"reloadNativeLibrary", "(Ljava/lang/String;)V", (void*)reloadNativeLibrary},
-        {"getSystemProperty", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getSystemProperty},
-        {"setSystemProperty", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)setSystemProperty},
-        {"clearSystemProperties", "()V", (void*)clearSystemProperties},
-};
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
-    ALOGI("%s: JNI_OnLoad", __FILE__);
-
-    JNIEnv* env = GetJNIEnvOrDie(vm);
-    gVM = vm;
-
-    // Fetch several references for future use
-    gEnvController = FindGlobalClassOrDie(env, kRuntimeEnvController);
-    gCheckSystemPropertyAccess =
-            GetStaticMethodIDOrDie(env, gEnvController, "checkSystemPropertyAccess",
-                                   "(Ljava/lang/String;Z)V");
-
-    // Expose raw property methods as JNI methods
-    jint res = jniRegisterNativeMethods(env, kRuntimeNative, sMethods, NELEM(sMethods));
-    if (res < 0) return -1;
-
-    return JNI_VERSION_1_4;
-}
diff --git a/ravenwood/scripts/extract-last-soong-commands.py b/ravenwood/scripts/extract-last-soong-commands.py
new file mode 100755
index 0000000..bdc1de0
--- /dev/null
+++ b/ravenwood/scripts/extract-last-soong-commands.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env 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 extracts all the commands executed in the last soong run,
+# and write them into a script file, and print the filename.
+#
+# All the commands are commented out. Uncomment what you want to execute as
+# needed before running it.
+
+import datetime
+import gzip
+import os
+import re
+import shlex
+import sys
+
+re_command = re.compile(r''' ^\[.*?\]  \s*  (.*) ''', re.X)
+
+HEADER = r'''#!/bin/bash
+
+set -e # Stop on a failed command
+
+cd "${ANDROID_BUILD_TOP:?}"
+
+'''
+
+OUT_SCRIPT_DIR = "/tmp/"
+OUT_SCRIPT_FORMAT = "soong-rerun-%Y-%m-%d_%H-%M-%S.sh"
+
+def main(args):
+    log = os.environ["ANDROID_BUILD_TOP"] + "/out/verbose.log.gz"
+    outdir = "/tmp/"
+    outfile = outdir + datetime.datetime.now().strftime(OUT_SCRIPT_FORMAT)
+
+    with open(outfile, "w") as out:
+        out.write(HEADER)
+
+        with gzip.open(log) as f:
+            for line in f:
+                s = line.decode("utf-8")
+
+                if s.startswith("verbose"):
+                    continue
+                if re.match('^\[.*bootstrap blueprint', s):
+                    continue
+
+                s = s.rstrip()
+
+                m = re_command.search(s)
+                if m:
+                    command = m.groups()[0]
+
+                    out.write('#========\n')
+
+                    # Show the full command line before executing it.
+                    out.write('#echo ' + shlex.quote(command) + '\n')
+                    out.write('\n')
+
+                    # Execute the command.
+                    out.write('#' + command + '\n')
+
+                    out.write('\n')
+
+                    continue
+
+                if s.startswith("FAILED:"):
+                    break
+
+    os.chmod(outfile, 0o755)
+    print(outfile)
+
+    return 0
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:]))
diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md
index c29fb7f..6d82a74 100644
--- a/ravenwood/test-authors.md
+++ b/ravenwood/test-authors.md
@@ -106,45 +106,6 @@
 
 > **Note:** There's a known bug #308854804 where `TEST_MAPPING` is not being applied, so we're currently planning to run all Ravenwood tests unconditionally in presubmit for changes to `frameworks/base/` and `cts/` until there is a better path forward.
 
-## Strategies for feature flags
-
-Ravenwood supports writing tests against logic that uses feature flags through the existing `SetFlagsRule` infrastructure maintained by the feature flagging team:
-
-```
-import android.platform.test.flag.junit.SetFlagsRule;
-
-@RunWith(AndroidJUnit4.class)
-public class MyCodeTest {
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(SetFlagsRule.DefaultInitValueType.NULL_DEFAULT);
-
-    @Test
-    public void testEnabled() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_MY_FLAG);
-        // verify test logic that depends on flag being enabled
-    }
-```
-
-This naturally composes together well with any `RavenwoodRule` that your test may need.
-
-While `SetFlagsRule` is generally a best-practice (as it can explicitly confirm behaviors for both "on" and "off" states), you may need to write tests that use `CheckFlagsRule` (such as when writing CTS).  Ravenwood currently supports `CheckFlagsRule` by offering "all-on" and "all-off" behaviors:
-
-```
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.platform.test.flag.junit.RavenwoodFlagsValueProvider;
-import android.platform.test.ravenwood.RavenwoodRule;
-
-@RunWith(AndroidJUnit4.class)
-public class MyCodeTest {
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood()
-            ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
-            : DeviceFlagsValueProvider.createCheckFlagsRule();
-```
-
-Ravenwood currently doesn't have knowledge of the "default" value of any flags, so using `createAllOnCheckFlagsRule()` is recommended to verify the widest possible set of behaviors.  The example code above falls back to using default values from `DeviceFlagsValueProvider` when not running on Ravenwood.
-
 ## Strategies for migration/bivalent tests
 
 Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can be dual-compiled to run on both a real Android device and under a Ravenwood environment.
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
deleted file mode 100644
index c25d2b4..0000000
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
+++ /dev/null
@@ -1,57 +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.ravenwoodtest.bivalenttest;
-
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Assume;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-
-/**
- * Make sure having multiple RavenwoodRule's is detected.
- * (But only when running on ravenwod. Otherwise it'll be ignored.)
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodMultipleRuleTest {
-
-    @Rule(order = Integer.MIN_VALUE)
-    public final ExpectedException mExpectedException = ExpectedException.none();
-
-    @Rule
-    public final RavenwoodRule mRavenwood1 = new RavenwoodRule();
-
-    @Rule
-    public final RavenwoodRule mRavenwood2 = new RavenwoodRule();
-
-    public RavenwoodMultipleRuleTest() {
-        // We can't call it within the test method because the exception happens before
-        // calling the method, so set it up here.
-        if (RavenwoodConfig.isOnRavenwood()) {
-            mExpectedException.expectMessage("Multiple nesting RavenwoodRule");
-        }
-    }
-
-    @Test
-    public void testMultipleRulesNotAllowed() {
-        Assume.assumeTrue(RavenwoodConfig.isOnRavenwood());
-    }
-}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodLogLevelTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodLogLevelTest.java
new file mode 100644
index 0000000..74c1f3f
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodLogLevelTest.java
@@ -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.ravenwoodtest.coretest;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
+import org.junit.Test;
+
+public class RavenwoodLogLevelTest {
+    /**
+     * Assert that the `priority` is loggable, but one level below is not.
+     */
+    private void assertBarelyLoggable(String tag, int priority) {
+        assertEquals(true, Log.isLoggable(tag, priority));
+        assertEquals(false, Log.isLoggable(tag, priority - 1));
+    }
+
+    @Test
+    public void testDefaultLogTags() {
+        RavenwoodRule.setAndroidLogTags(null);
+
+        // Info should always be loggable.
+        assertEquals(true, Log.isLoggable("TAG1", Log.INFO));
+        assertEquals(true, Log.isLoggable("TAG2", Log.INFO));
+
+        assertEquals(true, Log.isLoggable("TAG1", Log.DEBUG));
+        assertEquals(true, Log.isLoggable("TAG2", Log.VERBOSE));
+    }
+
+    @Test
+    public void testAllVerbose() {
+        RavenwoodRule.setAndroidLogTags("*:V");
+
+        assertEquals(true, Log.isLoggable("TAG1", Log.INFO));
+        assertEquals(true, Log.isLoggable("TAG2", Log.INFO));
+
+        assertEquals(true, Log.isLoggable("TAG1", Log.DEBUG));
+        assertEquals(true, Log.isLoggable("TAG2", Log.VERBOSE));
+    }
+
+    @Test
+    public void testAllSilent() {
+        RavenwoodRule.setAndroidLogTags("*:S");
+
+        assertEquals(false, Log.isLoggable("TAG1", Log.ASSERT));
+        assertEquals(false, Log.isLoggable("TAG2", Log.ASSERT));
+    }
+
+    @Test
+    public void testComplex() {
+        RavenwoodRule.setAndroidLogTags("TAG1:W TAG2:D *:I");
+
+        assertBarelyLoggable("TAG1", Log.WARN);
+        assertBarelyLoggable("TAG2", Log.DEBUG);
+        assertBarelyLoggable("TAG3", Log.INFO);
+    }
+
+    @Test
+    public void testAllVerbose_setLogLevel() {
+        RavenwoodRule.setAndroidLogTags(null);
+        RavenwoodRule.setLogLevel(null, Log.VERBOSE);
+
+        assertEquals(true, Log.isLoggable("TAG1", Log.INFO));
+        assertEquals(true, Log.isLoggable("TAG2", Log.INFO));
+
+        assertEquals(true, Log.isLoggable("TAG1", Log.DEBUG));
+        assertEquals(true, Log.isLoggable("TAG2", Log.VERBOSE));
+    }
+
+    @Test
+    public void testAllSilent_setLogLevel() {
+        RavenwoodRule.setAndroidLogTags(null);
+        RavenwoodRule.setLogLevel(null, Log.ASSERT + 1);
+
+        assertEquals(false, Log.isLoggable("TAG1", Log.ASSERT));
+        assertEquals(false, Log.isLoggable("TAG2", Log.ASSERT));
+    }
+
+    @Test
+    public void testComplex_setLogLevel() {
+        RavenwoodRule.setAndroidLogTags(null);
+        RavenwoodRule.setLogLevel(null, Log.INFO);
+        RavenwoodRule.setLogLevel("TAG1", Log.WARN);
+        RavenwoodRule.setLogLevel("TAG2", Log.DEBUG);
+
+        assertBarelyLoggable("TAG1", Log.WARN);
+        assertBarelyLoggable("TAG2", Log.DEBUG);
+        assertBarelyLoggable("TAG3", Log.INFO);
+    }
+}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRuleValidationTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRuleValidationTest.java
new file mode 100644
index 0000000..f9e73db
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRuleValidationTest.java
@@ -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.ravenwoodtest.runnercallbacktests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemProperties;
+import android.platform.test.annotations.NoRavenizer;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for RavenwoodRule.
+ */
+@NoRavenizer // This class shouldn't be executed with RavenwoodAwareTestRunner.
+public class RavenwoodRuleValidationTest extends RavenwoodRunnerTestBase {
+
+    public static class RuleInBaseClass {
+        static String PROPERTY_KEY = "debug.ravenwood.prop.in.base";
+        static String PROPERTY_VAL = "ravenwood";
+        @Rule
+        public final RavenwoodRule mRavenwood1 = new RavenwoodRule.Builder()
+                .setSystemPropertyImmutable(PROPERTY_KEY, PROPERTY_VAL).build();
+    }
+
+    /**
+     * Make sure that RavenwoodRule in a base class takes effect.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest
+    testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest)
+    testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest)
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest
+    testSuiteFinished: classes
+    testRunFinished: 1,0,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class RuleInBaseClassSuccessTest extends RuleInBaseClass {
+
+        @Test
+        public void testRuleInBaseClass() {
+            assertThat(SystemProperties.get(PROPERTY_KEY)).isEqualTo(PROPERTY_VAL);
+        }
+    }
+
+    /**
+     * Same as {@link RuleInBaseClass}, but the type of the rule field is not {@link RavenwoodRule}.
+     */
+    public abstract static class RuleWithDifferentTypeInBaseClass {
+        static String PROPERTY_KEY = "debug.ravenwood.prop.in.base.different.type";
+        static String PROPERTY_VAL = "ravenwood";
+        @Rule
+        public final TestRule mRavenwood1 = new RavenwoodRule.Builder()
+                .setSystemPropertyImmutable(PROPERTY_KEY, PROPERTY_VAL).build();
+    }
+
+    /**
+     * Make sure that RavenwoodRule in a base class takes effect, even if the field type is not
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
+    testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
+    testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
+    testSuiteFinished: classes
+    testRunFinished: 1,0,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class RuleWithDifferentTypeInBaseClassSuccessTest extends RuleWithDifferentTypeInBaseClass {
+
+        @Test
+        public void testRuleInBaseClass() {
+            assertThat(SystemProperties.get(PROPERTY_KEY)).isEqualTo(PROPERTY_VAL);
+        }
+    }
+}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
deleted file mode 100644
index f94b98b..0000000
--- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
+++ /dev/null
@@ -1,484 +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.ravenwoodtest.runnercallbacktests;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.NoRavenizer;
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-
-
-/**
- * Test for @Config field extraction and validation.
- *
- * TODO(b/377765941) Most of the tests here will be obsolete and deleted with b/377765941, but
- * some of the tests may need to be re-implemented one way or another. (e.g. the package name
- * test.) Until that happens, we'll keep all tests here but add an {@code @Ignore} instead.
- */
-@NoRavenizer // This class shouldn't be executed with RavenwoodAwareTestRunner.
-public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase {
-    public abstract static class ConfigInBaseClass {
-        static String PACKAGE_NAME = "com.ConfigInBaseClass";
-
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
-                .setPackageName(PACKAGE_NAME).build();
-    }
-
-    /**
-     * Make sure a config in the base class is detected.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest
-    testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest)
-    testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest)
-    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest
-    testSuiteFinished: classes
-    testRunFinished: 1,0,0,0
-    """)
-    // CHECKSTYLE:ON
-    @Ignore // Package name is no longer set via config.
-    public static class ConfigInBaseClassTest extends ConfigInBaseClass {
-        @Test
-        public void test() {
-            assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
-                    .isEqualTo(PACKAGE_NAME);
-        }
-    }
-
-    /**
-     * Make sure a config in the base class is detected.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest
-    testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest)
-    testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest)
-    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest
-    testSuiteFinished: classes
-    testRunFinished: 1,0,0,0
-    """)
-    // CHECKSTYLE:ON
-    @Ignore // Package name is no longer set via config.
-    public static class ConfigOverridingTest extends ConfigInBaseClass {
-        static String PACKAGE_NAME_OVERRIDE = "com.ConfigOverridingTest";
-
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
-                .setPackageName(PACKAGE_NAME_OVERRIDE).build();
-
-        @Test
-        public void test() {
-            assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
-                    .isEqualTo(PACKAGE_NAME_OVERRIDE);
-        }
-    }
-
-    /**
-     * Test to make sure that if a test has a config error, the failure would be reported from
-     * each test method.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
-    testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest.sConfig expected to be public static
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class ErrorMustBeReportedFromEachTest {
-        @RavenwoodConfig.Config
-        private static RavenwoodConfig sConfig = // Invalid because it's private.
-                new RavenwoodConfig.Builder().build();
-
-        @Test
-        public void testMethod1() {
-        }
-
-        @Test
-        public void testMethod2() {
-        }
-
-        @Test
-        public void testMethod3() {
-        }
-    }
-
-    /**
-     * Invalid because there are two @Config's.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
-    testFailure: Class com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.DuplicateConfigTest has multiple fields with @RavenwoodConfig.Config
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class DuplicateConfigTest {
-
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig1 =
-                new RavenwoodConfig.Builder().build();
-
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig2 =
-                new RavenwoodConfig.Builder().build();
-
-        @Test
-        public void testConfig() {
-        }
-    }
-
-    /**
-     * @Config's must be static.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest)
-    testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest.sConfig expected to be public static
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class NonStaticConfigTest {
-
-        @RavenwoodConfig.Config
-        public RavenwoodConfig sConfig =
-                new RavenwoodConfig.Builder().build();
-
-        @Test
-        public void testConfig() {
-        }
-    }
-
-    /**
-     * @Config's must be public.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest)
-    testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest.sConfig expected to be public static
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class NonPublicConfigTest {
-
-        @RavenwoodConfig.Config
-        RavenwoodConfig sConfig =
-                new RavenwoodConfig.Builder().build();
-
-        @Test
-        public void testConfig() {
-        }
-    }
-
-    /**
-     * @Config's must be of type RavenwoodConfig.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
-    testFailure: Field com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.WrongTypeConfigTest.sConfig has @RavenwoodConfig.Config but type is not RavenwoodConfig
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class WrongTypeConfigTest {
-
-        @RavenwoodConfig.Config
-        public static Object sConfig =
-                new RavenwoodConfig.Builder().build();
-
-        @Test
-        public void testConfig() {
-        }
-
-    }
-
-    /**
-     * @Rule must be of type RavenwoodRule.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest
-    testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest)
-    testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
-    testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest)
-    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class WrongTypeRuleTest {
-
-        @Rule
-        public TestRule mRule = new RavenwoodRule.Builder().build();
-
-        @Test
-        public void testConfig() {
-        }
-
-    }
-
-    /**
-     * Config can't be used with a (instance) Rule.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
-    testFailure: RavenwoodConfig and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfig.
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class WithInstanceRuleTest {
-
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig =
-                new RavenwoodConfig.Builder().build();
-
-        @Rule
-        public RavenwoodRule mRule = new RavenwoodRule.Builder().build();
-
-        @Test
-        public void testConfig() {
-        }
-    }
-
-    /**
-     * Config can't be used with a (static) Rule.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithStaticRuleTest)
-    testFailure: Failed to instantiate class androidx.test.ext.junit.runners.AndroidJUnit4
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithStaticRuleTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class WithStaticRuleTest {
-
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig =
-                new RavenwoodConfig.Builder().build();
-
-        @Rule
-        public static RavenwoodRule sRule = new RavenwoodRule.Builder().build();
-
-        @Test
-        public void testConfig() {
-        }
-    }
-
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest
-    testStarted: testMultipleRulesNotAllowed(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest)
-    testFailure: Multiple nesting RavenwoodRule's are detected in the same class, which is not supported.
-    testFinished: testMultipleRulesNotAllowed(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest)
-    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class DuplicateRulesTest {
-
-        @Rule
-        public final RavenwoodRule mRavenwood1 = new RavenwoodRule();
-
-        @Rule
-        public final RavenwoodRule mRavenwood2 = new RavenwoodRule();
-
-        @Test
-        public void testMultipleRulesNotAllowed() {
-        }
-    }
-
-    public static class RuleInBaseClass {
-        static String PACKAGE_NAME = "com.RuleInBaseClass";
-        @Rule
-        public final RavenwoodRule mRavenwood1 = new RavenwoodRule.Builder()
-                .setPackageName(PACKAGE_NAME).build();
-    }
-
-    /**
-     * Make sure that RavenwoodRule in a base class takes effect.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest
-    testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest)
-    testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest)
-    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest
-    testSuiteFinished: classes
-    testRunFinished: 1,0,0,0
-    """)
-    // CHECKSTYLE:ON
-    @Ignore // Package name is no longer set via config.
-    public static class RuleInBaseClassSuccessTest extends RuleInBaseClass {
-
-        @Test
-        public void testRuleInBaseClass() {
-            assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
-                    .isEqualTo(PACKAGE_NAME);
-        }
-    }
-
-    /**
-     * Make sure that having a config and a rule in a base class should fail.
-     * RavenwoodRule.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
-    testFailure: RavenwoodConfig and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfig.
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class ConfigWithRuleInBaseClassTest extends RuleInBaseClass {
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder().build();
-
-        @Test
-        public void test() {
-        }
-    }
-
-    /**
-     * Same as {@link RuleInBaseClass}, but the type of the rule field is not {@link RavenwoodRule}.
-     */
-    public abstract static class RuleWithDifferentTypeInBaseClass {
-        static String PACKAGE_NAME = "com.RuleWithDifferentTypeInBaseClass";
-        @Rule
-        public final TestRule mRavenwood1 = new RavenwoodRule.Builder()
-                .setPackageName(PACKAGE_NAME).build();
-    }
-
-    /**
-     * Make sure that RavenwoodRule in a base class takes effect, even if the field type is not
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
-    testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
-    testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
-    testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
-    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    @Ignore // Package name is no longer set via config.
-    public static class RuleWithDifferentTypeInBaseClassSuccessTest extends RuleWithDifferentTypeInBaseClass {
-
-        @Test
-        public void testRuleInBaseClass() {
-            assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
-                    .isEqualTo(PACKAGE_NAME);
-        }
-    }
-
-    /**
-     * Make sure that having a config and a rule in a base class should fail, even if the field type is not
-     * RavenwoodRule.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest
-    testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest)
-    testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
-    testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest)
-    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class ConfigWithRuleWithDifferentTypeInBaseClassTest extends RuleWithDifferentTypeInBaseClass {
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder().build();
-
-        @Test
-        public void test() {
-        }
-    }
-}
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
index 8e04b69..271c27f 100644
--- a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
@@ -15,7 +15,6 @@
  */
 package com.android.ravenwoodtest.runtimetest;
 
-import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
 import static android.os.Process.FIRST_APPLICATION_UID;
 
 import static org.junit.Assert.assertEquals;
@@ -23,7 +22,6 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Process;
-import android.platform.test.ravenwood.RavenwoodConfig;
 import android.system.Os;
 
 import com.android.ravenwood.RavenwoodRuntimeState;
@@ -34,13 +32,6 @@
 
 public class IdentityTest {
 
-    @RavenwoodConfig.Config
-    public static final RavenwoodConfig sConfig =
-            new RavenwoodConfig.Builder()
-                    .setTargetSdkLevel(UPSIDE_DOWN_CAKE)
-                    .setProcessApp()
-                    .build();
-
     @Test
     public void testUid() {
         assertEquals(FIRST_APPLICATION_UID, RavenwoodRuntimeState.sUid);
@@ -60,7 +51,7 @@
     @Test
     public void testTargetSdkLevel() {
         assertEquals(Build.VERSION_CODES.CUR_DEVELOPMENT, RavenwoodRuntimeState.CUR_DEVELOPMENT);
-        assertEquals(UPSIDE_DOWN_CAKE, RavenwoodRuntimeState.sTargetSdkLevel);
-        assertEquals(UPSIDE_DOWN_CAKE, VMRuntime.getRuntime().getTargetSdkVersion());
+        assertEquals(RavenwoodRuntimeState.sTargetSdkLevel,
+                VMRuntime.getRuntime().getTargetSdkVersion());
     }
 }
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/SystemPropertyTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/SystemPropertyTest.java
new file mode 100644
index 0000000..70bf204
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/SystemPropertyTest.java
@@ -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.ravenwoodtest.runtimetest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.SystemProperties;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runners.model.Statement;
+
+public class SystemPropertyTest {
+
+    private static final String PROP_KEY_1 = "debug.ravenwood.prop1";
+    private static final String PROP_VAL_1 = "ravenwood.1";
+    private static final String PROP_KEY_2 = "debug.ravenwood.prop2";
+    private static final String PROP_VAL_2 = "ravenwood.2";
+    private static final String PROP_KEY_3 = "debug.ravenwood.prop3";
+    private static final String PROP_VAL_3 = "ravenwood.3";
+    private static final String PROP_VAL_4 = "ravenwood.4";
+
+    @ClassRule(order = 0)
+    public static TestRule mCheckClassRule = (base, description) -> new Statement() {
+        @Override
+        public void evaluate() throws Throwable {
+            assertTrue(SystemProperties.get(PROP_KEY_1).isEmpty());
+            assertTrue(SystemProperties.get(PROP_KEY_3).isEmpty());
+            try {
+                base.evaluate();
+            } finally {
+                assertTrue(SystemProperties.get(PROP_KEY_1).isEmpty());
+                assertTrue(SystemProperties.get(PROP_KEY_3).isEmpty());
+            }
+        }
+    };
+
+    @ClassRule(order = 1)
+    public static RavenwoodRule mClassRule = new RavenwoodRule.Builder()
+            .setSystemPropertyImmutable(PROP_KEY_1, PROP_VAL_1)
+            .setSystemPropertyImmutable(PROP_KEY_3, PROP_VAL_4)
+            .build();
+
+    @Rule(order = 0)
+    public TestRule mCheckRule = (base, description) -> new Statement() {
+        @Override
+        public void evaluate() throws Throwable {
+            assertTrue(SystemProperties.get(PROP_KEY_2).isEmpty());
+            assertEquals(SystemProperties.get(PROP_KEY_3), PROP_VAL_4);
+            try {
+                base.evaluate();
+            } finally {
+                assertTrue(SystemProperties.get(PROP_KEY_2).isEmpty());
+                assertEquals(SystemProperties.get(PROP_KEY_3), PROP_VAL_4);
+            }
+        }
+    };
+
+    @Rule(order = 1)
+    public RavenwoodRule mRule = new RavenwoodRule.Builder()
+            .setSystemPropertyImmutable(PROP_KEY_2, PROP_VAL_2)
+            .setSystemPropertyImmutable(PROP_KEY_3, PROP_VAL_3)
+            .build();
+
+    @Test
+    public void testRavenwoodRuleSetProperty() {
+        assertEquals(SystemProperties.get(PROP_KEY_1), PROP_VAL_1);
+        assertEquals(SystemProperties.get(PROP_KEY_2), PROP_VAL_2);
+        assertEquals(SystemProperties.get(PROP_KEY_3), PROP_VAL_3);
+    }
+}
diff --git a/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java b/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
index 4aae1e1..e83a247 100644
--- a/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
+++ b/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
@@ -24,8 +24,6 @@
 import android.content.Context;
 import android.hardware.SerialManager;
 import android.hardware.SerialManagerInternal;
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodConfig.Config;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -42,12 +40,6 @@
 public class RavenwoodServicesTest {
     private static final String TEST_VIRTUAL_PORT = "virtual:example";
 
-    @Config
-    public static final RavenwoodConfig sRavenwood = new RavenwoodConfig.Builder()
-            .setProcessSystem()
-            .setServicesRequired(SerialManager.class)
-            .build();
-
     private Context mContext;
 
     @Before
diff --git a/ravenwood/texts/ravenwood-common-policies.txt b/ravenwood/texts/ravenwood-common-policies.txt
index 08f53977..83c3151 100644
--- a/ravenwood/texts/ravenwood-common-policies.txt
+++ b/ravenwood/texts/ravenwood-common-policies.txt
@@ -14,7 +14,7 @@
 
 # Support APIs not available in standard JRE
 class java.io.FileDescriptor keep
-    method getInt$ ()I @com.android.ravenwood.RavenwoodJdkPatch.getInt$
-    method setInt$ (I)V @com.android.ravenwood.RavenwoodJdkPatch.setInt$
+    method getInt$ @com.android.ravenwood.RavenwoodJdkPatch.getInt$
+    method setInt$ @com.android.ravenwood.RavenwoodJdkPatch.setInt$
 class java.util.LinkedHashMap keep
-    method eldest ()Ljava/util/Map$Entry; @com.android.ravenwood.RavenwoodJdkPatch.eldest
+    method eldest @com.android.ravenwood.RavenwoodJdkPatch.eldest
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
index 59fa464..fc885d6 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
@@ -66,6 +66,9 @@
         methodName: String,
         descriptor: String
     ) {
+        if (descriptor == "*") {
+            return
+        }
         if (classes.findMethod(className, methodName, descriptor) == null) {
             log.w("Unknown method $className.$methodName$descriptor")
         }
@@ -92,7 +95,8 @@
             descriptor: String,
             ): FilterPolicyWithReason {
         return mPolicies[getMethodKey(className, methodName, descriptor)]
-                ?: super.getPolicyForMethod(className, methodName, descriptor)
+            ?: mPolicies[getMethodKey(className, methodName, "*")]
+            ?: super.getPolicyForMethod(className, methodName, descriptor)
     }
 
     fun setPolicyForMethod(
@@ -107,7 +111,8 @@
 
     override fun getRenameTo(className: String, methodName: String, descriptor: String): String? {
         return mRenames[getMethodKey(className, methodName, descriptor)]
-                ?: super.getRenameTo(className, methodName, descriptor)
+            ?: mRenames[getMethodKey(className, methodName, "*")]
+            ?: super.getRenameTo(className, methodName, descriptor)
     }
 
     fun setRenameTo(className: String, methodName: String, descriptor: String, toName: String) {
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index caf80eb..7462a8c 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -303,12 +303,21 @@
     }
 
     private fun parseMethod(fields: Array<String>) {
-        if (fields.size < 4) {
-            throw ParseException("Method ('m') expects 3 fields.")
+        if (fields.size < 3 || fields.size > 4) {
+            throw ParseException("Method ('m') expects 3 or 4 fields.")
         }
         val name = fields[1]
-        val signature = fields[2]
-        val policy = parsePolicy(fields[3])
+        val signature: String
+        val policyStr: String
+        if (fields.size <= 3) {
+            signature = "*"
+            policyStr = fields[2]
+        } else {
+            signature = fields[2]
+            policyStr = fields[3]
+        }
+
+        val policy = parsePolicy(policyStr)
 
         if (!policy.isUsableWithMethods) {
             throw ParseException("Method can't have policy '$policy'")
@@ -321,7 +330,7 @@
             policy.withReason(FILTER_REASON)
         )
         if (policy == FilterPolicy.Substitute) {
-            val fromName = fields[3].substring(1)
+            val fromName = policyStr.substring(1)
 
             if (fromName == name) {
                 throw ParseException(
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
index d45f414..a3f934c 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
@@ -48,10 +48,11 @@
         // Maybe use 'Tri' if we end up having too many replacements.
         spec.forEach {
             if (className == it.fromClass &&
-                methodName == it.fromMethod &&
-                descriptor == it.fromDescriptor
+                methodName == it.fromMethod
                 ) {
-                return MethodReplaceTarget(it.toClass, it.toMethod)
+                if (it.fromDescriptor == "*" || descriptor == it.fromDescriptor) {
+                    return MethodReplaceTarget(it.toClass, it.toMethod)
+                }
             }
         }
         return null
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/Android.bp b/ravenwood/tools/hoststubgen/test-tiny-framework/Android.bp
index 1570549..483c4be 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/Android.bp
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/Android.bp
@@ -156,6 +156,7 @@
     ],
     data: [
         "golden-output/*.txt",
+        "golden-output.RELEASE_TARGET_JAVA_21/*.txt",
     ],
     java_data: [
         "hoststubgen-test-tiny-framework-orig-dump",
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
new file mode 100644
index 0000000..5e5ca62
--- /dev/null
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -0,0 +1,3697 @@
+## Class: android/hosttest/annotation/HostSideTestClassLoadHook.class
+  Compiled from "HostSideTestClassLoadHook.java"
+public interface android.hosttest.annotation.HostSideTestClassLoadHook extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestClassLoadHook
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  public abstract java.lang.String value();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+}
+SourceFile: "HostSideTestClassLoadHook.java"
+RuntimeVisibleAnnotations:
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestIgnore.class
+  Compiled from "HostSideTestIgnore.java"
+public interface android.hosttest.annotation.HostSideTestIgnore extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestIgnore
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestIgnore.java"
+RuntimeVisibleAnnotations:
+  x: #x(#x=[e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestKeep.class
+  Compiled from "HostSideTestKeep.java"
+public interface android.hosttest.annotation.HostSideTestKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestKeep.java"
+RuntimeVisibleAnnotations:
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestRedirect.class
+  Compiled from "HostSideTestRedirect.java"
+public interface android.hosttest.annotation.HostSideTestRedirect extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestRedirect
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestRedirect.java"
+RuntimeVisibleAnnotations:
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestRedirectionClass.class
+  Compiled from "HostSideTestRedirectionClass.java"
+public interface android.hosttest.annotation.HostSideTestRedirectionClass extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestRedirectionClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  public abstract java.lang.String value();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+}
+SourceFile: "HostSideTestRedirectionClass.java"
+RuntimeVisibleAnnotations:
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestRemove.class
+  Compiled from "HostSideTestRemove.java"
+public interface android.hosttest.annotation.HostSideTestRemove extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestRemove
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestRemove.java"
+RuntimeVisibleAnnotations:
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestStaticInitializerKeep.class
+  Compiled from "HostSideTestStaticInitializerKeep.java"
+public interface android.hosttest.annotation.HostSideTestStaticInitializerKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestStaticInitializerKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestStaticInitializerKeep.java"
+RuntimeVisibleAnnotations:
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestSubstitute.class
+  Compiled from "HostSideTestSubstitute.java"
+public interface android.hosttest.annotation.HostSideTestSubstitute extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestSubstitute
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  public abstract java.lang.String suffix();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+}
+SourceFile: "HostSideTestSubstitute.java"
+RuntimeVisibleAnnotations:
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestThrow.class
+  Compiled from "HostSideTestThrow.java"
+public interface android.hosttest.annotation.HostSideTestThrow extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestThrow
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestThrow.java"
+RuntimeVisibleAnnotations:
+  x: #x(#x=[e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestWholeClassKeep.class
+  Compiled from "HostSideTestWholeClassKeep.java"
+public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestWholeClassKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestWholeClassKeep.java"
+RuntimeVisibleAnnotations:
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/tests/HostSideTestSuppress.class
+  Compiled from "HostSideTestSuppress.java"
+public interface android.hosttest.annotation.tests.HostSideTestSuppress extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/tests/HostSideTestSuppress
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestSuppress.java"
+RuntimeVisibleAnnotations:
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD]
+    )
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
+    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/IPretendingAidl$Stub$Proxy;
+
+  public static int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_2
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0     a   I
+}
+SourceFile: "IPretendingAidl.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+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/IPretendingAidl$Stub.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
+    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/IPretendingAidl$Stub;
+
+  public static int addOne(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0     a   I
+}
+SourceFile: "IPretendingAidl.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+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/IPretendingAidl.class
+  Compiled from "IPretendingAidl.java"
+public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 3
+}
+SourceFile: "IPretendingAidl.java"
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
+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: 65
+  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: 65
+  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/TinyFrameworkAnnotations.class
+  Compiled from "TinyFrameworkAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 9, attributes: 2
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public int remove;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_1
+         x: putfield      #x                  // Field keep:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+            0       4     1 value   I
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public void toBeRemoved(java.lang.String);
+    descriptor: (Ljava/lang/String;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+            0       8     1   foo   Ljava/lang/String;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRemove
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String not supported on host side
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+            0      10     1 value   I
+    RuntimeInvisibleAnnotations:
+      x: #x(#x=s#x)
+        android.hosttest.annotation.HostSideTestSubstitute(
+          suffix="_host"
+        )
+
+  public int addTwo_host(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_2
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+            0       4     1 value   I
+
+  public static native int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeInvisibleAnnotations:
+      x: #x(#x=s#x)
+        android.hosttest.annotation.HostSideTestSubstitute(
+          suffix="_host"
+        )
+
+  private static int nativeAddThree_host(int);
+    descriptor: (I)I
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_3
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0 value   I
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: ldc           #x                 // String This value shouldn\'t be seen on the host side.
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+
+  public int toBeIgnored();
+    descriptor: ()I
+    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 not supported on host side
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestIgnore
+}
+SourceFile: "TinyFrameworkAnnotations.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
+  Compiled from "TinyFrameworkClassLoadHook.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 3, attributes: 2
+  public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
+    descriptor: Ljava/util/Set;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/Set<Ljava/lang/Class<*>;>;
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook();
+    descriptor: ()V
+    flags: (0x0002) ACC_PRIVATE
+    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/TinyFrameworkClassLoadHook;
+
+  public static void onClassLoaded(java.lang.Class<?>);
+    descriptor: (Ljava/lang/Class;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: getstatic     #x                  // Field sLoadedClasses:Ljava/util/Set;
+         x: aload_0
+         x: invokeinterface #x,  2           // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z
+         x: pop
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0 clazz   Ljava/lang/Class;
+      LocalVariableTypeTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0 clazz   Ljava/lang/Class<*>;
+    Signature: #x                          // (Ljava/lang/Class<*>;)V
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: new           #x                 // class java/util/HashSet
+         x: dup
+         x: invokespecial #x                 // Method java/util/HashSet."<init>":()V
+         x: putstatic     #x                  // Field sLoadedClasses:Ljava/util/Set;
+        x: return
+      LineNumberTable:
+}
+SourceFile: "TinyFrameworkClassLoadHook.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.class
+  Compiled from "TinyFrameworkClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 6, attributes: 2
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int remove;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRemove
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_1
+         x: putfield      #x                  // Field keep:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+            0       4     1 value   I
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String not supported on host side
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+            0      10     1 value   I
+    RuntimeInvisibleAnnotations:
+      x: #x(#x=s#x)
+        android.hosttest.annotation.HostSideTestSubstitute(
+          suffix="_host"
+        )
+
+  public int addTwo_host(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_2
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+            0       4     1 value   I
+
+  public void toBeRemoved(java.lang.String);
+    descriptor: (Ljava/lang/String;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+            0       8     1   foo   Ljava/lang/String;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRemove
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: ldc           #x                 // String This value shouldn\'t be seen on the host side.
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+}
+SourceFile: "TinyFrameworkClassWideAnnotations.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.class
+  Compiled from "TinyFrameworkClassWithInitializerDefault.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 2, attributes: 2
+  public static boolean sInitialized;
+    descriptor: Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static java.lang.Object sObject;
+    descriptor: Ljava/lang/Object;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault();
+    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/TinyFrameworkClassWithInitializerDefault;
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: iconst_1
+         x: putstatic     #x                  // Field sInitialized:Z
+         x: new           #x                  // class java/lang/Object
+         x: dup
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+        x: putstatic     #x                 // Field sObject:Ljava/lang/Object;
+        x: return
+      LineNumberTable:
+}
+SourceFile: "TinyFrameworkClassWithInitializerDefault.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.class
+  Compiled from "TinyFrameworkClassWithInitializerStub.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerStub
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 2, attributes: 2
+  public static boolean sInitialized;
+    descriptor: Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static java.lang.Object sObject;
+    descriptor: Ljava/lang/Object;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerStub();
+    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/TinyFrameworkClassWithInitializerStub;
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: iconst_1
+         x: putstatic     #x                  // Field sInitialized:Z
+         x: new           #x                  // class java/lang/Object
+         x: dup
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+        x: putstatic     #x                 // Field sObject:Ljava/lang/Object;
+        x: return
+      LineNumberTable:
+}
+SourceFile: "TinyFrameworkClassWithInitializerStub.java"
+RuntimeInvisibleAnnotations:
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+  x: #x()
+    android.hosttest.annotation.HostSideTestStaticInitializerKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.class
+  Compiled from "TinyFrameworkEnumComplex.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex>
+  minor version: 0
+  major version: 65
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+  super_class: #x                        // java/lang/Enum
+  interfaces: 0, fields: 6, methods: 7, attributes: 3
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex GREEN;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex BLUE;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private final java.lang.String mLongName;
+    descriptor: Ljava/lang/String;
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private final java.lang.String mShortName;
+    descriptor: Ljava/lang/String;
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: getstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+         x: invokevirtual #x                 // Method "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;".clone:()Ljava/lang/Object;
+         x: checkcast     #x                 // class "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;"
+         x: areturn
+      LineNumberTable:
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: aload_0
+         x: invokestatic  #x                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
+         x: checkcast     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  name   Ljava/lang/String;
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
+    descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=5, args_size=5
+         x: aload_0
+         x: aload_1
+         x: iload_2
+         x: invokespecial #x                 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
+         x: aload_0
+         x: aload_3
+         x: putfield      #x                 // Field mLongName:Ljava/lang/String;
+        x: aload_0
+        x: aload         4
+        x: putfield      #x                 // Field mShortName:Ljava/lang/String;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      18     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+            0      18     3 longName   Ljava/lang/String;
+            0      18     4 shortName   Ljava/lang/String;
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
+      <no name>
+      <no name>
+    Signature: #x                          // (Ljava/lang/String;Ljava/lang/String;)V
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public java.lang.String getLongName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: getfield      #x                 // Field mLongName:Ljava/lang/String;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public java.lang.String getShortName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: getfield      #x                 // Field mShortName:Ljava/lang/String;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: iconst_3
+         x: anewarray     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: dup
+         x: iconst_0
+         x: getstatic     #x                  // Field RED:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+         x: aastore
+        x: dup
+        x: iconst_1
+        x: getstatic     #x                  // Field GREEN:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: aastore
+        x: dup
+        x: iconst_2
+        x: getstatic     #x                 // Field BLUE:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: aastore
+        x: areturn
+      LineNumberTable:
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=6, locals=0, args_size=0
+         x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: dup
+         x: ldc           #x                 // String RED
+         x: iconst_0
+         x: ldc           #x                 // String Red
+         x: ldc           #x                 // String R
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                  // Field RED:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: ldc           #x                 // String GREEN
+        x: iconst_1
+        x: ldc           #x                 // String Green
+        x: ldc           #x                 // String G
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                  // Field GREEN:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: ldc           #x                 // String BLUE
+        x: iconst_2
+        x: ldc           #x                 // String Blue
+        x: ldc           #x                 // String B
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                 // Field BLUE:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: invokestatic  #x                 // Method $values:()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: putstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: return
+      LineNumberTable:
+}
+Signature: #x                          // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;>;
+SourceFile: "TinyFrameworkEnumComplex.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.class
+  Compiled from "TinyFrameworkEnumSimple.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple>
+  minor version: 0
+  major version: 65
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+  super_class: #x                        // java/lang/Enum
+  interfaces: 0, fields: 3, methods: 5, attributes: 3
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple DOG;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: getstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+         x: invokevirtual #x                 // Method "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;".clone:()Ljava/lang/Object;
+         x: checkcast     #x                 // class "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;"
+         x: areturn
+      LineNumberTable:
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: aload_0
+         x: invokestatic  #x                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
+         x: checkcast     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  name   Ljava/lang/String;
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
+    descriptor: (Ljava/lang/String;I)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=3, args_size=3
+         x: aload_0
+         x: aload_1
+         x: iload_2
+         x: invokespecial #x                 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       7     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
+    Signature: #x                          // ()V
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: iconst_2
+         x: anewarray     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: dup
+         x: iconst_0
+         x: getstatic     #x                  // Field CAT:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+         x: aastore
+        x: dup
+        x: iconst_1
+        x: getstatic     #x                  // Field DOG:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: aastore
+        x: areturn
+      LineNumberTable:
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: dup
+         x: ldc           #x                 // String CAT
+         x: iconst_0
+         x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;I)V
+        x: putstatic     #x                  // Field CAT:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+        x: dup
+        x: ldc           #x                 // String DOG
+        x: iconst_1
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;I)V
+        x: putstatic     #x                  // Field DOG:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: invokestatic  #x                 // Method $values:()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: putstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: return
+      LineNumberTable:
+}
+Signature: #x                          // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;>;
+SourceFile: "TinyFrameworkEnumSimple.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
+  Compiled from "TinyFrameworkExceptionTester.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 2
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester();
+    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/TinyFrameworkExceptionTester;
+
+  public static int testException();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=0
+         x: new           #x                  // class java/lang/IllegalStateException
+         x: dup
+         x: ldc           #x                  // String Inner exception
+         x: invokespecial #x                 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
+         x: athrow
+        x: astore_0
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String Outer exception
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V
+        x: athrow
+      Exception table:
+         from    to  target type
+             0    10    10   Class java/lang/Exception
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      11     0     e   Ljava/lang/Exception;
+      StackMapTable: number_of_entries = 1
+        frame_type = 74 /* same_locals_1_stack_item */
+          stack = [ class java/lang/Exception ]
+}
+SourceFile: "TinyFrameworkExceptionTester.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
+  Compiled from "TinyFrameworkForTextPolicy.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 17, attributes: 1
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int remove;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_1
+         x: putfield      #x                  // Field stub:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0       4     1 value   I
+
+  public void toBeRemoved(java.lang.String);
+    descriptor: (Ljava/lang/String;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0       8     1   foo   Ljava/lang/String;
+
+  public java.lang.String toBeIgnoredObj();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public void toBeIgnoredV();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public boolean toBeIgnoredZ();
+    descriptor: ()Z
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public byte toBeIgnoredB();
+    descriptor: ()B
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public char toBeIgnoredC();
+    descriptor: ()C
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public short toBeIgnoredS();
+    descriptor: ()S
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public int toBeIgnoredI();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public float toBeIgnoredF();
+    descriptor: ()F
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public double toBeIgnoredD();
+    descriptor: ()D
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String not supported on host side
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0      10     1 value   I
+
+  public int addTwo_host(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_2
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0       4     1 value   I
+
+  public static native int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+
+  public static int addThree_host(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_3
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0 value   I
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: ldc           #x                 // String This value shouldn\'t be seen on the host side.
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+}
+SourceFile: "TinyFrameworkForTextPolicy.java"
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.class
+  Compiled from "TinyFrameworkLambdas.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 8, attributes: 5
+  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: invokedynamic #x,  0              // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+        x: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      14     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public java.util.function.Supplier<java.lang.Integer> getSupplier();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested;
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+         x: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static java.lang.Integer lambda$getSupplier_static$3();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: bipush        8
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+
+  private static java.lang.Integer lambda$getSupplier$2();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: bipush        7
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+
+  private static java.lang.Integer lambda$static$1();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: bipush        6
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+
+  private static java.lang.Integer lambda$new$0();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: iconst_5
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+         x: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
+         x: return
+      LineNumberTable:
+}
+SourceFile: "TinyFrameworkLambdas.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+  x: #x()
+    android.hosttest.annotation.HostSideTestStaticInitializerKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+BootstrapMethods:
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.lambda$new$0:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.lambda$getSupplier$2:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.lambda$getSupplier_static$3:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.lambda$static$1:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+InnerClasses:
+  public static #x= #x of #x;          // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.class
+  Compiled from "TinyFrameworkLambdas.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 8, attributes: 5
+  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: invokedynamic #x,  0              // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+        x: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      14     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public java.util.function.Supplier<java.lang.Integer> getSupplier();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas;
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+         x: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static java.lang.Integer lambda$getSupplier_static$3();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: iconst_4
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+
+  private static java.lang.Integer lambda$getSupplier$2();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: iconst_3
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+
+  private static java.lang.Integer lambda$static$1();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: iconst_2
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+
+  private static java.lang.Integer lambda$new$0();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: iconst_1
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+         x: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
+         x: return
+      LineNumberTable:
+}
+SourceFile: "TinyFrameworkLambdas.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+  x: #x()
+    android.hosttest.annotation.HostSideTestStaticInitializerKeep
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+BootstrapMethods:
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.lambda$new$0:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.lambda$getSupplier$2:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.lambda$getSupplier_static$3:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.lambda$static$1:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+InnerClasses:
+  public static #x= #x of #x;          // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
+    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/TinyFrameworkMethodCallReplace$ReplaceTo;
+
+  public static void startThread(java.lang.Thread);
+    descriptor: (Ljava/lang/Thread;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: iconst_1
+         x: invokevirtual #x                  // Method java/lang/Thread.setDaemon:(Z)V
+         x: aload_0
+         x: invokevirtual #x                 // Method java/lang/Thread.start:()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0 thread   Ljava/lang/Thread;
+
+  public static int add(int, int);
+    descriptor: (II)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_0
+         x: iload_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0     a   I
+            0       4     1     b   I
+}
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+InnerClasses:
+  public static #x= #x of #x;          // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 5, attributes: 5
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
+    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/TinyFrameworkMethodCallReplace;
+
+  public static boolean nonStaticMethodCallReplaceTester() throws java.lang.Exception;
+    descriptor: ()Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=2, args_size=0
+         x: new           #x                  // class java/util/concurrent/atomic/AtomicBoolean
+         x: dup
+         x: iconst_0
+         x: invokespecial #x                  // Method java/util/concurrent/atomic/AtomicBoolean."<init>":(Z)V
+         x: astore_0
+         x: new           #x                 // class java/lang/Thread
+        x: dup
+        x: aload_0
+        x: invokedynamic #x,  0             // InvokeDynamic #x:run:(Ljava/util/concurrent/atomic/AtomicBoolean;)Ljava/lang/Runnable;
+        x: invokespecial #x                 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
+        x: astore_1
+        x: aload_1
+        x: invokevirtual #x                 // Method java/lang/Thread.start:()V
+        x: aload_1
+        x: invokevirtual #x                 // Method java/lang/Thread.join:()V
+        x: aload_0
+        x: invokevirtual #x                 // Method java/util/concurrent/atomic/AtomicBoolean.get:()Z
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            9      27     0    ab   Ljava/util/concurrent/atomic/AtomicBoolean;
+           23      13     1    th   Ljava/lang/Thread;
+    Exceptions:
+      throws java.lang.Exception
+
+  public static int staticMethodCallReplaceTester();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: iconst_1
+         x: iconst_2
+         x: invokestatic  #x                 // Method originalAdd:(II)I
+         x: ireturn
+      LineNumberTable:
+
+  private static int originalAdd(int, int);
+    descriptor: (II)I
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_0
+         x: iload_1
+         x: iadd
+         x: iconst_1
+         x: isub
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0     a   I
+            0       6     1     b   I
+
+  private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
+    descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokestatic  #x                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
+         x: invokevirtual #x                 // Method java/lang/Thread.isDaemon:()Z
+         x: invokevirtual #x                 // Method java/util/concurrent/atomic/AtomicBoolean.set:(Z)V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0    ab   Ljava/util/concurrent/atomic/AtomicBoolean;
+}
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+BootstrapMethods:
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()V
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.lambda$nonStaticMethodCallReplaceTester$0:(Ljava/util/concurrent/atomic/AtomicBoolean;)V
+      #x ()V
+InnerClasses:
+  public static #x= #x of #x;          // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
+  Compiled from "TinyFrameworkNative.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 14, attributes: 2
+  int value;
+    descriptor: I
+    flags: (0x0000)
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative();
+    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/TinyFrameworkNative;
+
+  public static native int nativeAddTwo(int);
+    descriptor: (I)I
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public static int nativeAddTwo_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iload_0
+         x: invokestatic  #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0   arg   I
+
+  public static native long nativeLongPlus(long, long);
+    descriptor: (JJ)J
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public static long nativeLongPlus_should_be_like_this(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         x: lload_0
+         x: lload_2
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
+         x: lreturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  arg1   J
+            0       6     2  arg2   J
+
+  public void setValue(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: iload_1
+         x: putfield      #x                 // Field value:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+            0       6     1     v   I
+
+  public native int nativeNonStaticAddToValue(int);
+    descriptor: (I)I
+    flags: (0x0101) ACC_PUBLIC, ACC_NATIVE
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public int nativeNonStaticAddToValue_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: iload_1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+            0       6     1   arg   I
+
+  public static native void nativeStillNotSupported();
+    descriptor: ()V
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+
+  public static native void nativeStillKeep();
+    descriptor: ()V
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+
+  public static void nativeStillNotSupported_should_be_like_this();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+
+  public static native byte nativeBytePlus(byte, byte);
+    descriptor: (BB)B
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public void notNativeRedirected();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public static void notNativeStaticRedirected();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+}
+SourceFile: "TinyFrameworkNative.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestRedirectionClass(
+      value="TinyFrameworkNative_host"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.class
+  Compiled from "TinyFrameworkNative_host.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 7, attributes: 2
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
+    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/TinyFrameworkNative_host;
+
+  public static int nativeAddTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_2
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0   arg   I
+
+  public static long nativeLongPlus(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         x: lload_0
+         x: lload_2
+         x: ladd
+         x: lreturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  arg1   J
+            0       4     2  arg2   J
+
+  public static int nativeNonStaticAddToValue(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative, int);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: getfield      #x                  // Field com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.value:I
+         x: iload_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       7     0 source   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+            0       7     1   arg   I
+
+  public static byte nativeBytePlus(byte, byte);
+    descriptor: (BB)B
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_0
+         x: iload_1
+         x: iadd
+         x: i2b
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  arg1   B
+            0       5     1  arg2   B
+
+  public static void notNativeRedirected(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=0, locals=1, args_size=1
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       1     0 source   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+
+  public static void notNativeStaticRedirected();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=0, locals=0, args_size=0
+         x: return
+      LineNumberTable:
+}
+SourceFile: "TinyFrameworkNative_host.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 5
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0000)
+    Code:
+      stack=1, locals=2, args_size=2
+         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/TinyFrameworkNestedClasses$1;
+            0       5     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_1
+         x: invokestatic  #x                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+}
+Signature: #x                          // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+EnclosingMethod: #x.#x                 // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 5
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2();
+    descriptor: ()V
+    flags: (0x0000)
+    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/TinyFrameworkNestedClasses$2;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_2
+         x: invokestatic  #x                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+}
+Signature: #x                          // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+EnclosingMethod: #x.#x                 // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 5
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0000)
+    Code:
+      stack=1, locals=2, args_size=2
+         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/TinyFrameworkNestedClasses$3;
+            0       5     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_3
+         x: invokestatic  #x                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+}
+Signature: #x                          // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.getSupplier
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 5
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4();
+    descriptor: ()V
+    flags: (0x0000)
+    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/TinyFrameworkNestedClasses$4;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_4
+         x: invokestatic  #x                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+}
+Signature: #x                          // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.getSupplier_static
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 1, attributes: 3
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iload_1
+         x: putfield      #x                  // Field value:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass;
+            0      10     1     x   I
+}
+SourceFile: "TinyFrameworkNestedClasses.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 1, attributes: 3
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_5
+         x: putfield      #x                  // Field value:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass;
+            0      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
+}
+SourceFile: "TinyFrameworkNestedClasses.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 5
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1();
+    descriptor: ()V
+    flags: (0x0000)
+    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/TinyFrameworkNestedClasses$StaticNestedClass$1;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: bipush        7
+         x: invokestatic  #x                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+}
+Signature: #x                          // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass.getSupplier_static
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 1, attributes: 3
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: bipush        8
+         x: putfield      #x                  // Field value:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass;
+}
+SourceFile: "TinyFrameworkNestedClasses.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 3
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: bipush        6
+         x: putfield      #x                  // Field value:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass;
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         x: dup
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1."<init>":()V
+         x: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+}
+SourceFile: "TinyFrameworkNestedClasses.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  public static #x= #x of #x;           // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass extends com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  interfaces: 0, fields: 0, methods: 1, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: iload_1
+         x: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass."<init>":(I)V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass;
+            0       6     1     x   I
+}
+SourceFile: "TinyFrameworkNestedClasses.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 4, attributes: 4
+  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         x: dup
+         x: aload_0
+        x: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+        x: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      17     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+
+  public java.util.function.Supplier<java.lang.Integer> getSupplier();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         x: dup
+         x: aload_0
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       9     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         x: dup
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4."<init>":()V
+         x: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         x: dup
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2."<init>":()V
+         x: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
+        x: return
+      LineNumberTable:
+}
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  public static #x= #x of #x;          // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;          // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public #x= #x of #x;                 // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;          // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.class
+  Compiled from "TinyFrameworkPackageRedirect.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 2
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect();
+    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/TinyFrameworkPackageRedirect;
+
+  public static int foo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                  // class com/unsupported/UnsupportedClass
+         x: dup
+         x: iload_0
+         x: invokespecial #x                  // Method com/unsupported/UnsupportedClass."<init>":(I)V
+         x: invokevirtual #x                 // Method com/unsupported/UnsupportedClass.getValue:()I
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      12     0 value   I
+}
+SourceFile: "TinyFrameworkPackageRedirect.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.class
+  Compiled from "TinyFrameworkRenamedClassCaller.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 2
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
+    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/TinyFrameworkRenamedClassCaller;
+
+  public static int foo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+         x: dup
+         x: iload_0
+         x: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed."<init>":(I)V
+         x: invokevirtual #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getValue:()I
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      12     0 value   I
+}
+SourceFile: "TinyFrameworkRenamedClassCaller.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.class
+  Compiled from "TinyFrameworkToBeRenamed.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 2
+  private final int mValue;
+    descriptor: I
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iload_1
+         x: putfield      #x                  // Field mValue:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+            0      10     1 value   I
+
+  public int getValue();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: getfield      #x                  // Field mValue:I
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+}
+SourceFile: "TinyFrameworkToBeRenamed.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.A
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.packagetest.A();
+    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/packagetest/A;
+}
+SourceFile: "A.java"
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/B.class
+  Compiled from "B.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.B
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/B
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.packagetest.B();
+    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/packagetest/B;
+}
+SourceFile: "B.java"
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.packagetest.sub.A();
+    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/packagetest/sub/A;
+}
+SourceFile: "A.java"
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/B.class
+  Compiled from "B.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.B
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/B
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.packagetest.sub.B();
+    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/packagetest/sub/B;
+}
+SourceFile: "B.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
+  Compiled from "C1.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C1
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.C1();
+    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/subclasstest/C1;
+}
+SourceFile: "C1.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C2.class
+  Compiled from "C2.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.C2 extends com.android.hoststubgen.test.tinyframework.subclasstest.C1
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C2
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C1
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.C2();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/subclasstest/C1."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/subclasstest/C2;
+}
+SourceFile: "C2.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C3.class
+  Compiled from "C3.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.C3 extends com.android.hoststubgen.test.tinyframework.subclasstest.C2
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C3
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C2
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.C3();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/subclasstest/C2."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/subclasstest/C3;
+}
+SourceFile: "C3.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/CA.class
+  Compiled from "CA.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.CA
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/CA
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.CA();
+    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/subclasstest/CA;
+}
+SourceFile: "CA.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/CB.class
+  Compiled from "CB.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.CB
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/CB
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.CB();
+    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/subclasstest/CB;
+}
+SourceFile: "CB.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1.class
+  Compiled from "Class_C1.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1 extends com.android.hoststubgen.test.tinyframework.subclasstest.C1
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C1
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/subclasstest/C1."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/subclasstest/Class_C1;
+}
+SourceFile: "Class_C1.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2.class
+  Compiled from "Class_C2.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2 extends com.android.hoststubgen.test.tinyframework.subclasstest.C2
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C2
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/subclasstest/C2."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/subclasstest/Class_C2;
+}
+SourceFile: "Class_C2.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3.class
+  Compiled from "Class_C3.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3 extends com.android.hoststubgen.test.tinyframework.subclasstest.C3
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C3
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/subclasstest/C3."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/subclasstest/Class_C3;
+}
+SourceFile: "Class_C3.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_CA.class
+  Compiled from "Class_CA.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CA extends com.android.hoststubgen.test.tinyframework.subclasstest.CA
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CA
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/CA
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CA();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/subclasstest/CA."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/subclasstest/Class_CA;
+}
+SourceFile: "Class_CA.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB.class
+  Compiled from "Class_CB.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB extends com.android.hoststubgen.test.tinyframework.subclasstest.CB
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/CB
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/subclasstest/CB."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/subclasstest/Class_CB;
+}
+SourceFile: "Class_CB.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA.class
+  Compiled from "Class_CB_IA.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB_IA extends com.android.hoststubgen.test.tinyframework.subclasstest.CB implements com.android.hoststubgen.test.tinyframework.subclasstest.IA
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/CB
+  interfaces: 1, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_CB_IA();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/subclasstest/CB."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA;
+}
+SourceFile: "Class_CB_IA.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1.class
+  Compiled from "Class_I1.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1 implements com.android.hoststubgen.test.tinyframework.subclasstest.I1
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1();
+    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/subclasstest/Class_I1;
+}
+SourceFile: "Class_I1.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA.class
+  Compiled from "Class_I1_IA.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA implements com.android.hoststubgen.test.tinyframework.subclasstest.I1,com.android.hoststubgen.test.tinyframework.subclasstest.IA
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA
+  super_class: #x                         // java/lang/Object
+  interfaces: 2, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA();
+    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/subclasstest/Class_I1_IA;
+}
+SourceFile: "Class_I1_IA.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2.class
+  Compiled from "Class_I2.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2 implements com.android.hoststubgen.test.tinyframework.subclasstest.I2
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2();
+    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/subclasstest/Class_I2;
+}
+SourceFile: "Class_I2.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3.class
+  Compiled from "Class_I3.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3 implements com.android.hoststubgen.test.tinyframework.subclasstest.I3
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3();
+    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/subclasstest/Class_I3;
+}
+SourceFile: "Class_I3.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3_IA.class
+  Compiled from "Class_I3_IA.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3_IA implements com.android.hoststubgen.test.tinyframework.subclasstest.I3,com.android.hoststubgen.test.tinyframework.subclasstest.IA
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3_IA
+  super_class: #x                         // java/lang/Object
+  interfaces: 2, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3_IA();
+    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/subclasstest/Class_I3_IA;
+}
+SourceFile: "Class_I3_IA.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA.class
+  Compiled from "Class_IA.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA implements com.android.hoststubgen.test.tinyframework.subclasstest.IA
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA();
+    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/subclasstest/Class_IA;
+}
+SourceFile: "Class_IA.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I1.class
+  Compiled from "Class_IA_I1.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I1 implements com.android.hoststubgen.test.tinyframework.subclasstest.IA,com.android.hoststubgen.test.tinyframework.subclasstest.I1
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I1
+  super_class: #x                         // java/lang/Object
+  interfaces: 2, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I1();
+    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/subclasstest/Class_IA_I1;
+}
+SourceFile: "Class_IA_I1.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I3.class
+  Compiled from "Class_IA_I3.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I3 implements com.android.hoststubgen.test.tinyframework.subclasstest.IA,com.android.hoststubgen.test.tinyframework.subclasstest.I3
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I3
+  super_class: #x                         // java/lang/Object
+  interfaces: 2, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IA_I3();
+    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/subclasstest/Class_IA_I3;
+}
+SourceFile: "Class_IA_I3.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB.class
+  Compiled from "Class_IB.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB implements com.android.hoststubgen.test.tinyframework.subclasstest.IB
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB();
+    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/subclasstest/Class_IB;
+}
+SourceFile: "Class_IB.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB_IA.class
+  Compiled from "Class_IB_IA.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB_IA implements com.android.hoststubgen.test.tinyframework.subclasstest.IB,com.android.hoststubgen.test.tinyframework.subclasstest.IA
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB_IA
+  super_class: #x                         // java/lang/Object
+  interfaces: 2, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_IB_IA();
+    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/subclasstest/Class_IB_IA;
+}
+SourceFile: "Class_IB_IA.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_None.class
+  Compiled from "Class_None.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_None
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_None
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 1
+  public com.android.hoststubgen.test.tinyframework.subclasstest.Class_None();
+    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/subclasstest/Class_None;
+}
+SourceFile: "Class_None.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I1.class
+  Compiled from "I1.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.I1
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I1
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 1
+}
+SourceFile: "I1.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class
+  Compiled from "I2.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.I2 extends com.android.hoststubgen.test.tinyframework.subclasstest.I1
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I2
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 1
+}
+SourceFile: "I2.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class
+  Compiled from "I3.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.I3 extends com.android.hoststubgen.test.tinyframework.subclasstest.I2
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I3
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 1
+}
+SourceFile: "I3.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class
+  Compiled from "IA.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.IA
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/IA
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 1
+}
+SourceFile: "IA.java"
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class
+  Compiled from "IB.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.IB
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/IB
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 1
+}
+SourceFile: "IB.java"
+## Class: com/supported/UnsupportedClass.class
+  Compiled from "UnsupportedClass.java"
+public class com.supported.UnsupportedClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/supported/UnsupportedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 2
+  private final int mValue;
+    descriptor: I
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+
+  public com.supported.UnsupportedClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iload_1
+         x: putfield      #x                  // Field mValue:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/supported/UnsupportedClass;
+            0      10     1 value   I
+
+  public int getValue();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: getfield      #x                  // Field mValue:I
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/supported/UnsupportedClass;
+}
+SourceFile: "UnsupportedClass.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/unsupported/UnsupportedClass.class
+  Compiled from "UnsupportedClass.java"
+public class com.unsupported.UnsupportedClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/unsupported/UnsupportedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 2
+  public com.unsupported.UnsupportedClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: new           #x                  // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                  // String This class is not supported
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      14     0  this   Lcom/unsupported/UnsupportedClass;
+            0      14     1 value   I
+
+  public int getValue();
+    descriptor: ()I
+    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 This class is not supported
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/unsupported/UnsupportedClass;
+}
+SourceFile: "UnsupportedClass.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/03-hoststubgen-test-tiny-framework-host-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/03-hoststubgen-test-tiny-framework-host-dump.txt
new file mode 100644
index 0000000..84a8373
--- /dev/null
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/03-hoststubgen-test-tiny-framework-host-dump.txt
@@ -0,0 +1,3727 @@
+## Class: android/hosttest/annotation/HostSideTestClassLoadHook.class
+  Compiled from "HostSideTestClassLoadHook.java"
+public interface android.hosttest.annotation.HostSideTestClassLoadHook extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestClassLoadHook
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  public abstract java.lang.String value();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "HostSideTestClassLoadHook.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestKeep.class
+  Compiled from "HostSideTestKeep.java"
+public interface android.hosttest.annotation.HostSideTestKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestKeep.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestRedirect.class
+  Compiled from "HostSideTestRedirect.java"
+public interface android.hosttest.annotation.HostSideTestRedirect extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestRedirect
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestRedirect.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestRedirectionClass.class
+  Compiled from "HostSideTestRedirectionClass.java"
+public interface android.hosttest.annotation.HostSideTestRedirectionClass extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestRedirectionClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  public abstract java.lang.String value();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "HostSideTestRedirectionClass.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestRemove.class
+  Compiled from "HostSideTestRemove.java"
+public interface android.hosttest.annotation.HostSideTestRemove extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestRemove
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestRemove.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestStaticInitializerKeep.class
+  Compiled from "HostSideTestStaticInitializerKeep.java"
+public interface android.hosttest.annotation.HostSideTestStaticInitializerKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestStaticInitializerKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestStaticInitializerKeep.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestSubstitute.class
+  Compiled from "HostSideTestSubstitute.java"
+public interface android.hosttest.annotation.HostSideTestSubstitute extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestSubstitute
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  public abstract java.lang.String suffix();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "HostSideTestSubstitute.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestThrow.class
+  Compiled from "HostSideTestThrow.java"
+public interface android.hosttest.annotation.HostSideTestThrow extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestThrow
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestThrow.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestWholeClassKeep.class
+  Compiled from "HostSideTestWholeClassKeep.java"
+public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestWholeClassKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestWholeClassKeep.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
+    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/IPretendingAidl$Stub$Proxy;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_2
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0     a   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+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
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
+    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/IPretendingAidl$Stub;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int addOne(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0     a   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+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
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class
+  Compiled from "IPretendingAidl.java"
+public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 4
+}
+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
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+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: 65
+  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.HostStubGenProcessedAsKeep
+
+  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.HostStubGenProcessedAsKeep
+
+  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.HostStubGenProcessedAsKeep
+}
+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.HostStubGenProcessedAsKeep
+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: 65
+  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.HostStubGenProcessedAsKeep
+}
+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.HostStubGenProcessedAsKeep
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/R$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
+  Compiled from "TinyFrameworkAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 7, attributes: 3
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  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/TinyFrameworkAnnotations
+         x: ldc           #x                  // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+         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.TinyFrameworkAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_1
+         x: putfield      #x                 // Field keep:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+            0       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_2
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+            0       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_3
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Unreachable
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+
+  public int toBeIgnored();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_0
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestIgnore
+}
+SourceFile: "TinyFrameworkAnnotations.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
+  Compiled from "TinyFrameworkClassLoadHook.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 3, attributes: 3
+  public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
+    descriptor: Ljava/util/Set;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/Set<Ljava/lang/Class<*>;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook();
+    descriptor: ()V
+    flags: (0x0002) ACC_PRIVATE
+    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/TinyFrameworkClassLoadHook;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static void onClassLoaded(java.lang.Class<?>);
+    descriptor: (Ljava/lang/Class;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: getstatic     #x                 // Field sLoadedClasses:Ljava/util/Set;
+         x: aload_0
+         x: invokeinterface #x,  2           // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z
+         x: pop
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0 clazz   Ljava/lang/Class;
+      LocalVariableTypeTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0 clazz   Ljava/lang/Class<*>;
+    Signature: #x                          // (Ljava/lang/Class<*>;)V
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: new           #x                 // class java/util/HashSet
+         x: dup
+         x: invokespecial #x                 // Method java/util/HashSet."<init>":()V
+         x: putstatic     #x                 // Field sLoadedClasses:Ljava/util/Set;
+        x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkClassLoadHook.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.class
+  Compiled from "TinyFrameworkClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 4, attributes: 3
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_1
+         x: putfield      #x                 // Field keep:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+            0       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_2
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+            0       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Unreachable
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+}
+SourceFile: "TinyFrameworkClassWideAnnotations.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.class
+  Compiled from "TinyFrameworkClassWithInitializerDefault.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 0, attributes: 3
+  public static boolean sInitialized;
+    descriptor: Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static java.lang.Object sObject;
+    descriptor: Ljava/lang/Object;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+}
+SourceFile: "TinyFrameworkClassWithInitializerDefault.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.class
+  Compiled from "TinyFrameworkClassWithInitializerStub.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerStub
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 1, attributes: 3
+  public static boolean sInitialized;
+    descriptor: Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static java.lang.Object sObject;
+    descriptor: Ljava/lang/Object;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
+         x: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: iconst_1
+         x: putstatic     #x                 // Field sInitialized:Z
+        x: new           #x                  // class java/lang/Object
+        x: dup
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: putstatic     #x                 // Field sObject:Ljava/lang/Object;
+        x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkClassWithInitializerStub.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+  x: #x()
+    android.hosttest.annotation.HostSideTestStaticInitializerKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.class
+  Compiled from "TinyFrameworkEnumComplex.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex>
+  minor version: 0
+  major version: 65
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+  super_class: #x                         // java/lang/Enum
+  interfaces: 0, fields: 6, methods: 7, attributes: 4
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex GREEN;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex BLUE;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private final java.lang.String mLongName;
+    descriptor: Ljava/lang/String;
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private final java.lang.String mShortName;
+    descriptor: Ljava/lang/String;
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: getstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+         x: invokevirtual #x                 // Method "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;".clone:()Ljava/lang/Object;
+         x: checkcast     #x                 // class "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;"
+         x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: aload_0
+         x: invokestatic  #x                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
+         x: checkcast     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  name   Ljava/lang/String;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
+    descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=5, args_size=5
+         x: aload_0
+         x: aload_1
+         x: iload_2
+         x: invokespecial #x                 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
+         x: aload_0
+         x: aload_3
+         x: putfield      #x                 // Field mLongName:Ljava/lang/String;
+        x: aload_0
+        x: aload         4
+        x: putfield      #x                 // Field mShortName:Ljava/lang/String;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      18     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+            0      18     3 longName   Ljava/lang/String;
+            0      18     4 shortName   Ljava/lang/String;
+    Signature: #x                          // (Ljava/lang/String;Ljava/lang/String;)V
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
+      <no name>
+      <no name>
+
+  public java.lang.String getLongName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: getfield      #x                 // Field mLongName:Ljava/lang/String;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public java.lang.String getShortName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: getfield      #x                 // Field mShortName:Ljava/lang/String;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: iconst_3
+         x: anewarray     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: dup
+         x: iconst_0
+         x: getstatic     #x                 // Field RED:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+         x: aastore
+        x: dup
+        x: iconst_1
+        x: getstatic     #x                 // Field GREEN:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: aastore
+        x: dup
+        x: iconst_2
+        x: getstatic     #x                 // Field BLUE:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: aastore
+        x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=6, locals=0, args_size=0
+         x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: dup
+         x: ldc           #x                 // String RED
+         x: iconst_0
+         x: ldc           #x                 // String Red
+         x: ldc           #x                 // String R
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                 // Field RED:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: ldc           #x                 // String GREEN
+        x: iconst_1
+        x: ldc           #x                 // String Green
+        x: ldc           #x                 // String G
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                 // Field GREEN:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: ldc           #x                 // String BLUE
+        x: iconst_2
+        x: ldc           #x                 // String Blue
+        x: ldc           #x                 // String B
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                 // Field BLUE:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: invokestatic  #x                 // Method $values:()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: putstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;>;
+SourceFile: "TinyFrameworkEnumComplex.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.class
+  Compiled from "TinyFrameworkEnumSimple.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple>
+  minor version: 0
+  major version: 65
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+  super_class: #x                         // java/lang/Enum
+  interfaces: 0, fields: 3, methods: 5, attributes: 4
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple DOG;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: getstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+         x: invokevirtual #x                 // Method "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;".clone:()Ljava/lang/Object;
+         x: checkcast     #x                 // class "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;"
+         x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: aload_0
+         x: invokestatic  #x                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
+         x: checkcast     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  name   Ljava/lang/String;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
+    descriptor: (Ljava/lang/String;I)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=3, args_size=3
+         x: aload_0
+         x: aload_1
+         x: iload_2
+         x: invokespecial #x                 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       7     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    Signature: #x                          // ()V
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: iconst_2
+         x: anewarray     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: dup
+         x: iconst_0
+         x: getstatic     #x                 // Field CAT:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+         x: aastore
+        x: dup
+        x: iconst_1
+        x: getstatic     #x                 // Field DOG:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: aastore
+        x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: dup
+         x: ldc           #x                 // String CAT
+         x: iconst_0
+         x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;I)V
+        x: putstatic     #x                 // Field CAT:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+        x: dup
+        x: ldc           #x                 // String DOG
+        x: iconst_1
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;I)V
+        x: putstatic     #x                 // Field DOG:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: invokestatic  #x                 // Method $values:()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: putstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;>;
+SourceFile: "TinyFrameworkEnumSimple.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
+  Compiled from "TinyFrameworkExceptionTester.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester();
+    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/TinyFrameworkExceptionTester;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int testException();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=0
+         x: new           #x                 // class java/lang/IllegalStateException
+         x: dup
+         x: ldc           #x                 // String Inner exception
+         x: invokespecial #x                 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
+         x: athrow
+        x: astore_0
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String Outer exception
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V
+        x: athrow
+      Exception table:
+         from    to  target type
+             0    10    10   Class java/lang/Exception
+      StackMapTable: number_of_entries = 1
+        frame_type = 74 /* same_locals_1_stack_item */
+          stack = [ class java/lang/Exception ]
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      11     0     e   Ljava/lang/Exception;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkExceptionTester.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
+  Compiled from "TinyFrameworkForTextPolicy.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 15, attributes: 2
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  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/TinyFrameworkForTextPolicy
+         x: ldc           #x                  // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+         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.TinyFrameworkForTextPolicy();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_1
+         x: putfield      #x                 // Field stub:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.String toBeIgnoredObj();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aconst_null
+         x: areturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public void toBeIgnoredV();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=0, locals=1, args_size=1
+         x: return
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public boolean toBeIgnoredZ();
+    descriptor: ()Z
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_0
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public byte toBeIgnoredB();
+    descriptor: ()B
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_0
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public char toBeIgnoredC();
+    descriptor: ()C
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_0
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public short toBeIgnoredS();
+    descriptor: ()S
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_0
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int toBeIgnoredI();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_0
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public float toBeIgnoredF();
+    descriptor: ()F
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: fconst_0
+         x: freturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public double toBeIgnoredD();
+    descriptor: ()D
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: dconst_0
+         x: dreturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_2
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_3
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Unreachable
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkForTextPolicy.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.class
+  Compiled from "TinyFrameworkLambdas.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 8, attributes: 6
+  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+        x: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      14     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public java.util.function.Supplier<java.lang.Integer> getSupplier();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested;
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+         x: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static java.lang.Integer lambda$getSupplier_static$3();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: bipush        8
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static java.lang.Integer lambda$getSupplier$2();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: bipush        7
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static java.lang.Integer lambda$static$1();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: bipush        6
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static java.lang.Integer lambda$new$0();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: iconst_5
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+         x: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
+         x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+SourceFile: "TinyFrameworkLambdas.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+  x: #x()
+    android.hosttest.annotation.HostSideTestStaticInitializerKeep
+BootstrapMethods:
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.lambda$new$0:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.lambda$getSupplier$2:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.lambda$getSupplier_static$3:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.lambda$static$1:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.class
+  Compiled from "TinyFrameworkLambdas.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 8, attributes: 6
+  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+        x: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      14     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public java.util.function.Supplier<java.lang.Integer> getSupplier();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas;
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+         x: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static java.lang.Integer lambda$getSupplier_static$3();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: iconst_4
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static java.lang.Integer lambda$getSupplier$2();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: iconst_3
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static java.lang.Integer lambda$static$1();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: iconst_2
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static java.lang.Integer lambda$new$0();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: iconst_1
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+         x: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
+         x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;           // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+SourceFile: "TinyFrameworkLambdas.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+  x: #x()
+    android.hosttest.annotation.HostSideTestStaticInitializerKeep
+BootstrapMethods:
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.lambda$new$0:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.lambda$getSupplier$2:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.lambda$getSupplier_static$3:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.lambda$static$1:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
+    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/TinyFrameworkMethodCallReplace$ReplaceTo;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static void startThread(java.lang.Thread);
+    descriptor: (Ljava/lang/Thread;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: iconst_1
+         x: invokevirtual #x                 // Method java/lang/Thread.setDaemon:(Z)V
+         x: aload_0
+         x: invokevirtual #x                 // Method java/lang/Thread.start:()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0 thread   Ljava/lang/Thread;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int add(int, int);
+    descriptor: (II)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_0
+         x: iload_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0     a   I
+            0       4     1     b   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;             // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 4, attributes: 6
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
+    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/TinyFrameworkMethodCallReplace;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static boolean nonStaticMethodCallReplaceTester() throws java.lang.Exception;
+    descriptor: ()Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=2, args_size=0
+         x: new           #x                 // class java/util/concurrent/atomic/AtomicBoolean
+         x: dup
+         x: iconst_0
+         x: invokespecial #x                 // Method java/util/concurrent/atomic/AtomicBoolean."<init>":(Z)V
+         x: astore_0
+         x: new           #x                 // class java/lang/Thread
+        x: dup
+        x: aload_0
+        x: invokedynamic #x,  0             // InvokeDynamic #x:run:(Ljava/util/concurrent/atomic/AtomicBoolean;)Ljava/lang/Runnable;
+        x: invokespecial #x                 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
+        x: astore_1
+        x: aload_1
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.startThread:(Ljava/lang/Thread;)V
+        x: aload_1
+        x: invokevirtual #x                 // Method java/lang/Thread.join:()V
+        x: aload_0
+        x: invokevirtual #x                 // Method java/util/concurrent/atomic/AtomicBoolean.get:()Z
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            9      27     0    ab   Ljava/util/concurrent/atomic/AtomicBoolean;
+           23      13     1    th   Ljava/lang/Thread;
+    Exceptions:
+      throws java.lang.Exception
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int staticMethodCallReplaceTester();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: iconst_1
+         x: iconst_2
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.add:(II)I
+         x: ireturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
+    descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokestatic  #x                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
+         x: invokevirtual #x                 // Method java/lang/Thread.isDaemon:()Z
+         x: invokevirtual #x                 // Method java/util/concurrent/atomic/AtomicBoolean.set:(Z)V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0    ab   Ljava/util/concurrent/atomic/AtomicBoolean;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;            // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+BootstrapMethods:
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()V
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.lambda$nonStaticMethodCallReplaceTester$0:(Ljava/util/concurrent/atomic/AtomicBoolean;)V
+      #x ()V
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
+  Compiled from "TinyFrameworkNative.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 14, attributes: 3
+  int value;
+    descriptor: I
+    flags: (0x0000)
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative();
+    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/TinyFrameworkNative;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int nativeAddTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iload_0
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public static int nativeAddTwo_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iload_0
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0   arg   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static long nativeLongPlus(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         x: lload_0
+         x: lload_2
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
+         x: lreturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public static long nativeLongPlus_should_be_like_this(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         x: lload_0
+         x: lload_2
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
+         x: lreturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  arg1   J
+            0       6     2  arg2   J
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public void setValue(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: iload_1
+         x: putfield      #x                 // Field value:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+            0       6     1     v   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int nativeNonStaticAddToValue(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: iload_1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public int nativeNonStaticAddToValue_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: iload_1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+            0       6     1   arg   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static void nativeStillNotSupported();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Unreachable
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+
+  public static native void nativeStillKeep();
+    descriptor: ()V
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static void nativeStillNotSupported_should_be_like_this();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static byte nativeBytePlus(byte, byte);
+    descriptor: (BB)B
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_0
+         x: iload_1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public void notNativeRedirected();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.notNativeRedirected:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;)V
+         x: return
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public static void notNativeStaticRedirected();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=0, locals=0, args_size=0
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.notNativeStaticRedirected:()V
+         x: return
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+}
+SourceFile: "TinyFrameworkNative.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestRedirectionClass(
+      value="TinyFrameworkNative_host"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.class
+  Compiled from "TinyFrameworkNative_host.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 7, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
+    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/TinyFrameworkNative_host;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int nativeAddTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_2
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0   arg   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static long nativeLongPlus(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         x: lload_0
+         x: lload_2
+         x: ladd
+         x: lreturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  arg1   J
+            0       4     2  arg2   J
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int nativeNonStaticAddToValue(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative, int);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: getfield      #x                 // Field com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.value:I
+         x: iload_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       7     0 source   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+            0       7     1   arg   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static byte nativeBytePlus(byte, byte);
+    descriptor: (BB)B
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_0
+         x: iload_1
+         x: iadd
+         x: i2b
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  arg1   B
+            0       5     1  arg2   B
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static void notNativeRedirected(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=0, locals=1, args_size=1
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       1     0 source   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static void notNativeStaticRedirected();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=0, locals=0, args_size=0
+         x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkNative_host.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 6
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0000)
+    Code:
+      stack=1, locals=2, args_size=2
+         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/TinyFrameworkNestedClasses$1;
+            0       5     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_1
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+EnclosingMethod: #x.#x                 // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 6
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2();
+    descriptor: ()V
+    flags: (0x0000)
+    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/TinyFrameworkNestedClasses$2;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_2
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+EnclosingMethod: #x.#x                 // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 6
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0000)
+    Code:
+      stack=1, locals=2, args_size=2
+         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/TinyFrameworkNestedClasses$3;
+            0       5     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_3
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.getSupplier
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 6
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4();
+    descriptor: ()V
+    flags: (0x0000)
+    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/TinyFrameworkNestedClasses$4;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_4
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.getSupplier_static
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 1, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iload_1
+         x: putfield      #x                 // Field value:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass;
+            0      10     1     x   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;             // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 1, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_5
+         x: putfield      #x                 // Field value:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass;
+            0      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
+}
+InnerClasses:
+  public #x= #x of #x;                    // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 6
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1();
+    descriptor: ()V
+    flags: (0x0000)
+    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/TinyFrameworkNestedClasses$StaticNestedClass$1;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: bipush        7
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass.getSupplier_static
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 1, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: bipush        8
+         x: putfield      #x                 // Field value:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: bipush        6
+         x: putfield      #x                 // Field value:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         x: dup
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1."<init>":()V
+         x: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;             // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  public static #x= #x of #x;           // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass extends com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  interfaces: 0, fields: 0, methods: 1, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: iload_1
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass."<init>":(I)V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass;
+            0       6     1     x   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;             // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;            // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 4, attributes: 5
+  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         x: dup
+         x: aload_0
+        x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+        x: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      17     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.util.function.Supplier<java.lang.Integer> getSupplier();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         x: dup
+         x: aload_0
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       9     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         x: dup
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4."<init>":()V
+         x: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         x: dup
+         x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2."<init>":()V
+         x: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
+        x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  public static #x= #x of #x;            // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;          // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.class
+  Compiled from "TinyFrameworkPackageRedirect.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect();
+    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/TinyFrameworkPackageRedirect;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int foo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class com/supported/UnsupportedClass
+         x: dup
+         x: iload_0
+         x: invokespecial #x                 // Method com/supported/UnsupportedClass."<init>":(I)V
+         x: invokevirtual #x                 // Method com/supported/UnsupportedClass.getValue:()I
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      12     0 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkPackageRedirect.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.class
+  Compiled from "TinyFrameworkRenamedClassCaller.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
+    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/TinyFrameworkRenamedClassCaller;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int foo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+         x: dup
+         x: iload_0
+         x: invokespecial #x                 // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed."<init>":(I)V
+         x: invokevirtual #x                 // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getValue:()I
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      12     0 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkRenamedClassCaller.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.A
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
+  Compiled from "C1.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C1
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "C1.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C2.class
+  Compiled from "C2.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.C2 extends com.android.hoststubgen.test.tinyframework.subclasstest.C1
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C2
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C1
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "C2.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C3.class
+  Compiled from "C3.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.C3 extends com.android.hoststubgen.test.tinyframework.subclasstest.C2
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C3
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C2
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "C3.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/CA.class
+  Compiled from "CA.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.CA
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/CA
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "CA.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/CB.class
+  Compiled from "CB.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.CB
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/CB
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "CB.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1.class
+  Compiled from "Class_C1.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1 extends com.android.hoststubgen.test.tinyframework.subclasstest.C1
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C1
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "Class_C1.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2.class
+  Compiled from "Class_C2.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2 extends com.android.hoststubgen.test.tinyframework.subclasstest.C2
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C2
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "Class_C2.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3.class
+  Compiled from "Class_C3.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3 extends com.android.hoststubgen.test.tinyframework.subclasstest.C3
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C3
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "Class_C3.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1.class
+  Compiled from "Class_I1.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1 implements com.android.hoststubgen.test.tinyframework.subclasstest.I1
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "Class_I1.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA.class
+  Compiled from "Class_I1_IA.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA implements com.android.hoststubgen.test.tinyframework.subclasstest.I1,com.android.hoststubgen.test.tinyframework.subclasstest.IA
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA
+  super_class: #x                         // java/lang/Object
+  interfaces: 2, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "Class_I1_IA.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2.class
+  Compiled from "Class_I2.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2 implements com.android.hoststubgen.test.tinyframework.subclasstest.I2
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "Class_I2.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3.class
+  Compiled from "Class_I3.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3 implements com.android.hoststubgen.test.tinyframework.subclasstest.I3
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "Class_I3.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I1.class
+  Compiled from "I1.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.I1
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I1
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "I1.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class
+  Compiled from "I2.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.I2 extends com.android.hoststubgen.test.tinyframework.subclasstest.I1
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I2
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "I2.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class
+  Compiled from "I3.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.I3 extends com.android.hoststubgen.test.tinyframework.subclasstest.I2
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I3
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "I3.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class
+  Compiled from "IA.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.IA
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/IA
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "IA.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class
+  Compiled from "IB.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.IB
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/IB
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "IB.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/supported/UnsupportedClass.class
+  Compiled from "UnsupportedClass.java"
+public class com.supported.UnsupportedClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/supported/UnsupportedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 3
+  private final int mValue;
+    descriptor: I
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public com.supported.UnsupportedClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iload_1
+         x: putfield      #x                 // Field mValue:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/supported/UnsupportedClass;
+            0      10     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int getValue();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: getfield      #x                 // Field mValue:I
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/supported/UnsupportedClass;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "UnsupportedClass.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/unsupported/UnsupportedClass.class
+  Compiled from "UnsupportedClass.java"
+public class com.unsupported.UnsupportedClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/unsupported/UnsupportedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 3
+  public com.unsupported.UnsupportedClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String This class is not supported
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      14     0  this   Lcom/unsupported/UnsupportedClass;
+            0      14     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int getValue();
+    descriptor: ()I
+    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 This class is not supported
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/unsupported/UnsupportedClass;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "UnsupportedClass.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.class
+  Compiled from "TinyFrameworkToBeRenamed.java"
+public class rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 3
+  private final int mValue;
+    descriptor: I
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iload_1
+         x: putfield      #x                 // Field mValue:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+            0      10     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int getValue();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: getfield      #x                 // Field mValue:I
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkToBeRenamed.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
new file mode 100644
index 0000000..49769e6
--- /dev/null
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
@@ -0,0 +1,4896 @@
+## Class: android/hosttest/annotation/HostSideTestClassLoadHook.class
+  Compiled from "HostSideTestClassLoadHook.java"
+public interface android.hosttest.annotation.HostSideTestClassLoadHook extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestClassLoadHook
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 2, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestClassLoadHook
+         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 abstract java.lang.String value();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "HostSideTestClassLoadHook.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestKeep.class
+  Compiled from "HostSideTestKeep.java"
+public interface android.hosttest.annotation.HostSideTestKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestKeep
+         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
+}
+SourceFile: "HostSideTestKeep.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestRedirect.class
+  Compiled from "HostSideTestRedirect.java"
+public interface android.hosttest.annotation.HostSideTestRedirect extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestRedirect
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestRedirect
+         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
+}
+SourceFile: "HostSideTestRedirect.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestRedirectionClass.class
+  Compiled from "HostSideTestRedirectionClass.java"
+public interface android.hosttest.annotation.HostSideTestRedirectionClass extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestRedirectionClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 2, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestRedirectionClass
+         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 abstract java.lang.String value();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "HostSideTestRedirectionClass.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestRemove.class
+  Compiled from "HostSideTestRemove.java"
+public interface android.hosttest.annotation.HostSideTestRemove extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestRemove
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestRemove
+         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
+}
+SourceFile: "HostSideTestRemove.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestStaticInitializerKeep.class
+  Compiled from "HostSideTestStaticInitializerKeep.java"
+public interface android.hosttest.annotation.HostSideTestStaticInitializerKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestStaticInitializerKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestStaticInitializerKeep
+         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
+}
+SourceFile: "HostSideTestStaticInitializerKeep.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestSubstitute.class
+  Compiled from "HostSideTestSubstitute.java"
+public interface android.hosttest.annotation.HostSideTestSubstitute extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestSubstitute
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 2, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestSubstitute
+         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 abstract java.lang.String suffix();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "HostSideTestSubstitute.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestThrow.class
+  Compiled from "HostSideTestThrow.java"
+public interface android.hosttest.annotation.HostSideTestThrow extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestThrow
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestThrow
+         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
+}
+SourceFile: "HostSideTestThrow.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestWholeClassKeep.class
+  Compiled from "HostSideTestWholeClassKeep.java"
+public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 65
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestWholeClassKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestWholeClassKeep
+         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
+}
+SourceFile: "HostSideTestWholeClassKeep.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+  x: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  x: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, 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/IPretendingAidl$Stub$Proxy
+         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.IPretendingAidl$Stub$Proxy();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
+         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/IPretendingAidl$Stub$Proxy;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
+         x: ldc           #x                 // String addTwo
+         x: ldc           #x                 // String (I)I
+         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: iload_0
+        x: iconst_2
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0     a   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+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
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class
+  Compiled from "IPretendingAidl.java"
+public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, 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/IPretendingAidl$Stub
+         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.IPretendingAidl$Stub();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+         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/IPretendingAidl$Stub;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int addOne(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
+         x: ldc           #x                 // String addOne
+         x: ldc           #x                 // String (I)I
+         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: iload_0
+        x: iconst_1
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0     a   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+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
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class
+  Compiled from "IPretendingAidl.java"
+public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, 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/IPretendingAidl
+         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
+}
+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
+SourceFile: "IPretendingAidl.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+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: 65
+  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.HostStubGenProcessedAsKeep
+
+  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.HostStubGenProcessedAsKeep
+
+  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.HostStubGenProcessedAsKeep
+}
+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.HostStubGenProcessedAsKeep
+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: 65
+  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.HostStubGenProcessedAsKeep
+}
+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.HostStubGenProcessedAsKeep
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/R$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
+  Compiled from "TinyFrameworkAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 7, attributes: 3
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  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/TinyFrameworkAnnotations
+         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: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+         x: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+        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.TinyFrameworkAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+         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: aload_0
+        x: iconst_1
+        x: putfield      #x                 // Field keep:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+         x: ldc           #x                 // String addOne
+         x: ldc           #x                 // String (I)I
+         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: iload_1
+        x: iconst_1
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+           11       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+         x: ldc           #x                 // String addTwo
+         x: ldc           #x                 // String (I)I
+         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: iload_1
+        x: iconst_2
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+           11       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+         x: ldc           #x                 // String nativeAddThree
+         x: ldc           #x                 // String (I)I
+         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: iload_0
+        x: iconst_3
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+         x: ldc           #x                 // String unsupportedMethod
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         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: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String Unreachable
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+
+  public int toBeIgnored();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+         x: ldc           #x                 // String toBeIgnored
+         x: ldc           #x                 // String ()I
+         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: iconst_0
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestIgnore
+}
+SourceFile: "TinyFrameworkAnnotations.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
+  Compiled from "TinyFrameworkClassLoadHook.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 3, attributes: 3
+  public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
+    descriptor: Ljava/util/Set;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/Set<Ljava/lang/Class<*>;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook();
+    descriptor: ()V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+         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/TinyFrameworkClassLoadHook;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static void onClassLoaded(java.lang.Class<?>);
+    descriptor: (Ljava/lang/Class;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+         x: ldc           #x                 // String onClassLoaded
+         x: ldc           #x                 // String (Ljava/lang/Class;)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: getstatic     #x                 // Field sLoadedClasses:Ljava/util/Set;
+        x: aload_0
+        x: invokeinterface #x,  2           // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z
+        x: pop
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      11     0 clazz   Ljava/lang/Class;
+      LocalVariableTypeTable:
+        Start  Length  Slot  Name   Signature
+           11      11     0 clazz   Ljava/lang/Class<*>;
+    Signature: #x                          // (Ljava/lang/Class<*>;)V
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+         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/TinyFrameworkClassLoadHook
+        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: new           #x                 // class java/util/HashSet
+        x: dup
+        x: invokespecial #x                 // Method java/util/HashSet."<init>":()V
+        x: putstatic     #x                 // Field sLoadedClasses:Ljava/util/Set;
+        x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkClassLoadHook.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.class
+  Compiled from "TinyFrameworkClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 5, attributes: 3
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  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/TinyFrameworkClassWideAnnotations
+         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.TinyFrameworkClassWideAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+         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: aload_0
+        x: iconst_1
+        x: putfield      #x                 // Field keep:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+         x: ldc           #x                 // String addOne
+         x: ldc           #x                 // String (I)I
+         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: iload_1
+        x: iconst_1
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+           11       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+         x: ldc           #x                 // String addTwo
+         x: ldc           #x                 // String (I)I
+         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: iload_1
+        x: iconst_2
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+           11       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+         x: ldc           #x                 // String unsupportedMethod
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         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: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String Unreachable
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+}
+SourceFile: "TinyFrameworkClassWideAnnotations.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.class
+  Compiled from "TinyFrameworkClassWithInitializerDefault.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 0, attributes: 3
+  public static boolean sInitialized;
+    descriptor: Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static java.lang.Object sObject;
+    descriptor: Ljava/lang/Object;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+}
+SourceFile: "TinyFrameworkClassWithInitializerDefault.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.class
+  Compiled from "TinyFrameworkClassWithInitializerStub.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerStub
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 1, attributes: 3
+  public static boolean sInitialized;
+    descriptor: Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static java.lang.Object sObject;
+    descriptor: Ljava/lang/Object;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
+         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/TinyFrameworkClassWithInitializerStub
+        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: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
+        x: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+        x: iconst_1
+        x: putstatic     #x                 // Field sInitialized:Z
+        x: new           #x                  // class java/lang/Object
+        x: dup
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: putstatic     #x                 // Field sObject:Ljava/lang/Object;
+        x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkClassWithInitializerStub.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+  x: #x()
+    android.hosttest.annotation.HostSideTestStaticInitializerKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.class
+  Compiled from "TinyFrameworkEnumComplex.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex>
+  minor version: 0
+  major version: 65
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+  super_class: #x                         // java/lang/Enum
+  interfaces: 0, fields: 6, methods: 7, attributes: 4
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex GREEN;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex BLUE;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private final java.lang.String mLongName;
+    descriptor: Ljava/lang/String;
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private final java.lang.String mShortName;
+    descriptor: Ljava/lang/String;
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: ldc           #x                 // String values
+         x: ldc           #x                 // String ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+         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: getstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: invokevirtual #x                 // Method "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;".clone:()Ljava/lang/Object;
+        x: checkcast     #x                 // class "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;"
+        x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: ldc           #x                 // String valueOf
+         x: ldc           #x                 // String (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+         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/TinyFrameworkEnumComplex
+        x: aload_0
+        x: invokestatic  #x                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
+        x: checkcast     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0  name   Ljava/lang/String;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
+    descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=4, locals=5, args_size=5
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (Ljava/lang/String;ILjava/lang/String;Ljava/lang/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: aload_1
+        x: iload_2
+        x: invokespecial #x                 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
+        x: aload_0
+        x: aload_3
+        x: putfield      #x                 // Field mLongName:Ljava/lang/String;
+        x: aload_0
+        x: aload         4
+        x: putfield      #x                 // Field mShortName:Ljava/lang/String;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      18     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+           11      18     3 longName   Ljava/lang/String;
+           11      18     4 shortName   Ljava/lang/String;
+    Signature: #x                          // (Ljava/lang/String;Ljava/lang/String;)V
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
+      <no name>
+      <no name>
+
+  public java.lang.String getLongName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: ldc           #x                 // String getLongName
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         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: getfield      #x                 // Field mLongName:Ljava/lang/String;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public java.lang.String getShortName();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: ldc           #x                 // String getShortName
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         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: getfield      #x                 // Field mShortName:Ljava/lang/String;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         x: ldc           #x                 // String $values
+         x: ldc           #x                 // String ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+         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: iconst_3
+        x: anewarray     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: iconst_0
+        x: getstatic     #x                 // Field RED:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: aastore
+        x: dup
+        x: iconst_1
+        x: getstatic     #x                 // Field GREEN:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: aastore
+        x: dup
+        x: iconst_2
+        x: getstatic     #x                 // Field BLUE:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: aastore
+        x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=6, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+         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/TinyFrameworkEnumComplex
+        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: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: ldc           #x                 // String RED
+        x: iconst_0
+        x: ldc           #x                 // String Red
+        x: ldc           #x                 // String R
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                 // Field RED:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: ldc           #x                 // String GREEN
+        x: iconst_1
+        x: ldc           #x                 // String Green
+        x: ldc           #x                 // String G
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                 // Field GREEN:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
+        x: dup
+        x: ldc           #x                 // String BLUE
+        x: iconst_2
+        x: ldc           #x                 // String Blue
+        x: ldc           #x                // String B
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+        x: putstatic     #x                 // Field BLUE:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: invokestatic  #x                // Method $values:()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: putstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
+        x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;>;
+SourceFile: "TinyFrameworkEnumComplex.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.class
+  Compiled from "TinyFrameworkEnumSimple.java"
+public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple>
+  minor version: 0
+  major version: 65
+  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+  super_class: #x                         // java/lang/Enum
+  interfaces: 0, fields: 3, methods: 5, attributes: 4
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple DOG;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $VALUES;
+    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: ldc           #x                 // String values
+         x: ldc           #x                 // String ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+         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: getstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: invokevirtual #x                 // Method "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;".clone:()Ljava/lang/Object;
+        x: checkcast     #x                 // class "[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;"
+        x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple valueOf(java.lang.String);
+    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: ldc           #x                 // String valueOf
+         x: ldc           #x                 // String (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+         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/TinyFrameworkEnumSimple
+        x: aload_0
+        x: invokestatic  #x                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
+        x: checkcast     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0  name   Ljava/lang/String;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    MethodParameters:
+      Name                           Flags
+      <no name>                      mandated
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
+    descriptor: (Ljava/lang/String;I)V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=4, locals=3, args_size=3
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (Ljava/lang/String;I)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: aload_1
+        x: iload_2
+        x: invokespecial #x                 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       7     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    Signature: #x                          // ()V
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    MethodParameters:
+      Name                           Flags
+      <no name>                      synthetic
+      <no name>                      synthetic
+
+  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
+    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         x: ldc           #x                 // String $values
+         x: ldc           #x                 // String ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+         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: iconst_2
+        x: anewarray     #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+        x: dup
+        x: iconst_0
+        x: getstatic     #x                 // Field CAT:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: aastore
+        x: dup
+        x: iconst_1
+        x: getstatic     #x                 // Field DOG:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: aastore
+        x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+         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/TinyFrameworkEnumSimple
+        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: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+        x: dup
+        x: ldc           #x                 // String CAT
+        x: iconst_0
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;I)V
+        x: putstatic     #x                 // Field CAT:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
+        x: dup
+        x: ldc           #x                 // String DOG
+        x: iconst_1
+        x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;I)V
+        x: putstatic     #x                 // Field DOG:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: invokestatic  #x                 // Method $values:()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: putstatic     #x                 // Field $VALUES:[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+        x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;>;
+SourceFile: "TinyFrameworkEnumSimple.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
+  Compiled from "TinyFrameworkExceptionTester.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 3
+  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/TinyFrameworkExceptionTester
+         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.TinyFrameworkExceptionTester();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
+         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/TinyFrameworkExceptionTester;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int testException();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
+         x: ldc           #x                 // String testException
+         x: ldc           #x                 // String ()I
+         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: new           #x                 // class java/lang/IllegalStateException
+        x: dup
+        x: ldc           #x                 // String Inner exception
+        x: invokespecial #x                 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
+        x: athrow
+        x: astore_0
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String Outer exception
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V
+        x: athrow
+      Exception table:
+         from    to  target type
+            11    21    21   Class java/lang/Exception
+      StackMapTable: number_of_entries = 1
+        frame_type = 85 /* same_locals_1_stack_item */
+          stack = [ class java/lang/Exception ]
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           22      11     0     e   Ljava/lang/Exception;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkExceptionTester.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
+  Compiled from "TinyFrameworkForTextPolicy.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 15, attributes: 2
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  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/TinyFrameworkForTextPolicy
+         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: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+        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.TinyFrameworkForTextPolicy();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         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: aload_0
+        x: iconst_1
+        x: putfield      #x                 // Field stub:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String addOne
+         x: ldc           #x                 // String (I)I
+         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: iload_1
+        x: iconst_1
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+           11       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.String toBeIgnoredObj();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredObj
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         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: aconst_null
+        x: areturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public void toBeIgnoredV();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredV
+         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: return
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public boolean toBeIgnoredZ();
+    descriptor: ()Z
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredZ
+         x: ldc           #x                 // String ()Z
+         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: iconst_0
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public byte toBeIgnoredB();
+    descriptor: ()B
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredB
+         x: ldc           #x                 // String ()B
+         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: iconst_0
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public char toBeIgnoredC();
+    descriptor: ()C
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredC
+         x: ldc           #x                 // String ()C
+         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: iconst_0
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public short toBeIgnoredS();
+    descriptor: ()S
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredS
+         x: ldc           #x                 // String ()S
+         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: iconst_0
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int toBeIgnoredI();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredI
+         x: ldc           #x                 // String ()I
+         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: iconst_0
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public float toBeIgnoredF();
+    descriptor: ()F
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredF
+         x: ldc           #x                 // String ()F
+         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: fconst_0
+        x: freturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public double toBeIgnoredD();
+    descriptor: ()D
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredD
+         x: ldc           #x                 // String ()D
+         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: dconst_0
+        x: dreturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String addTwo
+         x: ldc           #x                 // String (I)I
+         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: iload_1
+        x: iconst_2
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+           11       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String nativeAddThree
+         x: ldc           #x                 // String (I)I
+         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: iload_0
+        x: iconst_3
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String unsupportedMethod
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         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: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String Unreachable
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkForTextPolicy.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.class
+  Compiled from "TinyFrameworkLambdas.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 8, attributes: 6
+  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$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: aload_0
+        x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+        x: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      14     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public java.util.function.Supplier<java.lang.Integer> getSupplier();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+         x: ldc           #x                 // String getSupplier
+         x: ldc           #x                 // String ()Ljava/util/function/Supplier;
+         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: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested;
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+         x: ldc           #x                 // String getSupplier_static
+         x: ldc           #x                 // String ()Ljava/util/function/Supplier;
+         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: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+        x: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static java.lang.Integer lambda$getSupplier_static$3();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+         x: ldc           #x                 // String lambda$getSupplier_static$3
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         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: bipush        8
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static java.lang.Integer lambda$getSupplier$2();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+         x: ldc           #x                 // String lambda$getSupplier$2
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         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: bipush        7
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static java.lang.Integer lambda$static$1();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+         x: ldc           #x                 // String lambda$static$1
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         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: bipush        6
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static java.lang.Integer lambda$new$0();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+         x: ldc           #x                 // String lambda$new$0
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         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: iconst_5
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$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/TinyFrameworkLambdas$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: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+        x: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
+        x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+SourceFile: "TinyFrameworkLambdas.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+  x: #x()
+    android.hosttest.annotation.HostSideTestStaticInitializerKeep
+BootstrapMethods:
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.lambda$new$0:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.lambda$getSupplier$2:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.lambda$getSupplier_static$3:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.lambda$static$1:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.class
+  Compiled from "TinyFrameworkLambdas.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 8, attributes: 6
+  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+         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: aload_0
+        x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+        x: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      14     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public java.util.function.Supplier<java.lang.Integer> getSupplier();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+         x: ldc           #x                 // String getSupplier
+         x: ldc           #x                 // String ()Ljava/util/function/Supplier;
+         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: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas;
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+         x: ldc           #x                 // String getSupplier_static
+         x: ldc           #x                 // String ()Ljava/util/function/Supplier;
+         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: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+        x: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static java.lang.Integer lambda$getSupplier_static$3();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+         x: ldc           #x                 // String lambda$getSupplier_static$3
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         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: iconst_4
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static java.lang.Integer lambda$getSupplier$2();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+         x: ldc           #x                 // String lambda$getSupplier$2
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         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: iconst_3
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static java.lang.Integer lambda$static$1();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+         x: ldc           #x                 // String lambda$static$1
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         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: iconst_2
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static java.lang.Integer lambda$new$0();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+         x: ldc           #x                 // String lambda$new$0
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         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: iconst_1
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+         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/TinyFrameworkLambdas
+        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: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
+        x: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
+        x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;           // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
+  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+SourceFile: "TinyFrameworkLambdas.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestKeep
+  x: #x()
+    android.hosttest.annotation.HostSideTestStaticInitializerKeep
+BootstrapMethods:
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.lambda$new$0:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.lambda$getSupplier$2:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.lambda$getSupplier_static$3:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()Ljava/lang/Object;
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.lambda$static$1:()Ljava/lang/Integer;
+      #x ()Ljava/lang/Integer;
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 4, 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/TinyFrameworkMethodCallReplace$ReplaceTo
+         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.TinyFrameworkMethodCallReplace$ReplaceTo();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+         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/TinyFrameworkMethodCallReplace$ReplaceTo;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static void startThread(java.lang.Thread);
+    descriptor: (Ljava/lang/Thread;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+         x: ldc           #x                 // String startThread
+         x: ldc           #x                 // String (Ljava/lang/Thread;)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: iconst_1
+        x: invokevirtual #x                 // Method java/lang/Thread.setDaemon:(Z)V
+        x: aload_0
+        x: invokevirtual #x                 // Method java/lang/Thread.start:()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0 thread   Ljava/lang/Thread;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int add(int, int);
+    descriptor: (II)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+         x: ldc           #x                 // String add
+         x: ldc           #x                 // String (II)I
+         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: iload_0
+        x: iload_1
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0     a   I
+           11       4     1     b   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;           // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 5, attributes: 6
+  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/TinyFrameworkMethodCallReplace
+         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.TinyFrameworkMethodCallReplace();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+         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/TinyFrameworkMethodCallReplace;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static boolean nonStaticMethodCallReplaceTester() throws java.lang.Exception;
+    descriptor: ()Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=2, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+         x: ldc           #x                 // String nonStaticMethodCallReplaceTester
+         x: ldc           #x                 // String ()Z
+         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: new           #x                 // class java/util/concurrent/atomic/AtomicBoolean
+        x: dup
+        x: iconst_0
+        x: invokespecial #x                 // Method java/util/concurrent/atomic/AtomicBoolean."<init>":(Z)V
+        x: astore_0
+        x: new           #x                 // class java/lang/Thread
+        x: dup
+        x: aload_0
+        x: invokedynamic #x,  0             // InvokeDynamic #x:run:(Ljava/util/concurrent/atomic/AtomicBoolean;)Ljava/lang/Runnable;
+        x: invokespecial #x                 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
+        x: astore_1
+        x: aload_1
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.startThread:(Ljava/lang/Thread;)V
+        x: aload_1
+        x: invokevirtual #x                 // Method java/lang/Thread.join:()V
+        x: aload_0
+        x: invokevirtual #x                 // Method java/util/concurrent/atomic/AtomicBoolean.get:()Z
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           20      27     0    ab   Ljava/util/concurrent/atomic/AtomicBoolean;
+           34      13     1    th   Ljava/lang/Thread;
+    Exceptions:
+      throws java.lang.Exception
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int staticMethodCallReplaceTester();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+         x: ldc           #x                 // String staticMethodCallReplaceTester
+         x: ldc           #x                 // String ()I
+         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: iconst_1
+        x: iconst_2
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.add:(II)I
+        x: ireturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
+    descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+         x: ldc           #x                 // String lambda$nonStaticMethodCallReplaceTester$0
+         x: ldc           #x                 // String (Ljava/util/concurrent/atomic/AtomicBoolean;)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: invokestatic  #x                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
+        x: invokevirtual #x                // Method java/lang/Thread.isDaemon:()Z
+        x: invokevirtual #x                // Method java/util/concurrent/atomic/AtomicBoolean.set:(Z)V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      11     0    ab   Ljava/util/concurrent/atomic/AtomicBoolean;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;           // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+BootstrapMethods:
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()V
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.lambda$nonStaticMethodCallReplaceTester$0:(Ljava/util/concurrent/atomic/AtomicBoolean;)V
+      #x ()V
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
+  Compiled from "TinyFrameworkNative.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 15, attributes: 3
+  int value;
+    descriptor: I
+    flags: (0x0000)
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  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/TinyFrameworkNative
+         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.TinyFrameworkNative();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         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/TinyFrameworkNative;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int nativeAddTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeAddTwo
+         x: ldc           #x                 // String (I)I
+         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: iload_0
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public static int nativeAddTwo_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeAddTwo_should_be_like_this
+         x: ldc           #x                 // String (I)I
+         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: iload_0
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0   arg   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static long nativeLongPlus(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeLongPlus
+         x: ldc           #x                 // String (JJ)J
+         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: lload_0
+        x: lload_2
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
+        x: lreturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public static long nativeLongPlus_should_be_like_this(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeLongPlus_should_be_like_this
+         x: ldc           #x                 // String (JJ)J
+         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: lload_0
+        x: lload_2
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
+        x: lreturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       6     0  arg1   J
+           11       6     2  arg2   J
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public void setValue(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String setValue
+         x: ldc           #x                 // String (I)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: iload_1
+        x: putfield      #x                 // Field value:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+           11       6     1     v   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int nativeNonStaticAddToValue(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeNonStaticAddToValue
+         x: ldc           #x                 // String (I)I
+         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: iload_1
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public int nativeNonStaticAddToValue_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeNonStaticAddToValue_should_be_like_this
+         x: ldc           #x                 // String (I)I
+         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: iload_1
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+           11       6     1   arg   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static void nativeStillNotSupported();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeStillNotSupported
+         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: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String Unreachable
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+
+  public static native void nativeStillKeep();
+    descriptor: ()V
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static void nativeStillNotSupported_should_be_like_this();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeStillNotSupported_should_be_like_this
+         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: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+        x: athrow
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static byte nativeBytePlus(byte, byte);
+    descriptor: (BB)B
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeBytePlus
+         x: ldc           #x                 // String (BB)B
+         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: iload_0
+        x: iload_1
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public void notNativeRedirected();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String notNativeRedirected
+         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: invokestatic  #x                // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.notNativeRedirected:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;)V
+        x: return
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+
+  public static void notNativeStaticRedirected();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                // String notNativeStaticRedirected
+         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: invokestatic  #x                // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.notNativeStaticRedirected:()V
+        x: return
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRedirect
+}
+SourceFile: "TinyFrameworkNative.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+  x: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestRedirectionClass(
+      value="TinyFrameworkNative_host"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.class
+  Compiled from "TinyFrameworkNative_host.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 8, attributes: 3
+  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/TinyFrameworkNative_host
+         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.TinyFrameworkNative_host();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         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/TinyFrameworkNative_host;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int nativeAddTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String nativeAddTwo
+         x: ldc           #x                 // String (I)I
+         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: iload_0
+        x: iconst_2
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0   arg   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static long nativeLongPlus(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String nativeLongPlus
+         x: ldc           #x                 // String (JJ)J
+         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: lload_0
+        x: lload_2
+        x: ladd
+        x: lreturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0  arg1   J
+           11       4     2  arg2   J
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int nativeNonStaticAddToValue(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative, int);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String nativeNonStaticAddToValue
+         x: ldc           #x                 // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+         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: getfield      #x                 // Field com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.value:I
+        x: iload_1
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       7     0 source   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+           11       7     1   arg   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static byte nativeBytePlus(byte, byte);
+    descriptor: (BB)B
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String nativeBytePlus
+         x: ldc           #x                 // String (BB)B
+         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: iload_0
+        x: iload_1
+        x: iadd
+        x: i2b
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  arg1   B
+           11       5     1  arg2   B
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static void notNativeRedirected(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String notNativeRedirected
+         x: ldc           #x                 // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;)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: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       1     0 source   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static void notNativeStaticRedirected();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         x: ldc           #x                 // String notNativeStaticRedirected
+         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: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkNative_host.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 4, attributes: 6
+  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/TinyFrameworkNestedClasses$1
+         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
+
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0000)
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)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/TinyFrameworkNestedClasses$1;
+           11       5     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         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: iconst_1
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         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: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+EnclosingMethod: #x.#x                 // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 4, attributes: 6
+  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/TinyFrameworkNestedClasses$2
+         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
+
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2();
+    descriptor: ()V
+    flags: (0x0000)
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         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/TinyFrameworkNestedClasses$2;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         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: iconst_2
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         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: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+EnclosingMethod: #x.#x                 // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 4, attributes: 6
+  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/TinyFrameworkNestedClasses$3
+         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
+
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0000)
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)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/TinyFrameworkNestedClasses$3;
+           11       5     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         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: iconst_3
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         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: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.getSupplier
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 4, attributes: 6
+  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/TinyFrameworkNestedClasses$4
+         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
+
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4();
+    descriptor: ()V
+    flags: (0x0000)
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         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/TinyFrameworkNestedClasses$4;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         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: iconst_4
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         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: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.getSupplier_static
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  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/TinyFrameworkNestedClasses$BaseClass
+         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.TinyFrameworkNestedClasses$BaseClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (I)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: aload_0
+        x: iload_1
+        x: putfield      #x                 // Field value:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass;
+           11      10     1     x   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  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/TinyFrameworkNestedClasses$InnerClass
+         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.TinyFrameworkNestedClasses$InnerClass(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)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: aload_0
+        x: iconst_5
+        x: putfield      #x                 // Field value:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass;
+           11      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    MethodParameters:
+      Name                           Flags
+      <no name>                      final mandated
+}
+InnerClasses:
+  public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 65
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 4, attributes: 6
+  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/TinyFrameworkNestedClasses$StaticNestedClass$1
+         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
+
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1();
+    descriptor: ()V
+    flags: (0x0000)
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         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/TinyFrameworkNestedClasses$StaticNestedClass$1;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Integer;
+         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: bipush        7
+        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         x: ldc           #x                 // String get
+         x: ldc           #x                 // String ()Ljava/lang/Object;
+         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: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass.getSupplier_static
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  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/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+         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.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+         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: aload_0
+        x: bipush        8
+        x: putfield      #x                 // Field value:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      11     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 3, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  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/TinyFrameworkNestedClasses$StaticNestedClass
+         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.TinyFrameworkNestedClasses$StaticNestedClass();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+         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: aload_0
+        x: bipush        6
+        x: putfield      #x                 // Field value:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      11     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+         x: ldc           #x                 // String getSupplier_static
+         x: ldc           #x                 // String ()Ljava/util/function/Supplier;
+         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: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+        x: dup
+        x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1."<init>":()V
+        x: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  public static #x= #x of #x;           // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass extends com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  interfaces: 0, fields: 0, methods: 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/TinyFrameworkNestedClasses$SubClass
+         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.TinyFrameworkNestedClasses$SubClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (I)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: iload_1
+        x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass."<init>":(I)V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass;
+           11       6     1     x   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 4, attributes: 5
+  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+         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: aload_0
+        x: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+        x: dup
+        x: aload_0
+        x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+        x: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      17     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.util.function.Supplier<java.lang.Integer> getSupplier();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+         x: ldc           #x                 // String getSupplier
+         x: ldc           #x                 // String ()Ljava/util/function/Supplier;
+         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: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+        x: dup
+        x: aload_0
+        x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+        x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       9     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+         x: ldc           #x                 // String getSupplier_static
+         x: ldc           #x                 // String ()Ljava/util/function/Supplier;
+         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: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+        x: dup
+        x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4."<init>":()V
+        x: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+         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/TinyFrameworkNestedClasses
+        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: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+        x: dup
+        x: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2."<init>":()V
+        x: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
+        x: return
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  public static #x= #x of #x;            // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;          // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.class
+  Compiled from "TinyFrameworkPackageRedirect.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 3
+  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/TinyFrameworkPackageRedirect
+         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.TinyFrameworkPackageRedirect();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
+         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/TinyFrameworkPackageRedirect;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int foo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
+         x: ldc           #x                 // String foo
+         x: ldc           #x                 // String (I)I
+         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: new           #x                 // class com/supported/UnsupportedClass
+        x: dup
+        x: iload_0
+        x: invokespecial #x                 // Method com/supported/UnsupportedClass."<init>":(I)V
+        x: invokevirtual #x                 // Method com/supported/UnsupportedClass.getValue:()I
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      12     0 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkPackageRedirect.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.class
+  Compiled from "TinyFrameworkRenamedClassCaller.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 3
+  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/TinyFrameworkRenamedClassCaller
+         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.TinyFrameworkRenamedClassCaller();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+         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/TinyFrameworkRenamedClassCaller;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public static int foo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+         x: ldc           #x                 // String foo
+         x: ldc           #x                 // String (I)I
+         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: new           #x                 // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+        x: dup
+        x: iload_0
+        x: invokespecial #x                 // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed."<init>":(I)V
+        x: invokevirtual #x                 // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getValue:()I
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      12     0 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkRenamedClassCaller.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.A
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  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/packagetest/A
+         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
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
+  Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  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/packagetest/sub/A
+         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
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
+  Compiled from "C1.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C1
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/C1
+         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
+}
+SourceFile: "C1.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C2.class
+  Compiled from "C2.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.C2 extends com.android.hoststubgen.test.tinyframework.subclasstest.C1
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C2
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C1
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/C2
+         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
+}
+SourceFile: "C2.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C3.class
+  Compiled from "C3.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.C3 extends com.android.hoststubgen.test.tinyframework.subclasstest.C2
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C3
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C2
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/C3
+         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
+}
+SourceFile: "C3.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/CA.class
+  Compiled from "CA.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.CA
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/CA
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/CA
+         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
+}
+SourceFile: "CA.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/CB.class
+  Compiled from "CB.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.CB
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/CB
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/CB
+         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
+}
+SourceFile: "CB.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1.class
+  Compiled from "Class_C1.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1 extends com.android.hoststubgen.test.tinyframework.subclasstest.C1
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C1
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/Class_C1
+         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
+}
+SourceFile: "Class_C1.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2.class
+  Compiled from "Class_C2.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2 extends com.android.hoststubgen.test.tinyframework.subclasstest.C2
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C2
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/Class_C2
+         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
+}
+SourceFile: "Class_C2.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3.class
+  Compiled from "Class_C3.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3 extends com.android.hoststubgen.test.tinyframework.subclasstest.C3
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C3
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/Class_C3
+         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
+}
+SourceFile: "Class_C3.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1.class
+  Compiled from "Class_I1.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1 implements com.android.hoststubgen.test.tinyframework.subclasstest.I1
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/Class_I1
+         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
+}
+SourceFile: "Class_I1.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA.class
+  Compiled from "Class_I1_IA.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA implements com.android.hoststubgen.test.tinyframework.subclasstest.I1,com.android.hoststubgen.test.tinyframework.subclasstest.IA
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA
+  super_class: #x                         // java/lang/Object
+  interfaces: 2, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/Class_I1_IA
+         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
+}
+SourceFile: "Class_I1_IA.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2.class
+  Compiled from "Class_I2.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2 implements com.android.hoststubgen.test.tinyframework.subclasstest.I2
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/Class_I2
+         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
+}
+SourceFile: "Class_I2.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3.class
+  Compiled from "Class_I3.java"
+public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3 implements com.android.hoststubgen.test.tinyframework.subclasstest.I3
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/Class_I3
+         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
+}
+SourceFile: "Class_I3.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I1.class
+  Compiled from "I1.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.I1
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I1
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/I1
+         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
+}
+SourceFile: "I1.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class
+  Compiled from "I2.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.I2 extends com.android.hoststubgen.test.tinyframework.subclasstest.I1
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I2
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/I2
+         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
+}
+SourceFile: "I2.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class
+  Compiled from "I3.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.I3 extends com.android.hoststubgen.test.tinyframework.subclasstest.I2
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I3
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/I3
+         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
+}
+SourceFile: "I3.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class
+  Compiled from "IA.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.IA
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/IA
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/IA
+         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
+}
+SourceFile: "IA.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class
+  Compiled from "IB.java"
+public interface com.android.hoststubgen.test.tinyframework.subclasstest.IB
+  minor version: 0
+  major version: 65
+  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/IB
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 1, attributes: 2
+  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/subclasstest/IB
+         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
+}
+SourceFile: "IB.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+## Class: com/supported/UnsupportedClass.class
+  Compiled from "UnsupportedClass.java"
+public class com.supported.UnsupportedClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/supported/UnsupportedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 3, attributes: 3
+  private final int mValue;
+    descriptor: I
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/supported/UnsupportedClass
+         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.supported.UnsupportedClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/supported/UnsupportedClass
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (I)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: aload_0
+        x: iload_1
+        x: putfield      #x                 // Field mValue:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0  this   Lcom/supported/UnsupportedClass;
+           11      10     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int getValue();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/supported/UnsupportedClass
+         x: ldc           #x                 // String getValue
+         x: ldc           #x                 // String ()I
+         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: getfield      #x                 // Field mValue:I
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/supported/UnsupportedClass;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "UnsupportedClass.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/unsupported/UnsupportedClass.class
+  Compiled from "UnsupportedClass.java"
+public class com.unsupported.UnsupportedClass
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/unsupported/UnsupportedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 3
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/unsupported/UnsupportedClass
+         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.unsupported.UnsupportedClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/unsupported/UnsupportedClass
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (I)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: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String This class is not supported
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      14     0  this   Lcom/unsupported/UnsupportedClass;
+           11      14     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int getValue();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/unsupported/UnsupportedClass
+         x: ldc           #x                 // String getValue
+         x: ldc           #x                 // String ()I
+         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: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String This class is not supported
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0  this   Lcom/unsupported/UnsupportedClass;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "UnsupportedClass.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.class
+  Compiled from "TinyFrameworkToBeRenamed.java"
+public class rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
+  minor version: 0
+  major version: 65
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 3, attributes: 3
+  private final int mValue;
+    descriptor: I
+    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  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/TinyFrameworkToBeRenamed
+         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 rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String (I)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: aload_0
+        x: iload_1
+        x: putfield      #x                 // Field mValue:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+           11      10     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int getValue();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+         x: ldc           #x                 // String getValue
+         x: ldc           #x                 // String ()I
+         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: getfield      #x                 // Field mValue:I
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+SourceFile: "TinyFrameworkToBeRenamed.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
index 3c138d2..2f35d35 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
@@ -3,11 +3,11 @@
   # field remove	remove # Implicitly remove
   method <init>	()V	            keep
   method addOne	(I)I	        keep
-  method addOneInner	(I)I	keep
+  method addOneInner	keep
   method toBeRemoved	(Ljava/lang/String;)V	remove
   method addTwo	(I)I	        @addTwo_host
   # method addTwo_host	(I)I	# used as a substitute
-  method nativeAddThree	(I)I	@addThree_host
+  method nativeAddThree	        @addThree_host
   # method addThree_host	(I)I	# used as a substitute
   method unsupportedMethod	()Ljava/lang/String;	throw
   method visibleButUsesUnsupportedMethod	()Ljava/lang/String;	keep
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
index 7a7de35..88fa492 100755
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
@@ -20,18 +20,23 @@
 import unittest
 import subprocess
 
-GOLDEN_DIR = 'golden-output'
+GOLDEN_DIRS = [
+    'golden-output',
+    'golden-output.RELEASE_TARGET_JAVA_21',
+]
+
 
 # Run diff.
 def run_diff(file1, file2):
-    command = ['diff', '-u', '--ignore-blank-lines', '--ignore-space-change', file1, file2]
+    command = ['diff', '-u', '--ignore-blank-lines',
+               '--ignore-space-change', file1, file2]
     print(' '.join(command))
-    result = subprocess.run(command, stderr = sys.stdout)
+    result = subprocess.run(command, stderr=sys.stdout)
 
     success = result.returncode == 0
 
     if success:
-        print(f'No diff found.')
+        print('No diff found.')
     else:
         print(f'Fail: {file1} and {file2} are different.')
 
@@ -39,27 +44,42 @@
 
 
 # Check one golden file.
-def check_one_file(filename):
+def check_one_file(golden_dir, filename):
     print(f'= Checking file: {filename}')
-    return run_diff(os.path.join(GOLDEN_DIR, filename), filename)
+    return run_diff(os.path.join(golden_dir, filename), filename)
+
 
 class TestWithGoldenOutput(unittest.TestCase):
 
     # Test to check the generated jar files to the golden output.
+    # Depending on build flags, the golden output may differ in expected ways.
+    # So only expect the files to match one of the possible golden outputs.
     def test_compare_to_golden(self):
-        self.skipTest("test cannot handle multiple images (see b/378470825)")
-        files = os.listdir(GOLDEN_DIR)
-        files.sort()
+        success = False
 
-        print(f"Golden files: {files}")
-        success = True
-
-        for file in files:
-            if not check_one_file(file):
-                success = False
+        for golden_dir in GOLDEN_DIRS:
+            if self.matches_golden(golden_dir):
+                success = True
+                print(f"Test passes for dir: {golden_dir}")
+                break
 
         if not success:
-            self.fail('Some files are different. See stdout log for more details.')
+            self.fail('Some files are different. ' +
+                      'See stdout log for more details.')
+
+    def matches_golden(self, golden_dir):
+        files = os.listdir(golden_dir)
+        files.sort()
+
+        print(f"Golden files for {golden_dir}: {files}")
+        match_success = True
+
+        for file in files:
+            if not check_one_file(golden_dir, file):
+                match_success = False
+
+        return match_success
+
 
 if __name__ == "__main__":
     unittest.main(verbosity=2)
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index a0b989b..ad87cea 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -86,10 +86,17 @@
 }
 
 flag {
-  name: "enable_hardware_shortcut_disables_warning"
-  namespace: "accessibility"
-  description: "When the user purposely enables the hardware shortcut, preemptively disables the first-time warning message."
-  bug: "287065325"
+    name: "enable_hardware_shortcut_disables_warning"
+    namespace: "accessibility"
+    description: "When the user purposely enables the hardware shortcut, preemptively disables the first-time warning message."
+    bug: "287065325"
+}
+
+flag {
+    name: "enable_low_vision_hats"
+    namespace: "accessibility"
+    description: "Use HaTS for low vision feedback."
+    bug: "380346799"
 }
 
 flag {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index d6fc6e4..e1b6c9c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -409,10 +409,6 @@
         final int eventSource = event.getSource();
         final int displayId = event.getDisplayId();
         if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
-            if (!Flags.doNotResetKeyEventState()) {
-                state.reset();
-                clearEventStreamHandler(displayId, eventSource);
-            }
             if (DEBUG) {
                 Slog.d(TAG, "Not processing event " + event);
             }
@@ -1180,18 +1176,8 @@
     }
 
     private boolean anyServiceWantsGenericMotionEvent(MotionEvent event) {
-        if (Flags.alwaysAllowObservingTouchEvents()) {
-            final boolean isTouchEvent = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN);
-            if (isTouchEvent && !canShareGenericTouchEvent()) {
-                return false;
-            }
-            final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
-            return (mCombinedGenericMotionEventSources & eventSourceWithoutClass) != 0;
-        }
-        // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing
-        // touch exploration.
-        if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)
-                && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
+        final boolean isTouchEvent = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN);
+        if (isTouchEvent && !canShareGenericTouchEvent()) {
             return false;
         }
         final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
@@ -1199,21 +1185,8 @@
     }
 
     private boolean anyServiceWantsToObserveMotionEvent(MotionEvent event) {
-        if (Flags.alwaysAllowObservingTouchEvents()) {
-            final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
-            return (mCombinedMotionEventObservedSources & eventSourceWithoutClass) != 0;
-        }
-        // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing
-        // touch exploration.
-        if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)
-                && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
-            return false;
-        }
         final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
-        return (mCombinedGenericMotionEventSources
-                & mCombinedMotionEventObservedSources
-                & eventSourceWithoutClass)
-                != 0;
+        return (mCombinedMotionEventObservedSources & eventSourceWithoutClass) != 0;
     }
 
     private boolean canShareGenericTouchEvent() {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index c210e72..5c1ad74 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1028,8 +1028,7 @@
         intentFilter.addAction(Intent.ACTION_USER_REMOVED);
         intentFilter.addAction(Intent.ACTION_SETTING_RESTORED);
 
-        Handler receiverHandler =
-                Flags.managerAvoidReceiverTimeout() ? BackgroundThread.getHandler() : null;
+        Handler receiverHandler = BackgroundThread.getHandler();
         mContext.registerReceiverAsUser(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
@@ -1071,8 +1070,11 @@
                                         newValue, restoredFromSdk);
                             }
                         }
+                        // Currently in SUW, the user can't see gesture shortcut option as the
+                        // navigation system is set to button navigation. We'll rely on the
+                        // SettingsBackupAgent to restore the settings since we don't
+                        // need to merge an empty gesture target.
                         case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
-                             Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS,
                              Settings.Secure.ACCESSIBILITY_QS_TARGETS,
                              Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE ->
                                 restoreShortcutTargets(newValue,
@@ -2257,10 +2259,6 @@
         if (shortcutType == QUICK_SETTINGS && !android.view.accessibility.Flags.a11yQsShortcut()) {
             return;
         }
-        if (shortcutType == HARDWARE
-                && !android.view.accessibility.Flags.restoreA11yShortcutTargetService()) {
-            return;
-        }
 
         synchronized (mLock) {
             final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
@@ -2269,8 +2267,7 @@
                             mContext, shortcutType, userState.mUserId))
                     : userState.getShortcutTargetsLocked(shortcutType);
 
-            if (Flags.clearDefaultFromA11yShortcutTargetServiceRestore()
-                    && shortcutType == HARDWARE) {
+            if (shortcutType == HARDWARE) {
                 final String defaultService =
                         mContext.getString(R.string.config_defaultAccessibilityService);
                 final ComponentName defaultServiceComponent = TextUtils.isEmpty(defaultService)
@@ -2930,27 +2927,25 @@
         final String builderValue = builder.toString();
         final String settingValue = TextUtils.isEmpty(builderValue)
                 ? defaultEmptyString : builderValue;
-        if (android.view.accessibility.Flags.restoreA11yShortcutTargetService()) {
-            final String currentValue = Settings.Secure.getStringForUser(
-                    mContext.getContentResolver(), settingName, userId);
-            if (Objects.equals(settingValue, currentValue)) {
-                // This logic exists to fix a bug where AccessibilityManagerService was writing
-                // `null` to the ACCESSIBILITY_SHORTCUT_TARGET_SERVICE setting during early boot
-                // during setup, due to a race condition in package scanning making A11yMS think
-                // that the default service was not installed.
-                //
-                // Writing `null` was implicitly causing that Setting to have the default
-                // `DEFAULT_OVERRIDEABLE_BY_RESTORE` property, which was preventing B&R for that
-                // Setting altogether.
-                //
-                // The "quick fix" here is to not write `null` if the existing value is already
-                // `null`. The ideal fix would be use the Settings.Secure#putStringForUser overload
-                // that allows override-by-restore, but the full repercussions of using that here
-                // have not yet been evaluated.
-                // TODO: b/333457719 - Evaluate and fix AccessibilityManagerService's usage of
-                //  "overridable by restore" when writing secure settings.
-                return;
-            }
+        final String currentValue = Settings.Secure.getStringForUser(
+                mContext.getContentResolver(), settingName, userId);
+        if (Objects.equals(settingValue, currentValue)) {
+            // This logic exists to fix a bug where AccessibilityManagerService was writing
+            // `null` to the ACCESSIBILITY_SHORTCUT_TARGET_SERVICE setting during early boot
+            // during setup, due to a race condition in package scanning making A11yMS think
+            // that the default service was not installed.
+            //
+            // Writing `null` was implicitly causing that Setting to have the default
+            // `DEFAULT_OVERRIDEABLE_BY_RESTORE` property, which was preventing B&R for that
+            // Setting altogether.
+            //
+            // The "quick fix" here is to not write `null` if the existing value is already
+            // `null`. The ideal fix would be use the Settings.Secure#putStringForUser overload
+            // that allows override-by-restore, but the full repercussions of using that here
+            // have not yet been evaluated.
+            // TODO: b/333457719 - Evaluate and fix AccessibilityManagerService's usage of
+            //  "overridable by restore" when writing secure settings.
+            return;
         }
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -3797,13 +3792,7 @@
                 || (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
                     && userState.isMagnificationTwoFingerTripleTapEnabledLocked()));
 
-        final boolean createConnectionForCurrentCapability =
-                com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
-                        || (userState.getMagnificationCapabilitiesLocked()
-                                != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
-
-        final boolean connect = (shortcutEnabled && createConnectionForCurrentCapability)
-                || userHasMagnificationServicesLocked(userState);
+        final boolean connect = shortcutEnabled || userHasMagnificationServicesLocked(userState);
 
         getMagnificationConnectionManager().requestConnection(connect);
     }
@@ -4852,8 +4841,7 @@
 
         getMagnificationConnectionManager().setConnection(connection);
 
-        if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
-                && connection == null
+        if (connection == null
                 && mMagnificationController.isFullScreenMagnificationControllerInitialized()) {
             // Since the connection does not exist, the system ui cannot provide the border
             // implementation for fullscreen magnification. So we call reset to deactivate the
@@ -6548,8 +6536,7 @@
 
                 // Only continue setting up the packages if the service has been initialized.
                 // See: b/340927041
-                if (Flags.skipPackageChangeBeforeUserSwitch()
-                        && !mManagerService.isServiceInitializedLocked()) {
+                if (!mManagerService.isServiceInitializedLocked()) {
                     Slog.w(LOG_TAG,
                             "onSomePackagesChanged: service not initialized, skip the callback.");
                     return;
@@ -6657,28 +6644,17 @@
                 }
                 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;
-                    }
+                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;
             }
         }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index f45fa92..5ae0773 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -405,10 +405,9 @@
      * @throws SecurityException if the input method is not in the same package as the service.
      */
     @AccessibilityService.SoftKeyboardController.EnableImeResult
-    int canEnableDisableInputMethod(String imeId, AbstractAccessibilityServiceConnection service)
-            throws SecurityException {
+    int canEnableDisableInputMethod(String imeId, AbstractAccessibilityServiceConnection service,
+            int callingUserId) throws SecurityException {
         final String servicePackageName = service.getComponentName().getPackageName();
-        final int callingUserId = UserHandle.getCallingUserId();
 
         InputMethodInfo inputMethodInfo = null;
         List<InputMethodInfo> inputMethodInfoList =
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 15999d1..a3fe9ec 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -410,9 +410,7 @@
         final @AccessibilityService.SoftKeyboardController.EnableImeResult int checkResult;
         final long identity = Binder.clearCallingIdentity();
         try {
-            synchronized (mLock) {
-                checkResult = mSecurityPolicy.canEnableDisableInputMethod(imeId, this);
-            }
+            checkResult = mSecurityPolicy.canEnableDisableInputMethod(imeId, this, callingUserId);
             if (checkResult != ENABLE_IME_SUCCESS) {
                 return checkResult;
             }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 5cbe0c4..b7fd09f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -433,10 +433,24 @@
             return Collections.emptyList();
         }
 
-        private void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId,
-                IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows) {
-            // TODO(b/322444245): no longer need to get a lock.
+        /**
+         * Called when the windows for accessibility changed.
+         *
+         * @param forceSend             Send the windows for accessibility even if they haven't
+         *                              changed.
+         * @param topFocusedDisplayId   The display Id which has the top focused window.
+         * @param topFocusedWindowToken The window token of top focused window.
+         * @param screenSize            The size of the display that the change happened.
+         * @param accessibilityWindows  The windows for accessibility.
+         */
+        @Override
+        public void onAccessibilityWindowsChanged(boolean forceSend, int topFocusedDisplayId,
+                @NonNull IBinder topFocusedWindowToken, @NonNull Point screenSize,
+                @NonNull List<AccessibilityWindow> accessibilityWindows) {
             synchronized (mLock) {
+                final List<WindowInfo> windows =
+                        createWindowInfoListLocked(screenSize, accessibilityWindows);
+
                 if (DEBUG) {
                     Slogf.i(LOG_TAG, "mDisplayId=%d, topFocusedDisplayId=%d, currentUserId=%d, "
                                     + "visibleBgUsers=%s", mDisplayId, topFocusedDisplayId,
@@ -451,14 +465,15 @@
                         Slogf.i(LOG_TAG, "%d windows changed: %s", windows.size(), windowsInfo);
                     }
                 }
-                if (shouldUpdateWindowsLocked(forceSend, windows)) {
+
+                if (forceSend || shouldUpdateWindowsLocked(windows)) {
                     mTopFocusedDisplayId = topFocusedDisplayId;
                     if (!isProxyed(topFocusedDisplayId)) {
                         mLastNonProxyTopFocusedDisplayId = topFocusedDisplayId;
                     }
                     mTopFocusedWindowToken = topFocusedWindowToken;
                     if (DEBUG) {
-                        Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): updating windows for "
+                        Slogf.d(LOG_TAG, "onAccessibilityWindowsChanged(): updating windows for "
                                         + "display %d and token %s",
                                 topFocusedDisplayId, topFocusedWindowToken);
                     }
@@ -468,37 +483,14 @@
                             windows);
                     // Someone may be waiting for the windows - advertise it.
                     mLock.notifyAll();
-                }
-                else if (DEBUG) {
-                    Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): NOT updating windows for "
+                } else if (DEBUG) {
+                    Slogf.d(LOG_TAG, "onAccessibilityWindowsChanged(): NOT updating windows for "
                                     + "display %d and token %s",
                             topFocusedDisplayId, topFocusedWindowToken);
                 }
             }
         }
 
-        /**
-         * Called when the windows for accessibility changed.
-         *
-         * @param forceSend             Send the windows for accessibility even if they haven't
-         *                              changed.
-         * @param topFocusedDisplayId   The display Id which has the top focused window.
-         * @param topFocusedWindowToken The window token of top focused window.
-         * @param screenSize            The size of the display that the change happened.
-         * @param windows               The windows for accessibility.
-         */
-        @Override
-        public void onAccessibilityWindowsChanged(boolean forceSend, int topFocusedDisplayId,
-                @NonNull IBinder topFocusedWindowToken, @NonNull Point screenSize,
-                @NonNull List<AccessibilityWindow> windows) {
-            synchronized (mLock) {
-                final List<WindowInfo> windowInfoList =
-                        createWindowInfoListLocked(screenSize, windows);
-                onWindowsForAccessibilityChanged(forceSend, topFocusedDisplayId,
-                        topFocusedWindowToken, windowInfoList);
-            }
-        }
-
         private List<WindowInfo> createWindowInfoListLocked(@NonNull Point screenSize,
                 @NonNull List<AccessibilityWindow> visibleWindows) {
             final Set<IBinder> addedWindows = new ArraySet<>();
@@ -650,12 +642,7 @@
             windowInfo.locales = attributes.getLocales();
         }
 
-        private boolean shouldUpdateWindowsLocked(boolean forceSend,
-                @NonNull List<WindowInfo> windows) {
-            if (forceSend) {
-                return true;
-            }
-
+        private boolean shouldUpdateWindowsLocked(@NonNull List<WindowInfo> windows) {
             final int windowCount = windows.size();
             if (VERBOSE) {
                 Slogf.v(LOG_TAG,
@@ -845,20 +832,12 @@
                         != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
             }
 
-            boolean hasWindowIgnore = false;
             if (windowCount > 0) {
-                for (int i = 0; i < windowCount; i++) {
-                    final WindowInfo windowInfo = windows.get(i);
-                    final AccessibilityWindowInfo window;
-                    if (mTrackingWindows) {
-                        window = populateReportedWindowLocked(userId, windowInfo, oldWindowsById);
-                        if (window == null) {
-                            hasWindowIgnore = true;
-                        }
-                    } else {
-                        window = null;
-                    }
-                    if (window != null) {
+                if (mTrackingWindows) {
+                    for (int i = 0; i < windowCount; i++) {
+                        final WindowInfo windowInfo = windows.get(i);
+                        final AccessibilityWindowInfo window =
+                                populateReportedWindowLocked(userId, windowInfo, oldWindowsById);
 
                         // Flip layers in list to be consistent with AccessibilityService#getWindows
                         window.setLayer(windowCount - 1 - window.getLayer());
@@ -883,13 +862,6 @@
                     }
                 }
                 final int accessibilityWindowCount = mWindows.size();
-                // Re-order the window layer of all windows in the windows list because there's
-                // window not been added into the windows list.
-                if (hasWindowIgnore) {
-                    for (int i = 0; i < accessibilityWindowCount; i++) {
-                        mWindows.get(i).setLayer(accessibilityWindowCount - 1 - i);
-                    }
-                }
                 if (isTopFocusedDisplay) {
                     if (mTouchInteractionInProgress && activeWindowGone) {
                         mActiveWindowId = mTopFocusedWindowId;
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index da11a76..f855145 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -183,14 +183,12 @@
 
         synchronized (mLock) {
             mProxyA11yServiceConnections.put(displayId, connection);
-            if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
-                if (mAppsOnVirtualDeviceListener == null) {
-                    mAppsOnVirtualDeviceListener = allRunningUids ->
-                            notifyProxyOfRunningAppsChange(allRunningUids);
-                    final VirtualDeviceManagerInternal localVdm = getLocalVdm();
-                    if (localVdm != null) {
-                        localVdm.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
-                    }
+            if (mAppsOnVirtualDeviceListener == null) {
+                mAppsOnVirtualDeviceListener = allRunningUids ->
+                        notifyProxyOfRunningAppsChange(allRunningUids);
+                final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+                if (localVdm != null) {
+                    localVdm.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
                 }
             }
             if (mProxyA11yServiceConnections.size() == 1) {
@@ -331,14 +329,12 @@
         // device.
         if (!isProxyedDeviceId(deviceId)) {
             synchronized (mLock) {
-                if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
-                    if (mProxyA11yServiceConnections.size() == 0) {
-                        final VirtualDeviceManagerInternal localVdm = getLocalVdm();
-                        if (localVdm != null && mAppsOnVirtualDeviceListener != null) {
-                            localVdm.unregisterAppsOnVirtualDeviceListener(
-                                    mAppsOnVirtualDeviceListener);
-                            mAppsOnVirtualDeviceListener = null;
-                        }
+                if (mProxyA11yServiceConnections.size() == 0) {
+                    final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+                    if (localVdm != null && mAppsOnVirtualDeviceListener != null) {
+                        localVdm.unregisterAppsOnVirtualDeviceListener(
+                                mAppsOnVirtualDeviceListener);
+                        mAppsOnVirtualDeviceListener = null;
                     }
                 }
                 mSystemSupport.removeDeviceIdLocked(deviceId);
@@ -671,8 +667,7 @@
                     + getLastSentStateLocked(deviceId));
             Slog.v(LOG_TAG, "force update: " + forceUpdate);
         }
-        if ((getLastSentStateLocked(deviceId)) != proxyState
-                || (Flags.proxyUseAppsOnVirtualDeviceListener() && forceUpdate)) {
+        if ((getLastSentStateLocked(deviceId)) != proxyState || forceUpdate) {
             setLastStateLocked(deviceId, proxyState);
             mMainHandler.post(() -> {
                 synchronized (mLock) {
@@ -873,33 +868,22 @@
         for (int i = 0; i < clients.getRegisteredCallbackCount(); i++) {
             final AccessibilityManagerService.Client client =
                     ((AccessibilityManagerService.Client) clients.getRegisteredCallbackCookie(i));
-            if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
-                if (deviceId == DEVICE_ID_DEFAULT || deviceId == DEVICE_ID_INVALID) {
-                    continue;
+            if (deviceId == DEVICE_ID_DEFAULT || deviceId == DEVICE_ID_INVALID) {
+                continue;
+            }
+            boolean uidBelongsToDevice =
+                    localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId);
+            if (client.mDeviceId != deviceId && uidBelongsToDevice) {
+                if (DEBUG) {
+                    Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
+                            + Arrays.toString(client.mPackageNames));
                 }
-                boolean uidBelongsToDevice =
-                        localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId);
-                if (client.mDeviceId != deviceId && uidBelongsToDevice) {
-                    if (DEBUG) {
-                        Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
-                                + Arrays.toString(client.mPackageNames));
-                    }
-                    client.mDeviceId = deviceId;
-                } else if (client.mDeviceId == deviceId && !uidBelongsToDevice) {
-                    client.mDeviceId = DEVICE_ID_DEFAULT;
-                    if (DEBUG) {
-                        Slog.v(LOG_TAG, "Packages moved to the default device from device id "
-                                + deviceId + " are " + Arrays.toString(client.mPackageNames));
-                    }
-                }
-            } else {
-                if (deviceId != DEVICE_ID_DEFAULT && deviceId != DEVICE_ID_INVALID
-                    && localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId)) {
-                    if (DEBUG) {
-                        Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
-                                + Arrays.toString(client.mPackageNames));
-                    }
-                    client.mDeviceId = deviceId;
+                client.mDeviceId = deviceId;
+            } else if (client.mDeviceId == deviceId && !uidBelongsToDevice) {
+                client.mDeviceId = DEVICE_ID_DEFAULT;
+                if (DEBUG) {
+                    Slog.v(LOG_TAG, "Packages moved to the default device from device id "
+                            + deviceId + " are " + Arrays.toString(client.mPackageNames));
                 }
             }
         }
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 0ed239e..0cbbf6d 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -240,10 +240,7 @@
     }
 
     private void clear(MotionEvent event, int policyFlags) {
-        if (mState.isTouchExploring() || Flags.sendHoverEventsBasedOnEventStream()) {
-            // If a touch exploration gesture is in progress send events for its end.
-            sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
-        }
+        sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
         mDraggingPointerId = INVALID_POINTER_ID;
         // Send exit to any pointers that we have delivered as part of delegating or dragging.
         mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
@@ -562,10 +559,7 @@
         // clear any hover events that might have been queued and never sent.
         mSendHoverEnterAndMoveDelayed.clear();
         mSendHoverExitDelayed.cancel();
-        // If a touch exploration gesture is in progress send events for its end.
-        if (mState.isTouchExploring() || Flags.sendHoverEventsBasedOnEventStream()) {
-            sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
-        }
+        sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
         if (mState.isClear()) {
             if (!mSendHoverEnterAndMoveDelayed.isPending()) {
                 // Queue a delayed transition to STATE_TOUCH_EXPLORING.
@@ -1599,9 +1593,7 @@
             if (mEvents.size() == 0) {
                 return;
             }
-            if (Flags.sendHoverEventsBasedOnEventStream()) {
-                sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags);
-            }
+            sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags);
             // Send an accessibility event to announce the touch exploration start.
             mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
             if (isSendMotionEventsEnabled()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index d3d80e1..11b8ccb 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -699,10 +699,9 @@
             if (!mRegistered) {
                 return false;
             }
-            // If the border implementation is on system ui side but the connection is not
+            // The border implementation is on system ui side but the connection is not
             // established, the fullscreen magnification should not work.
-            if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
-                    && !mMagnificationConnectionStateSupplier.get()) {
+            if (!mMagnificationConnectionStateSupplier.get()) {
                 return false;
             }
             if (DEBUG) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 51c4305..058b2be 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -51,7 +51,6 @@
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.wm.WindowManagerInternal;
-import com.android.window.flags.Flags;
 
 import java.util.concurrent.Executor;
 
@@ -634,10 +633,8 @@
 
     @Override
     public void onFullScreenMagnificationActivationState(int displayId, boolean activated) {
-        if (Flags.alwaysDrawMagnificationFullscreenBorder()) {
-            getMagnificationConnectionManager()
-                    .onFullscreenMagnificationActivationChanged(displayId, activated);
-        }
+        getMagnificationConnectionManager()
+                .onFullscreenMagnificationActivationChanged(displayId, activated);
 
         if (activated) {
             synchronized (mLock) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
index 6b48d2b..a4568aa 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
@@ -31,7 +31,6 @@
 import android.view.ViewConfiguration;
 
 import com.android.internal.R;
-import com.android.server.accessibility.Flags;
 
 /**
  * Handles the behavior while receiving scaling and panning gestures if it's enabled.
@@ -73,13 +72,9 @@
         mMaxScale = maxScale;
         mMinScale = minScale;
         mBlockScroll = blockScroll;
-        if (Flags.pinchZoomZeroMinSpan()) {
-            mScaleGestureDetector = new ScaleGestureDetector(context,
-                    ViewConfiguration.get(context).getScaledTouchSlop() * 2,
-                    /* minSpan= */ 0, Handler.getMain(), this);
-        } else {
-            mScaleGestureDetector = new ScaleGestureDetector(context, this, Handler.getMain());
-        }
+        mScaleGestureDetector = new ScaleGestureDetector(context,
+                ViewConfiguration.get(context).getScaledTouchSlop() * 2,
+                /* minSpan= */ 0, Handler.getMain(), this);
         mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain());
         mScaleGestureDetector.setQuickScaleEnabled(false);
         mMagnificationDelegate = magnificationDelegate;
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 5d2ef77..5e1b147 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -23,6 +23,16 @@
 }
 
 flag {
+  name: "relayout_fix"
+  namespace: "autofill"
+  description: "Fixing relayout issue. Guarding enabling device config flags"
+  bug: "381226145"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "include_invisible_view_group_in_assist_structure"
   namespace: "autofill"
   description: "Mitigation for autofill providers miscalculating view visibility"
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index b3fe5f2..b78d103 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -2,10 +2,18 @@
 container: "system"
 
 flag {
+  name: "autofill_session_destroyed"
+  namespace: "autofill"
+  description: "Guards against new metrics definitions introduced in W"
+  bug: "342676602"
+}
+
+flag {
   name: "autofill_w_metrics"
   namespace: "autofill"
   description: "Guards against new metrics definitions introduced in W"
   bug: "342676602"
+  is_exported: true
 }
 
 flag {
@@ -31,9 +39,24 @@
 }
 
 flag {
+    name: "fill_dialog_improvements_impl"
+    is_exported: true
+    namespace: "autofill"
+    description: "Improvements for Fill Dialog for non-api changes"
+    bug: "336223371"
+}
+
+flag {
     name: "fill_dialog_improvements"
     is_exported: true
     namespace: "autofill"
     description: "Improvements for Fill Dialog, including deprecation of pre-trigger API's"
     bug: "336223371"
 }
+
+flag {
+  name: "add_last_focused_id_to_fill_event_history"
+  namespace: "autofill"
+  description: "Adds focused id to each event that's part of the fill event history"
+  bug: "334141398"
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index b52c6505..5cf96bf 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -61,6 +61,7 @@
 import android.service.autofill.FillEventHistory.Event;
 import android.service.autofill.FillEventHistory.Event.NoSaveReason;
 import android.service.autofill.FillResponse;
+import android.service.autofill.Flags;
 import android.service.autofill.IAutoFillService;
 import android.service.autofill.InlineSuggestionRenderService;
 import android.service.autofill.SaveInfo;
@@ -100,6 +101,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Random;
+
 /**
  * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the
  * app's {@link IAutoFillService} implementation.
@@ -748,6 +750,22 @@
     @GuardedBy("mLock")
     void removeSessionLocked(int sessionId) {
         mSessions.remove(sessionId);
+        if (Flags.autofillSessionDestroyed()) {
+            if (sVerbose) {
+                Slog.v(
+                        TAG,
+                        "removeSessionLocked(): removed " + sessionId);
+            }
+            RemoteFillService remoteService =
+                    new RemoteFillService(
+                            getContext(),
+                            mInfo.getServiceInfo().getComponentName(),
+                            mUserId,
+                            /* callbacks= */ null,
+                            mMaster.isInstantServiceAllowed(),
+                            /* credentialAutofillService= */ null);
+            remoteService.onSessionDestroyed(null);
+        }
     }
 
     /**
@@ -900,13 +918,13 @@
      * Updates the last fill selection when an authentication was selected.
      */
     void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState,
-            int uiType) {
+            int uiType, @Nullable AutofillId focusedId) {
         synchronized (mLock) {
             if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null,
                                 null, null, null, null, null, null,
-                                NO_SAVE_UI_REASON_NONE, uiType));
+                                NO_SAVE_UI_REASON_NONE, uiType, focusedId));
             }
         }
     }
@@ -915,13 +933,13 @@
      * Updates the last fill selection when an dataset authentication was selected.
      */
     void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId,
-            @Nullable Bundle clientState, int uiType) {
+            @Nullable Bundle clientState, int uiType, @Nullable AutofillId focusedId) {
         synchronized (mLock) {
             if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
                                 clientState, null, null, null, null, null, null, null, null,
-                                NO_SAVE_UI_REASON_NONE, uiType));
+                                NO_SAVE_UI_REASON_NONE, uiType, focusedId));
             }
         }
     }
@@ -933,7 +951,7 @@
         synchronized (mLock) {
             if (isValidEventLocked("logSaveShown()", sessionId)) {
                 mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
-                        null, null, null, null, null, null, null));
+                        null, null, null, null, null, null, null, /* focusedId= */ null));
             }
         }
     }
@@ -942,13 +960,13 @@
      * Updates the last fill response when a dataset was selected.
      */
     void logDatasetSelected(@Nullable String selectedDataset, int sessionId,
-            @Nullable Bundle clientState,  int uiType) {
+            @Nullable Bundle clientState,  int uiType, @Nullable AutofillId focusedId) {
         synchronized (mLock) {
             if (isValidEventLocked("logDatasetSelected()", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
                                 null, null, null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
-                                uiType));
+                                uiType, focusedId));
             }
         }
     }
@@ -956,13 +974,14 @@
     /**
      * Updates the last fill response when a dataset is shown.
      */
-    void logDatasetShown(int sessionId, @Nullable Bundle clientState, int uiType) {
+    void logDatasetShown(int sessionId, @Nullable Bundle clientState, int uiType,
+            @Nullable AutofillId focusedId) {
         synchronized (mLock) {
             if (isValidEventLocked("logDatasetShown", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
                                 null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
-                                uiType));
+                                uiType, focusedId));
             }
         }
     }
@@ -970,7 +989,8 @@
     /**
      * Updates the last fill response when a view was entered.
      */
-    void logViewEntered(int sessionId, @Nullable Bundle clientState) {
+    void logViewEntered(int sessionId, @Nullable Bundle clientState,
+            @Nullable AutofillId focusedId) {
         synchronized (mLock) {
             if (!isValidEventLocked("logViewEntered", sessionId)) {
                 return;
@@ -988,7 +1008,7 @@
 
             mEventHistory.addEvent(
                     new Event(Event.TYPE_VIEW_REQUESTED_AUTOFILL, null, clientState, null,
-                            null, null, null, null, null, null, null));
+                            null, null, null, null, null, null, null, focusedId));
         }
     }
 
@@ -1001,7 +1021,8 @@
             }
             mAugmentedAutofillEventHistory.addEvent(
                     new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
-                            clientState, null, null, null, null, null, null, null, null));
+                            clientState, null, null, null, null, null, null, null, null,
+                            /* focusedId= */ null));
         }
     }
 
@@ -1014,7 +1035,7 @@
             }
             mAugmentedAutofillEventHistory.addEvent(
                     new Event(Event.TYPE_DATASET_SELECTED, suggestionId, clientState, null, null,
-                            null, null, null, null, null, null));
+                            null, null, null, null, null, null, /* focusedId= */ null));
         }
     }
 
@@ -1029,7 +1050,7 @@
             mAugmentedAutofillEventHistory.addEvent(
                     new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
                             null, null, null, null, null, NO_SAVE_UI_REASON_NONE,
-                            UI_TYPE_INLINE));
+                            UI_TYPE_INLINE, /* focusedId= */ null));
 
         }
     }
@@ -1113,7 +1134,8 @@
                     clientState, selectedDatasets, ignoredDatasets,
                     changedFieldIds, changedDatasetIds,
                     manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                    detectedFieldsIds, detectedFieldClassifications, saveDialogNotShowReason));
+                    detectedFieldsIds, detectedFieldClassifications, saveDialogNotShowReason,
+                    /* focusedId= */ null));
         }
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 07f5dcc..f1e8884 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -34,6 +34,7 @@
 import android.service.autofill.AutofillService;
 import android.service.autofill.ConvertCredentialRequest;
 import android.service.autofill.ConvertCredentialResponse;
+import android.service.autofill.FillEventHistory;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
 import android.service.autofill.IAutoFillService;
@@ -497,6 +498,14 @@
                 }));
     }
 
+    public void onSessionDestroyed(@Nullable FillEventHistory history) {
+        boolean success = run(service -> {
+            service.onSessionDestroyed(history);
+        });
+
+        if (sVerbose) Slog.v(TAG, "called onSessionDestroyed(): " + success);
+    }
+
     void onSavedPasswordCountRequest(IResultReceiver receiver) {
         run(service -> service.onSavedPasswordCountRequest(receiver));
     }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 8f12b1d..6b227d7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1871,7 +1871,7 @@
 
             if (mLogViewEntered) {
                 mLogViewEntered = false;
-                mService.logViewEntered(id, null);
+                mService.logViewEntered(id, null, mCurrentViewId);
             }
         }
 
@@ -2775,9 +2775,9 @@
                 forceRemoveFromServiceLocked();
                 return;
             }
+            mService.setAuthenticationSelected(id, mClientState, uiType, mCurrentViewId);
         }
 
-        mService.setAuthenticationSelected(id, mClientState, uiType);
 
         final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex);
         mHandler.sendMessage(
@@ -2850,7 +2850,7 @@
                 if (!mLoggedInlineDatasetShown) {
                     // Chip inflation already logged, do not log again.
                     // This is needed because every chip inflation will call this.
-                    mService.logDatasetShown(this.id, mClientState, uiType);
+                    mService.logDatasetShown(this.id, mClientState, uiType, mCurrentViewId);
                     Slog.d(TAG, "onShown(): " + uiType + ", " + numDatasetsShown);
                 }
                 mLoggedInlineDatasetShown = true;
@@ -2858,7 +2858,7 @@
                 mPresentationStatsEventLogger.logWhenDatasetShown(numDatasetsShown);
                 // Explicitly sets maybeSetSuggestionPresentedTimestampMs
                 mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs();
-                mService.logDatasetShown(this.id, mClientState, uiType);
+                mService.logDatasetShown(this.id, mClientState, uiType, mCurrentViewId);
                 Slog.d(TAG, "onShown(): " + uiType + ", " + numDatasetsShown);
             }
         }
@@ -5139,7 +5139,7 @@
                         // so this calling logViewEntered will be a nop.
                         // Calling logViewEntered() twice will only log it once
                         // TODO(271181979): this is broken for multiple partitions
-                        mService.logViewEntered(this.id, null);
+                        mService.logViewEntered(this.id, null, mCurrentViewId);
                     }
 
                     // If this is the first time view is entered for inline, the last
@@ -6657,7 +6657,8 @@
             // Autofill it directly...
             if (dataset.getAuthentication() == null) {
                 if (generateEvent) {
-                    mService.logDatasetSelected(dataset.getId(), id, mClientState, uiType);
+                    mService.logDatasetSelected(dataset.getId(), id, mClientState, uiType,
+                            mCurrentViewId);
                 }
                 if (mCurrentViewId != null) {
                     mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
@@ -6667,7 +6668,8 @@
             }
 
             // ...or handle authentication.
-            mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState, uiType);
+            mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState, uiType,
+                        mCurrentViewId);
             mPresentationStatsEventLogger.maybeSetAuthenticationType(
                     AUTHENTICATION_TYPE_DATASET_AUTHENTICATION);
             // does not matter the value of isPrimary because null response won't be overridden.
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index fcb7934..b4adad2 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -68,4 +68,20 @@
             "B&R operations in certain cases."
     bug: "376661510"
     is_fixed_read_only: true
+    is_exported: true
+}
+
+flag {
+    name: "enable_read_all_external_storage_files"
+    is_exported: true
+    namespace: "onboarding"
+    description: "Enables read all external storage files"
+    bug: "376598575"
+}
+flag {
+    name: "enable_metrics_settings_backup_agents"
+    namespace: "onboarding"
+    description: "Enable SettingsBackupAgent to collect B&R agent metrics."
+    bug: "379861078"
+    is_fixed_read_only: true
 }
diff --git a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
index 5e4bab15..6ced096 100644
--- a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
+++ b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
@@ -41,6 +41,7 @@
 import android.util.ArraySet;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 import com.android.server.backup.internal.LifecycleOperationStorage;
@@ -71,8 +72,10 @@
     // Activity Manager; use this lock object to signal when a requested binding has
     // completed.
     private final Object mAgentConnectLock = new Object();
-    private IBackupAgent mConnectedAgent;
-    private volatile boolean mConnecting;
+    @GuardedBy("mAgentConnectLock")
+    @Nullable
+    private BackupAgentConnection mCurrentConnection;
+
     private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>();
     private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>();
 
@@ -96,88 +99,173 @@
         mUserIdMsg = "[UserID:" + userId + "] ";
     }
 
+    private static final class BackupAgentConnection {
+        public final ApplicationInfo appInfo;
+        public final int backupMode;
+        public final boolean inRestrictedMode;
+        public IBackupAgent backupAgent;
+        public boolean connecting = true; // Assume we are trying to connect on creation.
+
+        private BackupAgentConnection(ApplicationInfo appInfo, int backupMode,
+                boolean inRestrictedMode) {
+            this.appInfo = appInfo;
+            this.backupMode = backupMode;
+            this.inRestrictedMode = inRestrictedMode;
+        }
+    }
+
     /**
-     * Fires off a backup agent, blocking until it attaches (and ActivityManager will call
+     * Fires off a backup agent, blocking until it attaches (i.e. ActivityManager calls
      * {@link #agentConnected(String, IBinder)}) or until this operation times out.
      *
-     * @param mode a {@code BACKUP_MODE} from {@link android.app.ApplicationThreadConstants}.
+     * @param backupMode a {@code BACKUP_MODE} from {@link android.app.ApplicationThreadConstants}.
      */
     @Nullable
-    public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode,
+    public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int backupMode,
             @BackupAnnotations.BackupDestination int backupDestination) {
-        IBackupAgent agent = null;
+        if (app == null) {
+            Slog.w(TAG, mUserIdMsg + "bindToAgentSynchronous for null app");
+            return null;
+        }
+
         synchronized (mAgentConnectLock) {
-            mConnecting = true;
-            mConnectedAgent = null;
-            boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode,
+            boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(backupMode,
                     app.packageName);
+            if (mCurrentConnection != null) {
+                Slog.e(TAG, mUserIdMsg + "binding to new agent before unbinding from old one: "
+                        + mCurrentConnection.appInfo.packageName);
+            }
+            mCurrentConnection = new BackupAgentConnection(app, backupMode, useRestrictedMode);
+
+            // bindBackupAgent() is an async API. It will kick off the app's process and call
+            // agentConnected() when it receives the agent from the app.
+            boolean startedBindSuccessfully = false;
             try {
-                if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId,
-                        backupDestination, useRestrictedMode)) {
-                    Slog.d(TAG, mUserIdMsg + "awaiting agent for " + app);
-
-                    // success; wait for the agent to arrive
-                    // only wait 10 seconds for the bind to happen
-                    long timeoutMark = System.currentTimeMillis() + 10 * 1000;
-                    while (mConnecting && mConnectedAgent == null && (System.currentTimeMillis()
-                            < timeoutMark)) {
-                        try {
-                            mAgentConnectLock.wait(5000);
-                        } catch (InterruptedException e) {
-                            // just bail
-                            Slog.w(TAG, mUserIdMsg + "Interrupted: " + e);
-                            mConnecting = false;
-                            mConnectedAgent = null;
-                        }
-                    }
-
-                    // if we timed out with no connect, abort and move on
-                    if (mConnecting) {
-                        Slog.w(TAG, mUserIdMsg + "Timeout waiting for agent " + app);
-                        mConnectedAgent = null;
-                    }
-                    Slog.i(TAG, mUserIdMsg + "got agent " + mConnectedAgent);
-                    agent = mConnectedAgent;
-                }
+                startedBindSuccessfully = mActivityManager.bindBackupAgent(app.packageName,
+                        backupMode, mUserId, backupDestination, useRestrictedMode);
             } catch (RemoteException e) {
                 // can't happen - ActivityManager is local
             }
-        }
-        if (agent == null) {
+
+            if (!startedBindSuccessfully) {
+                Slog.w(TAG, mUserIdMsg + "bind request failed for " + app.packageName);
+                mCurrentConnection = null;
+            } else {
+                Slog.d(TAG, mUserIdMsg + "awaiting agent for " + app.packageName);
+
+                // Wait 10 seconds for the agent and then time out if we still haven't bound to it.
+                long timeoutMark = System.currentTimeMillis() + 10 * 1000;
+                while (mCurrentConnection != null && mCurrentConnection.connecting && (
+                        System.currentTimeMillis() < timeoutMark)) {
+                    try {
+                        mAgentConnectLock.wait(5000);
+                    } catch (InterruptedException e) {
+                        Slog.w(TAG, mUserIdMsg + "Interrupted: " + e);
+                        mCurrentConnection = null;
+                    }
+                }
+            }
+
+            if (mCurrentConnection != null) {
+                if (!mCurrentConnection.connecting) {
+                    return mCurrentConnection.backupAgent;
+                }
+                // If we are still connecting, we've timed out.
+                Slog.w(TAG, mUserIdMsg + "Timeout waiting for agent " + app);
+                mCurrentConnection = null;
+            }
+
             mActivityManagerInternal.clearPendingBackup(mUserId);
+            return null;
         }
-        return agent;
     }
 
     /**
      * Tell the ActivityManager that we are done with the {@link IBackupAgent} of this {@code app}.
      * It will tell the app to destroy the agent.
+     *
+     * <p>If {@code allowKill} is set, this will kill the app's process if the app is in restricted
+     * mode or if it was started for restore and specified {@code android:killAfterRestore} in its
+     * manifest.
+     *
+     * @see #shouldUseRestrictedBackupModeForPackage(int, String)
      */
-    public void unbindAgent(ApplicationInfo app) {
-        try {
-            mActivityManager.unbindBackupAgent(app);
-        } catch (RemoteException e) {
-            // Can't happen - activity manager is local
+    public void unbindAgent(ApplicationInfo app, boolean allowKill) {
+        if (app == null) {
+            Slog.w(TAG, mUserIdMsg + "unbindAgent for null app");
+            return;
         }
+
+        synchronized (mAgentConnectLock) {
+            // Even if we weren't expecting to be bound to this agent, we should still call
+            // ActivityManager just in case. It will ignore the call if it also wasn't expecting it.
+            try {
+                mActivityManager.unbindBackupAgent(app);
+
+                // Evaluate this before potentially setting mCurrentConnection = null.
+                boolean willKill = allowKill && shouldKillAppOnUnbind(app);
+
+                if (mCurrentConnection == null) {
+                    Slog.w(TAG, mUserIdMsg + "unbindAgent but no current connection");
+                } else if (!mCurrentConnection.appInfo.packageName.equals(app.packageName)) {
+                    Slog.w(TAG,
+                            mUserIdMsg + "unbindAgent for unexpected package: " + app.packageName
+                                    + " expected: " + mCurrentConnection.appInfo.packageName);
+                } else {
+                    mCurrentConnection = null;
+                }
+
+                if (willKill) {
+                    Slog.i(TAG, mUserIdMsg + "Killing agent host process");
+                    mActivityManager.killApplicationProcess(app.processName, app.uid);
+                }
+            } catch (RemoteException e) {
+                // Can't happen - activity manager is local
+            }
+        }
+    }
+
+    @GuardedBy("mAgentConnectLock")
+    private boolean shouldKillAppOnUnbind(ApplicationInfo app) {
+        // We don't ask system UID processes to be killed.
+        if (UserHandle.isCore(app.uid)) {
+            return false;
+        }
+
+        // If the app is in restricted mode or if we're not sure if it is because our internal
+        // state is messed up, we need to avoid it being stuck in it.
+        if (mCurrentConnection == null || mCurrentConnection.inRestrictedMode) {
+            return true;
+        }
+
+        // App was doing restore and asked to be killed afterwards.
+        return isBackupModeRestore(mCurrentConnection.backupMode)
+                && (app.flags & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0;
     }
 
     /**
      * Callback: a requested backup agent has been instantiated. This should only be called from
-     * the
-     * {@link ActivityManager} when it's telling us that an agent is ready after a call to
+     * the {@link ActivityManager} when it's telling us that an agent is ready after a call to
      * {@link #bindToAgentSynchronous(ApplicationInfo, int, int)}.
      */
     public void agentConnected(String packageName, IBinder agentBinder) {
         synchronized (mAgentConnectLock) {
-            if (getCallingUid() == android.os.Process.SYSTEM_UID) {
-                Slog.d(TAG,
-                        mUserIdMsg + "agentConnected pkg=" + packageName + " agent=" + agentBinder);
-                mConnectedAgent = IBackupAgent.Stub.asInterface(agentBinder);
-                mConnecting = false;
-            } else {
+            if (getCallingUid() != Process.SYSTEM_UID) {
                 Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid()
                         + " claiming agent connected");
+                return;
             }
+
+            Slog.d(TAG, mUserIdMsg + "agentConnected pkg=" + packageName + " agent=" + agentBinder);
+            if (mCurrentConnection == null) {
+                Slog.w(TAG, mUserIdMsg + "was not expecting connection");
+            } else if (!mCurrentConnection.appInfo.packageName.equals(packageName)) {
+                Slog.w(TAG, mUserIdMsg + "got agent for unexpected package=" + packageName);
+            } else {
+                mCurrentConnection.backupAgent = IBackupAgent.Stub.asInterface(agentBinder);
+                mCurrentConnection.connecting = false;
+            }
+
             mAgentConnectLock.notifyAll();
         }
     }
@@ -189,16 +277,22 @@
      */
     public void agentDisconnected(String packageName) {
         synchronized (mAgentConnectLock) {
-            if (getCallingUid() == Process.SYSTEM_UID) {
-                mConnectedAgent = null;
-                mConnecting = false;
-            } else {
+            if (getCallingUid() != Process.SYSTEM_UID) {
                 Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid()
                         + " claiming agent disconnected");
+                return;
             }
+
             Slog.w(TAG, mUserIdMsg + "agentDisconnected: the backup agent for " + packageName
                     + " died: cancel current operations");
 
+            // Only abort the current connection if the agent we were expecting or already
+            // connected to has disconnected.
+            if (mCurrentConnection != null && mCurrentConnection.appInfo.packageName.equals(
+                    packageName)) {
+                mCurrentConnection = null;
+            }
+
             // Offload operation cancellation off the main thread as the cancellation callbacks
             // might call out to BackupTransport. Other operations started on the same package
             // before the cancellation callback has executed will also be cancelled by the callback.
@@ -261,16 +355,16 @@
      */
     private boolean shouldUseRestrictedBackupModeForPackage(
             @BackupAnnotations.OperationType int mode, String packageName) {
-        if (!Flags.enableRestrictedModeChanges()) {
-            return true;
-        }
-
         // Key/Value apps are never put in restricted mode.
         if (mode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
                 || mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) {
             return false;
         }
 
+        if (!Flags.enableRestrictedModeChanges()) {
+            return true;
+        }
+
         try {
             PackageManager.Property property = mPackageManager.getPropertyAsUser(
                     PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE,
@@ -306,6 +400,11 @@
         return true;
     }
 
+    private static boolean isBackupModeRestore(int backupMode) {
+        return backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE
+                || backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL;
+    }
+
     @VisibleForTesting
     Thread getThreadForCancellation(Runnable operation) {
         return new Thread(operation, /* operationName */ "agent-disconnected");
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index 136bacd..b343ec8 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -300,7 +300,8 @@
     }
 
     private void cleanup() {
-        mBackupManagerService.tearDownAgentAndKill(mCurrentPackage.applicationInfo);
+        mBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+                mCurrentPackage.applicationInfo, /* allowKill= */ true);
         mBlankStateName.delete();
         mNewStateName.delete();
         mBackupDataName.delete();
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index e085f6e..3025e2e 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -314,8 +314,6 @@
 
     private static final String SERIAL_ID_FILE = "serial_id";
 
-    private static final String SKIP_USER_FACING_PACKAGES = "backup_skip_user_facing_packages";
-
     private final @UserIdInt int mUserId;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
     private final TransportManager mTransportManager;
@@ -1998,39 +1996,6 @@
         return mOperationStorage.isBackupOperationInProgress();
     }
 
-    /** Unbind the backup agent and kill the app if it's a non-system app. */
-    public void tearDownAgentAndKill(ApplicationInfo app) {
-        if (app == null) {
-            // Null means the system package, so just quietly move on.  :)
-            return;
-        }
-
-        try {
-            // unbind and tidy up even on timeout or failure, just in case
-            mActivityManager.unbindBackupAgent(app);
-
-            // The agent was running with a stub Application object, so shut it down.
-            // !!! We hardcode the confirmation UI's package name here rather than use a
-            //     manifest flag!  TODO something less direct.
-            if (!UserHandle.isCore(app.uid)
-                    && !app.packageName.equals("com.android.backupconfirm")) {
-                if (MORE_DEBUG) {
-                    Slog.d(TAG, addUserIdToLogMessage(mUserId, "Killing agent host process"));
-                }
-                mActivityManager.killApplicationProcess(app.processName, app.uid);
-            } else {
-                if (MORE_DEBUG) {
-                    Slog.d(
-                            TAG,
-                            addUserIdToLogMessage(
-                                    mUserId, "Not killing after operation: " + app.processName));
-                }
-            }
-        } catch (RemoteException e) {
-            Slog.d(TAG, addUserIdToLogMessage(mUserId, "Lost app trying to shut down"));
-        }
-    }
-
     // ----- Full-data backup scheduling -----
 
     /**
@@ -3536,40 +3501,6 @@
         }
     }
 
-    /**
-     * We want to skip backup/restore of certain packages if 'backup_skip_user_facing_packages' is
-     * set to true in secure settings. See b/153940088 for details.
-     *
-     * TODO(b/154822946): Remove this logic in the next release.
-     */
-    public List<PackageInfo> filterUserFacingPackages(List<PackageInfo> packages) {
-        if (!shouldSkipUserFacingData()) {
-            return packages;
-        }
-
-        List<PackageInfo> filteredPackages = new ArrayList<>(packages.size());
-        for (PackageInfo packageInfo : packages)  {
-            if (!shouldSkipPackage(packageInfo.packageName)) {
-                filteredPackages.add(packageInfo);
-            } else {
-                Slog.i(TAG, "Will skip backup/restore for " + packageInfo.packageName);
-            }
-        }
-
-        return filteredPackages;
-    }
-
-    @VisibleForTesting
-    public boolean shouldSkipUserFacingData() {
-        return Settings.Secure.getInt(mContext.getContentResolver(), SKIP_USER_FACING_PACKAGES,
-                /* def */ 0) != 0;
-    }
-
-    @VisibleForTesting
-    public boolean shouldSkipPackage(String packageName) {
-        return WALLPAPER_PACKAGE.equals(packageName);
-    }
-
     private void updateStateForTransport(String newTransportName) {
         // Publish the name change
         Settings.Secure.putStringForUser(mContext.getContentResolver(),
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index b98cb10..cf617a5 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -323,7 +323,8 @@
 
     private void tearDown() {
         if (mPkg != null) {
-            backupManagerService.tearDownAgentAndKill(mPkg.applicationInfo);
+            backupManagerService.getBackupAgentConnectionManager().unbindAgent(
+                    mPkg.applicationInfo, /* allowKill= */ true);
         }
     }
 }
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index dc67091..0ba0d71 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -503,7 +503,8 @@
             Slog.w(TAG, "adb backup cancel of " + target);
         }
         if (target != null) {
-            mUserBackupManagerService.tearDownAgentAndKill(mCurrentTarget.applicationInfo);
+            mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+                    target.applicationInfo, /* allowKill= */ true);
         }
         mOperationStorage.removeOperation(mCurrentOpToken);
     }
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 65730c9..990c941 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -272,8 +272,6 @@
             }
         }
 
-        mPackages = backupManagerService.filterUserFacingPackages(mPackages);
-
         Set<String> packageNames = Sets.newHashSet();
         for (PackageInfo pkgInfo : mPackages) {
             packageNames.add(pkgInfo.packageName);
@@ -596,8 +594,8 @@
                     // from the preflight pass.  If we got as far as preflight, we now need
                     // to tear down the target process.
                     if (mBackupRunner != null) {
-                        mUserBackupManagerService.tearDownAgentAndKill(
-                                currentPackage.applicationInfo);
+                        mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+                                currentPackage.applicationInfo, /* allowKill= */ true);
                     }
                     // ... and continue looping.
                 } else if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
@@ -609,7 +607,8 @@
                         EventLog.writeEvent(EventLogTags.FULL_BACKUP_QUOTA_EXCEEDED,
                                 packageName);
                     }
-                    mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+                    mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+                            currentPackage.applicationInfo, /* allowKill= */ true);
                     // Do nothing, clean up, and continue looping.
                 } else if (backupPackageStatus == BackupTransport.AGENT_ERROR) {
                     BackupObserverUtils
@@ -617,7 +616,8 @@
                                     BackupManager.ERROR_AGENT_FAILURE);
                     Slog.w(TAG, "Application failure for package: " + packageName);
                     EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName);
-                    mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+                    mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+                            currentPackage.applicationInfo, /* allowKill= */ true);
                     // Do nothing, clean up, and continue looping.
                 } else if (backupPackageStatus == BackupManager.ERROR_BACKUP_CANCELLED) {
                     BackupObserverUtils
@@ -626,7 +626,8 @@
                     Slog.w(TAG, "Backup cancelled. package=" + packageName +
                             ", cancelAll=" + mCancelAll);
                     EventLog.writeEvent(EventLogTags.FULL_BACKUP_CANCELLED, packageName);
-                    mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+                    mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+                            currentPackage.applicationInfo, /* allowKill= */ true);
                     // Do nothing, clean up, and continue looping.
                 } else if (backupPackageStatus != BackupTransport.TRANSPORT_OK) {
                     BackupObserverUtils
@@ -636,7 +637,8 @@
                     EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE);
                     // Abort entire backup pass.
                     backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
-                    mUserBackupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+                    mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+                            currentPackage.applicationInfo, /* allowKill= */ true);
                     return;
                 } else {
                     // Success!
@@ -1005,7 +1007,8 @@
             mIsCancelled = true;
             // Cancel tasks spun off by this task.
             mUserBackupManagerService.handleCancel(mEphemeralToken, cancelAll);
-            mUserBackupManagerService.tearDownAgentAndKill(mTarget.applicationInfo);
+            mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+                    mTarget.applicationInfo, /* allowKill= */ true);
             // Free up everyone waiting on this task and its children.
             mPreflightLatch.countDown();
             mBackupLatch.countDown();
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 82232a6..689c43a 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -1303,7 +1303,7 @@
         // For PM metadata (for which applicationInfo is null) there is no agent-bound state.
         if (mCurrentPackage.applicationInfo != null) {
             mBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
-                    mCurrentPackage.applicationInfo);
+                    mCurrentPackage.applicationInfo, /* allowKill= */ false);
         }
         mAgent = null;
     }
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index b59e860..237ffa3 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -727,7 +727,8 @@
                     latch.await();
                 }
 
-                mBackupManagerService.tearDownAgentAndKill(app);
+                mBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+                        app, /* allowKill= */ true);
             } catch (RemoteException e) {
                 Slog.d(TAG, "Lost app trying to shut down");
             }
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index e5c7e5c..ec9d340 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -51,7 +51,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -316,8 +315,6 @@
             }
         }
 
-        mAcceptSet = backupManagerService.filterUserFacingPackages(mAcceptSet);
-
         if (MORE_DEBUG) {
             Slog.v(TAG, "Restore; accept set size is " + mAcceptSet.size());
             for (PackageInfo info : mAcceptSet) {
@@ -1487,49 +1484,10 @@
 
         // If this wasn't the PM pseudopackage, tear down the agent side
         if (mCurrentPackage.applicationInfo != null) {
-            // unbind and tidy up even on timeout or failure
-            try {
-                backupManagerService
-                        .getActivityManager()
-                        .unbindBackupAgent(mCurrentPackage.applicationInfo);
-
-                // The agent was probably running with a stub Application object,
-                // which isn't a valid run mode for the main app logic.  Shut
-                // down the app so that next time it's launched, it gets the
-                // usual full initialization.  Note that this is only done for
-                // full-system restores: when a single app has requested a restore,
-                // it is explicitly not killed following that operation.
-                //
-                // We execute this kill when these conditions hold:
-                //    1. it's not a system-uid process,
-                //    2. the app did not request its own restore (mTargetPackage == null), and
-                // either
-                //    3a. the app is a full-data target (TYPE_FULL_STREAM) or
-                //     b. the app does not state android:killAfterRestore="false" in its manifest
-                final int appFlags = mCurrentPackage.applicationInfo.flags;
-                final boolean killAfterRestore =
-                        !UserHandle.isCore(mCurrentPackage.applicationInfo.uid)
-                                && ((mRestoreDescription.getDataType()
-                                                == RestoreDescription.TYPE_FULL_STREAM)
-                                        || ((appFlags & ApplicationInfo.FLAG_KILL_AFTER_RESTORE)
-                                                != 0));
-
-                if (mTargetPackage == null && killAfterRestore) {
-                    if (DEBUG) {
-                        Slog.d(
-                                TAG,
-                                "Restore complete, killing host process of "
-                                        + mCurrentPackage.applicationInfo.processName);
-                    }
-                    backupManagerService
-                            .getActivityManager()
-                            .killApplicationProcess(
-                                    mCurrentPackage.applicationInfo.processName,
-                                    mCurrentPackage.applicationInfo.uid);
-                }
-            } catch (RemoteException e) {
-                // can't happen; we run in the same process as the activity manager
-            }
+            // If mTargetPackage is not null it means the app requested its own restore, in which
+            // case we won't allow the app to be killed.
+            backupManagerService.getBackupAgentConnectionManager().unbindAgent(
+                    mCurrentPackage.applicationInfo, /* allowKill= */ mTargetPackage == null);
         }
 
         // The caller is responsible for reestablishing the state machine; our
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index 7a2106b..4bcfb33 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -240,9 +240,10 @@
             boolean matchesMacAddress = Objects.equals(
                     associationInfo.getDeviceMacAddress(),
                     restored.getDeviceMacAddress());
-            boolean matchesTag = !Flags.associationTag()
-                    || Objects.equals(associationInfo.getTag(), restored.getTag());
-            return matchesMacAddress && matchesTag;
+            boolean matchesDeviceId = !Flags.associationTag()
+                    || (associationInfo.getDeviceId() != null
+                        && associationInfo.getDeviceId().isSameDevice(restored.getDeviceId()));
+            return matchesMacAddress || matchesDeviceId;
         };
         AssociationInfo local = CollectionUtils.find(localAssociations, isSameDevice);
 
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 7cba9e0..418f3a1 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -55,6 +55,7 @@
 import android.bluetooth.BluetoothManager;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
+import android.companion.DeviceId;
 import android.companion.IAssociationRequestCallback;
 import android.companion.ICompanionDeviceManager;
 import android.companion.IOnAssociationsChangedListener;
@@ -318,7 +319,6 @@
         public List<AssociationInfo> getAssociations(String packageName, int userId) {
             enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
                     "get associations");
-
             return mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
         }
 
@@ -694,14 +694,10 @@
         }
 
         @Override
-        public void setAssociationTag(int associationId, String tag) {
-            mAssociationRequestsProcessor.setAssociationTag(associationId, tag);
+        public void setDeviceId(int associationId, DeviceId deviceId) {
+            mAssociationRequestsProcessor.setDeviceId(associationId, deviceId);
         }
 
-        @Override
-        public void clearAssociationTag(int associationId) {
-            setAssociationTag(associationId, null);
-        }
 
         @Override
         public byte[] getBackupPayload(int userId) {
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 77b1780..f2d019b 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -38,6 +38,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.companion.AssociationInfo;
+import android.companion.DeviceId;
 import android.graphics.drawable.Icon;
 import android.net.MacAddress;
 import android.os.Environment;
@@ -131,8 +132,11 @@
  *             revoked="false"
  *             last_time_connected="1634641160229"
  *             time_approved="1634389553216"
- *             system_data_sync_flags="0"/>
- *
+ *             system_data_sync_flags="0"
+ *             device_icon="device_icon">
+ *             <device_id
+ *                 custom_device_id="1234"/>
+ *         </association>
  *         <association
  *             id="3"
  *             profile="android.app.role.COMPANION_DEVICE_WATCH"
@@ -143,7 +147,11 @@
  *             revoked="false"
  *             last_time_connected="1634641160229"
  *             time_approved="1634641160229"
- *             system_data_sync_flags="1"/>
+ *             system_data_sync_flags="1"
+ *             device_icon="device_icon">
+ *             <device_id
+ *                 custom_device_id="1234"/>
+ *         </association>
  *     </associations>
  * </state>
  * }</pre>
@@ -160,7 +168,7 @@
     private static final String XML_TAG_STATE = "state";
     private static final String XML_TAG_ASSOCIATIONS = "associations";
     private static final String XML_TAG_ASSOCIATION = "association";
-    private static final String XML_TAG_TAG = "tag";
+    private static final String XML_TAG_DEVICE_ID = "device_id";
 
     private static final String XML_ATTR_PERSISTENCE_VERSION = "persistence-version";
     private static final String XML_ATTR_MAX_ID = "max-id";
@@ -177,6 +185,8 @@
     private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected";
     private static final String XML_ATTR_SYSTEM_DATA_SYNC_FLAGS = "system_data_sync_flags";
     private static final String XML_ATTR_DEVICE_ICON = "device_icon";
+    private static final String XML_ATTR_CUSTOM_DEVICE_ID = "custom_device_id";
+    private static final String XML_ATTR_MAC_ADDRESS_DEVICE_ID = "mac_address_device_id";
 
     private static final String LEGACY_XML_ATTR_DEVICE = "device";
 
@@ -389,16 +399,16 @@
         requireStartOfTag(parser, XML_TAG_ASSOCIATION);
 
         final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
-        final String tag = readStringAttribute(parser, XML_TAG_TAG);
         final String deviceAddress = readStringAttribute(parser, LEGACY_XML_ATTR_DEVICE);
         final String profile = readStringAttribute(parser, XML_ATTR_PROFILE);
         final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
         final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
 
-        return new AssociationInfo(associationId, userId, appPackage, tag,
+        return new AssociationInfo(associationId, userId, appPackage,
                 MacAddress.fromString(deviceAddress), null, profile, null,
                 /* managedByCompanionApp */ false, notify, /* revoked */ false, /* pending */ false,
-                timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0, null);
+                timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0, /* deviceIcon */ null,
+                /* deviceId */ null);
     }
 
     private static Associations readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -436,7 +446,6 @@
         final int associationId = readIntAttribute(parser, XML_ATTR_ID);
         final String profile = readStringAttribute(parser, XML_ATTR_PROFILE);
         final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
-        final String tag = readStringAttribute(parser, XML_TAG_TAG);
         final MacAddress macAddress = stringToMacAddress(
                 readStringAttribute(parser, XML_ATTR_MAC_ADDRESS));
         final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME);
@@ -451,10 +460,26 @@
                 XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, 0);
         final Icon deviceIcon = byteArrayToIcon(
                 readByteArrayAttribute(parser, XML_ATTR_DEVICE_ICON));
+        parser.nextTag();
+        final DeviceId deviceId = readDeviceId(parser);
 
-        return new AssociationInfo(associationId, userId, appPackage, tag, macAddress, displayName,
+        return new AssociationInfo(associationId, userId, appPackage, macAddress, displayName,
                 profile, null, selfManaged, notify, revoked, pending, timeApproved,
-                lastTimeConnected, systemDataSyncFlags, deviceIcon);
+                lastTimeConnected, systemDataSyncFlags, deviceIcon, deviceId);
+    }
+
+    private static DeviceId readDeviceId(@NonNull TypedXmlPullParser parser)
+            throws XmlPullParserException {
+        if (isStartOfTag(parser, XML_TAG_DEVICE_ID)) {
+            final String customDeviceId = readStringAttribute(
+                    parser, XML_ATTR_CUSTOM_DEVICE_ID);
+            final MacAddress macAddress = stringToMacAddress(
+                    readStringAttribute(parser, XML_ATTR_MAC_ADDRESS_DEVICE_ID));
+
+            return new DeviceId(customDeviceId, macAddress);
+        }
+
+        return null;
     }
 
     private static void writeAssociations(@NonNull XmlSerializer parent,
@@ -475,7 +500,6 @@
         writeIntAttribute(serializer, XML_ATTR_ID, a.getId());
         writeStringAttribute(serializer, XML_ATTR_PROFILE, a.getDeviceProfile());
         writeStringAttribute(serializer, XML_ATTR_PACKAGE, a.getPackageName());
-        writeStringAttribute(serializer, XML_TAG_TAG, a.getTag());
         writeStringAttribute(serializer, XML_ATTR_MAC_ADDRESS, a.getDeviceMacAddressAsString());
         writeStringAttribute(serializer, XML_ATTR_DISPLAY_NAME, a.getDisplayName());
         writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged());
@@ -490,14 +514,33 @@
         writeByteArrayAttribute(
                 serializer, XML_ATTR_DEVICE_ICON, iconToByteArray(a.getDeviceIcon()));
 
+        writeDeviceId(serializer, a);
         serializer.endTag(null, XML_TAG_ASSOCIATION);
     }
 
+    private static void writeDeviceId(XmlSerializer parent, @NonNull AssociationInfo a)
+            throws IOException {
+        if (a.getDeviceId() != null) {
+            final XmlSerializer serializer = parent.startTag(null, XML_TAG_DEVICE_ID);
+            writeStringAttribute(
+                    serializer,
+                    XML_ATTR_CUSTOM_DEVICE_ID,
+                    a.getDeviceId().getCustomId()
+            );
+            writeStringAttribute(
+                    serializer,
+                    XML_ATTR_MAC_ADDRESS_DEVICE_ID,
+                    a.getDeviceId().getMacAddressAsString()
+            );
+            serializer.endTag(null, XML_TAG_DEVICE_ID);
+        }
+    }
+
     private static void requireStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
             throws XmlPullParserException {
         if (isStartOfTag(parser, tag)) return;
         throw new XmlPullParserException(
-                "Should be at the start of \"" + XML_TAG_ASSOCIATIONS + "\" tag");
+                "Should be at the start of \"" + tag + "\" tag");
     }
 
     private static @Nullable MacAddress stringToMacAddress(@Nullable String address) {
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index aebd11a..899b302 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -41,6 +41,7 @@
 import android.companion.AssociatedDevice;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
+import android.companion.DeviceId;
 import android.companion.Flags;
 import android.companion.IAssociationRequestCallback;
 import android.content.ComponentName;
@@ -298,10 +299,10 @@
         final long timestamp = System.currentTimeMillis();
 
         final AssociationInfo association = new AssociationInfo(id, userId, packageName,
-                /* tag */ null, macAddress, displayName, deviceProfile, associatedDevice,
+                 macAddress, displayName, deviceProfile, associatedDevice,
                 selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
                 /* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0,
-                deviceIcon);
+                deviceIcon, /* deviceId */ null);
         // Add role holder for association (if specified) and add new association to store.
         maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
     }
@@ -354,14 +355,14 @@
     }
 
     /**
-     * Set association tag.
+     * Set Device id for the association.
      */
-    public void setAssociationTag(int associationId, String tag) {
-        Slog.i(TAG, "Setting association tag=[" + tag + "] to id=[" + associationId + "]...");
+    public void setDeviceId(int associationId, DeviceId deviceId) {
+        Slog.i(TAG, "Setting DeviceId=[" + deviceId + "] to id=[" + associationId + "]...");
 
         AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
                 associationId);
-        association = (new AssociationInfo.Builder(association)).setTag(tag).build();
+        association = (new AssociationInfo.Builder(association)).setDeviceId(deviceId).build();
         mAssociationStore.updateAssociation(association);
     }
 
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 4b9065b..6069e34 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -88,6 +88,9 @@
         /** Called when a secure window shows on the virtual display. */
         void onSecureWindowShown(int displayId, @NonNull ActivityInfo activityInfo);
 
+        /** Called when a secure window is no longer shown on the virtual display. */
+        void onSecureWindowHidden(int displayId);
+
         /** Returns true when an intent should be intercepted */
         boolean shouldInterceptIntent(@NonNull Intent intent);
     }
@@ -123,6 +126,9 @@
     private boolean mIsMirrorDisplay = false;
     private final CountDownLatch mDisplayIdSetLatch = new CountDownLatch(1);
 
+    // Used for detecting changes in the window flags.
+    private int mCurrentWindowFlags = 0;
+
     @NonNull
     @GuardedBy("mGenericWindowPolicyControllerLock")
     private final ArraySet<Integer> mRunningUids = new ArraySet<>();
@@ -371,12 +377,19 @@
     public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
             int systemWindowFlags) {
         int displayId = waitAndGetDisplayId();
-        // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
-        // aware that the virtual display has a secure window on top.
-        if ((windowFlags & FLAG_SECURE) != 0 && displayId != INVALID_DISPLAY) {
+        if (displayId != INVALID_DISPLAY) {
+            // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
+            // aware that the virtual display has a secure window on top.
             // Post callback on the main thread, so it doesn't block activity launching.
-            mHandler.post(() -> mActivityListener.onSecureWindowShown(displayId, activityInfo));
+            if ((windowFlags & FLAG_SECURE) != 0 && (mCurrentWindowFlags & FLAG_SECURE) == 0) {
+                mHandler.post(
+                        () -> mActivityListener.onSecureWindowShown(displayId, activityInfo));
+            }
+            if ((windowFlags & FLAG_SECURE) == 0 && (mCurrentWindowFlags & FLAG_SECURE) != 0) {
+                mHandler.post(() -> mActivityListener.onSecureWindowHidden(displayId));
+            }
         }
+        mCurrentWindowFlags = windowFlags;
 
         if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
                 activityInfo.packageName,
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 8b5b93e..a1d621d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -312,6 +312,17 @@
             }
         }
 
+        @Override
+        public void onSecureWindowHidden(int displayId) {
+            if (android.companion.virtualdevice.flags.Flags.activityControlApi()) {
+                try {
+                    mActivityListener.onSecureWindowHidden(displayId);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
+                }
+            }
+        }
+
         /**
          * Intercepts intent when matching any of the IntentFilter of any interceptor. Returns true
          * if the intent matches any filter notifying the DisplayPolicyController to abort the
diff --git a/services/core/Android.bp b/services/core/Android.bp
index aea16b0..ffa259b 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -178,6 +178,7 @@
 
     static_libs: [
         "android.frameworks.vibrator-V1-java", // AIDL
+        "android.frameworks.devicestate-V1-java", // AIDL
         "android.hardware.authsecret-V1.0-java",
         "android.hardware.authsecret-V1-java",
         "android.hardware.boot-V1.0-java", // HIDL
@@ -188,7 +189,7 @@
         "android.hardware.health-V1.0-java", // HIDL
         "android.hardware.health-V2.0-java", // HIDL
         "android.hardware.health-V2.1-java", // HIDL
-        "android.hardware.health-V3-java", // AIDL
+        "android.hardware.health-V4-java", // AIDL
         "android.hardware.health-translate-java",
         "android.hardware.light-V1-java",
         "android.hardware.security.authgraph-V1-java",
@@ -220,6 +221,7 @@
         "securebox",
         "apache-commons-math",
         "battery_saver_flag_lib",
+        "guava",
         "notification_flags_lib",
         "power_hint_flags_lib",
         "biometrics_flags_lib",
@@ -238,11 +240,11 @@
         "connectivity_flags_lib",
         "device_config_service_flags_java",
         "dreams_flags_lib",
-        "aconfig_flags_java",
         "aconfig_new_storage_flags_lib",
         "powerstats_flags_lib",
         "locksettings_flags_lib",
         "profiling_flags_lib",
+        "android.adpf.sessionmanager_aidl-java",
     ],
     javac_shard_size: 50,
     javacflags: [
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 43774bb..b0dae6a 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -90,6 +90,7 @@
      */
     public static final int RESOLVE_NON_RESOLVER_ONLY = 0x00000002;
 
+    @Deprecated
     @IntDef(value = {
             INTEGRITY_VERIFICATION_ALLOW,
             INTEGRITY_VERIFICATION_REJECT,
@@ -97,18 +98,10 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface IntegrityVerificationResult {}
 
-    /**
-     * Used as the {@code verificationCode} argument for
-     * {@link PackageManagerInternal#setIntegrityVerificationResult(int, int)} to indicate that the
-     * integrity component allows the install to proceed.
-     */
+    @Deprecated
     public static final int INTEGRITY_VERIFICATION_ALLOW = 1;
 
-    /**
-     * Used as the {@code verificationCode} argument for
-     * {@link PackageManagerInternal#setIntegrityVerificationResult(int, int)} to indicate that the
-     * integrity component does not allow install to proceed.
-     */
+    @Deprecated
     public static final int INTEGRITY_VERIFICATION_REJECT = 0;
 
     /**
@@ -1131,17 +1124,13 @@
     public abstract boolean isPermissionUpgradeNeeded(@UserIdInt int userId);
 
     /**
-     * Allows the integrity component to respond to the
-     * {@link Intent#ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION package verification
-     * broadcast} to respond to the package manager. The response must include
-     * the {@code verificationCode} which is one of
-     * {@link #INTEGRITY_VERIFICATION_ALLOW} and {@link #INTEGRITY_VERIFICATION_REJECT}.
+     * Used to allow the integrity component to respond to the
+     * ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION package verification
+     * broadcast to respond to the package manager.
      *
-     * @param verificationId pending package identifier as passed via the
-     *            {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra.
-     * @param verificationResult either {@link #INTEGRITY_VERIFICATION_ALLOW}
-     *            or {@link #INTEGRITY_VERIFICATION_REJECT}.
+     * Deprecated.
      */
+    @Deprecated
     public abstract void setIntegrityVerificationResult(int verificationId,
             @IntegrityVerificationResult int verificationResult);
 
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 3dcca14..4cf17ae 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -69,6 +69,7 @@
 import android.sysprop.PowerProperties;
 import android.util.EventLog;
 import android.util.Slog;
+import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -303,6 +304,17 @@
      */
     @VisibleForTesting
     public long mLastBroadcastVoltageUpdateTime;
+    /**
+     * Time when the max charging current was updated last by HAL and we sent the
+     * {@link Intent#ACTION_BATTERY_CHANGED} broadcast.
+     * Note: This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast
+     * so it is possible that max current was updated but we did not send the broadcast so in that
+     * case we do not update the time.
+     */
+    @VisibleForTesting
+    public long mLastBroadcastMaxChargingCurrentUpdateTime;
+
+    private boolean mIsFirstBatteryChangedUpdate = true;
 
     private Led mLed;
 
@@ -350,16 +362,21 @@
     private static final int ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE = 10;
     /**
      * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
-     * only send the broadcast if the last voltage was updated at least 20s seconds back and has a
+     * only send the broadcast if the last voltage was updated at least 20 seconds back and has a
      * fluctuation of at least 1%.
      */
     private static final int TIME_DIFF_FOR_VOLTAGE_UPDATE_MS = 20000;
     /**
      * The value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
-     * only send the broadcast if the last voltage was updated at least 20s seconds back and has a
+     * only send the broadcast if the last voltage was updated at least 20 seconds back and has a
      * fluctuation of at least 1%.
      */
     private static final float BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE = 0.01f;
+    /**
+     * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+     * only send the broadcast if the last max charging current was updated at least 5 seconds back.
+     */
+    private static final int TIME_DIFF_FOR_MAX_CHARGING_CURRENT_UPDATE_MS = 5000;
 
     private final Handler.Callback mLocalCallback = msg -> {
         switch (msg.what) {
@@ -1252,8 +1269,10 @@
         if (!com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast()) {
             return false;
         }
-        if (mLastBroadcastBatteryVoltage == 0 || mLastBroadcastBatteryTemperature == 0) {
+        if (mIsFirstBatteryChangedUpdate) {
             mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+            mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+            mIsFirstBatteryChangedUpdate = false;
             return false;
         }
 
@@ -1261,13 +1280,14 @@
                 mLastBroadcastBatteryVoltage != mHealthInfo.batteryVoltageMillivolts;
         final boolean temperatureUpdated =
                 mLastBroadcastBatteryTemperature != mHealthInfo.batteryTemperatureTenthsCelsius;
+        final boolean maxChargingCurrentUpdated =
+                mLastBroadcastMaxChargingCurrent != mHealthInfo.maxChargingCurrentMicroamps;
         final boolean otherStatesUpdated = forceUpdate
                 || mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
                 || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
                 || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
                 || mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel
                 || mPlugType != mLastBroadcastPlugType
-                || mHealthInfo.maxChargingCurrentMicroamps != mLastBroadcastMaxChargingCurrent
                 || mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage
                 || mInvalidCharger != mLastBroadcastInvalidCharger
                 || mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount
@@ -1280,6 +1300,9 @@
             if (voltageUpdated) {
                 mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
             }
+            if (maxChargingCurrentUpdated) {
+                mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+            }
             return false;
         }
 
@@ -1295,6 +1318,9 @@
                         >= TIME_DIFF_FOR_VOLTAGE_UPDATE_MS) {
             mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
 
+            if (maxChargingCurrentUpdated) {
+                mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+            }
             return false;
         }
 
@@ -1307,6 +1333,20 @@
             if (voltageUpdated) {
                 mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
             }
+            if (maxChargingCurrentUpdated) {
+                mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+            }
+            return false;
+        }
+
+        if (maxChargingCurrentUpdated
+                && SystemClock.elapsedRealtime() - mLastBroadcastMaxChargingCurrentUpdateTime
+                >= TIME_DIFF_FOR_MAX_CHARGING_CURRENT_UPDATE_MS) {
+            mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+
+            if (voltageUpdated) {
+                mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+            }
             return false;
         }
 
@@ -1615,6 +1655,9 @@
                 pw.println("  Wireless powered: " + mHealthInfo.chargerWirelessOnline);
                 pw.println("  Dock powered: " + mHealthInfo.chargerDockOnline);
                 pw.println("  Max charging current: " + mHealthInfo.maxChargingCurrentMicroamps);
+                pw.println(" Time when the latest updated value of the Max charging current was"
+                        + " sent via battery changed broadcast: "
+                        + TimeUtils.formatDuration(mLastBroadcastMaxChargingCurrentUpdateTime));
                 pw.println("  Max charging voltage: " + mHealthInfo.maxChargingVoltageMicrovolts);
                 pw.println("  Charge counter: " + mHealthInfo.batteryChargeCounterUah);
                 pw.println("  status: " + mHealthInfo.batteryStatus);
@@ -1624,7 +1667,8 @@
                 pw.println("  scale: " + BATTERY_SCALE);
                 pw.println("  voltage: " + mHealthInfo.batteryVoltageMillivolts);
                 pw.println(" Time when the latest updated value of the voltage was sent via "
-                        + "battery changed broadcast: " + mLastBroadcastVoltageUpdateTime);
+                        + "battery changed broadcast: "
+                        + TimeUtils.formatDuration(mLastBroadcastVoltageUpdateTime));
                 pw.println(" The last voltage value sent via the battery changed broadcast: "
                         + mLastBroadcastBatteryVoltage);
                 pw.println("  temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius);
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 485bf31..36dff89 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -65,6 +65,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -100,6 +101,9 @@
 import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
 
+import com.android.server.pm.BackgroundInstallControlService;
+import com.android.server.pm.BackgroundInstallControlCallbackHelper;
+
 /**
  * @hide
  */
@@ -137,6 +141,10 @@
     static final int MBA_STATUS_NEW_INSTALL = 3;
     // used for indicating newly installed MBAs that are updated (but unused currently)
     static final int MBA_STATUS_UPDATED_NEW_INSTALL = 4;
+    // used for indicating preloaded MBAs that are downgraded
+    static final int MBA_STATUS_DOWNGRADED_PRELOADED = 5;
+    // used for indicating MBAs that are uninstalled
+    static final int MBA_STATUS_UNINSTALLED = 6;
 
     @VisibleForTesting
     static final String KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION =
@@ -201,7 +209,9 @@
          * @param mbaStatus Assign this value of MBA status to the returned elements.
          * @return a @{@code List<IBinaryTransparencyService.AppInfo>}
          */
-        private @NonNull List<IBinaryTransparencyService.AppInfo> collectAppInfo(
+        @VisibleForTesting
+        @NonNull
+        List<IBinaryTransparencyService.AppInfo> collectAppInfo(
                 PackageState packageState, int mbaStatus) {
             // compute content digest
             if (DEBUG) {
@@ -335,26 +345,28 @@
                         + " packages after considering APEXs.");
             }
 
-            // proceed with all preloaded apps
-            List<IBinaryTransparencyService.AppInfo> allUpdatedPreloadInfo =
-                    collectAllUpdatedPreloadInfo(packagesMeasured);
-            for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) {
-                packagesMeasured.putBoolean(appInfo.packageName, true);
-                writeAppInfoToLog(appInfo);
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "Measured " + packagesMeasured.size()
-                        + " packages after considering preloads");
-            }
-
-            if (CompatChanges.isChangeEnabled(LOG_MBA_INFO)) {
-                // lastly measure all newly installed MBAs
-                List<IBinaryTransparencyService.AppInfo> allMbaInfo =
-                        collectAllSilentInstalledMbaInfo(packagesMeasured);
-                for (IBinaryTransparencyService.AppInfo appInfo : allMbaInfo) {
+            if (!android.app.Flags.backgroundInstallControlCallbackApi()) {
+                // proceed with all preloaded apps
+                List<IBinaryTransparencyService.AppInfo> allUpdatedPreloadInfo =
+                        collectAllUpdatedPreloadInfo(packagesMeasured);
+                for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) {
                     packagesMeasured.putBoolean(appInfo.packageName, true);
                     writeAppInfoToLog(appInfo);
                 }
+                if (DEBUG) {
+                    Slog.d(TAG, "Measured " + packagesMeasured.size()
+                            + " packages after considering preloads");
+                }
+
+                if (CompatChanges.isChangeEnabled(LOG_MBA_INFO)) {
+                    // lastly measure all newly installed MBAs
+                    List<IBinaryTransparencyService.AppInfo> allMbaInfo =
+                            collectAllSilentInstalledMbaInfo(packagesMeasured);
+                    for (IBinaryTransparencyService.AppInfo appInfo : allMbaInfo) {
+                        packagesMeasured.putBoolean(appInfo.packageName, true);
+                        writeAppInfoToLog(appInfo);
+                    }
+                }
             }
             long timeSpentMeasuring = System.currentTimeMillis() - currentTimeMs;
             digestAllPackagesLatency.logSample(timeSpentMeasuring);
@@ -464,7 +476,8 @@
                     apexInfo.signerDigests);
         }
 
-        private void writeAppInfoToLog(IBinaryTransparencyService.AppInfo appInfo) {
+        @VisibleForTesting
+        void writeAppInfoToLog(IBinaryTransparencyService.AppInfo appInfo) {
             // Must order by the proto's field number.
             FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
                     appInfo.packageName,
@@ -1158,6 +1171,94 @@
     }
 
     /**
+     * Receive callbacks from BIC to write silently installed apps to log
+     *
+     * TODO: Add a host test for testing registration and callback of BicCallbackHandler
+     *  b/380002484
+     */
+    @VisibleForTesting
+    static class BicCallbackHandler extends IRemoteCallback.Stub {
+        private static final String BIC_CALLBACK_HANDLER_TAG = TAG + ".BicCallbackHandler";
+
+        private static final int INSTALL_EVENT_TYPE_UNSET = -1;
+
+        private final IBicAppInfoHelper mBicAppInfoHelper;
+
+        @VisibleForTesting
+        BicCallbackHandler(IBicAppInfoHelper bicAppInfoHelper) {
+            mBicAppInfoHelper = bicAppInfoHelper;
+        }
+
+        @Override
+        public void sendResult(Bundle data) {
+            String packageName = data.getString(
+                    BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY);
+            int installType = data.getInt(
+                    BackgroundInstallControlCallbackHelper.INSTALL_EVENT_TYPE_KEY,
+                    INSTALL_EVENT_TYPE_UNSET);
+            if (packageName == null || installType == INSTALL_EVENT_TYPE_UNSET) {
+                Slog.w(BIC_CALLBACK_HANDLER_TAG, "Package name or install type is "
+                        + "unavailable, ignoring event");
+                return;
+            }
+            Slog.d(BIC_CALLBACK_HANDLER_TAG, "Detected new bic event for: " + packageName);
+            if (installType == BackgroundInstallControlService.INSTALL_EVENT_TYPE_INSTALL) {
+                PackageState packageState = LocalServices.getService(PackageManagerInternal.class)
+                    .getPackageStateInternal(packageName);
+                if (packageState == null) {
+                    Slog.w(TAG, "Package state is unavailable, ignoring the package "
+                            + packageName);
+                    return;
+                }
+                int mbaStatus = MBA_STATUS_NEW_INSTALL;
+                if (packageState.isUpdatedSystemApp()) {
+                    mbaStatus = MBA_STATUS_UPDATED_PRELOAD;
+                }
+                List<IBinaryTransparencyService.AppInfo> mbaInfo = mBicAppInfoHelper.collectAppInfo(
+                        packageState, mbaStatus);
+                for (IBinaryTransparencyService.AppInfo appInfo : mbaInfo) {
+                    mBicAppInfoHelper.writeAppInfoToLog(appInfo);
+                }
+            } else if (installType
+                        == BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL) {
+                IBinaryTransparencyService.AppInfo appInfo
+                    = new IBinaryTransparencyService.AppInfo();
+                // since app is already uninstalled we won't be able to retrieve additional
+                // info on it.
+                appInfo.packageName = packageName;
+                appInfo.mbaStatus = MBA_STATUS_UNINSTALLED;
+                mBicAppInfoHelper.writeAppInfoToLog(appInfo);
+            } else {
+                Slog.w(BIC_CALLBACK_HANDLER_TAG, "Unsupported BIC event: " + installType);
+            }
+        }
+
+        /**
+         * A wrapper of interface for{@link FrameworkStatsLog and ApkDigests}
+         * for easier testing
+         */
+        @VisibleForTesting
+        public interface IBicAppInfoHelper {
+
+            /**
+             * A wrapper of {@link FrameworkStatsLog}
+             *
+             * @param appInfo The app info of the changed MBA to be logged
+             */
+            public void writeAppInfoToLog(IBinaryTransparencyService.AppInfo appInfo);
+
+            /**
+             * A wrapper of {@link BinaryTransparencyServiceImpl}
+             *
+             * @param packageState The packageState provided retrieved from PackageManagerInternal
+             * @param mbaStatus The MBA status of the package
+             */
+            public List<IBinaryTransparencyService.AppInfo> collectAppInfo(
+                PackageState packageState, int mbaStatus);
+        }
+    };
+
+    /**
      * Called when the system service should publish a binder service using
      * {@link #publishBinderService(String, IBinder).}
      */
@@ -1534,6 +1635,43 @@
         }
     }
 
+    private void registerBicCallback() {
+        if(!com.android.server.flags.Flags.optionalBackgroundInstallControl()) {
+            Slog.d(TAG, "BICS is disabled for this device, skipping registration.");
+            return;
+        }
+        IBackgroundInstallControlService iBics =
+                IBackgroundInstallControlService.Stub.asInterface(
+                        ServiceManager.getService(
+                                Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
+        if(iBics == null) {
+            Slog.e(TAG, "Failed to register BackgroundInstallControl callback, either "
+                + "background install control service does not exist or disabled on this "
+                + "build.");
+            return;
+        }
+        try {
+            iBics.registerBackgroundInstallCallback(
+                    new BicCallbackHandler(
+                        new BicCallbackHandler.IBicAppInfoHelper() {
+                            @Override
+                            public void writeAppInfoToLog(
+                                    IBinaryTransparencyService.AppInfo appInfo) {
+                                mServiceImpl.writeAppInfoToLog(appInfo);
+                            }
+
+                            @Override
+                            public List<IBinaryTransparencyService.AppInfo> collectAppInfo(
+                                PackageState packageState, int mbaStatus) {
+                                return mServiceImpl.collectAppInfo(packageState, mbaStatus);
+                            }
+                        }
+                    ));
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to register BackgroundInstallControl callback.");
+        }
+    }
+
     private boolean isPackagePreloaded(String packageName) {
         PackageManager pm = mContext.getPackageManager();
         try {
@@ -1575,8 +1713,12 @@
             }
 
             String packageName = data.getSchemeSpecificPart();
-            // now we've got to check what package is this
-            if (isPackagePreloaded(packageName) || isPackageAnApex(packageName)) {
+
+            boolean shouldMeasureMba =
+                !android.app.Flags.backgroundInstallControlCallbackApi()
+                && isPackagePreloaded(packageName);
+
+            if (shouldMeasureMba || isPackageAnApex(packageName)) {
                 Slog.d(TAG, packageName + " was updated. Scheduling measurement...");
                 UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
                         BinaryTransparencyService.this);
@@ -1596,6 +1738,10 @@
     private void registerAllPackageUpdateObservers() {
         registerApkAndNonStagedApexUpdateListener();
         registerStagedApexUpdateObserver();
+        if (android.app.Flags.backgroundInstallControlCallbackApi()
+                && CompatChanges.isChangeEnabled(LOG_MBA_INFO)) {
+            registerBicCallback();
+        }
     }
 
     private String translateContentDigestAlgorithmIdToString(int algorithmId) {
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index fb527c1..2412b01 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -19,6 +19,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.media.AudioManager;
 import android.media.Ringtone;
@@ -35,6 +36,7 @@
 import android.util.Pair;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FrameworkStatsLog;
@@ -259,11 +261,19 @@
                     + mReportedDockState);
             final int previousDockState = mPreviousDockState;
             mPreviousDockState = mReportedDockState;
-            // Skip the dock intent if not yet provisioned.
+
             final ContentResolver cr = getContext().getContentResolver();
-            if (!mDeviceProvisionedObserver.isDeviceProvisioned()) {
-                Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
-                return;
+
+            /// If the allow dock rotation before provision is enabled then we allow rotation.
+            final Resources r = getContext().getResources();
+            final boolean allowDockBeforeProvision =
+                    r.getBoolean(R.bool.config_allowDockBeforeProvision);
+            if (!allowDockBeforeProvision) {
+                // Skip the dock intent if not yet provisioned.
+                if (!mDeviceProvisionedObserver.isDeviceProvisioned()) {
+                    Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
+                    return;
+                }
             }
 
             // Pack up the values and broadcast them to everyone
diff --git a/services/core/java/com/android/server/SecurityStateManagerService.java b/services/core/java/com/android/server/SecurityStateManagerService.java
index 98039be..fe21fbd 100644
--- a/services/core/java/com/android/server/SecurityStateManagerService.java
+++ b/services/core/java/com/android/server/SecurityStateManagerService.java
@@ -22,6 +22,7 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.ISecurityStateManager;
@@ -56,6 +57,15 @@
 
     @Override
     public Bundle getGlobalSecurityState() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return getGlobalSecurityStateInternal();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private Bundle getGlobalSecurityStateInternal() {
         Bundle globalSecurityState = new Bundle();
         globalSecurityState.putString(KEY_SYSTEM_SPL, Build.VERSION.SECURITY_PATCH);
         globalSecurityState.putString(KEY_VENDOR_SPL,
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index ce66dc3..8da8358 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -176,6 +176,10 @@
                     "include-filter": "com.android.server.wm.BackgroundActivityStart*"
                 }
             ]
+        },
+        {
+            "name": "FrameworksMockingServicesTests_service_batteryServiceTest",
+            "file_patterns": ["BatteryService\\.java"]
         }
    ]
 }
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index d13dd2f..896c9b8 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -885,8 +885,6 @@
                                 ? customModeType
                                 : MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
                         mNightMode.set(mode);
-                        //deactivates AttentionMode if user toggles DarkTheme
-                        mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
                         resetNightModeOverrideLocked();
                         persistNightMode(user);
                         // on screen off will update configuration instead
@@ -1009,15 +1007,16 @@
 
         @Override
         public boolean setNightModeActivatedForCustomMode(int modeNightCustomType, boolean active) {
-            return setNightModeActivatedForModeInternal(modeNightCustomType, active);
+            return setNightModeActivatedForModeInternal(modeNightCustomType, active, false);
         }
 
         @Override
         public boolean setNightModeActivated(boolean active) {
-            return setNightModeActivatedForModeInternal(mNightModeCustomType, active);
+            return setNightModeActivatedForModeInternal(mNightModeCustomType, active, true);
         }
 
-        private boolean setNightModeActivatedForModeInternal(int modeCustomType, boolean active) {
+        private boolean setNightModeActivatedForModeInternal(int modeCustomType,
+            boolean active, boolean isUserInteraction) {
             if (getContext().checkCallingOrSelfPermission(
                     android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
                     != PackageManager.PERMISSION_GRANTED) {
@@ -1053,13 +1052,16 @@
                         mOverrideNightModeOn = active;
                         mOverrideNightModeUser = mCurrentUser;
                         persistNightModeOverrides(mCurrentUser);
-                    } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_NO
-                            && active) {
+                    } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_NO && active) {
                         mNightMode.set(UiModeManager.MODE_NIGHT_YES);
-                    } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_YES
-                            && !active) {
+                    } else if (mNightMode.get() == UiModeManager.MODE_NIGHT_YES && !active) {
                         mNightMode.set(UiModeManager.MODE_NIGHT_NO);
                     }
+
+                    if (isUserInteraction) {
+                        // deactivates AttentionMode if user toggles DarkTheme
+                        mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
+                    }
                     updateConfigurationLocked();
                     applyConfigurationExternallyLocked();
                     persistNightMode(mCurrentUser);
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
deleted file mode 100644
index 2012f56..0000000
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ /dev/null
@@ -1,1541 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static android.Manifest.permission.DUMP;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_TEST;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import static android.net.vcn.VcnGatewayConnectionConfig.ALLOWED_CAPABILITIES;
-import static android.net.vcn.VcnManager.VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY;
-import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
-import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE;
-import static android.net.vcn.VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED;
-import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
-import static android.telephony.SubscriptionManager.isValidSubscriptionId;
-
-import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.net.vcn.IVcnManagementService;
-import android.net.vcn.IVcnStatusCallback;
-import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
-import android.net.vcn.VcnConfig;
-import android.net.vcn.VcnManager.VcnErrorCode;
-import android.net.vcn.VcnManager.VcnStatusCode;
-import android.net.vcn.VcnUnderlyingNetworkPolicy;
-import android.net.wifi.WifiInfo;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.ParcelUuid;
-import android.os.PersistableBundle;
-import android.os.Process;
-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;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
-import android.util.LocalLog;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.net.module.util.BinderUtils;
-import com.android.net.module.util.LocationPermissionChecker;
-import com.android.net.module.util.PermissionUtils;
-import com.android.server.vcn.TelephonySubscriptionTracker;
-import com.android.server.vcn.Vcn;
-import com.android.server.vcn.VcnContext;
-import com.android.server.vcn.VcnNetworkProvider;
-import com.android.server.vcn.util.PersistableBundleUtils;
-import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * VcnManagementService manages Virtual Carrier Network profiles and lifecycles.
- *
- * <pre>The internal structure of the VCN Management subsystem is as follows:
- *
- * +-------------------------+ 1:1                                +--------------------------------+
- * |  VcnManagementService   | ------------ Creates ------------> |  TelephonySubscriptionManager  |
- * |                         |                                    |                                |
- * |   Manages configs and   |                                    | Tracks subscriptions, carrier  |
- * | Vcn instance lifecycles | <--- Notifies of subscription & -- | privilege changes, caches maps |
- * +-------------------------+      carrier privilege changes     +--------------------------------+
- *      | 1:N          ^
- *      |              |
- *      |              +-------------------------------+
- *      +---------------+                              |
- *                      |                              |
- *         Creates when config present,                |
- *        subscription group active, and               |
- *      providing app is carrier privileged     Notifies of safe
- *                      |                      mode state changes
- *                      v                              |
- * +-----------------------------------------------------------------------+
- * |                                  Vcn                                  |
- * |                                                                       |
- * |       Manages GatewayConnection lifecycles based on fulfillable       |
- * |                NetworkRequest(s) and overall safe-mode                |
- * +-----------------------------------------------------------------------+
- *                      | 1:N                          ^
- *              Creates to fulfill                     |
- *           NetworkRequest(s), tears   Notifies of VcnGatewayConnection
- *          down when no longer needed   teardown (e.g. Network reaped)
- *                      |                 and safe-mode timer changes
- *                      v                              |
- * +-----------------------------------------------------------------------+
- * |                          VcnGatewayConnection                         |
- * |                                                                       |
- * |       Manages a single (IKEv2) tunnel session and NetworkAgent,       |
- * |  handles mobility events, (IPsec) Tunnel setup and safe-mode timers   |
- * +-----------------------------------------------------------------------+
- *                      | 1:1                          ^
- *                      |                              |
- *          Creates upon instantiation      Notifies of changes in
- *                      |                 selected underlying network
- *                      |                     or its properties
- *                      v                              |
- * +-----------------------------------------------------------------------+
- * |                       UnderlyingNetworkController                     |
- * |                                                                       |
- * | Manages lifecycle of underlying physical networks, filing requests to |
- * | bring them up, and releasing them as they become no longer necessary  |
- * +-----------------------------------------------------------------------+
- * </pre>
- *
- * @hide
- */
-// TODO(b/180451994): ensure all incoming + outgoing calls have a cleared calling identity
-public class VcnManagementService extends IVcnManagementService.Stub {
-    @NonNull private static final String TAG = VcnManagementService.class.getSimpleName();
-    @NonNull private static final String CONTEXT_ATTRIBUTION_TAG = "VCN";
-
-    private static final long DUMP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(5);
-    private static final int LOCAL_LOG_LINE_COUNT = 512;
-
-    private static final Set<Integer> RESTRICTED_TRANSPORTS_DEFAULT =
-            Collections.singleton(TRANSPORT_WIFI);
-
-    // Public for use in all other VCN classes
-    @NonNull public static final LocalLog LOCAL_LOG = new LocalLog(LOCAL_LOG_LINE_COUNT);
-
-    public static final boolean VDBG = false; // STOPSHIP: if true
-
-    // The system path is copied from Environment.getDataSystemDirectory
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final String VCN_CONFIG_FILE =
-            new File(Environment.getDataDirectory(), "system/vcn/configs.xml").getPath();
-
-    // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
-
-    /* Binder context for this service */
-    @NonNull private final Context mContext;
-    @NonNull private final Dependencies mDeps;
-
-    @NonNull private final Looper mLooper;
-    @NonNull private final Handler mHandler;
-    @NonNull private final VcnNetworkProvider mNetworkProvider;
-    @NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb;
-    @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
-    @NonNull private final BroadcastReceiver mVcnBroadcastReceiver;
-
-    @NonNull
-    private final TrackingNetworkCallback mTrackingNetworkCallback = new TrackingNetworkCallback();
-
-    @GuardedBy("mLock")
-    @NonNull
-    private final Map<ParcelUuid, VcnConfig> mConfigs = new ArrayMap<>();
-
-    @GuardedBy("mLock")
-    @NonNull
-    private final Map<ParcelUuid, Vcn> mVcns = new ArrayMap<>();
-
-    @GuardedBy("mLock")
-    @NonNull
-    private TelephonySubscriptionSnapshot mLastSnapshot =
-            TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT;
-
-    @NonNull private final Object mLock = new Object();
-
-    @NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper;
-
-    @GuardedBy("mLock")
-    @NonNull
-    private final Map<IBinder, PolicyListenerBinderDeath> mRegisteredPolicyListeners =
-            new ArrayMap<>();
-
-    @GuardedBy("mLock")
-    @NonNull
-    private final Map<IBinder, VcnStatusCallbackInfo> mRegisteredStatusCallbacks = new ArrayMap<>();
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    VcnManagementService(@NonNull Context context, @NonNull Dependencies deps) {
-        mContext =
-                requireNonNull(context, "Missing context")
-                        .createAttributionContext(CONTEXT_ATTRIBUTION_TAG);
-        mDeps = requireNonNull(deps, "Missing dependencies");
-
-        mLooper = mDeps.getLooper();
-        mHandler = new Handler(mLooper);
-        mNetworkProvider = new VcnNetworkProvider(mContext, mLooper);
-        mTelephonySubscriptionTrackerCb = new VcnSubscriptionTrackerCallback();
-        mTelephonySubscriptionTracker = mDeps.newTelephonySubscriptionTracker(
-                mContext, mLooper, mTelephonySubscriptionTrackerCb);
-
-        mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE);
-
-        mVcnBroadcastReceiver = new VcnBroadcastReceiver();
-
-        final IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
-        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        intentFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
-        intentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
-        intentFilter.addDataScheme("package");
-        mContext.registerReceiver(
-                mVcnBroadcastReceiver, intentFilter, null /* broadcastPermission */, mHandler);
-
-        // Run on handler to ensure I/O does not block system server startup
-        mHandler.post(() -> {
-            PersistableBundle configBundle = null;
-            try {
-                configBundle = mConfigDiskRwHelper.readFromDisk();
-            } catch (IOException e1) {
-                logErr("Failed to read configs from disk; retrying", e1);
-
-                // Retry immediately. The IOException may have been transient.
-                try {
-                    configBundle = mConfigDiskRwHelper.readFromDisk();
-                } catch (IOException e2) {
-                    logWtf("Failed to read configs from disk", e2);
-                    return;
-                }
-            }
-
-            if (configBundle != null) {
-                final Map<ParcelUuid, VcnConfig> configs =
-                        PersistableBundleUtils.toMap(
-                                configBundle,
-                                PersistableBundleUtils::toParcelUuid,
-                                VcnConfig::new);
-
-                synchronized (mLock) {
-                    for (Entry<ParcelUuid, VcnConfig> entry : configs.entrySet()) {
-                        // Ensure no new configs are overwritten; a carrier app may have added a new
-                        // config.
-                        if (!mConfigs.containsKey(entry.getKey())) {
-                            mConfigs.put(entry.getKey(), entry.getValue());
-                        }
-                    }
-
-                    // Re-evaluate subscriptions, and start/stop VCNs. This starts with an empty
-                    // snapshot, and therefore safe even before telephony subscriptions are loaded.
-                    mTelephonySubscriptionTrackerCb.onNewSnapshot(mLastSnapshot);
-                }
-            }
-        });
-    }
-
-    // Package-visibility for SystemServer to create instances.
-    static VcnManagementService create(@NonNull Context context) {
-        return new VcnManagementService(context, new Dependencies());
-    }
-
-    /** External dependencies used by VcnManagementService, for injection in tests */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class Dependencies {
-        private HandlerThread mHandlerThread;
-
-        /** Retrieves a looper for the VcnManagementService */
-        public Looper getLooper() {
-            if (mHandlerThread == null) {
-                synchronized (this) {
-                    if (mHandlerThread == null) {
-                        mHandlerThread = new HandlerThread(TAG);
-                        mHandlerThread.start();
-                    }
-                }
-            }
-            return mHandlerThread.getLooper();
-        }
-
-        /** Creates a new VcnInstance using the provided configuration */
-        public TelephonySubscriptionTracker newTelephonySubscriptionTracker(
-                @NonNull Context context,
-                @NonNull Looper looper,
-                @NonNull TelephonySubscriptionTrackerCallback callback) {
-            return new TelephonySubscriptionTracker(context, new Handler(looper), callback);
-        }
-
-        /**
-         * Retrieves the caller's UID
-         *
-         * <p>This call MUST be made before calling {@link Binder#clearCallingIdentity}, otherwise
-         * this will not work properly.
-         *
-         * @return
-         */
-        public int getBinderCallingUid() {
-            return Binder.getCallingUid();
-        }
-
-        /**
-         * Creates and returns a new {@link PersistableBundle.LockingReadWriteHelper}
-         *
-         * @param path the file path to read/write from/to.
-         * @return the {@link PersistableBundleUtils.LockingReadWriteHelper} instance
-         */
-        public PersistableBundleUtils.LockingReadWriteHelper
-                newPersistableBundleLockingReadWriteHelper(@NonNull String path) {
-            return new PersistableBundleUtils.LockingReadWriteHelper(path);
-        }
-
-        /** Creates a new VcnContext */
-        public VcnContext newVcnContext(
-                @NonNull Context context,
-                @NonNull Looper looper,
-                @NonNull VcnNetworkProvider vcnNetworkProvider,
-                boolean isInTestMode) {
-            return new VcnContext(context, looper, vcnNetworkProvider, isInTestMode);
-        }
-
-        /** Creates a new Vcn instance using the provided configuration */
-        public Vcn newVcn(
-                @NonNull VcnContext vcnContext,
-                @NonNull ParcelUuid subscriptionGroup,
-                @NonNull VcnConfig config,
-                @NonNull TelephonySubscriptionSnapshot snapshot,
-                @NonNull VcnCallback vcnCallback) {
-            return new Vcn(vcnContext, subscriptionGroup, config, snapshot, vcnCallback);
-        }
-
-        /** Gets the subId indicated by the given {@link WifiInfo}. */
-        public int getSubIdForWifiInfo(@NonNull WifiInfo wifiInfo) {
-            return wifiInfo.getSubscriptionId();
-        }
-
-        /** Creates a new LocationPermissionChecker for the provided Context. */
-        public LocationPermissionChecker newLocationPermissionChecker(@NonNull Context context) {
-            return new LocationPermissionChecker(context);
-        }
-
-        /** Gets transports that need to be marked as restricted by the VCN from CarrierConfig */
-        // TODO: b/262269892 This method was created to perform experiments before the relevant API
-        // was exposed. Now it is obsolete and should be removed.
-        @VisibleForTesting(visibility = Visibility.PRIVATE)
-        public Set<Integer> getRestrictedTransportsFromCarrierConfig(
-                ParcelUuid subGrp, TelephonySubscriptionSnapshot lastSnapshot) {
-            if (!Build.isDebuggable()) {
-                return RESTRICTED_TRANSPORTS_DEFAULT;
-            }
-
-            final PersistableBundleWrapper carrierConfig =
-                    lastSnapshot.getCarrierConfigForSubGrp(subGrp);
-            if (carrierConfig == null) {
-                return RESTRICTED_TRANSPORTS_DEFAULT;
-            }
-
-            final int[] defaultValue =
-                    RESTRICTED_TRANSPORTS_DEFAULT.stream().mapToInt(i -> i).toArray();
-            final int[] restrictedTransportsArray =
-                    carrierConfig.getIntArray(
-                            VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
-                            defaultValue);
-
-            // Convert to a boxed set
-            final Set<Integer> restrictedTransports = new ArraySet<>();
-            for (int transport : restrictedTransportsArray) {
-                restrictedTransports.add(transport);
-            }
-            return restrictedTransports;
-        }
-
-        /** Gets the transports that need to be marked as restricted by the VCN */
-        public Set<Integer> getRestrictedTransports(
-                ParcelUuid subGrp,
-                TelephonySubscriptionSnapshot lastSnapshot,
-                VcnConfig vcnConfig) {
-            final Set<Integer> restrictedTransports = new ArraySet<>();
-            restrictedTransports.addAll(vcnConfig.getRestrictedUnderlyingNetworkTransports());
-
-            // TODO: b/262269892 Remove the ability to configure restricted transports
-            // via CarrierConfig
-            restrictedTransports.addAll(
-                    getRestrictedTransportsFromCarrierConfig(subGrp, lastSnapshot));
-
-            return restrictedTransports;
-        }
-    }
-
-    /** Notifies the VcnManagementService that external dependencies can be set up. */
-    public void systemReady() {
-        mNetworkProvider.register();
-        mContext.getSystemService(ConnectivityManager.class)
-                .registerNetworkCallback(
-                        new NetworkRequest.Builder().clearCapabilities().build(),
-                        mTrackingNetworkCallback);
-        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) {
-            throw new IllegalStateException(
-                    "Calling identity was System Server. Was Binder calling identity cleared?");
-        }
-
-        final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
-        final UserManager userManager = mContext.getSystemService(UserManager.class);
-
-        BinderUtils.withCleanCallingIdentity(
-                () -> {
-                    if (!Objects.equals(userManager.getMainUser(), userHandle)) {
-                        throw new SecurityException(
-                                "VcnManagementService can only be used by callers running as"
-                                        + " the main user");
-                    }
-                });
-    }
-
-    private void enforceCallingUserAndCarrierPrivilege(
-            ParcelUuid subscriptionGroup, String pkgName) {
-        // Only apps running in the primary (system) user are allowed to configure the VCN. This is
-        // in line with Telephony's behavior with regards to binding to a Carrier App provided
-        // CarrierConfigService.
-        enforcePrimaryUser();
-
-        // TODO (b/172619301): Check based on events propagated from CarrierPrivilegesTracker
-        final SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class);
-        final List<SubscriptionInfo> subscriptionInfos = new ArrayList<>();
-        BinderUtils.withCleanCallingIdentity(
-                () -> {
-                    List<SubscriptionInfo> subsInGroup =
-                            subMgr.getSubscriptionsInGroup(subscriptionGroup);
-                    if (subsInGroup == null) {
-                        logWtf("Received null from getSubscriptionsInGroup");
-                        subsInGroup = Collections.emptyList();
-                    }
-                    subscriptionInfos.addAll(subsInGroup);
-                });
-
-        for (SubscriptionInfo info : subscriptionInfos) {
-            final TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class)
-                    .createForSubscriptionId(info.getSubscriptionId());
-
-            // Check subscription is active first; much cheaper/faster check, and an app (currently)
-            // cannot be carrier privileged for inactive subscriptions.
-            final int simSlotIndex = info.getSimSlotIndex();
-            final boolean isValidSlotIndex =
-                    simSlotIndex >= 0 && simSlotIndex < telMgr.getActiveModemCount();
-            if (isValidSlotIndex
-                    && telMgr.checkCarrierPrivilegesForPackage(pkgName)
-                            == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
-                // TODO (b/173717728): Allow configuration for inactive, but manageable
-                // subscriptions.
-                // TODO (b/173718661): Check for whole subscription groups at a time.
-                return;
-            }
-        }
-
-        throw new SecurityException(
-                "Carrier privilege required for subscription group to set VCN Config");
-    }
-
-    private void enforceManageTestNetworksForTestMode(@NonNull VcnConfig vcnConfig) {
-        if (vcnConfig.isTestModeProfile()) {
-            mContext.enforceCallingPermission(
-                    android.Manifest.permission.MANAGE_TEST_NETWORKS,
-                    "Test-mode require the MANAGE_TEST_NETWORKS permission");
-        }
-    }
-
-    private boolean isActiveSubGroup(
-            @NonNull ParcelUuid subGrp, @NonNull TelephonySubscriptionSnapshot snapshot) {
-        if (subGrp == null || snapshot == null) {
-            return false;
-        }
-
-        return Objects.equals(subGrp, snapshot.getActiveDataSubscriptionGroup());
-    }
-
-    private class VcnBroadcastReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (action == null) {
-                return;
-            }
-
-            switch (action) {
-                case Intent.ACTION_PACKAGE_ADDED: // Fallthrough
-                case Intent.ACTION_PACKAGE_REPLACED: // Fallthrough
-                case Intent.ACTION_PACKAGE_REMOVED:
-                    // Reevaluate subscriptions
-                    mTelephonySubscriptionTracker.handleSubscriptionsChanged();
-
-                    break;
-                case Intent.ACTION_PACKAGE_FULLY_REMOVED:
-                case Intent.ACTION_PACKAGE_DATA_CLEARED:
-                    final String pkgName = intent.getData().getSchemeSpecificPart();
-
-                    if (pkgName == null || pkgName.isEmpty()) {
-                        logWtf("Package name was empty or null for intent with action" + action);
-                        return;
-                    }
-
-                    // Clear configs for the packages that had data cleared, or removed.
-                    synchronized (mLock) {
-                        final List<ParcelUuid> toRemove = new ArrayList<>();
-                        for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
-                            if (pkgName.equals(entry.getValue().getProvisioningPackageName())) {
-                                toRemove.add(entry.getKey());
-                            }
-                        }
-
-                        for (ParcelUuid subGrp : toRemove) {
-                            stopAndClearVcnConfigInternalLocked(subGrp);
-                        }
-
-                        if (!toRemove.isEmpty()) {
-                            writeConfigsToDiskLocked();
-                        }
-                    }
-
-                    break;
-                default:
-                    Slog.wtf(TAG, "received unexpected intent: " + action);
-            }
-        }
-    }
-
-    private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback {
-        /**
-         * Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker}
-         *
-         * <p>Start any unstarted VCN instances
-         *
-         * @hide
-         */
-        public void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
-            // Startup VCN instances
-            synchronized (mLock) {
-                final TelephonySubscriptionSnapshot oldSnapshot = mLastSnapshot;
-                mLastSnapshot = snapshot;
-                logInfo("new snapshot: " + mLastSnapshot);
-
-                // Start any VCN instances as necessary
-                for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
-                    final ParcelUuid subGrp = entry.getKey();
-
-                    // TODO(b/193687515): Support multiple VCNs active at the same time
-                    if (snapshot.packageHasPermissionsForSubscriptionGroup(
-                                    subGrp, entry.getValue().getProvisioningPackageName())
-                            && isActiveSubGroup(subGrp, snapshot)) {
-                        if (!mVcns.containsKey(subGrp)) {
-                            startVcnLocked(subGrp, entry.getValue());
-                        }
-
-                        // Cancel any scheduled teardowns for active subscriptions
-                        mHandler.removeCallbacksAndMessages(mVcns.get(subGrp));
-                    }
-                }
-
-                boolean needNotifyAllPolicyListeners = false;
-                // Schedule teardown of any VCN instances that have lost carrier privileges (after a
-                // delay)
-                for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
-                    final ParcelUuid subGrp = entry.getKey();
-                    final VcnConfig config = mConfigs.get(subGrp);
-
-                    final boolean isActiveSubGrp = isActiveSubGroup(subGrp, snapshot);
-                    final boolean isValidActiveDataSubIdNotInVcnSubGrp =
-                            isValidSubscriptionId(snapshot.getActiveDataSubscriptionId())
-                                    && !isActiveSubGroup(subGrp, snapshot);
-
-                    // TODO(b/193687515): Support multiple VCNs active at the same time
-                    if (config == null
-                            || !snapshot.packageHasPermissionsForSubscriptionGroup(
-                                    subGrp, config.getProvisioningPackageName())
-                            || !isActiveSubGrp) {
-                        final ParcelUuid uuidToTeardown = subGrp;
-                        final Vcn instanceToTeardown = entry.getValue();
-
-                        // TODO(b/193687515): Support multiple VCNs active at the same time
-                        // If directly switching to a subscription not in the current group,
-                        // teardown immediately to prevent other subscription's network from being
-                        // outscored by the VCN. Otherwise, teardown after a delay to ensure that
-                        // SIM profile switches do not trigger the VCN to cycle.
-                        final long teardownDelayMs =
-                                isValidActiveDataSubIdNotInVcnSubGrp
-                                        ? 0
-                                        : CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS;
-                        mHandler.postDelayed(() -> {
-                            synchronized (mLock) {
-                                // Guard against case where this is run after a old instance was
-                                // torn down, and a new instance was started. Verify to ensure
-                                // correct instance is torn down. This could happen as a result of a
-                                // Carrier App manually removing/adding a VcnConfig.
-                                if (mVcns.get(uuidToTeardown) == instanceToTeardown) {
-                                    stopVcnLocked(uuidToTeardown);
-
-                                    // TODO(b/181789060): invoke asynchronously after Vcn notifies
-                                    // through VcnCallback
-                                    notifyAllPermissionedStatusCallbacksLocked(
-                                            uuidToTeardown, VCN_STATUS_CODE_INACTIVE);
-                                }
-                            }
-                        }, instanceToTeardown, teardownDelayMs);
-                    } else {
-                        // If this VCN's status has not changed, update it with the new snapshot
-                        entry.getValue().updateSubscriptionSnapshot(mLastSnapshot);
-                        needNotifyAllPolicyListeners |=
-                                !Objects.equals(
-                                        oldSnapshot.getCarrierConfigForSubGrp(subGrp),
-                                        mLastSnapshot.getCarrierConfigForSubGrp(subGrp));
-                    }
-                }
-
-                final Map<ParcelUuid, Set<Integer>> oldSubGrpMappings =
-                        getSubGroupToSubIdMappings(oldSnapshot);
-                final Map<ParcelUuid, Set<Integer>> currSubGrpMappings =
-                        getSubGroupToSubIdMappings(mLastSnapshot);
-                if (!currSubGrpMappings.equals(oldSubGrpMappings)) {
-                    garbageCollectAndWriteVcnConfigsLocked();
-                    needNotifyAllPolicyListeners = true;
-                }
-
-                if (needNotifyAllPolicyListeners) {
-                    notifyAllPolicyListenersLocked();
-                }
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private Map<ParcelUuid, Set<Integer>> getSubGroupToSubIdMappings(
-            @NonNull TelephonySubscriptionSnapshot snapshot) {
-        final Map<ParcelUuid, Set<Integer>> subGrpMappings = new ArrayMap<>();
-        for (ParcelUuid subGrp : mVcns.keySet()) {
-            subGrpMappings.put(subGrp, snapshot.getAllSubIdsInGroup(subGrp));
-        }
-        return subGrpMappings;
-    }
-
-    @GuardedBy("mLock")
-    private void stopVcnLocked(@NonNull ParcelUuid uuidToTeardown) {
-        logInfo("Stopping VCN config for subGrp: " + uuidToTeardown);
-
-        // Remove in 2 steps. Make sure teardownAsync is triggered before removing from the map.
-        final Vcn vcnToTeardown = mVcns.get(uuidToTeardown);
-        if (vcnToTeardown == null) {
-            return;
-        }
-
-        vcnToTeardown.teardownAsynchronously();
-        mVcns.remove(uuidToTeardown);
-
-        // Now that the VCN is removed, notify all registered listeners to refresh their
-        // UnderlyingNetworkPolicy.
-        notifyAllPolicyListenersLocked();
-    }
-
-    @GuardedBy("mLock")
-    private void notifyAllPolicyListenersLocked() {
-        for (final PolicyListenerBinderDeath policyListener : mRegisteredPolicyListeners.values()) {
-            BinderUtils.withCleanCallingIdentity(() -> {
-                try {
-                    policyListener.mListener.onPolicyChanged();
-                } catch (RemoteException e) {
-                    logDbg("VcnStatusCallback threw on VCN status change", e);
-                }
-            });
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void notifyAllPermissionedStatusCallbacksLocked(
-            @NonNull ParcelUuid subGroup, @VcnStatusCode int statusCode) {
-        for (final VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
-            if (isCallbackPermissioned(cbInfo, subGroup)) {
-                BinderUtils.withCleanCallingIdentity(() -> {
-                    try {
-                        cbInfo.mCallback.onVcnStatusChanged(statusCode);
-                    } catch (RemoteException e) {
-                        logDbg("VcnStatusCallback threw on VCN status change", e);
-                    }
-                });
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
-        logInfo("Starting VCN config for subGrp: " + subscriptionGroup);
-
-        // TODO(b/193687515): Support multiple VCNs active at the same time
-        if (!mVcns.isEmpty()) {
-            // Only one VCN supported at a time; teardown all others before starting new one
-            for (ParcelUuid uuidToTeardown : mVcns.keySet()) {
-                stopVcnLocked(uuidToTeardown);
-            }
-        }
-
-        final VcnCallbackImpl vcnCallback = new VcnCallbackImpl(subscriptionGroup);
-
-        final VcnContext vcnContext =
-                mDeps.newVcnContext(
-                        mContext, mLooper, mNetworkProvider, config.isTestModeProfile());
-        final Vcn newInstance =
-                mDeps.newVcn(vcnContext, subscriptionGroup, config, mLastSnapshot, vcnCallback);
-        mVcns.put(subscriptionGroup, newInstance);
-
-        // Now that a new VCN has started, notify all registered listeners to refresh their
-        // UnderlyingNetworkPolicy.
-        notifyAllPolicyListenersLocked();
-
-        // TODO(b/181789060): invoke asynchronously after Vcn notifies through VcnCallback
-        notifyAllPermissionedStatusCallbacksLocked(subscriptionGroup, VCN_STATUS_CODE_ACTIVE);
-    }
-
-    @GuardedBy("mLock")
-    private void startOrUpdateVcnLocked(
-            @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
-        logDbg("Starting or updating VCN config for subGrp: " + subscriptionGroup);
-
-        if (mVcns.containsKey(subscriptionGroup)) {
-            final Vcn vcn = mVcns.get(subscriptionGroup);
-            vcn.updateConfig(config);
-            notifyAllPolicyListenersLocked();
-        } else {
-            // TODO(b/193687515): Support multiple VCNs active at the same time
-            if (isActiveSubGroup(subscriptionGroup, mLastSnapshot)) {
-                startVcnLocked(subscriptionGroup, config);
-            }
-        }
-    }
-
-    /**
-     * Sets a VCN config for a given subscription group.
-     *
-     * <p>Implements the IVcnManagementService Binder interface.
-     */
-    @Override
-    public void setVcnConfig(
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull VcnConfig config,
-            @NonNull String opPkgName) {
-        requireNonNull(subscriptionGroup, "subscriptionGroup was null");
-        requireNonNull(config, "config was null");
-        requireNonNull(opPkgName, "opPkgName was null");
-        if (!config.getProvisioningPackageName().equals(opPkgName)) {
-            throw new IllegalArgumentException("Mismatched caller and VcnConfig creator");
-        }
-        logInfo("VCN config updated for subGrp: " + subscriptionGroup);
-
-        mContext.getSystemService(AppOpsManager.class)
-                .checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName());
-        enforceManageTestNetworksForTestMode(config);
-        enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName);
-
-        BinderUtils.withCleanCallingIdentity(() -> {
-            synchronized (mLock) {
-                mConfigs.put(subscriptionGroup, config);
-                startOrUpdateVcnLocked(subscriptionGroup, config);
-
-                writeConfigsToDiskLocked();
-            }
-        });
-    }
-
-    private void enforceCarrierPrivilegeOrProvisioningPackage(
-            @NonNull ParcelUuid subscriptionGroup, @NonNull String pkg) {
-        // Only apps running in the primary (system) user are allowed to configure the VCN. This is
-        // in line with Telephony's behavior with regards to binding to a Carrier App provided
-        // CarrierConfigService.
-        enforcePrimaryUser();
-
-        if (isProvisioningPackageForConfig(subscriptionGroup, pkg)) {
-            return;
-        }
-
-        // Must NOT be called from cleared binder identity, since this checks user calling identity
-        enforceCallingUserAndCarrierPrivilege(subscriptionGroup, pkg);
-    }
-
-    private boolean isProvisioningPackageForConfig(
-            @NonNull ParcelUuid subscriptionGroup, @NonNull String pkg) {
-        // Try-finally to return early if matching owned subscription found.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
-                final VcnConfig config = mConfigs.get(subscriptionGroup);
-                if (config != null && pkg.equals(config.getProvisioningPackageName())) {
-                    return true;
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-
-        return false;
-    }
-
-    /**
-     * Clears the VcnManagementService for a given subscription group.
-     *
-     * <p>Implements the IVcnManagementService Binder interface.
-     */
-    @Override
-    public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull String opPkgName) {
-        requireNonNull(subscriptionGroup, "subscriptionGroup was null");
-        requireNonNull(opPkgName, "opPkgName was null");
-        logInfo("VCN config cleared for subGrp: " + subscriptionGroup);
-
-        mContext.getSystemService(AppOpsManager.class)
-                .checkPackage(mDeps.getBinderCallingUid(), opPkgName);
-        enforceCarrierPrivilegeOrProvisioningPackage(subscriptionGroup, opPkgName);
-
-        BinderUtils.withCleanCallingIdentity(() -> {
-            synchronized (mLock) {
-                stopAndClearVcnConfigInternalLocked(subscriptionGroup);
-                writeConfigsToDiskLocked();
-            }
-        });
-    }
-
-    private void stopAndClearVcnConfigInternalLocked(@NonNull ParcelUuid subscriptionGroup) {
-        mConfigs.remove(subscriptionGroup);
-        final boolean vcnExists = mVcns.containsKey(subscriptionGroup);
-
-        stopVcnLocked(subscriptionGroup);
-
-        if (vcnExists) {
-            // TODO(b/181789060): invoke asynchronously after Vcn notifies through
-            // VcnCallback
-            notifyAllPermissionedStatusCallbacksLocked(
-                    subscriptionGroup, VCN_STATUS_CODE_NOT_CONFIGURED);
-        }
-    }
-
-    private void garbageCollectAndWriteVcnConfigsLocked() {
-        final SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class);
-        final Set<ParcelUuid> subGroups = mLastSnapshot.getAllSubscriptionGroups();
-
-        boolean shouldWrite = false;
-
-        final Iterator<ParcelUuid> configsIterator = mConfigs.keySet().iterator();
-        while (configsIterator.hasNext()) {
-            final ParcelUuid subGrp = configsIterator.next();
-
-            if (!subGroups.contains(subGrp)) {
-                // Trim subGrps with no more subscriptions; must have moved to another subGrp
-                logDbg("Garbage collect VcnConfig for group=" + subGrp);
-                configsIterator.remove();
-                shouldWrite = true;
-            }
-        }
-
-        if (shouldWrite) {
-            writeConfigsToDiskLocked();
-        }
-    }
-
-    /**
-     * Retrieves the list of subscription groups with configured VcnConfigs
-     *
-     * <p>Limited to subscription groups for which the caller had configured.
-     *
-     * <p>Implements the IVcnManagementService Binder interface.
-     */
-    @Override
-    @NonNull
-    public List<ParcelUuid> getConfiguredSubscriptionGroups(@NonNull String opPkgName) {
-        requireNonNull(opPkgName, "opPkgName was null");
-
-        mContext.getSystemService(AppOpsManager.class)
-                .checkPackage(mDeps.getBinderCallingUid(), opPkgName);
-        enforcePrimaryUser();
-
-        final List<ParcelUuid> result = new ArrayList<>();
-        synchronized (mLock) {
-            for (ParcelUuid subGrp : mConfigs.keySet()) {
-                if (mLastSnapshot.packageHasPermissionsForSubscriptionGroup(subGrp, opPkgName)
-                        || isProvisioningPackageForConfig(subGrp, opPkgName)) {
-                    result.add(subGrp);
-                }
-            }
-        }
-
-        return result;
-    }
-
-    @GuardedBy("mLock")
-    private void writeConfigsToDiskLocked() {
-        try {
-            PersistableBundle bundle =
-                    PersistableBundleUtils.fromMap(
-                            mConfigs,
-                            PersistableBundleUtils::fromParcelUuid,
-                            VcnConfig::toPersistableBundle);
-            mConfigDiskRwHelper.writeToDisk(bundle);
-        } catch (IOException e) {
-            logErr("Failed to save configs to disk", e);
-            throw new ServiceSpecificException(0, "Failed to save configs");
-        }
-    }
-
-    /** Get current configuration list for testing purposes */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    Map<ParcelUuid, VcnConfig> getConfigs() {
-        synchronized (mLock) {
-            return Collections.unmodifiableMap(mConfigs);
-        }
-    }
-
-    /** Get current VCNs for testing purposes */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public Map<ParcelUuid, Vcn> getAllVcns() {
-        synchronized (mLock) {
-            return Collections.unmodifiableMap(mVcns);
-        }
-    }
-
-    /** Get current VcnStatusCallbacks for testing purposes. */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public Map<IBinder, VcnStatusCallbackInfo> getAllStatusCallbacks() {
-        synchronized (mLock) {
-            return Collections.unmodifiableMap(mRegisteredStatusCallbacks);
-        }
-    }
-
-    /** Binder death recipient used to remove a registered policy listener. */
-    private class PolicyListenerBinderDeath implements Binder.DeathRecipient {
-        @NonNull private final IVcnUnderlyingNetworkPolicyListener mListener;
-
-        PolicyListenerBinderDeath(@NonNull IVcnUnderlyingNetworkPolicyListener listener) {
-            mListener = listener;
-        }
-
-        @Override
-        public void binderDied() {
-            Log.e(TAG, "app died without removing VcnUnderlyingNetworkPolicyListener");
-            removeVcnUnderlyingNetworkPolicyListener(mListener);
-        }
-    }
-
-    /** Adds the provided listener for receiving VcnUnderlyingNetworkPolicy updates. */
-    @Override
-    public void addVcnUnderlyingNetworkPolicyListener(
-            @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
-        requireNonNull(listener, "listener was null");
-
-        PermissionUtils.enforceAnyPermissionOf(
-                mContext,
-                android.Manifest.permission.NETWORK_FACTORY,
-                android.Manifest.permission.MANAGE_TEST_NETWORKS);
-
-        BinderUtils.withCleanCallingIdentity(() -> {
-            PolicyListenerBinderDeath listenerBinderDeath = new PolicyListenerBinderDeath(listener);
-
-            synchronized (mLock) {
-                mRegisteredPolicyListeners.put(listener.asBinder(), listenerBinderDeath);
-
-                try {
-                    listener.asBinder().linkToDeath(listenerBinderDeath, 0 /* flags */);
-                } catch (RemoteException e) {
-                    // Remote binder already died - cleanup registered Listener
-                    listenerBinderDeath.binderDied();
-                }
-            }
-        });
-    }
-
-    /** Removes the provided listener from receiving VcnUnderlyingNetworkPolicy updates. */
-    @Override
-    public void removeVcnUnderlyingNetworkPolicyListener(
-            @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
-        requireNonNull(listener, "listener was null");
-
-        PermissionUtils.enforceAnyPermissionOf(
-                mContext,
-                android.Manifest.permission.NETWORK_FACTORY,
-                android.Manifest.permission.MANAGE_TEST_NETWORKS);
-
-        BinderUtils.withCleanCallingIdentity(() -> {
-            synchronized (mLock) {
-                PolicyListenerBinderDeath listenerBinderDeath =
-                        mRegisteredPolicyListeners.remove(listener.asBinder());
-
-                if (listenerBinderDeath != null) {
-                    listener.asBinder().unlinkToDeath(listenerBinderDeath, 0 /* flags */);
-                }
-            }
-        });
-    }
-
-    private ParcelUuid getSubGroupForNetworkCapabilities(
-            @NonNull NetworkCapabilities networkCapabilities) {
-        ParcelUuid subGrp = null;
-        final TelephonySubscriptionSnapshot snapshot;
-
-        // Always access mLastSnapshot under lock. Technically this can be treated as a volatile
-        // but for consistency and safety, always access under lock.
-        synchronized (mLock) {
-            snapshot = mLastSnapshot;
-        }
-
-        // If multiple subscription IDs exist, they MUST all point to the same subscription
-        // group. Otherwise undefined behavior may occur.
-        for (int subId : networkCapabilities.getSubscriptionIds()) {
-            // Verify that all subscriptions point to the same group
-            if (subGrp != null && !subGrp.equals(snapshot.getGroupForSubId(subId))) {
-                logWtf("Got multiple subscription groups for a single network");
-            }
-
-            subGrp = snapshot.getGroupForSubId(subId);
-        }
-
-        return subGrp;
-    }
-
-    /**
-     * Gets the UnderlyingNetworkPolicy as determined by the provided NetworkCapabilities and
-     * LinkProperties.
-     */
-    @NonNull
-    @Override
-    public VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(
-            @NonNull NetworkCapabilities networkCapabilities,
-            @NonNull LinkProperties linkProperties) {
-        requireNonNull(networkCapabilities, "networkCapabilities was null");
-        requireNonNull(linkProperties, "linkProperties was null");
-
-        PermissionUtils.enforceAnyPermissionOf(
-                mContext,
-                android.Manifest.permission.NETWORK_FACTORY,
-                android.Manifest.permission.MANAGE_TEST_NETWORKS);
-
-        final boolean isUsingManageTestNetworks =
-                mContext.checkCallingOrSelfPermission(android.Manifest.permission.NETWORK_FACTORY)
-                        != PackageManager.PERMISSION_GRANTED;
-
-        if (isUsingManageTestNetworks && !networkCapabilities.hasTransport(TRANSPORT_TEST)) {
-            throw new IllegalStateException(
-                    "NetworkCapabilities must be for Test Network if using permission"
-                            + " MANAGE_TEST_NETWORKS");
-        }
-
-        return BinderUtils.withCleanCallingIdentity(() -> {
-            // Defensive copy in case this call is in-process and the given NetworkCapabilities
-            // mutates
-            final NetworkCapabilities ncCopy = new NetworkCapabilities(networkCapabilities);
-
-            final ParcelUuid subGrp = getSubGroupForNetworkCapabilities(ncCopy);
-            boolean isVcnManagedNetwork = false;
-            boolean isRestricted = false;
-            synchronized (mLock) {
-                final Vcn vcn = mVcns.get(subGrp);
-                final VcnConfig vcnConfig = mConfigs.get(subGrp);
-                if (vcn != null && vcnConfig != null) {
-                    if (vcn.getStatus() == VCN_STATUS_CODE_ACTIVE) {
-                        isVcnManagedNetwork = true;
-                    }
-
-                    final Set<Integer> restrictedTransports = mDeps.getRestrictedTransports(
-                            subGrp, mLastSnapshot, vcnConfig);
-                    for (int restrictedTransport : restrictedTransports) {
-                        if (ncCopy.hasTransport(restrictedTransport)) {
-                            if (restrictedTransport == TRANSPORT_CELLULAR
-                                    || restrictedTransport == TRANSPORT_TEST) {
-                                // For cell or test network, only mark it as restricted when
-                                // the VCN is in active mode.
-                                isRestricted |= (vcn.getStatus() == VCN_STATUS_CODE_ACTIVE);
-                            } else {
-                                isRestricted = true;
-                                break;
-                            }
-                        }
-                    }
-                } else if (vcn != null && vcnConfig == null) {
-                    logWtf("Vcn instance exists but VcnConfig does not for " + subGrp);
-                }
-            }
-
-            final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder(ncCopy);
-
-            if (isVcnManagedNetwork) {
-                ncBuilder.removeCapability(
-                        NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
-            } else {
-                ncBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
-            }
-
-            if (isRestricted) {
-                ncBuilder.removeCapability(
-                        NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
-            }
-
-            final NetworkCapabilities result = ncBuilder.build();
-            final VcnUnderlyingNetworkPolicy policy = new VcnUnderlyingNetworkPolicy(
-                    mTrackingNetworkCallback
-                            .requiresRestartForImmutableCapabilityChanges(result, linkProperties),
-                    result);
-
-            logVdbg("getUnderlyingNetworkPolicy() called for caps: " + networkCapabilities
-                        + "; and lp: " + linkProperties + "; result = " + policy);
-            return policy;
-        });
-    }
-
-    /** Binder death recipient used to remove registered VcnStatusCallbacks. */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    class VcnStatusCallbackInfo implements Binder.DeathRecipient {
-        @NonNull final ParcelUuid mSubGroup;
-        @NonNull final IVcnStatusCallback mCallback;
-        @NonNull final String mPkgName;
-        final int mUid;
-
-        private VcnStatusCallbackInfo(
-                @NonNull ParcelUuid subGroup,
-                @NonNull IVcnStatusCallback callback,
-                @NonNull String pkgName,
-                int uid) {
-            mSubGroup = subGroup;
-            mCallback = callback;
-            mPkgName = pkgName;
-            mUid = uid;
-        }
-
-        @Override
-        public void binderDied() {
-            Log.e(TAG, "app died without unregistering VcnStatusCallback");
-            unregisterVcnStatusCallback(mCallback);
-        }
-    }
-
-    private boolean isCallbackPermissioned(
-            @NonNull VcnStatusCallbackInfo cbInfo, @NonNull ParcelUuid subgroup) {
-        if (!subgroup.equals(cbInfo.mSubGroup)) {
-            return false;
-        }
-
-        if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(subgroup, cbInfo.mPkgName)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /** Registers the provided callback for receiving VCN status updates. */
-    @Override
-    public void registerVcnStatusCallback(
-            @NonNull ParcelUuid subGroup,
-            @NonNull IVcnStatusCallback callback,
-            @NonNull String opPkgName) {
-        final int callingUid = mDeps.getBinderCallingUid();
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            requireNonNull(subGroup, "subGroup must not be null");
-            requireNonNull(callback, "callback must not be null");
-            requireNonNull(opPkgName, "opPkgName must not be null");
-
-            mContext.getSystemService(AppOpsManager.class).checkPackage(callingUid, opPkgName);
-
-            final IBinder cbBinder = callback.asBinder();
-            final VcnStatusCallbackInfo cbInfo =
-                    new VcnStatusCallbackInfo(subGroup, callback, opPkgName, callingUid);
-
-            try {
-                cbBinder.linkToDeath(cbInfo, 0 /* flags */);
-            } catch (RemoteException e) {
-                // Remote binder already died - don't add to mRegisteredStatusCallbacks and exit
-                return;
-            }
-
-            synchronized (mLock) {
-                if (mRegisteredStatusCallbacks.containsKey(cbBinder)) {
-                    throw new IllegalStateException(
-                            "Attempting to register a callback that is already in use");
-                }
-
-                mRegisteredStatusCallbacks.put(cbBinder, cbInfo);
-
-                // now that callback is registered, send it the VCN's current status
-                final VcnConfig vcnConfig = mConfigs.get(subGroup);
-                final Vcn vcn = mVcns.get(subGroup);
-                final int vcnStatus =
-                        vcn == null ? VCN_STATUS_CODE_NOT_CONFIGURED : vcn.getStatus();
-                final int resultStatus;
-                if (vcnConfig == null || !isCallbackPermissioned(cbInfo, subGroup)) {
-                    resultStatus = VCN_STATUS_CODE_NOT_CONFIGURED;
-                } else if (vcn == null) {
-                    resultStatus = VCN_STATUS_CODE_INACTIVE;
-                } else if (vcnStatus == VCN_STATUS_CODE_ACTIVE
-                        || vcnStatus == VCN_STATUS_CODE_SAFE_MODE) {
-                    resultStatus = vcnStatus;
-                } else {
-                    logWtf("Unknown VCN status: " + vcnStatus);
-                    resultStatus = VCN_STATUS_CODE_NOT_CONFIGURED;
-                }
-
-                try {
-                    cbInfo.mCallback.onVcnStatusChanged(resultStatus);
-                } catch (RemoteException e) {
-                    logDbg("VcnStatusCallback threw on VCN status change", e);
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /** Unregisters the provided callback from receiving future VCN status updates. */
-    @Override
-    public void unregisterVcnStatusCallback(@NonNull IVcnStatusCallback callback) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            requireNonNull(callback, "callback must not be null");
-
-            final IBinder cbBinder = callback.asBinder();
-            synchronized (mLock) {
-                VcnStatusCallbackInfo cbInfo = mRegisteredStatusCallbacks.remove(cbBinder);
-
-                if (cbInfo != null) {
-                    cbBinder.unlinkToDeath(cbInfo, 0 /* flags */);
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void setLastSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
-        mLastSnapshot = Objects.requireNonNull(snapshot);
-    }
-
-    private void logVdbg(String msg) {
-        if (VDBG) {
-            Slog.v(TAG, msg);
-        }
-    }
-
-    private void logDbg(String msg) {
-        Slog.d(TAG, msg);
-    }
-
-    private void logDbg(String msg, Throwable tr) {
-        Slog.d(TAG, msg, tr);
-    }
-
-    private void logInfo(String msg) {
-        Slog.i(TAG, msg);
-        LOCAL_LOG.log("[INFO] [" + TAG + "] " + msg);
-    }
-
-    private void logInfo(String msg, Throwable tr) {
-        Slog.i(TAG, msg, tr);
-        LOCAL_LOG.log("[INFO] [" + TAG + "] " + msg + tr);
-    }
-
-    private void logErr(String msg) {
-        Slog.e(TAG, msg);
-        LOCAL_LOG.log("[ERR] [" + TAG + "] " + msg);
-    }
-
-    private void logErr(String msg, Throwable tr) {
-        Slog.e(TAG, msg, tr);
-        LOCAL_LOG.log("[ERR ] [" + TAG + "] " + msg + tr);
-    }
-
-    private void logWtf(String msg) {
-        Slog.wtf(TAG, msg);
-        LOCAL_LOG.log("[WTF] [" + TAG + "] " + msg);
-    }
-
-    private void logWtf(String msg, Throwable tr) {
-        Slog.wtf(TAG, msg, tr);
-        LOCAL_LOG.log("[WTF ] [" + TAG + "] " + msg + tr);
-    }
-
-    /**
-     * Dumps the state of the VcnManagementService for logging and debugging purposes.
-     *
-     * <p>PII and credentials MUST NEVER be dumped here.
-     */
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        mContext.enforceCallingOrSelfPermission(DUMP, TAG);
-
-        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "| ");
-
-        // Post to handler thread to prevent ConcurrentModificationExceptions, and avoid lock-hell.
-        mHandler.runWithScissors(() -> {
-            mNetworkProvider.dump(pw);
-            pw.println();
-
-            mTrackingNetworkCallback.dump(pw);
-            pw.println();
-
-            synchronized (mLock) {
-                mLastSnapshot.dump(pw);
-                pw.println();
-
-                pw.println("mConfigs:");
-                pw.increaseIndent();
-                for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
-                    pw.println(entry.getKey() + ": "
-                            + entry.getValue().getProvisioningPackageName());
-                }
-                pw.decreaseIndent();
-                pw.println();
-
-                pw.println("mVcns:");
-                pw.increaseIndent();
-                for (Vcn vcn : mVcns.values()) {
-                    vcn.dump(pw);
-                }
-                pw.decreaseIndent();
-                pw.println();
-            }
-
-            pw.println("Local log:");
-            pw.increaseIndent();
-            LOCAL_LOG.dump(pw);
-            pw.decreaseIndent();
-            pw.println();
-        }, DUMP_TIMEOUT_MILLIS);
-    }
-
-    // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService
-    /** Callback for Vcn signals sent up to VcnManagementService. */
-    public interface VcnCallback {
-        /** Called by a Vcn to signal that its safe mode status has changed. */
-        void onSafeModeStatusChanged(boolean isInSafeMode);
-
-        /** Called by a Vcn to signal that an error occurred. */
-        void onGatewayConnectionError(
-                @NonNull String gatewayConnectionName,
-                @VcnErrorCode int errorCode,
-                @Nullable String exceptionClass,
-                @Nullable String exceptionMessage);
-    }
-
-    /**
-     * TrackingNetworkCallback tracks all active networks
-     *
-     * <p>This is used to ensure that no underlying networks have immutable capabilities changed
-     * without requiring a Network restart.
-     */
-    private class TrackingNetworkCallback extends ConnectivityManager.NetworkCallback {
-        private final Object mLockObject = new Object();
-        private final Map<Network, NetworkCapabilities> mCaps = new ArrayMap<>();
-        private final Map<Network, LinkProperties> mLinkProperties = new ArrayMap<>();
-
-        @Override
-        public void onCapabilitiesChanged(Network network, NetworkCapabilities caps) {
-            synchronized (mLockObject) {
-                mCaps.put(network, caps);
-            }
-        }
-
-        @Override
-        public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
-            synchronized (mLockObject) {
-                mLinkProperties.put(network, lp);
-            }
-        }
-
-        @Override
-        public void onLost(Network network) {
-            synchronized (mLockObject) {
-                mCaps.remove(network);
-                mLinkProperties.remove(network);
-            }
-        }
-
-        private Set<Integer> getNonTestTransportTypes(NetworkCapabilities caps) {
-            final Set<Integer> transportTypes = new ArraySet<>();
-            for (int t : caps.getTransportTypes()) {
-                transportTypes.add(t);
-            }
-            return transportTypes;
-        }
-
-        private boolean hasSameTransportsAndCapabilities(
-                NetworkCapabilities caps, NetworkCapabilities capsOther) {
-            if (!Objects.equals(
-                    getNonTestTransportTypes(caps), getNonTestTransportTypes(capsOther))) {
-                return false;
-            }
-
-            for (int capability : ALLOWED_CAPABILITIES) {
-                if (caps.hasCapability(capability) != capsOther.hasCapability(capability)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        private boolean requiresRestartForImmutableCapabilityChanges(
-                NetworkCapabilities caps, LinkProperties lp) {
-            if (caps.getSubscriptionIds() == null) {
-                return false;
-            }
-
-            synchronized (mLockObject) {
-                // Search for an existing network (using interfce names)
-                // TODO: Get network from NetworkFactory (if exists) for this match.
-                for (Entry<Network, LinkProperties> lpEntry : mLinkProperties.entrySet()) {
-                    if (lp.getInterfaceName() != null
-                            && !lp.getInterfaceName().isEmpty()
-                            && Objects.equals(
-                                    lp.getInterfaceName(), lpEntry.getValue().getInterfaceName())) {
-                        return mCaps.get(lpEntry.getKey())
-                                        .hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
-                                != caps.hasCapability(NET_CAPABILITY_NOT_RESTRICTED);
-                    }
-                }
-            }
-
-            // If no network found, by definition does not need restart.
-            return false;
-        }
-
-        /** Dumps the state of this snapshot for logging and debugging purposes. */
-        public void dump(IndentingPrintWriter pw) {
-            pw.println("TrackingNetworkCallback:");
-            pw.increaseIndent();
-
-            pw.println("mCaps:");
-            pw.increaseIndent();
-            synchronized (mCaps) {
-                for (Entry<Network, NetworkCapabilities> entry : mCaps.entrySet()) {
-                    pw.println(entry.getKey() + ": " + entry.getValue());
-                }
-            }
-            pw.decreaseIndent();
-            pw.println();
-
-            pw.decreaseIndent();
-        }
-    }
-
-    /** VcnCallbackImpl for Vcn signals sent up to VcnManagementService. */
-    private class VcnCallbackImpl implements VcnCallback {
-        @NonNull private final ParcelUuid mSubGroup;
-
-        private VcnCallbackImpl(@NonNull final ParcelUuid subGroup) {
-            mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
-        }
-
-        @Override
-        public void onSafeModeStatusChanged(boolean isInSafeMode) {
-            synchronized (mLock) {
-                // Ignore if this subscription group doesn't exist anymore
-                if (!mVcns.containsKey(mSubGroup)) {
-                    return;
-                }
-
-                final int status =
-                        isInSafeMode ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
-
-                notifyAllPolicyListenersLocked();
-                notifyAllPermissionedStatusCallbacksLocked(mSubGroup, status);
-            }
-        }
-
-        @Override
-        public void onGatewayConnectionError(
-                @NonNull String gatewayConnectionName,
-                @VcnErrorCode int errorCode,
-                @Nullable String exceptionClass,
-                @Nullable String exceptionMessage) {
-            synchronized (mLock) {
-                // Ignore if this subscription group doesn't exist anymore
-                if (!mVcns.containsKey(mSubGroup)) {
-                    return;
-                }
-
-                // Notify all registered StatusCallbacks for this subGroup
-                for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
-                    if (isCallbackPermissioned(cbInfo, mSubGroup)) {
-                        BinderUtils.withCleanCallingIdentity(() -> {
-                            try {
-                                cbInfo.mCallback.onGatewayConnectionError(
-                                        gatewayConnectionName,
-                                        errorCode,
-                                        exceptionClass,
-                                        exceptionMessage);
-                            } catch (RemoteException e) {
-                                logDbg("VcnStatusCallback threw on VCN status change", e);
-                            }
-                        });
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index ab3ab15..60516c3 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4604,7 +4604,7 @@
         return true;
     }
 
-    void unbindFinishedLocked(ServiceRecord r, Intent intent, boolean doRebind) {
+    void unbindFinishedLocked(ServiceRecord r, Intent intent) {
         final long origId = mAm.mInjector.clearCallingIdentity();
         try {
             if (r != null) {
@@ -5845,7 +5845,7 @@
             if (r.inSharedIsolatedProcess) {
                 app = mAm.mProcessList.getSharedIsolatedProcess(procName, r.appInfo.uid,
                         r.appInfo.packageName);
-                if (app != null) {
+                if (app != null && !app.isKilled()) {
                     final IApplicationThread thread = app.getThread();
                     final int pid = app.getPid();
                     final UidRecord uidRecord = app.getUidRecord();
@@ -5870,6 +5870,8 @@
                         // If a dead object exception was thrown -- fall through to
                         // restart the application.
                     }
+                } else {
+                    app = null;
                 }
             } else {
                 // If this service runs in an isolated process, then each time
@@ -9319,6 +9321,46 @@
             Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist");
         }
     }
+    /**
+     * Handles notifications from MediaSessionService about active media service.
+     * This method evaluates the provided information and transitions corresponding service to
+     * foreground state.
+     *
+     * @param packageName The package name of the app running the service.
+     * @param userId The user ID associated with the service.
+     * @param notificationId The ID of the media notification associated with the service.
+     */
+    void notifyActiveMediaForegroundServiceLocked(@NonNull String packageName,
+            @UserIdInt int userId, int notificationId) {
+        if (!enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+            return;
+        }
+
+        final ServiceMap smap = mServiceMap.get(userId);
+        if (smap == null) {
+            return;
+        }
+        final int serviceSize = smap.mServicesByInstanceName.size();
+        for (int i = 0; i < serviceSize; i++) {
+            final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i);
+            if (sr.appInfo.packageName.equals(packageName) && !sr.isForeground) {
+                // foregroundServiceType is cleared when media session is user-disengaged
+                // and calls notifyInactiveMediaForegroundService->setServiceForegroundInnerLocked.
+                if (sr.foregroundServiceType
+                        == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE
+                        && sr.foregroundId == notificationId) {
+                    if (DEBUG_FOREGROUND_SERVICE) {
+                        Slog.d(TAG, "Moving media service to foreground for package "
+                                + packageName);
+                    }
+                    setServiceForegroundInnerLocked(sr, sr.foregroundId,
+                             sr.foregroundNoti, /* flags */ 0,
+                             ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+                             /* callingUidStart */ 0);
+                }
+            }
+        }
+    }
 
     /**
      * Handles notifications from MediaSessionService about inactive media foreground services.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5c5361b..f41aaee 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -131,6 +131,9 @@
 import static android.provider.Settings.Global.DEBUG_APP;
 import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
 import static android.security.Flags.preventIntentRedirect;
+import static android.security.Flags.preventIntentRedirectCollectNestedKeysOnServerIfNotCollected;
+import static android.security.Flags.preventIntentRedirectShowToastIfNestedKeysNotCollectedRW;
+import static android.security.Flags.preventIntentRedirectThrowExceptionIfNestedKeysNotCollected;
 import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
 import static android.view.Display.INVALID_DISPLAY;
 
@@ -189,6 +192,7 @@
 
 import android.Manifest;
 import android.Manifest.permission;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.PermissionMethod;
@@ -387,6 +391,7 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.autofill.AutofillManagerInternal;
+import android.widget.Toast;
 
 import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
@@ -437,6 +442,7 @@
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.ThreadPriorityBooster;
+import com.android.server.UiThread;
 import com.android.server.Watchdog;
 import com.android.server.am.LowMemDetector.MemFactor;
 import com.android.server.appop.AppOpsService;
@@ -478,6 +484,7 @@
 
 import dalvik.annotation.optimization.NeverCompile;
 import dalvik.system.VMRuntime;
+
 import libcore.util.EmptyArray;
 
 import java.io.File;
@@ -7546,7 +7553,7 @@
     }
 
     void setProfileApp(ApplicationInfo app, String processName, ProfilerInfo profilerInfo,
-            ApplicationInfo sdkSandboxClientApp) {
+            ApplicationInfo sdkSandboxClientApp, int profileType) {
         synchronized (mAppProfiler.mProfilerLock) {
             if (!Build.IS_DEBUGGABLE) {
                 boolean isAppDebuggable = (app.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
@@ -7562,7 +7569,7 @@
                             + "and not profileable by shell: " + app.packageName);
                 }
             }
-            mAppProfiler.setProfileAppLPf(processName, profilerInfo);
+            mAppProfiler.setProfileAppLPf(processName, profilerInfo, profileType);
         }
     }
 
@@ -13970,14 +13977,14 @@
         }
     }
 
-    public void unbindFinished(IBinder token, Intent intent, boolean doRebind) {
+    public void unbindFinished(IBinder token, Intent intent) {
         // Refuse possible leaked file descriptors
         if (intent != null && intent.hasFileDescriptors() == true) {
             throw new IllegalArgumentException("File descriptors passed in Intent");
         }
 
         synchronized(this) {
-            mServices.unbindFinishedLocked((ServiceRecord)token, intent, doRebind);
+            mServices.unbindFinishedLocked((ServiceRecord)token, intent);
         }
     }
 
@@ -15840,7 +15847,8 @@
                     + android.Manifest.permission.SET_ACTIVITY_WATCHER);
         }
 
-        if (start && (profilerInfo == null || profilerInfo.profileFd == null)) {
+        if (start && profileType == ProfilerInfo.PROFILE_TYPE_REGULAR
+                && (profilerInfo == null || profilerInfo.profileFd == null)) {
             throw new IllegalArgumentException("null profile info or fd");
         }
 
@@ -17483,7 +17491,9 @@
                     }
 
                     if (profilerInfo != null) {
-                        setProfileApp(aInfo.applicationInfo, aInfo.processName, profilerInfo, null);
+                        // We only support normal method tracing along with app startup for now.
+                        setProfileApp(aInfo.applicationInfo, aInfo.processName, profilerInfo,
+                                null, /*profileType= */ ProfilerInfo.PROFILE_TYPE_REGULAR);
                     }
                     wmLock.notify();
                 }
@@ -17923,6 +17933,15 @@
         }
 
         @Override
+        public void notifyActiveMediaForegroundService(@NonNull String packageName,
+                @UserIdInt int userId, int notificationId) {
+            synchronized (ActivityManagerService.this) {
+                mServices.notifyActiveMediaForegroundServiceLocked(packageName, userId,
+                        notificationId);
+            }
+        }
+
+        @Override
         public void notifyInactiveMediaForegroundService(@NonNull String packageName,
                 @UserIdInt int userId, int notificationId) {
             synchronized (ActivityManagerService.this) {
@@ -19213,6 +19232,11 @@
             return mKeyFields.mCreatorPackage;
         }
 
+        @VisibleForTesting
+        public @NonNull Key getKeyFields() {
+            return mKeyFields;
+        }
+
         public static boolean isValid(@NonNull Intent intent) {
             IBinder binder = intent.getCreatorToken();
             IntentCreatorToken token = null;
@@ -19256,9 +19280,13 @@
                 this.mFlags = intent.getFlags() & Intent.IMMUTABLE_FLAGS;
                 ClipData clipData = intent.getClipData();
                 if (clipData != null) {
-                    this.mClipDataUris = new ArrayList<>(clipData.getItemCount());
-                    for (int i = 0; i < clipData.getItemCount(); i++) {
-                        this.mClipDataUris.add(clipData.getItemAt(i).getUri());
+                    clipData = clipData.cloneOnlyUriItems();
+                    if (clipData != null) {
+                        List<Uri> clipDataUris = new ArrayList<>();
+                        clipData.collectUris(clipDataUris);
+                        if (!clipDataUris.isEmpty()) {
+                            this.mClipDataUris = clipDataUris;
+                        }
                     }
                 }
             }
@@ -19304,9 +19332,32 @@
      */
     public void addCreatorToken(@Nullable Intent intent, String creatorPackage) {
         if (!preventIntentRedirect()) return;
-
         if (intent == null) return;
 
+        if ((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED) == 0) {
+            Slog.wtf(TAG,
+                    "[IntentRedirect] The intent does not have its nested keys collected as a "
+                            + "preparation for creating intent creator tokens. Intent: "
+                            + intent + "; creatorPackage: " + creatorPackage);
+            if (preventIntentRedirectShowToastIfNestedKeysNotCollectedRW()) {
+                UiThread.getHandler().post(
+                        () -> Toast.makeText(mContext,
+                                "Nested keys not collected. go/report-bug-intentRedir to report a"
+                                        + " bug", Toast.LENGTH_LONG).show());
+            }
+            if (preventIntentRedirectThrowExceptionIfNestedKeysNotCollected()) {
+                // this flag will be internal only, not ramped to public.
+                throw new SecurityException(
+                        "The intent does not have its nested keys collected as a preparation for "
+                                + "creating intent creator tokens. Intent: "
+                                + intent + "; creatorPackage: " + creatorPackage);
+            }
+            if (preventIntentRedirectCollectNestedKeysOnServerIfNotCollected()) {
+                // this flag will be ramped to public.
+                intent.collectExtraIntentKeys();
+            }
+        }
+
         String targetPackage = intent.getComponent() != null
                 ? intent.getComponent().getPackageName()
                 : intent.getPackage();
@@ -19337,11 +19388,34 @@
             String creatorPackage) {
         if (IntentCreatorToken.isValid(intent)) return null;
         IntentCreatorToken.Key key = new IntentCreatorToken.Key(creatorUid, creatorPackage, intent);
+        return createOrGetIntentCreatorToken(intent, key);
+    }
+
+    /**
+     * @hide
+     */
+    @EnforcePermission("android.permission.INTERACT_ACROSS_USERS_FULL")
+    public IBinder refreshIntentCreatorToken(Intent intent) {
+        refreshIntentCreatorToken_enforcePermission();
+        IBinder binder = intent.getCreatorToken();
+        if (binder instanceof IntentCreatorToken) {
+            IntentCreatorToken token = (IntentCreatorToken) binder;
+            IntentCreatorToken.Key key = new IntentCreatorToken.Key(token.getCreatorUid(),
+                    token.getCreatorPackage(), intent);
+            return createOrGetIntentCreatorToken(intent, key);
+
+        } else {
+            throw new IllegalArgumentException("intent does not contain a creator token.");
+        }
+    }
+
+    private static IntentCreatorToken createOrGetIntentCreatorToken(Intent intent,
+            IntentCreatorToken.Key key) {
         IntentCreatorToken token;
         synchronized (sIntentCreatorTokenCache) {
             WeakReference<IntentCreatorToken> ref = sIntentCreatorTokenCache.get(key);
             if (ref == null || ref.get() == null) {
-                token = new IntentCreatorToken(creatorUid, creatorPackage, intent);
+                token = new IntentCreatorToken(key.mCreatorUid, key.mCreatorPackage, intent);
                 sIntentCreatorTokenCache.put(key, token.mRef);
             } else {
                 token = ref.get();
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index d3d3fc9..9a63546 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -471,14 +471,14 @@
         }
         int userId = UserHandle.USER_CURRENT;
         final String cmd = getNextArgRequired();
-        if ("inactive".equals(cmd)) {
+        if ("inactive".equals(cmd) || "active".equals(cmd)) {
             String opt;
             while ((opt = getNextOption()) != null) {
                 if (opt.equals("--user")) {
                     userId = UserHandle.parseUserArg(getNextArgRequired());
                     if (userId == UserHandle.USER_ALL) {
                         err.println(
-                                "Error: Can't set media fgs inactive with user 'all'");
+                                "Error: Can't set media fgs with user 'all'");
                         return -1;
                     }
                 } else {
@@ -492,8 +492,13 @@
                 err.println("Error: notification id cannot be zero");
                 return -1;
             }
-            mInternal.mInternal.notifyInactiveMediaForegroundService(pkgName,
-                    userId, notificationId);
+            if ("inactive".equals(cmd)) {
+                mInternal.mInternal.notifyInactiveMediaForegroundService(pkgName,
+                        userId, notificationId);
+            } else {
+                mInternal.mInternal.notifyActiveMediaForegroundService(pkgName,
+                        userId, notificationId);
+            }
             return 0;
         }
         err.println("Error: Unknown set-media-foreground-service command: " + cmd);
@@ -852,6 +857,7 @@
                 }
                 options.setDismissKeyguardIfInsecure();
             }
+            intent.collectExtraIntentKeys();
             if (mWaitOption) {
                 result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, null, intent,
                         mimeType, null, null, 0, mStartFlags, profilerInfo,
@@ -970,6 +976,7 @@
         }
         pw.println("Starting service: " + intent);
         pw.flush();
+        intent.collectExtraIntentKeys();
         ComponentName cn = mInterface.startService(null, intent, intent.getType(),
                 asForeground, SHELL_PACKAGE_NAME, null, mUserId);
         if (cn == null) {
@@ -1002,6 +1009,7 @@
         }
         pw.println("Stopping service: " + intent);
         pw.flush();
+        intent.collectExtraIntentKeys();
         int result = mInterface.stopService(null, intent, intent.getType(), mUserId);
         if (result == 0) {
             err.println("Service not stopped: was not running.");
@@ -1147,7 +1155,7 @@
         String profileFile = null;
         boolean start = false;
         int userId = UserHandle.USER_CURRENT;
-        int profileType = 0;
+        int profileType = ProfilerInfo.PROFILE_TYPE_REGULAR;
         mSamplingInterval = 0;
         mStreaming = false;
         mClockType = ProfilerInfo.CLOCK_TYPE_DEFAULT;
@@ -1189,6 +1197,18 @@
                 }
             }
             process = getNextArgRequired();
+        } else if ("lowoverhead".equals(cmd)) {
+            // This is an experimental low overhead profiling.
+            profileType = ProfilerInfo.PROFILE_TYPE_LOW_OVERHEAD;
+            cmd = getNextArgRequired();
+            if ("start".equals(cmd)) {
+                start = true;
+            } else if ("stop".equals(cmd)) {
+                start = false;
+            } else {
+                throw new IllegalArgumentException("Profile command not valid");
+            }
+            process = getNextArgRequired();
         } else {
             // Compatibility with old syntax: process is specified first.
             process = cmd;
@@ -1208,7 +1228,12 @@
         ParcelFileDescriptor fd = null;
         ProfilerInfo profilerInfo = null;
 
-        if (start) {
+        // For regular method tracing  profileFile should be provided with the start command. For
+        // low overhead method tracing the profileFile is optional and provided with the stop
+        // command.
+        if ((start && profileType == ProfilerInfo.PROFILE_TYPE_REGULAR)
+                || (profileType == ProfilerInfo.PROFILE_TYPE_LOW_OVERHEAD
+                  && !start && getRemainingArgsCount() > 0)) {
             profileFile = getNextArgRequired();
             fd = openFileForSystem(profileFile, "w");
             if (fd == null) {
@@ -1399,6 +1424,12 @@
             heapFile = "/data/local/tmp/heapdump-" + logNameTimeString + ".prof";
         }
 
+        String argAfterHeapFile = getNextArg();
+        if (argAfterHeapFile != null) {
+            err.println("Error: Arguments cannot be placed after the heap file");
+            return -1;
+        }
+
         // Writes an error message to stderr on failure
         ParcelFileDescriptor fd = openFileForSystem(heapFile, "w");
         if (fd == null) {
@@ -4683,9 +4714,9 @@
             pw.println("         --protobuf: format output using protobuffer");
             pw.println("  set-app-zygote-preload-timeout <TIMEOUT_IN_MS>");
             pw.println("         Set the timeout for preloading code in the app-zygote");
-            pw.println("  set-media-foreground-service inactive [--user USER_ID]"
-                    + " <PACKAGE> <NOTIFICATION_ID>");
-            pw.println("         Set an app's media foreground service inactive.");
+            pw.println("  set-media-foreground-service inactive|active [--user USER_ID] <PACKAGE>"
+                            + " <NOTIFICATION_ID>");
+            pw.println("         Set an app's media service inactive or active.");
             Intent.printIntentArgsHelp(pw, "");
         }
     }
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 4f2d69e..6b24df4 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1992,7 +1992,7 @@
     }
 
     @GuardedBy("mProfilerLock")
-    private void stopProfilerLPf(ProcessRecord proc, int profileType) {
+    private void stopProfilerLPf(ProcessRecord proc, ProfilerInfo profilerInfo, int profileType) {
         if (proc == null || proc == mProfileData.getProfileProc()) {
             proc = mProfileData.getProfileProc();
             profileType = mProfileType;
@@ -2006,7 +2006,7 @@
             return;
         }
         try {
-            thread.profilerControl(false, null, profileType);
+            thread.profilerControl(false, profilerInfo, profileType);
         } catch (RemoteException e) {
             throw new IllegalStateException("Process disappeared");
         }
@@ -2041,41 +2041,58 @@
             ProfilerInfo profilerInfo, int profileType) {
         try {
             if (start) {
-                stopProfilerLPf(null, 0);
+                boolean needsFile = (profileType == ProfilerInfo.PROFILE_TYPE_REGULAR);
+                stopProfilerLPf(null, null, 0);
                 mService.setProfileApp(proc.info, proc.processName, profilerInfo,
-                        proc.isSdkSandbox ? proc.getClientInfoForSdkSandbox() : null);
+                        proc.isSdkSandbox ? proc.getClientInfoForSdkSandbox() : null, profileType);
                 mProfileData.setProfileProc(proc);
                 mProfileType = profileType;
-                ParcelFileDescriptor fd = profilerInfo.profileFd;
-                try {
-                    fd = fd.dup();
-                } catch (IOException e) {
-                    fd = null;
-                }
-                profilerInfo.profileFd = fd;
-                proc.mProfile.getThread().profilerControl(start, profilerInfo, profileType);
-                fd = null;
-                try {
-                    mProfileData.getProfilerInfo().profileFd.close();
-                } catch (IOException e) {
-                }
-                mProfileData.getProfilerInfo().profileFd = null;
 
-                if (proc.getPid() == mService.MY_PID) {
-                    // When profiling the system server itself, avoid closing the file
-                    // descriptor, as profilerControl will not create a copy.
-                    // Note: it is also not correct to just set profileFd to null, as the
-                    //       whole ProfilerInfo instance is passed down!
-                    profilerInfo = null;
-                }
-            } else {
-                stopProfilerLPf(proc, profileType);
-                if (profilerInfo != null && profilerInfo.profileFd != null) {
+                ParcelFileDescriptor fd = null;
+                if (needsFile) {
+                    fd = profilerInfo.profileFd;
                     try {
-                        profilerInfo.profileFd.close();
+                        fd = fd.dup();
+                    } catch (IOException e) {
+                        fd = null;
+                    }
+                    profilerInfo.profileFd = fd;
+                }
+
+                proc.mProfile.getThread().profilerControl(start, profilerInfo, profileType);
+
+                if (needsFile) {
+                    fd = null;
+                    try {
+                        mProfileData.getProfilerInfo().profileFd.close();
                     } catch (IOException e) {
                     }
+                    mProfileData.getProfilerInfo().profileFd = null;
+
+                    if (proc.getPid() == mService.MY_PID) {
+                        // When profiling the system server itself, avoid closing the file
+                        // descriptor, as profilerControl will not create a copy.
+                        // Note: it is also not correct to just set profileFd to null, as the
+                        //       whole ProfilerInfo instance is passed down!
+                        profilerInfo = null;
+                    }
                 }
+            } else {
+                boolean mayNeedFile = (profileType == ProfilerInfo.PROFILE_TYPE_LOW_OVERHEAD);
+                if (profilerInfo != null && profilerInfo.profileFd != null) {
+                    ParcelFileDescriptor fd = profilerInfo.profileFd;
+                    try {
+                        if (mayNeedFile) {
+                            fd = fd.dup();
+                        } else {
+                            fd.close();
+                        }
+                    } catch (IOException e) {
+                        fd = null;
+                    }
+                    profilerInfo.profileFd = fd;
+                }
+                stopProfilerLPf(proc, profilerInfo, profileType);
             }
 
             return true;
@@ -2092,7 +2109,7 @@
     }
 
     @GuardedBy("mProfilerLock")
-    void setProfileAppLPf(String processName, ProfilerInfo profilerInfo) {
+    void setProfileAppLPf(String processName, ProfilerInfo profilerInfo, int profileType) {
         mProfileData.setProfileApp(processName);
 
         if (mProfileData.getProfilerInfo() != null) {
@@ -2103,8 +2120,10 @@
                 }
             }
         }
-        mProfileData.setProfilerInfo(new ProfilerInfo(profilerInfo));
-        mProfileType = 0;
+        if (profilerInfo != null) {
+            mProfileData.setProfilerInfo(new ProfilerInfo(profilerInfo));
+        }
+        mProfileType = profileType;
     }
 
     @GuardedBy("mProfilerLock")
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index d5bd057..c27126a 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1126,20 +1126,8 @@
                     break;
                 }
                 case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
-                    if (Flags.disableCompositeBatteryUsageStatsAtoms()) {
-                        return StatsManager.PULL_SKIP;
-                    }
+                    return StatsManager.PULL_SKIP;
 
-                    final BatteryUsageStatsQuery queryPowerProfile =
-                            new BatteryUsageStatsQuery.Builder()
-                                    .setMaxStatsAgeMs(0)
-                                    .includeProcessStateData()
-                                    .includeVirtualUids()
-                                    .powerProfileModeledOnly()
-                                    .includePowerModels()
-                                    .build();
-                    bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
-                    break;
                 case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: {
                     if (Flags.disableCompositeBatteryUsageStatsAtoms()) {
                         return StatsManager.PULL_SKIP;
@@ -1263,12 +1251,10 @@
         }
 
         private static float clampPowerMah(double powerMah, String consumer) {
-            float resultPowerMah = 0;
-            if (powerMah <= Float.MAX_VALUE && powerMah >= Float.MIN_VALUE) {
-                resultPowerMah = (float) powerMah;
-            } else {
-                // Handle overflow appropriately
-                Slog.wtfStack(TAG, consumer + " reported powerMah float overflow: " + powerMah);
+            float resultPowerMah = Double.valueOf(powerMah).floatValue();
+            if (Float.isInfinite(resultPowerMah)) {
+                resultPowerMah = 0;
+                Slog.d(TAG, consumer + " reported powerMah float overflow : " + powerMah);
             }
             return resultPowerMah;
         }
@@ -1373,11 +1359,10 @@
 
             final String powerComponentName = batteryConsumer.getPowerComponentName(componentId);
             final double consumedPowerMah = batteryConsumer.getConsumedPower(key);
-            float powerMah =
+            final float powerMah =
                     clampPowerMah(
-                            consumedPowerMah, "uidConsumer-" + uid + "-" + powerComponentName);
+                            consumedPowerMah, "uid-" + uid + "-" + powerComponentName);
             final long powerComponentDurationMillis = batteryConsumer.getUsageDurationMillis(key);
-
             if (powerMah == 0 && powerComponentDurationMillis == 0) {
                 return true;
             }
@@ -3184,7 +3169,7 @@
         }
     }
 
-    private void dumpUsageStats(FileDescriptor fd, PrintWriter pw, int model,
+    private void dumpUsageStats(FileDescriptor fd, PrintWriter pw,
             boolean proto, boolean accumulated) {
         awaitCompletion();
         syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
@@ -3196,9 +3181,6 @@
         if (Flags.batteryUsageStatsByPowerAndScreenState()) {
             builder.includeScreenStateData().includePowerStateData();
         }
-        if (model == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
-            builder.powerProfileModeledOnly();
-        }
         if (accumulated) {
             builder.accumulated();
         }
@@ -3393,7 +3375,6 @@
                     dumpPowerProfile(pw);
                     return;
                 } else if ("--usage".equals(arg)) {
-                    int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
                     boolean proto = false;
                     boolean accumulated = false;
                     for (int j = i + 1; j < args.length; j++) {
@@ -3401,29 +3382,12 @@
                             case "--proto":
                                 proto = true;
                                 break;
-                            case "--model": {
-                                if (j + 1 < args.length) {
-                                    j++;
-                                    if ("power-profile".equals(args[j])) {
-                                        model = BatteryConsumer.POWER_MODEL_POWER_PROFILE;
-                                    } else {
-                                        pw.println("Unknown power model: " + args[j]);
-                                        dumpHelp(pw);
-                                        return;
-                                    }
-                                } else {
-                                    pw.println("--model without a value");
-                                    dumpHelp(pw);
-                                    return;
-                                }
-                                break;
-                            }
                             case "--accumulated":
                                 accumulated = true;
                                 break;
                         }
                     }
-                    dumpUsageStats(fd, pw, model, proto, accumulated);
+                    dumpUsageStats(fd, pw, proto, accumulated);
                     return;
                 } else if ("--wakeups".equals(arg)) {
                     mCpuWakeupStats.dump(new IndentingPrintWriter(pw, "  "),
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index c6cb67f..354f281 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -57,6 +57,7 @@
 import android.app.ApplicationThreadConstants;
 import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
+import android.app.BroadcastStickyCache;
 import android.app.IApplicationThread;
 import android.app.compat.CompatChanges;
 import android.appwidget.AppWidgetManager;
@@ -183,6 +184,13 @@
     final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
 
     /**
+     * If {@code false} invalidate the list of {@link android.os.IpcDataCache} present inside the
+     * {@link BroadcastStickyCache} class.
+     * The invalidation is required to start caching of the sticky broadcast in the client side.
+     */
+    private volatile boolean mAreStickyCachesInvalidated = false;
+
+    /**
      * Resolver for broadcast intents to registered receivers.
      * Holds BroadcastFilter (subclass of IntentFilter).
      */
@@ -288,6 +296,11 @@
             IIntentReceiver receiver, IntentFilter filter, String permission,
             int userId, int flags) {
         mService.enforceNotIsolatedCaller("registerReceiver");
+
+        if (!mAreStickyCachesInvalidated) {
+            BroadcastStickyCache.invalidateAllCaches();
+            mAreStickyCachesInvalidated = true;
+        }
         ArrayList<StickyBroadcast> stickyBroadcasts = null;
         ProcessRecord callerApp = null;
         final boolean visibleToInstantApps =
@@ -700,6 +713,7 @@
             String[] excludedPackages, int appOp, Bundle bOptions,
             boolean serialized, boolean sticky, int userId) {
         mService.enforceNotIsolatedCaller("broadcastIntent");
+        final int result;
 
         synchronized (mService) {
             intent = verifyBroadcastLocked(intent);
@@ -722,7 +736,7 @@
 
             final long origId = Binder.clearCallingIdentity();
             try {
-                return broadcastIntentLocked(callerApp,
+                result = broadcastIntentLocked(callerApp,
                         callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
                         intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
                         resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
@@ -733,6 +747,11 @@
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
+
+        if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
+            BroadcastStickyCache.invalidateCache(intent.getAction());
+        }
+        return result;
     }
 
     // Not the binder call surface
@@ -743,6 +762,7 @@
             boolean serialized, boolean sticky, int userId,
             BackgroundStartPrivileges backgroundStartPrivileges,
             @Nullable int[] broadcastAllowList) {
+        final int result;
         synchronized (mService) {
             intent = verifyBroadcastLocked(intent);
 
@@ -750,7 +770,7 @@
             String[] requiredPermissions = requiredPermission == null ? null
                     : new String[] {requiredPermission};
             try {
-                return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
+                result =  broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
                         resultToApp, resultTo, resultCode, resultData, resultExtras,
                         requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
                         uid, realCallingUid, realCallingPid, userId,
@@ -760,6 +780,11 @@
                 Binder.restoreCallingIdentity(origId);
             }
         }
+
+        if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
+            BroadcastStickyCache.invalidateCache(intent.getAction());
+        }
+        return result;
     }
 
     @GuardedBy("mService")
@@ -1458,6 +1483,7 @@
                     list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
                             callingUid, callerAppProcessState, resolvedType));
                 }
+                BroadcastStickyCache.invalidateCache(intent.getAction());
             }
         }
 
@@ -1724,6 +1750,7 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
+        final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
         synchronized (mStickyBroadcasts) {
             ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
             if (stickies != null) {
@@ -1740,12 +1767,16 @@
                     if (list.size() <= 0) {
                         stickies.remove(intent.getAction());
                     }
+                    changedStickyBroadcasts.add(intent.getAction());
                 }
                 if (stickies.size() <= 0) {
                     mStickyBroadcasts.remove(userId);
                 }
             }
         }
+        for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
+            BroadcastStickyCache.invalidateCache(changedStickyBroadcasts.get(i));
+        }
     }
 
     void finishReceiver(IBinder caller, int resultCode, String resultData,
@@ -2124,9 +2155,18 @@
     }
 
     void removeStickyBroadcasts(int userId) {
+        final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
         synchronized (mStickyBroadcasts) {
+            final ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
+                    mStickyBroadcasts.get(userId);
+            if (stickies != null) {
+                changedStickyBroadcasts.addAll(stickies.keySet());
+            }
             mStickyBroadcasts.remove(userId);
         }
+        for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
+            BroadcastStickyCache.invalidateCache(changedStickyBroadcasts.get(i));
+        }
     }
 
     @NeverCompile
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index a32d3cb..05aeb42 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -57,7 +57,6 @@
     final boolean visibleToInstantApp;
     public final boolean exported;
     final int initialPriority;
-    final ApplicationInfo applicationInfo;
 
     BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList,
             String _packageName, String _featureId, String _receiverId, String _requiredPermission,
@@ -74,10 +73,9 @@
         instantApp = _instantApp;
         visibleToInstantApp = _visibleToInstantApp;
         exported = _exported;
-        applicationInfo = _applicationInfo;
         initialPriority = getPriority();
         setPriority(calculateAdjustedPriority(owningUid, initialPriority,
-                applicationInfo, platformCompat));
+                _applicationInfo, platformCompat));
     }
 
     public @Nullable String getReceiverClassName() {
@@ -91,7 +89,7 @@
     }
 
     public @NonNull ApplicationInfo getApplicationInfo() {
-        return applicationInfo;
+        return receiverList.app.info;
     }
 
     @NeverCompile
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index ab7cd5f..1a6051b 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -66,6 +66,9 @@
 # Activity Security
 per-file ActivityManager* = file:/ACTIVITY_SECURITY_OWNERS
 
+# Aconfig Flags
+per-file flags.aconfig = yamasani@google.com, bills@google.com, nalini@google.com
+
 # Londoners
 michaelwr@google.com #{LAST_RESORT_SUGGESTION}
 narayan@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index b84bf6b9..f42641e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1376,9 +1376,11 @@
             ProcessRecord app = lruList.get(i);
             final ProcessStateRecord state = app.mState;
             if (!app.isKilledByAm() && app.getThread() != null) {
-                // We don't need to apply the update for the process which didn't get computed
-                if (state.getCompletedAdjSeq() == mAdjSeq) {
-                    applyOomAdjLSP(app, doingAll, now, nowElapsed, oomAdjReason, true);
+                if (!Flags.fixApplyOomadjOrder()) {
+                    // We don't need to apply the update for the process which didn't get computed
+                    if (state.getCompletedAdjSeq() == mAdjSeq) {
+                        applyOomAdjLSP(app, doingAll, now, nowElapsed, oomAdjReason, true);
+                    }
                 }
 
                 if (app.isPendingFinishAttach()) {
@@ -1480,6 +1482,19 @@
             }
         }
 
+        if (Flags.fixApplyOomadjOrder()) {
+            // We need to apply the update starting from the least recently used.
+            // Otherwise, they won't be in the correct LRU order in LMKD.
+            for (int i = 0; i < numLru; i++) {
+                ProcessRecord app = lruList.get(i);
+                // We don't need to apply the update for the process which didn't get computed
+                if (!app.isKilledByAm() && app.getThread() != null
+                        && app.mState.getCompletedAdjSeq() == mAdjSeq) {
+                    applyOomAdjLSP(app, doingAll, now, nowElapsed, oomAdjReason, true);
+                }
+            }
+        }
+
         if (!mProcsToOomAdj.isEmpty()) {
             mInjector.batchSetOomAdj(mProcsToOomAdj);
             mProcsToOomAdj.clear();
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index f86474f..70febcd 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -22,6 +22,7 @@
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
 import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
+import static android.content.pm.Flags.appCompatOption16kb;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
@@ -2025,6 +2026,16 @@
                 runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE;
             }
 
+            if (appCompatOption16kb()) {
+                boolean is16KbDevice = Os.sysconf(OsConstants._SC_PAGESIZE) == 16384;
+                if (is16KbDevice
+                        && mService.mContext
+                        .getPackageManager()
+                        .isPageSizeCompatEnabled(app.info.packageName)) {
+                    runtimeFlags |= Zygote.ENABLE_PAGE_SIZE_APP_COMPAT;
+                }
+            }
+
             String invokeWith = null;
             if (debuggableFlag) {
                 // Debuggable apps may include a wrapper script with their library directory.
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 78c4f74..d0153d8 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -42,7 +42,6 @@
 import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
 import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
 import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
-import static com.android.aconfig.flags.Flags.enableSystemAconfigdRust;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -140,6 +139,13 @@
     // The list is sorted.
     @VisibleForTesting
     static final String[] sDeviceConfigAconfigScopes = new String[] {
+        "tv_os",
+        "aaos_carframework_triage",
+        "aaos_performance_triage",
+        "aaos_user_triage",
+        "aaos_window_triage",
+        "aaos_audio_triage",
+        "aaos_power_triage",
         "aaos_sdv",
         "accessibility",
         "android_core_networking",
@@ -459,9 +465,8 @@
     static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) {
         // connect to aconfigd socket
         LocalSocket client = new LocalSocket();
-        String socketName = enableSystemAconfigdRust()
-                    ? "aconfigd_system" : "aconfigd";
-        try{
+        String socketName = "aconfigd_system";
+        try {
             client.connect(new LocalSocketAddress(
                 socketName, LocalSocketAddress.Namespace.RESERVED));
             Slog.d(TAG, "connected to aconfigd socket");
@@ -533,9 +538,8 @@
      * @param packageName the package of the flag
      * @param flagName the name of the flag
      * @param immediate if true, clear immediately; otherwise, clear on next reboot
-     *
-     * @hide
      */
+    @VisibleForTesting
     public static void writeFlagOverrideRemovalRequest(
         ProtoOutputStream proto, String packageName, String flagName, boolean immediate) {
       long msgsToken = proto.start(StorageRequestMessages.MSGS);
@@ -591,7 +595,8 @@
      * apply flag local override in aconfig new storage
      * @param props
      */
-    static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
+    @VisibleForTesting
+    public static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
         int num_requests = 0;
         ProtoOutputStream requests = new ProtoOutputStream();
         for (String flagName : props.getKeyset()) {
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 6d247d2..89e4a8d 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -245,6 +245,14 @@
 }
 
 flag {
+    name: "fix_apply_oomadj_order"
+    namespace: "backstage_power"
+    is_fixed_read_only: true
+    description: "Fix the iteration direction of process LRU list when applying oom adj"
+    bug: "378580264"
+}
+
+flag {
     name: "phantom_processes_fix"
     namespace: "backstage_power"
     description: "Make sure setProcessGroupForPhantomProcessOfApp deals with phantom processes properly"
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 8c5152f..6f8dc10 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -100,6 +100,7 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.SystemService.TargetUser;
+import com.android.server.utils.LazyJniRegistrar;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.CompatScaleProvider;
 
@@ -158,6 +159,10 @@
     private static final String GAME_MODE_INTERVENTION_LIST_FILE_NAME =
             "game_mode_intervention.list";
 
+    static {
+        LazyJniRegistrar.registerGameManagerService();
+    }
+
     private final Context mContext;
     private final Object mLock = new Object();
     private final Object mDeviceConfigLock = new Object();
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 06c586f..295e044 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3191,7 +3191,7 @@
                     resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
                     Process.INVALID_UID, null, null,
                     Context.DEVICE_ID_DEFAULT, proxyFlags, !isProxyTrusted,
-                    "proxy " + message, shouldCollectMessage);
+                    "proxy " + message, shouldCollectMessage, 1);
             if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
                 return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
                         proxiedPackageName);
@@ -3210,7 +3210,20 @@
         return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
                 proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolveProxyPackageName,
                 proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, shouldCollectAsyncNotedOp,
-                message, shouldCollectMessage);
+                message, shouldCollectMessage, 1);
+    }
+
+    @Override
+    public void noteOperationsInBatch(Map batchedNoteOps) {
+        for (var entry : ((Map<AppOpsManager.NotedOp, Integer>) batchedNoteOps).entrySet()) {
+            AppOpsManager.NotedOp notedOp = entry.getKey();
+            int notedCount = entry.getValue();
+            mCheckOpsDelegateDispatcher.noteOperation(
+                    notedOp.getOp(), notedOp.getUid(), notedOp.getPackageName(),
+                    notedOp.getAttributionTag(), notedOp.getVirtualDeviceId(),
+                    notedOp.getShouldCollectAsyncNotedOp(), notedOp.getMessage(),
+                    notedOp.getShouldCollectMessage(), notedCount);
+        }
     }
 
     @Override
@@ -3228,7 +3241,7 @@
         }
         return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
                 attributionTag, Context.DEVICE_ID_DEFAULT, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage);
+                shouldCollectMessage, 1);
     }
 
     @Override
@@ -3237,13 +3250,12 @@
             String message, boolean shouldCollectMessage) {
         return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
                 attributionTag, virtualDeviceId, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage);
+                shouldCollectMessage, 1);
     }
 
     private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName,
-             @Nullable String attributionTag, int virtualDeviceId,
-             boolean shouldCollectAsyncNotedOp, @Nullable String message,
-             boolean shouldCollectMessage) {
+            @Nullable String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
+            @Nullable String message, boolean shouldCollectMessage, int notedCount) {
         String resolvedPackageName;
         if (!shouldUseNewCheckOp()) {
             verifyIncomingUid(uid);
@@ -3278,14 +3290,14 @@
         return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
                 virtualDeviceId, Process.INVALID_UID, null, null,
                 Context.DEVICE_ID_DEFAULT, AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp,
-                message, shouldCollectMessage);
+                message, shouldCollectMessage, notedCount);
     }
 
     private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
             @Nullable String attributionTag, int virtualDeviceId, int proxyUid,
             String proxyPackageName, @Nullable String proxyAttributionTag, int proxyVirtualDeviceId,
             @OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message,
-            boolean shouldCollectMessage) {
+            boolean shouldCollectMessage, int notedCount) {
         PackageVerificationResult pvr;
         try {
             pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3388,11 +3400,11 @@
                     virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED);
 
             attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
-                    getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags);
+                    getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags, notedCount);
 
             if (shouldCollectAsyncNotedOp) {
                 collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
-                        shouldCollectMessage);
+                        shouldCollectMessage, notedCount);
             }
 
             return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
@@ -3551,7 +3563,7 @@
      */
     private void collectAsyncNotedOp(int uid, @NonNull String packageName, int opCode,
             @Nullable String attributionTag, @OpFlags int flags, @NonNull String message,
-            boolean shouldCollectMessage) {
+            boolean shouldCollectMessage, int notedCount) {
         Objects.requireNonNull(message);
 
         int callingUid = Binder.getCallingUid();
@@ -3559,42 +3571,51 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
-
-                RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
-                AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid,
-                        attributionTag, message, System.currentTimeMillis());
-                final boolean[] wasNoteForwarded = {false};
-
                 if ((flags & (OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED)) != 0
                         && shouldCollectMessage) {
                     reportRuntimeAppOpAccessMessageAsyncLocked(uid, packageName, opCode,
                             attributionTag, message);
                 }
 
-                if (callbacks != null) {
+                Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+                RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+                if (callbacks == null) {
+                    return;
+                }
+
+                final boolean[] wasNoteForwarded = {false};
+                if (Flags.rateLimitBatchedNoteOpAsyncCallbacksEnabled()) {
+                    notedCount = 1;
+                }
+
+                for (int i = 0; i < notedCount; i++) {
+                    AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid,
+                            attributionTag, message, System.currentTimeMillis());
+                    wasNoteForwarded[0] = false;
                     callbacks.broadcast((cb) -> {
                         try {
                             cb.opNoted(asyncNotedOp);
                             wasNoteForwarded[0] = true;
                         } catch (RemoteException e) {
                             Slog.e(TAG,
-                                    "Could not forward noteOp of " + opCode + " to " + packageName
+                                    "Could not forward noteOp of " + opCode + " to "
+                                            + packageName
                                             + "/" + uid + "(" + attributionTag + ")", e);
                         }
                     });
-                }
 
-                if (!wasNoteForwarded[0]) {
-                    ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(key);
-                    if (unforwardedOps == null) {
-                        unforwardedOps = new ArrayList<>(1);
-                        mUnforwardedAsyncNotedOps.put(key, unforwardedOps);
-                    }
+                    if (!wasNoteForwarded[0]) {
+                        ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(
+                                key);
+                        if (unforwardedOps == null) {
+                            unforwardedOps = new ArrayList<>(1);
+                            mUnforwardedAsyncNotedOps.put(key, unforwardedOps);
+                        }
 
-                    unforwardedOps.add(asyncNotedOp);
-                    if (unforwardedOps.size() > MAX_UNFORWARDED_OPS) {
-                        unforwardedOps.remove(0);
+                        unforwardedOps.add(asyncNotedOp);
+                        if (unforwardedOps.size() > MAX_UNFORWARDED_OPS) {
+                            unforwardedOps.remove(0);
+                        }
                     }
                 }
             }
@@ -4026,7 +4047,7 @@
 
         if (shouldCollectAsyncNotedOp && !isRestricted) {
             collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
-                    message, shouldCollectMessage);
+                    message, shouldCollectMessage, 1);
         }
 
         return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
@@ -7574,34 +7595,36 @@
 
         public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
                 String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
-                String message, boolean shouldCollectMessage) {
+                String message, boolean shouldCollectMessage, int notedCount) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
                     return mPolicy.noteOperation(code, uid, packageName, attributionTag,
                             virtualDeviceId, shouldCollectAsyncNotedOp, message,
-                            shouldCollectMessage, this::noteDelegateOperationImpl
+                            shouldCollectMessage, notedCount, this::noteDelegateOperationImpl
                     );
                 } else {
                     return mPolicy.noteOperation(code, uid, packageName, attributionTag,
                             virtualDeviceId, shouldCollectAsyncNotedOp, message,
-                            shouldCollectMessage, AppOpsService.this::noteOperationImpl
+                            shouldCollectMessage, notedCount, AppOpsService.this::noteOperationImpl
                     );
                 }
             } else if (mCheckOpsDelegate != null) {
                 return noteDelegateOperationImpl(code, uid, packageName, attributionTag,
-                        virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                        virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+                        notedCount);
             }
             return noteOperationImpl(code, uid, packageName, attributionTag,
-                    virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                    virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+                    notedCount);
         }
 
         private SyncNotedAppOp noteDelegateOperationImpl(int code, int uid,
                 @Nullable String packageName, @Nullable String featureId, int virtualDeviceId,
                 boolean shouldCollectAsyncNotedOp, @Nullable String message,
-                boolean shouldCollectMessage) {
+                boolean shouldCollectMessage, int notedCount) {
             return mCheckOpsDelegate.noteOperation(code, uid, packageName, featureId,
                     virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                    AppOpsService.this::noteOperationImpl
+                    notedCount, AppOpsService.this::noteOperationImpl
             );
         }
 
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 314664b..4d114b4 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -100,10 +100,12 @@
      * @param proxyDeviceId       The device Id of the proxy
      * @param uidState            UID state of the app noteOp/startOp was called for
      * @param flags               OpFlags of the call
+     * @param accessCount         The number of times the op is accessed
      */
     public void accessed(int proxyUid, @Nullable String proxyPackageName,
             @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
-            @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
+            @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+            int accessCount) {
         long accessTime = System.currentTimeMillis();
         accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId,
                 uidState, flags);
@@ -111,7 +113,7 @@
         mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                 parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime,
                 AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE,
-                DiscreteRegistry.ACCESS_TYPE_NOTE_OP);
+                DiscreteRegistry.ACCESS_TYPE_NOTE_OP, accessCount);
     }
 
     /**
@@ -255,7 +257,7 @@
         if (isStarted) {
             mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                     parent.packageName, persistentDeviceId, tag, uidState, flags, startTime,
-                    attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP);
+                    attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP, 1);
         }
     }
 
@@ -451,7 +453,7 @@
             mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                     parent.packageName, persistentDeviceId, tag, event.getUidState(),
                     event.getFlags(), startTime, event.getAttributionFlags(),
-                    event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP);
+                    event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP, 1);
             if (shouldSendActive) {
                 mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
                         parent.packageName, tag, event.getVirtualDeviceId(), true,
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 6b02538..5e67f26 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -475,7 +475,7 @@
             @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
             @OpFlags int flags, long accessTime,
             @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
-            @DiscreteRegistry.AccessType int accessType) {
+            @DiscreteRegistry.AccessType int accessType, int accessCount) {
         synchronized (mInMemoryLock) {
             if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
                 if (!isPersistenceInitializedMLocked()) {
@@ -484,7 +484,7 @@
                 }
                 getUpdatedPendingHistoricalOpsMLocked(
                         System.currentTimeMillis()).increaseAccessCount(op, uid, packageName,
-                        attributionTag, uidState, flags, 1);
+                        attributionTag, uidState, flags, accessCount);
 
                 mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op,
                         attributionTag, flags, uidState, accessTime, -1, attributionFlags,
diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
index 4736918..7502664 100644
--- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
+++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
@@ -27,6 +27,7 @@
 import static android.Manifest.permission.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION;
 import static android.Manifest.permission.MODIFY_AUDIO_ROUTING;
 import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS;
+import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
 import static android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS;
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
 import static android.Manifest.permission.RECORD_AUDIO;
@@ -84,6 +85,8 @@
         MONITORED_PERMS[PermissionEnum.BLUETOOTH_CONNECT] = BLUETOOTH_CONNECT;
         MONITORED_PERMS[PermissionEnum.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION] =
                 BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION;
+        MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_SETTINGS_PRIVILEGED] =
+                MODIFY_AUDIO_SETTINGS_PRIVILEGED;
     }
 
     private final Object mLock = new Object();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5c2eb5c..2f7a54d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4954,31 +4954,24 @@
         }
 
         final Set<Integer> deviceTypes = getDeviceSetForStreamDirect(streamType);
+        Set<Integer> absVolumeDeviceTypes = new ArraySet<>(
+                AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
+        absVolumeDeviceTypes.addAll(mAbsVolumeMultiModeCaseDevices);
 
-        final Set<Integer> a2dpDevices = AudioSystem.intersectionAudioDeviceTypes(
-                AudioSystem.DEVICE_OUT_ALL_A2DP_SET, deviceTypes);
-        if (!a2dpDevices.isEmpty()) {
-            int index = getStreamVolume(streamType,
-                    a2dpDevices.toArray(new Integer[0])[0].intValue());
-            mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index);
-        }
-
-        final Set<Integer> absVolumeMultiModeCaseDevices =
-                AudioSystem.intersectionAudioDeviceTypes(
-                        mAbsVolumeMultiModeCaseDevices, deviceTypes);
-        if (absVolumeMultiModeCaseDevices.isEmpty()) {
+        final Set<Integer> absVolumeDevices =
+                AudioSystem.intersectionAudioDeviceTypes(absVolumeDeviceTypes, deviceTypes);
+        if (absVolumeDevices.isEmpty()) {
             return;
         }
-        if (absVolumeMultiModeCaseDevices.size() > 1) {
+        if (absVolumeDevices.size() > 1) {
             Log.w(TAG, "onUpdateContextualVolumes too many active devices: "
-                    + absVolumeMultiModeCaseDevices.stream().map(AudioSystem::getOutputDeviceName)
+                    + absVolumeDevices.stream().map(AudioSystem::getOutputDeviceName)
                         .collect(Collectors.joining(","))
                     + ", for stream: " + streamType);
             return;
         }
 
-        final int device = absVolumeMultiModeCaseDevices.toArray(new Integer[0])[0].intValue();
-
+        final int device = absVolumeDevices.toArray(new Integer[0])[0].intValue();
         final int index = getStreamVolume(streamType, device);
 
         if (DEBUG_VOL) {
@@ -4992,6 +4985,8 @@
                     getVssForStreamOrDefault(streamType).getMaxIndex(), streamType);
         } else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
             mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
+        } else if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)) {
+            mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index);
         } else {
             return;
         }
@@ -9559,9 +9554,11 @@
                             index = (mIndexMap.valueAt(i) + 5)/10;
                         }
 
-                        sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION,
-                                SENDMSG_REPLACE, device,  isAbsoluteVolume ? 1 : 0, this,
-                                /*delay=*/0);
+                        if (mStreamType == AudioSystem.STREAM_MUSIC) {
+                            sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION,
+                                    SENDMSG_QUEUE, device, isAbsoluteVolume ? 1 : 0, this,
+                                    /*delay=*/0);
+                        }
 
                         setStreamVolumeIndex(index, device);
                     }
@@ -10108,7 +10105,7 @@
     /*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {
 
         synchronized (VolumeStreamState.class) {
-            sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION, SENDMSG_REPLACE,
+            sendMsg(mAudioHandler, SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION, SENDMSG_QUEUE,
                     device, (isAbsoluteVolumeDevice(device) || isA2dpAbsoluteVolumeDevice(device)
                             || AudioSystem.isLeAudioDeviceType(device) ? 1 : 0),
                     streamState, /*delay=*/0);
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 995e16b..1b5f0e5 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -657,8 +657,8 @@
                     return "CSD lowering volume to RS1";
                 case UPDATE_ABS_VOLUME_ATTENUATION:
                     return String.format(java.util.Locale.US,
-                            "Updating CSD absolute volume attenuation on device %d with %.2f dB ",
-                            mLongValue, mFloatValue);
+                            "Updating CSD absolute volume attenuation on device 0x%s with %.2f dB ",
+                            Long.toHexString(mLongValue), mFloatValue);
             }
             return new StringBuilder("FIXME invalid event type:").append(mEventType).toString();
         }
diff --git a/services/core/java/com/android/server/audio/OWNERS b/services/core/java/com/android/server/audio/OWNERS
index b70de29..709e4c2 100644
--- a/services/core/java/com/android/server/audio/OWNERS
+++ b/services/core/java/com/android/server/audio/OWNERS
@@ -1,2 +1,3 @@
+atneya@google.com
 jmtrivi@google.com
 elaurent@google.com
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index a62ac82..1c01fb9 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,13 +17,14 @@
 package com.android.server.audio;
 
 import static android.media.AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_MUTE;
-import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_OP_PLAY_AUDIO;
 import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME;
 import static android.media.AudioPlaybackConfiguration.MUTED_BY_MASTER;
 import static android.media.AudioPlaybackConfiguration.MUTED_BY_PORT_VOLUME;
 import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_MUTED;
 import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_VOLUME;
 import static android.media.AudioPlaybackConfiguration.MUTED_BY_VOLUME_SHAPER;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_OP_CONTROL_AUDIO;
 import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
 import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED;
 
@@ -1376,8 +1377,8 @@
                         if ((eventValue & MUTED_BY_STREAM_MUTED) != 0) {
                             builder.append("streamMute ");
                         }
-                        if ((eventValue & MUTED_BY_APP_OPS) != 0) {
-                            builder.append("appOps ");
+                        if ((eventValue & MUTED_BY_OP_PLAY_AUDIO) != 0) {
+                            builder.append("opPlayAudio ");
                         }
                         if ((eventValue & MUTED_BY_CLIENT_VOLUME) != 0) {
                             builder.append("clientVolume ");
@@ -1388,6 +1389,10 @@
                         if ((eventValue & MUTED_BY_PORT_VOLUME) != 0) {
                             builder.append("portVolume ");
                         }
+                        if ((eventValue & MUTED_BY_OP_CONTROL_AUDIO) != 0) {
+                            builder.append("opControlAudio ");
+                        }
+
                     }
                     return builder.toString();
                 default:
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index afa90d5..608edbb 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -129,6 +129,8 @@
     /** current level as reported by native Spatializer in callback */
     private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
     private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+    /** cached version of Spatializer.getSpatializedChannelMasks */
+    private List<Integer> mSpatializedChannelMasks = Collections.emptyList();
 
     private boolean mTransauralSupported = false;
     private boolean mBinauralSupported = false;
@@ -1030,6 +1032,17 @@
                 return;
             }
             try {
+                final int[] nativeMasks = mSpat.getSpatializedChannelMasks();
+                for (int i = 0; i < nativeMasks.length; i++) {
+                    nativeMasks[i] = AudioFormat.convertNativeChannelMaskToOutMask(nativeMasks[i]);
+                }
+                mSpatializedChannelMasks = Arrays.stream(nativeMasks).boxed().toList();
+
+            } catch (Exception e) { // just catch Exception in case nativeMasks is null
+                Log.e(TAG, "Error calling getSpatializedChannelMasks", e);
+                mSpatializedChannelMasks = Collections.emptyList();
+            }
+            try {
                 //TODO: register heatracking callback only when sensors are registered
                 if (mIsHeadTrackingSupported) {
                     mActualHeadTrackingMode =
@@ -1103,20 +1116,7 @@
     }
 
     synchronized @NonNull List<Integer> getSpatializedChannelMasks() {
-        if (!checkSpatializer("getSpatializedChannelMasks")) {
-            return Collections.emptyList();
-        }
-        try {
-            final int[] nativeMasks = new int[0]; // FIXME mSpat query goes here
-            for (int i = 0; i < nativeMasks.length; i++) {
-                nativeMasks[i] = AudioFormat.convertNativeChannelMaskToOutMask(nativeMasks[i]);
-            }
-            final List<Integer> masks = Arrays.stream(nativeMasks).boxed().toList();
-            return masks;
-        } catch (Exception e) { // just catch Exception in case nativeMasks is null
-            Log.e(TAG, "Error calling getSpatializedChannelMasks", e);
-            return Collections.emptyList();
-        }
+        return mSpatializedChannelMasks;
     }
 
     //------------------------------------------------------
@@ -1622,6 +1622,14 @@
         pw.println("\tmState:" + mState);
         pw.println("\tmSpatLevel:" + mSpatLevel);
         pw.println("\tmCapableSpatLevel:" + mCapableSpatLevel);
+        List<Integer> speakerMasks = getSpatializedChannelMasks();
+        StringBuilder masks = speakerMasks.isEmpty()
+                ? new StringBuilder("none") : new StringBuilder("");
+        for (Integer mask : speakerMasks) {
+            masks.append(AudioFormat.javaChannelOutMaskToString(mask)).append(" ");
+        }
+        pw.println("\tspatialized speaker masks: " + masks);
+
         pw.println("\tmIsHeadTrackingSupported:" + mIsHeadTrackingSupported);
         StringBuilder modesString = new StringBuilder();
         for (int mode : mSupportedHeadTrackingModes) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 97a8854..b365ef7 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -434,21 +434,12 @@
 
         public boolean getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(int userId) {
             if (!mMandatoryBiometricsEnabled.containsKey(userId)) {
-                Slog.d(TAG, "update mb toggle for user " + userId);
                 updateMandatoryBiometricsForAllProfiles(userId);
             }
             if (!mMandatoryBiometricsRequirementsSatisfied.containsKey(userId)) {
-                Slog.d(TAG, "update mb reqs for user " + userId);
                 updateMandatoryBiometricsRequirementsForAllProfiles(userId);
             }
 
-            Slog.d(TAG, mMandatoryBiometricsEnabled.getOrDefault(userId,
-                    DEFAULT_MANDATORY_BIOMETRICS_STATUS)
-                    + " " + mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
-                    DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS)
-                    + " " + getEnabledForApps(userId)
-                    + " " + (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */)
-                    || mFaceEnrolledForUser.getOrDefault(userId, false /* default */)));
             return mMandatoryBiometricsEnabled.getOrDefault(userId,
                     DEFAULT_MANDATORY_BIOMETRICS_STATUS)
                     && mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index d3da8dd..95ba695 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -34,3 +34,10 @@
       purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "frr_dialog_improvement"
+  namespace: "biometrics_framework"
+  description: "This flag controls FRR dialog improvement"
+  bug: "380800403"
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java b/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
index eed2bdd..958ab8e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
@@ -18,6 +18,8 @@
 
 import android.util.SparseArray;
 
+import java.util.concurrent.ConcurrentHashMap;
+
 /**
  * Tracks biometric performance across sensors and users.
  */
@@ -25,17 +27,12 @@
 
     private static final String TAG = "PerformanceTracker";
     // Keyed by SensorId
-    private static SparseArray<PerformanceTracker> sTrackers;
+    private static final ConcurrentHashMap<Integer, PerformanceTracker> sTrackers =
+            new ConcurrentHashMap<>();
 
     public static PerformanceTracker getInstanceForSensorId(int sensorId) {
-        if (sTrackers == null) {
-            sTrackers = new SparseArray<>();
-        }
-
-        if (!sTrackers.contains(sensorId)) {
-            sTrackers.put(sensorId, new PerformanceTracker());
-        }
-        return sTrackers.get(sensorId);
+        PerformanceTracker tracker = sTrackers.putIfAbsent(sensorId, new PerformanceTracker());
+        return tracker != null ? tracker : sTrackers.get(sensorId);
     }
 
     private static class Info {
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 36e4a7e..944e85c 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
@@ -851,13 +851,8 @@
             for (int i = 0; i < mFaceSensors.size(); i++) {
                 final Sensor sensor = mFaceSensors.valueAt(i);
                 final int sensorId = mFaceSensors.keyAt(i);
-                final PerformanceTracker performanceTracker = PerformanceTracker.getInstanceForSensorId(
-                        sensorId);
-                if (performanceTracker != null) {
-                    performanceTracker.incrementHALDeathCount();
-                } else {
-                    Slog.w(getTag(), "Performance tracker is null. Not counting HAL death.");
-                }
+                PerformanceTracker.getInstanceForSensorId(sensorId)
+                        .incrementHALDeathCount();
                 sensor.onBinderDied();
             }
         });
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index e1bb8a1..4c5f652 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -26,13 +26,13 @@
 import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
 import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_AUTO;
 import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_AUTO;
+import static android.net.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
 import static android.os.PowerWhitelistManager.REASON_VPN;
 import static android.os.UserHandle.PER_USER_RANGE;
 import static android.telephony.CarrierConfigManager.KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
 import static android.telephony.CarrierConfigManager.KEY_PREFERRED_IKE_PROTOCOL_INT;
 
 import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
-import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
 
 import static java.util.Objects.requireNonNull;
 
@@ -103,6 +103,8 @@
 import android.net.ipsec.ike.exceptions.IkeTimeoutException;
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.util.MtuUtils;
+import android.net.vcn.util.PersistableBundleUtils;
 import android.os.Binder;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -150,8 +152,6 @@
 import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
 import com.android.server.net.BaseNetworkObserver;
-import com.android.server.vcn.util.MtuUtils;
-import com.android.server.vcn.util.PersistableBundleUtils;
 
 import libcore.io.IoUtils;
 
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 8b9c664..2513443 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -19,7 +19,16 @@
 import static android.Manifest.permission.CONTROL_DEVICE_STATE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FEATURE_DUAL_DISPLAY;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FEATURE_REAR_DISPLAY;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.frameworks.devicestate.DeviceStateConfiguration.DeviceStatePropertyValue.FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
 import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST;
 import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
@@ -44,6 +53,10 @@
 import android.app.ActivityManagerInternal;
 import android.app.IProcessObserver;
 import android.content.Context;
+import android.frameworks.devicestate.DeviceStateConfiguration;
+import android.frameworks.devicestate.ErrorCode;
+import android.frameworks.devicestate.IDeviceStateListener;
+import android.frameworks.devicestate.IDeviceStateService;
 import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateInfo;
 import android.hardware.devicestate.DeviceStateManager;
@@ -56,9 +69,12 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.os.ShellCallback;
 import android.os.SystemProperties;
 import android.os.Trace;
+import android.util.LongSparseLongArray;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -82,6 +98,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.WeakHashMap;
@@ -130,6 +147,8 @@
     @NonNull
     private final BinderService mBinderService;
     @NonNull
+    private final HalService mHalService;
+    @NonNull
     private final OverrideRequestController mOverrideRequestController;
     @NonNull
     private final DeviceStateProviderListener mDeviceStateProviderListener;
@@ -139,7 +158,7 @@
 
     // All supported device states keyed by identifier.
     @GuardedBy("mLock")
-    private SparseArray<DeviceState> mDeviceStates = new SparseArray<>();
+    private final SparseArray<DeviceState> mDeviceStates = new SparseArray<>();
 
     // The current committed device state. Will be empty until the first device state provided by
     // the DeviceStateProvider is committed.
@@ -177,7 +196,7 @@
     @GuardedBy("mLock")
     private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
 
-    private Set<Integer> mDeviceStatesAvailableForAppRequests = new HashSet<>();
+    private final Set<Integer> mDeviceStatesAvailableForAppRequests = new HashSet<>();
 
     private Set<Integer> mFoldedDeviceStates = new HashSet<>();
 
@@ -259,6 +278,7 @@
         mDeviceStateProviderListener = new DeviceStateProviderListener();
         mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener);
         mBinderService = new BinderService();
+        mHalService = new HalService();
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
         mDeviceStateNotificationController = new DeviceStateNotificationController(
                 context, mHandler,
@@ -272,6 +292,10 @@
     @Override
     public void onStart() {
         publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService);
+        String halServiceName = IDeviceStateService.DESCRIPTOR + "/default";
+        if (ServiceManager.isDeclared(halServiceName)) {
+            publishBinderService(halServiceName, mHalService);
+        }
         publishLocalService(DeviceStateManagerInternal.class, new LocalService());
 
         if (!Flags.deviceStatePropertyMigration()) {
@@ -440,6 +464,11 @@
         return mBinderService;
     }
 
+    @VisibleForTesting
+    IDeviceStateService getHalBinderService() {
+        return mHalService;
+    }
+
     private void updateSupportedStates(DeviceState[] supportedDeviceStates,
             @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
         synchronized (mLock) {
@@ -1282,6 +1311,124 @@
         }
     }
 
+    private final class HalService extends IDeviceStateService.Stub {
+        private final LongSparseLongArray mPublicProperties = new LongSparseLongArray();
+        public HalService() {
+            mPublicProperties.put(
+                    DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED,
+                    FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED);
+            mPublicProperties.put(
+                    DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN,
+                    FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN);
+            mPublicProperties.put(
+                    DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN,
+                    FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN);
+            mPublicProperties.put(
+                    PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+                    FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY);
+            mPublicProperties.put(
+                    PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY,
+                    FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY);
+            mPublicProperties.put(
+                    PROPERTY_FEATURE_REAR_DISPLAY,
+                    FEATURE_REAR_DISPLAY);
+            mPublicProperties.put(
+                    PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT,
+                    FEATURE_DUAL_DISPLAY);
+        }
+
+        private final class HalBinderCallback implements IDeviceStateManagerCallback {
+            private final IDeviceStateListener mListener;
+
+            private HalBinderCallback(@NonNull IDeviceStateListener listener) {
+                mListener = listener;
+            }
+
+            @Override
+            public void onDeviceStateInfoChanged(DeviceStateInfo info) throws RemoteException {
+                DeviceStateConfiguration config = new DeviceStateConfiguration();
+                Set<Integer> systemProperties = new HashSet<>(
+                        info.currentState.getConfiguration().getSystemProperties());
+                Set<Integer> physicalProperties = new HashSet<>(
+                        info.currentState.getConfiguration().getPhysicalProperties());
+                config.deviceProperties = 0;
+                for (Integer prop : systemProperties) {
+                    Long publicProperty = mPublicProperties.get(prop);
+                    if (publicProperty != null) {
+                        config.deviceProperties |= publicProperty.longValue();
+                    }
+                }
+                for (Integer prop : physicalProperties) {
+                    Long publicProperty = mPublicProperties.get(prop);
+                    if (publicProperty != null) {
+                        config.deviceProperties |= publicProperty.longValue();
+                    }
+                }
+                mListener.onDeviceStateChanged(config);
+            }
+
+            @Override
+            public void onRequestActive(IBinder token) {
+                //No-op
+            }
+
+            @Override
+            public void onRequestCanceled(IBinder token) {
+                //No-op
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return mListener.asBinder();
+            }
+        }
+
+        @Override
+        public void registerListener(IDeviceStateListener listener) throws RemoteException {
+            if (listener == null) {
+                throw new ServiceSpecificException(ErrorCode.BAD_INPUT);
+            }
+
+            final int callingPid = Binder.getCallingPid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                HalBinderCallback callback = new HalBinderCallback(listener);
+                DeviceStateInfo info = registerProcess(callingPid, callback);
+                if (info != null)  {
+                    callback.onDeviceStateInfoChanged(info);
+                }
+            } catch (SecurityException e) {
+                throw new ServiceSpecificException(ErrorCode.ALREADY_EXISTS);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void unregisterListener(IDeviceStateListener listener) throws RemoteException {
+            final int callingPid = Binder.getCallingPid();
+
+            synchronized (mLock) {
+                if (mProcessRecords.contains(callingPid)) {
+                    mProcessRecords.remove(callingPid);
+                    return;
+                }
+            }
+
+            throw new ServiceSpecificException(ErrorCode.BAD_INPUT);
+        }
+
+        @Override
+        public int getInterfaceVersion() throws RemoteException {
+            return IDeviceStateService.VERSION;
+        }
+
+        @Override
+        public String getInterfaceHash() throws RemoteException {
+            return IDeviceStateService.HASH;
+        }
+    }
+
     /** Implementation of {@link IDeviceStateManager} published as a binder service. */
     private final class BinderService extends IDeviceStateManager.Stub {
 
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 0f65360..c19d2c9 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -130,6 +130,8 @@
     private static final int MSG_START_SENSOR_LISTENER = 3;
     private static final int MSG_SHOULD_COLLECT_COLOR_SAMPLE_CHANGED = 4;
     private static final int MSG_SENSOR_CHANGED = 5;
+    private static final int MSG_START_DISPLAY_LISTENER = 6;
+    private static final int MSG_STOP_DISPLAY_LISTENER = 7;
 
     private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
 
@@ -159,7 +161,9 @@
     private SensorListener mSensorListener;
     private Sensor mLightSensor;
     private SettingsObserver mSettingsObserver;
-    private DisplayListener mDisplayListener;
+    private final DisplayListener mDisplayListener = new DisplayListener();
+    private boolean mDisplayListenerRegistered;
+    private boolean mIsDisplayActive;
     private boolean mSensorRegistered;
     private boolean mColorSamplingEnabled;
     private int mNoFramesToSample;
@@ -231,6 +235,8 @@
 
         mSettingsObserver = new SettingsObserver(mBgHandler);
         mInjector.registerBrightnessModeObserver(mContentResolver, mSettingsObserver);
+        startDisplayListener();
+        mIsDisplayActive = isDisplayActive();
         startSensorListener();
 
         final IntentFilter intentFilter = new IntentFilter();
@@ -260,6 +266,7 @@
             Slog.d(TAG, "Stop");
         }
         mBgHandler.removeMessages(MSG_BACKGROUND_START);
+        stopDisplayListener();
         stopSensorListener();
         mInjector.unregisterSensorListener(mContext, mSensorListener);
         mInjector.unregisterBrightnessModeObserver(mContext, mSettingsObserver);
@@ -443,6 +450,11 @@
     private void handleSensorChanged(Sensor lightSensor) {
         if (mLightSensor != lightSensor) {
             mLightSensor = lightSensor;
+            if (lightSensor != null) {
+                mBgHandler.sendEmptyMessage(MSG_START_DISPLAY_LISTENER);
+            } else {
+                mBgHandler.sendEmptyMessage(MSG_STOP_DISPLAY_LISTENER);
+            }
             stopSensorListener();
             // Attempt to restart the sensor listener. It will check to see if it should be running
             // so there is no need to also check here.
@@ -455,6 +467,7 @@
                 && mLightSensor != null
                 && mAmbientBrightnessStatsTracker != null
                 && mInjector.isInteractive(mContext)
+                && mIsDisplayActive
                 && mInjector.isBrightnessModeAutomatic(mContentResolver)) {
             mAmbientBrightnessStatsTracker.start();
             mSensorRegistered = true;
@@ -825,11 +838,14 @@
         pw.println("  mColorSamplingEnabled=" + mColorSamplingEnabled);
         pw.println("  mNoFramesToSample=" + mNoFramesToSample);
         pw.println("  mFrameRate=" + mFrameRate);
+        pw.println("  mIsDisplayActive=" + mIsDisplayActive);
+        pw.println("  mDisplayListenerRegistered=" + mDisplayListenerRegistered);
     }
 
     private void enableColorSampling() {
         if (!mInjector.isBrightnessModeAutomatic(mContentResolver)
                 || !mInjector.isInteractive(mContext)
+                || !mIsDisplayActive
                 || mColorSamplingEnabled
                 || !mShouldCollectColorSample) {
             return;
@@ -860,10 +876,6 @@
                         + mNoFramesToSample + " frames, success=" + mColorSamplingEnabled);
             }
         }
-        if (mColorSamplingEnabled && mDisplayListener == null) {
-            mDisplayListener = new DisplayListener();
-            mInjector.registerDisplayListener(mContext, mDisplayListener, mBgHandler);
-        }
     }
 
     private void disableColorSampling() {
@@ -872,10 +884,6 @@
         }
         mInjector.enableColorSampling(/* enable= */ false, /* noFrames= */ 0);
         mColorSamplingEnabled = false;
-        if (mDisplayListener != null) {
-            mInjector.unRegisterDisplayListener(mContext, mDisplayListener);
-            mDisplayListener = null;
-        }
         if (DEBUG) {
             Slog.i(TAG, "turning off color sampling");
         }
@@ -913,6 +921,25 @@
         }
     }
 
+    private boolean isDisplayActive() {
+        return Display.isActiveState(mInjector.getDisplayState(mContext));
+    }
+
+    private void startDisplayListener() {
+        if (!mDisplayListenerRegistered && mLightSensor != null && mInjector.isInteractive(mContext)
+                && mInjector.isBrightnessModeAutomatic(mContentResolver)) {
+            mInjector.registerDisplayListener(mContext, mDisplayListener, mBgHandler);
+            mDisplayListenerRegistered = true;
+        }
+    }
+
+    private void stopDisplayListener() {
+        if (mDisplayListenerRegistered) {
+            mInjector.unregisterDisplayListener(mContext, mDisplayListener);
+            mDisplayListenerRegistered = false;
+        }
+    }
+
     private final class SensorListener implements SensorEventListener {
         @Override
         public void onSensorChanged(SensorEvent event) {
@@ -941,6 +968,15 @@
         public void onDisplayChanged(int displayId) {
             if (displayId == Display.DEFAULT_DISPLAY) {
                 updateColorSampling();
+                boolean isDisplayActive = isDisplayActive();
+                if (mIsDisplayActive != isDisplayActive) {
+                    mIsDisplayActive = isDisplayActive;
+                    if (isDisplayActive) {
+                        mBgHandler.obtainMessage(MSG_START_SENSOR_LISTENER).sendToTarget();
+                    } else {
+                        mBgHandler.obtainMessage(MSG_STOP_SENSOR_LISTENER).sendToTarget();
+                    }
+                }
             }
         }
     }
@@ -956,8 +992,10 @@
                 Slog.v(TAG, "settings change " + uri);
             }
             if (mInjector.isBrightnessModeAutomatic(mContentResolver)) {
+                mBgHandler.sendEmptyMessage(MSG_START_DISPLAY_LISTENER);
                 mBgHandler.obtainMessage(MSG_START_SENSOR_LISTENER).sendToTarget();
             } else {
+                mBgHandler.sendEmptyMessage(MSG_STOP_DISPLAY_LISTENER);
                 mBgHandler.obtainMessage(MSG_STOP_SENSOR_LISTENER).sendToTarget();
             }
         }
@@ -980,8 +1018,10 @@
                     batteryLevelChanged(level, scale);
                 }
             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                mBgHandler.sendEmptyMessage(MSG_STOP_DISPLAY_LISTENER);
                 mBgHandler.obtainMessage(MSG_STOP_SENSOR_LISTENER).sendToTarget();
             } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
+                mBgHandler.sendEmptyMessage(MSG_START_DISPLAY_LISTENER);
                 mBgHandler.obtainMessage(MSG_START_SENSOR_LISTENER).sendToTarget();
             }
         }
@@ -1023,7 +1063,12 @@
                 case MSG_SENSOR_CHANGED:
                     handleSensorChanged((Sensor) msg.obj);
                     break;
-
+                case MSG_START_DISPLAY_LISTENER:
+                    startDisplayListener();
+                    break;
+                case MSG_STOP_DISPLAY_LISTENER:
+                    stopDisplayListener();
+                    break;
             }
         }
     }
@@ -1151,6 +1196,12 @@
             return context.getSystemService(PowerManager.class).isInteractive();
         }
 
+        public int getDisplayState(Context context) {
+            final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+            Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+            return display.getState();
+        }
+
         public int getNightDisplayColorTemperature(Context context) {
             return context.getSystemService(ColorDisplayManager.class)
                     .getNightDisplayColorTemperature();
@@ -1208,7 +1259,7 @@
             displayManager.registerDisplayListener(listener, handler);
         }
 
-        public void unRegisterDisplayListener(Context context,
+        public void unregisterDisplayListener(Context context,
                 DisplayManager.DisplayListener listener) {
             final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
             displayManager.unregisterDisplayListener(listener);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index c4e1036..e10bdaa 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -765,6 +765,7 @@
     private float mBacklightMinimum = Float.NaN;
     private float mBacklightMaximum = Float.NaN;
     private float mBrightnessDefault = Float.NaN;
+    private float mBrightnessDim = Float.NaN;
     private float mBrightnessRampFastDecrease = Float.NaN;
     private float mBrightnessRampFastIncrease = Float.NaN;
     private float mBrightnessRampSlowDecrease = Float.NaN;
@@ -1282,6 +1283,24 @@
     }
 
     /**
+     * Return the minimum brightness on a scale of 0.0f - 1.0f
+     *
+     * @return minimum brightness
+     */
+    public float getBrightnessMinimum() {
+        return getBrightnessFromBacklight(mBacklightMinimum);
+    }
+
+    /**
+     * Return the maximum brightness on a scale of 0.0f - 1.0f
+     *
+     * @return maximum brightness
+     */
+    public float getBrightnessMaximum() {
+        return getBrightnessFromBacklight(mBacklightMaximum);
+    }
+
+    /**
      * Return the default brightness on a scale of 0.0f - 1.0f
      *
      * @return default brightness
@@ -1290,6 +1309,15 @@
         return mBrightnessDefault;
     }
 
+    /**
+     * Return the dim brightness on a scale of 0.0f - 1.0f
+     *
+     * @return dim brightness
+     */
+    public float getBrightnessDim() {
+        return mBrightnessDim;
+    }
+
     public float getBrightnessRampFastDecrease() {
         return mBrightnessRampFastDecrease;
     }
@@ -1689,6 +1717,7 @@
                 + ", mBacklightMinimum=" + mBacklightMinimum
                 + ", mBacklightMaximum=" + mBacklightMaximum
                 + ", mBrightnessDefault=" + mBrightnessDefault
+                + ", mBrightnessDim=" + mBrightnessDim
                 + ", mQuirks=" + mQuirks
                 + "\n"
                 + "mLuxThrottlingData=" + mLuxThrottlingData
@@ -1906,6 +1935,7 @@
         mBacklightMinimum = PowerManager.BRIGHTNESS_MIN;
         mBacklightMaximum = PowerManager.BRIGHTNESS_MAX;
         mBrightnessDefault = BRIGHTNESS_DEFAULT;
+        mBrightnessDim = PowerManager.BRIGHTNESS_INVALID;
         mBrightnessRampFastDecrease = PowerManager.BRIGHTNESS_MAX;
         mBrightnessRampFastIncrease = PowerManager.BRIGHTNESS_MAX;
         mBrightnessRampSlowDecrease = PowerManager.BRIGHTNESS_MAX;
@@ -2003,6 +2033,15 @@
             mBacklightMinimum = min;
             mBacklightMaximum = max;
         }
+        final float dim = mContext.getResources().getFloat(com.android.internal.R.dimen
+                .config_screenBrightnessDimFloat);
+        if (dim == INVALID_BRIGHTNESS_IN_CONFIG) {
+            mBrightnessDim = BrightnessSynchronizer.brightnessIntToFloat(
+                    mContext.getResources().getInteger(com.android.internal.R.integer
+                            .config_screenBrightnessDim));
+        } else {
+            mBrightnessDim = dim;
+        }
     }
 
     private void loadBrightnessMap(DisplayConfiguration config) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index d2c044f..3aaf4f6 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -311,6 +311,11 @@
      */
     public FrameRateCategoryRate frameRateCategoryRate;
     /**
+     * All the refresh rates supported for the default display mode.
+     */
+    public float[] supportedRefreshRates = new float[0];
+
+    /**
      * The default mode of the display.
      */
     public int defaultModeId;
@@ -472,6 +477,7 @@
     public float brightnessMinimum;
     public float brightnessMaximum;
     public float brightnessDefault;
+    public float brightnessDim;
 
     // NaN means unsupported
     public float hdrSdrRatio = Float.NaN;
@@ -556,13 +562,14 @@
                 || !Objects.equals(ownerPackageName, other.ownerPackageName)
                 || !BrightnessSynchronizer.floatEquals(brightnessMinimum, other.brightnessMinimum)
                 || !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum)
-                || !BrightnessSynchronizer.floatEquals(brightnessDefault,
-                other.brightnessDefault)
+                || !BrightnessSynchronizer.floatEquals(brightnessDefault, other.brightnessDefault)
+                || !BrightnessSynchronizer.floatEquals(brightnessDim, other.brightnessDim)
                 || !Objects.equals(roundedCorners, other.roundedCorners)
                 || installOrientation != other.installOrientation
                 || !Objects.equals(displayShape, other.displayShape)
                 || hasArrSupport != other.hasArrSupport
-                || !Objects.equals(frameRateCategoryRate, other.frameRateCategoryRate)) {
+                || !Objects.equals(frameRateCategoryRate, other.frameRateCategoryRate)
+                || !Arrays.equals(supportedRefreshRates, other.supportedRefreshRates)) {
             diff |= DIFF_OTHER;
         }
         return diff;
@@ -582,6 +589,7 @@
         renderFrameRate = other.renderFrameRate;
         hasArrSupport = other.hasArrSupport;
         frameRateCategoryRate = other.frameRateCategoryRate;
+        supportedRefreshRates = other.supportedRefreshRates;
         defaultModeId = other.defaultModeId;
         userPreferredModeId = other.userPreferredModeId;
         supportedModes = other.supportedModes;
@@ -611,6 +619,7 @@
         brightnessMinimum = other.brightnessMinimum;
         brightnessMaximum = other.brightnessMaximum;
         brightnessDefault = other.brightnessDefault;
+        brightnessDim = other.brightnessDim;
         hdrSdrRatio = other.hdrSdrRatio;
         roundedCorners = other.roundedCorners;
         installOrientation = other.installOrientation;
@@ -628,6 +637,7 @@
         sb.append(", renderFrameRate ").append(renderFrameRate);
         sb.append(", hasArrSupport ").append(hasArrSupport);
         sb.append(", frameRateCategoryRate ").append(frameRateCategoryRate);
+        sb.append(", supportedRefreshRates ").append(Arrays.toString(supportedRefreshRates));
         sb.append(", defaultModeId ").append(defaultModeId);
         sb.append(", userPreferredModeId ").append(userPreferredModeId);
         sb.append(", supportedModes ").append(Arrays.toString(supportedModes));
@@ -664,6 +674,7 @@
         sb.append(", brightnessMinimum ").append(brightnessMinimum);
         sb.append(", brightnessMaximum ").append(brightnessMaximum);
         sb.append(", brightnessDefault ").append(brightnessDefault);
+        sb.append(", brightnessDim ").append(brightnessDim);
         sb.append(", hdrSdrRatio ").append(hdrSdrRatio);
         if (roundedCorners != null) {
             sb.append(", roundedCorners ").append(roundedCorners);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ae74dbe..5c62995 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.Manifest.permission.ADD_MIRROR_DISPLAY;
 import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
 import static android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT;
 import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
@@ -667,7 +668,8 @@
         mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector());
         if (mFlags.isDisplayTopologyEnabled()) {
             mDisplayTopologyCoordinator =
-                    new DisplayTopologyCoordinator(this::isExtendedDisplayEnabled);
+                    new DisplayTopologyCoordinator(this::isExtendedDisplayEnabled,
+                            this::deliverTopologyUpdate, new HandlerExecutor(mHandler), mSyncRoot);
         } else {
             mDisplayTopologyCoordinator = null;
         }
@@ -1677,11 +1679,11 @@
     }
 
     private boolean canCreateMirrorDisplays(IVirtualDevice virtualDevice) {
-        if (virtualDevice == null) {
-            return false;
+        if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+            return checkCallingPermission(ADD_MIRROR_DISPLAY, "canCreateMirrorDisplays");
         }
         try {
-            return virtualDevice.canCreateMirrorDisplays();
+            return virtualDevice != null && virtualDevice.canCreateMirrorDisplays();
         } catch (RemoteException e) {
             Slog.e(TAG, "Unable to query virtual device for permissions", e);
             return false;
@@ -1818,7 +1820,7 @@
             // display.
             if (!canProjectVideo(projection) && !canCreateMirrorDisplays(virtualDevice)
                     && !hasVideoOutputPermission("createVirtualDisplayInternal")) {
-                throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
+                throw new SecurityException("Requires ADD_MIRROR_DISPLAY, CAPTURE_VIDEO_OUTPUT or "
                         + "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
                         + "MediaProjection token in order to create a screen sharing virtual "
                         + "display. In order to create a virtual display that does not perform "
@@ -3506,6 +3508,28 @@
         callbackRecord.notifyDisplayEventAsync(displayId, event);
     }
 
+    private void deliverTopologyUpdate(DisplayTopology topology) {
+        if (DEBUG) {
+            Slog.d(TAG, "Delivering topology update");
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+            Trace.instant(Trace.TRACE_TAG_POWER, "deliverTopologyUpdate");
+        }
+
+        // Grab the lock and copy the callbacks.
+        List<CallbackRecord> callbacks = new ArrayList<>();
+        synchronized (mSyncRoot) {
+            for (int i = 0; i < mCallbacks.size(); i++) {
+                callbacks.add(mCallbacks.valueAt(i));
+            }
+        }
+
+        // After releasing the lock, send the notifications out.
+        for (CallbackRecord callback : callbacks) {
+            callback.notifyTopologyUpdateAsync(topology);
+        }
+    }
+
     private boolean extraLogging(String packageName) {
         return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
     }
@@ -4141,7 +4165,7 @@
          * cached or frozen.
          */
         public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
-            if (!shouldSendEvent(event)) {
+            if (!shouldSendDisplayEvent(event)) {
                 if (extraLogging(mPackageName)) {
                     Slog.i(TAG,
                             "Not sending displayEvent: " + event + " due to mask:"
@@ -4195,7 +4219,7 @@
         /**
          * Return true if the client is interested in this event.
          */
-        private boolean shouldSendEvent(@DisplayEvent int event) {
+        private boolean shouldSendDisplayEvent(@DisplayEvent int event) {
             final long mask = mInternalEventFlagsMask.get();
             switch (event) {
                 case DisplayManagerGlobal.EVENT_DISPLAY_ADDED:
@@ -4256,6 +4280,45 @@
             mPendingEvents.add(new Event(displayId, event));
         }
 
+        /**
+         * @return {@code false} if RemoteException happens; otherwise {@code true} for
+         * success.
+         */
+        boolean notifyTopologyUpdateAsync(DisplayTopology topology) {
+            if ((mInternalEventFlagsMask.get()
+                    & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED) == 0) {
+                if (extraLogging(mPackageName)) {
+                    Slog.i(TAG, "Not sending topology update: " + topology + " due to mask: "
+                            + mInternalEventFlagsMask);
+                }
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+                    Trace.instant(Trace.TRACE_TAG_POWER,
+                            "notifyTopologyUpdateAsync#notSendingUpdate=" + topology
+                                    + ",mInternalEventFlagsMask=" + mInternalEventFlagsMask);
+                }
+                // The client is not interested in this event, so do nothing.
+                return true;
+            }
+            return transmitTopologyUpdate(topology);
+        }
+
+        /**
+         * Transmit a single display topology update. The client is presumed ready. Return true on
+         * success and false if the client died.
+         */
+        private boolean transmitTopologyUpdate(DisplayTopology topology) {
+            // The client is ready to receive the event.
+            try {
+                mCallback.onTopologyChanged(topology);
+                return true;
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process "
+                        + mPid + " that display topology changed, assuming it died.", ex);
+                binderDied();
+                return false;
+            }
+        }
+
         // Send all pending events.  This can safely be called if the process is not ready, but it
         // would be unusual to do so.  The method returns true on success.
         // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
index 4722686..5b78726 100644
--- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -25,7 +25,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.util.concurrent.Executor;
 import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
 
 /**
  * Manages the relative placement (topology) of extended displays. Responsible for updating and
@@ -33,7 +35,7 @@
  */
 class DisplayTopologyCoordinator {
 
-    @GuardedBy("mLock")
+    @GuardedBy("mSyncRoot")
     private DisplayTopology mTopology;
 
     /**
@@ -41,16 +43,31 @@
      */
     private final BooleanSupplier mIsExtendedDisplayEnabled;
 
-    private final Object mLock = new Object();
+    /**
+     * Callback used to send topology updates.
+     * Should be invoked from the corresponding executor.
+     * A copy of the topology should be sent that will not be modified by the system.
+     */
+    private final Consumer<DisplayTopology> mOnTopologyChangedCallback;
+    private final Executor mTopologyChangeExecutor;
+    private final DisplayManagerService.SyncRoot mSyncRoot;
 
-    DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayEnabled) {
-        this(new Injector(), isExtendedDisplayEnabled);
+    DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayEnabled,
+            Consumer<DisplayTopology> onTopologyChangedCallback,
+            Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot) {
+        this(new Injector(), isExtendedDisplayEnabled, onTopologyChangedCallback,
+                topologyChangeExecutor, syncRoot);
     }
 
     @VisibleForTesting
-    DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayEnabled) {
+    DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayEnabled,
+            Consumer<DisplayTopology> onTopologyChangedCallback,
+            Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot) {
         mTopology = injector.getTopology();
         mIsExtendedDisplayEnabled = isExtendedDisplayEnabled;
+        mOnTopologyChangedCallback = onTopologyChangedCallback;
+        mTopologyChangeExecutor = topologyChangeExecutor;
+        mSyncRoot = syncRoot;
     }
 
     /**
@@ -61,8 +78,9 @@
         if (!isDisplayAllowedInTopology(info)) {
             return;
         }
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             mTopology.addDisplay(info.displayId, getWidth(info), getHeight(info));
+            sendTopologyUpdateLocked();
         }
     }
 
@@ -71,8 +89,9 @@
      * @param displayId The logical display ID
      */
     void onDisplayRemoved(int displayId) {
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             mTopology.removeDisplay(displayId);
+            sendTopologyUpdateLocked();
         }
     }
 
@@ -80,14 +99,16 @@
      * @return A deep copy of the topology.
      */
     DisplayTopology getTopology() {
-        synchronized (mLock) {
-            return mTopology;
+        synchronized (mSyncRoot) {
+            return mTopology.copy();
         }
     }
 
     void setTopology(DisplayTopology topology) {
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             mTopology = topology;
+            mTopology.normalize();
+            sendTopologyUpdateLocked();
         }
     }
 
@@ -96,7 +117,7 @@
      * @param pw The stream to dump information to.
      */
     void dump(PrintWriter pw) {
-        synchronized (mLock) {
+        synchronized (mSyncRoot) {
             mTopology.dump(pw);
         }
     }
@@ -124,6 +145,12 @@
                 && info.displayGroupId == Display.DEFAULT_DISPLAY_GROUP;
     }
 
+    @GuardedBy("mSyncRoot")
+    private void sendTopologyUpdateLocked() {
+        DisplayTopology copy = mTopology.copy();
+        mTopologyChangeExecutor.execute(() -> mOnTopologyChangedCallback.accept(copy));
+    }
+
     @VisibleForTesting
     static class Injector {
         DisplayTopology getTopology() {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index a4bb8c3..d37dd30 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -249,6 +249,7 @@
         private int mActiveColorMode;
         private boolean mHasArrSupport;
         private FrameRateCategoryRate mFrameRateCategoryRate;
+        private float[] mSupportedRefreshRates = new float[0];
         private Display.HdrCapabilities mHdrCapabilities;
         private boolean mAllmSupported;
         private boolean mGameContentTypeSupported;
@@ -316,6 +317,7 @@
             changed |= updateGameContentTypeSupport(dynamicInfo.gameContentTypeSupported);
             changed |= updateHasArrSupportLocked(dynamicInfo.hasArrSupport);
             changed |= updateFrameRateCategoryRatesLocked(dynamicInfo.frameRateCategoryRate);
+            changed |= updateSupportedRefreshatesLocked(dynamicInfo.supportedRefreshRates);
 
             if (changed) {
                 mHavePendingChanges = true;
@@ -624,6 +626,20 @@
             return true;
         }
 
+        private boolean updateSupportedRefreshatesLocked(float[] supportedRefreshRates) {
+            if (!getFeatureFlags().enableGetSupportedRefreshRates()) {
+                return false;
+            }
+            if (supportedRefreshRates == null) {
+                return false;
+            }
+            if (Arrays.equals(mSupportedRefreshRates, supportedRefreshRates)) {
+                return false;
+            }
+            mSupportedRefreshRates = supportedRefreshRates;
+            return true;
+        }
+
         private boolean updateAllmSupport(boolean supported) {
             if (mAllmSupported == supported) {
                 return false;
@@ -708,6 +724,7 @@
                 mInfo.hdrCapabilities = mHdrCapabilities;
                 mInfo.hasArrSupport = mHasArrSupport;
                 mInfo.frameRateCategoryRate = mFrameRateCategoryRate;
+                mInfo.supportedRefreshRates = mSupportedRefreshRates;
                 mInfo.appVsyncOffsetNanos = mActiveSfDisplayMode.appVsyncOffsetNanos;
                 mInfo.presentationDeadlineNanos = mActiveSfDisplayMode.presentationDeadlineNanos;
                 mInfo.state = mState;
@@ -795,9 +812,10 @@
 
                 // The display is trusted since it is created by system.
                 mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED;
-                mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN;
-                mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX;
+                mInfo.brightnessMinimum = getDisplayDeviceConfig().getBrightnessMinimum();
+                mInfo.brightnessMaximum = getDisplayDeviceConfig().getBrightnessMaximum();
                 mInfo.brightnessDefault = getDisplayDeviceConfig().getBrightnessDefault();
+                mInfo.brightnessDim = getDisplayDeviceConfig().getBrightnessDim();
                 mInfo.hdrSdrRatio = mCurrentHdrSdrRatio;
             }
             return mInfo;
@@ -1299,6 +1317,7 @@
             pw.println("mDefaultModeId=" + mDefaultModeId);
             pw.println("mUserPreferredModeId=" + mUserPreferredModeId);
             pw.println("mHasArrSupport=" + mHasArrSupport);
+            pw.println("mSupportedRefreshRates=" + Arrays.toString(mSupportedRefreshRates));
             pw.println("mState=" + Display.stateToString(mState));
             pw.println("mCommittedState=" + Display.stateToString(mCommittedState));
             pw.println("mBrightnessState=" + mBrightnessState);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 7cfdcaf..1de9c95 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -508,6 +508,8 @@
             mBaseDisplayInfo.renderFrameRate = deviceInfo.renderFrameRate;
             mBaseDisplayInfo.hasArrSupport = deviceInfo.hasArrSupport;
             mBaseDisplayInfo.frameRateCategoryRate = deviceInfo.frameRateCategoryRate;
+            mBaseDisplayInfo.supportedRefreshRates = Arrays.copyOf(
+                    deviceInfo.supportedRefreshRates, deviceInfo.supportedRefreshRates.length);
             mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId;
             mBaseDisplayInfo.userPreferredModeId = deviceInfo.userPreferredModeId;
             mBaseDisplayInfo.supportedModes = Arrays.copyOf(
@@ -546,6 +548,7 @@
             mBaseDisplayInfo.brightnessMinimum = deviceInfo.brightnessMinimum;
             mBaseDisplayInfo.brightnessMaximum = deviceInfo.brightnessMaximum;
             mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault;
+            mBaseDisplayInfo.brightnessDim = deviceInfo.brightnessDim;
             mBaseDisplayInfo.hdrSdrRatio = deviceInfo.hdrSdrRatio;
             mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners;
             mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation;
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index eb76dcb..382c883 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -73,9 +73,13 @@
  * </pre>
  * Supported flags:
  * <ul>
- * <li><pre>secure</pre>: creates a secure display</li>
- * <li><pre>own_content_only</pre>: only shows this display's own content</li>
- * <li><pre>should_show_system_decorations</pre>: supports system decorations</li>
+ * <li><code>secure</code>: creates a secure display</li>
+ * <li><code>own_content_only</code>: only shows this display's own content</li>
+ * <li><code>should_show_system_decorations</code>: supports system decorations</li>
+ * <li><code>gravity_top_left</code>: display the overlay at the top left of the screen</li>
+ * <li><code>gravity_top_right</code>: display the overlay at the top right of the screen</li>
+ * <li><code>gravity_bottom_right</code>: display the overlay at the bottom right of the screen</li>
+ * <li><code>gravity_bottom_left</code>: display the overlay at the bottom left of the screen</li>
  * </ul>
  * </p><p>
  * Example:
@@ -113,6 +117,12 @@
     private static final String OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS =
             "should_show_system_decorations";
 
+    // Gravity flags to decide where the overlay should be shown.
+    private static final String GRAVITY_TOP_LEFT = "gravity_top_left";
+    private static final String GRAVITY_BOTTOM_RIGHT = "gravity_bottom_right";
+    private static final String GRAVITY_TOP_RIGHT = "gravity_top_right";
+    private static final String GRAVITY_BOTTOM_LEFT = "gravity_bottom_left";
+
     private static final int MIN_WIDTH = 100;
     private static final int MIN_HEIGHT = 100;
     private static final int MAX_WIDTH = 4096;
@@ -237,8 +247,11 @@
                     String name = getContext().getResources().getString(
                             com.android.internal.R.string.display_manager_overlay_display_name,
                             number);
-                    int gravity = chooseOverlayGravity(number);
                     OverlayFlags flags = OverlayFlags.parseFlags(flagString);
+                    int gravity = flags.mGravity;
+                    if (flags.mGravity == Gravity.NO_GRAVITY) {
+                        gravity = chooseOverlayGravity(number);
+                    }
 
                     Slog.i(TAG, "Showing overlay display device #" + number
                             + ": name=" + name + ", modes=" + Arrays.toString(modes.toArray())
@@ -266,6 +279,16 @@
         }
     }
 
+    private static int parseOverlayGravity(String overlayGravity) {
+        return switch (overlayGravity) {
+            case GRAVITY_TOP_LEFT -> Gravity.TOP | Gravity.LEFT;
+            case GRAVITY_TOP_RIGHT -> Gravity.TOP | Gravity.RIGHT;
+            case GRAVITY_BOTTOM_RIGHT -> Gravity.BOTTOM | Gravity.RIGHT;
+            case GRAVITY_BOTTOM_LEFT -> Gravity.BOTTOM | Gravity.LEFT;
+            default -> Gravity.NO_GRAVITY;
+        };
+    }
+
     private abstract class OverlayDisplayDevice extends DisplayDevice {
         private final String mName;
         private final float mRefreshRate;
@@ -605,13 +628,17 @@
         /** See {@link #OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS}. */
         final boolean mShouldShowSystemDecorations;
 
+        final int mGravity;
+
         OverlayFlags(
                 boolean secure,
                 boolean ownContentOnly,
-                boolean shouldShowSystemDecorations) {
+                boolean shouldShowSystemDecorations,
+                int gravity) {
             mSecure = secure;
             mOwnContentOnly = ownContentOnly;
             mShouldShowSystemDecorations = shouldShowSystemDecorations;
+            mGravity = gravity;
         }
 
         static OverlayFlags parseFlags(@Nullable String flagString) {
@@ -619,24 +646,26 @@
                 return new OverlayFlags(
                         false /* secure */,
                         false /* ownContentOnly */,
-                        false /* shouldShowSystemDecorations */);
+                        false /* shouldShowSystemDecorations */,
+                        Gravity.NO_GRAVITY);
             }
 
             boolean secure = false;
             boolean ownContentOnly = false;
             boolean shouldShowSystemDecorations = false;
+            int gravity = Gravity.NO_GRAVITY;
             for (String flag: flagString.split(FLAG_SPLITTER)) {
                 if (OVERLAY_DISPLAY_FLAG_SECURE.equals(flag)) {
                     secure = true;
-                }
-                if (OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY.equals(flag)) {
+                } else if (OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY.equals(flag)) {
                     ownContentOnly = true;
-                }
-                if (OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS.equals(flag)) {
+                } else if (OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS.equals(flag)) {
                     shouldShowSystemDecorations = true;
+                } else {
+                    gravity = parseOverlayGravity(flag);
                 }
             }
-            return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations);
+            return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations, gravity);
         }
 
         @Override
@@ -645,6 +674,7 @@
                     .append("secure=").append(mSecure)
                     .append(", ownContentOnly=").append(mOwnContentOnly)
                     .append(", shouldShowSystemDecorations=").append(mShouldShowSystemDecorations)
+                    .append(", gravity").append(Gravity.toString(mGravity))
                     .append("}")
                     .toString();
         }
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index dabef84..836f4ed 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -43,6 +43,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Point;
+import android.hardware.display.IBrightnessListener;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
 import android.media.projection.IMediaProjection;
@@ -183,8 +184,11 @@
         if (projection != null) {
             mediaProjectionCallback = new MediaProjectionCallback(appToken);
         }
+
+        Callback callbackDelegate = new Callback(
+                callback, virtualDisplayConfig.getBrightnessListener(), mHandler);
         VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken,
-                ownerUid, ownerPackageName, surface, flags, new Callback(callback, mHandler),
+                ownerUid, ownerPackageName, surface, flags, callbackDelegate,
                 projection, mediaProjectionCallback, uniqueId, virtualDisplayConfig);
 
         mVirtualDisplayDevices.put(appToken, device);
@@ -336,7 +340,9 @@
         private boolean mIsWindowManagerMirroring;
         private final DisplayCutout mDisplayCutout;
         private final float mDefaultBrightness;
+        private final float mDimBrightness;
         private float mCurrentBrightness;
+        private final IBrightnessListener mBrightnessListener;
 
         public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
                 int ownerUid, String ownerPackageName, Surface surface, int flags,
@@ -354,7 +360,9 @@
             mRequestedRefreshRate = virtualDisplayConfig.getRequestedRefreshRate();
             mDisplayCutout = virtualDisplayConfig.getDisplayCutout();
             mDefaultBrightness = virtualDisplayConfig.getDefaultBrightness();
-            mCurrentBrightness = mDefaultBrightness;
+            mDimBrightness = virtualDisplayConfig.getDimBrightness();
+            mCurrentBrightness = PowerManager.BRIGHTNESS_INVALID;
+            mBrightnessListener = virtualDisplayConfig.getBrightnessListener();
             mMode = createMode(mWidth, mHeight, getRefreshRate());
             mSurface = surface;
             mFlags = flags;
@@ -464,6 +472,7 @@
                 }
             }
             if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
+                    && mBrightnessListener != null
                     && BrightnessUtils.isValidBrightnessValue(brightnessState)
                     && brightnessState != mCurrentBrightness) {
                 mCurrentBrightness = brightnessState;
@@ -638,6 +647,7 @@
                 mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN;
                 mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX;
                 mInfo.brightnessDefault = mDefaultBrightness;
+                mInfo.brightnessDim = mDimBrightness;
 
                 mInfo.ownerUid = mOwnerUid;
                 mInfo.ownerPackageName = mOwnerPackageName;
@@ -661,10 +671,13 @@
         private static final int MSG_ON_REQUESTED_BRIGHTNESS_CHANGED = 3;
 
         private final IVirtualDisplayCallback mCallback;
+        private final IBrightnessListener mBrightnessListener;
 
-        public Callback(IVirtualDisplayCallback callback, Handler handler) {
+        Callback(IVirtualDisplayCallback callback, IBrightnessListener brightnessListener,
+                Handler handler) {
             super(handler.getLooper());
             mCallback = callback;
+            mBrightnessListener = brightnessListener;
         }
 
         @Override
@@ -681,7 +694,9 @@
                         mCallback.onStopped();
                         break;
                     case MSG_ON_REQUESTED_BRIGHTNESS_CHANGED:
-                        mCallback.onRequestedBrightnessChanged((Float) msg.obj);
+                        if (mBrightnessListener != null) {
+                            mBrightnessListener.onBrightnessChanged((Float) msg.obj);
+                        }
                         break;
                 }
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
deleted file mode 100644
index a1fd164..0000000
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ /dev/null
@@ -1,78 +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.server.display.brightness.clamper;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-import android.os.PowerManager;
-
-import com.android.server.display.DisplayBrightnessState;
-
-import java.io.PrintWriter;
-
-/**
- * Provides brightness range constraints
- */
-abstract class BrightnessClamper<T> {
-
-    protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
-
-    protected boolean mIsActive = false;
-
-    @NonNull
-    protected final Handler mHandler;
-
-    @NonNull
-    protected final BrightnessClamperController.ClamperChangeListener mChangeListener;
-
-    BrightnessClamper(Handler handler,
-            BrightnessClamperController.ClamperChangeListener changeListener) {
-        mHandler = handler;
-        mChangeListener = changeListener;
-    }
-
-    float getBrightnessCap() {
-        return mBrightnessCap;
-    }
-
-    float getCustomAnimationRate() {
-        return DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
-    }
-
-    boolean isActive() {
-        return mIsActive;
-    }
-
-    void dump(PrintWriter writer) {
-        writer.println("BrightnessClamper:" + getType());
-        writer.println(" mBrightnessCap: " + mBrightnessCap);
-        writer.println(" mIsActive: " + mIsActive);
-    }
-
-    @NonNull
-    abstract Type getType();
-
-    abstract void onDeviceConfigChanged();
-
-    abstract void onDisplayChanged(T displayData);
-
-    abstract void stop();
-
-    protected enum Type {
-        POWER,
-    }
-}
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 6e579bf..440a271 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
@@ -18,8 +18,6 @@
 
 import static android.view.Display.STATE_ON;
 
-import static com.android.server.display.brightness.clamper.BrightnessClamper.Type;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -33,7 +31,6 @@
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfigInterface;
 import android.util.IndentingPrintWriter;
-import android.util.Slog;
 import android.util.Spline;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -43,7 +40,6 @@
 import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
 import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
-import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.config.SensorData;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.feature.DisplayManagerFlags;
@@ -65,7 +61,6 @@
 
     private final ClamperChangeListener mClamperChangeListenerExternal;
     private final Executor mExecutor;
-    private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers;
 
     private final List<BrightnessStateModifier> mModifiers;
 
@@ -77,16 +72,6 @@
     private ModifiersAggregatedState mModifiersAggregatedState = new ModifiersAggregatedState();
 
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
-    private final DisplayManagerFlags mDisplayManagerFlags;
-    private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
-
-    private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
-    @Nullable
-    private BrightnessPowerClamper mPowerClamper;
-    @Nullable
-    private Type mClamperType = null;
-
-    private boolean mClamperApplied = false;
 
     private final LightSensorController.LightSensorListener mLightSensorListener =
             new LightSensorController.LightSensorListener() {
@@ -96,6 +81,8 @@
                 }
             };
 
+    private volatile boolean mStarted = false;
+
     public BrightnessClamperController(Handler handler,
             ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
             DisplayManagerFlags flags, SensorManager sensorManager, float currentBrightness) {
@@ -109,32 +96,21 @@
             DisplayManagerFlags flags, SensorManager sensorManager, float currentBrightness) {
         mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
         mHandler = handler;
-        mDisplayManagerFlags = flags;
         mLightSensorController = injector.getLightSensorController(sensorManager, context,
                 mLightSensorListener, mHandler);
 
         mClamperChangeListenerExternal = clamperChangeListener;
         mExecutor = new HandlerExecutor(handler);
 
-        Runnable clamperChangeRunnableInternal = this::recalculateBrightnessCap;
+        Runnable modifiersChangeRunnableInternal = this::recalculateModifiersState;
         ClamperChangeListener clamperChangeListenerInternal = () -> {
-            if (!mHandler.hasCallbacks(clamperChangeRunnableInternal)) {
-                mHandler.post(clamperChangeRunnableInternal);
+            if (mStarted && !mHandler.hasCallbacks(modifiersChangeRunnableInternal)) {
+                mHandler.post(modifiersChangeRunnableInternal);
             }
         };
 
-        mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags,
-                context, currentBrightness);
-        if (mDisplayManagerFlags.isPowerThrottlingClamperEnabled()) {
-            for (BrightnessClamper clamper: mClampers) {
-                if (clamper.getType() == Type.POWER) {
-                    mPowerClamper = (BrightnessPowerClamper) clamper;
-                    break;
-                }
-            }
-        }
-        mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListener,
-                data);
+        mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListenerInternal,
+                data, currentBrightness);
 
         mModifiers.forEach(m -> {
             if (m instanceof  DisplayDeviceDataListener l) {
@@ -151,7 +127,6 @@
             }
         });
         mOnPropertiesChangedListener = properties -> {
-            mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
             mDeviceConfigListeners.forEach(DeviceConfigListener::onDeviceConfigChanged);
         };
         mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId());
@@ -163,7 +138,6 @@
      */
     public void onDisplayChanged(DisplayDeviceData data) {
         mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId());
-        mClampers.forEach(clamper -> clamper.onDisplayChanged(data));
         mDisplayDeviceDataListeners.forEach(l -> l.onDisplayChanged(data));
         adjustLightSensorSubscription();
     }
@@ -175,24 +149,10 @@
     public DisplayBrightnessState clamp(DisplayBrightnessState displayBrightnessState,
             DisplayManagerInternal.DisplayPowerRequest request,
             float brightnessValue, boolean slowChange, int displayState) {
-        float cappedBrightness = Math.min(brightnessValue, mBrightnessCap);
-
         DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from(
                 displayBrightnessState);
         builder.setIsSlowChange(slowChange);
-        builder.setBrightness(cappedBrightness);
-        builder.setMaxBrightness(mBrightnessCap);
-        builder.setCustomAnimationRate(mCustomAnimationRate);
-        builder.setBrightnessMaxReason(getBrightnessMaxReason());
-        if (mClamperType != null) {
-            builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
-            if (!mClamperApplied) {
-                builder.setIsSlowChange(false);
-            }
-            mClamperApplied = true;
-        } else {
-            mClamperApplied = false;
-        }
+        builder.setBrightness(brightnessValue);
 
         if (displayState != STATE_ON) {
             mLightSensorController.stop();
@@ -204,27 +164,9 @@
             mModifiers.get(i).apply(request, builder);
         }
 
-        if (mDisplayManagerFlags.isPowerThrottlingClamperEnabled()) {
-            if (mPowerClamper != null) {
-                mPowerClamper.updateCurrentBrightness(cappedBrightness);
-            }
-        }
-
         return builder.build();
     }
 
-    @BrightnessInfo.BrightnessMaxReason
-    private int getBrightnessMaxReason() {
-        if (mClamperType == null) {
-            return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
-        } else if (mClamperType == Type.POWER) {
-            return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;
-        }  else {
-            Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType);
-            return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
-        }
-    }
-
     /**
      * Called when the user switches.
      */
@@ -237,13 +179,8 @@
      */
     public void dump(PrintWriter writer) {
         writer.println("BrightnessClamperController:");
-        writer.println("----------------------------");
-        writer.println("  mBrightnessCap: " + mBrightnessCap);
-        writer.println("  mClamperType: " + mClamperType);
-        writer.println("  mClamperApplied: " + mClamperApplied);
         IndentingPrintWriter ipw = new IndentingPrintWriter(writer, "    ");
         mLightSensorController.dump(ipw);
-        mClampers.forEach(clamper -> clamper.dump(ipw));
         mModifiers.forEach(modifier -> modifier.dump(ipw));
     }
 
@@ -252,41 +189,20 @@
      * Called in DisplayControllerHandler
      */
     public void stop() {
+        mStarted = false;
         mDeviceConfigParameterProvider.removeOnPropertiesChangedListener(
                 mOnPropertiesChangedListener);
         mLightSensorController.stop();
-        mClampers.forEach(BrightnessClamper::stop);
         mModifiers.forEach(BrightnessStateModifier::stop);
     }
 
 
     // Called in DisplayControllerHandler
-    private void recalculateBrightnessCap() {
-        float brightnessCap = PowerManager.BRIGHTNESS_MAX;
-        Type clamperType = null;
-        float customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
-
-        BrightnessClamper<?> minClamper = mClampers.stream()
-                .filter(BrightnessClamper::isActive)
-                .min((clamper1, clamper2) -> Float.compare(clamper1.getBrightnessCap(),
-                        clamper2.getBrightnessCap())).orElse(null);
-
-        if (minClamper != null) {
-            brightnessCap = minClamper.getBrightnessCap();
-            clamperType = minClamper.getType();
-            customAnimationRate = minClamper.getCustomAnimationRate();
-        }
-
+    private void recalculateModifiersState() {
         ModifiersAggregatedState newAggregatedState = new ModifiersAggregatedState();
-        mStatefulModifiers.forEach((clamper) -> clamper.applyStateChange(newAggregatedState));
+        mStatefulModifiers.forEach((modifier) -> modifier.applyStateChange(newAggregatedState));
 
-        if (mBrightnessCap != brightnessCap
-                || mClamperType != clamperType
-                || mCustomAnimationRate != customAnimationRate
-                || needToNotifyExternalListener(mModifiersAggregatedState, newAggregatedState)) {
-            mBrightnessCap = brightnessCap;
-            mClamperType = clamperType;
-            mCustomAnimationRate = customAnimationRate;
+        if (needToNotifyExternalListener(mModifiersAggregatedState, newAggregatedState)) {
             mClamperChangeListenerExternal.onChanged();
         }
         mModifiersAggregatedState = newAggregatedState;
@@ -305,11 +221,12 @@
     }
 
     private void start() {
-        if (!mClampers.isEmpty() || !mDeviceConfigListeners.isEmpty()) {
+        if (!mDeviceConfigListeners.isEmpty()) {
             mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
                     mExecutor, mOnPropertiesChangedListener);
         }
         adjustLightSensorSubscription();
+        mStarted = true;
     }
 
     private void adjustLightSensorSubscription() {
@@ -336,33 +253,25 @@
             return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
         }
 
-        List<BrightnessClamper<? super DisplayDeviceData>> getClampers(Handler handler,
-                ClamperChangeListener clamperChangeListener, DisplayDeviceData data,
-                DisplayManagerFlags flags, Context context, float currentBrightness) {
-            List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>();
-
-            if (flags.isPowerThrottlingClamperEnabled()) {
-                // Check if power-throttling config is present.
-                PowerThrottlingConfigData configData = data.getPowerThrottlingConfigData();
-                if (configData != null) {
-                    clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener,
-                            data, currentBrightness));
-                }
-            }
-            return clampers;
-        }
-
         List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
                 Handler handler, ClamperChangeListener listener,
-                DisplayDeviceData data) {
+                DisplayDeviceData data, float currentBrightness) {
             List<BrightnessStateModifier> modifiers = new ArrayList<>();
             modifiers.add(new BrightnessThermalModifier(handler, listener, data));
             if (flags.isBrightnessWearBedtimeModeClamperEnabled()) {
                 modifiers.add(new BrightnessWearBedtimeModeModifier(handler, context,
                         listener, data));
             }
+            if (flags.isPowerThrottlingClamperEnabled()) {
+                // Check if power-throttling config is present.
+                PowerThrottlingConfigData configData = data.getPowerThrottlingConfigData();
+                if (configData != null) {
+                    modifiers.add(new BrightnessPowerModifier(handler, listener,
+                            data, currentBrightness));
+                }
+            }
 
-            modifiers.add(new DisplayDimModifier(context));
+            modifiers.add(new DisplayDimModifier(data.mDisplayId, context));
             modifiers.add(new BrightnessLowPowerModeModifier());
             if (flags.isEvenDimmerEnabled() && data.mDisplayDeviceConfig.isEvenDimmerAvailable()) {
                 modifiers.add(new BrightnessLowLuxModifier(handler, listener, context,
@@ -393,7 +302,7 @@
      * Config Data for clampers/modifiers
      */
     public static class DisplayDeviceData implements BrightnessThermalModifier.ThermalData,
-            BrightnessPowerClamper.PowerData,
+            BrightnessPowerModifier.PowerData,
             BrightnessWearBedtimeModeModifier.WearBedtimeModeData {
         @NonNull
         private final String mUniqueDisplayId;
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
index be8fa5a..cbeb863 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display.brightness.clamper;
 
+import static com.android.server.display.DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+
 import android.hardware.display.DisplayManagerInternal;
 import android.os.PowerManager;
 
@@ -50,10 +52,12 @@
             }
             if (!mApplied) {
                 stateBuilder.setIsSlowChange(false);
+                stateBuilder.setCustomAnimationRate(CUSTOM_ANIMATION_RATE_NOT_SET);
             }
             mApplied = true;
         } else if (mApplied) {
             stateBuilder.setIsSlowChange(false);
+            stateBuilder.setCustomAnimationRate(CUSTOM_ANIMATION_RATE_NOT_SET);
             mApplied = false;
         }
     }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
deleted file mode 100644
index 1a18b00..0000000
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
+++ /dev/null
@@ -1,467 +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.server.display.brightness.clamper;
-
-import static com.android.server.display.DisplayDeviceConfig.DEFAULT_ID;
-import static com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.Handler;
-import android.os.IThermalEventListener;
-import android.os.IThermalService;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.Temperature;
-import android.provider.DeviceConfigInterface;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.display.DisplayBrightnessState;
-import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
-import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
-import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
-import com.android.server.display.brightness.BrightnessUtils;
-import com.android.server.display.feature.DeviceConfigParameterProvider;
-import com.android.server.display.utils.DeviceConfigParsingUtils;
-
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.Map;
-import java.util.function.BiFunction;
-import java.util.function.Function;
-
-
-class BrightnessPowerClamper extends
-        BrightnessClamper<BrightnessPowerClamper.PowerData> {
-
-    private static final String TAG = "BrightnessPowerClamper";
-    @NonNull
-    private final Injector mInjector;
-    @NonNull
-    private final DeviceConfigParameterProvider mConfigParameterProvider;
-    @Nullable
-    private PmicMonitor mPmicMonitor;
-    // data from DeviceConfig, for all displays, for all dataSets
-    // mapOf(uniqueDisplayId to mapOf(dataSetId to PowerThrottlingData))
-    @NonNull
-    private Map<String, Map<String, PowerThrottlingData>>
-            mPowerThrottlingDataOverride = Map.of();
-    // data from DisplayDeviceConfig, for particular display+dataSet
-    @Nullable
-    private PowerThrottlingData mPowerThrottlingDataFromDDC = null;
-    // Active data, if mPowerThrottlingDataOverride contains data for mUniqueDisplayId,
-    // mDataId, then use it, otherwise mPowerThrottlingDataFromDDC.
-    @Nullable
-    private PowerThrottlingData mPowerThrottlingDataActive = null;
-    @Nullable
-    private PowerThrottlingConfigData mPowerThrottlingConfigData = null;
-    @NonNull
-    private final ThermalLevelListener mThermalLevelListener;
-    @NonNull
-    private final PowerChangeListener mPowerChangeListener;
-    private @Temperature.ThrottlingStatus int mCurrentThermalLevel = Temperature.THROTTLING_NONE;
-    private boolean mCurrentThermalLevelChanged = false;
-    private float mCurrentAvgPowerConsumed = 0;
-    @Nullable
-    private String mUniqueDisplayId = null;
-    @Nullable
-    private String mDataId = null;
-    private float mCurrentBrightness = PowerManager.BRIGHTNESS_INVALID;
-    private float mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
-    private float mCustomAnimationRateDeviceConfig =
-                        DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
-    private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
-        try {
-            int status = DeviceConfigParsingUtils.parseThermalStatus(key);
-            float powerQuota = Float.parseFloat(value);
-            return new ThrottlingLevel(status, powerQuota);
-        } catch (IllegalArgumentException iae) {
-            return null;
-        }
-    };
-
-    private final Function<List<ThrottlingLevel>, PowerThrottlingData>
-            mDataSetMapper = PowerThrottlingData::create;
-
-
-    BrightnessPowerClamper(Handler handler, ClamperChangeListener listener,
-            PowerData powerData, float currentBrightness) {
-        this(new Injector(), handler, listener, powerData, currentBrightness);
-    }
-
-    @VisibleForTesting
-    BrightnessPowerClamper(Injector injector, Handler handler, ClamperChangeListener listener,
-                           PowerData powerData, float currentBrightness) {
-        super(handler, listener);
-        mInjector = injector;
-        mCurrentBrightness = currentBrightness;
-        mPowerChangeListener = (powerConsumed, thermalStatus) -> {
-            recalculatePowerQuotaChange(powerConsumed, thermalStatus);
-        };
-        mPowerThrottlingConfigData = powerData.getPowerThrottlingConfigData();
-        if (mPowerThrottlingConfigData != null) {
-            mCustomAnimationRateDeviceConfig = mPowerThrottlingConfigData.customAnimationRate;
-        }
-        mThermalLevelListener = new ThermalLevelListener(handler);
-        mPmicMonitor =
-            mInjector.getPmicMonitor(mPowerChangeListener,
-                    mThermalLevelListener.getThermalService(),
-                    mPowerThrottlingConfigData.pollingWindowMaxMillis,
-                    mPowerThrottlingConfigData.pollingWindowMinMillis);
-
-        mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
-        mHandler.post(() -> {
-            setDisplayData(powerData);
-            loadOverrideData();
-            start();
-        });
-    }
-
-    @VisibleForTesting
-    PowerChangeListener getPowerChangeListener() {
-        return mPowerChangeListener;
-    }
-
-    @Override
-    @NonNull
-    BrightnessClamper.Type getType() {
-        return Type.POWER;
-    }
-
-    @Override
-    float getCustomAnimationRate() {
-        return mCustomAnimationRateSec;
-    }
-
-    @Override
-    void onDeviceConfigChanged() {
-        mHandler.post(() -> {
-            loadOverrideData();
-            recalculateActiveData();
-        });
-    }
-
-    @Override
-    void onDisplayChanged(PowerData data) {
-        mHandler.post(() -> {
-            setDisplayData(data);
-            recalculateActiveData();
-        });
-    }
-
-    @Override
-    void stop() {
-        if (mPmicMonitor != null) {
-            mPmicMonitor.shutdown();
-        }
-        if (mThermalLevelListener != null) {
-            mThermalLevelListener.stop();
-        }
-    }
-
-    /**
-     * Dumps the state of BrightnessPowerClamper.
-     */
-    public void dump(PrintWriter pw) {
-        pw.println("BrightnessPowerClamper:");
-        pw.println("  mCurrentAvgPowerConsumed=" + mCurrentAvgPowerConsumed);
-        pw.println("  mUniqueDisplayId=" + mUniqueDisplayId);
-        pw.println("  mCurrentThermalLevel=" + mCurrentThermalLevel);
-        pw.println("  mCurrentThermalLevelChanged=" + mCurrentThermalLevelChanged);
-        pw.println("  mPowerThrottlingDataFromDDC=" + (mPowerThrottlingDataFromDDC == null ? "null"
-                : mPowerThrottlingDataFromDDC.toString()));
-        mThermalLevelListener.dump(pw);
-        super.dump(pw);
-    }
-
-    /**
-     * Updates current brightness, for power calculations.
-     */
-    public void updateCurrentBrightness(float currentBrightness) {
-        mCurrentBrightness = currentBrightness;
-    }
-
-    private void recalculateActiveData() {
-        if (mUniqueDisplayId == null || mDataId == null) {
-            return;
-        }
-        mPowerThrottlingDataActive = mPowerThrottlingDataOverride
-                .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId,
-                        mPowerThrottlingDataFromDDC);
-        if (mPowerThrottlingDataActive == null) {
-            if (mPmicMonitor != null) {
-                mPmicMonitor.stop();
-            }
-        }
-    }
-
-    private void loadOverrideData() {
-        String throttlingDataOverride = mConfigParameterProvider.getPowerThrottlingData();
-        mPowerThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap(
-                throttlingDataOverride, mDataPointMapper, mDataSetMapper);
-    }
-
-    private void setDisplayData(@NonNull PowerData data) {
-        mUniqueDisplayId = data.getUniqueDisplayId();
-        mDataId = data.getPowerThrottlingDataId();
-        mPowerThrottlingDataFromDDC = data.getPowerThrottlingData();
-        if (mPowerThrottlingDataFromDDC == null && !DEFAULT_ID.equals(mDataId)) {
-            Slog.wtf(TAG,
-                    "Power throttling data is missing for powerThrottlingDataId=" + mDataId);
-        }
-
-        mPowerThrottlingConfigData = data.getPowerThrottlingConfigData();
-    }
-
-    private void recalculateBrightnessCap() {
-        boolean isActive = false;
-        float targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
-        float powerQuota = getPowerQuotaForThermalStatus(mCurrentThermalLevel);
-        if (mPowerThrottlingDataActive == null) {
-            return;
-        }
-        if (powerQuota > 0) {
-            if (BrightnessUtils.isValidBrightnessValue(mCurrentBrightness)
-                    && (mCurrentAvgPowerConsumed > powerQuota)) {
-                isActive = true;
-                // calculate new brightness Cap.
-                // Brightness has a linear relation to power-consumed.
-                targetBrightnessCap =
-                    (powerQuota / mCurrentAvgPowerConsumed) * mCurrentBrightness;
-            } else if (mCurrentThermalLevelChanged) {
-                if (mCurrentThermalLevel == Temperature.THROTTLING_NONE) {
-                    // reset pmic and remove the power-throttling cap.
-                    isActive = true;
-                    targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
-                    mPmicMonitor.stop();
-                } else {
-                    isActive = true;
-                    // Since the thermal status has changed, we need to remove power-throttling cap.
-                    // Instead of recalculating and changing brightness again, adding flicker,
-                    // we will wait for the next pmic cycle to re-evaluate this value
-                    // make act on it, if needed.
-                    targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
-                    if (mPmicMonitor.isStopped()) {
-                        mPmicMonitor.start();
-                    }
-                }
-            } else { // Current power consumed is under the quota.
-                isActive = true;
-                targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
-            }
-        }
-
-        // Cap to lowest allowed brightness on device.
-        if (mPowerThrottlingConfigData != null) {
-            targetBrightnessCap = Math.max(targetBrightnessCap,
-                                mPowerThrottlingConfigData.brightnessLowestCapAllowed);
-        }
-
-        if (mBrightnessCap != targetBrightnessCap || mIsActive != isActive) {
-            mIsActive = isActive;
-            Slog.i(TAG, "Power clamper changing current brightness cap mBrightnessCap: "
-                    + mBrightnessCap + " to target brightness cap:" + targetBrightnessCap
-                    + " for current screen brightness: " + mCurrentBrightness + "\n"
-                    + "Power clamper changed state: thermalStatus:" + mCurrentThermalLevel
-                    + " mCurrentThermalLevelChanged:" + mCurrentThermalLevelChanged
-                    + " mCurrentAvgPowerConsumed:" + mCurrentAvgPowerConsumed
-                    + " mCustomAnimationRateSec:" + mCustomAnimationRateDeviceConfig);
-            mBrightnessCap = targetBrightnessCap;
-            mCustomAnimationRateSec = mCustomAnimationRateDeviceConfig;
-            mChangeListener.onChanged();
-        } else {
-            mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
-        }
-    }
-
-    private float getPowerQuotaForThermalStatus(@Temperature.ThrottlingStatus int thermalStatus) {
-        float powerQuota = 0f;
-        if (mPowerThrottlingDataActive != null) {
-            // Throttling levels are sorted by increasing severity
-            for (ThrottlingLevel level : mPowerThrottlingDataActive.throttlingLevels) {
-                if (level.thermalStatus <= thermalStatus) {
-                    powerQuota = level.powerQuotaMilliWatts;
-                } else {
-                    // Throttling levels that are greater than the current status are irrelevant
-                    break;
-                }
-            }
-        }
-        return powerQuota;
-    }
-
-    private void recalculatePowerQuotaChange(float avgPowerConsumed, int thermalStatus) {
-        mHandler.post(() -> {
-            if (mCurrentThermalLevel != thermalStatus) {
-                mCurrentThermalLevelChanged = true;
-            } else {
-                mCurrentThermalLevelChanged = false;
-            }
-            mCurrentThermalLevel = thermalStatus;
-            mCurrentAvgPowerConsumed = avgPowerConsumed;
-            recalculateBrightnessCap();
-        });
-    }
-
-    private void start() {
-        if (mPowerThrottlingConfigData == null) {
-            return;
-        }
-        if (mPowerThrottlingConfigData.pollingWindowMaxMillis
-                <= mPowerThrottlingConfigData.pollingWindowMinMillis) {
-            Slog.e(TAG, "Brightness power max polling window:"
-                    + mPowerThrottlingConfigData.pollingWindowMaxMillis
-                    + " msec, should be greater than brightness min polling window:"
-                    + mPowerThrottlingConfigData.pollingWindowMinMillis + " msec.");
-            return;
-        }
-        if ((mPowerThrottlingConfigData.pollingWindowMaxMillis
-                % mPowerThrottlingConfigData.pollingWindowMinMillis) != 0) {
-            Slog.e(TAG, "Brightness power max polling window:"
-                    + mPowerThrottlingConfigData.pollingWindowMaxMillis
-                    + " msec, is not divisible by brightness min polling window:"
-                    + mPowerThrottlingConfigData.pollingWindowMinMillis + " msec.");
-            return;
-        }
-        mCustomAnimationRateDeviceConfig = mPowerThrottlingConfigData.customAnimationRate;
-        mThermalLevelListener.start();
-    }
-
-    private void activatePmicMonitor() {
-        if (!mPmicMonitor.isStopped()) {
-            return;
-        }
-        mPmicMonitor.start();
-    }
-
-    private void deactivatePmicMonitor(@Temperature.ThrottlingStatus int status) {
-        if (status != Temperature.THROTTLING_NONE) {
-            return;
-        }
-        if (mPmicMonitor.isStopped()) {
-            return;
-        }
-        mPmicMonitor.stop();
-    }
-
-    private final class ThermalLevelListener extends IThermalEventListener.Stub {
-        private final Handler mHandler;
-        private IThermalService mThermalService;
-        private boolean mStarted;
-
-        ThermalLevelListener(Handler handler) {
-            mHandler = handler;
-            mStarted = false;
-            mThermalService = IThermalService.Stub.asInterface(
-                    ServiceManager.getService(Context.THERMAL_SERVICE));
-        }
-
-        IThermalService getThermalService() {
-            return mThermalService;
-        }
-
-        void start() {
-            if (mStarted) {
-                return;
-            }
-            if (mThermalService == null) {
-                return;
-            }
-            try {
-                // TODO b/279114539 Try DISPLAY first and then fallback to SKIN.
-                mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
-                mStarted = true;
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to register thermal status listener", e);
-            }
-        }
-
-        @Override
-        public void notifyThrottling(Temperature temp) {
-            @Temperature.ThrottlingStatus int status = temp.getStatus();
-            if (status >= Temperature.THROTTLING_LIGHT) {
-                Slog.d(TAG, "Activating pmic monitor due to thermal state:" + status);
-                mHandler.post(() -> activatePmicMonitor());
-            } else {
-                if (!mPmicMonitor.isStopped()) {
-                    mHandler.post(() -> deactivatePmicMonitor(status));
-                }
-            }
-        }
-
-        void stop() {
-            if (!mStarted) {
-                return;
-            }
-            try {
-                mThermalService.unregisterThermalEventListener(this);
-                mStarted = false;
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to unregister thermal status listener", e);
-            }
-            mThermalService = null;
-        }
-
-        void dump(PrintWriter writer) {
-            writer.println("  ThermalLevelObserver:");
-            writer.println("    mStarted: " + mStarted);
-        }
-    }
-
-    public interface PowerData {
-        @NonNull
-        String getUniqueDisplayId();
-
-        @NonNull
-        String getPowerThrottlingDataId();
-
-        @Nullable
-        PowerThrottlingData getPowerThrottlingData();
-
-        @Nullable
-        PowerThrottlingConfigData getPowerThrottlingConfigData();
-    }
-
-    /**
-     * Power change listener
-     */
-    @FunctionalInterface
-    public interface PowerChangeListener {
-        /**
-         * Notifies that power state changed from power controller.
-         */
-        void onChanged(float avgPowerConsumed, @Temperature.ThrottlingStatus int thermalStatus);
-    }
-
-    @VisibleForTesting
-    static class Injector {
-        PmicMonitor getPmicMonitor(PowerChangeListener powerChangeListener,
-                                   IThermalService thermalService,
-                                   int pollingMaxTimeMillis,
-                                   int pollingMinTimeMillis) {
-            return new PmicMonitor(powerChangeListener, thermalService, pollingMaxTimeMillis,
-                                        pollingMinTimeMillis);
-        }
-
-        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
-            return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerModifier.java
new file mode 100644
index 0000000..146f2f0
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerModifier.java
@@ -0,0 +1,483 @@
+/*
+ * 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.server.display.brightness.clamper;
+
+import static com.android.server.display.DisplayDeviceConfig.DEFAULT_ID;
+import static com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Temperature;
+import android.provider.DeviceConfigInterface;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+
+class BrightnessPowerModifier implements BrightnessStateModifier,
+        BrightnessClamperController.DisplayDeviceDataListener,
+        BrightnessClamperController.StatefulModifier,
+        BrightnessClamperController.DeviceConfigListener {
+
+    private static final String TAG = "BrightnessPowerClamper";
+    @NonNull
+    private final DeviceConfigParameterProvider mConfigParameterProvider;
+    @NonNull
+    private final PmicMonitor mPmicMonitor;
+    // data from DeviceConfig, for all displays, for all dataSets
+    // mapOf(uniqueDisplayId to mapOf(dataSetId to PowerThrottlingData))
+    @NonNull
+    private Map<String, Map<String, PowerThrottlingData>>
+            mPowerThrottlingDataOverride = Map.of();
+    // data from DisplayDeviceConfig, for particular display+dataSet
+    @Nullable
+    private PowerThrottlingData mPowerThrottlingDataFromDDC = null;
+    // Active data, if mPowerThrottlingDataOverride contains data for mUniqueDisplayId,
+    // mDataId, then use it, otherwise mPowerThrottlingDataFromDDC.
+    @Nullable
+    private PowerThrottlingData mPowerThrottlingDataActive = null;
+    @Nullable
+    private PowerThrottlingConfigData mPowerThrottlingConfigData;
+    @NonNull
+    private final ThermalLevelListener mThermalLevelListener;
+    private @Temperature.ThrottlingStatus int mCurrentThermalLevel = Temperature.THROTTLING_NONE;
+    private boolean mCurrentThermalLevelChanged = false;
+    private float mCurrentAvgPowerConsumed = 0;
+    @Nullable
+    private String mUniqueDisplayId = null;
+    @Nullable
+    private String mDataId = null;
+    private float mCurrentBrightness = PowerManager.BRIGHTNESS_INVALID;
+    private float mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+    private float mCustomAnimationRateDeviceConfig =
+                        DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+    private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
+        try {
+            int status = DeviceConfigParsingUtils.parseThermalStatus(key);
+            float powerQuota = Float.parseFloat(value);
+            return new ThrottlingLevel(status, powerQuota);
+        } catch (IllegalArgumentException iae) {
+            return null;
+        }
+    };
+
+    private final Function<List<ThrottlingLevel>, PowerThrottlingData>
+            mDataSetMapper = PowerThrottlingData::create;
+
+    protected final Handler mHandler;
+    protected final BrightnessClamperController.ClamperChangeListener mChangeListener;
+
+    private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;;
+    private boolean mApplied = false;
+
+
+    BrightnessPowerModifier(Handler handler, ClamperChangeListener listener,
+            PowerData powerData, float currentBrightness) {
+        this(new Injector(), handler, listener, powerData, currentBrightness);
+    }
+
+    @VisibleForTesting
+    BrightnessPowerModifier(@NonNull Injector injector, Handler handler,
+            ClamperChangeListener listener, PowerData powerData, float currentBrightness) {
+        mHandler = handler;
+        mChangeListener = listener;
+        mCurrentBrightness = currentBrightness;
+
+        mPowerThrottlingConfigData = powerData.getPowerThrottlingConfigData();
+        if (mPowerThrottlingConfigData != null) {
+            mCustomAnimationRateDeviceConfig = mPowerThrottlingConfigData.customAnimationRate;
+        }
+        mThermalLevelListener = new ThermalLevelListener(handler);
+        mPmicMonitor =
+            injector.getPmicMonitor(this::recalculatePowerQuotaChange,
+                    mThermalLevelListener.getThermalService(),
+                    mPowerThrottlingConfigData.pollingWindowMaxMillis,
+                    mPowerThrottlingConfigData.pollingWindowMinMillis);
+
+        mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+        mHandler.post(() -> {
+            setDisplayData(powerData);
+            loadOverrideData();
+            start();
+        });
+    }
+
+    //region BrightnessStateModifier
+    @Override
+    public void apply(DisplayManagerInternal.DisplayPowerRequest request,
+            DisplayBrightnessState.Builder stateBuilder) {
+        if (stateBuilder.getMaxBrightness() > mBrightnessCap) {
+            stateBuilder.setMaxBrightness(mBrightnessCap);
+            stateBuilder.setBrightness(Math.min(stateBuilder.getBrightness(), mBrightnessCap));
+            stateBuilder.setBrightnessMaxReason(BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC);
+            stateBuilder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
+            // set custom animation rate only when modifier is activated.
+            // this will allow auto brightness to apply slow change even when modifier is active
+            if (!mApplied) {
+                stateBuilder.setCustomAnimationRate(mCustomAnimationRateSec);
+                mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+            }
+            mApplied = true;
+        } else {
+            mApplied = false;
+        }
+        mCurrentBrightness = stateBuilder.getBrightness();
+    }
+
+    @Override
+    public void stop() {
+        mPmicMonitor.shutdown();
+        mThermalLevelListener.stop();
+    }
+
+    /**
+     * Dumps the state of BrightnessPowerClamper.
+     */
+    public void dump(PrintWriter pw) {
+        pw.println("BrightnessPowerClamper:");
+        pw.println("  mCurrentAvgPowerConsumed=" + mCurrentAvgPowerConsumed);
+        pw.println("  mUniqueDisplayId=" + mUniqueDisplayId);
+        pw.println("  mCurrentThermalLevel=" + mCurrentThermalLevel);
+        pw.println("  mCurrentThermalLevelChanged=" + mCurrentThermalLevelChanged);
+        pw.println("  mPowerThrottlingDataFromDDC=" + (mPowerThrottlingDataFromDDC == null ? "null"
+                : mPowerThrottlingDataFromDDC.toString()));
+        pw.println("  mBrightnessCap: " + mBrightnessCap);
+        pw.println("  mApplied: " + mApplied);
+        mThermalLevelListener.dump(pw);
+    }
+
+    @Override
+    public boolean shouldListenToLightSensor() {
+        return false;
+    }
+
+    @Override
+    public void setAmbientLux(float lux) {
+        // noop
+    }
+    //endregion
+
+    //region DisplayDeviceDataListener
+    @Override
+    public void onDisplayChanged(BrightnessClamperController.DisplayDeviceData data) {
+        mHandler.post(() -> {
+            setDisplayData(data);
+            recalculateActiveData();
+        });
+    }
+    //endregion
+
+    //region StatefulModifier
+    @Override
+    public void applyStateChange(
+            BrightnessClamperController.ModifiersAggregatedState aggregatedState) {
+        if (aggregatedState.mMaxBrightness > mBrightnessCap) {
+            aggregatedState.mMaxBrightness = mBrightnessCap;
+            aggregatedState.mMaxBrightnessReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;
+        }
+    }
+    //endregion
+
+    //region DeviceConfigListener
+    @Override
+    public void onDeviceConfigChanged() {
+        mHandler.post(() -> {
+            loadOverrideData();
+            recalculateActiveData();
+        });
+    }
+    //endregion
+
+    private void recalculateActiveData() {
+        if (mUniqueDisplayId == null || mDataId == null) {
+            return;
+        }
+        mPowerThrottlingDataActive = mPowerThrottlingDataOverride
+                .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId,
+                        mPowerThrottlingDataFromDDC);
+        if (mPowerThrottlingDataActive == null) {
+            mPmicMonitor.stop();
+        }
+    }
+
+    private void loadOverrideData() {
+        String throttlingDataOverride = mConfigParameterProvider.getPowerThrottlingData();
+        mPowerThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap(
+                throttlingDataOverride, mDataPointMapper, mDataSetMapper);
+    }
+
+    private void setDisplayData(@NonNull PowerData data) {
+        mUniqueDisplayId = data.getUniqueDisplayId();
+        mDataId = data.getPowerThrottlingDataId();
+        mPowerThrottlingDataFromDDC = data.getPowerThrottlingData();
+        if (mPowerThrottlingDataFromDDC == null && !DEFAULT_ID.equals(mDataId)) {
+            Slog.wtf(TAG,
+                    "Power throttling data is missing for powerThrottlingDataId=" + mDataId);
+        }
+
+        mPowerThrottlingConfigData = data.getPowerThrottlingConfigData();
+    }
+
+    private void recalculateBrightnessCap() {
+        float targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+        float powerQuota = getPowerQuotaForThermalStatus(mCurrentThermalLevel);
+        if (mPowerThrottlingDataActive == null) {
+            return;
+        }
+        if (powerQuota > 0) {
+            if (BrightnessUtils.isValidBrightnessValue(mCurrentBrightness)
+                    && (mCurrentAvgPowerConsumed > powerQuota)) {
+                // calculate new brightness Cap.
+                // Brightness has a linear relation to power-consumed.
+                targetBrightnessCap =
+                    (powerQuota / mCurrentAvgPowerConsumed) * mCurrentBrightness;
+            } else if (mCurrentThermalLevelChanged) {
+                if (mCurrentThermalLevel == Temperature.THROTTLING_NONE) {
+                    // reset pmic and remove the power-throttling cap.
+                    targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+                    mPmicMonitor.stop();
+                } else {
+                    // Since the thermal status has changed, we need to remove power-throttling cap.
+                    // Instead of recalculating and changing brightness again, adding flicker,
+                    // we will wait for the next pmic cycle to re-evaluate this value
+                    // make act on it, if needed.
+                    targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+                    if (mPmicMonitor.isStopped()) {
+                        mPmicMonitor.start();
+                    }
+                }
+            } else { // Current power consumed is under the quota.
+                targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+            }
+        }
+
+        // Cap to lowest allowed brightness on device.
+        if (mPowerThrottlingConfigData != null) {
+            targetBrightnessCap = Math.max(targetBrightnessCap,
+                                mPowerThrottlingConfigData.brightnessLowestCapAllowed);
+        }
+
+        if (mBrightnessCap != targetBrightnessCap) {
+            Slog.i(TAG, "Power clamper changing current brightness cap mBrightnessCap: "
+                    + mBrightnessCap + " to target brightness cap:" + targetBrightnessCap
+                    + " for current screen brightness: " + mCurrentBrightness + "\n"
+                    + "Power clamper changed state: thermalStatus:" + mCurrentThermalLevel
+                    + " mCurrentThermalLevelChanged:" + mCurrentThermalLevelChanged
+                    + " mCurrentAvgPowerConsumed:" + mCurrentAvgPowerConsumed
+                    + " mCustomAnimationRateSec:" + mCustomAnimationRateDeviceConfig);
+            mBrightnessCap = targetBrightnessCap;
+            mCustomAnimationRateSec = mCustomAnimationRateDeviceConfig;
+            mChangeListener.onChanged();
+        } else {
+            mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+        }
+    }
+
+    private float getPowerQuotaForThermalStatus(@Temperature.ThrottlingStatus int thermalStatus) {
+        float powerQuota = 0f;
+        if (mPowerThrottlingDataActive != null) {
+            // Throttling levels are sorted by increasing severity
+            for (ThrottlingLevel level : mPowerThrottlingDataActive.throttlingLevels) {
+                if (level.thermalStatus <= thermalStatus) {
+                    powerQuota = level.powerQuotaMilliWatts;
+                } else {
+                    // Throttling levels that are greater than the current status are irrelevant
+                    break;
+                }
+            }
+        }
+        return powerQuota;
+    }
+
+    private void recalculatePowerQuotaChange(float avgPowerConsumed, int thermalStatus) {
+        mHandler.post(() -> {
+            mCurrentThermalLevelChanged = mCurrentThermalLevel != thermalStatus;
+            mCurrentThermalLevel = thermalStatus;
+            mCurrentAvgPowerConsumed = avgPowerConsumed;
+            recalculateBrightnessCap();
+        });
+    }
+
+    private void start() {
+        if (mPowerThrottlingConfigData == null) {
+            return;
+        }
+        if (mPowerThrottlingConfigData.pollingWindowMaxMillis
+                <= mPowerThrottlingConfigData.pollingWindowMinMillis) {
+            Slog.e(TAG, "Brightness power max polling window:"
+                    + mPowerThrottlingConfigData.pollingWindowMaxMillis
+                    + " msec, should be greater than brightness min polling window:"
+                    + mPowerThrottlingConfigData.pollingWindowMinMillis + " msec.");
+            return;
+        }
+        if ((mPowerThrottlingConfigData.pollingWindowMaxMillis
+                % mPowerThrottlingConfigData.pollingWindowMinMillis) != 0) {
+            Slog.e(TAG, "Brightness power max polling window:"
+                    + mPowerThrottlingConfigData.pollingWindowMaxMillis
+                    + " msec, is not divisible by brightness min polling window:"
+                    + mPowerThrottlingConfigData.pollingWindowMinMillis + " msec.");
+            return;
+        }
+        mCustomAnimationRateDeviceConfig = mPowerThrottlingConfigData.customAnimationRate;
+        mThermalLevelListener.start();
+    }
+
+    private void activatePmicMonitor() {
+        if (!mPmicMonitor.isStopped()) {
+            return;
+        }
+        mPmicMonitor.start();
+    }
+
+    private void deactivatePmicMonitor(@Temperature.ThrottlingStatus int status) {
+        if (status != Temperature.THROTTLING_NONE) {
+            return;
+        }
+        if (mPmicMonitor.isStopped()) {
+            return;
+        }
+        mPmicMonitor.stop();
+    }
+
+    private final class ThermalLevelListener extends IThermalEventListener.Stub {
+        private final Handler mHandler;
+        private IThermalService mThermalService;
+        private boolean mStarted;
+
+        ThermalLevelListener(Handler handler) {
+            mHandler = handler;
+            mStarted = false;
+            mThermalService = IThermalService.Stub.asInterface(
+                    ServiceManager.getService(Context.THERMAL_SERVICE));
+        }
+
+        IThermalService getThermalService() {
+            return mThermalService;
+        }
+
+        void start() {
+            if (mStarted) {
+                return;
+            }
+            if (mThermalService == null) {
+                return;
+            }
+            try {
+                // TODO b/279114539 Try DISPLAY first and then fallback to SKIN.
+                mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
+                mStarted = true;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to register thermal status listener", e);
+            }
+        }
+
+        @Override
+        public void notifyThrottling(Temperature temp) {
+            @Temperature.ThrottlingStatus int status = temp.getStatus();
+            if (status >= Temperature.THROTTLING_LIGHT) {
+                Slog.d(TAG, "Activating pmic monitor due to thermal state:" + status);
+                mHandler.post(BrightnessPowerModifier.this::activatePmicMonitor);
+            } else {
+                if (!mPmicMonitor.isStopped()) {
+                    mHandler.post(() -> deactivatePmicMonitor(status));
+                }
+            }
+        }
+
+        void stop() {
+            if (!mStarted) {
+                return;
+            }
+            try {
+                mThermalService.unregisterThermalEventListener(this);
+                mStarted = false;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to unregister thermal status listener", e);
+            }
+            mThermalService = null;
+        }
+
+        void dump(PrintWriter writer) {
+            writer.println("  ThermalLevelObserver:");
+            writer.println("    mStarted: " + mStarted);
+        }
+    }
+
+    public interface PowerData {
+        @NonNull
+        String getUniqueDisplayId();
+
+        @NonNull
+        String getPowerThrottlingDataId();
+
+        @Nullable
+        PowerThrottlingData getPowerThrottlingData();
+
+        @Nullable
+        PowerThrottlingConfigData getPowerThrottlingConfigData();
+    }
+
+    /**
+     * Power change listener
+     */
+    @FunctionalInterface
+    public interface PowerChangeListener {
+        /**
+         * Notifies that power state changed from power controller.
+         */
+        void onChanged(float avgPowerConsumed, @Temperature.ThrottlingStatus int thermalStatus);
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        @NonNull
+        PmicMonitor getPmicMonitor(PowerChangeListener powerChangeListener,
+                                   IThermalService thermalService,
+                                   int pollingMaxTimeMillis,
+                                   int pollingMinTimeMillis) {
+            return new PmicMonitor(powerChangeListener, thermalService, pollingMaxTimeMillis,
+                                        pollingMinTimeMillis);
+        }
+
+        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+            return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
index ab880bf..0237af3 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
@@ -38,12 +38,12 @@
     // mScreenBrightnessDimConfig.
     private final float mScreenBrightnessMinimumDimAmount;
 
-    DisplayDimModifier(Context context) {
+    DisplayDimModifier(int displayId, Context context) {
         PowerManager pm = Objects.requireNonNull(context.getSystemService(PowerManager.class));
         Resources resources = context.getResources();
 
         mScreenBrightnessDimConfig = BrightnessUtils.clampAbsoluteBrightness(
-                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
+                pm.getBrightnessConstraint(displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
         mScreenBrightnessMinimumDimAmount = resources.getFloat(
                 R.dimen.config_screenBrightnessMinimumDimAmountFloat);
     }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
index 355f4fe..3b748d7 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
@@ -16,7 +16,7 @@
 
 package com.android.server.display.brightness.clamper;
 
-import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
+import static com.android.server.display.brightness.clamper.BrightnessPowerModifier.PowerChangeListener;
 
 import android.annotation.Nullable;
 import android.hardware.power.stats.EnergyConsumer;
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 7f46bbb..45106f5 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -243,6 +243,11 @@
             Flags::autoBrightnessModeBedtimeWear
     );
 
+    private final FlagState mGetSupportedRefreshRatesFlagState = new FlagState(
+            Flags.FLAG_ENABLE_GET_SUPPORTED_REFRESH_RATES,
+            Flags::enableGetSupportedRefreshRates
+    );
+
     private final FlagState mEnablePluginManagerFlagState = new FlagState(
             Flags.FLAG_ENABLE_PLUGIN_MANAGER,
             Flags::enablePluginManager
@@ -252,6 +257,11 @@
             Flags::displayListenerPerformanceImprovements
     );
 
+    private final FlagState mSubscribeGranularDisplayEvents = new FlagState(
+            Flags.FLAG_SUBSCRIBE_GRANULAR_DISPLAY_EVENTS,
+            Flags::subscribeGranularDisplayEvents
+    );
+
     /**
      * @return {@code true} if 'port' is allowed in display layout configuration file.
      */
@@ -528,6 +538,13 @@
         return mAutoBrightnessModeBedtimeWearFlagState.isEnabled();
     }
 
+    /**
+     * @return {@code true} if supported refresh rate api is enabled.
+     */
+    public boolean enableGetSupportedRefreshRates() {
+        return mGetSupportedRefreshRatesFlagState.isEnabled();
+    }
+
     public boolean isPluginManagerEnabled() {
         return mEnablePluginManagerFlagState.isEnabled();
     }
@@ -540,6 +557,13 @@
     }
 
     /**
+     * @return {@code true} if the flag for subscribing to granular display events is enabled
+     */
+    public boolean isSubscribeGranularDisplayEventsEnabled() {
+        return mSubscribeGranularDisplayEvents.isEnabled();
+    }
+
+    /**
      * dumps all flagstates
      * @param pw printWriter
      */
@@ -590,8 +614,10 @@
         pw.println(" " + mIsUserRefreshRateForExternalDisplayEnabled);
         pw.println(" " + mHasArrSupport);
         pw.println(" " + mAutoBrightnessModeBedtimeWearFlagState);
+        pw.println(" " + mGetSupportedRefreshRatesFlagState);
         pw.println(" " + mEnablePluginManagerFlagState);
         pw.println(" " + mDisplayListenerPerformanceImprovementsFlagState);
+        pw.println(" " + mSubscribeGranularDisplayEvents);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index e7ea868..123b7df 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -9,6 +9,7 @@
     description: "Allows querying of AOD availability"
     bug: "324046664"
     is_fixed_read_only: true
+    is_exported: true
 }
 
 flag {
@@ -267,6 +268,7 @@
     description: "Feature flag for an API to get the highest defined HDR/SDR ratio for a display."
     bug: "335181559"
     is_fixed_read_only: true
+    is_exported: true
 }
 
 flag {
@@ -429,6 +431,7 @@
     description: "Flag for an API to get whether display supports ARR or not"
     bug: "361433651"
     is_fixed_read_only: true
+    is_exported: true
 }
 
 flag {
@@ -445,6 +448,7 @@
     description: "Flag for an API to get suggested frame rates"
     bug: "361433796"
     is_fixed_read_only: true
+    is_exported: true
 }
 
 flag {
@@ -453,6 +457,7 @@
     description: "Feature flag for an API to let the apps subscribe to a specific property change of the Display."
     bug: "372700957"
     is_fixed_read_only: true
+    is_exported: true
 }
 
 flag {
@@ -461,6 +466,7 @@
     description: "Flag to use the surfaceflinger rates for getSupportedRefreshRates"
     bug: "365163968"
     is_fixed_read_only: true
+    is_exported: true
 }
 
 flag {
@@ -478,3 +484,14 @@
     bug: "378385869"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "subscribe_granular_display_events"
+    namespace: "display_manager"
+    description: "Enable subscription to granular display change events."
+    bug: "379250634"
+    is_fixed_read_only: true
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 0e7d2b6..a06f9ef 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -524,6 +524,13 @@
     static final String PROPERTY_DISABLE_CEC_ON_STANDBY_IN_LOW_ENERGY_MODE =
             "persist.sys.hdmi.property_disable_cec_on_standby_in_low_energy_mode";
 
+    /**
+     * Property that checks if CEC was manually enabled by the user in offline mode. With the help
+     * of this property we avoid turning off CEC when the device goes to sleep and if the device
+     * is in low energy mode.
+     */
+    static final String PROPERTY_USER_ACTION_KEEP_CEC_ENABLED_IN_OFFLINE_MODE =
+            "persist.sys.hdmi.property_user_action_keep_cec_enabled_in_offline_mode";
     static final int RECORDING_TYPE_DIGITAL_RF = 1;
     static final int RECORDING_TYPE_ANALOGUE_RF = 2;
     static final int RECORDING_TYPE_EXTERNAL_PHYSICAL_ADDRESS = 3;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
index 53c0217..102de73 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
@@ -16,6 +16,8 @@
 
 package com.android.server.hdmi;
 
+import static android.media.tv.flags.Flags.hdmiControlCollectPhysicalAddress;
+
 import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING;
 import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_CONNECTED;
 import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING;
@@ -35,6 +37,8 @@
     @VisibleForTesting
     protected static final int FEATURE_ABORT_OPCODE_UNKNOWN = 0x100;
     private static final int ERROR_CODE_UNKNOWN = -1;
+    @VisibleForTesting
+    protected static final int PHYSICAL_ADDRESS_INVALID = 0xFFFF;
 
     /**
      * Writes a HdmiCecMessageReported atom representing an HDMI CEC message.
@@ -95,6 +99,11 @@
                 return createUserControlPressedSpecialArgs(message);
             case Constants.MESSAGE_FEATURE_ABORT:
                 return createFeatureAbortSpecialArgs(message);
+            case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS:
+                if (hdmiControlCollectPhysicalAddress()) {
+                    return createReportPhysicalAddressSpecialArgs(message);
+                }
+                return new MessageReportedSpecialArgs();
             default:
                 return new MessageReportedSpecialArgs();
         }
@@ -140,6 +149,23 @@
     }
 
     /**
+     * Constructs the special arguments for a <Report Physical Address> message.
+     *
+     * @param message The HDMI CEC message to log
+     */
+    private MessageReportedSpecialArgs createReportPhysicalAddressSpecialArgs(
+            HdmiCecMessage message) {
+        MessageReportedSpecialArgs specialArgs = new MessageReportedSpecialArgs();
+
+        if (message.getParams().length > 1) {
+            int physicalAddress = (message.getParams()[0] << 8) | message.getParams()[1];
+            specialArgs.mPhysicalAddress = physicalAddress;
+        }
+
+        return specialArgs;
+    }
+
+    /**
      * Writes a HdmiCecMessageReported atom.
      *
      * @param genericArgs Generic arguments; shared by all HdmiCecMessageReported atoms
@@ -156,7 +182,8 @@
                 genericArgs.mSendMessageResult,
                 specialArgs.mUserControlPressedCommand,
                 specialArgs.mFeatureAbortOpcode,
-                specialArgs.mFeatureAbortReason);
+                specialArgs.mFeatureAbortReason,
+                specialArgs.mPhysicalAddress);
     }
 
     /**
@@ -166,7 +193,7 @@
     protected void writeHdmiCecMessageReportedAtom(int uid, int direction,
             int initiatorLogicalAddress, int destinationLogicalAddress, int opcode,
             int sendMessageResult, int userControlPressedCommand, int featureAbortOpcode,
-            int featureAbortReason) {
+            int featureAbortReason, int physicalAddress) {
         FrameworkStatsLog.write(
                 FrameworkStatsLog.HDMI_CEC_MESSAGE_REPORTED,
                 uid,
@@ -177,7 +204,8 @@
                 sendMessageResult,
                 userControlPressedCommand,
                 featureAbortOpcode,
-                featureAbortReason);
+                featureAbortReason,
+                physicalAddress);
     }
 
     /**
@@ -237,6 +265,26 @@
                 enumLogReason);
     }
 
+    /**
+     * Writes a HdmiPowerStateChangeOnActiveSourceLostToggled atom representing a
+     * HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST setting change.
+     * @param isEnabled           Whether the setting is enabled.
+     * @param enumLogReason       The event that triggered the log.
+     * @param manufacturerPnpId   Manufacturer PNP ID reported in the EDID.
+     * @param manufacturerYear    Manufacture year reported in the EDID.
+     * @param manufacturerWeek    Manufacture week reporter in the EDID.
+     */
+    public void powerStateChangeOnActiveSourceLostChanged(boolean isEnabled, int enumLogReason,
+            String manufacturerPnpId, int manufacturerYear, int manufacturerWeek) {
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.HDMI_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLED,
+                isEnabled,
+                enumLogReason,
+                manufacturerPnpId,
+                manufacturerYear,
+                manufacturerWeek);
+    }
+
     private int earcStateToEnum(int earcState) {
         switch (earcState) {
             case HDMI_EARC_STATUS_IDLE:
@@ -284,5 +332,6 @@
         int mUserControlPressedCommand = HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN;
         int mFeatureAbortOpcode = FEATURE_ABORT_OPCODE_UNKNOWN;
         int mFeatureAbortReason = HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN;
+        int mPhysicalAddress = PHYSICAL_ADDRESS_INVALID;
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 1b527da..0b667fc 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.hardware.display.DeviceProductInfo;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
@@ -31,6 +32,7 @@
 import android.os.SystemProperties;
 import android.sysprop.HdmiProperties;
 import android.util.Slog;
+import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.LocalePicker;
@@ -82,6 +84,8 @@
     // lost.
     private Handler mDelayedPopupOnActiveSourceLostHandler;
 
+    private boolean mIsActiveSourceLostPopupLaunched;
+
     // Determines what action should be taken upon receiving Routing Control messages.
     @VisibleForTesting
     protected HdmiProperties.playback_device_action_on_routing_control_values
@@ -96,6 +100,7 @@
         mDelayedStandbyOnActiveSourceLostHandler = new Handler(service.getServiceLooper());
         mDelayedPopupOnActiveSourceLostHandler = new Handler(service.getServiceLooper());
         mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
+        mIsActiveSourceLostPopupLaunched = false;
     }
 
     @Override
@@ -275,6 +280,7 @@
         public void run() {
             if (!isActiveSource()) {
                 mService.standby();
+                mIsActiveSourceLostPopupLaunched = false;
             }
         }
     }
@@ -283,6 +289,7 @@
     void dismissUiOnActiveSourceStatusRecovered() {
         assertRunOnServiceThread();
         Intent intent = new Intent(HdmiControlManager.ACTION_ON_ACTIVE_SOURCE_RECOVERED_DISMISS_UI);
+        mIsActiveSourceLostPopupLaunched = false;
         mService.sendBroadcastAsUser(intent);
     }
 
@@ -516,6 +523,7 @@
                     )));
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             context.startActivityAsUser(intent, context.getUser());
+            mIsActiveSourceLostPopupLaunched = true;
         } catch (ActivityNotFoundException e) {
             Slog.e(TAG, "Unable to start HdmiCecActiveSourceLostActivity");
         } finally {
@@ -733,6 +741,14 @@
         return Constants.ADDR_TV;
     }
 
+    boolean isActiveSourceLostPopupLaunched() {
+        return mIsActiveSourceLostPopupLaunched;
+    }
+
+    void setIsActiveSourceLostPopupLaunched(boolean isActiveSourceLostPopupLaunched) {
+        mIsActiveSourceLostPopupLaunched = isActiveSourceLostPopupLaunched;
+    }
+
     @Override
     @ServiceThreadOnly
     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 0c5069f..35ef18b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -50,6 +50,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.database.ContentObserver;
+import android.hardware.display.DeviceProductInfo;
 import android.hardware.display.DisplayManager;
 import android.hardware.hdmi.DeviceFeatures;
 import android.hardware.hdmi.HdmiControlManager;
@@ -106,6 +107,7 @@
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.KeyEvent;
+import android.view.WindowManager;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -725,6 +727,13 @@
         }
         mPowerStatusController.setPowerStatus(getInitialPowerStatus());
         setProhibitMode(false);
+        if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) {
+            Slog.w(TAG, "Re-enable CEC on boot-up since it was disabled due to low energy "
+                    + " mode.");
+            mHdmiCecConfig.setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+                    HDMI_CEC_CONTROL_ENABLED);
+            setWasCecDisabledOnStandbyByLowEnergyMode(false);
+        }
         mHdmiControlEnabled = mHdmiCecConfig.getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
 
@@ -771,14 +780,6 @@
             Slog.i(TAG, "Device does not support eARC.");
         }
         mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController);
-        if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) {
-            Slog.w(TAG, "Re-enable CEC on boot-up since it was disabled due to low energy "
-                    + " mode.");
-            getHdmiCecConfig().setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
-                    HDMI_CEC_CONTROL_ENABLED);
-            setWasCecDisabledOnStandbyByLowEnergyMode(false);
-            setCecEnabled(HDMI_CEC_CONTROL_ENABLED);
-        }
         if (isCecControlEnabled()) {
             initializeCec(INITIATED_BY_BOOT_UP);
         } else {
@@ -1008,6 +1009,21 @@
                     }
                 }, mServiceThreadExecutor);
 
+        if (isPlaybackDevice()) {
+            mHdmiCecConfig.registerChangeListener(
+                    HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+                    new HdmiCecConfig.SettingChangeListener() {
+                        @Override
+                        public void onChange(String setting) {
+                            boolean goToStandbyOnActiveSourceLost =
+                                    mHdmiCecConfig.getStringValue(
+                                            HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)
+                                            .equals(HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+                            writePowerStateChangeOnActiveSourceLostAtom(goToStandbyOnActiveSourceLost);
+                        }
+                    }, mServiceThreadExecutor);
+        }
+
         mDeviceConfig.addOnPropertiesChangedListener(getContext().getMainExecutor(),
                 new DeviceConfig.OnPropertiesChangedListener() {
                     @Override
@@ -3190,6 +3206,7 @@
             // Cancel an existing timer to send the device to sleep since OTP was triggered.
             playback().mDelayedStandbyOnActiveSourceLostHandler
                     .removeCallbacksAndMessages(null);
+            playback().setIsActiveSourceLostPopupLaunched(false);
         }
 
         if (source == null) {
@@ -4027,7 +4044,8 @@
                     return;
                 }
                 if (isTvDevice() && getDisableCecOnStandbyByLowEnergyMode()
-                        && mPowerManager.isLowPowerStandbyEnabled()) {
+                        && mPowerManager.isLowPowerStandbyEnabled()
+                        && !userEnabledCecInOfflineMode()) {
                     Slog.w(TAG, "Disable CEC on standby due to low power energy mode.");
                     setWasCecDisabledOnStandbyByLowEnergyMode(true);
                     getHdmiCecConfig().setIntValue(
@@ -5225,4 +5243,42 @@
                 Constants.PROPERTY_WAS_CEC_DISABLED_ON_STANDBY_BY_LOW_ENERGY_MODE,
                 String.valueOf(value));
     }
+
+    /**
+     * Writes a HdmiPowerStateChangeOnActiveSourceLostToggled atom representing a
+     * HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST setting change.
+     */
+    protected void writePowerStateChangeOnActiveSourceLostAtom(boolean isSettingEnabled) {
+        String manufacturerPnpId = "undefined";
+        int manufactureYear = -1;
+        int manufactureWeek = -1;
+        Display display = getContext().getDisplay();
+        if (display != null) {
+            DeviceProductInfo deviceProductInfo = display.getDeviceProductInfo();
+            manufacturerPnpId = deviceProductInfo.getManufacturerPnpId();
+            manufactureYear = deviceProductInfo.getManufactureYear();
+        }
+        int enumLogReason =
+                HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_UNKNOWN;
+        if (playback() != null) {
+            if (playback().isActiveSourceLostPopupLaunched()) {
+                enumLogReason = HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_POP_UP;
+            } else {
+                enumLogReason = HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_SETTING;
+            }
+        }
+
+        getAtomWriter().powerStateChangeOnActiveSourceLostChanged(isSettingEnabled, enumLogReason,
+                manufacturerPnpId, manufactureYear, manufactureWeek);
+    }
+
+    /**
+     * Reads the property that checks if CEC was enabled by the user while in offline mode such that
+     * it won't be disabled when going to sleep by low energy mode.
+     */
+    @VisibleForTesting
+    protected boolean userEnabledCecInOfflineMode() {
+        return SystemProperties.getBoolean(
+                Constants.PROPERTY_USER_ACTION_KEEP_CEC_ENABLED_IN_OFFLINE_MODE, false);
+    }
 }
diff --git a/services/core/java/com/android/server/incident/PendingReports.java b/services/core/java/com/android/server/incident/PendingReports.java
index 35b3673..9a6c87e 100644
--- a/services/core/java/com/android/server/incident/PendingReports.java
+++ b/services/core/java/com/android/server/incident/PendingReports.java
@@ -324,16 +324,12 @@
 
         // Allow system apps to skip the consent dialog and use their in-built consent mechanism
         // instead.
-        boolean captureConsentlessBugreportDelegatedConsentGranted = false;
-        if ((flags & IncidentManager.FLAG_ALLOW_CONSENTLESS_BUGREPORT) != 0) {
-            captureConsentlessBugreportDelegatedConsentGranted =
-                    mPermissionManager.checkPermissionForDataDelivery(
-                                    Manifest.permission
-                                            .CAPTURE_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT,
-                                    attributionSource,
-                                    /* message= */ null)
-                            == PERMISSION_GRANTED;
-        }
+        boolean captureConsentlessBugreportDelegatedConsentGranted =
+                mPermissionManager.checkPermissionForDataDelivery(
+                        Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_DELEGATED_CONSENT,
+                        attributionSource,
+                        /* message= */ null)
+                        == PERMISSION_GRANTED;
 
         if (captureConsentlessBugreportOnUserdebugBuildGranted
                 || captureConsentlessBugreportDelegatedConsentGranted) {
diff --git a/services/core/java/com/android/server/input/InputFeatureFlagProvider.java b/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
deleted file mode 100644
index a646d1e..0000000
--- a/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.input;
-
-import android.sysprop.InputProperties;
-
-import java.util.Optional;
-
-/**
- * A component of {@link InputManagerService} responsible for managing the input sysprop flags
- *
- * @hide
- */
-@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
-public final class InputFeatureFlagProvider {
-
-    // To disable Keyboard backlight control via Framework, run:
-    // 'adb shell setprop persist.input.keyboard_backlight_control.enabled false' (requires restart)
-    private static final boolean KEYBOARD_BACKLIGHT_CONTROL_ENABLED =
-            InputProperties.enable_keyboard_backlight_control().orElse(true);
-
-    // To disable Framework controlled keyboard backlight animation run:
-    // adb shell setprop persist.input.keyboard.backlight_animation.enabled false (requires restart)
-    private static final boolean KEYBOARD_BACKLIGHT_ANIMATION_ENABLED =
-            InputProperties.enable_keyboard_backlight_animation().orElse(false);
-
-    // To disable Custom keyboard backlight levels support via IDC files run:
-    // adb shell setprop persist.input.keyboard.backlight_custom_levels.enabled false (requires
-    // restart)
-    private static final boolean KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED =
-            InputProperties.enable_keyboard_backlight_custom_levels().orElse(true);
-
-    // To disable als based ambient keyboard backlight control run:
-    // adb shell setprop persist.input.keyboard.ambient_backlight_control.enabled false (requires
-    // restart)
-    private static final boolean AMBIENT_KEYBOARD_BACKLIGHT_CONTROL_ENABLED =
-            InputProperties.enable_ambient_keyboard_backlight_control().orElse(true);
-
-    private static Optional<Boolean> sKeyboardBacklightControlOverride = Optional.empty();
-    private static Optional<Boolean> sKeyboardBacklightAnimationOverride = Optional.empty();
-    private static Optional<Boolean> sKeyboardBacklightCustomLevelsOverride = Optional.empty();
-    private static Optional<Boolean> sAmbientKeyboardBacklightControlOverride = Optional.empty();
-
-    public static boolean isKeyboardBacklightControlEnabled() {
-        return sKeyboardBacklightControlOverride.orElse(KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
-    }
-
-    public static boolean isKeyboardBacklightAnimationEnabled() {
-        return sKeyboardBacklightAnimationOverride.orElse(KEYBOARD_BACKLIGHT_ANIMATION_ENABLED);
-    }
-
-    public static boolean isKeyboardBacklightCustomLevelsEnabled() {
-        return sKeyboardBacklightCustomLevelsOverride.orElse(
-                KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED);
-    }
-
-    public static boolean isAmbientKeyboardBacklightControlEnabled() {
-        return sAmbientKeyboardBacklightControlOverride.orElse(
-                AMBIENT_KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
-    }
-
-    public static void setKeyboardBacklightControlEnabled(boolean enabled) {
-        sKeyboardBacklightControlOverride = Optional.of(enabled);
-    }
-
-    public static void setKeyboardBacklightAnimationEnabled(boolean enabled) {
-        sKeyboardBacklightAnimationOverride = Optional.of(enabled);
-    }
-
-    public static void setKeyboardBacklightCustomLevelsEnabled(boolean enabled) {
-        sKeyboardBacklightCustomLevelsOverride = Optional.of(enabled);
-    }
-
-    public static void setAmbientKeyboardBacklightControlEnabled(boolean enabled) {
-        sAmbientKeyboardBacklightControlOverride = Optional.of(enabled);
-    }
-
-    /**
-     * Clears all input feature flag overrides.
-     */
-    public static void clearOverrides() {
-        sKeyboardBacklightControlOverride = Optional.empty();
-        sKeyboardBacklightAnimationOverride = Optional.empty();
-        sKeyboardBacklightCustomLevelsOverride = Optional.empty();
-        sAmbientKeyboardBacklightControlOverride = Optional.empty();
-    }
-}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 1adf1c9..4454dd4 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -471,11 +471,9 @@
         }
 
         KeyboardBacklightControllerInterface getKeyboardBacklightController(
-                NativeInputManagerService nativeService, PersistentDataStore dataStore) {
-            return InputFeatureFlagProvider.isKeyboardBacklightControlEnabled()
-                    ? new KeyboardBacklightController(mContext, nativeService, dataStore,
-                    mLooper, mUEventManager)
-                    : new KeyboardBacklightControllerInterface() {};
+                NativeInputManagerService nativeService) {
+            return new KeyboardBacklightController(mContext, nativeService, mLooper,
+                    mUEventManager);
         }
     }
 
@@ -500,7 +498,7 @@
                         injector.getLooper(), this) : null;
         mBatteryController = new BatteryController(mContext, mNative, injector.getLooper(),
                 injector.getUEventManager());
-        mKeyboardBacklightController = injector.getKeyboardBacklightController(mNative, mDataStore);
+        mKeyboardBacklightController = injector.getKeyboardBacklightController(mNative);
         mStickyModifierStateController = new StickyModifierStateController();
         mKeyGestureController = new KeyGestureController(mContext, injector.getLooper());
         mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(),
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 3b2305c..bf08563 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -81,6 +81,8 @@
                         (reason) -> updateTouchpadHardwareStateNotificationsEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE),
                         (reason) -> updateTouchpadRightClickZoneEnabled()),
+                Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_SYSTEM_GESTURES),
+                        (reason) -> updateTouchpadSystemGesturesEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
                         (reason) -> updateShowTouches()),
                 Map.entry(Settings.System.getUriFor(Settings.System.POINTER_LOCATION),
@@ -213,6 +215,10 @@
                 InputSettings.useTouchpadThreeFingerTapShortcut(mContext));
     }
 
+    private void updateTouchpadSystemGesturesEnabled() {
+        mNative.setTouchpadSystemGesturesEnabled(InputSettings.useTouchpadSystemGestures(mContext));
+    }
+
     private void updateShowTouches() {
         mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
     }
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index 0defd27..16368c7 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -33,6 +33,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UEventObserver;
+import android.sysprop.InputProperties;
 import android.text.TextUtils;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
@@ -47,7 +48,6 @@
 import java.time.Duration;
 import java.util.Arrays;
 import java.util.Objects;
-import java.util.OptionalInt;
 import java.util.TreeSet;
 
 /**
@@ -63,6 +63,10 @@
     // 'adb shell setprop log.tag.KbdBacklightController DEBUG' (requires restart)
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    // To disable Framework controlled keyboard backlight animation run:
+    // adb shell setprop persist.input.keyboard.backlight_animation.enabled false (requires restart)
+    private final boolean mKeyboardBacklightAnimationEnabled;
+
     private enum Direction {
         DIRECTION_UP, DIRECTION_DOWN
     }
@@ -87,9 +91,6 @@
 
     private final Context mContext;
     private final NativeInputManagerService mNative;
-    // The PersistentDataStore should be locked before use.
-    @GuardedBy("mDataStore")
-    private final PersistentDataStore mDataStore;
     private final Handler mHandler;
     private final AnimatorFactory mAnimatorFactory;
     private final UEventManager mUEventManager;
@@ -123,17 +124,15 @@
     }
 
     KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
-            PersistentDataStore dataStore, Looper looper, UEventManager uEventManager) {
-        this(context, nativeService, dataStore, looper, ValueAnimator::ofInt, uEventManager);
+            Looper looper, UEventManager uEventManager) {
+        this(context, nativeService, looper, ValueAnimator::ofInt, uEventManager);
     }
 
     @VisibleForTesting
     KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
-            PersistentDataStore dataStore, Looper looper, AnimatorFactory animatorFactory,
-            UEventManager uEventManager) {
+            Looper looper, AnimatorFactory animatorFactory, UEventManager uEventManager) {
         mContext = context;
         mNative = nativeService;
-        mDataStore = dataStore;
         mHandler = new Handler(looper, this::handleMessage);
         mAnimatorFactory = animatorFactory;
         mAmbientController = new AmbientKeyboardBacklightController(context, looper);
@@ -141,6 +140,8 @@
         Resources res = mContext.getResources();
         mUserInactivityThresholdMs = res.getInteger(
                 com.android.internal.R.integer.config_keyboardBacklightTimeoutMs);
+        mKeyboardBacklightAnimationEnabled =
+                InputProperties.enable_keyboard_backlight_animation().orElse(false);
     }
 
     @Override
@@ -164,10 +165,8 @@
             }
         }, UEVENT_KEYBOARD_BACKLIGHT_TAG);
 
-        if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
-            // Start ambient backlight controller
-            mAmbientController.systemRunning();
-        }
+        // Start ambient backlight controller
+        mAmbientController.systemRunning();
     }
 
     @Override
@@ -229,9 +228,6 @@
         // level through keyboard up/down button
         updateAmbientLightListener();
 
-        maybeBackupBacklightBrightness(inputDevice, state.mLight,
-                state.mBrightnessValueForLevel[newBrightnessLevel]);
-
         if (DEBUG) {
             Slog.d(TAG,
                     "Changing state from " + state.mBrightnessLevel + " to " + newBrightnessLevel);
@@ -248,47 +244,6 @@
         }
     }
 
-    private void maybeBackupBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight,
-            int brightnessValue) {
-        // Don't back up or restore when ALS based keyboard backlight is enabled
-        if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
-            return;
-        }
-        synchronized (mDataStore) {
-            try {
-                mDataStore.setKeyboardBacklightBrightness(inputDevice.getDescriptor(),
-                        keyboardBacklight.getId(),
-                        brightnessValue);
-            } finally {
-                mDataStore.saveIfNeeded();
-            }
-        }
-    }
-
-    private void maybeRestoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) {
-        // Don't back up or restore when ALS based keyboard backlight is enabled
-        if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
-            return;
-        }
-        KeyboardBacklightState state = mKeyboardBacklights.get(inputDevice.getId());
-        OptionalInt brightness;
-        synchronized (mDataStore) {
-            brightness = mDataStore.getKeyboardBacklightBrightness(
-                    inputDevice.getDescriptor(), keyboardBacklight.getId());
-        }
-        if (state != null && brightness.isPresent()) {
-            int brightnessValue = Math.max(0, Math.min(MAX_BRIGHTNESS, brightness.getAsInt()));
-            int newLevel = Arrays.binarySearch(state.mBrightnessValueForLevel, brightnessValue);
-            if (newLevel < 0) {
-                newLevel = Math.min(state.getNumBrightnessChangeSteps(), -(newLevel + 1));
-            }
-            state.setBrightnessLevel(newLevel);
-            if (DEBUG) {
-                Slog.d(TAG, "Restoring brightness level " + brightness.getAsInt());
-            }
-        }
-    }
-
     private void handleUserActivity() {
         // Ignore user activity if device is not interactive. When device becomes interactive, we
         // will send another user activity to turn backlight on.
@@ -393,7 +348,6 @@
         }
         // The keyboard backlight was added or changed.
         mKeyboardBacklights.put(deviceId, new KeyboardBacklightState(deviceId, keyboardBacklight));
-        maybeRestoreBacklightBrightness(inputDevice, keyboardBacklight);
     }
 
     private InputDevice getInputDevice(int deviceId) {
@@ -472,9 +426,6 @@
     }
 
     private void updateAmbientLightListener() {
-        if (!InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
-            return;
-        }
         boolean needToListenAmbientLightSensor = false;
         for (int i = 0; i < mKeyboardBacklights.size(); i++) {
             needToListenAmbientLightSensor |= mKeyboardBacklights.valueAt(i).mUseAmbientController;
@@ -555,8 +506,7 @@
         private int mBrightnessLevel;
         private ValueAnimator mAnimator;
         private final int[] mBrightnessValueForLevel;
-        private boolean mUseAmbientController =
-                InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled();
+        private boolean mUseAmbientController = true;
 
         KeyboardBacklightState(int deviceId, Light light) {
             mDeviceId = deviceId;
@@ -565,9 +515,6 @@
         }
 
         private int[] setupBrightnessLevels() {
-            if (!InputFeatureFlagProvider.isKeyboardBacklightCustomLevelsEnabled()) {
-                return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL;
-            }
             int[] customLevels = mLight.getPreferredBrightnessLevels();
             if (customLevels == null || customLevels.length == 0) {
                 return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL;
@@ -627,7 +574,7 @@
             if (fromValue == toValue) {
                 return;
             }
-            if (InputFeatureFlagProvider.isKeyboardBacklightAnimationEnabled()) {
+            if (mKeyboardBacklightAnimationEnabled) {
                 startAnimation(fromValue, toValue);
             } else {
                 mNative.setLightColor(mDeviceId, mLight.getId(), Color.argb(toValue, 0, 0, 0));
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 728e440..935f0ff 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -147,6 +147,8 @@
 
     void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
 
+    void setTouchpadSystemGesturesEnabled(boolean enabled);
+
     void setShowTouches(boolean enabled);
 
     void setNonInteractiveDisplays(int[] displayIds);
@@ -437,6 +439,9 @@
         public native void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
 
         @Override
+        public native void setTouchpadSystemGesturesEnabled(boolean enabled);
+
+        @Override
         public native void setShowTouches(boolean enabled);
 
         @Override
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index d132449..da20891 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -19,13 +19,19 @@
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 
 import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.Region;
 import android.os.InputConfig;
 import android.os.Process;
+import android.util.Slog;
 import android.view.InputApplicationHandle;
 import android.view.InputChannel;
 import android.view.InputWindowHandle;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
+import android.view.WindowMetrics;
+import android.view.inputmethod.Flags;
 
 import com.android.server.input.InputManagerService;
 
@@ -39,8 +45,8 @@
     private final SurfaceControl mInputSurface;
     private boolean mIsIntercepting;
 
-    HandwritingEventReceiverSurface(String name, int displayId, @NonNull SurfaceControl sc,
-            @NonNull InputChannel inputChannel) {
+    HandwritingEventReceiverSurface(Context context, String name, int displayId,
+            @NonNull SurfaceControl sc, @NonNull InputChannel inputChannel) {
         mClientChannel = inputChannel;
         mInputSurface = sc;
 
@@ -59,15 +65,31 @@
                         | InputConfig.SPY
                         | InputConfig.INTERCEPTS_STYLUS;
 
-        // Configure the surface to receive stylus events across the entire display.
-        mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
+        Rect bounds = null;
+        if (Flags.adaptiveHandwritingBounds()) {
+            mWindowHandle.setTouchableRegionCrop(mInputSurface);
+            // Default touchable area to getMaximumWindowMetrics()
+            WindowMetrics windowMetrics =  context.getSystemService(WindowManager.class)
+                    .getMaximumWindowMetrics();
+            bounds = windowMetrics.getBounds();
+            if (DEBUG) Slog.d(TAG, "initial handwriting touchable bounds: " + bounds);
+            mWindowHandle.setTouchableRegion(windowMetrics.getBounds());
+        } else {
+            // Configure the surface to receive stylus events across the entire display.
+            mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
+        }
 
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
         t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_HANDWRITING_SURFACE);
         t.setPosition(mInputSurface, 0, 0);
-        t.setCrop(mInputSurface, null /* crop to parent surface */);
+        if (Flags.adaptiveHandwritingBounds()) {
+            // crop to parent surface if null, else bounds.
+            t.setCrop(mInputSurface, bounds);
+        } else {
+            t.setCrop(mInputSurface, null /* crop to parent surface */);
+        }
         t.show(mInputSurface);
         t.apply();
 
@@ -79,12 +101,23 @@
         mWindowHandle.ownerUid = imeUid;
         mWindowHandle.inputConfig &= ~InputConfig.SPY;
 
+        if (Flags.adaptiveHandwritingBounds()) {
+            // watch outside touch to finish handwriting.
+            mWindowHandle.inputConfig |= InputConfig.WATCH_OUTSIDE_TOUCH;
+        }
         new SurfaceControl.Transaction()
                 .setInputWindowInfo(mInputSurface, mWindowHandle)
                 .apply();
         mIsIntercepting = true;
     }
 
+    void setTouchableRegion(Region touchableRegion) {
+        mWindowHandle.touchableRegion.set(touchableRegion);
+        new SurfaceControl.Transaction()
+                .setInputWindowInfo(mInputSurface, mWindowHandle)
+                .apply();
+    }
+
     void setNotTouchable(boolean notTouchable) {
         if (notTouchable) {
             mWindowHandle.inputConfig |= InputConfig.NOT_TOUCHABLE;
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index c19cb03..45d7d04 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -27,6 +27,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
+import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerGlobal;
 import android.os.Handler;
@@ -139,7 +140,7 @@
         }
 
         mHandwritingSurface = new HandwritingEventReceiverSurface(
-                name, displayId, surface, channel);
+                mContext, name, displayId, surface, channel);
 
         // Use a dup of the input channel so that event processing can be paused by disposing the
         // event receiver without causing a fd hangup.
@@ -163,6 +164,13 @@
         mHandwritingSurface.setNotTouchable(notTouchable);
     }
 
+    void setHandwritingTouchableRegion(Region region) {
+        if (!getCurrentRequestId().isPresent()) {
+            return;
+        }
+        mHandwritingSurface.setTouchableRegion(region);
+    }
+
     boolean isStylusGestureOngoing() {
         if (mRecordingGestureAfterStylusUp && !mHandwritingBuffer.isEmpty()) {
             // If it is less than AFTER_STYLUS_UP_ALLOW_PERIOD_MS after the stylus up event, return
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index b7af9a4..02dd884 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -84,6 +84,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
+import android.graphics.Region;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputManager;
 import android.inputmethodservice.InputMethodService;
@@ -6888,6 +6889,14 @@
 
         @BinderThread
         @Override
+        public void setHandwritingTouchableRegion(Region region) {
+            synchronized (ImfLock.class) {
+                mImms.mHwController.setHandwritingTouchableRegion(region);
+            }
+        }
+
+        @BinderThread
+        @Override
         public void createInputContentUriToken(Uri contentUri, String packageName,
                 AndroidFuture future /* T=IBinder */) {
             @SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future;
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index a132876b..0914b7e 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -93,29 +93,6 @@
         mContext = context;
         mPackageManagerInternal = packageManagerInternal;
         mHandler = handler;
-
-        IntentFilter integrityVerificationFilter = new IntentFilter();
-        integrityVerificationFilter.addAction(ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
-        try {
-            integrityVerificationFilter.addDataType(PACKAGE_MIME_TYPE);
-        } catch (IntentFilter.MalformedMimeTypeException e) {
-            throw new RuntimeException("Mime type malformed: should never happen.", e);
-        }
-
-        mContext.registerReceiver(
-                new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        if (!ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION.equals(
-                                intent.getAction())) {
-                            return;
-                        }
-                        mHandler.post(() -> handleIntegrityVerification(intent));
-                    }
-                },
-                integrityVerificationFilter,
-                /* broadcastPermission= */ null,
-                mHandler);
     }
 
     @Override
@@ -157,10 +134,4 @@
     public List<String> getWhitelistedRuleProviders() {
         return Collections.emptyList();
     }
-
-    private void handleIntegrityVerification(Intent intent) {
-        int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
-        mPackageManagerInternal.setIntegrityVerificationResult(
-                verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
-    }
 }
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 2e167ef..6053557 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -108,6 +108,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.location.eventlog.LocationEventLog;
+import com.android.server.location.fudger.LocationFudgerCache;
 import com.android.server.location.geofence.GeofenceManager;
 import com.android.server.location.geofence.GeofenceProxy;
 import com.android.server.location.gnss.GnssConfiguration;
@@ -147,6 +148,7 @@
 import com.android.server.location.provider.StationaryThrottlingLocationProvider;
 import com.android.server.location.provider.proxy.ProxyGeocodeProvider;
 import com.android.server.location.provider.proxy.ProxyLocationProvider;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
 import com.android.server.location.settings.LocationSettings;
 import com.android.server.location.settings.LocationUserSettings;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
@@ -260,6 +262,11 @@
     private volatile @Nullable GnssManagerService mGnssManagerService = null;
     private ProxyGeocodeProvider mGeocodeProvider;
 
+    private @Nullable ProxyPopulationDensityProvider mPopulationDensityProvider = null;
+
+    // A cache for population density lookups. Used if density-based coarse locations are enabled.
+    private @Nullable LocationFudgerCache mLocationFudgerCache = null;
+
     private final Object mDeprecatedGnssBatchingLock = new Object();
     @GuardedBy("mDeprecatedGnssBatchingLock")
     private @Nullable ILocationListener mDeprecatedGnssBatchingListener;
@@ -392,6 +399,25 @@
         }
     }
 
+    @VisibleForTesting
+    protected void setProxyPopulationDensityProvider(ProxyPopulationDensityProvider provider) {
+        if (Flags.populationDensityProvider()) {
+            mPopulationDensityProvider = provider;
+        }
+    }
+
+    @VisibleForTesting
+    protected void setLocationFudgerCache(LocationFudgerCache cache) {
+        if (!Flags.densityBasedCoarseLocations()) {
+            return;
+        }
+
+        mLocationFudgerCache = cache;
+        for (LocationProviderManager manager : mProviderManagers) {
+            manager.setLocationFudgerCache(cache);
+        }
+    }
+
     private void removeLocationProviderManager(LocationProviderManager manager) {
         synchronized (mProviderManagers) {
             boolean removed = mProviderManagers.remove(manager);
@@ -510,6 +536,17 @@
             Log.e(TAG, "no geocoder provider found");
         }
 
+        if (Flags.populationDensityProvider()) {
+            setProxyPopulationDensityProvider(
+                    ProxyPopulationDensityProvider.createAndRegister(mContext));
+            if (mPopulationDensityProvider == null) {
+                Log.e(TAG, "no population density provider found");
+            }
+        }
+        if (mPopulationDensityProvider != null && Flags.densityBasedCoarseLocations()) {
+            setLocationFudgerCache(new LocationFudgerCache(mPopulationDensityProvider));
+        }
+
         // bind to hardware activity recognition
         HardwareActivityRecognitionProxy hardwareActivityRecognitionProxy =
                 HardwareActivityRecognitionProxy.createAndRegister(mContext);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
new file mode 100644
index 0000000..53389ca
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -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.server.location.contexthub;
+
+import android.content.Context;
+import android.hardware.contexthub.EndpointInfo;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.HubMessage;
+import android.hardware.contexthub.HubServiceInfo;
+import android.hardware.contexthub.IContextHubEndpoint;
+import android.hardware.contexthub.IContextHubEndpointCallback;
+import android.hardware.location.IContextHubTransactionCallback;
+
+/**
+ * A class that represents a broker for the endpoint registered by the client app. This class
+ * manages direct IContextHubEndpoint/IContextHubEndpointCallback API/callback calls.
+ *
+ * @hide
+ */
+public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub {
+    private static final String TAG = "ContextHubEndpointBroker";
+
+    /** The context of the service. */
+    private final Context mContext;
+
+    /** The proxy to talk to the Context Hub HAL. */
+    private final IContextHubWrapper mContextHubProxy;
+
+    /** The manager that registered this endpoint. */
+    private final ContextHubEndpointManager mEndpointManager;
+
+    /** Metadata about this endpoint (app-facing container). */
+    private final HubEndpointInfo mEndpointInfo;
+
+    /** Metadata about this endpoint (HAL-facing container). */
+    private final EndpointInfo mHalEndpointInfo;
+
+    /** The remote callback interface for this endpoint. */
+    private final IContextHubEndpointCallback mContextHubEndpointCallback;
+
+    /* package */ ContextHubEndpointBroker(
+            Context context,
+            IContextHubWrapper contextHubProxy,
+            ContextHubEndpointManager endpointManager,
+            EndpointInfo halEndpointInfo,
+            IContextHubEndpointCallback callback) {
+        mContext = context;
+        mContextHubProxy = contextHubProxy;
+        mEndpointManager = endpointManager;
+        mEndpointInfo = new HubEndpointInfo(halEndpointInfo);
+        mHalEndpointInfo = halEndpointInfo;
+        mContextHubEndpointCallback = callback;
+    }
+
+    @Override
+    public HubEndpointInfo getAssignedHubEndpointInfo() {
+        return mEndpointInfo;
+    }
+
+    @Override
+    public int openSession(HubEndpointInfo destination, HubServiceInfo serviceInfo) {
+        // TODO(b/378487799): Implement this
+        return 0;
+    }
+
+    @Override
+    public void closeSession(int sessionId, int reason) {
+        // TODO(b/378487799): Implement this
+
+    }
+
+    @Override
+    public void openSessionRequestComplete(int sessionId) {
+        // TODO(b/378487799): Implement this
+
+    }
+
+    @Override
+    public void unregister() {
+        // TODO(b/378487799): Implement this
+
+    }
+
+    @Override
+    public void sendMessage(
+            int sessionId, HubMessage message, IContextHubTransactionCallback callback) {
+        // TODO(b/381102453): Implement this
+    }
+
+    @Override
+    public void sendMessageDeliveryStatus(int sessionId, int messageSeqNumber, byte errorCode) {
+        // TODO(b/381102453): Implement this
+    }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
new file mode 100644
index 0000000..54ce74f
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java
@@ -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.server.location.contexthub;
+
+import android.content.Context;
+import android.hardware.contexthub.EndpointInfo;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.IContextHubEndpoint;
+import android.hardware.contexthub.IContextHubEndpointCallback;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A class that manages registration/unregistration of clients and manages messages to/from clients.
+ *
+ * @hide
+ */
+/* package */ class ContextHubEndpointManager {
+    private static final String TAG = "ContextHubEndpointManager";
+
+    /** The hub ID of the Context Hub Service. */
+    private static final long SERVICE_HUB_ID = 0x416e64726f696400L;
+
+    /** The context of the service. */
+    private final Context mContext;
+
+    /** The proxy to talk to the Context Hub. */
+    private final IContextHubWrapper mContextHubProxy;
+
+    /** A map of endpoint IDs to brokers currently registered. */
+    private final Map<Long, ContextHubEndpointBroker> mEndpointMap = new ConcurrentHashMap<>();
+
+    /** Variables for managing endpoint ID creation */
+    private final Object mEndpointLock = new Object();
+
+    @GuardedBy("mEndpointLock")
+    private long mNextEndpointId = 0;
+
+    /* package */ ContextHubEndpointManager(Context context, IContextHubWrapper contextHubProxy) {
+        mContext = context;
+        mContextHubProxy = contextHubProxy;
+    }
+
+    /**
+     * Registers a new endpoint with the service.
+     *
+     * @param pendingEndpointInfo the object describing the endpoint being registered
+     * @param callback the callback interface of the endpoint to register
+     * @return the endpoint interface
+     * @throws IllegalStateException if max number of endpoints have already registered
+     */
+    /* package */ IContextHubEndpoint registerEndpoint(
+            HubEndpointInfo pendingEndpointInfo, IContextHubEndpointCallback callback)
+            throws RemoteException {
+        ContextHubEndpointBroker broker;
+        long endpointId = getNewEndpointId();
+        EndpointInfo halEndpointInfo =
+                ContextHubServiceUtil.createHalEndpointInfo(
+                        pendingEndpointInfo, endpointId, SERVICE_HUB_ID);
+        try {
+            mContextHubProxy.registerEndpoint(halEndpointInfo);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException while calling HAL registerEndpoint", e);
+            throw e;
+        }
+        broker =
+                new ContextHubEndpointBroker(
+                        mContext,
+                        mContextHubProxy,
+                        this /* endpointManager */,
+                        halEndpointInfo,
+                        callback);
+        mEndpointMap.put(endpointId, broker);
+
+        // TODO(b/378487799): Add death recipient
+
+        Log.d(TAG, "Registered endpoint with ID = " + endpointId);
+        return IContextHubEndpoint.Stub.asInterface(broker);
+    }
+
+    /**
+     * @return an available endpoint ID
+     */
+    private long getNewEndpointId() {
+        synchronized (mEndpointLock) {
+            if (mNextEndpointId == Long.MAX_VALUE) {
+                throw new IllegalStateException("Too many endpoints connected");
+            }
+            return mNextEndpointId++;
+        }
+    }
+}
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 76049ca..0d926b9 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -34,6 +34,7 @@
 import android.hardware.contexthub.HubEndpointInfo;
 import android.hardware.contexthub.IContextHubEndpoint;
 import android.hardware.contexthub.IContextHubEndpointCallback;
+import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
 import android.hardware.contexthub.MessageDeliveryStatus;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubMessage;
@@ -154,6 +155,9 @@
     // The manager for sending messages to/from clients
     private ContextHubClientManager mClientManager;
 
+    // Manages endpoint and its sessions between apps and HAL
+    private ContextHubEndpointManager mEndpointManager;
+
     // The default client for old API clients
     private Map<Integer, IContextHubClient> mDefaultClientMap;
 
@@ -329,14 +333,17 @@
             HubInfoRegistry registry;
             try {
                 registry = new HubInfoRegistry(mContextHubWrapper);
+                mEndpointManager = new ContextHubEndpointManager(mContext, mContextHubWrapper);
                 Log.i(TAG, "Enabling generic offload API");
             } catch (UnsupportedOperationException e) {
+                mEndpointManager = null;
                 registry = null;
                 Log.w(TAG, "Generic offload API not supported, disabling");
             }
             mHubInfoRegistry = registry;
         } else {
             mHubInfoRegistry = null;
+            mEndpointManager = null;
             Log.i(TAG, "Disabling generic offload API");
         }
 
@@ -789,8 +796,36 @@
             HubEndpointInfo pendingHubEndpointInfo, IContextHubEndpointCallback callback)
             throws RemoteException {
         super.registerEndpoint_enforcePermission();
+        if (mEndpointManager == null) {
+            Log.e(TAG, "ContextHubService.registerEndpoint: endpoint manager failed to initialize");
+            throw new UnsupportedOperationException("Endpoint registration is not supported");
+        }
+        return mEndpointManager.registerEndpoint(pendingHubEndpointInfo, callback);
+    }
+
+    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @Override
+    public void registerEndpointDiscoveryCallbackId(
+            long endpointId, IContextHubEndpointDiscoveryCallback callback) throws RemoteException {
+        super.registerEndpointDiscoveryCallbackId_enforcePermission();
         // TODO(b/375487784): Implement this
-        return null;
+    }
+
+    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @Override
+    public void registerEndpointDiscoveryCallbackDescriptor(
+            String serviceDescriptor, IContextHubEndpointDiscoveryCallback callback)
+            throws RemoteException {
+        super.registerEndpointDiscoveryCallbackDescriptor_enforcePermission();
+        // TODO(b/375487784): Implement this
+    }
+
+    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @Override
+    public void unregisterEndpointDiscoveryCallback(IContextHubEndpointDiscoveryCallback callback)
+            throws RemoteException {
+        super.unregisterEndpointDiscoveryCallback_enforcePermission();
+        // TODO(b/375487784): Implement this
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index 33d2ff0..05be427 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -18,6 +18,9 @@
 
 import android.Manifest;
 import android.content.Context;
+import android.hardware.contexthub.EndpointInfo;
+import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.HubServiceInfo;
 import android.hardware.contexthub.V1_0.AsyncEventType;
 import android.hardware.contexthub.V1_0.ContextHubMsg;
 import android.hardware.contexthub.V1_0.HostEndPoint;
@@ -415,4 +418,51 @@
     static String formatDateFromTimestamp(long timeStampInMs) {
         return DATE_FORMATTER.format(Instant.ofEpochMilli(timeStampInMs));
     }
+
+    /**
+     * Converts a context hub HAL EndpointInfo object based on the provided HubEndpointInfo.
+     *
+     * @param info the HubEndpointInfo object
+     * @return the equivalent EndpointInfo object
+     */
+    /* package */
+    static EndpointInfo convertHalEndpointInfo(HubEndpointInfo info) {
+        return createHalEndpointInfo(
+                info, info.getIdentifier().getEndpoint(), info.getIdentifier().getHub());
+    }
+
+    /**
+     * Creates a context hub HAL EndpointInfo object based on the provided HubEndpointInfo. As
+     * opposed to convertHalEndpointInfo, this method can be used to overwrite/specify the endpoint
+     * and hub ID.
+     *
+     * @param info the HubEndpointInfo object
+     * @param endpointId the endpoint ID of this object
+     * @param hubId the hub ID of this object
+     * @return the equivalent EndpointInfo object
+     */
+    /* package */
+    static EndpointInfo createHalEndpointInfo(HubEndpointInfo info, long endpointId, long hubId) {
+        EndpointInfo outputInfo = new EndpointInfo();
+        outputInfo.id = new android.hardware.contexthub.EndpointId();
+        outputInfo.id.id = endpointId;
+        outputInfo.id.hubId = hubId;
+        outputInfo.name = info.getName();
+        outputInfo.version = info.getVersion();
+        outputInfo.tag = info.getTag();
+        Collection<String> permissions = info.getRequiredPermissions();
+        outputInfo.requiredPermissions = permissions.toArray(new String[permissions.size()]);
+        Collection<HubServiceInfo> services = info.getServiceInfoCollection();
+        outputInfo.services = new android.hardware.contexthub.Service[services.size()];
+        int i = 0;
+        for (HubServiceInfo service : services) {
+            outputInfo.services[i] = new android.hardware.contexthub.Service();
+            outputInfo.services[i].format = service.getFormat();
+            outputInfo.services[i].serviceDescriptor = service.getServiceDescriptor();
+            outputInfo.services[i].majorVersion = service.getMajorVersion();
+            outputInfo.services[i].minorVersion = service.getMinorVersion();
+            i++;
+        }
+        return outputInfo;
+    }
 }
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 da31bf2..ccfa61b 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -492,14 +492,21 @@
     /* package */
     void onTransactionResponse(int transactionId, boolean success) {
         TransactionAcceptConditions conditions =
-                transaction -> transaction.getTransactionId() == transactionId;
+                transaction -> {
+                    if (transaction.getTransactionId() != transactionId) {
+                        Log.w(
+                                TAG,
+                                "Unexpected transaction: expected "
+                                        + transactionId
+                                        + ", received "
+                                        + transaction.getTransactionId());
+                        return false;
+                    }
+                    return true;
+                };
         ContextHubServiceTransaction transaction = getTransactionAndHandleNext(conditions);
         if (transaction == null) {
-            Log.w(TAG, "Received unexpected transaction response (expected ID = "
-                    + transactionId
-                    + ", received ID = "
-                    + transaction.getTransactionId()
-                    + ")");
+            Log.w(TAG, "Received unexpected transaction response");
             return;
         }
 
@@ -581,7 +588,7 @@
                 transaction.getTransactionType() == ContextHubTransaction.TYPE_QUERY_NANOAPPS;
         ContextHubServiceTransaction transaction = getTransactionAndHandleNext(conditions);
         if (transaction == null) {
-            Log.w(TAG, "Received unexpected query response (expected " + transaction + ")");
+            Log.w(TAG, "Received unexpected query response");
             return;
         }
 
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 9b729eb..14d75b0 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -239,6 +239,10 @@
     public void registerEndpointCallback(android.hardware.contexthub.IEndpointCallback cb)
             throws RemoteException {}
 
+    /** Registers the endpoint with the ContextHub HAL */
+    public void registerEndpoint(android.hardware.contexthub.EndpointInfo info)
+            throws RemoteException {}
+
     /**
      * @return True if this version of the Contexthub HAL supports Location setting notifications.
      */
@@ -671,6 +675,16 @@
             hub.registerEndpointCallback(cb);
         }
 
+        @Override
+        public void registerEndpoint(android.hardware.contexthub.EndpointInfo info)
+                throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+            hub.registerEndpoint(info);
+        }
+
         public boolean supportsLocationSettingNotifications() {
             return true;
         }
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudger.java b/services/core/java/com/android/server/location/fudger/LocationFudger.java
index 88a2697..0da1514 100644
--- a/services/core/java/com/android/server/location/fudger/LocationFudger.java
+++ b/services/core/java/com/android/server/location/fudger/LocationFudger.java
@@ -16,13 +16,16 @@
 
 package com.android.server.location.fudger;
 
+import android.annotation.FlaggedApi;
 import android.annotation.Nullable;
 import android.location.Location;
 import android.location.LocationResult;
+import android.location.flags.Flags;
 import android.os.SystemClock;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.location.geometry.S2CellIdUtils;
 
 import java.security.SecureRandom;
 import java.time.Clock;
@@ -83,6 +86,9 @@
     @GuardedBy("this")
     @Nullable private LocationResult mCachedCoarseLocationResult;
 
+    @GuardedBy("this")
+    @Nullable private LocationFudgerCache mLocationFudgerCache = null;
+
     public LocationFudger(float accuracyM) {
         this(accuracyM, SystemClock.elapsedRealtimeClock(), new SecureRandom());
     }
@@ -97,6 +103,16 @@
     }
 
     /**
+     * Provides the optional {@link LocationFudgerCache} for coarsening based on population density.
+     */
+    @FlaggedApi(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+    public void setLocationFudgerCache(LocationFudgerCache cache) {
+        synchronized (this) {
+            mLocationFudgerCache = cache;
+        }
+    }
+
+    /**
      * Resets the random offsets completely.
      */
     public void resetOffsets() {
@@ -162,16 +178,34 @@
         longitude += wrapLongitude(metersToDegreesLongitude(mLongitudeOffsetM, latitude));
         latitude += wrapLatitude(metersToDegreesLatitude(mLatitudeOffsetM));
 
-        // quantize location by snapping to a grid. this is the primary means of obfuscation. it
-        // gives nice consistent results and is very effective at hiding the true location (as long
-        // as you are not sitting on a grid boundary, which the random offsets mitigate).
-        //
-        // note that we quantize the latitude first, since the longitude quantization depends on the
-        // latitude value and so leaks information about the latitude
-        double latGranularity = metersToDegreesLatitude(mAccuracyM);
-        latitude = wrapLatitude(Math.round(latitude / latGranularity) * latGranularity);
-        double lonGranularity = metersToDegreesLongitude(mAccuracyM, latitude);
-        longitude = wrapLongitude(Math.round(longitude / lonGranularity) * lonGranularity);
+        // We copy a reference to the cache, so even if mLocationFudgerCache is concurrently set
+        // to null, we can continue executing the condition below.
+        LocationFudgerCache cacheCopy = null;
+        synchronized (this) {
+            cacheCopy = mLocationFudgerCache;
+        }
+
+        // TODO(b/381204398): To ensure a safe rollout, two algorithms co-exist. The first is the
+        // new density-based algorithm, while the second is the traditional coarsening algorithm.
+        // Once rollout is done, clean up the unused algorithm.
+        if (Flags.densityBasedCoarseLocations() && cacheCopy != null
+                && cacheCopy.hasDefaultValue()) {
+            int level = cacheCopy.getCoarseningLevel(latitude, longitude);
+            double[] center = snapToCenterOfS2Cell(latitude, longitude, level);
+            latitude = center[S2CellIdUtils.LAT_INDEX];
+            longitude = center[S2CellIdUtils.LNG_INDEX];
+        } else {
+            // quantize location by snapping to a grid. this is the primary means of obfuscation. it
+            // gives nice consistent results and is very effective at hiding the true location (as
+            // long as you are not sitting on a grid boundary, which the random offsets mitigate).
+            //
+            // note that we quantize the latitude first, since the longitude quantization depends on
+            // the latitude value and so leaks information about the latitude
+            double latGranularity = metersToDegreesLatitude(mAccuracyM);
+            latitude = wrapLatitude(Math.round(latitude / latGranularity) * latGranularity);
+            double lonGranularity = metersToDegreesLongitude(mAccuracyM, latitude);
+            longitude = wrapLongitude(Math.round(longitude / lonGranularity) * lonGranularity);
+        }
 
         coarse.setLatitude(latitude);
         coarse.setLongitude(longitude);
@@ -185,6 +219,15 @@
         return coarse;
     }
 
+    @VisibleForTesting
+    protected double[] snapToCenterOfS2Cell(double latDegrees, double lngDegrees, int level) {
+        long leafCell = S2CellIdUtils.fromLatLngDegrees(latDegrees, lngDegrees);
+        long coarsenedCell = S2CellIdUtils.getParent(leafCell, level);
+        double[] center = new double[] {0.0, 0.0};
+        S2CellIdUtils.toLatLngDegrees(coarsenedCell, center);
+        return center;
+    }
+
     /**
      * Update the random offsets over time.
      *
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java b/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java
new file mode 100644
index 0000000..ce8bec8
--- /dev/null
+++ b/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java
@@ -0,0 +1,200 @@
+/*
+ * 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.location.fudger;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.location.flags.Flags;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.geometry.S2CellIdUtils;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
+
+import java.util.Objects;
+
+/**
+ * A cache for returning the coarsening level to be used. The coarsening level depends on the user
+ * location. If the cache contains the requested latitude/longitude, the s2 level of the cached
+ * cell id is returned. If not, a default value is returned.
+ * This class has a {@link ProxyPopulationDensityProvider} used to refresh the cache.
+ * This cache exists because {@link ProxyPopulationDensityProvider} must be queried asynchronously,
+ * whereas a synchronous answer is needed.
+ * The cache is first-in, first-out, and has a fixed size. Cache entries are valid until evicted by
+ * another value.
+ */
+@FlaggedApi(Flags.FLAG_POPULATION_DENSITY_PROVIDER)
+public class LocationFudgerCache {
+
+    // The maximum number of S2 cell ids stored in the cache.
+    // Each cell id is a long, so the memory requirement is 8*MAX_CACHE_SIZE bytes.
+    protected static final int MAX_CACHE_SIZE = 20;
+
+    private final Object mLock = new Object();
+
+    // mCache is a circular buffer of size MAX_CACHE_SIZE. The next position to be written to is
+    // mPosInCache. Initially, the cache is filled with INVALID_CELL_IDs.
+    @GuardedBy("mLock")
+    private final long[] mCache = new long[MAX_CACHE_SIZE];
+
+    @GuardedBy("mLock")
+    private int mPosInCache = 0;
+
+    @GuardedBy("mLock")
+    private int mCacheSize = 0;
+
+    // The S2 level to coarsen to, if the cache doesn't contain a better answer.
+    // Updated concurrently by callbacks.
+    @GuardedBy("mLock")
+    private Integer mDefaultCoarseningLevel = null;
+
+    // The provider that asynchronously provides what is stored in the cache.
+    private final ProxyPopulationDensityProvider mPopulationDensityProvider;
+
+    private static String sTAG = "LocationFudgerCache";
+
+    public LocationFudgerCache(@NonNull ProxyPopulationDensityProvider provider) {
+        mPopulationDensityProvider = Objects.requireNonNull(provider);
+
+        asyncFetchDefaultCoarseningLevel();
+    }
+
+    /** Returns true if the cache has successfully received a default value from the provider. */
+    public boolean hasDefaultValue() {
+        synchronized (mLock) {
+            return (mDefaultCoarseningLevel != null);
+        }
+    }
+
+    /**
+     * Returns the S2 level to which the provided location should be coarsened.
+     * The answer comes from the cache if available, otherwise the default value is returned.
+     */
+    public int getCoarseningLevel(double latitudeDegrees, double longitudeDegrees) {
+        // If we still haven't received the default level from the provider, try fetching it again.
+        // The answer wouldn't come in time, but it will be used for the following queries.
+        if (!hasDefaultValue()) {
+            asyncFetchDefaultCoarseningLevel();
+        }
+        Long s2CellId = readCacheForLatLng(latitudeDegrees, longitudeDegrees);
+        if (s2CellId == null) {
+            // Asynchronously queries the density from the provider. The answer won't come in time,
+            // but it will update the cache for the following queries.
+            refreshCache(latitudeDegrees, longitudeDegrees);
+
+            return getDefaultCoarseningLevel();
+        }
+        return S2CellIdUtils.getLevel(s2CellId);
+    }
+
+    /**
+     * If the cache contains the current location, returns the corresponding S2 cell id.
+     * Otherwise, returns null.
+     */
+    @Nullable
+    private Long readCacheForLatLng(double latDegrees, double lngDegrees) {
+        synchronized (mLock) {
+            for (int i = 0; i < mCacheSize; i++) {
+                if (S2CellIdUtils.containsLatLngDegrees(mCache[i], latDegrees, lngDegrees)) {
+                    return mCache[i];
+                }
+            }
+        }
+        return null;
+    }
+
+    /** Adds the provided s2 cell id to the cache. This might evict other values from the cache. */
+    public void addToCache(long s2CellId) {
+        addToCache(new long[] {s2CellId});
+    }
+
+    /**
+     * Adds the provided s2 cell ids to the cache. This might evict other values from the cache.
+     * If more than MAX_CACHE_SIZE elements are provided, only the first elements are copied.
+     * The first element of the input is added last into the FIFO cache, so it gets evicted last.
+     */
+    public void addToCache(long[] s2CellIds) {
+        synchronized (mLock) {
+            // Only copy up to MAX_CACHE_SIZE elements
+            int end = Math.min(s2CellIds.length, MAX_CACHE_SIZE);
+            mCacheSize = Math.min(mCacheSize + end, MAX_CACHE_SIZE);
+
+            // Add in reverse so the first cell of s2CellIds is the last evicted
+            for (int i = end - 1; i >= 0; i--) {
+                mCache[mPosInCache] = s2CellIds[i];
+                mPosInCache = (mPosInCache + 1) % MAX_CACHE_SIZE;
+            }
+        }
+    }
+
+    /**
+     * Queries the population density provider for the default coarsening level (to be used if the
+     * cache doesn't contain a better answer), and updates mDefaultCoarseningLevel with the answer.
+     */
+    private void asyncFetchDefaultCoarseningLevel() {
+        IS2LevelCallback callback = new IS2LevelCallback.Stub() {
+            @Override
+            public void onResult(int s2level) {
+                synchronized (mLock) {
+                    mDefaultCoarseningLevel = Integer.valueOf(s2level);
+                }
+            }
+
+            @Override
+            public void onError() {
+                Log.e(sTAG, "could not get default population density");
+            }
+        };
+        mPopulationDensityProvider.getDefaultCoarseningLevel(callback);
+    }
+
+    /**
+     *  Queries the population density provider and store the result in the cache.
+     */
+    private void refreshCache(double latitude, double longitude) {
+        IS2CellIdsCallback callback = new IS2CellIdsCallback.Stub() {
+            @Override
+            public void onResult(long[] s2CellIds) {
+                addToCache(s2CellIds);
+            }
+
+            @Override
+            public void onError() {
+                Log.e(sTAG, "could not get population density");
+            }
+        };
+        mPopulationDensityProvider.getCoarsenedS2Cells(latitude, longitude, MAX_CACHE_SIZE - 1,
+                callback);
+    }
+
+    /**
+     * Returns the default S2 level to coarsen to. This should be used if the cache
+     * does not provide a better answer.
+     */
+    private int getDefaultCoarseningLevel() {
+        synchronized (mLock) {
+            // The minimum valid level is 0.
+            if (mDefaultCoarseningLevel == null) {
+                return 0;
+            }
+            return mDefaultCoarseningLevel;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 4a9bf88..a8c9010 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -48,6 +48,7 @@
 import static java.lang.Math.max;
 import static java.lang.Math.min;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -105,6 +106,7 @@
 import com.android.server.location.LocationPermissions;
 import com.android.server.location.LocationPermissions.PermissionLevel;
 import com.android.server.location.fudger.LocationFudger;
+import com.android.server.location.fudger.LocationFudgerCache;
 import com.android.server.location.injector.AlarmHelper;
 import com.android.server.location.injector.AppForegroundHelper;
 import com.android.server.location.injector.AppForegroundHelper.AppForegroundListener;
@@ -1663,6 +1665,18 @@
     }
 
     /**
+     * Provides the optional {@link LocationFudgerCache} for coarsening based on population density.
+     */
+    @FlaggedApi(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+    public void setLocationFudgerCache(LocationFudgerCache cache) {
+        if (!Flags.densityBasedCoarseLocations()) {
+            return;
+        }
+
+        mLocationFudger.setLocationFudgerCache(cache);
+    }
+
+    /**
      * Returns true if this provider is visible to the current caller (whether called from a binder
      * thread or not). If a provider isn't visible, then all APIs return the same data they would if
      * the provider didn't exist (i.e. the caller can't see or use the provider).
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java
new file mode 100644
index 0000000..7b454e4
--- /dev/null
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java
@@ -0,0 +1,120 @@
+/*
+ * 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.location.provider.proxy;
+
+import static android.location.provider.PopulationDensityProviderBase.ACTION_POPULATION_DENSITY_PROVIDER;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.location.provider.IPopulationDensityProvider;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.ServiceWatcher;
+
+/**
+ * Proxy for IPopulationDensityProvider implementations.
+ */
+public class ProxyPopulationDensityProvider {
+
+    public static final String TAG = "ProxyPopulationDensityProvider";
+
+    final ServiceWatcher mServiceWatcher;
+
+    /**
+     * Creates and registers this proxy. If no suitable service is available for the proxy, returns
+     * null.
+     */
+    @Nullable
+    public static ProxyPopulationDensityProvider createAndRegister(Context context) {
+        ProxyPopulationDensityProvider proxy = new ProxyPopulationDensityProvider(context);
+        if (proxy.register()) {
+            return proxy;
+        } else {
+            return null;
+        }
+    }
+
+    private ProxyPopulationDensityProvider(Context context) {
+        mServiceWatcher = ServiceWatcher.create(
+                context,
+                "PopulationDensityProxy",
+                CurrentUserServiceSupplier.createFromConfig(
+                        context,
+                        ACTION_POPULATION_DENSITY_PROVIDER,
+                        com.android.internal.R.bool.config_enablePopulationDensityProviderOverlay,
+                        com.android.internal.R.string.config_populationDensityProviderPackageName),
+                null);
+    }
+
+    private boolean register() {
+        boolean resolves = mServiceWatcher.checkServiceResolves();
+        if (resolves) {
+            mServiceWatcher.register();
+        }
+        return resolves;
+    }
+
+    /** Gets the default coarsening level. */
+    public void getDefaultCoarseningLevel(IS2LevelCallback callback) {
+        mServiceWatcher.runOnBinder(
+                new ServiceWatcher.BinderOperation() {
+                    @Override
+                    public void run(IBinder binder) throws RemoteException {
+                        IPopulationDensityProvider.Stub.asInterface(binder)
+                              .getDefaultCoarseningLevel(callback);
+                    }
+
+                    @Override
+                    public void onError(Throwable t) {
+                        try {
+                            callback.onError();
+                        } catch (RemoteException e) {
+                            Log.w(TAG, "remote exception while querying default coarsening level");
+                        }
+                    }
+                });
+    }
+
+
+    /** Gets the population density at the requested location. */
+    public void getCoarsenedS2Cells(double latitudeDegrees, double longitudeDegrees,
+              int numAdditionalCells, IS2CellIdsCallback callback) {
+        mServiceWatcher.runOnBinder(
+                new ServiceWatcher.BinderOperation() {
+                    @Override
+                    public void run(IBinder binder) throws RemoteException {
+                        IPopulationDensityProvider.Stub.asInterface(binder)
+                                .getCoarsenedS2Cells(latitudeDegrees, longitudeDegrees,
+                                      numAdditionalCells, callback);
+                    }
+
+                    @Override
+                    public void onError(Throwable t) {
+                        try {
+                            callback.onError();
+                        } catch (RemoteException e) {
+                            Log.w(TAG, "remote exception while querying coarsened S2 cell");
+                        }
+                    }
+                });
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 27bc1cf..ab68ed3 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -246,7 +246,7 @@
                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
                 if (hasSystemRoutingPermissions) {
                     MediaRoute2ProviderInfo providerInfo =
-                            userRecord.mHandler.mSystemProvider.getProviderInfo();
+                            userRecord.mHandler.getSystemProvider().getProviderInfo();
                     if (providerInfo != null) {
                         systemRoutes = providerInfo.getRoutes();
                     } else {
@@ -258,7 +258,8 @@
                     }
                 } else {
                     systemRoutes = new ArrayList<>();
-                    systemRoutes.add(userRecord.mHandler.mSystemProvider.getDefaultRoute());
+                    systemRoutes.add(
+                            userRecord.mHandler.getSystemProvider().getDefaultRoute());
                 }
             }
             return new ArrayList<>(systemRoutes);
@@ -850,10 +851,10 @@
                     if (setDeviceRouteSelected) {
                         // Return a fake system session that shows the device route as selected and
                         // available bluetooth routes as transferable.
-                        return userRecord.mHandler.mSystemProvider
+                        return userRecord.mHandler.getSystemProvider()
                                 .generateDeviceRouteSelectedSessionInfo(targetPackageName);
                     } else {
-                        sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
+                        sessionInfos = userRecord.mHandler.getSystemProvider().getSessionInfos();
                         if (!sessionInfos.isEmpty()) {
                             // Return a copy of the current system session with no modification,
                             // except setting the client package name.
@@ -866,7 +867,7 @@
                     }
                 } else {
                     return new RoutingSessionInfo.Builder(
-                                    userRecord.mHandler.mSystemProvider.getDefaultSessionInfo())
+                                    userRecord.mHandler.getSystemProvider().getDefaultSessionInfo())
                             .setClientPackageName(targetPackageName)
                             .build();
                 }
@@ -1136,6 +1137,7 @@
         UserRecord userRecord = getOrCreateUserRecordLocked(userId);
         RouterRecord routerRecord =
                 new RouterRecord(
+                        mContext,
                         userRecord,
                         router,
                         uid,
@@ -1374,7 +1376,7 @@
             }
             manager.mLastSessionCreationRequest = null;
         } else {
-            String defaultRouteId = userHandler.mSystemProvider.getDefaultRoute().getId();
+            String defaultRouteId = userHandler.getSystemProvider().getDefaultRoute().getId();
             if (route.isSystemRoute()
                     && !routerRecord.hasSystemRoutingPermission()
                     && !TextUtils.equals(route.getId(), defaultRouteId)) {
@@ -1462,7 +1464,7 @@
                         routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
 
         UserHandler userHandler = routerRecord.mUserRecord.mHandler;
-        String defaultRouteId = userHandler.mSystemProvider.getDefaultRoute().getId();
+        String defaultRouteId = userHandler.getSystemProvider().getDefaultRoute().getId();
         if (route.isSystemRoute()
                 && !routerRecord.hasSystemRoutingPermission()
                 && !TextUtils.equals(route.getId(), defaultRouteId)) {
@@ -2055,6 +2057,7 @@
     }
 
     final class RouterRecord implements IBinder.DeathRecipient {
+        public final Context mContext;
         public final UserRecord mUserRecord;
         public final String mPackageName;
         public final List<Integer> mSelectRouteSequenceNumbers;
@@ -2073,6 +2076,7 @@
         @Nullable public RouteListingPreference mRouteListingPreference;
 
         RouterRecord(
+                Context context,
                 UserRecord userRecord,
                 IMediaRouter2 router,
                 int uid,
@@ -2082,6 +2086,7 @@
                 boolean hasModifyAudioRoutingPermission,
                 boolean hasMediaContentControlPermission,
                 boolean hasMediaRoutingControl) {
+            mContext = context;
             mUserRecord = userRecord;
             mPackageName = packageName;
             mSelectRouteSequenceNumbers = new ArrayList<>();
@@ -2125,11 +2130,11 @@
                 notifyRoutesUpdated(routesToReport.values().stream().toList());
 
                 List<RoutingSessionInfo> sessionInfos =
-                        mUserRecord.mHandler.mSystemProvider.getSessionInfos();
+                        mUserRecord.mHandler.getSystemProvider().getSessionInfos();
                 RoutingSessionInfo systemSessionToReport =
                         newSystemRoutingPermissionValue && !sessionInfos.isEmpty()
                                 ? sessionInfos.get(0)
-                                : mUserRecord.mHandler.mSystemProvider.getDefaultSessionInfo();
+                                : mUserRecord.mHandler.getSystemProvider().getDefaultSessionInfo();
                 notifySessionInfoChanged(systemSessionToReport);
             }
         }
@@ -2279,7 +2284,7 @@
                 if (route.isSystemRoute() && !hasSystemRoutingPermission()) {
                     // The router lacks permission to modify system routing, so we hide system
                     // route info from them.
-                    route = mUserRecord.mHandler.mSystemProvider.getDefaultRoute();
+                    route = mUserRecord.mHandler.getSystemProvider().getDefaultRoute();
                 }
                 mRouter.requestCreateSessionByManager(uniqueRequestId, oldSession, route);
             } catch (RemoteException ex) {
@@ -2322,18 +2327,34 @@
         }
 
         /**
-         * Returns a filtered copy of {@code routes} that contains only the routes that are {@link
-         * MediaRoute2Info#isVisibleTo visible} to the router corresponding to this record.
+         * Returns a filtered copy of {@code routes} that contains only the routes that are visible
+         * to this RouterRecord.
          */
         private List<MediaRoute2Info> getVisibleRoutes(@NonNull List<MediaRoute2Info> routes) {
             List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
             for (MediaRoute2Info route : routes) {
-                if (route.isVisibleTo(mPackageName)) {
+                if (route.isVisibleTo(mPackageName) && hasPermissionsToSeeRoute(route)) {
                     filteredRoutes.add(route);
                 }
             }
             return filteredRoutes;
         }
+
+        /**
+         * @return whether this RouterRecord has the required permissions to see the given route.
+         */
+        private boolean hasPermissionsToSeeRoute(MediaRoute2Info route) {
+            if (!Flags.enableRouteVisibilityControlApi()) {
+                return true;
+            }
+            for (String permission : route.getRequiredPermissions()) {
+                if (mContext.checkPermission(permission, mPid, mUid)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    return false;
+                }
+            }
+            return true;
+        }
     }
 
     final class ManagerRecord implements IBinder.DeathRecipient {
@@ -2535,6 +2556,10 @@
 
         private boolean mRunning;
 
+        private SystemMediaRoute2Provider getSystemProvider() {
+            return mSystemProvider;
+        }
+
         // TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler.
         UserHandler(
                 @NonNull MediaRouter2ServiceImpl service,
@@ -2544,21 +2569,24 @@
             mServiceRef = new WeakReference<>(service);
             mUserRecord = userRecord;
             mSystemProvider =
-                    new SystemMediaRoute2Provider(
-                            service.mContext, UserHandle.of(userRecord.mUserId), looper);
-            mRouteProviders.add(mSystemProvider);
+                    Flags.enableMirroringInMediaRouter2()
+                            ? new SystemMediaRoute2Provider2(
+                                    service.mContext, UserHandle.of(userRecord.mUserId), looper)
+                            : new SystemMediaRoute2Provider(
+                                    service.mContext, UserHandle.of(userRecord.mUserId), looper);
+            mRouteProviders.add(getSystemProvider());
             mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
                     this, mUserRecord.mUserId);
         }
 
         void init() {
-            mSystemProvider.setCallback(this);
+            getSystemProvider().setCallback(this);
         }
 
         private void start() {
             if (!mRunning) {
                 mRunning = true;
-                mSystemProvider.start();
+                getSystemProvider().start();
                 mWatcher.start();
             }
         }
@@ -2567,7 +2595,7 @@
             if (mRunning) {
                 mRunning = false;
                 mWatcher.stop(); // also stops all providers
-                mSystemProvider.stop();
+                getSystemProvider().stop();
             }
         }
 
@@ -2659,7 +2687,7 @@
             String indent = prefix + "  ";
             pw.println(indent + "mRunning=" + mRunning);
 
-            mSystemProvider.dump(pw, prefix);
+            getSystemProvider().dump(pw, prefix);
             mWatcher.dump(pw, prefix);
         }
 
@@ -2752,7 +2780,7 @@
                     hasAddedOrModifiedRoutes,
                     hasRemovedRoutes,
                     provider.mIsSystemRouteProvider,
-                    mSystemProvider.getDefaultRoute());
+                    getSystemProvider().getDefaultRoute());
         }
 
         private static String getPackageNameFromNullableRecord(
@@ -2966,7 +2994,8 @@
             }
 
             // Bypass checking router if it's the system session (routerRecord should be null)
-            if (TextUtils.equals(getProviderId(uniqueSessionId), mSystemProvider.getUniqueId())) {
+            if (TextUtils.equals(
+                    getProviderId(uniqueSessionId), getSystemProvider().getUniqueId())) {
                 return true;
             }
 
@@ -3097,7 +3126,7 @@
                     && !matchingRequest.mRouterRecord.hasSystemRoutingPermission()) {
                 // The router lacks permission to modify system routing, so we hide system routing
                 // session info from them.
-                sessionInfo = mSystemProvider.getDefaultSessionInfo();
+                sessionInfo = getSystemProvider().getDefaultSessionInfo();
             }
             matchingRequest.mRouterRecord.notifySessionCreated(
                     toOriginalRequestId(uniqueRequestId), sessionInfo);
@@ -3111,13 +3140,13 @@
             }
 
             // For system provider, notify all routers.
-            if (provider == mSystemProvider) {
+            if (provider == getSystemProvider()) {
                 if (mServiceRef.get() == null) {
                     return;
                 }
                 notifySessionInfoChangedToRouters(getRouterRecords(true), sessionInfo);
                 notifySessionInfoChangedToRouters(
-                        getRouterRecords(false), mSystemProvider.getDefaultSessionInfo());
+                        getRouterRecords(false), getSystemProvider().getDefaultSessionInfo());
                 return;
             }
 
@@ -3253,7 +3282,8 @@
             MediaRoute2ProviderInfo systemProviderInfo = null;
             for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
                 // TODO: Create MediaRoute2ProviderInfo#isSystemProvider()
-                if (TextUtils.equals(providerInfo.getUniqueId(), mSystemProvider.getUniqueId())) {
+                if (TextUtils.equals(
+                        providerInfo.getUniqueId(), getSystemProvider().getUniqueId())) {
                     // Adding routes from system provider will be handled below, so skip it here.
                     systemProviderInfo = providerInfo;
                     continue;
@@ -3269,10 +3299,10 @@
                     // This shouldn't happen.
                     Slog.wtf(TAG, "System route provider not found.");
                 }
-                currentSystemSessionInfo = mSystemProvider.getSessionInfos().get(0);
+                currentSystemSessionInfo = getSystemProvider().getSessionInfos().get(0);
             } else {
-                currentRoutes.add(mSystemProvider.getDefaultRoute());
-                currentSystemSessionInfo = mSystemProvider.getDefaultSessionInfo();
+                currentRoutes.add(getSystemProvider().getDefaultRoute());
+                currentSystemSessionInfo = getSystemProvider().getDefaultSessionInfo();
             }
 
             if (!currentRoutes.isEmpty()) {
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index c8a8799..e7b79ab 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -16,7 +16,6 @@
 
 package com.android.server.media;
 
-import android.app.ForegroundServiceDelegationOptions;
 import android.app.Notification;
 import android.media.MediaController2;
 import android.media.Session2CommandGroup;
@@ -98,11 +97,8 @@
     }
 
     @Override
-    public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
-        // For an app to be eligible for FGS delegation, it needs a media session liked to a media
-        // notification. Currently, notifications cannot be linked to MediaSession2 so it is not
-        // supported.
-        return null;
+    public boolean hasLinkedNotificationSupport() {
+        return false;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 668ee2a..5f7c86f 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -29,7 +29,6 @@
 import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
-import android.app.ForegroundServiceDelegationOptions;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.compat.CompatChanges;
@@ -184,8 +183,6 @@
     private final UriGrantsManagerInternal mUgmInternal;
     private final Context mContext;
 
-    private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
-
     private final Object mLock = new Object();
     // This field is partially guarded by mLock. Writes and non-atomic iterations (for example:
     // index-based-iterations) must be guarded by mLock. But it is safe to acquire an iterator
@@ -306,32 +303,10 @@
         mPolicies = policies;
         mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
 
-        mForegroundServiceDelegationOptions = createForegroundServiceDelegationOptions();
-
         // May throw RemoteException if the session app is killed.
         mSessionCb.mCb.asBinder().linkToDeath(this, 0);
     }
 
-    private ForegroundServiceDelegationOptions createForegroundServiceDelegationOptions() {
-        return new ForegroundServiceDelegationOptions.Builder()
-                .setClientPid(mOwnerPid)
-                .setClientUid(getUid())
-                .setClientPackageName(getPackageName())
-                .setClientAppThread(null)
-                .setSticky(false)
-                .setClientInstanceName(
-                        "MediaSessionFgsDelegate_"
-                                + getUid()
-                                + "_"
-                                + mOwnerPid
-                                + "_"
-                                + getPackageName())
-                .setForegroundServiceTypes(0)
-                .setDelegationService(
-                        ForegroundServiceDelegationOptions.DELEGATION_SERVICE_MEDIA_PLAYBACK)
-                .build();
-    }
-
     /**
      * Get the session binder for the {@link MediaSession}.
      *
@@ -389,6 +364,11 @@
         return mUserId;
     }
 
+    @Override
+    public boolean hasLinkedNotificationSupport() {
+        return true;
+    }
+
     /**
      * Check if this session has system priorty and should receive media buttons
      * before any other sessions.
@@ -752,11 +732,6 @@
         return mPackageName + "/" + mTag + "/" + getUniqueId() + " (userId=" + mUserId + ")";
     }
 
-    @Override
-    public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
-        return mForegroundServiceDelegationOptions;
-    }
-
     private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
             final String callingOpPackageName, final int callingPid, final int callingUid,
             final boolean asSystemService, final boolean useSuggested,
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 6c3b123..3966608 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -16,10 +16,8 @@
 
 package com.android.server.media;
 
-import android.app.ForegroundServiceDelegationOptions;
 import android.app.Notification;
 import android.media.AudioManager;
-import android.media.session.PlaybackState;
 import android.os.ResultReceiver;
 import android.view.KeyEvent;
 
@@ -63,13 +61,14 @@
     public abstract int getUserId();
 
     /**
-     * Get the {@link ForegroundServiceDelegationOptions} needed for notifying activity manager
-     * service with changes in the {@link PlaybackState} for this session.
+     * Returns whether this session supports linked notifications.
      *
-     * @return the {@link ForegroundServiceDelegationOptions} needed for notifying the activity
-     *     manager service with changes in the {@link PlaybackState} for this session.
+     * <p>A notification is linked to a media session if it contains
+     * {@link android.app.Notification#EXTRA_MEDIA_SESSION}.
+     *
+     * @return {@code true} if this session supports linked notifications, {@code false} otherwise.
      */
-    public abstract ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions();
+    public abstract boolean hasLinkedNotificationSupport();
 
     /**
      * Check if this session has system priority and should receive media buttons before any other
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2b29fbd..e091fc6 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -30,7 +30,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
-import android.app.ForegroundServiceDelegationOptions;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -185,9 +184,9 @@
 
     /**
      * Maps uid with all user engaged session records associated to it. It's used for calling
-     * ActivityManagerInternal startFGS and stopFGS. This collection doesn't contain
-     * MediaSession2Record(s). When the media session is paused, There exists a timeout before
-     * calling stopFGS unlike usage logging which considers it disengaged immediately.
+     * ActivityManagerInternal internal api to set fgs active/inactive. This collection doesn't
+     * contain MediaSession2Record(s). When the media session is paused, There exists a timeout
+     * before setting FGS inactive unlike usage logging which considers it disengaged immediately.
      */
     @GuardedBy("mLock")
     private final Map<Integer, Set<MediaSessionRecordImpl>> mUserEngagedSessionsForFgs =
@@ -195,7 +194,7 @@
 
     /* Maps uid with all media notifications associated to it */
     @GuardedBy("mLock")
-    private final Map<Integer, Set<Notification>> mMediaNotifications = new HashMap<>();
+    private final Map<Integer, Set<StatusBarNotification>> mMediaNotifications = new HashMap<>();
 
     /**
      * Holds all {@link MediaSessionRecordImpl} which we've reported as being {@link
@@ -700,10 +699,10 @@
             MediaSessionRecordImpl mediaSessionRecord, boolean isUserEngaged) {
         if (isUserEngaged) {
             addUserEngagedSession(mediaSessionRecord);
-            startFgsIfSessionIsLinkedToNotification(mediaSessionRecord);
+            setFgsActiveIfSessionIsLinkedToNotification(mediaSessionRecord);
         } else {
             removeUserEngagedSession(mediaSessionRecord);
-            stopFgsIfNoSessionIsLinkedToNotification(mediaSessionRecord);
+            setFgsInactiveIfNoSessionIsLinkedToNotification(mediaSessionRecord);
         }
     }
 
@@ -737,17 +736,20 @@
         }
     }
 
-    private void startFgsIfSessionIsLinkedToNotification(
+    private void setFgsActiveIfSessionIsLinkedToNotification(
             MediaSessionRecordImpl mediaSessionRecord) {
-        Log.d(TAG, "startFgsIfSessionIsLinkedToNotification: record=" + mediaSessionRecord);
+        Log.d(TAG, "setFgsIfSessionIsLinkedToNotification: record=" + mediaSessionRecord);
         if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
             return;
         }
+        if (!mediaSessionRecord.hasLinkedNotificationSupport()) {
+            return;
+        }
         synchronized (mLock) {
             int uid = mediaSessionRecord.getUid();
-            for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid, Set.of())) {
-                if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) {
-                    startFgsDelegateLocked(mediaSessionRecord);
+            for (StatusBarNotification sbn : mMediaNotifications.getOrDefault(uid, Set.of())) {
+                if (mediaSessionRecord.isLinkedToNotification(sbn.getNotification())) {
+                    setFgsActiveLocked(mediaSessionRecord, sbn);
                     return;
                 }
             }
@@ -755,81 +757,92 @@
     }
 
     @GuardedBy("mLock")
-    private void startFgsDelegateLocked(MediaSessionRecordImpl mediaSessionRecord) {
-        ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
-                mediaSessionRecord.getForegroundServiceDelegationOptions();
-        if (foregroundServiceDelegationOptions == null) {
-            return; // This record doesn't support FGS. Typically a MediaSession2 record.
-        }
+    private void setFgsActiveLocked(MediaSessionRecordImpl mediaSessionRecord,
+            StatusBarNotification sbn) {
         if (!mFgsAllowedMediaSessionRecords.add(mediaSessionRecord)) {
-            return; // This record is already FGS-started.
+            return; // This record already is FGS-activated.
         }
         final long token = Binder.clearCallingIdentity();
         try {
+            final String packageName = sbn.getPackageName();
+            final int uid = sbn.getUid();
+            final int notificationId = sbn.getId();
             Log.i(
                     TAG,
                     TextUtils.formatSimple(
-                            "startFgsDelegate: pkg=%s uid=%d",
-                            foregroundServiceDelegationOptions.mClientPackageName,
-                            foregroundServiceDelegationOptions.mClientUid));
-            mActivityManagerInternal.startForegroundServiceDelegate(
-                    foregroundServiceDelegationOptions, /* connection= */ null);
+                            "setFgsActiveLocked: pkg=%s uid=%d notification=%d",
+                            packageName, uid, notificationId));
+            mActivityManagerInternal.notifyActiveMediaForegroundService(packageName,
+                    sbn.getUser().getIdentifier(), notificationId);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
-    private void stopFgsIfNoSessionIsLinkedToNotification(
+    @Nullable
+    private StatusBarNotification getLinkedNotification(
+            int uid, MediaSessionRecordImpl record) {
+        synchronized (mLock) {
+            for (StatusBarNotification sbn :
+                    mMediaNotifications.getOrDefault(uid, Set.of())) {
+                if (record.isLinkedToNotification(sbn.getNotification())) {
+                    return sbn;
+                }
+            }
+        }
+        return null;
+    }
+
+    private void setFgsInactiveIfNoSessionIsLinkedToNotification(
             MediaSessionRecordImpl mediaSessionRecord) {
-        Log.d(TAG, "stopFgsIfNoSessionIsLinkedToNotification: record=" + mediaSessionRecord);
+        Log.d(TAG, "setFgsIfNoSessionIsLinkedToNotification: record=" + mediaSessionRecord);
         if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
             return;
         }
+        if (!mediaSessionRecord.hasLinkedNotificationSupport()) {
+            return;
+        }
         synchronized (mLock) {
-            int uid = mediaSessionRecord.getUid();
-            ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
-                    mediaSessionRecord.getForegroundServiceDelegationOptions();
-            if (foregroundServiceDelegationOptions == null) {
-                return;
-            }
-
+            final int uid = mediaSessionRecord.getUid();
             for (MediaSessionRecordImpl record :
                     mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
-                for (Notification mediaNotification :
+                for (StatusBarNotification sbn :
                         mMediaNotifications.getOrDefault(uid, Set.of())) {
-                    if (record.isLinkedToNotification(mediaNotification)) {
+                    if (record.isLinkedToNotification(sbn.getNotification())) {
                         // A user engaged session linked with a media notification is found.
                         // We shouldn't call stop FGS in this case.
                         return;
                     }
                 }
             }
-
-            stopFgsDelegateLocked(mediaSessionRecord);
+            final StatusBarNotification linkedNotification =
+                    getLinkedNotification(uid, mediaSessionRecord);
+            if (linkedNotification != null) {
+                setFgsInactiveLocked(mediaSessionRecord, linkedNotification);
+            }
         }
     }
 
     @GuardedBy("mLock")
-    private void stopFgsDelegateLocked(MediaSessionRecordImpl mediaSessionRecord) {
-        ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
-                mediaSessionRecord.getForegroundServiceDelegationOptions();
-        if (foregroundServiceDelegationOptions == null) {
-            return; // This record doesn't support FGS. Typically a MediaSession2 record.
-        }
+    private void setFgsInactiveLocked(MediaSessionRecordImpl mediaSessionRecord,
+            StatusBarNotification sbn) {
         if (!mFgsAllowedMediaSessionRecords.remove(mediaSessionRecord)) {
-            return; // This record is not FGS-started. No need to stop it.
+            return; // This record is not FGS-active. No need to set inactive.
         }
 
         final long token = Binder.clearCallingIdentity();
         try {
+            final String packageName = sbn.getPackageName();
+            final int userId = sbn.getUser().getIdentifier();
+            final int uid = sbn.getUid();
+            final int notificationId = sbn.getId();
             Log.i(
                     TAG,
                     TextUtils.formatSimple(
-                            "stopFgsDelegate: pkg=%s uid=%d",
-                            foregroundServiceDelegationOptions.mClientPackageName,
-                            foregroundServiceDelegationOptions.mClientUid));
-            mActivityManagerInternal.stopForegroundServiceDelegate(
-                    foregroundServiceDelegationOptions);
+                            "setFgsInactiveLocked: pkg=%s uid=%d notification=%d",
+                            packageName, uid, notificationId));
+            mActivityManagerInternal.notifyInactiveMediaForegroundService(packageName,
+                    userId, notificationId);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -3259,18 +3272,18 @@
         @Override
         public void onNotificationPosted(StatusBarNotification sbn) {
             super.onNotificationPosted(sbn);
-            Notification postedNotification = sbn.getNotification();
             int uid = sbn.getUid();
+            final Notification postedNotification = sbn.getNotification();
             if (!postedNotification.isMediaNotification()) {
                 return;
             }
             synchronized (mLock) {
                 mMediaNotifications.putIfAbsent(uid, new HashSet<>());
-                mMediaNotifications.get(uid).add(postedNotification);
+                mMediaNotifications.get(uid).add(sbn);
                 for (MediaSessionRecordImpl mediaSessionRecord :
                         mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
                     if (mediaSessionRecord.isLinkedToNotification(postedNotification)) {
-                        startFgsDelegateLocked(mediaSessionRecord);
+                        setFgsActiveLocked(mediaSessionRecord, sbn);
                         return;
                     }
                 }
@@ -3286,9 +3299,9 @@
                 return;
             }
             synchronized (mLock) {
-                Set<Notification> uidMediaNotifications = mMediaNotifications.get(uid);
+                Set<StatusBarNotification> uidMediaNotifications = mMediaNotifications.get(uid);
                 if (uidMediaNotifications != null) {
-                    uidMediaNotifications.remove(removedNotification);
+                    uidMediaNotifications.remove(sbn);
                     if (uidMediaNotifications.isEmpty()) {
                         mMediaNotifications.remove(uid);
                     }
@@ -3300,8 +3313,7 @@
                 if (notificationRecord == null) {
                     return;
                 }
-
-                stopFgsIfNoSessionIsLinkedToNotification(notificationRecord);
+                setFgsInactiveIfNoSessionIsLinkedToNotification(notificationRecord);
             }
         }
 
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 49897b9..5fb80ba 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -90,7 +90,12 @@
     private volatile SessionCreationOrTransferRequest mPendingTransferRequest;
 
     SystemMediaRoute2Provider(Context context, UserHandle user, Looper looper) {
-        super(COMPONENT_NAME, /* isSystemRouteProvider= */ true);
+        this(context, COMPONENT_NAME, user, looper);
+    }
+
+    protected SystemMediaRoute2Provider(
+            Context context, ComponentName componentName, UserHandle user, Looper looper) {
+        super(componentName, /* isSystemRouteProvider= */ true);
         mContext = context;
         mUser = user;
         mHandler = new Handler(looper);
@@ -588,7 +593,8 @@
     @Override
     protected String getDebugString() {
         return TextUtils.formatSimple(
-                "SystemMR2Provider - package: %s, selected route id: %s, bluetooth impl: %s",
+                "%s - package: %s, selected route id: %s, bluetooth impl: %s",
+                getClass().getSimpleName(),
                 mComponentName.getPackageName(),
                 mSelectedRouteId,
                 mBluetoothRouteController.getClass().getSimpleName());
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
new file mode 100644
index 0000000..8a14a38
--- /dev/null
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 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.media;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.MediaRoute2ProviderService;
+import android.os.Looper;
+import android.os.UserHandle;
+
+/**
+ * Extends {@link SystemMediaRoute2Provider} by adding system routes provided by {@link
+ * MediaRoute2ProviderService provider services}.
+ *
+ * <p>System routes are those which can handle the system audio and/or video.
+ */
+/* package */ class SystemMediaRoute2Provider2 extends SystemMediaRoute2Provider {
+
+    private static final ComponentName COMPONENT_NAME =
+            new ComponentName(
+                    SystemMediaRoute2Provider2.class.getPackage().getName(),
+                    SystemMediaRoute2Provider2.class.getName());
+
+    SystemMediaRoute2Provider2(Context context, UserHandle user, Looper looper) {
+        super(context, COMPONENT_NAME, user, looper);
+    }
+}
diff --git a/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java b/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java
index 6c74cba..05fe781 100644
--- a/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java
+++ b/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java
@@ -30,7 +30,8 @@
             int hostUid,
             int targetUid,
             int timeSinceLastActive,
-            int creationSource) {
+            int creationSource,
+            int stopSource) {
         FrameworkStatsLog.write(
                 code,
                 sessionId,
@@ -39,7 +40,8 @@
                 hostUid,
                 targetUid,
                 timeSinceLastActive,
-                creationSource);
+                creationSource,
+                stopSource);
     }
 
     /** Wrapper around {@link FrameworkStatsLog#write} for MediaProjectionTargetChanged atom. */
@@ -49,13 +51,23 @@
             int targetType,
             int hostUid,
             int targetUid,
-            int windowingMode) {
+            int windowingMode,
+            int width,
+            int height,
+            int centerX,
+            int centerY,
+            int targetChangeType) {
         FrameworkStatsLog.write(
                 code,
                 sessionId,
                 targetType,
                 hostUid,
                 targetUid,
-                windowingMode);
+                windowingMode,
+                width,
+                height,
+                centerX,
+                centerY,
+                targetChangeType);
     }
 }
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index df5ecf8..c428f39 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -51,6 +51,7 @@
 import android.content.pm.PackageManager.ApplicationInfoFlags;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ServiceInfo;
+import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.media.MediaRouter;
 import android.media.projection.IMediaProjection;
@@ -60,6 +61,7 @@
 import android.media.projection.MediaProjectionInfo;
 import android.media.projection.MediaProjectionManager;
 import android.media.projection.ReviewGrantedConsentResult;
+import android.media.projection.StopReason;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -178,7 +180,7 @@
             if (!mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant, reason)) {
                 Slog.d(TAG, "Content Recording: Stopping MediaProjection due to "
                         + MediaProjectionStopController.stopReasonToString(reason));
-                mProjectionGrant.stop();
+                mProjectionGrant.stop(StopReason.STOP_DEVICE_LOCKED);
             }
         }
     }
@@ -257,7 +259,7 @@
         synchronized (mLock) {
             if (mProjectionGrant != null) {
                 Slog.d(TAG, "Content Recording: Stopped MediaProjection due to user switching");
-                mProjectionGrant.stop();
+                mProjectionGrant.stop(StopReason.STOP_USER_SWITCH);
             }
         }
     }
@@ -295,7 +297,7 @@
             Slog.d(TAG,
                     "Content Recording: Stopped MediaProjection due to foreground service change");
             if (mProjectionGrant != null) {
-                mProjectionGrant.stop();
+                mProjectionGrant.stop(StopReason.STOP_FOREGROUND_SERVICE_CHANGE);
             }
         }
     }
@@ -304,7 +306,7 @@
         if (mProjectionGrant != null) {
             Slog.d(TAG, "Content Recording: Stopped MediaProjection to start new "
                     + "incoming projection");
-            mProjectionGrant.stop();
+            mProjectionGrant.stop(StopReason.STOP_NEW_PROJECTION);
         }
         if (mMediaRouteInfo != null) {
             mMediaRouter.getFallbackRoute().select();
@@ -314,7 +316,10 @@
         dispatchStart(projection);
     }
 
-    private void stopProjectionLocked(final MediaProjection projection) {
+    private void stopProjectionLocked(
+            final MediaProjection projection,
+            @StopReason int stopReason
+    ) {
         Slog.d(TAG, "Content Recording: Stopped active MediaProjection and "
                 + "dispatching stop to callbacks");
         ContentRecordingSession session = projection.mSession;
@@ -322,7 +327,7 @@
                 session != null
                         ? session.getTargetUid()
                         : ContentRecordingSession.TARGET_UID_UNKNOWN;
-        mMediaProjectionMetricsLogger.logStopped(projection.uid, targetUid);
+        mMediaProjectionMetricsLogger.logStopped(projection.uid, targetUid, stopReason);
         mProjectionToken = null;
         mProjectionGrant = null;
         dispatchStop(projection);
@@ -403,7 +408,7 @@
                             + "ContentRecordingSession - id= "
                             + mProjectionGrant.getVirtualDisplayId() + "type=" + projectionType);
 
-                    mProjectionGrant.stop();
+                    mProjectionGrant.stop(StopReason.STOP_ERROR);
                 }
                 return false;
             }
@@ -517,6 +522,18 @@
         }
     }
 
+    @VisibleForTesting
+    void notifyCaptureBoundsChanged(int contentToRecord, int targetUid, Rect captureBounds) {
+        synchronized (mLock) {
+            if (mProjectionGrant == null) {
+                Slog.i(TAG, "Cannot log MediaProjectionTargetChanged atom due to null projection");
+            } else {
+                mMediaProjectionMetricsLogger.logChangedCaptureBounds(
+                        contentToRecord, mProjectionGrant.uid, targetUid, captureBounds);
+            }
+        }
+    }
+
     /**
      * Handles result of dialog shown from
      * {@link BinderService#buildReviewGrantedConsentIntentLocked()}.
@@ -557,7 +574,7 @@
                         Slog.w(TAG, "Content Recording: Stopped MediaProjection due to user "
                                 + "consent result of CANCEL - "
                                 + "id= " + mProjectionGrant.getVirtualDisplayId());
-                        mProjectionGrant.stop();
+                        mProjectionGrant.stop(StopReason.STOP_ERROR);
                     }
                     break;
                 case RECORD_CONTENT_DISPLAY:
@@ -773,14 +790,14 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override // Binder call
-        public void stopActiveProjection() {
+        public void stopActiveProjection(@StopReason int stopReason) {
             stopActiveProjection_enforcePermission();
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
                     if (mProjectionGrant != null) {
                         Slog.d(TAG, "Content Recording: Stopping active projection");
-                        mProjectionGrant.stop();
+                        mProjectionGrant.stop(stopReason);
                     }
                 }
             } finally {
@@ -790,8 +807,9 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
         @Override // Binder call
-        public void notifyActiveProjectionCapturedContentResized(int width, int height) {
-            notifyActiveProjectionCapturedContentResized_enforcePermission();
+        public void notifyCaptureBoundsChanged(
+                int contentToRecord, int targetProcessUid, Rect newBounds) {
+            notifyCaptureBoundsChanged_enforcePermission();
             synchronized (mLock) {
                 if (!isCurrentProjection(mProjectionGrant)) {
                     return;
@@ -801,7 +819,13 @@
             try {
                 synchronized (mLock) {
                     if (mProjectionGrant != null && mCallbackDelegate != null) {
-                        mCallbackDelegate.dispatchResize(mProjectionGrant, width, height);
+                        // log metrics for the new bounds
+                        MediaProjectionManagerService.this.notifyCaptureBoundsChanged(
+                                contentToRecord, targetProcessUid, newBounds);
+
+                        // update clients of the new update bounds
+                        mCallbackDelegate.dispatchResize(
+                                mProjectionGrant, newBounds.width(), newBounds.height());
                     }
                 }
             } finally {
@@ -1133,7 +1157,7 @@
                         Slog.d(TAG, "Content Recording: MediaProjection stopped by Binder death - "
                                 + "id= " + mVirtualDisplayId);
                         mCallbackDelegate.remove(callback);
-                        stop();
+                        stop(StopReason.STOP_TARGET_REMOVED);
                     };
                     mToken.linkToDeath(mDeathEater, 0);
                 } catch (RemoteException e) {
@@ -1182,7 +1206,7 @@
         }
 
         @Override // Binder call
-        public void stop() {
+        public void stop(@StopReason int stopReason) {
             synchronized (mLock) {
                 if (!isCurrentProjection(asBinder())) {
                     Slog.w(TAG, "Attempted to stop inactive MediaProjection "
@@ -1211,7 +1235,7 @@
                 Slog.d(TAG, "Content Recording: handling stopping this projection token"
                         + " createTime= " + mCreateTimeMillis
                         + " countStarts= " + mCountStarts);
-                stopProjectionLocked(this);
+                stopProjectionLocked(this, stopReason);
                 mToken.unlinkToDeath(mDeathEater, 0);
                 mToken = null;
                 unregisterCallback(mCallback);
@@ -1295,7 +1319,7 @@
                     Slog.v(TAG, "Reusing token: Throw exception due to invalid projection.");
                     // Tear down projection here; necessary to ensure (among other reasons) that
                     // stop is dispatched to client and cast icon disappears from status bar.
-                    mProjectionGrant.stop();
+                    mProjectionGrant.stop(StopReason.STOP_ERROR);
                     throw new SecurityException("Don't re-use the resultData to retrieve "
                             + "the same projection instance, and don't use a token that has "
                             + "timed out. Don't take multiple captures by invoking "
@@ -1315,10 +1339,9 @@
                     Slog.w(TAG,
                             "Content Recording: MediaProjection start disallowed, aborting "
                                     + "MediaProjection");
-                    stop();
+                    stop(StopReason.STOP_DEVICE_LOCKED);
                     return;
                 }
-
                 mVirtualDisplayId = displayId;
 
                 // If prior session was does not have a valid display id, then update the display
@@ -1357,7 +1380,7 @@
                     if (mProjectionGrant != null) {
                         Slog.d(TAG, "Content Recording: Stopped MediaProjection due to "
                                 + "route type of REMOTE_DISPLAY not selected");
-                        mProjectionGrant.stop();
+                        mProjectionGrant.stop(StopReason.STOP_NEW_MEDIA_ROUTE);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
index be2a25a..8e5c016 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
@@ -19,15 +19,34 @@
 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.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
 import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
 
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CANCELLED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_DEVICE_LOCK;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_ERROR;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_FOREGROUND_SERVICE_CHANGE;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_HOST_APP_STOP;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_NEW_MEDIA_ROUTE;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_NEW_PROJECTION;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_QS_TILE;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_STATUS_BAR_CHIP_STOP;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_TASK_APP_CLOSE;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_USER_SWITCH;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_CHANGE_TYPE__TARGET_CHANGE_BOUNDS;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_CHANGE_TYPE__TARGET_CHANGE_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_CHANGE_TYPE__TARGET_CHANGE_WINDOWING_MODE;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_APP_TASK;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_DISPLAY;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_UNKNOWN;
@@ -36,8 +55,12 @@
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_SPLIT_SCREEN;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_UNKNOWN;
 
+import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
 import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.media.projection.StopReason;
 import android.util.Log;
 import android.view.ContentRecordingSession.RecordContent;
 
@@ -59,8 +82,10 @@
     private final MediaProjectionSessionIdGenerator mSessionIdGenerator;
     private final MediaProjectionTimestampStore mTimestampStore;
 
-    private int mPreviousState =
-            FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN;
+    private final Rect mPreviousTargetBounds = new Rect();
+    private int mPreviousTargetWindowingMode = WINDOWING_MODE_UNDEFINED;
+    private int mPreviousProjectionState =
+            MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN;
 
     MediaProjectionMetricsLogger(
             FrameworkStatsLogWrapper frameworkStatsLogWrapper,
@@ -113,7 +138,8 @@
                 hostUid,
                 TARGET_UID_UNKNOWN,
                 timeSinceLastActiveInSeconds,
-                sessionCreationSource);
+                sessionCreationSource,
+                MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN);
     }
 
     /**
@@ -130,7 +156,8 @@
                 hostUid,
                 TARGET_UID_UNKNOWN,
                 TIME_SINCE_LAST_ACTIVE_UNKNOWN,
-                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN,
+                MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN);
     }
 
     /**
@@ -141,13 +168,12 @@
     public void logProjectionPermissionRequestCancelled(int hostUid) {
         writeStateChanged(
                 mSessionIdGenerator.getCurrentSessionId(),
-                FrameworkStatsLog
-                        .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CANCELLED,
+                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CANCELLED,
                 hostUid,
                 TARGET_UID_UNKNOWN,
                 TIME_SINCE_LAST_ACTIVE_UNKNOWN,
-                FrameworkStatsLog
-                        .MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN,
+                MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN);
     }
 
     /**
@@ -163,7 +189,8 @@
                 hostUid,
                 TARGET_UID_UNKNOWN,
                 TIME_SINCE_LAST_ACTIVE_UNKNOWN,
-                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN,
+                MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN);
     }
 
     /**
@@ -180,7 +207,8 @@
                 hostUid,
                 targetUid,
                 TIME_SINCE_LAST_ACTIVE_UNKNOWN,
-                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN,
+                MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN);
     }
 
     /**
@@ -196,14 +224,63 @@
      */
     public void logChangedWindowingMode(
             int contentToRecord, int hostUid, int targetUid, int windowingMode) {
-        Log.d(TAG, "logChangedWindowingMode");
+        Log.d(TAG, "logChangedWindowingMode: windowingMode= "
+                + WindowConfiguration.windowingModeToString(windowingMode));
+        Log.d(TAG, "targetChangeType= changeWindowingMode");
         writeTargetChanged(
                 mSessionIdGenerator.getCurrentSessionId(),
                 contentToRecordToTargetType(contentToRecord),
                 hostUid,
                 targetUid,
-                windowingModeToTargetWindowingMode(windowingMode));
+                windowingModeToTargetWindowingMode(windowingMode),
+                mPreviousTargetBounds.width(),
+                mPreviousTargetBounds.height(),
+                mPreviousTargetBounds.centerX(),
+                mPreviousTargetBounds.centerY(),
+                MEDIA_PROJECTION_TARGET_CHANGED__TARGET_CHANGE_TYPE__TARGET_CHANGE_WINDOWING_MODE);
+        mPreviousTargetWindowingMode = windowingMode;
+    }
 
+    /**
+     * Logs that the bounds of projection's capture target has changed.
+     *
+     * @param contentToRecord ContentRecordingSession.RecordContent indicating whether it is a
+     *                        task capture or display capture - gets converted to the corresponding
+     *                        TargetType before being logged.
+     * @param hostUid UID of the package that initiates MediaProjection.
+     * @param targetUid UID of the package that is captured if selected.
+     * @param captureBounds Updated bounds of the captured region.
+     */
+    public void logChangedCaptureBounds(
+            int contentToRecord, int hostUid, int targetUid, Rect captureBounds) {
+        final Point capturePosition = new Point(captureBounds.centerX(), captureBounds.centerY());
+        Log.d(TAG, "logChangedCaptureBounds: captureBounds= " + captureBounds + " position= "
+                + capturePosition);
+
+        writeTargetChanged(
+                mSessionIdGenerator.getCurrentSessionId(),
+                contentToRecordToTargetType(contentToRecord),
+                hostUid,
+                targetUid,
+                mPreviousTargetWindowingMode,
+                captureBounds.width(),
+                captureBounds.height(),
+                captureBounds.centerX(),
+                captureBounds.centerY(),
+                captureBoundsToTargetChangeType(captureBounds));
+        mPreviousTargetBounds.set(captureBounds);
+    }
+
+    private int captureBoundsToTargetChangeType(Rect captureBounds) {
+        final boolean hasChangedSize = captureBounds.width() != mPreviousTargetBounds.width()
+                && captureBounds.height() != mPreviousTargetBounds.height();
+
+        if (hasChangedSize) {
+            Log.d(TAG, "targetChangeType= changeBounds");
+            return MEDIA_PROJECTION_TARGET_CHANGED__TARGET_CHANGE_TYPE__TARGET_CHANGE_BOUNDS;
+        }
+        Log.d(TAG, "targetChangeType= changePosition");
+        return MEDIA_PROJECTION_TARGET_CHANGED__TARGET_CHANGE_TYPE__TARGET_CHANGE_POSITION;
     }
 
     @VisibleForTesting
@@ -231,45 +308,85 @@
         };
     }
 
+    @VisibleForTesting
+    public int stopReasonToSessionStopSource(@StopReason int stopReason) {
+        return switch (stopReason) {
+            case StopReason.STOP_HOST_APP ->
+                    MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_HOST_APP_STOP;
+            case StopReason.STOP_TARGET_REMOVED ->
+                    MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_TASK_APP_CLOSE;
+            case StopReason.STOP_DEVICE_LOCKED->
+                    MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_DEVICE_LOCK;
+            case StopReason.STOP_PRIVACY_CHIP ->
+                    MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_STATUS_BAR_CHIP_STOP;
+            case StopReason.STOP_QS_TILE ->
+                    MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_QS_TILE;
+            case StopReason.STOP_USER_SWITCH ->
+                    MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_USER_SWITCH;
+            case StopReason.STOP_FOREGROUND_SERVICE_CHANGE ->
+                    MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_FOREGROUND_SERVICE_CHANGE;
+            case StopReason.STOP_NEW_PROJECTION ->
+                    MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_NEW_PROJECTION;
+            case StopReason.STOP_NEW_MEDIA_ROUTE ->
+                    MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_NEW_MEDIA_ROUTE;
+            case StopReason.STOP_ERROR ->
+                    MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_ERROR;
+            default ->
+                    MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN;
+        };
+    }
+
     /**
      * Logs that the capturing stopped, either normally or because of error.
      *
      * @param hostUid UID of the package that initiates MediaProjection.
      * @param targetUid UID of the package that is captured if selected.
      */
-    public void logStopped(int hostUid, int targetUid) {
+    public void logStopped(int hostUid, int targetUid, int stopReason) {
         boolean wasCaptureInProgress =
-                mPreviousState
+                mPreviousProjectionState
                         == MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
-        Log.d(TAG, "logStopped: wasCaptureInProgress=" + wasCaptureInProgress);
+        Log.d(TAG, "logStopped: wasCaptureInProgress=" + wasCaptureInProgress +
+                " stopReason=" + stopReason);
         writeStateChanged(
                 mSessionIdGenerator.getCurrentSessionId(),
                 MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED,
                 hostUid,
                 targetUid,
                 TIME_SINCE_LAST_ACTIVE_UNKNOWN,
-                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+                MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN,
+                stopReasonToSessionStopSource(stopReason));
 
         if (wasCaptureInProgress) {
             mTimestampStore.registerActiveSessionEnded();
         }
     }
 
-    public void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) {
-        writeStateChanged(hostUid, state, sessionCreationSource);
+    public void notifyProjectionStateChange(
+            int hostUid,
+            int state,
+            int sessionCreationSource,
+            int sessionStopSource
+    ) {
+        writeStateChanged(hostUid, state, sessionCreationSource, sessionStopSource);
     }
 
-    private void writeStateChanged(int hostUid, int state, int sessionCreationSource) {
+    private void writeStateChanged(
+            int hostUid,
+            int state,
+            int sessionCreationSource,
+            int sessionStopSource
+    ) {
         mFrameworkStatsLogWrapper.writeStateChanged(
-                /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED,
+                /* code */ MEDIA_PROJECTION_STATE_CHANGED,
                 /* session_id */ 123,
                 /* state */ state,
-                /* previous_state */ FrameworkStatsLog
-                        .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN,
+                /* previous_state */ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN,
                 /* host_uid */ hostUid,
                 /* target_uid */ -1,
                 /* time_since_last_active */ 0,
-                /* creation_source */ sessionCreationSource);
+                /* creation_source */ sessionCreationSource,
+                /* stop_source */ sessionStopSource);
     }
 
     private void writeStateChanged(
@@ -278,17 +395,19 @@
             int hostUid,
             int targetUid,
             int timeSinceLastActive,
-            int creationSource) {
+            int creationSource,
+            int stopSource) {
         mFrameworkStatsLogWrapper.writeStateChanged(
-                /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED,
+                /* code */ MEDIA_PROJECTION_STATE_CHANGED,
                 sessionId,
                 state,
-                mPreviousState,
+                mPreviousProjectionState,
                 hostUid,
                 targetUid,
                 timeSinceLastActive,
-                creationSource);
-        mPreviousState = state;
+                creationSource,
+                stopSource);
+        mPreviousProjectionState = state;
     }
 
     private void writeTargetChanged(
@@ -296,13 +415,23 @@
             int targetType,
             int hostUid,
             int targetUid,
-            int targetWindowingMode) {
+            int targetWindowingMode,
+            int width,
+            int height,
+            int centerX,
+            int centerY,
+            int targetChangeType) {
         mFrameworkStatsLogWrapper.writeTargetChanged(
-                /* code */ FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED,
+                /* code */ MEDIA_PROJECTION_TARGET_CHANGED,
                 sessionId,
                 targetType,
                 hostUid,
                 targetUid,
-                targetWindowingMode);
+                targetWindowingMode,
+                width,
+                height,
+                centerX,
+                centerY,
+                targetChangeType);
     }
 }
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index c7e00d3..4479e9d 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -25,7 +25,7 @@
 import android.media.quality.IMediaQualityManager;
 import android.media.quality.IPictureProfileCallback;
 import android.media.quality.ISoundProfileCallback;
-import android.media.quality.MediaQualityContract;
+import android.media.quality.MediaQualityContract.BaseParameters;
 import android.media.quality.ParamCapability;
 import android.media.quality.PictureProfile;
 import android.media.quality.PictureProfileHandle;
@@ -35,6 +35,9 @@
 
 import com.android.server.SystemService;
 
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+
 import org.json.JSONException;
 import org.json.JSONObject;
 
@@ -42,6 +45,8 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
+import java.util.UUID;
 
 /**
  * This service manage picture profile and sound profile for TV setting. Also communicates with the
@@ -53,10 +58,14 @@
     private static final String TAG = "MediaQualityService";
     private final Context mContext;
     private final MediaQualityDbHelper mMediaQualityDbHelper;
+    private final BiMap<Long, String> mPictureProfileTempIdMap;
+    private final BiMap<Long, String> mSoundProfileTempIdMap;
 
     public MediaQualityService(Context context) {
         super(context);
         mContext = context;
+        mPictureProfileTempIdMap = HashBiMap.create();
+        mSoundProfileTempIdMap = HashBiMap.create();
         mMediaQualityDbHelper = new MediaQualityDbHelper(mContext);
         mMediaQualityDbHelper.setWriteAheadLoggingEnabled(true);
         mMediaQualityDbHelper.setIdleConnectionTimeout(30);
@@ -71,47 +80,52 @@
     private final class BinderService extends IMediaQualityManager.Stub {
 
         @Override
-        public PictureProfile createPictureProfile(PictureProfile pp) {
+        public PictureProfile createPictureProfile(PictureProfile pp, int userId) {
             SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
 
             ContentValues values = new ContentValues();
-            values.put(MediaQualityContract.BaseParameters.PARAMETER_TYPE, pp.getProfileType());
-            values.put(MediaQualityContract.BaseParameters.PARAMETER_NAME, pp.getName());
-            values.put(MediaQualityContract.BaseParameters.PARAMETER_PACKAGE, pp.getPackageName());
-            values.put(MediaQualityContract.BaseParameters.PARAMETER_INPUT_ID, pp.getInputId());
-            values.put(mMediaQualityDbHelper.SETTINGS, bundleToJson(pp.getParameters()));
+            values.put(BaseParameters.PARAMETER_TYPE, pp.getProfileType());
+            values.put(BaseParameters.PARAMETER_NAME, pp.getName());
+            values.put(BaseParameters.PARAMETER_PACKAGE, pp.getPackageName());
+            values.put(BaseParameters.PARAMETER_INPUT_ID, pp.getInputId());
+            values.put(mMediaQualityDbHelper.SETTINGS, persistableBundleToJson(pp.getParameters()));
 
             // id is auto-generated by SQLite upon successful insertion of row
-            long id = db.insert(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, null, values);
-            return new PictureProfile.Builder(pp).setProfileId(Long.toString(id)).build();
+            Long id = db.insert(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
+                    null, values);
+            populateTempIdMap(mPictureProfileTempIdMap, id);
+            pp.setProfileId(mPictureProfileTempIdMap.get(id));
+            return pp;
         }
 
         @Override
-        public void updatePictureProfile(String id, PictureProfile pp) {
-            // TODO: implement
-        }
-        @Override
-        public void removePictureProfile(String id) {
+        public void updatePictureProfile(String id, PictureProfile pp, int userId) {
             // TODO: implement
         }
 
         @Override
-        public PictureProfile getPictureProfile(int type, String name) {
-            SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase();
+        public void removePictureProfile(String id, int userId) {
+            Long intId = mPictureProfileTempIdMap.inverse().get(id);
+            if (intId != null) {
+                SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+                String selection = BaseParameters.PARAMETER_ID + " = ?";
+                String[] selectionArgs = {Long.toString(intId)};
+                db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, selection,
+                        selectionArgs);
+                mPictureProfileTempIdMap.remove(intId);
+            }
+        }
 
-            String selection = MediaQualityContract.BaseParameters.PARAMETER_TYPE + " = ? AND "
-                    + MediaQualityContract.BaseParameters.PARAMETER_NAME + " = ?";
+        @Override
+        public PictureProfile getPictureProfile(int type, String name, int userId) {
+            String selection = BaseParameters.PARAMETER_TYPE + " = ? AND "
+                    + BaseParameters.PARAMETER_NAME + " = ?";
             String[] selectionArguments = {Integer.toString(type), name};
 
             try (
-                    Cursor cursor = db.query(
+                    Cursor cursor = getCursorAfterQuerying(
                             mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
-                            getAllPictureProfileColumns(),
-                            selection,
-                            selectionArguments,
-                            /*groupBy=*/ null,
-                            /*having=*/ null,
-                            /*orderBy=*/ null)
+                            getAllMediaProfileColumns(), selection, selectionArguments)
             ) {
                 int count = cursor.getCount();
                 if (count == 0) {
@@ -120,172 +134,309 @@
                 if (count > 1) {
                     Log.wtf(TAG, String.format(Locale.US, "%d entries found for type=%d and name=%s"
                                     + " in %s. Should only ever be 0 or 1.", count, type, name,
-                                    mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME));
+                            mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME));
                     return null;
                 }
                 cursor.moveToFirst();
-                return getPictureProfileFromCursor(cursor);
+                return getPictureProfileWithTempIdFromCursor(cursor);
             }
         }
 
-        private String bundleToJson(PersistableBundle bundle) {
-            JSONObject jsonObject = new JSONObject();
-            if (bundle == null) {
-                return jsonObject.toString();
-            }
-            for (String key : bundle.keySet()) {
-                try {
-                    jsonObject.put(key, bundle.getString(key));
-                } catch (JSONException e) {
-                    Log.e(TAG, "Unable to serialize ", e);
-                }
-            }
-            return jsonObject.toString();
-        }
-
-        private PersistableBundle jsonToBundle(String jsonString) {
-            JSONObject jsonObject = null;
-            PersistableBundle bundle = new PersistableBundle();
-
-            try {
-                jsonObject = new JSONObject(jsonString);
-
-                Iterator<String> keys = jsonObject.keys();
-                while (keys.hasNext()) {
-                    String key = keys.next();
-                    Object value = jsonObject.get(key);
-
-                    if (value instanceof String) {
-                        bundle.putString(key, (String) value);
-                    } else if (value instanceof Integer) {
-                        bundle.putInt(key, (Integer) value);
-                    } else if (value instanceof Boolean) {
-                        bundle.putBoolean(key, (Boolean) value);
-                    } else if (value instanceof Double) {
-                        bundle.putDouble(key, (Double) value);
-                    } else if (value instanceof Long) {
-                        bundle.putLong(key, (Long) value);
-                    }
-                }
-            } catch (JSONException e) {
-                throw new RuntimeException(e);
-            }
-
-            return bundle;
-        }
-
-        private String[] getAllPictureProfileColumns() {
-            return new String[]{
-                    MediaQualityContract.BaseParameters.PARAMETER_ID,
-                    MediaQualityContract.BaseParameters.PARAMETER_TYPE,
-                    MediaQualityContract.BaseParameters.PARAMETER_NAME,
-                    MediaQualityContract.BaseParameters.PARAMETER_INPUT_ID,
-                    MediaQualityContract.BaseParameters.PARAMETER_PACKAGE,
-                    mMediaQualityDbHelper.SETTINGS
-            };
-        }
-
-        private PictureProfile getPictureProfileFromCursor(Cursor cursor) {
-            String returnId = cursor.getString(cursor.getColumnIndexOrThrow(
-                    MediaQualityContract.BaseParameters.PARAMETER_ID));
-            int type = cursor.getInt(cursor.getColumnIndexOrThrow(
-                    MediaQualityContract.BaseParameters.PARAMETER_TYPE));
-            String name = cursor.getString(cursor.getColumnIndexOrThrow(
-                    MediaQualityContract.BaseParameters.PARAMETER_NAME));
-            String inputId = cursor.getString(cursor.getColumnIndexOrThrow(
-                    MediaQualityContract.BaseParameters.PARAMETER_INPUT_ID));
-            String packageName = cursor.getString(cursor.getColumnIndexOrThrow(
-                    MediaQualityContract.BaseParameters.PARAMETER_PACKAGE));
-            String settings = cursor.getString(
-                    cursor.getColumnIndexOrThrow(mMediaQualityDbHelper.SETTINGS));
-            return new PictureProfile(returnId, type, name, inputId,
-                    packageName, jsonToBundle(settings));
-        }
-
         @Override
-        public List<PictureProfile> getPictureProfilesByPackage(String packageName) {
-            String selection = MediaQualityContract.BaseParameters.PARAMETER_PACKAGE + " = ?";
+        public List<PictureProfile> getPictureProfilesByPackage(String packageName, int userId) {
+            String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
             String[] selectionArguments = {packageName};
-            return getPictureProfilesBasedOnConditions(getAllPictureProfileColumns(), selection,
+            return getPictureProfilesBasedOnConditions(getAllMediaProfileColumns(), selection,
                     selectionArguments);
         }
 
         @Override
-        public List<PictureProfile> getAvailablePictureProfiles() {
+        public List<PictureProfile> getAvailablePictureProfiles(int userId) {
             return new ArrayList<>();
         }
 
         @Override
-        public List<String> getPictureProfilePackageNames() {
-            String [] column = {MediaQualityContract.BaseParameters.PARAMETER_NAME};
+        public boolean setDefaultPictureProfile(String profileId, int userId) {
+            // TODO: pass the profile ID to MediaQuality HAL when ready.
+            return false;
+        }
+
+        @Override
+        public List<String> getPictureProfilePackageNames(int userId) {
+            String [] column = {BaseParameters.PARAMETER_PACKAGE};
             List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column,
                     null, null);
-            List<String> packageNames = new ArrayList<>();
-            for (PictureProfile pictureProfile: pictureProfiles) {
-                packageNames.add(pictureProfile.getName());
+            return pictureProfiles.stream()
+                    .map(PictureProfile::getPackageName)
+                    .distinct()
+                    .collect(Collectors.toList());
+        }
+
+        @Override
+        public PictureProfileHandle getPictureProfileHandle(String id, int userId) {
+            return null;
+        }
+
+        @Override
+        public SoundProfile createSoundProfile(SoundProfile sp, int userId) {
+            SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+
+            ContentValues values = new ContentValues();
+            values.put(BaseParameters.PARAMETER_TYPE, sp.getProfileType());
+            values.put(BaseParameters.PARAMETER_NAME, sp.getName());
+            values.put(BaseParameters.PARAMETER_PACKAGE, sp.getPackageName());
+            values.put(BaseParameters.PARAMETER_INPUT_ID, sp.getInputId());
+            values.put(mMediaQualityDbHelper.SETTINGS, persistableBundleToJson(sp.getParameters()));
+
+            // id is auto-generated by SQLite upon successful insertion of row
+            Long id = db.insert(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
+                    null, values);
+            populateTempIdMap(mSoundProfileTempIdMap, id);
+            sp.setProfileId(mSoundProfileTempIdMap.get(id));
+            return sp;
+        }
+
+        @Override
+        public void updateSoundProfile(String id, SoundProfile pp, int userId) {
+            // TODO: implement
+        }
+
+        @Override
+        public void removeSoundProfile(String id, int userId) {
+            Long intId = mSoundProfileTempIdMap.inverse().get(id);
+            if (intId != null) {
+                SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+                String selection = BaseParameters.PARAMETER_ID + " = ?";
+                String[] selectionArgs = {Long.toString(intId)};
+                db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection,
+                        selectionArgs);
+                mSoundProfileTempIdMap.remove(intId);
             }
-            return packageNames;
+        }
+
+        @Override
+        public SoundProfile getSoundProfile(int type, String id, int userId) {
+            String selection = BaseParameters.PARAMETER_TYPE + " = ? AND "
+                    + BaseParameters.PARAMETER_NAME + " = ?";
+            String[] selectionArguments = {String.valueOf(type), id};
+
+            try (
+                    Cursor cursor = getCursorAfterQuerying(
+                            mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
+                            getAllMediaProfileColumns(), selection, selectionArguments)
+            ) {
+                int count = cursor.getCount();
+                if (count == 0) {
+                    return null;
+                }
+                if (count > 1) {
+                    Log.wtf(TAG, String.format(Locale.US, "%d entries found for id=%s"
+                                    + " in %s. Should only ever be 0 or 1.", count, id,
+                            mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME));
+                    return null;
+                }
+                cursor.moveToFirst();
+                return getSoundProfileWithTempIdFromCursor(cursor);
+            }
+        }
+
+        @Override
+        public List<SoundProfile> getSoundProfilesByPackage(String packageName, int userId) {
+            String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
+            String[] selectionArguments = {packageName};
+            return getSoundProfilesBasedOnConditions(getAllMediaProfileColumns(), selection,
+                    selectionArguments);
+        }
+
+        @Override
+        public List<SoundProfile> getAvailableSoundProfiles(int userId) {
+            return new ArrayList<>();
+        }
+
+        @Override
+        public boolean setDefaultSoundProfile(String profileId, int userId) {
+            // TODO: pass the profile ID to MediaQuality HAL when ready.
+            return false;
+        }
+
+        @Override
+        public List<String> getSoundProfilePackageNames(int userId) {
+            String [] column = {BaseParameters.PARAMETER_NAME};
+            List<SoundProfile> soundProfiles = getSoundProfilesBasedOnConditions(column,
+                    null, null);
+            return soundProfiles.stream()
+                    .map(SoundProfile::getPackageName)
+                    .distinct()
+                    .collect(Collectors.toList());
+        }
+
+        private void populateTempIdMap(BiMap<Long, String> map, Long id) {
+            if (id != null && map.get(id) == null) {
+                String uuid = UUID.randomUUID().toString();
+                while (map.inverse().containsKey(uuid)) {
+                    uuid = UUID.randomUUID().toString();
+                }
+                map.put(id, uuid);
+            }
+        }
+
+        private String persistableBundleToJson(PersistableBundle bundle) {
+            JSONObject json = new JSONObject();
+            for (String key : bundle.keySet()) {
+                Object value = bundle.get(key);
+                try {
+                    if (value instanceof String) {
+                        json.put(key, bundle.getString(key));
+                    } else if (value instanceof Integer) {
+                        json.put(key, bundle.getInt(key));
+                    } else if (value instanceof Long) {
+                        json.put(key, bundle.getLong(key));
+                    } else if (value instanceof Boolean) {
+                        json.put(key, bundle.getBoolean(key));
+                    } else if (value instanceof Double) {
+                        json.put(key, bundle.getDouble(key));
+                    }
+                } catch (JSONException e) {
+                    Log.e(TAG, "Unable to serialize ", e);
+                }
+            }
+            return json.toString();
+        }
+
+        private PersistableBundle jsonToBundle(String jsonString) {
+            PersistableBundle bundle = new PersistableBundle();
+            if (jsonString != null) {
+                JSONObject jsonObject = null;
+                try {
+                    jsonObject = new JSONObject(jsonString);
+
+                    Iterator<String> keys = jsonObject.keys();
+                    while (keys.hasNext()) {
+                        String key = keys.next();
+                        Object value = jsonObject.get(key);
+
+                        if (value instanceof String) {
+                            bundle.putString(key, (String) value);
+                        } else if (value instanceof Integer) {
+                            bundle.putInt(key, (Integer) value);
+                        } else if (value instanceof Boolean) {
+                            bundle.putBoolean(key, (Boolean) value);
+                        } else if (value instanceof Double) {
+                            bundle.putDouble(key, (Double) value);
+                        } else if (value instanceof Long) {
+                            bundle.putLong(key, (Long) value);
+                        }
+                    }
+                } catch (JSONException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            return bundle;
+        }
+
+        private String[] getAllMediaProfileColumns() {
+            return new String[]{
+                    BaseParameters.PARAMETER_ID,
+                    BaseParameters.PARAMETER_TYPE,
+                    BaseParameters.PARAMETER_NAME,
+                    BaseParameters.PARAMETER_INPUT_ID,
+                    BaseParameters.PARAMETER_PACKAGE,
+                    mMediaQualityDbHelper.SETTINGS
+            };
+        }
+
+        private PictureProfile getPictureProfileWithTempIdFromCursor(Cursor cursor) {
+            return new PictureProfile(
+                    getTempId(mPictureProfileTempIdMap, cursor),
+                    getType(cursor),
+                    getName(cursor),
+                    getInputId(cursor),
+                    getPackageName(cursor),
+                    jsonToBundle(getSettingsString(cursor))
+            );
+        }
+
+        private SoundProfile getSoundProfileWithTempIdFromCursor(Cursor cursor) {
+            return new SoundProfile(
+                    getTempId(mSoundProfileTempIdMap, cursor),
+                    getType(cursor),
+                    getName(cursor),
+                    getInputId(cursor),
+                    getPackageName(cursor),
+                    jsonToBundle(getSettingsString(cursor))
+            );
+        }
+
+        private String getTempId(BiMap<Long, String> map, Cursor cursor) {
+            int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_ID);
+            Long dbId = colIndex != -1 ? cursor.getLong(colIndex) : null;
+            populateTempIdMap(map, dbId);
+            return map.get(dbId);
+        }
+
+        private int getType(Cursor cursor) {
+            int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_TYPE);
+            return colIndex != -1 ? cursor.getInt(colIndex) : 0;
+        }
+
+        private String getName(Cursor cursor) {
+            int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_NAME);
+            return colIndex != -1 ? cursor.getString(colIndex) : null;
+        }
+
+        private String getInputId(Cursor cursor) {
+            int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_INPUT_ID);
+            return colIndex != -1 ? cursor.getString(colIndex) : null;
+        }
+
+        private String getPackageName(Cursor cursor) {
+            int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_PACKAGE);
+            return colIndex != -1 ? cursor.getString(colIndex) : null;
+        }
+
+        private String getSettingsString(Cursor cursor) {
+            int colIndex = cursor.getColumnIndex(mMediaQualityDbHelper.SETTINGS);
+            return colIndex != -1 ? cursor.getString(colIndex) : null;
+        }
+
+        private Cursor getCursorAfterQuerying(String table, String[] columns, String selection,
+                String[] selectionArgs) {
+            SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase();
+            return db.query(table, columns, selection, selectionArgs,
+                    /*groupBy=*/ null, /*having=*/ null, /*orderBy=*/ null);
         }
 
         private List<PictureProfile> getPictureProfilesBasedOnConditions(String[] columns,
                 String selection, String[] selectionArguments) {
-            SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase();
-
             try (
-                    Cursor cursor = db.query(
-                            mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
-                            columns,
-                            selection,
-                            selectionArguments,
-                            /*groupBy=*/ null,
-                            /*having=*/ null,
-                            /*orderBy=*/ null)
+                    Cursor cursor = getCursorAfterQuerying(
+                            mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, columns, selection,
+                            selectionArguments)
             ) {
                 List<PictureProfile> pictureProfiles = new ArrayList<>();
                 while (cursor.moveToNext()) {
-                    pictureProfiles.add(getPictureProfileFromCursor(cursor));
+                    pictureProfiles.add(getPictureProfileWithTempIdFromCursor(cursor));
                 }
                 return pictureProfiles;
             }
         }
 
-        @Override
-        public PictureProfileHandle getPictureProfileHandle(String id) {
-            return null;
+        private List<SoundProfile> getSoundProfilesBasedOnConditions(String[] columns,
+                String selection, String[] selectionArguments) {
+            try (
+                    Cursor cursor = getCursorAfterQuerying(
+                            mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, columns, selection,
+                            selectionArguments)
+            ) {
+                List<SoundProfile> soundProfiles = new ArrayList<>();
+                while (cursor.moveToNext()) {
+                    soundProfiles.add(getSoundProfileWithTempIdFromCursor(cursor));
+                }
+                return soundProfiles;
+            }
         }
 
         @Override
-        public SoundProfile createSoundProfile(SoundProfile pp) {
-            // TODO: implement
-            return pp;
-        }
-        @Override
-        public void updateSoundProfile(String id, SoundProfile pp) {
-            // TODO: implement
-        }
-        @Override
-        public void removeSoundProfile(String id) {
-            // TODO: implement
-        }
-        @Override
-        public SoundProfile getSoundProfile(int type, String id) {
-            return null;
-        }
-        @Override
-        public List<SoundProfile> getSoundProfilesByPackage(String packageName) {
-            return new ArrayList<>();
-        }
-        @Override
-        public List<SoundProfile> getAvailableSoundProfiles() {
-            return new ArrayList<>();
-        }
-        @Override
-        public List<String> getSoundProfilePackageNames() {
-            return new ArrayList<>();
-        }
-
-
-        @Override
         public void registerPictureProfileCallback(final IPictureProfileCallback callback) {
         }
         @Override
@@ -297,70 +448,70 @@
         }
 
         @Override
-        public void setAmbientBacklightSettings(AmbientBacklightSettings settings) {
+        public void setAmbientBacklightSettings(AmbientBacklightSettings settings, int userId) {
         }
 
         @Override
-        public void setAmbientBacklightEnabled(boolean enabled) {
+        public void setAmbientBacklightEnabled(boolean enabled, int userId) {
         }
 
         @Override
-        public List<ParamCapability> getParamCapabilities(List<String> names) {
+        public List<ParamCapability> getParamCapabilities(List<String> names, int userId) {
             return new ArrayList<>();
         }
 
         @Override
-        public List<String> getPictureProfileAllowList() {
+        public List<String> getPictureProfileAllowList(int userId) {
             return new ArrayList<>();
         }
 
         @Override
-        public void setPictureProfileAllowList(List<String> packages) {
+        public void setPictureProfileAllowList(List<String> packages, int userId) {
         }
 
         @Override
-        public List<String> getSoundProfileAllowList() {
+        public List<String> getSoundProfileAllowList(int userId) {
             return new ArrayList<>();
         }
 
         @Override
-        public void setSoundProfileAllowList(List<String> packages) {
+        public void setSoundProfileAllowList(List<String> packages, int userId) {
         }
 
         @Override
-        public boolean isSupported() {
+        public boolean isSupported(int userId) {
             return false;
         }
 
         @Override
-        public void setAutoPictureQualityEnabled(boolean enabled) {
+        public void setAutoPictureQualityEnabled(boolean enabled, int userId) {
         }
 
         @Override
-        public boolean isAutoPictureQualityEnabled() {
+        public boolean isAutoPictureQualityEnabled(int userId) {
             return false;
         }
 
         @Override
-        public void setSuperResolutionEnabled(boolean enabled) {
+        public void setSuperResolutionEnabled(boolean enabled, int userId) {
         }
 
         @Override
-        public boolean isSuperResolutionEnabled() {
+        public boolean isSuperResolutionEnabled(int userId) {
             return false;
         }
 
         @Override
-        public void setAutoSoundQualityEnabled(boolean enabled) {
+        public void setAutoSoundQualityEnabled(boolean enabled, int userId) {
         }
 
         @Override
-        public boolean isAutoSoundQualityEnabled() {
+        public boolean isAutoSoundQualityEnabled(int userId) {
             return false;
         }
 
         @Override
-        public boolean isAmbientBacklightEnabled() {
+        public boolean isAmbientBacklightEnabled(int userId) {
             return false;
         }
     }
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index f914551..4b41696 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -59,6 +59,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.Predicate;
@@ -243,7 +244,7 @@
                 if (!sbn.isAppGroup()) {
                     sbnToBeAutogrouped = maybeGroupWithSections(record, autogroupSummaryExists);
                 } else {
-                    maybeUngroupWithSections(record);
+                    maybeUngroupOnAppGrouped(record);
                 }
             } else {
                 final StatusBarNotification sbn = record.getSbn();
@@ -553,11 +554,13 @@
     }
 
     /**
-     * A non-app grouped notification has been added or updated
+     * A non-app-grouped notification has been added or updated
      * Evaluate if:
      * (a) an existing autogroup summary needs updated attributes
      * (b) a new autogroup summary needs to be added with correct attributes
      * (c) other non-app grouped children need to be moved to the autogroup
+     * (d) the notification has been updated from a groupable to a non-groupable section and needs
+     *  to trigger a cleanup
      *
      * This method implements autogrouping with sections support.
      *
@@ -567,11 +570,11 @@
             boolean autogroupSummaryExists) {
         final StatusBarNotification sbn = record.getSbn();
         boolean sbnToBeAutogrouped = false;
-
         final NotificationSectioner sectioner = getSection(record);
         if (sectioner == null) {
+            maybeUngroupOnNonGroupableUpdate(record);
             if (DEBUG) {
-                Log.i(TAG, "Skipping autogrouping for " + record + " no valid section found.");
+                Slog.i(TAG, "Skipping autogrouping for " + record + " no valid section found.");
             }
             return false;
         }
@@ -584,7 +587,6 @@
         if (record.getGroupKey().equals(fullAggregateGroupKey.toString())) {
             return false;
         }
-
         synchronized (mAggregatedNotifications) {
             ArrayMap<String, NotificationAttributes> ungrouped =
                 mUngroupedAbuseNotifications.getOrDefault(fullAggregateGroupKey, new ArrayMap<>());
@@ -601,11 +603,11 @@
             if (ungrouped.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
                 if (DEBUG) {
                     if (ungrouped.size() >= mAutoGroupAtCount) {
-                        Log.i(TAG,
+                        Slog.i(TAG,
                             "Found >=" + mAutoGroupAtCount
                                 + " ungrouped notifications => force grouping");
                     } else {
-                        Log.i(TAG, "Found aggregate summary => force grouping");
+                        Slog.i(TAG, "Found aggregate summary => force grouping");
                     }
                 }
 
@@ -642,7 +644,24 @@
     }
 
     /**
-     * A notification was added that's app grouped.
+     * A notification was added that was previously part of a valid section and needs to trigger
+     * GH state cleanup.
+     */
+    private void maybeUngroupOnNonGroupableUpdate(NotificationRecord record) {
+        maybeUngroupWithSections(record, getPreviousValidSectionKey(record));
+    }
+
+    /**
+     * A notification was added that is app-grouped.
+     */
+    private void maybeUngroupOnAppGrouped(NotificationRecord record) {
+        maybeUngroupWithSections(record, getSectionGroupKeyWithFallback(record));
+    }
+
+    /**
+     * Called when a notification is posted and is either app-grouped or was previously part of
+     * a valid section and needs to trigger GH state cleanup.
+     *
      * Evaluate whether:
      * (a) an existing autogroup summary needs updated attributes
      * (b) if we need to remove our autogroup overlay for this notification
@@ -652,13 +671,20 @@
      *
      * And updates the internal state of un-app-grouped notifications and their flags.
      */
-    private void maybeUngroupWithSections(NotificationRecord record) {
+    private void maybeUngroupWithSections(NotificationRecord record,
+            @Nullable FullyQualifiedGroupKey fullAggregateGroupKey) {
+        if (fullAggregateGroupKey == null) {
+            if (DEBUG) {
+                Slog.i(TAG,
+                        "Skipping maybeUngroupWithSections for " + record
+                            + " no valid section found.");
+            }
+            return;
+        }
+
         final StatusBarNotification sbn = record.getSbn();
         final String pkgName = sbn.getPackageName();
         final int userId = record.getUserId();
-        final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(userId,
-                pkgName, getSection(record));
-
         synchronized (mAggregatedNotifications) {
             // if this notification still exists and has an autogroup overlay, but is now
             // grouped by the app, clear the overlay
@@ -675,21 +701,22 @@
                 mAggregatedNotifications.put(fullAggregateGroupKey, aggregatedNotificationsAttrs);
 
                 if (DEBUG) {
-                    Log.i(TAG, "maybeUngroup removeAutoGroup: " + record);
+                    Slog.i(TAG, "maybeUngroup removeAutoGroup: " + record);
                 }
 
                 mCallback.removeAutoGroup(sbn.getKey());
 
                 if (aggregatedNotificationsAttrs.isEmpty()) {
                     if (DEBUG) {
-                        Log.i(TAG, "Aggregate group is empty: " + fullAggregateGroupKey);
+                        Slog.i(TAG, "Aggregate group is empty: " + fullAggregateGroupKey);
                     }
                     mCallback.removeAutoGroupSummary(userId, pkgName,
                             fullAggregateGroupKey.toString());
                     mAggregatedNotifications.remove(fullAggregateGroupKey);
                 } else {
                     if (DEBUG) {
-                        Log.i(TAG, "Aggregate group not empty, updating: " + fullAggregateGroupKey);
+                        Slog.i(TAG,
+                                "Aggregate group not empty, updating: " + fullAggregateGroupKey);
                     }
                     updateAggregateAppGroup(fullAggregateGroupKey, sbn.getKey(), true, 0);
                 }
@@ -860,8 +887,15 @@
         final StatusBarNotification sbn = record.getSbn();
         final String pkgName = sbn.getPackageName();
         final int userId = record.getUserId();
-        final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey(userId,
-                pkgName, getSection(record));
+
+        final FullyQualifiedGroupKey fullAggregateGroupKey = getSectionGroupKeyWithFallback(record);
+        if (fullAggregateGroupKey == null) {
+            if (DEBUG) {
+                Slog.i(TAG,
+                        "Skipping autogroup cleanup for " + record + " no valid section found.");
+            }
+            return;
+        }
 
         synchronized (mAggregatedNotifications) {
             ArrayMap<String, NotificationAttributes> ungrouped =
@@ -879,14 +913,15 @@
 
                 if (aggregatedNotificationsAttrs.isEmpty()) {
                     if (DEBUG) {
-                        Log.i(TAG, "Aggregate group is empty: " + fullAggregateGroupKey);
+                        Slog.i(TAG, "Aggregate group is empty: " + fullAggregateGroupKey);
                     }
                     mCallback.removeAutoGroupSummary(userId, pkgName,
                             fullAggregateGroupKey.toString());
                     mAggregatedNotifications.remove(fullAggregateGroupKey);
                 } else {
                     if (DEBUG) {
-                        Log.i(TAG, "Aggregate group not empty, updating: " + fullAggregateGroupKey);
+                        Slog.i(TAG,
+                                "Aggregate group not empty, updating: " + fullAggregateGroupKey);
                     }
                     updateAggregateAppGroup(fullAggregateGroupKey, sbn.getKey(), true, 0);
                 }
@@ -901,6 +936,52 @@
     }
 
     /**
+     * Get the section key for a notification. If the section is invalid, ie. notification is not
+     * auto-groupable, then return the previous valid section, if any.
+     * @param record the notification
+     * @return a section group key, null if not found
+     */
+    @Nullable
+    private FullyQualifiedGroupKey getSectionGroupKeyWithFallback(final NotificationRecord record) {
+        final NotificationSectioner sectioner = getSection(record);
+        if (sectioner != null) {
+            return new FullyQualifiedGroupKey(record.getUserId(), record.getSbn().getPackageName(),
+                sectioner);
+        } else {
+            return getPreviousValidSectionKey(record);
+        }
+    }
+
+    /**
+     * Get the previous valid section key of a notification that may have been updated to an invalid
+     * section. This is needed in case a notification is updated as an ungroupable (invalid section)
+     *  => auto-groups need to be updated/GH state cleanup.
+     * @param record the notification
+     * @return a section group key or null if not found
+     */
+    @Nullable
+    private FullyQualifiedGroupKey getPreviousValidSectionKey(final NotificationRecord record) {
+        synchronized (mAggregatedNotifications) {
+            final String recordKey = record.getKey();
+            // Search in ungrouped
+            for (Entry<FullyQualifiedGroupKey, ArrayMap<String, NotificationAttributes>>
+                        ungroupedSection : mUngroupedAbuseNotifications.entrySet()) {
+                if (ungroupedSection.getValue().containsKey(recordKey)) {
+                    return ungroupedSection.getKey();
+                }
+            }
+            // Search in aggregated
+            for (Entry<FullyQualifiedGroupKey, ArrayMap<String, NotificationAttributes>>
+                    aggregatedSection : mAggregatedNotifications.entrySet()) {
+                if (aggregatedSection.getValue().containsKey(recordKey)) {
+                    return aggregatedSection.getKey();
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
      * Called when a child notification is removed, after some delay, so that this helper can
      * trigger a forced grouping if the group has become sparse/singleton
      * or only the summary is left.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e019267..15af36b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6597,6 +6597,14 @@
                         android.Manifest.permission.INTERACT_ACROSS_USERS,
                         "setNotificationListenerAccessGrantedForUser for user " + userId);
             }
+            if (mUmInternal.isVisibleBackgroundFullUser(userId)) {
+                // The main use case for visible background users is the Automotive multi-display
+                // configuration where a passenger can use a secondary display while the driver is
+                // using the main display. NotificationListeners is designed only for the current
+                // user and work profile. We added a condition to prevent visible background users
+                // from updating the data managed within the NotificationListeners object.
+                return;
+            }
             checkNotificationListenerAccess();
             if (granted && listener.flattenToString().length()
                     > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) {
@@ -12746,6 +12754,20 @@
         }
 
         @Override
+        public void onUserUnlocked(int user) {
+            if (mUmInternal.isVisibleBackgroundFullUser(user)) {
+                // The main use case for visible background users is the Automotive
+                // multi-display configuration where a passenger can use a secondary
+                // display while the driver is using the main display.
+                // NotificationListeners is designed only for the current user and work
+                // profile. We added a condition to prevent visible background users from
+                // updating the data managed within the NotificationListeners object.
+                return;
+            }
+            super.onUserUnlocked(user);
+        }
+
+        @Override
         protected boolean allowRebindForParentUser() {
             return true;
         }
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
index 27c4e9d..bc0fc2b 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
@@ -33,9 +33,10 @@
 
 public class BackgroundInstallControlCallbackHelper {
 
-    @VisibleForTesting static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
-    @VisibleForTesting static final String FLAGGED_USER_ID_KEY = "userId";
-    @VisibleForTesting static final String INSTALL_EVENT_TYPE_KEY = "installEventType";
+    public static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
+    public static final String FLAGGED_USER_ID_KEY = "userId";
+    public static final String INSTALL_EVENT_TYPE_KEY = "installEventType";
+
     private static final String TAG = "BackgroundInstallControlCallbackHelper";
 
     private final Handler mHandler;
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
index 527d680..673a102 100644
--- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -16,29 +16,37 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageInstaller.ACTION_INSTALL_DEPENDENCY;
 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
 import static android.os.Process.SYSTEM_UID;
 
 import android.annotation.NonNull;
+import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.dependencyinstaller.DependencyInstallerCallback;
 import android.content.pm.dependencyinstaller.IDependencyInstallerCallback;
 import android.content.pm.dependencyinstaller.IDependencyInstallerService;
 import android.content.pm.parsing.PackageLite;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.OutcomeReceiver;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.infra.ServiceConnector;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
@@ -48,22 +56,26 @@
 public class InstallDependencyHelper {
     private static final String TAG = InstallDependencyHelper.class.getSimpleName();
     private static final boolean DEBUG = true;
-    private static final String ACTION_INSTALL_DEPENDENCY =
-            "android.intent.action.INSTALL_DEPENDENCY";
+    private static final String ROLE_SYSTEM_DEPENDENCY_INSTALLER =
+            "android.app.role.SYSTEM_DEPENDENCY_INSTALLER";
     // The maximum amount of time to wait before the system unbinds from the verifier.
     private static final long UNBIND_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(6);
     private static final long REQUEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(1);
 
-    private final SharedLibrariesImpl mSharedLibraries;
     private final Context mContext;
-    private final Object mRemoteServiceLock = new Object();
+    private final SharedLibrariesImpl mSharedLibraries;
+    private final PackageInstallerService mPackageInstallerService;
+    @GuardedBy("mTrackers")
+    private final List<DependencyInstallTracker> mTrackers = new ArrayList<>();
+    @GuardedBy("mRemoteServices")
+    private final ArrayMap<Integer, ServiceConnector<IDependencyInstallerService>> mRemoteServices =
+            new ArrayMap<>();
 
-    @GuardedBy("mRemoteServiceLock")
-    private ServiceConnector<IDependencyInstallerService> mRemoteService = null;
-
-    InstallDependencyHelper(Context context, SharedLibrariesImpl sharedLibraries) {
+    InstallDependencyHelper(Context context, SharedLibrariesImpl sharedLibraries,
+            PackageInstallerService packageInstallerService) {
         mContext = context;
         mSharedLibraries = sharedLibraries;
+        mPackageInstallerService = packageInstallerService;
     }
 
     void resolveLibraryDependenciesIfNeeded(PackageLite pkg, Computer snapshot, int userId,
@@ -86,34 +98,28 @@
 
         if (missing.isEmpty()) {
             if (DEBUG) {
-                Slog.i(TAG, "No missing dependency for " + pkg);
+                Slog.d(TAG, "No missing dependency for " + pkg.getPackageName());
             }
             // No need for dependency resolution. Move to installation directly.
             callback.onResult(null);
             return;
         }
 
+        if (DEBUG) {
+            Slog.d(TAG, "Missing dependencies found for pkg: " + pkg.getPackageName()
+                    + " user: " + userId);
+        }
+
         if (!bindToDependencyInstallerIfNeeded(userId, handler, snapshot)) {
             onError(callback, "Dependency Installer Service not found");
             return;
         }
 
-        IDependencyInstallerCallback serviceCallback = new IDependencyInstallerCallback.Stub() {
-            @Override
-            public void onAllDependenciesResolved(int[] sessionIds) throws RemoteException {
-                // TODO(b/372862145): Implement waiting for sessions to finish installation
-                callback.onResult(null);
-            }
-
-            @Override
-            public void onFailureToResolveAllDependencies() throws RemoteException {
-                onError(callback, "Failed to resolve all dependencies automatically");
-            }
-        };
-
+        IDependencyInstallerCallback serviceCallback =
+                new DependencyInstallerCallbackCallOnce(handler, callback, userId);
         boolean scheduleSuccess;
-        synchronized (mRemoteServiceLock) {
-            scheduleSuccess = mRemoteService.run(service -> {
+        synchronized (mRemoteServices) {
+            scheduleSuccess = mRemoteServices.get(userId).run(service -> {
                 service.onDependenciesRequired(missing,
                         new DependencyInstallerCallback(serviceCallback.asBinder()));
             });
@@ -123,35 +129,68 @@
         }
     }
 
-    private void onError(CallOnceProxy callback, String msg) {
+    void notifySessionComplete(int sessionId, boolean success) {
+        if (DEBUG) {
+            Slog.d(TAG, "Session complete for " + sessionId + " result: " + success);
+        }
+        synchronized (mTrackers) {
+            List<DependencyInstallTracker> completedTrackers = new ArrayList<>();
+            for (DependencyInstallTracker tracker: mTrackers) {
+                if (!tracker.onSessionComplete(sessionId, success)) {
+                    completedTrackers.add(tracker);
+                }
+            }
+            mTrackers.removeAll(completedTrackers);
+        }
+    }
+
+    private static void onError(CallOnceProxy callback, String msg) {
         PackageManagerException pe = new PackageManagerException(
                 INSTALL_FAILED_MISSING_SHARED_LIBRARY, msg);
         callback.onError(pe);
+        if (DEBUG) {
+            Slog.i(TAG, "Orig session error: " + msg);
+        }
     }
 
     private boolean bindToDependencyInstallerIfNeeded(int userId, Handler handler,
             Computer snapshot) {
-        synchronized (mRemoteServiceLock) {
-            if (mRemoteService != null) {
+        synchronized (mRemoteServices) {
+            if (mRemoteServices.containsKey(userId)) {
                 if (DEBUG) {
-                    Slog.i(TAG, "DependencyInstallerService already bound");
+                    Slog.i(TAG, "DependencyInstallerService for user " + userId + " already bound");
                 }
                 return true;
             }
         }
 
+        Slog.i(TAG, "Attempting to bind to Dependency Installer Service for user " + userId);
+
+        RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+        if (roleManager == null) {
+            Slog.w(TAG, "Cannot find RoleManager system service");
+            return false;
+        }
+        List<String> holders = roleManager.getRoleHoldersAsUser(
+                ROLE_SYSTEM_DEPENDENCY_INSTALLER, UserHandle.of(userId));
+        if (holders.isEmpty()) {
+            Slog.w(TAG, "No holders of ROLE_SYSTEM_DEPENDENCY_INSTALLER found for user " + userId);
+            return false;
+        }
+
         Intent serviceIntent = new Intent(ACTION_INSTALL_DEPENDENCY);
-        // TODO(b/372862145): Use RoleManager to find the package name
+        serviceIntent.setPackage(holders.getFirst());
         List<ResolveInfo> resolvedIntents = snapshot.queryIntentServicesInternal(
                 serviceIntent, /*resolvedType=*/ null, /*flags=*/0,
                 userId, SYSTEM_UID, Process.INVALID_PID,
                 /*includeInstantApps*/ false, /*resolveForStart*/ false);
 
         if (resolvedIntents.isEmpty()) {
+            Slog.w(TAG, "No package holding ROLE_SYSTEM_DEPENDENCY_INSTALLER found for user "
+                    + userId);
             return false;
         }
 
-
         ResolveInfo resolveInfo = resolvedIntents.getFirst();
         ComponentName componentName = resolveInfo.getComponentInfo().getComponentName();
         serviceIntent.setComponent(componentName);
@@ -177,13 +216,14 @@
                 };
 
 
-        synchronized (mRemoteServiceLock) {
+        synchronized (mRemoteServices) {
             // Some other thread managed to connect to the service first
-            if (mRemoteService != null) {
+            if (mRemoteServices.containsKey(userId)) {
                 return true;
             }
-            mRemoteService = serviceConnector;
-            mRemoteService.setServiceLifecycleCallbacks(
+            mRemoteServices.put(userId, serviceConnector);
+            // Block the lock until we connect to the service
+            serviceConnector.setServiceLifecycleCallbacks(
                 new ServiceConnector.ServiceLifecycleCallbacks<>() {
                     @Override
                     public void onDisconnected(@NonNull IDependencyInstallerService service) {
@@ -199,17 +239,18 @@
                     }
 
                     private void destroy() {
-                        synchronized (mRemoteServiceLock) {
-                            if (mRemoteService != null) {
-                                mRemoteService.unbind();
-                                mRemoteService = null;
+                        synchronized (mRemoteServices) {
+                            if (mRemoteServices.containsKey(userId)) {
+                                mRemoteServices.get(userId).unbind();
+                                mRemoteServices.remove(userId);
                             }
                         }
                     }
 
                 });
-            AndroidFuture<IDependencyInstallerService> unusedFuture = mRemoteService.connect();
+            AndroidFuture<IDependencyInstallerService> unusedFuture = serviceConnector.connect();
         }
+        Slog.i(TAG, "Successfully bound to Dependency Installer Service for user " + userId);
         return true;
     }
 
@@ -253,4 +294,191 @@
             }
         }
     }
+
+    /**
+     * Ensure we call one of the outcomes only once, on the right handler.
+     *
+     * Repeated calls will be no-op.
+     */
+    private class DependencyInstallerCallbackCallOnce extends IDependencyInstallerCallback.Stub {
+
+        private final Handler mHandler;
+        private final CallOnceProxy mCallback;
+        private final int mUserId;
+
+        @GuardedBy("this")
+        private boolean mDependencyInstallerCallbackInvoked = false;
+
+        DependencyInstallerCallbackCallOnce(Handler handler, CallOnceProxy callback, int userId) {
+            mHandler = handler;
+            mCallback = callback;
+            mUserId = userId;
+        }
+
+        @Override
+        public void onAllDependenciesResolved(int[] sessionIds) throws RemoteException {
+            synchronized (this) {
+                if (mDependencyInstallerCallbackInvoked) {
+                    throw new IllegalStateException(
+                            "Callback is being or has been already processed");
+                }
+                mDependencyInstallerCallbackInvoked = true;
+            }
+
+
+            if (DEBUG) {
+                Slog.d(TAG, "onAllDependenciesResolved started");
+            }
+
+            try {
+                // Before creating any tracker, validate the arguments
+                ArraySet<Integer> validSessionIds = validateSessionIds(sessionIds);
+
+                if (validSessionIds.isEmpty()) {
+                    mCallback.onResult(null);
+                    return;
+                }
+
+                // Create a tracker now if there are any pending sessions remaining.
+                DependencyInstallTracker tracker = new DependencyInstallTracker(
+                        mCallback, validSessionIds);
+                synchronized (mTrackers) {
+                    mTrackers.add(tracker);
+                }
+
+                // By the time the tracker was created, some of the sessions in validSessionIds
+                // could have finished. Avoid waiting for them indefinitely.
+                for (int sessionId : validSessionIds) {
+                    SessionInfo sessionInfo = mPackageInstallerService.getSessionInfo(sessionId);
+
+                    // Don't wait for sessions that finished already
+                    if (sessionInfo == null) {
+                        Binder.withCleanCallingIdentity(() -> {
+                            notifySessionComplete(sessionId, /*success=*/ true);
+                        });
+                    }
+                }
+            } catch (Exception e) {
+                // Allow calling the callback again
+                synchronized (this) {
+                    mDependencyInstallerCallbackInvoked = false;
+                }
+                throw e;
+            }
+        }
+
+        @Override
+        public void onFailureToResolveAllDependencies() throws RemoteException {
+            synchronized (this) {
+                if (mDependencyInstallerCallbackInvoked) {
+                    throw new IllegalStateException(
+                            "Callback is being or has been already processed");
+                }
+                mDependencyInstallerCallbackInvoked = true;
+            }
+
+            Binder.withCleanCallingIdentity(() -> {
+                onError(mCallback, "Failed to resolve all dependencies automatically");
+            });
+        }
+
+        private ArraySet<Integer> validateSessionIds(int[] sessionIds) {
+            // Before creating any tracker, validate the arguments
+            ArraySet<Integer> validSessionIds = new ArraySet<>();
+
+            List<SessionInfo> historicalSessions = null;
+            for (int i = 0; i < sessionIds.length; i++) {
+                int sessionId = sessionIds[i];
+                SessionInfo sessionInfo = mPackageInstallerService.getSessionInfo(sessionId);
+
+                // Continue waiting if session exists and hasn't passed or failed yet.
+                if (sessionInfo != null) {
+                    if (sessionInfo.isSessionFailed) {
+                        throw new IllegalArgumentException("Session already finished: "
+                                + sessionId);
+                    }
+
+                    // Wait for session to finish install if it's not already successful.
+                    if (!sessionInfo.isSessionApplied) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "onAllDependenciesResolved pending session: " + sessionId);
+                        }
+                        validSessionIds.add(sessionId);
+                    }
+
+                    // An applied session found. No need to check historical session anymore.
+                    continue;
+                }
+
+                if (DEBUG) {
+                    Slog.d(TAG, "onAllDependenciesResolved cleaning up finished"
+                            + " session: " + sessionId);
+                }
+
+                if (historicalSessions == null) {
+                    historicalSessions = mPackageInstallerService.getHistoricalSessions(
+                            mUserId).getList();
+                }
+
+                sessionInfo = historicalSessions.stream().filter(
+                        s -> s.sessionId == sessionId).findFirst().orElse(null);
+
+                if (sessionInfo == null) {
+                    throw new IllegalArgumentException("Failed to find session: " + sessionId);
+                }
+
+                // Historical session must have been successful, otherwise throw IAE.
+                if (!sessionInfo.isSessionApplied) {
+                    throw new IllegalArgumentException("Session already finished: " + sessionId);
+                }
+            }
+
+            return validSessionIds;
+        }
+    }
+
+    /**
+     * Tracks a list of session ids against a particular callback.
+     *
+     * If all the sessions completes successfully, it invokes the positive flow. If any of the
+     * sessions fails, it invokes the failure flow immediately.
+     */
+    // TODO(b/372862145): Determine and add support for rebooting while dependency is being resolved
+    private static class DependencyInstallTracker {
+        private final CallOnceProxy mCallback;
+        @GuardedBy("this")
+        private final ArraySet<Integer> mPendingSessionIds;
+
+        DependencyInstallTracker(CallOnceProxy callback, ArraySet<Integer> pendingSessionIds) {
+            mCallback = callback;
+            mPendingSessionIds = pendingSessionIds;
+        }
+
+        /**
+         * Process a session complete event.
+         *
+         * Returns true if we still need to continue tracking.
+         */
+        public boolean onSessionComplete(int sessionId, boolean success) {
+            synchronized (this) {
+                if (!mPendingSessionIds.contains(sessionId)) {
+                    // This had no impact on tracker, so continue tracking
+                    return true;
+                }
+
+                if (!success) {
+                    // If one of the dependency fails, the orig session would fail too.
+                    onError(mCallback, "Failed to install all dependencies");
+                    return false; // No point in tracking anymore
+                }
+
+                mPendingSessionIds.remove(sessionId);
+                if (mPendingSessionIds.isEmpty()) {
+                    mCallback.onResult(null);
+                    return false; // Nothing to track anymore
+                }
+                return true; // Keep on tracking
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 8168c54..69c6ce8 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1205,71 +1205,68 @@
 
     private boolean scanInstallPackages(List<InstallRequest> requests,
             Map<String, Boolean> createdAppId, Map<String, Settings.VersionInfo> versionInfos) {
-        // TODO(b/362840929): remove locker
-        try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
-            final Set<String> scannedPackages = new ArraySet<>(requests.size());
-            for (InstallRequest request : requests) {
-                final ParsedPackage packageToScan = request.getParsedPackage();
-                if (packageToScan == null) {
-                    request.setError(INSTALL_FAILED_SESSION_INVALID,
-                            "Failed to obtain package to scan");
+        final Set<String> scannedPackages = new ArraySet<>(requests.size());
+        for (InstallRequest request : requests) {
+            final ParsedPackage packageToScan = request.getParsedPackage();
+            if (packageToScan == null) {
+                request.setError(INSTALL_FAILED_SESSION_INVALID,
+                        "Failed to obtain package to scan");
+                return false;
+            }
+            request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
+            final String packageName = packageToScan.getPackageName();
+            try {
+                request.onScanStarted();
+                final ScanResult scanResult = scanPackageTraced(request.getParsedPackage(),
+                        request.getParseFlags(), request.getScanFlags(),
+                        System.currentTimeMillis(), request.getUser(),
+                        request.getAbiOverride());
+                request.setScanResult(scanResult);
+                request.onScanFinished();
+                if (!scannedPackages.add(packageName)) {
+                    request.setError(
+                            PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
+                            "Duplicate package "
+                                    + packageName
+                                    + " in multi-package install request.");
                     return false;
                 }
-                request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
-                final String packageName = packageToScan.getPackageName();
-                try {
-                    request.onScanStarted();
-                    final ScanResult scanResult = scanPackageTracedLI(request.getParsedPackage(),
-                            request.getParseFlags(), request.getScanFlags(),
-                            System.currentTimeMillis(), request.getUser(),
-                            request.getAbiOverride());
-                    request.setScanResult(scanResult);
-                    request.onScanFinished();
-                    if (!scannedPackages.add(packageName)) {
-                        request.setError(
-                                PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
-                                "Duplicate package "
-                                        + packageName
-                                        + " in multi-package install request.");
-                        return false;
-                    }
-                    if (!checkNoAppStorageIsConsistent(
-                            request.getScanRequestOldPackage(), packageToScan)) {
-                        // TODO: INSTALL_FAILED_UPDATE_INCOMPATIBLE is about incomptabible
-                        //  signatures. Is there a better error code?
-                        request.setError(
-                                INSTALL_FAILED_UPDATE_INCOMPATIBLE,
-                                "Update attempted to change value of "
-                                        + PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
-                        return false;
-                    }
-                    final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
-                    final boolean isSdkLibrary = packageToScan.isSdkLibrary();
-                    if (isApex || (isSdkLibrary && disallowSdkLibsToBeApps())) {
-                        request.getScannedPackageSetting().setAppId(Process.INVALID_UID);
-                    } else {
-                        createdAppId.put(packageName, optimisticallyRegisterAppId(request));
-                    }
-                    versionInfos.put(packageName,
-                            mPm.getSettingsVersionForPackage(packageToScan));
-                } catch (PackageManagerException e) {
-                    request.setError("Scanning Failed.", e);
+                if (!checkNoAppStorageIsConsistent(
+                        request.getScanRequestOldPackage(), packageToScan)) {
+                    // TODO: INSTALL_FAILED_UPDATE_INCOMPATIBLE is about incomptabible
+                    //  signatures. Is there a better error code?
+                    request.setError(
+                            INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                            "Update attempted to change value of "
+                                    + PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
                     return false;
                 }
-                if (request.isArchived()) {
-                    final SparseArray<String> responsibleInstallerTitles =
-                            PackageArchiver.getResponsibleInstallerTitles(mContext,
-                                    mPm.snapshotComputer(), request.getInstallSource(),
-                                    request.getUserId(), mPm.mUserManager.getUserIds());
-                    if (responsibleInstallerTitles == null
-                            || responsibleInstallerTitles.size() == 0) {
-                        request.setError(PackageManagerException.ofInternalError(
-                                "Failed to obtain the responsible installer info",
-                                INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE));
-                        return false;
-                    }
-                    request.setResponsibleInstallerTitles(responsibleInstallerTitles);
+                final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
+                final boolean isSdkLibrary = packageToScan.isSdkLibrary();
+                if (isApex || (isSdkLibrary && disallowSdkLibsToBeApps())) {
+                    request.getScannedPackageSetting().setAppId(Process.INVALID_UID);
+                } else {
+                    createdAppId.put(packageName, optimisticallyRegisterAppId(request));
                 }
+                versionInfos.put(packageName,
+                        mPm.getSettingsVersionForPackage(packageToScan));
+            } catch (PackageManagerException e) {
+                request.setError("Scanning Failed.", e);
+                return false;
+            }
+            if (request.isArchived()) {
+                final SparseArray<String> responsibleInstallerTitles =
+                        PackageArchiver.getResponsibleInstallerTitles(mContext,
+                                mPm.snapshotComputer(), request.getInstallSource(),
+                                request.getUserId(), mPm.mUserManager.getUserIds());
+                if (responsibleInstallerTitles == null
+                        || responsibleInstallerTitles.size() == 0) {
+                    request.setError(PackageManagerException.ofInternalError(
+                            "Failed to obtain the responsible installer info",
+                            INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE));
+                    return false;
+                }
+                request.setResponsibleInstallerTitles(responsibleInstallerTitles);
             }
         }
         return true;
@@ -1366,7 +1363,6 @@
         }
     }
 
-    @GuardedBy("mPm.mInstallLock")
     private boolean checkNoAppStorageIsConsistent(AndroidPackage oldPkg, AndroidPackage newPkg) {
         if (oldPkg == null) {
             // New install, nothing to check against.
@@ -4119,14 +4115,13 @@
         }
     }
 
-    @GuardedBy("mPm.mInstallLock")
-    private ScanResult scanPackageTracedLI(ParsedPackage parsedPackage,
+    private ScanResult scanPackageTraced(ParsedPackage parsedPackage,
             final @ParsingPackageUtils.ParseFlags int parseFlags,
             @PackageManagerService.ScanFlags int scanFlags, long currentTime,
             @Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage");
         try {
-            return scanPackageNewLI(parsedPackage, parseFlags, scanFlags, currentTime, user,
+            return scanPackageNew(parsedPackage, parseFlags, scanFlags, currentTime, user,
                     cpuAbiOverride);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -4203,8 +4198,7 @@
                 realPkgName, parseFlags, scanFlags, isPlatformPackage, user, cpuAbiOverride);
     }
 
-    @GuardedBy("mPm.mInstallLock")
-    private ScanResult scanPackageNewLI(@NonNull ParsedPackage parsedPackage,
+    private ScanResult scanPackageNew(@NonNull ParsedPackage parsedPackage,
             final @ParsingPackageUtils.ParseFlags int parseFlags,
             @PackageManagerService.ScanFlags int scanFlags, long currentTime,
             @Nullable UserHandle user, String cpuAbiOverride)
@@ -4235,7 +4229,7 @@
                     initialScanRequest.mOriginalPkgSetting, initialScanRequest.mRealPkgName,
                     parseFlags, scanFlags, initialScanRequest.mIsPlatformPackage, user,
                     cpuAbiOverride);
-            return ScanPackageUtils.scanPackageOnlyLI(request, mPm.mInjector, mPm.mFactoryTest,
+            return ScanPackageUtils.scanPackageOnly(request, mPm.mInjector, mPm.mFactoryTest,
                     currentTime);
         }
     }
@@ -4289,7 +4283,7 @@
                 ScanPackageUtils.applyPolicy(parsedPackage, scanFlags,
                         mPm.getPlatformPackage(), true);
                 final ScanResult scanResult =
-                        ScanPackageUtils.scanPackageOnlyLI(request, mPm.mInjector,
+                        ScanPackageUtils.scanPackageOnly(request, mPm.mInjector,
                                 mPm.mFactoryTest, -1L);
                 if (scanResult.mExistingSettingCopied
                         && scanResult.mRequest.mPkgSetting != null) {
@@ -4481,7 +4475,7 @@
 
         final long firstInstallTime = Flags.fixSystemAppsFirstInstallTime()
                 ? System.currentTimeMillis() : 0;
-        final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
+        final ScanResult scanResult = scanPackageNew(parsedPackage, parseFlags,
                 scanFlags | SCAN_UPDATE_SIGNATURE, firstInstallTime, user, null);
         return new Pair<>(scanResult, shouldHideSystemApp);
     }
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelper.java b/services/core/java/com/android/server/pm/PackageAbiHelper.java
index c66a9e9..0930299 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelper.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelper.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
 import android.util.ArraySet;
 import android.util.Pair;
 
@@ -28,8 +29,6 @@
 
 import java.io.File;
 
-
-
 // TODO: Move to .parsing sub-package
 @VisibleForTesting
 public interface PackageAbiHelper {
@@ -79,6 +78,23 @@
             AndroidPackage scannedPackage);
 
     /**
+     * Checks alignment of APK and native libraries for 16KB device
+     *
+     * @param pkg AndroidPackage for which alignment check is being done
+     * @param libraryRoot directory for libraries
+     * @param nativeLibraryRootRequiresIsa use isa
+     * @param cpuAbiOverride ABI override mentioned in package
+     * @return {ApplicationInfo.PageSizeAppCompat} if successful or error code
+     *     which suggests undefined mode
+     */
+    @ApplicationInfo.PageSizeAppCompatFlags
+    int checkPackageAlignment(
+            AndroidPackage pkg,
+            String libraryRoot,
+            boolean nativeLibraryRootRequiresIsa,
+            String cpuAbiOverride);
+
+    /**
      * The native library paths and related properties that should be set on a
      * {@link ParsedPackage}.
      */
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
index 9db4d33..7229f07 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
@@ -29,6 +29,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.Flags;
 import android.content.pm.PackageManager;
 import android.os.Build;
@@ -625,4 +626,22 @@
         }
         return adjustedAbi;
     }
+
+    @Override
+    public int checkPackageAlignment(
+            AndroidPackage pkg,
+            String libraryRoot,
+            boolean nativeLibraryRootRequiresIsa,
+            String abiOverride) {
+        NativeLibraryHelper.Handle handle = null;
+        try {
+            handle = AndroidPackageUtils.createNativeLibraryHandle(pkg);
+            return NativeLibraryHelper.checkAlignmentForCompatMode(
+                            handle, libraryRoot, nativeLibraryRootRequiresIsa, abiOverride);
+        } catch (IOException e) {
+            Slog.e(PackageManagerService.TAG, "Failed to check alignment of package : "
+                    + pkg.getPackageName());
+            return ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 0a0882d..4ea4054 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -18,7 +18,6 @@
 
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
-import static com.android.server.pm.PackageManagerService.CHECK_PENDING_INTEGRITY_VERIFICATION;
 import static com.android.server.pm.PackageManagerService.CHECK_PENDING_VERIFICATION;
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
 import static com.android.server.pm.PackageManagerService.DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD;
@@ -29,7 +28,6 @@
 import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_STATUS;
 import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_TIMEOUT;
 import static com.android.server.pm.PackageManagerService.INSTANT_APP_RESOLUTION_PHASE_TWO;
-import static com.android.server.pm.PackageManagerService.INTEGRITY_VERIFICATION_COMPLETE;
 import static com.android.server.pm.PackageManagerService.PACKAGE_VERIFIED;
 import static com.android.server.pm.PackageManagerService.POST_INSTALL;
 import static com.android.server.pm.PackageManagerService.PRUNE_UNUSED_STATIC_SHARED_LIBRARIES;
@@ -149,42 +147,6 @@
 
                 break;
             }
-            case CHECK_PENDING_INTEGRITY_VERIFICATION: {
-                final int verificationId = msg.arg1;
-                final PackageVerificationState state = mPm.mPendingVerification.get(verificationId);
-
-                if (state != null && !state.isIntegrityVerificationComplete()) {
-                    final VerifyingSession verifyingSession = state.getVerifyingSession();
-                    final Uri originUri = Uri.fromFile(verifyingSession.mOriginInfo.mResolvedFile);
-
-                    String errorMsg = "Integrity verification timed out for " + originUri;
-                    Slog.i(TAG, errorMsg);
-
-                    state.setIntegrityVerificationResult(
-                            getDefaultIntegrityVerificationResponse());
-
-                    if (getDefaultIntegrityVerificationResponse()
-                            == PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW) {
-                        Slog.i(TAG, "Integrity check times out, continuing with " + originUri);
-                    } else {
-                        verifyingSession.setReturnCode(
-                                PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
-                                errorMsg);
-                    }
-
-                    if (state.areAllVerificationsComplete()) {
-                        mPm.mPendingVerification.remove(verificationId);
-                    }
-
-                    Trace.asyncTraceEnd(
-                            TRACE_TAG_PACKAGE_MANAGER,
-                            "integrity_verification",
-                            verificationId);
-
-                    verifyingSession.handleIntegrityVerificationFinished();
-                }
-                break;
-            }
             case PACKAGE_VERIFIED: {
                 final int verificationId = msg.arg1;
 
@@ -205,42 +167,6 @@
 
                 break;
             }
-            case INTEGRITY_VERIFICATION_COMPLETE: {
-                final int verificationId = msg.arg1;
-
-                final PackageVerificationState state = mPm.mPendingVerification.get(verificationId);
-                if (state == null) {
-                    Slog.w(TAG, "Integrity verification with id " + verificationId
-                            + " not found. It may be invalid or overridden by verifier");
-                    break;
-                }
-
-                final int response = (Integer) msg.obj;
-                final VerifyingSession verifyingSession = state.getVerifyingSession();
-                final Uri originUri = Uri.fromFile(verifyingSession.mOriginInfo.mResolvedFile);
-
-                state.setIntegrityVerificationResult(response);
-
-                if (response == PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW) {
-                    Slog.i(TAG, "Integrity check passed for " + originUri);
-                } else {
-                    verifyingSession.setReturnCode(
-                            PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
-                            "Integrity check failed for " + originUri);
-                }
-
-                if (state.areAllVerificationsComplete()) {
-                    mPm.mPendingVerification.remove(verificationId);
-                }
-
-                Trace.asyncTraceEnd(
-                        TRACE_TAG_PACKAGE_MANAGER,
-                        "integrity_verification",
-                        verificationId);
-
-                verifyingSession.handleIntegrityVerificationFinished();
-                break;
-            }
             case INSTANT_APP_RESOLUTION_PHASE_TWO: {
                 InstantAppResolver.doInstantAppResolutionPhaseTwo(mPm.mContext,
                         mPm.snapshotComputer(),
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index a4152a7..516b002 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -327,7 +327,7 @@
                 context, mInstallThread.getLooper(), new AppStateHelper(context));
         mPackageArchiver = new PackageArchiver(mContext, mPm);
         mInstallDependencyHelper = new InstallDependencyHelper(mContext,
-                mPm.mInjector.getSharedLibrariesImpl());
+                mPm.mInjector.getSharedLibrariesImpl(), this);
 
         LocalServices.getService(SystemServiceManager.class).startService(
                 new Lifecycle(context, this));
@@ -337,6 +337,10 @@
         return mStagingManager;
     }
 
+    InstallDependencyHelper getInstallDependencyHelper() {
+        return mInstallDependencyHelper;
+    }
+
     boolean okToSendBroadcasts()  {
         return mOkToSendBroadcasts;
     }
@@ -2325,6 +2329,11 @@
                             }
                         }
 
+                        if (Flags.sdkDependencyInstaller()) {
+                            mInstallDependencyHelper.notifySessionComplete(
+                                    session.sessionId, success);
+                        }
+
                         final File appIconFile = buildAppIconFile(session.sessionId);
                         if (appIconFile.exists()) {
                             appIconFile.delete();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 8f8802e..891d66a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -61,6 +61,7 @@
 
 import android.Manifest;
 import android.annotation.AnyThread;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -188,6 +189,7 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
+import com.android.server.art.ArtManagedInstallFileHelper;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -852,7 +854,11 @@
             if (file.getName().endsWith(REMOVE_MARKER_EXTENSION)) return false;
             if (file.getName().endsWith(V4Signature.EXT)) return false;
             if (isAppMetadata(file)) return false;
-            if (DexMetadataHelper.isDexMetadataFile(file)) return false;
+            if (com.android.art.flags.Flags.artServiceV3()) {
+                if (ArtManagedInstallFileHelper.isArtManaged(file.getPath())) return false;
+            } else {
+                if (DexMetadataHelper.isDexMetadataFile(file)) return false;
+            }
             if (VerityUtils.isFsveritySignatureFile(file)) return false;
             if (ApkChecksums.isDigestOrDigestSignatureFile(file)) return false;
             return true;
@@ -876,6 +882,13 @@
             return true;
         }
     };
+    private static final FileFilter sArtManagedFilter = new FileFilter() {
+        @Override
+        public boolean accept(File file) {
+            return !file.isDirectory() && com.android.art.flags.Flags.artServiceV3()
+                    && ArtManagedInstallFileHelper.isArtManaged(file.getPath());
+        }
+    };
 
     static boolean isDataLoaderInstallation(SessionParams params) {
         return params.dataLoaderParams != null;
@@ -1607,6 +1620,19 @@
     }
 
     @GuardedBy("mLock")
+    private List<String> getArtManagedFilePathsLocked() {
+        String[] names = getNamesLocked();
+        ArrayList<String> result = new ArrayList<>(names.length);
+        for (String name : names) {
+            File file = new File(stageDir, name);
+            if (sArtManagedFilter.accept(file)) {
+                result.add(file.getPath());
+            }
+        }
+        return result;
+    }
+
+    @GuardedBy("mLock")
     private void enableFsVerityToAddedApksWithIdsig() throws PackageManagerException {
         try {
             List<File> files = getAddedApksLocked();
@@ -3453,7 +3479,7 @@
         }
 
         final File targetFile = new File(stageDir, targetName);
-        resolveAndStageFileLocked(addedFile, targetFile, null);
+        resolveAndStageFileLocked(addedFile, targetFile, null, List.of() /* artManagedFilePaths */);
         mResolvedBaseFile = targetFile;
 
         // Populate package name of the apex session
@@ -3546,6 +3572,7 @@
                     TextUtils.formatSimple("Session: %d. No packages staged in %s", sessionId,
                           stageDir.getAbsolutePath()));
         }
+        final List<String> artManagedFilePaths = getArtManagedFilePathsLocked();
 
         // Verify that all staged packages are internally consistent
         final ArraySet<String> stagedSplits = new ArraySet<>();
@@ -3602,7 +3629,8 @@
             final File targetFile = new File(stageDir, targetName);
             if (!isArchivedInstallation()) {
                 final File sourceFile = new File(apk.getPath());
-                resolveAndStageFileLocked(sourceFile, targetFile, apk.getSplitName());
+                resolveAndStageFileLocked(
+                        sourceFile, targetFile, apk.getSplitName(), artManagedFilePaths);
             }
 
             // Base is coming from session
@@ -3763,7 +3791,7 @@
             // Inherit base if not overridden.
             if (mResolvedBaseFile == null) {
                 mResolvedBaseFile = new File(appInfo.getBaseCodePath());
-                inheritFileLocked(mResolvedBaseFile);
+                inheritFileLocked(mResolvedBaseFile, artManagedFilePaths);
                 // Collect the requiredSplitTypes from base
                 CollectionUtils.addAll(requiredSplitTypes, existing.getBaseRequiredSplitTypes());
             } else if ((params.installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
@@ -3782,7 +3810,7 @@
                     final boolean splitRemoved = removeSplitList.contains(splitName);
                     final boolean splitReplaced = stagedSplits.contains(splitName);
                     if (!splitReplaced && !splitRemoved) {
-                        inheritFileLocked(splitFile);
+                        inheritFileLocked(splitFile, artManagedFilePaths);
                         // Collect the requiredSplitTypes and staged splitTypes from splits
                         CollectionUtils.addAll(requiredSplitTypes,
                                 existing.getRequiredSplitTypes()[i]);
@@ -3968,6 +3996,23 @@
                 DexMetadataHelper.isFsVerityRequired());
     }
 
+    @FlaggedApi(com.android.art.flags.Flags.FLAG_ART_SERVICE_V3)
+    @GuardedBy("mLock")
+    private void maybeStageArtManagedInstallFilesLocked(File origFile, File targetFile,
+            List<String> artManagedFilePaths) throws PackageManagerException {
+        for (String path : ArtManagedInstallFileHelper.filterPathsForApk(
+                     artManagedFilePaths, origFile.getPath())) {
+            File artManagedFile = new File(path);
+            if (!FileUtils.isValidExtFilename(artManagedFile.getName())) {
+                throw new PackageManagerException(
+                        INSTALL_FAILED_INVALID_APK, "Invalid filename: " + artManagedFile);
+            }
+            File targetArtManagedFile = new File(
+                    ArtManagedInstallFileHelper.getTargetPathForApk(path, targetFile.getPath()));
+            stageFileLocked(artManagedFile, targetArtManagedFile);
+        }
+    }
+
     private IncrementalFileStorages getIncrementalFileStorages() {
         synchronized (mLock) {
             return mIncrementalFileStorages;
@@ -4065,8 +4110,8 @@
     }
 
     @GuardedBy("mLock")
-    private void resolveAndStageFileLocked(File origFile, File targetFile, String splitName)
-            throws PackageManagerException {
+    private void resolveAndStageFileLocked(File origFile, File targetFile, String splitName,
+            List<String> artManagedFilePaths) throws PackageManagerException {
         stageFileLocked(origFile, targetFile);
 
         // Stage APK's fs-verity signature if present.
@@ -4077,8 +4122,13 @@
                 && VerityUtils.isFsVeritySupported()) {
             maybeStageV4SignatureLocked(origFile, targetFile);
         }
-        // Stage dex metadata (.dm) and corresponding fs-verity signature if present.
-        maybeStageDexMetadataLocked(origFile, targetFile);
+        // Stage ART managed install files (e.g., dex metadata (.dm)) and corresponding fs-verity
+        // signature if present.
+        if (com.android.art.flags.Flags.artServiceV3()) {
+            maybeStageArtManagedInstallFilesLocked(origFile, targetFile, artManagedFilePaths);
+        } else {
+            maybeStageDexMetadataLocked(origFile, targetFile);
+        }
         // Stage checksums (.digests) if present.
         maybeStageDigestsLocked(origFile, targetFile, splitName);
     }
@@ -4103,7 +4153,7 @@
     }
 
     @GuardedBy("mLock")
-    private void inheritFileLocked(File origFile) {
+    private void inheritFileLocked(File origFile, List<String> artManagedFilePaths) {
         mResolvedInheritedFiles.add(origFile);
 
         maybeInheritFsveritySignatureLocked(origFile);
@@ -4111,12 +4161,20 @@
             maybeInheritV4SignatureLocked(origFile);
         }
 
-        // Inherit the dex metadata if present.
-        final File dexMetadataFile =
-                DexMetadataHelper.findDexMetadataForFile(origFile);
-        if (dexMetadataFile != null) {
-            mResolvedInheritedFiles.add(dexMetadataFile);
-            maybeInheritFsveritySignatureLocked(dexMetadataFile);
+        // Inherit ART managed install files (e.g., dex metadata (.dm)) if present.
+        if (com.android.art.flags.Flags.artServiceV3()) {
+            for (String path : ArtManagedInstallFileHelper.filterPathsForApk(
+                         artManagedFilePaths, origFile.getPath())) {
+                File artManagedFile = new File(path);
+                mResolvedInheritedFiles.add(artManagedFile);
+                maybeInheritFsveritySignatureLocked(artManagedFile);
+            }
+        } else {
+            final File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(origFile);
+            if (dexMetadataFile != null) {
+                mResolvedInheritedFiles.add(dexMetadataFile);
+                maybeInheritFsveritySignatureLocked(dexMetadataFile);
+            }
         }
         // Inherit the digests if present.
         final File digestsFile = ApkChecksums.findDigestsForFile(origFile);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 807445e..65bb701 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -923,8 +923,8 @@
     static final int ENABLE_ROLLBACK_TIMEOUT = 22;
     static final int DEFERRED_NO_KILL_POST_DELETE = 23;
     static final int DEFERRED_NO_KILL_INSTALL_OBSERVER = 24;
-    static final int INTEGRITY_VERIFICATION_COMPLETE = 25;
-    static final int CHECK_PENDING_INTEGRITY_VERIFICATION = 26;
+    // static final int UNUSED = 25;
+    // static final int UNUSED = 26;
     static final int DOMAIN_VERIFICATION = 27;
     static final int PRUNE_UNUSED_STATIC_SHARED_LIBRARIES = 28;
     static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER = 29;
@@ -5874,6 +5874,67 @@
                     userId, callingPackage);
         }
 
+        @Override
+        public void setPageSizeAppCompatFlagsSettingsOverride(String packageName, boolean enabled) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingAppId = UserHandle.getAppId(callingUid);
+
+            if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)) {
+                throw new SecurityException("Caller must be the system or root.");
+            }
+
+            int settingsMode = enabled
+                    ? ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED
+                    : ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED;
+            PackageStateMutator.Result result =
+                    commitPackageStateMutation(
+                            null,
+                            packageName,
+                            packageState ->
+                                    packageState
+                                            .setPageSizeAppCompatFlags(settingsMode));
+            if (result.isSpecificPackageNull()) {
+                throw new IllegalArgumentException("Unknown package: " + packageName);
+            }
+            scheduleWriteSettings();
+        }
+
+        @Override
+        public boolean isPageSizeCompatEnabled(String packageName) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingAppId = UserHandle.getAppId(callingUid);
+            final int userId = UserHandle.getCallingUserId();
+
+            if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)) {
+                throw new SecurityException("Caller must be the system or root.");
+            }
+
+            PackageStateInternal packageState =
+                    snapshotComputer().getPackageStateForInstalledAndFiltered(
+                            packageName, callingUid, userId);
+
+            return packageState == null ? false : packageState.isPageSizeAppCompatEnabled();
+        }
+
+        @Override
+        public String getPageSizeCompatWarningMessage(String packageName) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingAppId = UserHandle.getAppId(callingUid);
+            final int userId = UserHandle.getCallingUserId();
+
+            if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)) {
+                throw new SecurityException("Caller must be the system or root.");
+            }
+
+            PackageStateInternal packageState =
+                    snapshotComputer().getPackageStateForInstalledAndFiltered(
+                            packageName, callingUid, userId);
+
+            return packageState == null
+                    ? null
+                    : packageState.getPageSizeCompatWarningMessage(mContext);
+        }
+
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USERS)
         @Override
         public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden,
@@ -6578,6 +6639,20 @@
         }
 
         @Override
+        @NonNull
+        public List<String> getAllApexDirectories() {
+            PackageManagerServiceUtils.enforceSystemOrRoot(
+                    "getAllApexDirectories can only be called by system or root");
+            List<String> apexDirectories = new ArrayList<>();
+            List<ApexManager.ActiveApexInfo> apexes = mApexManager.getActiveApexInfos();
+            for (int i = 0; i < apexes.size(); i++) {
+                ApexManager.ActiveApexInfo apex = apexes.get(i);
+                apexDirectories.add(apex.apexDirectory.getAbsolutePath());
+            }
+            return apexDirectories;
+        }
+
+        @Override
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                 throws RemoteException {
             try {
@@ -7049,12 +7124,10 @@
             return mSettings.isPermissionUpgradeNeeded(userId);
         }
 
+        @Deprecated
         @Override
         public void setIntegrityVerificationResult(int verificationId, int verificationResult) {
-            final Message msg = mHandler.obtainMessage(INTEGRITY_VERIFICATION_COMPLETE);
-            msg.arg1 = verificationId;
-            msg.obj = verificationResult;
-            mHandler.sendMessage(msg);
+          // Do nothing.
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index 52e8c52..4b82de0 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -73,22 +73,16 @@
     }
 
     public void onUserRemoved(int userId) {
-        ArrayList<IRemoteCallback> targetUnRegisteredCallbacks = null;
+        final ArrayList<IRemoteCallback> targetUnRegisteredCallbacks = new ArrayList<>();
         synchronized (mLock) {
-            int registerCount = mCallbacks.getRegisteredCallbackCount();
-            for (int i = 0; i < registerCount; i++) {
-                RegisterUser registerUser =
-                        (RegisterUser) mCallbacks.getRegisteredCallbackCookie(i);
+            mCallbacks.broadcast((callback, user) -> {
+                RegisterUser registerUser = (RegisterUser) user;
                 if (registerUser.getUserId() == userId) {
-                    IRemoteCallback callback = mCallbacks.getRegisteredCallbackItem(i);
-                    if (targetUnRegisteredCallbacks == null) {
-                        targetUnRegisteredCallbacks = new ArrayList<>();
-                    }
                     targetUnRegisteredCallbacks.add(callback);
                 }
-            }
+            });
         }
-        if (targetUnRegisteredCallbacks != null && targetUnRegisteredCallbacks.size() > 0) {
+        if (!targetUnRegisteredCallbacks.isEmpty()) {
             int count = targetUnRegisteredCallbacks.size();
             for (int i = 0; i < count; i++) {
                 unregisterPackageMonitorCallback(targetUnRegisteredCallbacks.get(i));
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9428de7..fb16b86 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -27,6 +27,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.SharedLibraryInfo;
@@ -221,6 +222,8 @@
     /** @see PackageState#getCategoryOverride() */
     private int categoryOverride = ApplicationInfo.CATEGORY_UNDEFINED;
 
+    private int mPageSizeAppCompatFlags = ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+
     @NonNull
     private final PackageStateUnserialized pkgState = new PackageStateUnserialized(this);
 
@@ -863,6 +866,8 @@
         }
 
         copyMimeGroups(other.mimeGroups);
+        mPageSizeAppCompatFlags = other.mPageSizeAppCompatFlags;
+
         pkgState.updateFrom(other.pkgState);
         onChanged();
     }
@@ -1617,6 +1622,34 @@
         return this;
     }
 
+    /**
+     * @see Set page size app compat mode.
+     */
+    public PackageSetting setPageSizeAppCompatFlags(int mode) {
+        if (mode < 0 || mode >= ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MAX) {
+            throw new IllegalArgumentException("Invalid page size compat mode specified");
+        }
+
+        // OR assignment is used here to avoid overriding the mode set by the manifest.
+        this.mPageSizeAppCompatFlags |= mode;
+
+        // Only one bit of the following can be set at same time. Both are needed to detect app
+        // compat 'disabled' state from settings vs bit was never set.
+        if (ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED == mode) {
+            this.mPageSizeAppCompatFlags &=
+                    ~ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED;
+        } else if (ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED == mode) {
+            this.mPageSizeAppCompatFlags &=
+                    ~ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED;
+        }
+        onChanged();
+        return this;
+    }
+
+    public int getPageSizeAppCompatFlags() {
+        return mPageSizeAppCompatFlags;
+    }
+
     public PackageSetting setLegacyNativeLibraryPath(
             String legacyNativeLibraryPathString) {
         this.legacyNativeLibraryPath = legacyNativeLibraryPathString;
@@ -1787,6 +1820,63 @@
         return getBoolean(Booleans.SCANNED_AS_STOPPED_SYSTEM_APP);
     }
 
+    /** Returns true if ELF files will be loaded in Page size compatibility mode */
+    @Override
+    public boolean isPageSizeAppCompatEnabled() {
+        // If manifest or settings has disabled the compat mode, don't run app in compat mode.
+        boolean manifestOverrideDisabled = (mPageSizeAppCompatFlags
+                &  ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED) != 0;
+        boolean settingsOverrideDisabled = (mPageSizeAppCompatFlags
+                &  ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED) != 0;
+        if (manifestOverrideDisabled || settingsOverrideDisabled) {
+            return false;
+        }
+
+        int mask =
+                ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED
+                        | ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED
+                        | ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED;
+        return (mPageSizeAppCompatFlags & mask) != 0;
+    }
+
+    /**
+     * Returns dialog string based on alignment of uncompressed shared libs inside the APK and ELF
+     * alignment.
+     */
+    @Override
+    public String getPageSizeCompatWarningMessage(Context context) {
+        boolean manifestOverrideEnabled =  (mPageSizeAppCompatFlags
+                & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED) != 0;
+        boolean settingsOverrideEnabled =  (mPageSizeAppCompatFlags
+                & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED) != 0;
+        if (manifestOverrideEnabled || settingsOverrideEnabled) {
+            return null;
+        }
+
+        boolean uncompressedLibsNotAligned = (mPageSizeAppCompatFlags
+                & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED) != 0;
+        boolean elfNotAligned = (mPageSizeAppCompatFlags
+                & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED) != 0;
+
+        if (uncompressedLibsNotAligned && elfNotAligned) {
+            return context.getText(
+                            com.android.internal.R.string.page_size_compat_apk_and_elf_warning)
+                    .toString();
+        }
+
+        if (uncompressedLibsNotAligned) {
+            return context.getText(com.android.internal.R.string.page_size_compat_apk_warning)
+                    .toString();
+        }
+
+        if (elfNotAligned) {
+            return context.getText(com.android.internal.R.string.page_size_compat_elf_warning)
+                    .toString();
+        }
+
+        return null;
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -1952,7 +2042,6 @@
     @Deprecated
     private void __metadata() {}
 
-
     //@formatter:on
     // End of generated code
 
diff --git a/services/core/java/com/android/server/pm/PackageVerificationState.java b/services/core/java/com/android/server/pm/PackageVerificationState.java
index 0b6ccc4..63c2ee2 100644
--- a/services/core/java/com/android/server/pm/PackageVerificationState.java
+++ b/services/core/java/com/android/server/pm/PackageVerificationState.java
@@ -43,8 +43,6 @@
 
     private boolean mRequiredVerificationPassed;
 
-    private boolean mIntegrityVerificationComplete;
-
     /**
      * Create a new package verification state where {@code requiredVerifierUid} is the user ID for
      * the package that must reply affirmative before things can continue.
@@ -213,15 +211,7 @@
         return mExtendedTimeoutUids.get(uid, false);
     }
 
-    void setIntegrityVerificationResult(int code) {
-        mIntegrityVerificationComplete = true;
-    }
-
-    boolean isIntegrityVerificationComplete() {
-        return mIntegrityVerificationComplete;
-    }
-
     boolean areAllVerificationsComplete() {
-        return mIntegrityVerificationComplete && isVerificationComplete();
+        return isVerificationComplete();
     }
 }
diff --git a/services/core/java/com/android/server/pm/SaferIntentUtils.java b/services/core/java/com/android/server/pm/SaferIntentUtils.java
index bc36fab..854e142 100644
--- a/services/core/java/com/android/server/pm/SaferIntentUtils.java
+++ b/services/core/java/com/android/server/pm/SaferIntentUtils.java
@@ -26,6 +26,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.Overridable;
 import android.content.Intent;
@@ -88,6 +89,22 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     private static final long IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS = 229362273;
 
+    /**
+     * Intents sent from apps enabling this feature will stop resolving to components with
+     * non matching intent filters, even when explicitly setting a component name, unless the
+     * target components are in the same app as the calling app.
+     * <p>
+     * When an app registers an exported component in its manifest and adds &lt;intent-filter&gt;s,
+     * the component can be started by any intent - even those that do not match the intent filter.
+     * This has proven to be something that many developers find counterintuitive.
+     * Without checking the intent when the component is started, in some circumstances this can
+     * allow 3P apps to trigger internal-only functionality.
+     */
+    @ChangeId
+    @Overridable
+    @Disabled
+    private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
+
     @Nullable
     private static ParsedMainComponent infoToComponent(
             ComponentInfo info, ComponentResolverApi resolver, boolean isReceiver) {
@@ -249,6 +266,20 @@
      */
     public static void enforceIntentFilterMatching(
             IntentArgs args, List<ResolveInfo> resolveInfos) {
+        // Switch to the new intent matching logic if the feature flag is enabled.
+        // Otherwise, use the existing AppCompat based implementation.
+        if (Flags.enableIntentMatchingFlags()) {
+            enforceIntentFilterMatchingWithIntentMatchingFlags(args, resolveInfos);
+        } else {
+            enforceIntentFilterMatchingWithAppCompat(args, resolveInfos);
+        }
+    }
+
+    /**
+     * This version of the method is implemented in Android B and uses "IntentMatchingFlags"
+     */
+    private static void enforceIntentFilterMatchingWithIntentMatchingFlags(
+                IntentArgs args, List<ResolveInfo> resolveInfos) {
         if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
 
         // Do not enforce filter matching when the caller is system or root
@@ -339,6 +370,97 @@
     }
 
     /**
+     * This version of the method is implemented in Android V and uses "AppCompat"
+     */
+    private static void enforceIntentFilterMatchingWithAppCompat(
+            IntentArgs args, List<ResolveInfo> resolveInfos) {
+        if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
+
+        // Do not enforce filter matching when the caller is system or root
+        if (ActivityManager.canAccessUnexportedComponents(args.callingUid)) return;
+
+        final Computer computer = (Computer) args.snapshot;
+        final ComponentResolverApi resolver = computer.getComponentResolver();
+
+        final Printer logPrinter = DEBUG_INTENT_MATCHING
+                ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
+                : null;
+
+        final boolean enforceMatch = Flags.enforceIntentFilterMatch()
+                && args.isChangeEnabled(ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS);
+        final boolean blockNullAction = Flags.blockNullActionIntents()
+                && args.isChangeEnabled(IntentFilter.BLOCK_NULL_ACTION_INTENTS);
+
+        for (int i = resolveInfos.size() - 1; i >= 0; --i) {
+            final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
+
+            // Skip filter matching when the caller is targeting the same app
+            if (UserHandle.isSameApp(args.callingUid, info.applicationInfo.uid)) {
+                continue;
+            }
+
+            final ParsedMainComponent comp = infoToComponent(info, resolver, args.isReceiver);
+
+            if (comp == null || comp.getIntents().isEmpty()) {
+                continue;
+            }
+
+            Boolean match = null;
+
+            if (args.intent.getAction() == null) {
+                args.reportEvent(
+                        UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH,
+                        enforceMatch && blockNullAction);
+                if (blockNullAction) {
+                    // Skip intent filter matching if blocking null action
+                    match = false;
+                }
+            }
+
+            if (match == null) {
+                // Check if any intent filter matches
+                for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
+                    IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
+                    if (IntentResolver.intentMatchesFilter(
+                            intentFilter, args.intent, args.resolvedType)) {
+                        match = true;
+                        break;
+                    }
+                }
+            }
+
+            // At this point, the value `match` has the following states:
+            // null : Intent does not match any intent filter
+            // false: Null action intent detected AND blockNullAction == true
+            // true : The intent matches at least one intent filter
+
+            if (match == null) {
+                args.reportEvent(
+                        UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH,
+                        enforceMatch);
+                match = false;
+            }
+
+            if (!match) {
+                // All non-matching intents has to be marked accordingly
+                if (Flags.enforceIntentFilterMatch()) {
+                    args.intent.addExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+                }
+                if (enforceMatch) {
+                    Slog.w(TAG, "Intent does not match component's intent filter: " + args.intent);
+                    Slog.w(TAG, "Access blocked: " + comp.getComponentName());
+                    if (DEBUG_INTENT_MATCHING) {
+                        Slog.v(TAG, "Component intent filters:");
+                        comp.getIntents().forEach(f -> f.getIntentFilter().dump(logPrinter, "  "));
+                        Slog.v(TAG, "-----------------------------");
+                    }
+                    resolveInfos.remove(i);
+                }
+            }
+        }
+    }
+
+    /**
      * Filter non-exported components from the componentList if intent is implicit.
      * <p>
      * Implicit intents cannot be used to start Services since API 21+.
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 0802e9e..5c80420 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -52,6 +52,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.PackageManager;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
@@ -63,6 +64,8 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.system.Os;
+import android.system.OsConstants;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -72,7 +75,6 @@
 import android.util.apk.ApkSignatureVerifier;
 import android.util.jar.StrictJarFile;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ComponentMutateUtils;
@@ -105,6 +107,9 @@
  * Helper class that handles package scanning logic
  */
 final class ScanPackageUtils {
+
+    public static final int PAGE_SIZE_16KB = 16384;
+
     /**
      * Just scans the package without any side effects.
      *
@@ -114,10 +119,9 @@
      * @param currentTime The current time, in millis
      * @return The results of the scan
      */
-    @GuardedBy("mPm.mInstallLock")
     @VisibleForTesting
     @NonNull
-    public static ScanResult scanPackageOnlyLI(@NonNull ScanRequest request,
+    public static ScanResult scanPackageOnly(@NonNull ScanRequest request,
             PackageManagerServiceInjector injector,
             boolean isUnderFactoryTest, long currentTime)
             throws PackageManagerException {
@@ -418,6 +422,37 @@
                     + " abiOverride=" + pkgSetting.getCpuAbiOverride());
         }
 
+        boolean is16KbDevice = Os.sysconf(OsConstants._SC_PAGESIZE) == PAGE_SIZE_16KB;
+        if (Flags.appCompatOption16kb() && is16KbDevice) {
+            // Alignment checks are used decide whether this app should run in compat mode when
+            // nothing was specified in manifest. Manifest should always take precedence over
+            // something decided by platform.
+            if (parsedPackage.getPageSizeAppCompatFlags()
+                    > ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) {
+                pkgSetting.setPageSizeAppCompatFlags(parsedPackage.getPageSizeAppCompatFlags());
+            } else {
+                // 16 KB is only support for 64 bit ABIs and for apps which are being installed
+                // Check alignment. System, Apex and Platform packages should be page-agnostic now
+                if ((Build.SUPPORTED_64_BIT_ABIS.length > 0)
+                        && !isSystemApp
+                        && !isApex
+                        && !isPlatformPackage) {
+                    int mode =
+                            packageAbiHelper.checkPackageAlignment(
+                                    parsedPackage,
+                                    pkgSetting.getLegacyNativeLibraryPath(),
+                                    parsedPackage.isNativeLibraryRootRequiresIsa(),
+                                    pkgSetting.getCpuAbiOverride());
+                    if (mode >= ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) {
+                        pkgSetting.setPageSizeAppCompatFlags(mode);
+                    } else {
+                        Slog.e(TAG, "Error occurred while checking alignment of package : "
+                                + parsedPackage.getPackageName());
+                    }
+                }
+            }
+        }
+
         if ((scanFlags & SCAN_BOOTING) == 0 && oldSharedUserSetting != null) {
             // We don't do this here during boot because we can do it all
             // at once after scanning all existing packages.
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1f672a0..485a280 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3313,6 +3313,11 @@
         if (pkg.getBaseRevisionCode() != 0) {
             serializer.attributeInt(null, "baseRevisionCode", pkg.getBaseRevisionCode());
         }
+        if (pkg.getPageSizeAppCompatFlags()
+                != ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) {
+            serializer.attributeInt(null, "pageSizeCompat", pkg.getPageSizeAppCompatFlags());
+        }
+
         serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
         serializer.attributeLongHex(null, "loadingCompletedTime", pkg.getLoadingCompletedTime());
 
@@ -4129,6 +4134,7 @@
         boolean isScannedAsStoppedSystemApp = false;
         boolean isSdkLibrary = false;
         int baseRevisionCode = 0;
+        int PageSizeCompat = 0;
         try {
             name = parser.getAttributeValue(null, ATTR_NAME);
             realName = parser.getAttributeValue(null, "realName");
@@ -4175,6 +4181,8 @@
             appMetadataSource = parser.getAttributeInt(null, "appMetadataSource",
                     PackageManager.APP_METADATA_SOURCE_UNKNOWN);
             baseRevisionCode = parser.getAttributeInt(null, "baseRevisionCode", 0);
+            PageSizeCompat = parser.getAttributeInt(null, "pageSizeCompat",
+                    ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED);
 
             isScannedAsStoppedSystemApp = parser.getAttributeBoolean(null,
                 "scannedAsStoppedSystemApp", false);
@@ -4330,7 +4338,8 @@
                     .setTargetSdkVersion(targetSdkVersion)
                     .setBaseRevisionCode(baseRevisionCode)
                     .setRestrictUpdateHash(restrictUpdateHash)
-                    .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp);
+                    .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp)
+                    .setPageSizeAppCompatFlags(PageSizeCompat);
             // Handle legacy string here for single-user mode
             final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
             if (enabledStr != null) {
@@ -5211,6 +5220,10 @@
                 pw.print(" (override=true)");
             }
             pw.println();
+            pw.print(prefix);
+            pw.print("  pageSizeCompat=");
+            pw.print(ps.getPageSizeAppCompatFlags());
+            pw.println();
             if (!ps.getPkg().getQueriesPackages().isEmpty()) {
                 pw.append(prefix).append("  queriesPackages=")
                         .println(ps.getPkg().getQueriesPackages());
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b2b8aaf..066fce0 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -6122,8 +6122,11 @@
             // If the user switch hasn't been explicitly toggled on or off by the user, turn it on.
             if (android.provider.Settings.Global.getString(mContext.getContentResolver(),
                     android.provider.Settings.Global.USER_SWITCHER_ENABLED) == null) {
-                android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                        android.provider.Settings.Global.USER_SWITCHER_ENABLED, 1);
+                if (Resources.getSystem().getBoolean(
+                        com.android.internal.R.bool.config_enableUserSwitcherUponUserCreation)) {
+                    android.provider.Settings.Global.putInt(mContext.getContentResolver(),
+                            android.provider.Settings.Global.USER_SWITCHER_ENABLED, 1);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index f7eb29f..542ae8e 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -28,7 +28,6 @@
 import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
-import static com.android.server.pm.PackageManagerService.CHECK_PENDING_INTEGRITY_VERIFICATION;
 import static com.android.server.pm.PackageManagerService.CHECK_PENDING_VERIFICATION;
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
 import static com.android.server.pm.PackageManagerService.DEBUG_VERIFY;
@@ -87,11 +86,6 @@
      * Whether verification is enabled by default.
      */
     private static final boolean DEFAULT_VERIFY_ENABLE = true;
-
-    /**
-     * Whether integrity verification is enabled by default.
-     */
-    private static final boolean DEFAULT_INTEGRITY_VERIFY_ENABLE = true;
     /**
      * The default maximum time to wait for the integrity verification to return in
      * milliseconds.
@@ -129,7 +123,6 @@
     private final boolean mUserActionRequired;
     private final int mUserActionRequiredType;
     private boolean mWaitForVerificationToComplete;
-    private boolean mWaitForIntegrityVerificationToComplete;
     private boolean mWaitForEnableRollbackToComplete;
     private int mRet = PackageManager.INSTALL_SUCCEEDED;
     private String mErrorMessage = null;
@@ -217,7 +210,6 @@
                 new PackageVerificationState(this);
         mPm.mPendingVerification.append(verificationId, verificationState);
 
-        sendIntegrityVerificationRequest(verificationId, pkgLite, verificationState);
         sendPackageVerificationRequest(
                 verificationId, pkgLite, verificationState);
 
@@ -270,89 +262,6 @@
         mPm.mHandler.sendMessageDelayed(msg, rollbackTimeout);
     }
 
-    /**
-     * Send a request to check the integrity of the package.
-     */
-    void sendIntegrityVerificationRequest(
-            int verificationId,
-            PackageInfoLite pkgLite,
-            PackageVerificationState verificationState) {
-        if (!isIntegrityVerificationEnabled()) {
-            // Consider the integrity check as passed.
-            verificationState.setIntegrityVerificationResult(
-                    PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
-            return;
-        }
-
-        final Intent integrityVerification =
-                new Intent(Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
-
-        integrityVerification.setDataAndType(Uri.fromFile(new File(mOriginInfo.mResolvedPath)),
-                PACKAGE_MIME_TYPE);
-
-        final int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
-                | Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                | Intent.FLAG_RECEIVER_FOREGROUND;
-        integrityVerification.addFlags(flags);
-
-        integrityVerification.putExtra(EXTRA_VERIFICATION_ID, verificationId);
-        integrityVerification.putExtra(EXTRA_PACKAGE_NAME, pkgLite.packageName);
-        integrityVerification.putExtra(EXTRA_VERSION_CODE, pkgLite.versionCode);
-        integrityVerification.putExtra(EXTRA_LONG_VERSION_CODE, pkgLite.getLongVersionCode());
-        populateInstallerExtras(integrityVerification);
-
-        // send to integrity component only.
-        integrityVerification.setPackage("android");
-
-        final BroadcastOptions options = BroadcastOptions.makeBasic();
-
-        mPm.mContext.sendOrderedBroadcastAsUser(integrityVerification, UserHandle.SYSTEM,
-                /* receiverPermission= */ null,
-                /* appOp= */ AppOpsManager.OP_NONE,
-                /* options= */ options.toBundle(),
-                new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        final Message msg =
-                                mPm.mHandler.obtainMessage(CHECK_PENDING_INTEGRITY_VERIFICATION);
-                        msg.arg1 = verificationId;
-                        mPm.mHandler.sendMessageDelayed(msg, getIntegrityVerificationTimeout());
-                    }
-                }, /* scheduler= */ null,
-                /* initialCode= */ 0,
-                /* initialData= */ null,
-                /* initialExtras= */ null);
-
-        Trace.asyncTraceBegin(
-                TRACE_TAG_PACKAGE_MANAGER, "integrity_verification", verificationId);
-
-        // stop the copy until verification succeeds.
-        mWaitForIntegrityVerificationToComplete = true;
-    }
-
-
-    /**
-     * Get the integrity verification timeout.
-     *
-     * @return verification timeout in milliseconds
-     */
-    private long getIntegrityVerificationTimeout() {
-        long timeout = Settings.Global.getLong(mPm.mContext.getContentResolver(),
-                Settings.Global.APP_INTEGRITY_VERIFICATION_TIMEOUT,
-                DEFAULT_INTEGRITY_VERIFICATION_TIMEOUT);
-        // The setting can be used to increase the timeout but not decrease it, since that is
-        // equivalent to disabling the integrity component.
-        return Math.max(timeout, DEFAULT_INTEGRITY_VERIFICATION_TIMEOUT);
-    }
-
-    /**
-     * Check whether or not integrity verification has been enabled.
-     */
-    private boolean isIntegrityVerificationEnabled() {
-        // We are not exposing this as a user-configurable setting because we don't want to provide
-        // an easy way to get around the integrity check.
-        return DEFAULT_INTEGRITY_VERIFY_ENABLE;
-    }
 
     /**
      * Send a request to verifier(s) to verify the package if necessary.
@@ -827,11 +736,6 @@
         handleReturnCode();
     }
 
-    void handleIntegrityVerificationFinished() {
-        mWaitForIntegrityVerificationToComplete = false;
-        handleReturnCode();
-    }
-
     void handleRollbackEnabled() {
         // TODO(b/112431924): Consider halting the install if we
         // couldn't enable rollback.
@@ -840,7 +744,7 @@
     }
 
     void handleReturnCode() {
-        if (mWaitForVerificationToComplete || mWaitForIntegrityVerificationToComplete
+        if (mWaitForVerificationToComplete
                 || mWaitForEnableRollbackToComplete) {
             return;
         }
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index e49dc82..976999c 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -426,6 +426,7 @@
     private static final int TRON_COMPILATION_REASON_PREBUILT = 23;
     private static final int TRON_COMPILATION_REASON_VDEX = 24;
     private static final int TRON_COMPILATION_REASON_BOOT_AFTER_MAINLINE_UPDATE = 25;
+    private static final int TRON_COMPILATION_REASON_CLOUD = 26;
 
     // The annotation to add as a suffix to the compilation reason when dexopt was
     // performed with dex metadata.
@@ -460,6 +461,8 @@
                 return TRON_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED;
             case "install-bulk-secondary-downgraded" :
                 return TRON_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
+            case "cloud":
+                return TRON_COMPILATION_REASON_CLOUD;
             // These are special markers for dex metadata installation that do not
             // have an equivalent as a system property.
             case "install" + DEXOPT_REASON_WITH_DEX_METADATA_ANNOTATION :
diff --git a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
index e9cb279..e989d68 100644
--- a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
+++ b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
@@ -40,7 +40,7 @@
 import com.android.internal.util.function.DodecFunction;
 import com.android.internal.util.function.HexConsumer;
 import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.OctFunction;
+import com.android.internal.util.function.NonaFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.TriFunction;
 import com.android.internal.util.function.UndecFunction;
@@ -351,22 +351,22 @@
         @Override
         public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
                 @Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
-                @Nullable String message, boolean shouldCollectMessage,
-                @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String,
-                        Boolean, SyncNotedAppOp> superImpl) {
+                @Nullable String message, boolean shouldCollectMessage, int notedCount,
+                @NonNull NonaFunction<Integer, Integer, String, String, Integer, Boolean, String,
+                                        Boolean, Integer, SyncNotedAppOp> superImpl) {
             if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
                         Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     return superImpl.apply(code, shellUid, SHELL_PKG, featureId, virtualDeviceId,
-                            shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                            shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
             return superImpl.apply(code, uid, packageName, featureId, virtualDeviceId,
-                    shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                    shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index bbc17c8..33fc066 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -22,6 +22,7 @@
 import android.annotation.Size;
 import android.annotation.SystemApi;
 import android.annotation.UserIdInt;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -198,6 +199,21 @@
     int getCategoryOverride();
 
     /**
+     * Returns true if ELF files will be loaded in Page size compatibility mode
+     *
+     * @hide
+     */
+    boolean isPageSizeAppCompatEnabled();
+
+    /**
+     * Returns dialog string based on alignment of uncompressed shared libs inside the APK and ELF
+     * alignment.
+     *
+     * @hide
+     */
+    String getPageSizeCompatWarningMessage(Context context);
+
+    /**
      * The install time CPU override, if any. This value is written at install time
      * and doesn't change during the life of an install. If non-null,
      * {@link #getPrimaryCpuAbiLegacy()} will also contain the same value.
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 253eb40..a46c4a6 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -257,6 +257,16 @@
 
         @NonNull
         @Override
+        public PackageStateWrite setPageSizeAppCompatFlags(
+                @ApplicationInfo.PageSizeAppCompatFlags int mode) {
+            if (mState != null) {
+                mState.setPageSizeAppCompatFlags(mode);
+            }
+            return this;
+        }
+
+        @NonNull
+        @Override
         public PackageStateWrite setUpdateAvailable(boolean updateAvailable) {
             if (mState != null) {
                 mState.setUpdateAvailable(updateAvailable);
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
index 55d96f3..f8f8695 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
@@ -46,6 +46,10 @@
     @NonNull
     PackageStateWrite setCategoryOverride(@ApplicationInfo.Category int category);
 
+    /** set 16Kb App compat mode. @see ApplicationInfo.PageSizeAppCompatFlags */
+    @NonNull
+    PackageStateWrite setPageSizeAppCompatFlags(@ApplicationInfo.PageSizeAppCompatFlags int mode);
+
     @NonNull
     PackageStateWrite setUpdateAvailable(boolean updateAvailable);
 
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index ecffd38..33210e2 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -53,7 +53,7 @@
 import com.android.internal.util.function.DodecFunction;
 import com.android.internal.util.function.HexConsumer;
 import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.OctFunction;
+import com.android.internal.util.function.NonaFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.UndecFunction;
 import com.android.server.LocalServices;
@@ -246,11 +246,12 @@
     public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
             @Nullable String attributionTag, int virtualDeviceId,
             boolean shouldCollectAsyncNotedOp, @Nullable String message,
-            boolean shouldCollectMessage, @NonNull OctFunction<Integer, Integer, String, String,
-                    Integer, Boolean, String, Boolean, SyncNotedAppOp> superImpl) {
+            boolean shouldCollectMessage, int notedCount,
+            @NonNull NonaFunction<Integer, Integer, String, String,
+                    Integer, Boolean, String, Boolean, Integer, SyncNotedAppOp> superImpl) {
         return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag),
                 resolveUid(code, uid), packageName, attributionTag, virtualDeviceId,
-                shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8e73023..7c4d425 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4426,9 +4426,9 @@
         int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
 
         float minLinearBrightness = mPowerManager.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+                screenDisplayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
         float maxLinearBrightness = mPowerManager.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
+                screenDisplayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
         float linearBrightness = mDisplayManager.getBrightness(screenDisplayId);
 
         float gammaBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness);
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index c573293..36bc0b9 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -6099,16 +6099,28 @@
             }
         }
 
-        public float getBrightnessConstraint(int constraint) {
+        @Override
+        public float getBrightnessConstraint(
+                int displayId, @PowerManager.BrightnessConstraint int constraint) {
+            DisplayInfo info = null;
+            if (android.companion.virtualdevice.flags.Flags.displayPowerManagerApis()
+                    && mDisplayManagerInternal != null) {
+                info = mDisplayManagerInternal.getDisplayInfo(displayId);
+            }
             switch (constraint) {
                 case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM:
-                    return mScreenBrightnessMinimum;
+                    return info != null && isValidBrightnessValue(info.brightnessMinimum)
+                            ? info.brightnessMinimum : mScreenBrightnessMinimum;
                 case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM:
-                    return mScreenBrightnessMaximum;
+                    return info != null && isValidBrightnessValue(info.brightnessMaximum)
+                            ? info.brightnessMaximum : mScreenBrightnessMaximum;
                 case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT:
-                    return mScreenBrightnessDefault;
+                    return info != null && isValidBrightnessValue(info.brightnessDefault)
+                            ? info.brightnessDefault : mScreenBrightnessDefault;
                 case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM:
-                    return mScreenBrightnessDim;
+                    return android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
+                            && info != null && isValidBrightnessValue(info.brightnessDim)
+                            ? info.brightnessDim : mScreenBrightnessDim;
                 default:
                     return PowerManager.BRIGHTNESS_INVALID_FLOAT;
             }
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 71f67d8..987a849 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -23,6 +23,7 @@
 import static com.android.server.power.hint.Flags.powerhintThreadCleanup;
 import static com.android.server.power.hint.Flags.resetOnForkEnabled;
 
+import android.adpf.ISessionManager;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -34,10 +35,13 @@
 import android.content.pm.PackageManager;
 import android.hardware.power.ChannelConfig;
 import android.hardware.power.CpuHeadroomParams;
+import android.hardware.power.CpuHeadroomResult;
 import android.hardware.power.GpuHeadroomParams;
+import android.hardware.power.GpuHeadroomResult;
 import android.hardware.power.IPower;
 import android.hardware.power.SessionConfig;
 import android.hardware.power.SessionTag;
+import android.hardware.power.SupportInfo;
 import android.hardware.power.WorkDuration;
 import android.os.Binder;
 import android.os.CpuHeadroomParamsInternal;
@@ -79,6 +83,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
@@ -96,10 +101,9 @@
 
     private static final int EVENT_CLEAN_UP_UID = 3;
     @VisibleForTesting  static final int CLEAN_UP_UID_DELAY_MILLIS = 1000;
+    // The minimum interval between the headroom calls as rate limiting.
     private static final int DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS = 1000;
     private static final int DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS = 1000;
-    private static final int HEADROOM_INTERVAL_UNSUPPORTED = -1;
-    @VisibleForTesting static final int DEFAULT_HEADROOM_PID = -1;
 
 
     @VisibleForTesting final long mHintSessionPreferredRate;
@@ -177,6 +181,7 @@
 
     private final IPower mPowerHal;
     private int mPowerHalVersion;
+    private SupportInfo mSupportInfo = null;
     private final PackageManager mPackageManager;
 
     private boolean mUsesFmq;
@@ -184,72 +189,75 @@
     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";
     private static final String PROPERTY_USE_HAL_HEADROOMS = "persist.hms.use_hal_headrooms";
+    private static final String PROPERTY_CHECK_HEADROOM_TID = "persist.hms.check_headroom_tid";
 
     private Boolean mFMQUsesIntegratedEventFlag = false;
 
     private final Object mCpuHeadroomLock = new Object();
 
-    private static class CpuHeadroomCacheItem {
-        long mExpiredTimeMillis;
-        CpuHeadroomParamsInternal mParams;
-        float[] mHeadroom;
-        long mPid;
+    private ISessionManager mSessionManager;
 
-        CpuHeadroomCacheItem(long expiredTimeMillis, CpuHeadroomParamsInternal params,
-                float[] headroom, long pid) {
-            mExpiredTimeMillis = expiredTimeMillis;
-            mParams = params;
-            mPid = pid;
-            mHeadroom = headroom;
-        }
+    // this cache tracks the expiration time of the items and performs cleanup on lookup
+    private static class HeadroomCache<K, V> {
+        final List<HeadroomCacheItem> mItemList;
+        final Map<K, HeadroomCacheItem> mKeyItemMap;
+        final long mItemExpDurationMillis;
 
-        private boolean match(CpuHeadroomParamsInternal params, long pid) {
-            if (mParams == null && params == null) return true;
-            if (mParams != null) {
-                return mParams.equals(params) && pid == mPid;
+        class HeadroomCacheItem {
+            long mExpTime;
+            K mKey;
+            V mValue;
+
+            HeadroomCacheItem(K k, V v) {
+                mExpTime = System.currentTimeMillis() + mItemExpDurationMillis;
+                mKey = k;
+                mValue = v;
             }
-            return false;
+
+            boolean isExpired() {
+                return mExpTime <= System.currentTimeMillis();
+            }
         }
 
-        private boolean isExpired() {
-            return System.currentTimeMillis() > mExpiredTimeMillis;
+        void add(K key, V value) {
+            if (mKeyItemMap.containsKey(key)) {
+                final HeadroomCacheItem item = mKeyItemMap.get(key);
+                mItemList.remove(item);
+            }
+            final HeadroomCacheItem item = new HeadroomCacheItem(key, value);
+            mItemList.add(item);
+            mKeyItemMap.put(key, item);
+        }
+
+        V get(K key) {
+            while (!mItemList.isEmpty() && mItemList.getFirst().isExpired()) {
+                mKeyItemMap.remove(mItemList.removeFirst().mKey);
+            }
+            final HeadroomCacheItem item = mKeyItemMap.get(key);
+            if (item == null) {
+                return null;
+            }
+            return item.mValue;
+        }
+
+        HeadroomCache(int size, long expDurationMillis) {
+            mItemList = new LinkedList<>();
+            mKeyItemMap = new ArrayMap<>(size);
+            mItemExpDurationMillis = expDurationMillis;
         }
     }
 
     @GuardedBy("mCpuHeadroomLock")
-    private final List<CpuHeadroomCacheItem> mCpuHeadroomCache;
-    private final long mCpuHeadroomIntervalMillis;
+    private final HeadroomCache<CpuHeadroomParams, CpuHeadroomResult> mCpuHeadroomCache;
 
     private final Object mGpuHeadroomLock = new Object();
 
-    private static class GpuHeadroomCacheItem {
-        long mExpiredTimeMillis;
-        GpuHeadroomParamsInternal mParams;
-        float mHeadroom;
-
-        GpuHeadroomCacheItem(long expiredTimeMillis, GpuHeadroomParamsInternal params,
-                float headroom) {
-            mExpiredTimeMillis = expiredTimeMillis;
-            mParams = params;
-            mHeadroom = headroom;
-        }
-
-        private boolean match(GpuHeadroomParamsInternal params) {
-            if (mParams == null && params == null) return true;
-            if (mParams != null) {
-                return mParams.equals(params);
-            }
-            return false;
-        }
-
-        private boolean isExpired() {
-            return System.currentTimeMillis() > mExpiredTimeMillis;
-        }
-    }
-
     @GuardedBy("mGpuHeadroomLock")
-    private final List<GpuHeadroomCacheItem> mGpuHeadroomCache;
-    private final long mGpuHeadroomIntervalMillis;
+    private final HeadroomCache<GpuHeadroomParams, GpuHeadroomResult> mGpuHeadroomCache;
+
+    // these are set to default values in CpuHeadroomParamsInternal and GpuHeadroomParamsInternal
+    private final int mDefaultCpuHeadroomCalculationWindowMillis;
+    private final int mDefaultGpuHeadroomCalculationWindowMillis;
 
     @VisibleForTesting
     final IHintManager.Stub mService = new BinderService();
@@ -287,72 +295,40 @@
         mPowerHal = injector.createIPower();
         mPowerHalVersion = 0;
         mUsesFmq = false;
-        long cpuHeadroomIntervalMillis = HEADROOM_INTERVAL_UNSUPPORTED;
-        long gpuHeadroomIntervalMillis = HEADROOM_INTERVAL_UNSUPPORTED;
         if (mPowerHal != null) {
-            try {
-                mPowerHalVersion = mPowerHal.getInterfaceVersion();
-                if (mPowerHal.getInterfaceVersion() >= 6) {
-                    if (SystemProperties.getBoolean(PROPERTY_USE_HAL_HEADROOMS, true)) {
-                        cpuHeadroomIntervalMillis = checkCpuHeadroomSupport();
-                        gpuHeadroomIntervalMillis = checkGpuHeadroomSupport();
-                    }
-                }
-            } catch (RemoteException e) {
-                throw new IllegalStateException("Could not contact PowerHAL!", e);
-            }
+            mSupportInfo = getSupportInfo();
         }
-        mCpuHeadroomIntervalMillis = cpuHeadroomIntervalMillis;
-        mGpuHeadroomIntervalMillis = gpuHeadroomIntervalMillis;
-        if (mCpuHeadroomIntervalMillis > 0) {
-            mCpuHeadroomCache = new ArrayList<>(4);
+        mDefaultCpuHeadroomCalculationWindowMillis =
+                new CpuHeadroomParamsInternal().calculationWindowMillis;
+        mDefaultGpuHeadroomCalculationWindowMillis =
+                new GpuHeadroomParamsInternal().calculationWindowMillis;
+        if (mSupportInfo.headroom.isCpuSupported) {
+            mCpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.cpuMinIntervalMillis);
         } else {
             mCpuHeadroomCache = null;
         }
-        if (mGpuHeadroomIntervalMillis > 0) {
-            mGpuHeadroomCache = new ArrayList<>(2);
+        if (mSupportInfo.headroom.isGpuSupported) {
+            mGpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.gpuMinIntervalMillis);
         } else {
             mGpuHeadroomCache = null;
         }
     }
 
-    private long checkCpuHeadroomSupport() {
+    SupportInfo getSupportInfo() {
         try {
-            synchronized (mCpuHeadroomLock) {
-                final CpuHeadroomParams defaultParams = new CpuHeadroomParams();
-                defaultParams.pid = Process.myPid();
-                float[] ret = mPowerHal.getCpuHeadroom(defaultParams);
-                if (ret != null && ret.length > 0) {
-                    return Math.max(
-                            DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS,
-                            mPowerHal.getCpuHeadroomMinIntervalMillis());
-                }
+            mPowerHalVersion = mPowerHal.getInterfaceVersion();
+            if (mPowerHalVersion >= 6) {
+                return mPowerHal.getSupportInfo();
             }
-
-        } catch (UnsupportedOperationException e) {
-            Slog.w(TAG, "getCpuHeadroom HAL API is not supported", e);
         } catch (RemoteException e) {
-            Slog.e(TAG, "getCpuHeadroom HAL API fails, disabling the API", e);
+            throw new IllegalStateException("Could not contact PowerHAL!", e);
         }
-        return HEADROOM_INTERVAL_UNSUPPORTED;
-    }
 
-    private long checkGpuHeadroomSupport() {
-        try {
-            synchronized (mGpuHeadroomLock) {
-                float ret = mPowerHal.getGpuHeadroom(new GpuHeadroomParams());
-                if (!Float.isNaN(ret)) {
-                    return Math.max(
-                            DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS,
-                            mPowerHal.getGpuHeadroomMinIntervalMillis());
-                }
-            }
-        } catch (UnsupportedOperationException e) {
-            Slog.w(TAG, "getGpuHeadroom HAL API is not supported", e);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "getGpuHeadroom HAL API fails, disabling the API", e);
-        }
-        return HEADROOM_INTERVAL_UNSUPPORTED;
+        SupportInfo supportInfo = new SupportInfo();
+        supportInfo.headroom = new SupportInfo.HeadroomSupportInfo();
+        supportInfo.headroom.isCpuSupported = false;
+        supportInfo.headroom.isGpuSupported = false;
+        return supportInfo;
     }
 
     private ServiceThread createCleanUpThread() {
@@ -541,7 +517,7 @@
             return targetDurations;
         }
     }
-    private boolean isHalSupported() {
+    private boolean isHintSessionSupported() {
         return mHintSessionPreferredRate != -1;
     }
 
@@ -804,6 +780,23 @@
                     for (int i = tokenMap.size() - 1; i >= 0; i--) {
                         // Will remove the session from tokenMap
                         ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(i);
+                        IntArray closedSessionsForSf = new IntArray();
+                        // Batch the closure call to SF for all the sessions that die
+                        for (int j = sessionSet.size() - 1; j >= 0; j--) {
+                            AppHintSession session = sessionSet.valueAt(j);
+                            if (session.isTrackedBySf()) {
+                                // Mark it as untracked so we don't untrack again on close
+                                session.setTrackedBySf(false);
+                                closedSessionsForSf.add(session.getSessionId());
+                            }
+                        }
+                        if (mSessionManager != null) {
+                            try {
+                                mSessionManager.trackedSessionsDied(closedSessionsForSf.toArray());
+                            } catch (RemoteException e) {
+                                Slog.e(TAG, "Failed to communicate with SessionManager");
+                            }
+                        }
                         for (int j = sessionSet.size() - 1; j >= 0; j--) {
                             sessionSet.valueAt(j).close();
                         }
@@ -1234,7 +1227,7 @@
         public IHintSession createHintSessionWithConfig(@NonNull IBinder token,
                     @SessionTag int tag, SessionCreationConfig creationConfig,
                     SessionConfig config) {
-            if (!isHalSupported()) {
+            if (!isHintSessionSupported()) {
                 throw new UnsupportedOperationException("PowerHAL is not supported!");
             }
 
@@ -1336,9 +1329,9 @@
                     }
                 }
 
-                final long sessionId = config.id != -1 ? config.id : halSessionPtr;
+                final long sessionIdForTracing = config.id != -1 ? config.id : halSessionPtr;
                 logPerformanceHintSessionAtom(
-                        callingUid, sessionId, durationNanos, tids, tag);
+                        callingUid, sessionIdForTracing, durationNanos, tids, tag);
 
                 synchronized (mSessionSnapshotMapLock) {
                     // Update session snapshot upon session creation
@@ -1348,8 +1341,12 @@
                 }
                 AppHintSession hs = null;
                 synchronized (mLock) {
+                    Integer configId = null;
+                    if (config.id != -1) {
+                        configId = new Integer((int) config.id);
+                    }
                     hs = new AppHintSession(callingUid, callingTgid, tag, tids,
-                            token, halSessionPtr, durationNanos);
+                            token, halSessionPtr, durationNanos, configId);
                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
                             mActiveSessions.get(callingUid);
                     if (tokenMap == null) {
@@ -1376,6 +1373,11 @@
                         }
                     }
 
+                    if (creationConfig.layerTokens != null
+                            && creationConfig.layerTokens.length > 0) {
+                        hs.associateToLayers(creationConfig.layerTokens);
+                    }
+
                     synchronized (mThreadsUsageObject) {
                         mThreadsUsageMap.computeIfAbsent(callingUid, k -> new ArraySet<>());
                         ArraySet<ThreadUsageTracker> threadsSet = mThreadsUsageMap.get(callingUid);
@@ -1445,121 +1447,118 @@
         }
 
         @Override
-        public float[] getCpuHeadroom(@Nullable CpuHeadroomParamsInternal params) {
-            if (mCpuHeadroomIntervalMillis <= 0) {
+        public CpuHeadroomResult getCpuHeadroom(@NonNull CpuHeadroomParamsInternal params) {
+            if (!mSupportInfo.headroom.isCpuSupported) {
                 throw new UnsupportedOperationException();
             }
-            CpuHeadroomParams halParams = new CpuHeadroomParams();
-            halParams.pid = Binder.getCallingPid();
-            if (params != null) {
-                halParams.calculationType = params.calculationType;
-                halParams.selectionType = params.selectionType;
-                if (params.usesDeviceHeadroom) {
-                    halParams.pid = DEFAULT_HEADROOM_PID;
+            final CpuHeadroomParams halParams = new CpuHeadroomParams();
+            halParams.tids = new int[]{Binder.getCallingPid()};
+            halParams.calculationType = params.calculationType;
+            halParams.calculationWindowMillis = params.calculationWindowMillis;
+            if (params.usesDeviceHeadroom) {
+                halParams.tids = new int[]{};
+            } else if (params.tids != null && params.tids.length > 0) {
+                if (params.tids.length > 5) {
+                    throw new IllegalArgumentException(
+                            "More than 5 TIDs is requested: " + params.tids.length);
                 }
-            }
-            synchronized (mCpuHeadroomLock) {
-                while (!mCpuHeadroomCache.isEmpty()) {
-                    if (mCpuHeadroomCache.getFirst().isExpired()) {
-                        mCpuHeadroomCache.removeFirst();
-                    } else {
-                        break;
+                if (SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_TID, true)) {
+                    final int tgid = Process.getThreadGroupLeader(Binder.getCallingPid());
+                    for (int tid : params.tids) {
+                        if (Process.getThreadGroupLeader(tid) != tgid) {
+                            throw new SecurityException("TID " + tid
+                                    + " doesn't belong to the calling process with pid "
+                                    + tgid);
+                        }
                     }
                 }
-                for (int i = 0; i < mCpuHeadroomCache.size(); ++i) {
-                    final CpuHeadroomCacheItem item = mCpuHeadroomCache.get(i);
-                    if (item.match(params, halParams.pid)) {
-                        item.mExpiredTimeMillis =
-                                System.currentTimeMillis() + mCpuHeadroomIntervalMillis;
-                        mCpuHeadroomCache.remove(i);
-                        mCpuHeadroomCache.add(item);
-                        return item.mHeadroom;
-                    }
-                }
+                halParams.tids = params.tids;
             }
-            // return from HAL directly
-            try {
-                float[] headroom = mPowerHal.getCpuHeadroom(halParams);
-                if (headroom == null || headroom.length == 0) {
-                    Slog.wtf(TAG,
-                            "CPU headroom from Power HAL is invalid: " + Arrays.toString(headroom));
-                    return new float[]{Float.NaN};
-                }
+            if (halParams.calculationWindowMillis
+                    == mDefaultCpuHeadroomCalculationWindowMillis) {
                 synchronized (mCpuHeadroomLock) {
-                    mCpuHeadroomCache.add(new CpuHeadroomCacheItem(
-                            System.currentTimeMillis() + mCpuHeadroomIntervalMillis,
-                            params, headroom, halParams.pid
-                    ));
-                }
-                return headroom;
-
-            } catch (RemoteException e) {
-                return new float[]{Float.NaN};
-            }
-        }
-
-        @Override
-        public float getGpuHeadroom(@Nullable GpuHeadroomParamsInternal params) {
-            if (mGpuHeadroomIntervalMillis <= 0) {
-                throw new UnsupportedOperationException();
-            }
-            GpuHeadroomParams halParams = new GpuHeadroomParams();
-            if (params != null) {
-                halParams.calculationType = params.calculationType;
-            }
-            synchronized (mGpuHeadroomLock) {
-                while (!mGpuHeadroomCache.isEmpty()) {
-                    if (mGpuHeadroomCache.getFirst().isExpired()) {
-                        mGpuHeadroomCache.removeFirst();
-                    } else {
-                        break;
-                    }
-                }
-                for (int i = 0; i < mGpuHeadroomCache.size(); ++i) {
-                    final GpuHeadroomCacheItem item = mGpuHeadroomCache.get(i);
-                    if (item.match(params)) {
-                        item.mExpiredTimeMillis =
-                                System.currentTimeMillis() + mGpuHeadroomIntervalMillis;
-                        mGpuHeadroomCache.remove(i);
-                        mGpuHeadroomCache.add(item);
-                        return item.mHeadroom;
-                    }
+                    final CpuHeadroomResult res = mCpuHeadroomCache.get(halParams);
+                    if (res != null) return res;
                 }
             }
             // return from HAL directly
             try {
-                float headroom = mPowerHal.getGpuHeadroom(halParams);
-                if (Float.isNaN(headroom)) {
-                    Slog.wtf(TAG,
-                            "GPU headroom from Power HAL is NaN");
-                    return Float.NaN;
+                final CpuHeadroomResult result = mPowerHal.getCpuHeadroom(halParams);
+                if (result == null) {
+                    Slog.wtf(TAG, "CPU headroom from Power HAL is invalid");
+                    return null;
                 }
+                if (halParams.calculationWindowMillis
+                        == mDefaultCpuHeadroomCalculationWindowMillis) {
+                    synchronized (mCpuHeadroomLock) {
+                        mCpuHeadroomCache.add(halParams, result);
+                    }
+                }
+                return result;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to get CPU headroom from Power HAL", e);
+                return null;
+            }
+        }
+
+        @Override
+        public GpuHeadroomResult getGpuHeadroom(@NonNull GpuHeadroomParamsInternal params) {
+            if (!mSupportInfo.headroom.isGpuSupported) {
+                throw new UnsupportedOperationException();
+            }
+            final GpuHeadroomParams halParams = new GpuHeadroomParams();
+            halParams.calculationType = params.calculationType;
+            halParams.calculationWindowMillis = params.calculationWindowMillis;
+            if (halParams.calculationWindowMillis
+                    == mDefaultGpuHeadroomCalculationWindowMillis) {
                 synchronized (mGpuHeadroomLock) {
-                    mGpuHeadroomCache.add(new GpuHeadroomCacheItem(
-                            System.currentTimeMillis() + mGpuHeadroomIntervalMillis,
-                            params, headroom
-                    ));
+                    final GpuHeadroomResult res = mGpuHeadroomCache.get(halParams);
+                    if (res != null) return res;
+                }
+            }
+            // return from HAL directly
+            try {
+                final GpuHeadroomResult headroom = mPowerHal.getGpuHeadroom(halParams);
+                if (headroom == null) {
+                    Slog.wtf(TAG, "GPU headroom from Power HAL is invalid");
+                    return null;
+                }
+                if (halParams.calculationWindowMillis
+                        == mDefaultGpuHeadroomCalculationWindowMillis) {
+                    synchronized (mGpuHeadroomLock) {
+                        mGpuHeadroomCache.add(halParams, headroom);
+                    }
                 }
                 return headroom;
             } catch (RemoteException e) {
-                return Float.NaN;
+                Slog.e(TAG, "Failed to get GPU headroom from Power HAL", e);
+                return null;
             }
         }
 
         @Override
-        public long getCpuHeadroomMinIntervalMillis() throws RemoteException {
-            if (mCpuHeadroomIntervalMillis <= 0) {
+        public long getCpuHeadroomMinIntervalMillis() {
+            if (!mSupportInfo.headroom.isCpuSupported) {
                 throw new UnsupportedOperationException();
             }
-            return mCpuHeadroomIntervalMillis;
+            return mSupportInfo.headroom.cpuMinIntervalMillis;
         }
 
         @Override
-        public long getGpuHeadroomMinIntervalMillis() throws RemoteException {
-            if (mGpuHeadroomIntervalMillis <= 0) {
+        public long getGpuHeadroomMinIntervalMillis() {
+            if (!mSupportInfo.headroom.isGpuSupported) {
                 throw new UnsupportedOperationException();
             }
-            return mGpuHeadroomIntervalMillis;
+            return mSupportInfo.headroom.gpuMinIntervalMillis;
+        }
+
+        @Override
+        public void passSessionManagerBinder(IBinder sessionManager) {
+            // Ensure caller is internal
+            if (Process.myUid() != Binder.getCallingUid()) {
+                return;
+            }
+            mSessionManager = ISessionManager.Stub.asInterface(sessionManager);
         }
 
         @Override
@@ -1569,7 +1568,7 @@
             }
             pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate);
             pw.println("MaxGraphicsPipelineThreadsCount: " + MAX_GRAPHICS_PIPELINE_THREADS_COUNT);
-            pw.println("HAL Support: " + isHalSupported());
+            pw.println("HAL Support: " + isHintSessionSupported());
             pw.println("Active Sessions:");
             synchronized (mLock) {
                 for (int i = 0; i < mActiveSessions.size(); i++) {
@@ -1585,23 +1584,22 @@
                     }
                 }
             }
-            pw.println("CPU Headroom Interval: " + mCpuHeadroomIntervalMillis);
-            pw.println("GPU Headroom Interval: " + mGpuHeadroomIntervalMillis);
+            pw.println("CPU Headroom Interval: " + mSupportInfo.headroom.cpuMinIntervalMillis);
+            pw.println("GPU Headroom Interval: " + mSupportInfo.headroom.gpuMinIntervalMillis);
             try {
                 CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal();
-                params.selectionType = CpuHeadroomParams.SelectionType.ALL;
                 params.usesDeviceHeadroom = true;
-                pw.println("CPU headroom: " + Arrays.toString(getCpuHeadroom(params)));
-                params = new CpuHeadroomParamsInternal();
-                params.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
-                params.usesDeviceHeadroom = true;
-                pw.println("CPU headroom per core: " + Arrays.toString(getCpuHeadroom(params)));
+                CpuHeadroomResult ret = getCpuHeadroom(params);
+                pw.println("CPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom()));
             } catch (Exception e) {
+                Slog.d(TAG, "Failed to dump CPU headroom", e);
                 pw.println("CPU headroom: N/A");
             }
             try {
-                pw.println("GPU headroom: " + getGpuHeadroom(null));
+                GpuHeadroomResult ret = getGpuHeadroom(new GpuHeadroomParamsInternal());
+                pw.println("GPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom()));
             } catch (Exception e) {
+                Slog.d(TAG, "Failed to dump GPU headroom", e);
                 pw.println("GPU headroom: N/A");
             }
         }
@@ -1679,6 +1677,8 @@
         protected boolean mHasBeenPowerEfficient;
         protected boolean mHasBeenGraphicsPipeline;
         protected boolean mShouldForcePause;
+        protected Integer mSessionId;
+        protected boolean mTrackedBySF;
 
         enum SessionModes {
             POWER_EFFICIENCY,
@@ -1687,7 +1687,7 @@
 
         protected AppHintSession(
                 int uid, int pid, int sessionTag, int[] threadIds, IBinder token,
-                long halSessionPtr, long durationNanos) {
+                long halSessionPtr, long durationNanos, Integer sessionId) {
             mUid = uid;
             mPid = pid;
             mTag = sessionTag;
@@ -1701,6 +1701,8 @@
             mHasBeenPowerEfficient = false;
             mHasBeenGraphicsPipeline = false;
             mShouldForcePause = false;
+            mSessionId = sessionId;
+            mTrackedBySF = false;
             final boolean allowed = mUidObserver.isUidForeground(mUid);
             updateHintAllowedByProcState(allowed);
             try {
@@ -1790,6 +1792,19 @@
                 } catch (NoSuchElementException ignored) {
                     Slogf.d(TAG, "Death link does not exist for session with UID " + mUid);
                 }
+                if (mTrackedBySF) {
+                    if (mSessionManager != null) {
+                        try {
+                            mSessionManager.trackedSessionsDied(new int[]{mSessionId});
+                        } catch (RemoteException e) {
+                            throw new IllegalStateException(
+                                    "Could not communicate with SessionManager", e);
+                        }
+                        mTrackedBySF = false;
+                    } else {
+                        Slog.e(TAG, "SessionManager is null but there are tracked sessions");
+                    }
+                }
             }
             synchronized (mLock) {
                 ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
@@ -1866,6 +1881,24 @@
             }
         }
 
+        @Override
+        public void associateToLayers(IBinder[] layerTokens) {
+            synchronized (this) {
+                if (mSessionManager != null && mSessionId != null && layerTokens != null) {
+                    // Sf only untracks a session when it dies
+                    if (layerTokens.length > 0) {
+                        mTrackedBySF = true;
+                    }
+                    try {
+                        mSessionManager.associateSessionToLayers(mSessionId, mUid, layerTokens);
+                    } catch (RemoteException e) {
+                        throw new IllegalStateException(
+                                "Could not communicate with SessionManager", e);
+                    }
+                }
+            }
+        }
+
         public void setThreads(@NonNull int[] tids) {
             setThreadsInternal(tids, true);
         }
@@ -2115,10 +2148,27 @@
             return mUid;
         }
 
+        public boolean isTrackedBySf() {
+            synchronized (this) {
+                return mTrackedBySF;
+            }
+        }
+
+        public void setTrackedBySf(boolean tracked) {
+            synchronized (this) {
+                mTrackedBySF = tracked;
+            }
+        }
+
+
         public int getTag() {
             return mTag;
         }
 
+        public Integer getSessionId() {
+            return mSessionId;
+        }
+
         public long getTargetDurationNs() {
             synchronized (this) {
                 return mTargetDurationNanos;
diff --git a/services/core/java/com/android/server/power/hint/adpf_flags.aconfig b/services/core/java/com/android/server/power/hint/adpf_flags.aconfig
index 147d76b..97d3483 100644
--- a/services/core/java/com/android/server/power/hint/adpf_flags.aconfig
+++ b/services/core/java/com/android/server/power/hint/adpf_flags.aconfig
@@ -5,3 +5,11 @@
 package: "android.adpf"
 container: "system"
 
+flag {
+    name: "adpf_viewrootimpl_action_down_boost"
+    is_exported: true
+    namespace: "game"
+    description: "Guards boosting on touch in ViewRootImpl."
+    is_fixed_read_only: true
+    bug: "360345939"
+}
diff --git a/services/core/java/com/android/server/power/hint/flags.aconfig b/services/core/java/com/android/server/power/hint/flags.aconfig
index e56b68c..c231f5a 100644
--- a/services/core/java/com/android/server/power/hint/flags.aconfig
+++ b/services/core/java/com/android/server/power/hint/flags.aconfig
@@ -21,3 +21,10 @@
     description: "Set reset_on_fork flag."
     bug: "370988407"
 }
+
+flag {
+    name: "cpu_headroom_affinity_check"
+    namespace: "game"
+    description: "Check affinity on CPU headroom."
+    bug: "346604998"
+}
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 028ac57..6f18107 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -409,11 +409,10 @@
                 @Override
                 public long getWakelockDurationMillis() {
                     synchronized (BatteryStatsImpl.this) {
-                        long rawRealtimeUs = mClock.uptimeMillis() * 1000;
-                        long batteryUptimeUs = getBatteryUptime(rawRealtimeUs);
-                        long screenOnTimeUs = getScreenOnTime(rawRealtimeUs,
+                        long batteryUptimeUs = getBatteryUptime(mClock.uptimeMillis() * 1000);
+                        long screenOnTimeUs = getScreenOnTime(mClock.elapsedRealtime() * 1000,
                                 BatteryStats.STATS_SINCE_CHARGED);
-                        return (batteryUptimeUs - screenOnTimeUs) / 1000;
+                        return Math.max(0, (batteryUptimeUs - screenOnTimeUs) / 1000);
                     }
                 }
 
@@ -437,8 +436,9 @@
                                 }
                             }
 
-                            if (wakeLockTimeUs != 0) {
-                                callback.onUidWakelockDuration(u.getUid(), wakeLockTimeUs / 1000);
+                            long wakelockTimeMs = wakeLockTimeUs / 1000;
+                            if (wakelockTimeMs != 0) {
+                                callback.onUidWakelockDuration(u.getUid(), wakelockTimeMs);
                             }
                         }
                     }
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 606bd1d..63e8d99 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.Process;
 import android.util.Log;
+import android.util.LogWriter;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -34,6 +35,7 @@
 import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -44,6 +46,8 @@
  */
 public class BatteryUsageStatsProvider {
     private static final String TAG = "BatteryUsageStatsProv";
+    private static final boolean DEBUG = false;
+
     private final Context mContext;
     private final PowerAttributor mPowerAttributor;
     private final PowerStatsStore mPowerStatsStore;
@@ -262,17 +266,25 @@
 
     private BatteryUsageStats getBatteryUsageStats(BatteryStatsImpl stats,
             BatteryUsageStatsQuery query, long currentTimeMs) {
+        BatteryUsageStats batteryUsageStats;
         if ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_ACCUMULATED) != 0) {
-            return getAccumulatedBatteryUsageStats(stats, query, currentTimeMs);
+            batteryUsageStats = getAccumulatedBatteryUsageStats(stats, query, currentTimeMs);
         } else if (query.getAggregatedToTimestamp() == 0) {
             BatteryUsageStats.Builder builder = computeBatteryUsageStats(stats, query,
                     query.getMonotonicStartTime(),
                     query.getMonotonicEndTime(), currentTimeMs);
-            return builder.build();
+            batteryUsageStats = builder.build();
         } else {
-            return getAggregatedBatteryUsageStats(stats, query);
+            batteryUsageStats = getAggregatedBatteryUsageStats(stats, query);
         }
+        if (DEBUG) {
+            Slog.d(TAG, "query = " + query);
+            PrintWriter pw = new PrintWriter(new LogWriter(Log.DEBUG, TAG));
+            batteryUsageStats.dump(pw, "");
+            pw.flush();
+        }
+        return batteryUsageStats;
     }
 
     private BatteryUsageStats getAccumulatedBatteryUsageStats(BatteryStatsImpl stats,
@@ -319,7 +331,7 @@
 
         if (accumulatedStats.builder == null) {
             accumulatedStats.builder = new BatteryUsageStats.Builder(
-                    stats.getCustomEnergyConsumerNames(), false, true, true, true, 0);
+                    stats.getCustomEnergyConsumerNames(), true, true, true, 0);
             accumulatedStats.startWallClockTime = stats.getStartClockTime();
             accumulatedStats.builder.setStatsStartTimestamp(accumulatedStats.startWallClockTime);
         }
@@ -338,8 +350,6 @@
     private BatteryUsageStats.Builder computeBatteryUsageStats(BatteryStatsImpl stats,
             BatteryUsageStatsQuery query, long monotonicStartTime, long monotonicEndTime,
             long currentTimeMs) {
-        final boolean includePowerModels = (query.getFlags()
-                & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
         final boolean includeProcessStateData = ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
                 && stats.isProcessStateDataAvailable();
@@ -353,8 +363,7 @@
         }
 
         final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
-                customEnergyConsumerNames, includePowerModels,
-                includeProcessStateData, query.isScreenStateDataNeeded(),
+                customEnergyConsumerNames, includeProcessStateData, query.isScreenStateDataNeeded(),
                 query.isPowerStateDataNeeded(), minConsumedPowerThreshold);
 
         synchronized (stats) {
@@ -444,8 +453,6 @@
 
     private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryStatsImpl stats,
             BatteryUsageStatsQuery query) {
-        final boolean includePowerModels = (query.getFlags()
-                & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
         final boolean includeProcessStateData = ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
                 && stats.isProcessStateDataAvailable();
@@ -453,7 +460,7 @@
 
         final String[] customEnergyConsumerNames = stats.getCustomEnergyConsumerNames();
         final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
-                customEnergyConsumerNames, includePowerModels, includeProcessStateData,
+                customEnergyConsumerNames, includeProcessStateData,
                 query.isScreenStateDataNeeded(), query.isPowerStateDataNeeded(),
                 minConsumedPowerThreshold);
         if (mPowerStatsStore == null) {
diff --git a/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java
index e36c994..e3e4e1b 100644
--- a/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/WakelockPowerStatsCollector.java
@@ -108,14 +108,16 @@
 
         mWakelockDurationRetriever.retrieveUidWakelockDuration((uid, durationMs) -> {
             if (!mFirstCollection) {
-                long[] uidStats = mPowerStats.uidStats.get(uid);
-                if (uidStats == null) {
-                    uidStats = new long[mDescriptor.uidStatsArrayLength];
-                    mPowerStats.uidStats.put(uid, uidStats);
-                }
+                long diffMs = Math.max(0, durationMs - mLastUidWakelockDurations.get(uid));
+                if (diffMs != 0) {
+                    long[] uidStats = mPowerStats.uidStats.get(uid);
+                    if (uidStats == null) {
+                        uidStats = new long[mDescriptor.uidStatsArrayLength];
+                        mPowerStats.uidStats.put(uid, uidStats);
+                    }
 
-                mStatsLayout.setUidUsageDuration(uidStats,
-                        Math.max(0, durationMs - mLastUidWakelockDurations.get(uid)));
+                    mStatsLayout.setUidUsageDuration(uidStats, diffMs);
+                }
             }
             mLastUidWakelockDurations.put(uid, durationMs);
         });
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index ce6f57f..5e04881 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -29,6 +29,7 @@
     namespace: "backstage_power"
     description: "Feature flag for streamlined connectivity battery stats"
     bug: "323970018"
+    is_exported: true
 }
 
 flag {
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
index b2442c8..ef0e63b 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
@@ -224,13 +224,12 @@
         BatteryConsumer.Key key = getKeyForPartialTotal(batteryUsageStatsBuilder, deviceScope,
                 powerComponentId, screenState, powerState);
         if (key != null) {
-            deviceScope.addConsumedPower(key, totalPower[0],
-                    BatteryConsumer.POWER_MODEL_UNDEFINED);
+            deviceScope.addConsumedPower(key, totalPower[0]);
             deviceScope.addUsageDurationMillis(key, durationMs[0]);
         }
         key = deviceScope.getKey(powerComponentId, BatteryConsumer.PROCESS_STATE_UNSPECIFIED);
         if (key != null) {
-            deviceScope.addConsumedPower(key, totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED);
+            deviceScope.addConsumedPower(key, totalPower[0]);
             deviceScope.addUsageDurationMillis(key, durationMs[0]);
         }
     }
@@ -373,7 +372,7 @@
                 BatteryConsumer.Key key = builder.getKey(powerComponentId, procState,
                         resultScreenState, resultPowerState);
                 if (key != null) {
-                    builder.addConsumedPower(key, power, BatteryConsumer.POWER_MODEL_UNDEFINED);
+                    builder.addConsumedPower(key, power);
                     builder.addUsageDurationMillis(key, duration);
                 }
             }
@@ -384,8 +383,7 @@
                         BatteryConsumer.PROCESS_STATE_UNSPECIFIED);
                 if (key != null) {
                     builder.addConsumedPower(key,
-                            powerByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED],
-                            BatteryConsumer.POWER_MODEL_UNDEFINED);
+                            powerByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED]);
                     builder.addUsageDurationMillis(key,
                             durationByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED]);
                 }
@@ -399,11 +397,9 @@
         BatteryConsumer.Key key = getKeyForPartialTotal(batteryUsageStatsBuilder, allAppsScope,
                 powerComponentId, screenState, powerState);
         if (key != null) {
-            allAppsScope.addConsumedPower(key, powerAllApps,
-                    BatteryConsumer.POWER_MODEL_UNDEFINED);
+            allAppsScope.addConsumedPower(key, powerAllApps);
         }
-        allAppsScope.addConsumedPower(powerComponentId, powerAllApps,
-                BatteryConsumer.POWER_MODEL_UNDEFINED);
+        allAppsScope.addConsumedPower(powerComponentId, powerAllApps);
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
index e8723b9..7fb5708 100644
--- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
+++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
@@ -46,7 +46,9 @@
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.security.advancedprotection.features.AdvancedProtectionHook;
 import com.android.server.security.advancedprotection.features.AdvancedProtectionProvider;
+import com.android.server.security.advancedprotection.features.DisallowCellular2GAdvancedProtectionHook;
 import com.android.server.security.advancedprotection.features.DisallowInstallUnknownSourcesAdvancedProtectionHook;
+import com.android.server.security.advancedprotection.features.MemoryTaggingExtensionHook;
 
 import java.io.FileDescriptor;
 import java.util.ArrayList;
@@ -78,7 +80,25 @@
 
     private void initFeatures(boolean enabled) {
         if (android.security.Flags.aapmFeatureDisableInstallUnknownSources()) {
+          try {
             mHooks.add(new DisallowInstallUnknownSourcesAdvancedProtectionHook(mContext, enabled));
+          } catch (Exception e) {
+            Slog.e(TAG, "Failed to initialize DisallowInstallUnknownSources", e);
+          }
+        }
+        if (android.security.Flags.aapmFeatureMemoryTaggingExtension()) {
+          try {
+            mHooks.add(new MemoryTaggingExtensionHook(mContext, enabled));
+          } catch (Exception e) {
+            Slog.e(TAG, "Failed to initialize MemoryTaggingExtension", e);
+          }
+        }
+        if (android.security.Flags.aapmFeatureDisableCellular2g()) {
+          try {
+            mHooks.add(new DisallowCellular2GAdvancedProtectionHook(mContext, enabled));
+          } catch (Exception e) {
+            Slog.e(TAG, "Failed to initialize DisallowCellular2g", e);
+          }
         }
     }
 
@@ -191,7 +211,7 @@
     }
 
     void sendCallbackAdded(boolean enabled, IAdvancedProtectionCallback callback) {
-        Message.obtain(mHandler, MODE_CHANGED, /*enabled*/ enabled ? 1 : 0, /*unused*/ -1,
+        Message.obtain(mHandler, CALLBACK_ADDED, /*enabled*/ enabled ? 1 : 0, /*unused*/ -1,
                         /*callback*/ callback)
                 .sendToTarget();
     }
@@ -270,8 +290,13 @@
 
             for (int i = 0; i < mHooks.size(); i++) {
                 AdvancedProtectionHook feature = mHooks.get(i);
-                if (feature.isAvailable()) {
-                    feature.onAdvancedProtectionChanged(enabled);
+                try {
+                    if (feature.isAvailable()) {
+                        feature.onAdvancedProtectionChanged(enabled);
+                    }
+                } catch (Exception e) {
+                    Slog.e(TAG, "Failed to call hook for feature "
+                            + feature.getFeature().getId(), e);
                 }
             }
             synchronized (mCallbacks) {
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
new file mode 100644
index 0000000..b9c8d3d
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
@@ -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.server.security.advancedprotection.features;
+
+import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY;
+import static android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_CELLULAR_2G;
+
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserManager;
+import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+
+/** @hide */
+public final class DisallowCellular2GAdvancedProtectionHook extends AdvancedProtectionHook {
+    private static final String TAG = "AdvancedProtectionDisallowCellular2G";
+
+    private final AdvancedProtectionFeature mFeature =
+            new AdvancedProtectionFeature(FEATURE_ID_DISALLOW_CELLULAR_2G);
+    private final DevicePolicyManager mDevicePolicyManager;
+    private final TelephonyManager mTelephonyManager;
+
+    public DisallowCellular2GAdvancedProtectionHook(@NonNull Context context, boolean enabled) {
+        super(context, enabled);
+        mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+        mTelephonyManager = context.getSystemService(TelephonyManager.class);
+
+        setPolicy(enabled);
+    }
+
+    @NonNull
+    @Override
+    public AdvancedProtectionFeature getFeature() {
+        return mFeature;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mTelephonyManager.isDataCapable();
+    }
+
+    private void setPolicy(boolean enabled) {
+        Slog.i(TAG, "setPolicy called with " + enabled);
+
+        if (enabled) {
+            Slog.d(TAG, "Setting DISALLOW_CELLULAR_2G_GLOBALLY restriction");
+            mDevicePolicyManager.addUserRestrictionGlobally(
+                    ADVANCED_PROTECTION_SYSTEM_ENTITY, UserManager.DISALLOW_CELLULAR_2G);
+        } else {
+            Slog.d(TAG, "Clearing DISALLOW_CELLULAR_2G_GLOBALLY restriction");
+            mDevicePolicyManager.clearUserRestrictionGlobally(
+                    ADVANCED_PROTECTION_SYSTEM_ENTITY, UserManager.DISALLOW_CELLULAR_2G);
+        }
+    }
+
+    @Override
+    public void onAdvancedProtectionChanged(boolean enabled) {
+        setPolicy(enabled);
+
+        // Leave 2G disabled even if APM is disabled.
+        if (!enabled) {
+            long oldAllowedTypes =
+                    mTelephonyManager.getAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+            long newAllowedTypes = oldAllowedTypes & ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
+            mTelephonyManager.setAllowedNetworkTypesForReason(
+                    TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, newAllowedTypes);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java
index 21752e5..bb523d6 100644
--- a/services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java
@@ -19,13 +19,24 @@
 import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY;
 import static android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES;
 
+import android.Manifest;
 import android.annotation.NonNull;
+import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.Process;
+import android.os.RemoteException;
 import android.os.UserManager;
 import android.security.advancedprotection.AdvancedProtectionFeature;
 import android.util.Slog;
 
+import com.android.server.LocalServices;
+
 /** @hide */
 public final class DisallowInstallUnknownSourcesAdvancedProtectionHook
         extends AdvancedProtectionHook {
@@ -33,13 +44,25 @@
 
     private final AdvancedProtectionFeature mFeature = new AdvancedProtectionFeature(
             FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES);
+
+    private final ActivityManagerInternal mActivityManagerInternal;
+    private final AppOpsManager mAppOpsManager;
     private final DevicePolicyManager mDevicePolicyManager;
+    private final IPackageManager mIPackageManager;
+    private final PackageManager mPackageManager;
+    private final UserManager mUserManager;
 
     public DisallowInstallUnknownSourcesAdvancedProtectionHook(@NonNull Context context,
             boolean enabled) {
         super(context, enabled);
+        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+        mAppOpsManager = context.getSystemService(AppOpsManager.class);
         mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
-        onAdvancedProtectionChanged(enabled);
+        mIPackageManager = AppGlobals.getPackageManager();
+        mUserManager = context.getSystemService(UserManager.class);
+        mPackageManager = context.getPackageManager();
+
+        setRestriction(enabled);
     }
 
     @NonNull
@@ -53,21 +76,47 @@
         return true;
     }
 
-    @Override
-    public void onAdvancedProtectionChanged(boolean enabled) {
+    private void setRestriction(boolean enabled) {
         if (enabled) {
             Slog.d(TAG, "Setting DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY restriction");
             mDevicePolicyManager.addUserRestrictionGlobally(ADVANCED_PROTECTION_SYSTEM_ENTITY,
                     UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
-            return;
+        } else {
+            Slog.d(TAG, "Clearing DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY restriction");
+            mDevicePolicyManager.clearUserRestrictionGlobally(ADVANCED_PROTECTION_SYSTEM_ENTITY,
+                    UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
         }
-        Slog.d(TAG, "Clearing DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY restriction");
-        mDevicePolicyManager.clearUserRestrictionGlobally(ADVANCED_PROTECTION_SYSTEM_ENTITY,
-                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
+    }
 
-        // TODO(b/369361373):
-        //  1. After clearing the restriction, set AppOpsManager.OP_REQUEST_INSTALL_PACKAGES to
-        //  disabled.
-        //  2. Update dialog strings.
+    @Override
+    public void onAdvancedProtectionChanged(boolean enabled) {
+        setRestriction(enabled);
+        if (enabled) return;
+
+        // Leave OP_REQUEST_INSTALL_PACKAGES disabled when APM is disabled.
+        Slog.d(TAG, "Setting all OP_REQUEST_INSTALL_PACKAGES to MODE_ERRORED");
+        for (UserInfo userInfo : mUserManager.getAliveUsers()) {
+            try {
+                final String[] packagesWithRequestInstallPermission = mIPackageManager
+                        .getAppOpPermissionPackages(
+                                Manifest.permission.REQUEST_INSTALL_PACKAGES, userInfo.id);
+                for (String packageName : packagesWithRequestInstallPermission) {
+                    try {
+                        int uid = mPackageManager.getPackageUidAsUser(packageName, userInfo.id);
+                        boolean isCallerInstrumented = mActivityManagerInternal
+                                .getInstrumentationSourceUid(uid) != Process.INVALID_UID;
+                        if (!isCallerInstrumented) {
+                            mAppOpsManager.setMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, uid,
+                                    packageName, AppOpsManager.MODE_ERRORED);
+                        }
+                    } catch (PackageManager.NameNotFoundException e) {
+                        Slog.e(TAG, "Couldn't retrieve uid for a package: " + e);
+                    }
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Couldn't retrieve packages with REQUEST_INSTALL_PACKAGES."
+                        + " getAppOpPermissionPackages() threw the following exception: " + e);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/MemoryTaggingExtensionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/MemoryTaggingExtensionHook.java
new file mode 100644
index 0000000..d3d3937
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/MemoryTaggingExtensionHook.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.server.security.advancedprotection.features;
+
+import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY;
+import static android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_ENABLE_MTE;
+
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.SystemProperties;
+import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.util.Slog;
+
+/** @hide */
+public final class MemoryTaggingExtensionHook
+        extends AdvancedProtectionHook {
+    private static final String TAG = "AdvancedProtectionMTE";
+    private static final String MTE_DPM_SYSTEM_PROPERTY =
+            "ro.arm64.memtag.bootctl_device_policy_manager";
+    private static final String MTE_SETTINGS_SYSTEM_PROPERTY =
+            "ro.arm64.memtag.bootctl_settings_toggle";
+
+    private final AdvancedProtectionFeature mFeature = new AdvancedProtectionFeature(
+            FEATURE_ID_ENABLE_MTE);
+    private final DevicePolicyManager mDevicePolicyManager;
+
+    public MemoryTaggingExtensionHook(@NonNull Context context,
+            boolean enabled) {
+        super(context, enabled);
+        mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+        onAdvancedProtectionChanged(enabled);
+    }
+
+    @NonNull
+    @Override
+    public AdvancedProtectionFeature getFeature() {
+        return mFeature;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return SystemProperties.getBoolean(MTE_DPM_SYSTEM_PROPERTY,
+                SystemProperties.getBoolean(MTE_SETTINGS_SYSTEM_PROPERTY, false));
+    }
+
+    @Override
+    public void onAdvancedProtectionChanged(boolean enabled) {
+        if (!isAvailable()) {
+            Slog.i(TAG, "MTE unavailable on device, skipping.");
+            return;
+        }
+        final int mtePolicy;
+        if (enabled) {
+            mtePolicy = DevicePolicyManager.MTE_ENABLED;
+        } else {
+            mtePolicy = DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+        }
+
+        Slog.d(TAG, "Setting MTE state to " + mtePolicy);
+        try {
+            mDevicePolicyManager.setMtePolicy(ADVANCED_PROTECTION_SYSTEM_ENTITY, mtePolicy);
+        } catch (UnsupportedOperationException e) {
+            Slog.i(TAG, "Setting MTE policy unsupported", e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
index 06e9dcd..0ea88e8 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
@@ -36,6 +36,9 @@
     private static final int MSG_DISABLE = 2;
 
     private static final int STORED_EVENTS_SIZE_LIMIT = 1024;
+    private static final IntrusionDetectionAdminReceiver ADMIN_RECEIVER =
+            new IntrusionDetectionAdminReceiver();
+
     private final IntrusionDetectionService mIntrusionDetectionService;
     private final ArrayList<DataSource> mDataSources;
 
@@ -60,10 +63,19 @@
      * Initialize DataSources
      * @return Whether the initialization succeeds.
      */
-    // TODO: Add the corresponding data sources
     public boolean initialize() {
         SecurityLogSource securityLogSource = new SecurityLogSource(mContext, this);
         mDataSources.add(securityLogSource);
+
+        NetworkLogSource networkLogSource = new NetworkLogSource(mContext, this);
+        ADMIN_RECEIVER.setNetworkLogEventCallback(networkLogSource);
+        mDataSources.add(networkLogSource);
+
+        for (DataSource ds : mDataSources) {
+            if (!ds.initialize()) {
+                return false;
+            }
+        }
         return true;
     }
 
diff --git a/services/core/java/com/android/server/security/intrusiondetection/DataSource.java b/services/core/java/com/android/server/security/intrusiondetection/DataSource.java
index 0bc4482..61fac46 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/DataSource.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/DataSource.java
@@ -18,6 +18,11 @@
 
 public interface DataSource {
     /**
+     * Initialize the data source.
+     */
+    boolean initialize();
+
+    /**
      * Enable the data collection.
      */
     void enable();
diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java
new file mode 100644
index 0000000..dba7374
--- /dev/null
+++ b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java
@@ -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.server.security.intrusiondetection;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Slog;
+
+public class IntrusionDetectionAdminReceiver extends DeviceAdminReceiver {
+    private static final String TAG = "IntrusionDetectionAdminReceiver";
+
+    private static NetworkLogSource sNetworkLogSource;
+
+    @Override
+    public void onNetworkLogsAvailable(
+            Context context, Intent intent, long batchToken, int networkLogsCount) {
+        if (sNetworkLogSource != null) {
+            sNetworkLogSource.onNetworkLogsAvailable(batchToken);
+        } else {
+            Slog.w(TAG, "Network log receiver is not initialized");
+        }
+    }
+
+    public void setNetworkLogEventCallback(NetworkLogSource networkLogSource) {
+        sNetworkLogSource = networkLogSource;
+    }
+}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
index 82f39b3..b25656e 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
@@ -60,7 +60,7 @@
         if (!bindService()) {
             return false;
         }
-        AndroidFuture<Integer> resultFuture = new AndroidFuture<>();
+        AndroidFuture<Boolean> resultFuture = new AndroidFuture<>();
         try {
             mService.initialize(resultFuture);
         } catch (RemoteException e) {
@@ -68,8 +68,8 @@
             unbindService();
             return false;
         }
-        Integer result = getFutureResult(resultFuture);
-        if (result != null && result == 0) {
+        Boolean result = getFutureResult(resultFuture);
+        if (result != null && result == true) {
             return true;
         } else {
             unbindService();
@@ -83,22 +83,22 @@
      * @return Whether the data is added to the binder service.
      */
     public boolean addData(List<IntrusionDetectionEvent> data) {
-        AndroidFuture<Integer> resultFuture = new AndroidFuture<>();
+        AndroidFuture<Boolean> resultFuture = new AndroidFuture<>();
         try {
             mService.addData(data, resultFuture);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote Exception", e);
             return false;
         }
-        Integer result = getFutureResult(resultFuture);
-        return result != null && result == 0;
+        Boolean result = getFutureResult(resultFuture);
+        return result != null && result == true;
     }
 
     /**
      * Release the BackupTransport binder service.
      */
     public void release() {
-        AndroidFuture<Integer> resultFuture = new AndroidFuture<>();
+        AndroidFuture<Boolean> resultFuture = new AndroidFuture<>();
         try {
             mService.release(resultFuture);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
new file mode 100644
index 0000000..1c93d3f
--- /dev/null
+++ b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
@@ -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.server.security.intrusiondetection;
+
+import android.app.admin.ConnectEvent;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.DnsEvent;
+import android.app.admin.NetworkEvent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.util.Slog;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class NetworkLogSource implements DataSource {
+
+    private static final String TAG = "IntrusionDetectionEvent NetworkLogSource";
+
+    private DevicePolicyManager mDpm;
+    private ComponentName mAdmin;
+    private DataAggregator mDataAggregator;
+
+    public NetworkLogSource(Context context, DataAggregator dataAggregator) {
+        mDataAggregator = dataAggregator;
+        mDpm = context.getSystemService(DevicePolicyManager.class);
+        mAdmin = new ComponentName(context, IntrusionDetectionAdminReceiver.class);
+    }
+
+    @Override
+    public boolean initialize() {
+        try {
+            if (!mDpm.isAdminActive(mAdmin)) {
+                Slog.e(TAG, "Admin " + mAdmin.flattenToString() + "is not active admin");
+                return false;
+            }
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Security exception in initialize: ", e);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void enable() {
+        enableNetworkLog();
+    }
+
+    @Override
+    public void disable() {
+        disableNetworkLog();
+    }
+
+    private void enableNetworkLog() {
+        if (!isNetworkLogEnabled()) {
+            mDpm.setNetworkLoggingEnabled(mAdmin, true);
+        }
+    }
+
+    private void disableNetworkLog() {
+        if (isNetworkLogEnabled()) {
+            mDpm.setNetworkLoggingEnabled(mAdmin, false);
+        }
+    }
+
+    private boolean isNetworkLogEnabled() {
+        return mDpm.isNetworkLoggingEnabled(mAdmin);
+    }
+
+    /**
+     * Retrieve network logs when onNetworkLogsAvailable callback is received.
+     *
+     * @param batchToken The token representing the current batch of network logs.
+     */
+    public void onNetworkLogsAvailable(long batchToken) {
+        List<NetworkEvent> events;
+        try {
+            events = mDpm.retrieveNetworkLogs(mAdmin, batchToken);
+        } catch (SecurityException e) {
+            Slog.e(
+                    TAG,
+                    "Admin "
+                            + mAdmin.flattenToString()
+                            + "does not have permission to retrieve network logs",
+                    e);
+            return;
+        }
+        if (events == null) {
+            if (!isNetworkLogEnabled()) {
+                Slog.w(TAG, "Network logging is disabled");
+            } else {
+                Slog.e(TAG, "Invalid batch token: " + batchToken);
+            }
+            return;
+        }
+
+        List<IntrusionDetectionEvent> intrusionDetectionEvents =
+                events.stream()
+                        .filter(event -> event != null)
+                        .map(event -> toIntrusionDetectionEvent(event))
+                        .collect(Collectors.toList());
+        mDataAggregator.addBatchData(intrusionDetectionEvents);
+    }
+
+    private IntrusionDetectionEvent toIntrusionDetectionEvent(NetworkEvent event) {
+        if (event instanceof DnsEvent) {
+            DnsEvent dnsEvent = (DnsEvent) event;
+            return new IntrusionDetectionEvent(dnsEvent);
+        } else if (event instanceof ConnectEvent) {
+            ConnectEvent connectEvent = (ConnectEvent) event;
+            return new IntrusionDetectionEvent(connectEvent);
+        }
+        throw new IllegalArgumentException(
+                "Invalid event type with ID: "
+                        + event.getId()
+                        + "from package: "
+                        + event.getPackageName());
+    }
+}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
index 226f9d8..c5f736e 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
@@ -22,6 +22,7 @@
 import android.app.admin.SecurityLog.SecurityEvent;
 import android.content.Context;
 import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.util.Slog;
 
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -33,7 +34,7 @@
 
     private static final String TAG = "IntrusionDetection SecurityLogSource";
 
-    private SecurityEventCallback mEventCallback = new SecurityEventCallback();
+    private SecurityEventCallback mEventCallback;
     private DevicePolicyManager mDpm;
     private Executor mExecutor;
     private DataAggregator mDataAggregator;
@@ -42,10 +43,27 @@
         mDataAggregator = dataAggregator;
         mDpm = context.getSystemService(DevicePolicyManager.class);
         mExecutor = Executors.newSingleThreadExecutor();
-        mEventCallback = new SecurityEventCallback();
     }
 
     @Override
+    public boolean initialize() {
+        // Confirm caller is system and the device is managed. Otherwise logs will
+        // be redacted.
+        try {
+            if (!mDpm.isDeviceManaged()) {
+                Slog.e(TAG, "Caller does not have device owner permissions");
+                return false;
+            }
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Security exception in initialize: ", e);
+            return false;
+        }
+        mEventCallback = new SecurityEventCallback();
+        return true;
+    }
+
+
+    @Override
     @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public void enable() {
         enableAuditLog();
@@ -72,12 +90,8 @@
         }
     }
 
-    /**
-     * Check if security audit logging is enabled for the caller.
-     *
-     * @return Whether security audit logging is enabled.
-     */
-    public boolean isAuditLogEnabled() {
+    @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+    private boolean isAuditLogEnabled() {
         return mDpm.isAuditLogEnabled();
     }
 
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 887e186..708bca7 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -2528,22 +2528,24 @@
     }
 
     private void notifyDeviceLockedListenersForUser(int userId, boolean locked) {
-        int numListeners = mDeviceLockedStateListeners.beginBroadcast();
-        try {
-            IntStream.range(0, numListeners).forEach(i -> {
-                try {
-                    Integer uid = (Integer) mDeviceLockedStateListeners.getBroadcastCookie(i);
-                    if (userId == uid.intValue()) {
-                        mDeviceLockedStateListeners.getBroadcastItem(i)
-                                .onDeviceLockedStateChanged(locked);
+        synchronized (mDeviceLockedStateListeners) {
+            int numListeners = mDeviceLockedStateListeners.beginBroadcast();
+            try {
+                IntStream.range(0, numListeners).forEach(i -> {
+                    try {
+                        Integer uid = (Integer) mDeviceLockedStateListeners.getBroadcastCookie(i);
+                        if (userId == uid.intValue()) {
+                            mDeviceLockedStateListeners.getBroadcastItem(i)
+                                    .onDeviceLockedStateChanged(locked);
+                        }
+                    } catch (RemoteException re) {
+                        Log.i(TAG, "Service died", re);
                     }
-                } catch (RemoteException re) {
-                    Log.i(TAG, "Service died", re);
-                }
-            });
+                });
 
-        } finally {
-            mDeviceLockedStateListeners.finishBroadcast();
+            } finally {
+                mDeviceLockedStateListeners.finishBroadcast();
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index 38bc026..e191ff2 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -119,14 +119,14 @@
      * If resource holder retains ownership of the resource in a challenge scenario then value is
      * true.
      */
-    private boolean mResourceHolderRetain;
+    private boolean mResourceOwnershipRetention;
 
     private ClientProfile(Builder builder) {
         this.mId = builder.mId;
         this.mTvInputSessionId = builder.mTvInputSessionId;
         this.mUseCase = builder.mUseCase;
         this.mProcessId = builder.mProcessId;
-        this.mResourceHolderRetain = builder.mResourceHolderRetain;
+        this.mResourceOwnershipRetention = builder.mResourceOwnershipRetention;
     }
 
     public int getId() {
@@ -149,8 +149,8 @@
      * Returns true when the resource holder retains ownership of the resource in a challenge
      * scenario.
      */
-    public boolean shouldResourceHolderRetain() {
-        return mResourceHolderRetain;
+    public boolean resourceOwnershipRetentionEnabled() {
+        return mResourceOwnershipRetention;
     }
 
     /**
@@ -199,12 +199,12 @@
      * scenario, when both resource holder and resource challenger have same processId and same
      * priority.
      *
-     * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or
-     *     false (or resourceHolderRetain not set at all) to allow the resource challenger to
-     *     acquire the resource. If not explicitly set, resourceHolderRetain is set to false.
+     * @param enabled Set to {@code true} to allow the resource holder to retain ownership,
+     *     or false to allow the resource challenger to acquire the resource.
+     *     If not explicitly set, enabled is set to {@code false}.
      */
-    public void setResourceHolderRetain(boolean resourceHolderRetain) {
-        mResourceHolderRetain = resourceHolderRetain;
+    public void setResourceOwnershipRetention(boolean enabled) {
+        mResourceOwnershipRetention = enabled;
     }
 
     /**
@@ -389,7 +389,7 @@
         private String mTvInputSessionId;
         private int mUseCase;
         private int mProcessId;
-        private boolean mResourceHolderRetain = false;
+        private boolean mResourceOwnershipRetention = false;
 
         Builder(int id) {
             this.mId = id;
@@ -428,12 +428,12 @@
         /**
          * Builder for {@link ClientProfile}.
          *
-         * @param resourceHolderRetain the determining factor for resource ownership during
-         *     challenger scenario. The default behavior favors the resource challenger and grants
-         *     them ownership of the resource if resourceHolderRetain is not explicitly set to true.
+         * @param enabled the determining factor for resource ownership during challenger scenario.
+         *     The default behavior favors the resource challenger and grants them ownership of
+         *     the resource if resourceOwnershipRetention is not explicitly set to true.
          */
-        public Builder resourceHolderRetain(boolean resourceHolderRetain) {
-            this.mResourceHolderRetain = resourceHolderRetain;
+        public Builder resourceOwnershipRetention(boolean enabled) {
+            this.mResourceOwnershipRetention = enabled;
             return this;
         }
 
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 5ae8c11..bb192c0 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -231,10 +231,10 @@
         }
 
         @Override
-        public void setResourceHolderRetain(int clientId, boolean resourceHolderRetain) {
-            enforceTrmAccessPermission("setResourceHolderRetain");
+        public void setResourceOwnershipRetention(int clientId, boolean enabled) {
+            enforceTrmAccessPermission("setResourceOwnershipRetention");
             synchronized (mLock) {
-                getClientProfile(clientId).setResourceHolderRetain(resourceHolderRetain);
+                getClientProfile(clientId).setResourceOwnershipRetention(enabled);
             }
         }
 
@@ -1079,7 +1079,8 @@
                             || ((requestClient.getPriority() == currentLowestPriority)
                                     && isRequestFromSameProcess
                                     && !(setResourceHolderRetain()
-                                            && requestClient.shouldResourceHolderRetain())))) {
+                                            && requestClient
+                                                    .resourceOwnershipRetentionEnabled())))) {
                 frontendHandle[0] = inUseLowestPriorityFrontend.getHandle();
                 reclaimOwnerId[0] = inUseLowestPriorityFrontend.getOwnerClientId();
                 return true;
@@ -1265,7 +1266,8 @@
                             || ((requestClient.getPriority() == currentLowestPriority)
                                     && isRequestFromSameProcess
                                     && !(setResourceHolderRetain()
-                                            && requestClient.shouldResourceHolderRetain())))) {
+                                            && requestClient
+                                                    .resourceOwnershipRetentionEnabled())))) {
                 lnbHandle[0] = inUseLowestPriorityLnb.getHandle();
                 reclaimOwnerId[0] = inUseLowestPriorityLnb.getOwnerClientId();
                 return true;
@@ -1352,7 +1354,8 @@
                             || ((requestClient.getPriority() == currentLowestPriority)
                                     && isRequestFromSameProcess
                                     && !(setResourceHolderRetain()
-                                            && requestClient.shouldResourceHolderRetain())))) {
+                                            && requestClient
+                                                    .resourceOwnershipRetentionEnabled())))) {
                 casSessionHandle[0] = cas.getHandle();
                 reclaimOwnerId[0] = lowestPriorityOwnerId;
                 return true;
@@ -1439,7 +1442,8 @@
                             || ((requestClient.getPriority() == currentLowestPriority)
                                     && isRequestFromSameProcess
                                     && !(setResourceHolderRetain()
-                                            && requestClient.shouldResourceHolderRetain())))) {
+                                            && requestClient
+                                                    .resourceOwnershipRetentionEnabled())))) {
                 ciCamHandle[0] = ciCam.getHandle();
                 reclaimOwnerId[0] = lowestPriorityOwnerId;
                 return true;
@@ -1677,7 +1681,8 @@
                             || ((requestClient.getPriority() == currentLowestPriority)
                                     && isRequestFromSameProcess
                                     && !(setResourceHolderRetain()
-                                            && requestClient.shouldResourceHolderRetain())))) {
+                                            && requestClient
+                                                    .resourceOwnershipRetentionEnabled())))) {
                 demuxHandle[0] = inUseLowestPriorityDemux.getHandle();
                 reclaimOwnerId[0] = inUseLowestPriorityDemux.getOwnerClientId();
                 return true;
diff --git a/services/core/java/com/android/server/utils/LazyJniRegistrar.java b/services/core/java/com/android/server/utils/LazyJniRegistrar.java
index ac4a92e..6d29e9e 100644
--- a/services/core/java/com/android/server/utils/LazyJniRegistrar.java
+++ b/services/core/java/com/android/server/utils/LazyJniRegistrar.java
@@ -42,6 +42,9 @@
     /** Registers native methods for ConsumerIrService. */
     public static native void registerConsumerIrService();
 
+    /** Registers native methods for GameManagerService. */
+    public static native void registerGameManagerService();
+
     /** Registers native methods for VrManagerService. */
     public static native void registerVrManagerService();
 }
diff --git a/services/core/java/com/android/server/utils/WatchableImpl.java b/services/core/java/com/android/server/utils/WatchableImpl.java
index 8a04ccf..fec4351 100644
--- a/services/core/java/com/android/server/utils/WatchableImpl.java
+++ b/services/core/java/com/android/server/utils/WatchableImpl.java
@@ -33,6 +33,7 @@
     /**
      * The list of observers.
      */
+    @GuardedBy("mObservers")
     protected final ArrayList<Watcher> mObservers = new ArrayList<>();
 
     /**
@@ -83,7 +84,9 @@
      * @return The number of registered observers.
      */
     public int registeredObserverCount() {
-        return mObservers.size();
+        synchronized (mObservers) {
+            return mObservers.size();
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/vcn/Android.bp b/services/core/java/com/android/server/vcn/Android.bp
deleted file mode 100644
index ab5da3e..0000000
--- a/services/core/java/com/android/server/vcn/Android.bp
+++ /dev/null
@@ -1,13 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-filegroup {
-    name: "framework-vcn-util-sources",
-    srcs: ["util/**/*.java"],
-}
diff --git a/services/core/java/com/android/server/vcn/TEST_MAPPING b/services/core/java/com/android/server/vcn/TEST_MAPPING
deleted file mode 100644
index 5b04d88..0000000
--- a/services/core/java/com/android/server/vcn/TEST_MAPPING
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "FrameworksVcnTests"
-    },
-    {
-      "name": "CtsVcnTestCases"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
deleted file mode 100644
index 3392d03..0000000
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ /dev/null
@@ -1,599 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.vcn;
-
-import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
-import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.vcn.VcnManager;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.ParcelUuid;
-import android.os.PersistableBundle;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
-import android.telephony.TelephonyCallback;
-import android.telephony.TelephonyManager;
-import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * TelephonySubscriptionTracker provides a caching layer for tracking active subscription groups.
- *
- * <p>This class performs two roles:
- *
- * <ol>
- *   <li>De-noises subscription changes by ensuring that only changes in active and ready
- *       subscription groups are acted upon
- *   <li>Caches mapping between subIds and subscription groups
- * </ol>
- *
- * <p>An subscription group is active and ready if any of its contained subIds has had BOTH the
- * {@link CarrierConfigManager#isConfigForIdentifiedCarrier()} return true, AND the subscription is
- * listed as active per SubscriptionManager#getAllSubscriptionInfoList().
- *
- * <p>Note that due to the asynchronous nature of callbacks and broadcasts, the output of this class
- * is (only) eventually consistent.
- *
- * @hide
- */
-public class TelephonySubscriptionTracker extends BroadcastReceiver {
-    @NonNull private static final String TAG = TelephonySubscriptionTracker.class.getSimpleName();
-    private static final boolean LOG_DBG = false; // STOPSHIP if true
-
-    @NonNull private final Context mContext;
-    @NonNull private final Handler mHandler;
-    @NonNull private final TelephonySubscriptionTrackerCallback mCallback;
-    @NonNull private final Dependencies mDeps;
-
-    @NonNull private final TelephonyManager mTelephonyManager;
-    @NonNull private final SubscriptionManager mSubscriptionManager;
-    @Nullable private final CarrierConfigManager mCarrierConfigManager;
-
-    @NonNull private final ActiveDataSubscriptionIdListener mActiveDataSubIdListener;
-
-    // TODO (Android T+): Add ability to handle multiple subIds per slot.
-    @NonNull private final Map<Integer, Integer> mReadySubIdsBySlotId = new HashMap<>();
-
-    @NonNull
-    private final Map<Integer, PersistableBundleWrapper> mSubIdToCarrierConfigMap = new HashMap<>();
-
-    @NonNull private final OnSubscriptionsChangedListener mSubscriptionChangedListener;
-
-    @NonNull
-    private final List<CarrierPrivilegesCallback> mCarrierPrivilegesCallbacks = new ArrayList<>();
-
-    @NonNull private TelephonySubscriptionSnapshot mCurrentSnapshot;
-
-    @NonNull
-    private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
-            (int logicalSlotIndex, int subscriptionId, int carrierId, int specificCarrierId) ->
-                    handleActionCarrierConfigChanged(logicalSlotIndex, subscriptionId);
-
-
-    public TelephonySubscriptionTracker(
-            @NonNull Context context,
-            @NonNull Handler handler,
-            @NonNull TelephonySubscriptionTrackerCallback callback) {
-        this(context, handler, callback, new Dependencies());
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    TelephonySubscriptionTracker(
-            @NonNull Context context,
-            @NonNull Handler handler,
-            @NonNull TelephonySubscriptionTrackerCallback callback,
-            @NonNull Dependencies deps) {
-        mContext = Objects.requireNonNull(context, "Missing context");
-        mHandler = Objects.requireNonNull(handler, "Missing handler");
-        mCallback = Objects.requireNonNull(callback, "Missing callback");
-        mDeps = Objects.requireNonNull(deps, "Missing deps");
-
-        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
-        mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
-        mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
-        mActiveDataSubIdListener = new ActiveDataSubscriptionIdListener();
-
-        mSubscriptionChangedListener =
-                new OnSubscriptionsChangedListener() {
-                    @Override
-                    public void onSubscriptionsChanged() {
-                        handleSubscriptionsChanged();
-                    }
-                };
-    }
-
-    /**
-     * Registers the receivers, and starts tracking subscriptions.
-     *
-     * <p>Must always be run on the VcnManagementService thread.
-     */
-    public void register() {
-        final HandlerExecutor executor = new HandlerExecutor(mHandler);
-        final IntentFilter filter = new IntentFilter();
-        filter.addAction(ACTION_MULTI_SIM_CONFIG_CHANGED);
-
-        mContext.registerReceiver(this, filter, null, mHandler);
-        mSubscriptionManager.addOnSubscriptionsChangedListener(
-                executor, mSubscriptionChangedListener);
-        mTelephonyManager.registerTelephonyCallback(executor, mActiveDataSubIdListener);
-        if (mCarrierConfigManager != null) {
-            mCarrierConfigManager.registerCarrierConfigChangeListener(executor,
-                    mCarrierConfigChangeListener);
-        }
-
-        registerCarrierPrivilegesCallbacks();
-    }
-
-    private void registerCarrierPrivilegesCallbacks() {
-        final HandlerExecutor executor = new HandlerExecutor(mHandler);
-        final int modemCount = mTelephonyManager.getActiveModemCount();
-        try {
-            for (int i = 0; i < modemCount; i++) {
-                CarrierPrivilegesCallback carrierPrivilegesCallback =
-                        new CarrierPrivilegesCallback() {
-                            @Override
-                            public void onCarrierPrivilegesChanged(
-                                    @NonNull Set<String> privilegedPackageNames,
-                                    @NonNull Set<Integer> privilegedUids) {
-                                // Re-trigger the synchronous check (which is also very cheap due
-                                // to caching in CarrierPrivilegesTracker). This allows consistency
-                                // with the onSubscriptionsChangedListener and broadcasts.
-                                handleSubscriptionsChanged();
-                            }
-                        };
-
-                mTelephonyManager.registerCarrierPrivilegesCallback(
-                        i, executor, carrierPrivilegesCallback);
-                mCarrierPrivilegesCallbacks.add(carrierPrivilegesCallback);
-            }
-        } catch (IllegalArgumentException e) {
-            Slog.wtf(TAG, "Encounted exception registering carrier privileges listeners", e);
-        }
-    }
-
-    /**
-     * Unregisters the receivers, and stops tracking subscriptions.
-     *
-     * <p>Must always be run on the VcnManagementService thread.
-     */
-    public void unregister() {
-        mContext.unregisterReceiver(this);
-        mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionChangedListener);
-        mTelephonyManager.unregisterTelephonyCallback(mActiveDataSubIdListener);
-        if (mCarrierConfigManager != null) {
-            mCarrierConfigManager.unregisterCarrierConfigChangeListener(
-                    mCarrierConfigChangeListener);
-        }
-
-        unregisterCarrierPrivilegesCallbacks();
-    }
-
-    private void unregisterCarrierPrivilegesCallbacks() {
-        for (CarrierPrivilegesCallback carrierPrivilegesCallback :
-                mCarrierPrivilegesCallbacks) {
-            mTelephonyManager.unregisterCarrierPrivilegesCallback(carrierPrivilegesCallback);
-        }
-        mCarrierPrivilegesCallbacks.clear();
-    }
-
-    /**
-     * Handles subscription changes, correlating available subscriptions and loaded carrier configs
-     *
-     * <p>The subscription change listener is registered with a HandlerExecutor backed by mHandler,
-     * so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking.
-     */
-    public void handleSubscriptionsChanged() {
-        final Map<ParcelUuid, Set<String>> privilegedPackages = new HashMap<>();
-        final Map<Integer, SubscriptionInfo> newSubIdToInfoMap = new HashMap<>();
-
-        final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList();
-        if (allSubs == null) {
-            return; // Telephony crashed; no way to verify subscriptions.
-        }
-
-        // If allSubs is empty, no subscriptions exist. Cache will be cleared by virtue of no active
-        // subscriptions
-        for (SubscriptionInfo subInfo : allSubs) {
-            if (subInfo.getGroupUuid() == null) {
-                continue;
-            }
-
-            // Build subId -> subGrp cache
-            newSubIdToInfoMap.put(subInfo.getSubscriptionId(), subInfo);
-
-            // Update subscription groups that are both ready, and active. For a group to be
-            // considered active, both of the following must be true:
-            //
-            // 1. A final CARRIER_CONFIG_CHANGED (where config is for an identified carrier)
-            // broadcast must have been received for the subId
-            // 2. A active subscription (is loaded into a SIM slot) must be part of the subscription
-            // group.
-            if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX
-                    && mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) {
-                final TelephonyManager subIdSpecificTelephonyManager =
-                        mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId());
-
-                final ParcelUuid subGroup = subInfo.getGroupUuid();
-                final Set<String> pkgs =
-                        privilegedPackages.getOrDefault(subGroup, new ArraySet<>());
-                pkgs.addAll(subIdSpecificTelephonyManager.getPackagesWithCarrierPrivileges());
-
-                privilegedPackages.put(subGroup, pkgs);
-            }
-        }
-
-        final TelephonySubscriptionSnapshot newSnapshot =
-                new TelephonySubscriptionSnapshot(
-                        mDeps.getActiveDataSubscriptionId(),
-                        newSubIdToInfoMap,
-                        mSubIdToCarrierConfigMap,
-                        privilegedPackages);
-
-        // If snapshot was meaningfully updated, fire the callback
-        if (!newSnapshot.equals(mCurrentSnapshot)) {
-            mCurrentSnapshot = newSnapshot;
-            mHandler.post(
-                    () -> {
-                        mCallback.onNewSnapshot(newSnapshot);
-                    });
-        }
-    }
-
-    /**
-     * Broadcast receiver for ACTION_MULTI_SIM_CONFIG_CHANGED
-     *
-     * <p>The broadcast receiver is registered with mHandler, so callbacks & broadcasts are all
-     * serialized on mHandler, avoiding the need for locking.
-     */
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        switch (intent.getAction()) {
-            case ACTION_MULTI_SIM_CONFIG_CHANGED:
-                handleActionMultiSimConfigChanged(context, intent);
-                break;
-            default:
-                Slog.v(TAG, "Unknown intent received with action: " + intent.getAction());
-        }
-    }
-
-    private void handleActionMultiSimConfigChanged(Context context, Intent intent) {
-        unregisterCarrierPrivilegesCallbacks();
-
-        // Clear invalid slotIds from the mReadySubIdsBySlotId map.
-        final int modemCount = mTelephonyManager.getActiveModemCount();
-        final Iterator<Integer> slotIdIterator = mReadySubIdsBySlotId.keySet().iterator();
-        while (slotIdIterator.hasNext()) {
-            final int slotId = slotIdIterator.next();
-
-            if (slotId >= modemCount) {
-                slotIdIterator.remove();
-            }
-        }
-
-        registerCarrierPrivilegesCallbacks();
-        handleSubscriptionsChanged();
-    }
-
-    private void handleActionCarrierConfigChanged(int slotId, int subId) {
-        if (slotId == INVALID_SIM_SLOT_INDEX) {
-            return;
-        }
-
-        if (SubscriptionManager.isValidSubscriptionId(subId)) {
-            // Get only configs as needed to save memory.
-            PersistableBundle carrierConfig = new PersistableBundle();
-            try {
-                carrierConfig =
-                        mCarrierConfigManager.getConfigForSubId(
-                                subId, VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS);
-
-            } catch (RuntimeException exception) {
-                Slog.w(TAG, "CarrierConfigLoader is not available.");
-            }
-
-            if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) {
-                mReadySubIdsBySlotId.put(slotId, subId);
-
-                if (!carrierConfig.isEmpty()) {
-                    mSubIdToCarrierConfigMap.put(subId,
-                            new PersistableBundleWrapper(carrierConfig));
-                }
-                handleSubscriptionsChanged();
-            }
-        } else {
-            final Integer oldSubid = mReadySubIdsBySlotId.remove(slotId);
-            if (oldSubid != null) {
-                mSubIdToCarrierConfigMap.remove(oldSubid);
-            }
-            handleSubscriptionsChanged();
-        }
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void setReadySubIdsBySlotId(Map<Integer, Integer> readySubIdsBySlotId) {
-        mReadySubIdsBySlotId.clear();
-        mReadySubIdsBySlotId.putAll(readySubIdsBySlotId);
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void setSubIdToCarrierConfigMap(
-            Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap) {
-        mSubIdToCarrierConfigMap.clear();
-        mSubIdToCarrierConfigMap.putAll(subIdToCarrierConfigMap);
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    Map<Integer, Integer> getReadySubIdsBySlotId() {
-        return Collections.unmodifiableMap(mReadySubIdsBySlotId);
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    Map<Integer, PersistableBundleWrapper> getSubIdToCarrierConfigMap() {
-        return Collections.unmodifiableMap(mSubIdToCarrierConfigMap);
-    }
-
-    /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */
-    public static class TelephonySubscriptionSnapshot {
-        private final int mActiveDataSubId;
-        private final Map<Integer, SubscriptionInfo> mSubIdToInfoMap;
-        private final Map<Integer, PersistableBundleWrapper> mSubIdToCarrierConfigMap;
-        private final Map<ParcelUuid, Set<String>> mPrivilegedPackages;
-
-        public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT =
-                new TelephonySubscriptionSnapshot(
-                        INVALID_SUBSCRIPTION_ID,
-                        Collections.emptyMap(),
-                        Collections.emptyMap(),
-                        Collections.emptyMap());
-
-        @VisibleForTesting(visibility = Visibility.PRIVATE)
-        TelephonySubscriptionSnapshot(
-                int activeDataSubId,
-                @NonNull Map<Integer, SubscriptionInfo> subIdToInfoMap,
-                @NonNull Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap,
-                @NonNull Map<ParcelUuid, Set<String>> privilegedPackages) {
-            mActiveDataSubId = activeDataSubId;
-            Objects.requireNonNull(subIdToInfoMap, "subIdToInfoMap was null");
-            Objects.requireNonNull(privilegedPackages, "privilegedPackages was null");
-            Objects.requireNonNull(subIdToCarrierConfigMap, "subIdToCarrierConfigMap was null");
-
-            mSubIdToInfoMap =
-                    Collections.unmodifiableMap(
-                            new HashMap<Integer, SubscriptionInfo>(subIdToInfoMap));
-            mSubIdToCarrierConfigMap =
-                    Collections.unmodifiableMap(
-                            new HashMap<Integer, PersistableBundleWrapper>(
-                                    subIdToCarrierConfigMap));
-
-            final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>();
-            for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) {
-                unmodifiableInnerSets.put(
-                        entry.getKey(), Collections.unmodifiableSet(entry.getValue()));
-            }
-            mPrivilegedPackages = Collections.unmodifiableMap(unmodifiableInnerSets);
-        }
-
-        /** Returns the active subscription ID. May be INVALID_SUBSCRIPTION_ID */
-        public int getActiveDataSubscriptionId() {
-            return mActiveDataSubId;
-        }
-
-        /** Returns the active subscription group */
-        @Nullable
-        public ParcelUuid getActiveDataSubscriptionGroup() {
-            final SubscriptionInfo info = mSubIdToInfoMap.get(getActiveDataSubscriptionId());
-            if (info == null) {
-                return null;
-            }
-
-            return info.getGroupUuid();
-        }
-
-        /** Returns the active subscription groups */
-        @NonNull
-        public Set<ParcelUuid> getActiveSubscriptionGroups() {
-            return mPrivilegedPackages.keySet();
-        }
-
-        /** Returns all subscription groups */
-        @NonNull
-        public Set<ParcelUuid> getAllSubscriptionGroups() {
-            final Set<ParcelUuid> subGroups = new ArraySet<>();
-            for (SubscriptionInfo subInfo : mSubIdToInfoMap.values()) {
-                subGroups.add(subInfo.getGroupUuid());
-            }
-
-            return subGroups;
-        }
-
-        /** Checks if the provided package is carrier privileged for the specified sub group. */
-        public boolean packageHasPermissionsForSubscriptionGroup(
-                @NonNull ParcelUuid subGrp, @NonNull String packageName) {
-            final Set<String> privilegedPackages = mPrivilegedPackages.get(subGrp);
-
-            return privilegedPackages != null && privilegedPackages.contains(packageName);
-        }
-
-        /** Returns the Subscription Group for a given subId. */
-        @Nullable
-        public ParcelUuid getGroupForSubId(int subId) {
-            return mSubIdToInfoMap.containsKey(subId)
-                    ? mSubIdToInfoMap.get(subId).getGroupUuid()
-                    : null;
-        }
-
-        /**
-         * Returns all the subIds in a given group, including available, but inactive subscriptions.
-         */
-        @NonNull
-        public Set<Integer> getAllSubIdsInGroup(ParcelUuid subGrp) {
-            final Set<Integer> subIds = new ArraySet<>();
-
-            for (Entry<Integer, SubscriptionInfo> entry : mSubIdToInfoMap.entrySet()) {
-                if (subGrp.equals(entry.getValue().getGroupUuid())) {
-                    subIds.add(entry.getKey());
-                }
-            }
-
-            return subIds;
-        }
-
-        /** Checks if the requested subscription is opportunistic */
-        @NonNull
-        public boolean isOpportunistic(int subId) {
-            return mSubIdToInfoMap.containsKey(subId)
-                    ? mSubIdToInfoMap.get(subId).isOpportunistic()
-                    : false;
-        }
-
-        /**
-         * Retrieves a carrier config for a subscription in the provided group.
-         *
-         * <p>This method will prioritize non-opportunistic subscriptions, but will use the a
-         * carrier config for an opportunistic subscription if no other subscriptions are found.
-         */
-        @Nullable
-        public PersistableBundleWrapper getCarrierConfigForSubGrp(@NonNull ParcelUuid subGrp) {
-            PersistableBundleWrapper result = null;
-
-            for (int subId : getAllSubIdsInGroup(subGrp)) {
-                final PersistableBundleWrapper config = mSubIdToCarrierConfigMap.get(subId);
-                if (config != null) {
-                    result = config;
-
-                    // Attempt to use (any) non-opportunistic subscription. If this subscription is
-                    // opportunistic, continue and try to find a non-opportunistic subscription,
-                    // using the opportunistic ones as a last resort.
-                    if (!isOpportunistic(subId)) {
-                        return config;
-                    }
-                }
-            }
-
-            return result;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(
-                    mActiveDataSubId,
-                    mSubIdToInfoMap,
-                    mSubIdToCarrierConfigMap,
-                    mPrivilegedPackages);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (!(obj instanceof TelephonySubscriptionSnapshot)) {
-                return false;
-            }
-
-            final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj;
-
-            return mActiveDataSubId == other.mActiveDataSubId
-                    && mSubIdToInfoMap.equals(other.mSubIdToInfoMap)
-                    && mSubIdToCarrierConfigMap.equals(other.mSubIdToCarrierConfigMap)
-                    && mPrivilegedPackages.equals(other.mPrivilegedPackages);
-        }
-
-        /** Dumps the state of this snapshot for logging and debugging purposes. */
-        public void dump(IndentingPrintWriter pw) {
-            pw.println("TelephonySubscriptionSnapshot:");
-            pw.increaseIndent();
-
-            pw.println("mActiveDataSubId: " + mActiveDataSubId);
-            pw.println("mSubIdToInfoMap: " + mSubIdToInfoMap);
-            pw.println("mSubIdToCarrierConfigMap: " + mSubIdToCarrierConfigMap);
-            pw.println("mPrivilegedPackages: " + mPrivilegedPackages);
-
-            pw.decreaseIndent();
-        }
-
-        @Override
-        public String toString() {
-            return "TelephonySubscriptionSnapshot{ "
-                    + "mActiveDataSubId=" + mActiveDataSubId
-                    + ", mSubIdToInfoMap=" + mSubIdToInfoMap
-                    + ", mSubIdToCarrierConfigMap=" + mSubIdToCarrierConfigMap
-                    + ", mPrivilegedPackages=" + mPrivilegedPackages
-                    + " }";
-        }
-    }
-
-    /**
-     * Interface for listening to changes in subscriptions
-     *
-     * @see TelephonySubscriptionTracker
-     */
-    public interface TelephonySubscriptionTrackerCallback {
-        /**
-         * Called when subscription information changes, and a new subscription snapshot was taken
-         *
-         * @param snapshot the snapshot of subscription information.
-         */
-        void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot);
-    }
-
-    private class ActiveDataSubscriptionIdListener extends TelephonyCallback
-            implements TelephonyCallback.ActiveDataSubscriptionIdListener {
-        @Override
-        public void onActiveDataSubscriptionIdChanged(int subId) {
-            handleSubscriptionsChanged();
-        }
-    }
-
-    /** External static dependencies for test injection */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class Dependencies {
-        /** Checks if the given bundle is for an identified carrier */
-        public boolean isConfigForIdentifiedCarrier(PersistableBundle bundle) {
-            return CarrierConfigManager.isConfigForIdentifiedCarrier(bundle);
-        }
-
-        /** Gets the active Subscription ID */
-        public int getActiveDataSubscriptionId() {
-            return SubscriptionManager.getActiveDataSubscriptionId();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
deleted file mode 100644
index 1fba297..0000000
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ /dev/null
@@ -1,785 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.vcn;
-
-import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
-import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE;
-import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
-
-import static com.android.server.VcnManagementService.LOCAL_LOG;
-import static com.android.server.VcnManagementService.VDBG;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.net.NetworkScore;
-import android.net.Uri;
-import android.net.vcn.VcnConfig;
-import android.net.vcn.VcnGatewayConnectionConfig;
-import android.net.vcn.VcnManager.VcnErrorCode;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.Message;
-import android.os.ParcelUuid;
-import android.provider.Settings;
-import android.telephony.TelephonyCallback;
-import android.telephony.TelephonyManager;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.server.VcnManagementService.VcnCallback;
-import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.util.LogUtils;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * Represents an single instance of a VCN.
- *
- * <p>Each Vcn instance manages all {@link VcnGatewayConnection}(s) for a given subscription group,
- * including per-capability networks, network selection, and multi-homing.
- *
- * @hide
- */
-public class Vcn extends Handler {
-    private static final String TAG = Vcn.class.getSimpleName();
-
-    private static final int VCN_LEGACY_SCORE_INT = 52;
-
-    private static final List<Integer> CAPS_REQUIRING_MOBILE_DATA =
-            Arrays.asList(NET_CAPABILITY_INTERNET, NET_CAPABILITY_DUN);
-
-    private static final int MSG_EVENT_BASE = 0;
-    private static final int MSG_CMD_BASE = 100;
-
-    // Copied from Settings.Global.MOBILE_DATA
-    private static final String SETTINGS_GLOBAL_MOBILE_DATA_STRING = "mobile_data";
-
-    /**
-     * A carrier app updated the configuration.
-     *
-     * <p>Triggers update of config, re-evaluating all active and underlying networks.
-     *
-     * @param obj VcnConfig
-     */
-    private static final int MSG_EVENT_CONFIG_UPDATED = MSG_EVENT_BASE;
-
-    /**
-     * A NetworkRequest was added or updated.
-     *
-     * <p>Triggers an evaluation of all active networks, bringing up a new one if necessary.
-     *
-     * @param obj NetworkRequest
-     */
-    private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1;
-
-    /**
-     * The TelephonySubscriptionSnapshot tracked by VcnManagementService has changed.
-     *
-     * <p>This updated snapshot should be cached locally and passed to all VcnGatewayConnections.
-     *
-     * @param obj TelephonySubscriptionSnapshot
-     */
-    private static final int MSG_EVENT_SUBSCRIPTIONS_CHANGED = MSG_EVENT_BASE + 2;
-
-    /**
-     * A GatewayConnection owned by this VCN quit.
-     *
-     * @param obj VcnGatewayConnectionConfig
-     */
-    private static final int MSG_EVENT_GATEWAY_CONNECTION_QUIT = MSG_EVENT_BASE + 3;
-
-    /**
-     * Triggers reevaluation of safe mode conditions.
-     *
-     * <p>Upon entering safe mode, the VCN will only provide gateway connections opportunistically,
-     * leaving the underlying networks marked as NOT_VCN_MANAGED.
-     *
-     * <p>Any VcnGatewayConnection in safe mode will result in the entire Vcn instance being put
-     * into safe mode. Upon receiving this message, the Vcn MUST query all VcnGatewayConnections to
-     * determine if any are in safe mode.
-     */
-    private static final int MSG_EVENT_SAFE_MODE_STATE_CHANGED = MSG_EVENT_BASE + 4;
-
-    /**
-     * Triggers reevaluation of mobile data enabled conditions.
-     *
-     * <p>Upon this notification, the VCN will check if any of the underlying subIds have mobile
-     * data enabled. If not, the VCN will restart any GatewayConnections providing INTERNET or DUN
-     * with the current mobile data toggle status.
-     */
-    private static final int MSG_EVENT_MOBILE_DATA_TOGGLED = MSG_EVENT_BASE + 5;
-
-    /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
-    private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
-
-    @NonNull private final VcnContext mVcnContext;
-    @NonNull private final ParcelUuid mSubscriptionGroup;
-    @NonNull private final Dependencies mDeps;
-    @NonNull private final VcnNetworkRequestListener mRequestListener;
-    @NonNull private final VcnCallback mVcnCallback;
-    @NonNull private final VcnContentResolver mContentResolver;
-    @NonNull private final ContentObserver mMobileDataSettingsObserver;
-
-    @NonNull
-    private final Map<Integer, VcnUserMobileDataStateListener> mMobileDataStateListeners =
-            new ArrayMap<>();
-
-    /**
-     * Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs.
-     *
-     * <p>Due to potential for race conditions, VcnGatewayConnections MUST only be created and added
-     * to this map in {@link #handleNetworkRequested(NetworkRequest, int, int)}, when a VCN receives
-     * a NetworkRequest that matches a VcnGatewayConnectionConfig for this VCN's VcnConfig.
-     *
-     * <p>A VcnGatewayConnection instance MUST NEVER overwrite an existing instance - otherwise
-     * there is potential for a orphaned VcnGatewayConnection instance that does not get properly
-     * shut down.
-     *
-     * <p>Due to potential for race conditions, VcnGatewayConnections MUST only be removed from this
-     * map once they have finished tearing down, which is reported to this VCN via {@link
-     * VcnGatewayStatusCallback#onQuit()}. Once this is done, all NetworkRequests are retrieved from
-     * the NetworkProvider so that another VcnGatewayConnectionConfig can match the
-     * previously-matched request.
-     */
-    // TODO(b/182533200): remove the invariant on VcnGatewayConnection lifecycles
-    @NonNull
-    private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
-            new HashMap<>();
-
-    @NonNull private VcnConfig mConfig;
-    @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
-
-    /**
-     * The current status of this Vcn instance
-     *
-     * <p>The value will be {@link VCN_STATUS_CODE_ACTIVE} while all VcnGatewayConnections are in
-     * good standing, {@link VCN_STATUS_CODE_SAFE_MODE} if any VcnGatewayConnections are in safe
-     * mode, and {@link VCN_STATUS_CODE_INACTIVE} once a teardown has been commanded.
-     */
-    // Accessed from different threads, but always under lock in VcnManagementService
-    private volatile int mCurrentStatus = VCN_STATUS_CODE_ACTIVE;
-
-    private boolean mIsMobileDataEnabled = false;
-
-    public Vcn(
-            @NonNull VcnContext vcnContext,
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull VcnConfig config,
-            @NonNull TelephonySubscriptionSnapshot snapshot,
-            @NonNull VcnCallback vcnCallback) {
-        this(vcnContext, subscriptionGroup, config, snapshot, vcnCallback, new Dependencies());
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public Vcn(
-            @NonNull VcnContext vcnContext,
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull VcnConfig config,
-            @NonNull TelephonySubscriptionSnapshot snapshot,
-            @NonNull VcnCallback vcnCallback,
-            @NonNull Dependencies deps) {
-        super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
-        mVcnContext = vcnContext;
-        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
-        mVcnCallback = Objects.requireNonNull(vcnCallback, "Missing vcnCallback");
-        mDeps = Objects.requireNonNull(deps, "Missing deps");
-        mRequestListener = new VcnNetworkRequestListener();
-        mContentResolver = mDeps.newVcnContentResolver(mVcnContext);
-        mMobileDataSettingsObserver = new VcnMobileDataContentObserver(this /* handler */);
-
-        // TODO: b/364740845: Replace it with DataEnabledListener
-        final Uri uri = Settings.Global.getUriFor(SETTINGS_GLOBAL_MOBILE_DATA_STRING);
-        mContentResolver.registerContentObserver(
-                uri, true /* notifyForDescendants */, mMobileDataSettingsObserver);
-
-        mConfig = Objects.requireNonNull(config, "Missing config");
-        mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
-
-        // Update mIsMobileDataEnabled before starting handling of NetworkRequests.
-        mIsMobileDataEnabled = getMobileDataStatus();
-
-        // Register mobile data state listeners.
-        updateMobileDataStateListeners();
-
-        // Register to receive cached and future NetworkRequests
-        mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
-    }
-
-    /** Asynchronously updates the configuration and triggers a re-evaluation of Networks */
-    public void updateConfig(@NonNull VcnConfig config) {
-        Objects.requireNonNull(config, "Missing config");
-
-        sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config));
-    }
-
-    /** Asynchronously updates the Subscription snapshot for this VCN. */
-    public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
-        Objects.requireNonNull(snapshot, "Missing snapshot");
-
-        sendMessage(obtainMessage(MSG_EVENT_SUBSCRIPTIONS_CHANGED, snapshot));
-    }
-
-    /** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */
-    public void teardownAsynchronously() {
-        sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN));
-    }
-
-    /** Synchronously retrieves the current status code. */
-    public int getStatus() {
-        return mCurrentStatus;
-    }
-
-    /** Sets the status of this VCN */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public void setStatus(int status) {
-        mCurrentStatus = status;
-    }
-
-    /** Get current Gateways for testing purposes */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public Set<VcnGatewayConnection> getVcnGatewayConnections() {
-        return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values()));
-    }
-
-    /** Get current Configs and Gateways for testing purposes */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public Map<VcnGatewayConnectionConfig, VcnGatewayConnection>
-            getVcnGatewayConnectionConfigMap() {
-        return Collections.unmodifiableMap(new HashMap<>(mVcnGatewayConnections));
-    }
-
-    private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener {
-        @Override
-        public void onNetworkRequested(@NonNull NetworkRequest request) {
-            Objects.requireNonNull(request, "Missing request");
-
-            sendMessage(obtainMessage(MSG_EVENT_NETWORK_REQUESTED, request));
-        }
-    }
-
-    @Override
-    public void handleMessage(@NonNull Message msg) {
-        if (mCurrentStatus != VCN_STATUS_CODE_ACTIVE
-                && mCurrentStatus != VCN_STATUS_CODE_SAFE_MODE) {
-            return;
-        }
-
-        switch (msg.what) {
-            case MSG_EVENT_CONFIG_UPDATED:
-                handleConfigUpdated((VcnConfig) msg.obj);
-                break;
-            case MSG_EVENT_NETWORK_REQUESTED:
-                handleNetworkRequested((NetworkRequest) msg.obj);
-                break;
-            case MSG_EVENT_SUBSCRIPTIONS_CHANGED:
-                handleSubscriptionsChanged((TelephonySubscriptionSnapshot) msg.obj);
-                break;
-            case MSG_EVENT_GATEWAY_CONNECTION_QUIT:
-                handleGatewayConnectionQuit((VcnGatewayConnectionConfig) msg.obj);
-                break;
-            case MSG_EVENT_SAFE_MODE_STATE_CHANGED:
-                handleSafeModeStatusChanged();
-                break;
-            case MSG_EVENT_MOBILE_DATA_TOGGLED:
-                handleMobileDataToggled();
-                break;
-            case MSG_CMD_TEARDOWN:
-                handleTeardown();
-                break;
-            default:
-                logWtf("Unknown msg.what: " + msg.what);
-        }
-    }
-
-    private void handleConfigUpdated(@NonNull VcnConfig config) {
-        // TODO: Add a dump function in VcnConfig that omits PII. Until then, use hashCode()
-        logDbg("Config updated: old = " + mConfig.hashCode() + "; new = " + config.hashCode());
-
-        mConfig = config;
-
-        // Teardown any GatewayConnections whose configs have been removed and get all current
-        // requests
-        for (final Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
-                mVcnGatewayConnections.entrySet()) {
-            final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
-            final VcnGatewayConnection gatewayConnection = entry.getValue();
-
-            // GatewayConnectionConfigs must match exactly (otherwise authentication or
-            // connection details may have changed).
-            if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) {
-                if (gatewayConnection == null) {
-                    logWtf("Found gatewayConnectionConfig without GatewayConnection");
-                } else {
-                    logInfo(
-                            "Config updated, restarting gateway "
-                                    + gatewayConnection.getLogPrefix());
-                    gatewayConnection.teardownAsynchronously();
-                }
-            }
-        }
-
-        // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be
-        // satisfied start a new GatewayConnection)
-        mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
-    }
-
-    private void handleTeardown() {
-        logDbg("Tearing down");
-        mVcnContext.getVcnNetworkProvider().unregisterListener(mRequestListener);
-
-        for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
-            gatewayConnection.teardownAsynchronously();
-        }
-
-        // Unregister MobileDataStateListeners
-        for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) {
-            getTelephonyManager().unregisterTelephonyCallback(listener);
-        }
-        mMobileDataStateListeners.clear();
-
-        mCurrentStatus = VCN_STATUS_CODE_INACTIVE;
-    }
-
-    private void handleSafeModeStatusChanged() {
-        logVdbg("VcnGatewayConnection safe mode status changed");
-        boolean hasSafeModeGatewayConnection = false;
-
-        // If any VcnGatewayConnection is in safe mode, mark the entire VCN as being in safe mode
-        for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
-            if (gatewayConnection.isInSafeMode()) {
-                hasSafeModeGatewayConnection = true;
-                break;
-            }
-        }
-
-        final int oldStatus = mCurrentStatus;
-        mCurrentStatus =
-                hasSafeModeGatewayConnection ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
-        if (oldStatus != mCurrentStatus) {
-            mVcnCallback.onSafeModeStatusChanged(hasSafeModeGatewayConnection);
-            logInfo(
-                    "Safe mode "
-                            + (mCurrentStatus == VCN_STATUS_CODE_SAFE_MODE ? "entered" : "exited"));
-        }
-    }
-
-    private void handleNetworkRequested(@NonNull NetworkRequest request) {
-        logVdbg("Received request " + request);
-
-        // If preexisting VcnGatewayConnection(s) satisfy request, return
-        for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
-            if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
-                logVdbg("Request already satisfied by existing VcnGatewayConnection: " + request);
-                return;
-            }
-        }
-
-        // If any supported (but not running) VcnGatewayConnection(s) can satisfy request, bring it
-        // up
-        for (VcnGatewayConnectionConfig gatewayConnectionConfig :
-                mConfig.getGatewayConnectionConfigs()) {
-            if (isRequestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
-                if (getExposedCapabilitiesForMobileDataState(gatewayConnectionConfig).isEmpty()) {
-                    // Skip; this network does not provide any services if mobile data is disabled.
-                    continue;
-                }
-
-                // This should never happen, by virtue of checking for the above check for
-                // pre-existing VcnGatewayConnections that satisfy a given request, but if state
-                // that affects the satsifying of requests changes, this is theoretically possible.
-                if (mVcnGatewayConnections.containsKey(gatewayConnectionConfig)) {
-                    logWtf(
-                            "Attempted to bring up VcnGatewayConnection for config "
-                                    + "with existing VcnGatewayConnection");
-                    return;
-                }
-
-                logInfo("Bringing up new VcnGatewayConnection for request " + request);
-                final VcnGatewayConnection vcnGatewayConnection =
-                        mDeps.newVcnGatewayConnection(
-                                mVcnContext,
-                                mSubscriptionGroup,
-                                mLastSnapshot,
-                                gatewayConnectionConfig,
-                                new VcnGatewayStatusCallbackImpl(gatewayConnectionConfig),
-                                mIsMobileDataEnabled);
-                mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection);
-
-                return;
-            }
-        }
-
-        logVdbg("Request could not be fulfilled by VCN: " + request);
-    }
-
-    private Set<Integer> getExposedCapabilitiesForMobileDataState(
-            VcnGatewayConnectionConfig gatewayConnectionConfig) {
-        if (mIsMobileDataEnabled) {
-            return gatewayConnectionConfig.getAllExposedCapabilities();
-        }
-
-        final Set<Integer> exposedCapsWithoutMobileData =
-                new ArraySet<>(gatewayConnectionConfig.getAllExposedCapabilities());
-        exposedCapsWithoutMobileData.removeAll(CAPS_REQUIRING_MOBILE_DATA);
-
-        return exposedCapsWithoutMobileData;
-    }
-
-    private void handleGatewayConnectionQuit(VcnGatewayConnectionConfig config) {
-        logInfo("VcnGatewayConnection quit: " + config);
-        mVcnGatewayConnections.remove(config);
-
-        // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied
-        // start a new GatewayConnection). VCN is always alive here, courtesy of the liveness check
-        // in handleMessage()
-        mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
-    }
-
-    private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
-        mLastSnapshot = snapshot;
-
-        for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
-            gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
-        }
-
-        updateMobileDataStateListeners();
-
-        // Update the mobile data state after updating the subscription snapshot as a change in
-        // subIds for a subGroup may affect the mobile data state.
-        handleMobileDataToggled();
-    }
-
-    private void updateMobileDataStateListeners() {
-        final Set<Integer> subIdsInGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup);
-        final HandlerExecutor executor = new HandlerExecutor(this);
-
-        // Register new callbacks
-        for (int subId : subIdsInGroup) {
-            if (!mMobileDataStateListeners.containsKey(subId)) {
-                final VcnUserMobileDataStateListener listener =
-                        new VcnUserMobileDataStateListener();
-
-                getTelephonyManagerForSubid(subId).registerTelephonyCallback(executor, listener);
-                mMobileDataStateListeners.put(subId, listener);
-            }
-        }
-
-        // Unregister old callbacks
-        Iterator<Entry<Integer, VcnUserMobileDataStateListener>> iterator =
-                mMobileDataStateListeners.entrySet().iterator();
-        while (iterator.hasNext()) {
-            final Entry<Integer, VcnUserMobileDataStateListener> entry = iterator.next();
-            if (!subIdsInGroup.contains(entry.getKey())) {
-                getTelephonyManager().unregisterTelephonyCallback(entry.getValue());
-                iterator.remove();
-            }
-        }
-    }
-
-    private void handleMobileDataToggled() {
-        final boolean oldMobileDataEnabledStatus = mIsMobileDataEnabled;
-        mIsMobileDataEnabled = getMobileDataStatus();
-
-        if (oldMobileDataEnabledStatus != mIsMobileDataEnabled) {
-            // Teardown any GatewayConnections that advertise INTERNET or DUN. If they provide other
-            // services, the VcnGatewayConnections will be restarted without advertising INTERNET or
-            // DUN.
-            for (Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
-                    mVcnGatewayConnections.entrySet()) {
-                final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
-                final VcnGatewayConnection gatewayConnection = entry.getValue();
-
-                final Set<Integer> exposedCaps =
-                        gatewayConnectionConfig.getAllExposedCapabilities();
-                if (exposedCaps.contains(NET_CAPABILITY_INTERNET)
-                        || exposedCaps.contains(NET_CAPABILITY_DUN)) {
-                    if (gatewayConnection == null) {
-                        logWtf("Found gatewayConnectionConfig without" + " GatewayConnection");
-                    } else {
-                        // TODO(b/184868850): Optimize by restarting NetworkAgents without teardown.
-                        gatewayConnection.teardownAsynchronously();
-                    }
-                }
-            }
-
-            // Trigger re-evaluation of all requests; mobile data state impacts supported caps.
-            mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
-
-            logInfo("Mobile data " + (mIsMobileDataEnabled ? "enabled" : "disabled"));
-        }
-    }
-
-    private boolean getMobileDataStatus() {
-        for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
-            if (getTelephonyManagerForSubid(subId).isDataEnabled()) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    private boolean isRequestSatisfiedByGatewayConnectionConfig(
-            @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
-        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
-        builder.addTransportType(TRANSPORT_CELLULAR);
-        builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
-        for (int cap : getExposedCapabilitiesForMobileDataState(config)) {
-            builder.addCapability(cap);
-        }
-
-        return request.canBeSatisfiedBy(builder.build());
-    }
-
-    private TelephonyManager getTelephonyManager() {
-        return mVcnContext.getContext().getSystemService(TelephonyManager.class);
-    }
-
-    private TelephonyManager getTelephonyManagerForSubid(int subid) {
-        return getTelephonyManager().createForSubscriptionId(subid);
-    }
-
-    private String getLogPrefix() {
-        return "("
-                + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
-                + "-"
-                + System.identityHashCode(this)
-                + ") ";
-    }
-
-    private void logVdbg(String msg) {
-        if (VDBG) {
-            Slog.v(TAG, getLogPrefix() + msg);
-        }
-    }
-
-    private void logDbg(String msg) {
-        Slog.d(TAG, getLogPrefix() + msg);
-    }
-
-    private void logDbg(String msg, Throwable tr) {
-        Slog.d(TAG, getLogPrefix() + msg, tr);
-    }
-
-    private void logInfo(String msg) {
-        Slog.i(TAG, getLogPrefix() + msg);
-        LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg);
-    }
-
-    private void logInfo(String msg, Throwable tr) {
-        Slog.i(TAG, getLogPrefix() + msg, tr);
-        LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg + tr);
-    }
-
-    private void logErr(String msg) {
-        Slog.e(TAG, getLogPrefix() + msg);
-        LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg);
-    }
-
-    private void logErr(String msg, Throwable tr) {
-        Slog.e(TAG, getLogPrefix() + msg, tr);
-        LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg + tr);
-    }
-
-    private void logWtf(String msg) {
-        Slog.wtf(TAG, getLogPrefix() + msg);
-        LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg);
-    }
-
-    private void logWtf(String msg, Throwable tr) {
-        Slog.wtf(TAG, getLogPrefix() + msg, tr);
-        LOCAL_LOG.log(getLogPrefix() + "WTF: " + msg + tr);
-    }
-
-    /**
-     * Dumps the state of this Vcn for logging and debugging purposes.
-     *
-     * <p>PII and credentials MUST NEVER be dumped here.
-     *
-     * <p>This method is not thread safe and MUST run on the VCN thread.
-     */
-    public void dump(IndentingPrintWriter pw) {
-        mVcnContext.ensureRunningOnLooperThread();
-
-        pw.println("Vcn (" + mSubscriptionGroup + "):");
-        pw.increaseIndent();
-
-        pw.println("mCurrentStatus: " + mCurrentStatus);
-        pw.println("mIsMobileDataEnabled: " + mIsMobileDataEnabled);
-        pw.println();
-
-        pw.println("mVcnGatewayConnections:");
-        pw.increaseIndent();
-        for (VcnGatewayConnection gw : mVcnGatewayConnections.values()) {
-            gw.dump(pw);
-        }
-        pw.decreaseIndent();
-        pw.println();
-
-        pw.decreaseIndent();
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public boolean isMobileDataEnabled() {
-        return mIsMobileDataEnabled;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public void setMobileDataEnabled(boolean isMobileDataEnabled) {
-        mIsMobileDataEnabled = isMobileDataEnabled;
-    }
-
-    /** Retrieves the network score for a VCN Network */
-    // Package visibility for use in VcnGatewayConnection and VcnNetworkProvider
-    static NetworkScore getNetworkScore() {
-        // TODO(b/193687515): Stop setting TRANSPORT_PRIMARY, define a TRANSPORT_VCN, and set in
-        //                    NetworkOffer/NetworkAgent.
-        return new NetworkScore.Builder()
-                .setLegacyInt(VCN_LEGACY_SCORE_INT)
-                .setTransportPrimary(true)
-                .build();
-    }
-
-    /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */
-    @VisibleForTesting(visibility = Visibility.PACKAGE)
-    public interface VcnGatewayStatusCallback {
-        /** Called by a VcnGatewayConnection to indicate that it's safe mode status has changed. */
-        void onSafeModeStatusChanged();
-
-        /** Callback by a VcnGatewayConnection to indicate that an error occurred. */
-        void onGatewayConnectionError(
-                @NonNull String gatewayConnectionName,
-                @VcnErrorCode int errorCode,
-                @Nullable String exceptionClass,
-                @Nullable String exceptionMessage);
-
-        /** Called by a VcnGatewayConnection to indicate that it has fully torn down. */
-        void onQuit();
-    }
-
-    private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
-        public final VcnGatewayConnectionConfig mGatewayConnectionConfig;
-
-        VcnGatewayStatusCallbackImpl(VcnGatewayConnectionConfig gatewayConnectionConfig) {
-            mGatewayConnectionConfig = gatewayConnectionConfig;
-        }
-
-        @Override
-        public void onQuit() {
-            sendMessage(obtainMessage(MSG_EVENT_GATEWAY_CONNECTION_QUIT, mGatewayConnectionConfig));
-        }
-
-        @Override
-        public void onSafeModeStatusChanged() {
-            sendMessage(obtainMessage(MSG_EVENT_SAFE_MODE_STATE_CHANGED));
-        }
-
-        @Override
-        public void onGatewayConnectionError(
-                @NonNull String gatewayConnectionName,
-                @VcnErrorCode int errorCode,
-                @Nullable String exceptionClass,
-                @Nullable String exceptionMessage) {
-            mVcnCallback.onGatewayConnectionError(
-                    gatewayConnectionName, errorCode, exceptionClass, exceptionMessage);
-        }
-    }
-
-    private class VcnMobileDataContentObserver extends ContentObserver {
-        private VcnMobileDataContentObserver(Handler handler) {
-            super(handler);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
-        }
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    class VcnUserMobileDataStateListener extends TelephonyCallback
-            implements TelephonyCallback.UserMobileDataStateListener {
-
-        @Override
-        public void onUserMobileDataStateChanged(boolean enabled) {
-            sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
-        }
-    }
-
-    /** External dependencies used by Vcn, for injection in tests */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class Dependencies {
-        /** Builds a new VcnGatewayConnection */
-        public VcnGatewayConnection newVcnGatewayConnection(
-                VcnContext vcnContext,
-                ParcelUuid subscriptionGroup,
-                TelephonySubscriptionSnapshot snapshot,
-                VcnGatewayConnectionConfig connectionConfig,
-                VcnGatewayStatusCallback gatewayStatusCallback,
-                boolean isMobileDataEnabled) {
-            return new VcnGatewayConnection(
-                    vcnContext,
-                    subscriptionGroup,
-                    snapshot,
-                    connectionConfig,
-                    gatewayStatusCallback,
-                    isMobileDataEnabled);
-        }
-
-        /** Builds a new VcnContentResolver instance */
-        public VcnContentResolver newVcnContentResolver(VcnContext vcnContext) {
-            return new VcnContentResolver(vcnContext);
-        }
-    }
-
-    /** Proxy Implementation of NetworkAgent, used for testing. */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class VcnContentResolver {
-        private final ContentResolver mImpl;
-
-        public VcnContentResolver(VcnContext vcnContext) {
-            mImpl = vcnContext.getContext().getContentResolver();
-        }
-
-        /** Registers the content observer */
-        public void registerContentObserver(
-                @NonNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer) {
-            mImpl.registerContentObserver(uri, notifyForDescendants, observer);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
deleted file mode 100644
index 2325f35..0000000
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ /dev/null
@@ -1,3035 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.vcn;
-
-import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
-import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
-import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR;
-import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR;
-import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;
-
-import static com.android.server.VcnManagementService.LOCAL_LOG;
-import static com.android.server.VcnManagementService.VDBG;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.net.ConnectivityDiagnosticsManager;
-import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
-import android.net.ConnectivityManager;
-import android.net.InetAddresses;
-import android.net.IpPrefix;
-import android.net.IpSecManager;
-import android.net.IpSecManager.IpSecTunnelInterface;
-import android.net.IpSecManager.ResourceUnavailableException;
-import android.net.IpSecTransform;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.NetworkAgent;
-import android.net.NetworkAgentConfig;
-import android.net.NetworkCapabilities;
-import android.net.NetworkProvider;
-import android.net.NetworkRequest;
-import android.net.NetworkScore;
-import android.net.RouteInfo;
-import android.net.TelephonyNetworkSpecifier;
-import android.net.Uri;
-import android.net.annotations.PolicyDirection;
-import android.net.ipsec.ike.ChildSaProposal;
-import android.net.ipsec.ike.ChildSessionCallback;
-import android.net.ipsec.ike.ChildSessionConfiguration;
-import android.net.ipsec.ike.ChildSessionParams;
-import android.net.ipsec.ike.IkeSession;
-import android.net.ipsec.ike.IkeSessionCallback;
-import android.net.ipsec.ike.IkeSessionConfiguration;
-import android.net.ipsec.ike.IkeSessionConnectionInfo;
-import android.net.ipsec.ike.IkeSessionParams;
-import android.net.ipsec.ike.IkeTrafficSelector;
-import android.net.ipsec.ike.IkeTunnelConnectionParams;
-import android.net.ipsec.ike.TunnelModeChildSessionParams;
-import android.net.ipsec.ike.exceptions.IkeException;
-import android.net.ipsec.ike.exceptions.IkeInternalException;
-import android.net.ipsec.ike.exceptions.IkeProtocolException;
-import android.net.vcn.VcnGatewayConnectionConfig;
-import android.net.vcn.VcnManager;
-import android.net.vcn.VcnTransportInfo;
-import android.net.wifi.WifiInfo;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.Message;
-import android.os.ParcelUuid;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.Process;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.telephony.TelephonyManager;
-import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.internal.util.WakeupMessage;
-import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
-import com.android.server.vcn.routeselection.UnderlyingNetworkController;
-import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback;
-import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
-import com.android.server.vcn.util.LogUtils;
-import com.android.server.vcn.util.MtuUtils;
-import com.android.server.vcn.util.OneWayBoolean;
-
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-
-/**
- * A single VCN Gateway Connection, providing a single public-facing VCN network.
- *
- * <p>This class handles mobility events, performs retries, and tracks safe-mode conditions.
- *
- * <pre>Internal state transitions are as follows:
- *
- * +----------------------------+                 +------------------------------+
- * |     DisconnectedState      |    Teardown or  |      DisconnectingState      |
- * |                            |<--no available--|                              |
- * |       Initial state.       |    underlying   | Transitive state for tearing |
- * +----------------------------+     networks    | tearing down an IKE session. |
- *               |                                +------------------------------+
- *               |                                         ^          |
- *       Underlying Network            Teardown requested  |   Not tearing down
- *            changed               +--or retriable error--+  and has available
- *               |                  |      occurred           underlying network
- *               |                  ^                                 |
- *               v                  |                                 v
- * +----------------------------+   |             +------------------------------+
- * |      ConnectingState       |<----------------|      RetryTimeoutState       |
- * |                            |   |             |                              |
- * |    Transitive state for    |   |             |     Transitive state for     |
- * |  starting IKE negotiation. |---+             |  handling retriable errors.  |
- * +----------------------------+   |             +------------------------------+
- *               |                  |
- *          IKE session             |
- *           negotiated             |
- *               |                  |
- *               v                  |
- * +----------------------------+   ^
- * |      ConnectedState        |   |
- * |                            |   |
- * |     Stable state where     |   |
- * |  gateway connection is set |   |
- * | up, and Android Network is |   |
- * |         connected.         |---+
- * +----------------------------+
- * </pre>
- *
- * <p>All messages in VcnGatewayConnection <b>should</b> be enqueued using {@link
- * #sendMessageAndAcquireWakeLock}. Careful consideration should be given to any uses of {@link
- * #sendMessage} directly, as they are not guaranteed to be processed in a timely manner (due to the
- * lack of WakeLocks).
- *
- * <p>Any attempt to remove messages from the Handler should be done using {@link
- * #removeEqualMessages}. This is necessary to ensure that the WakeLock is correctly released when
- * no messages remain in the Handler queue.
- *
- * @hide
- */
-public class VcnGatewayConnection extends StateMachine {
-    private static final String TAG = VcnGatewayConnection.class.getSimpleName();
-
-    /** Default number of parallel SAs requested */
-    static final int TUNNEL_AGGREGATION_SA_COUNT_MAX_DEFAULT = 1;
-
-    // The returned string of
-    // TelephonyManager#getNetworkTypeName(TelephonyManager.NETWORK_TYPE_UNKNOWN)
-    private static final String NETWORK_TYPE_STRING_UNKNOWN = "UNKNOWN";
-
-    // Matches DataConnection.NETWORK_TYPE private constant, and magic string from
-    // ConnectivityManager#getNetworkTypeName()
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final String NETWORK_INFO_NETWORK_TYPE_STRING = "MOBILE";
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final String NETWORK_INFO_EXTRA_INFO = "VCN";
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0");
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final String TEARDOWN_TIMEOUT_ALARM = TAG + "_TEARDOWN_TIMEOUT_ALARM";
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final String DISCONNECT_REQUEST_ALARM = TAG + "_DISCONNECT_REQUEST_ALARM";
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final String RETRY_TIMEOUT_ALARM = TAG + "_RETRY_TIMEOUT_ALARM";
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final String SAFEMODE_TIMEOUT_ALARM = TAG + "_SAFEMODE_TIMEOUT_ALARM";
-
-    private static final int[] MERGED_CAPABILITIES =
-            new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING};
-    private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE;
-
-    private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: ";
-    private static final String DISCONNECT_REASON_UNDERLYING_NETWORK_LOST =
-            "Underlying Network lost";
-    private static final String DISCONNECT_REASON_NETWORK_AGENT_UNWANTED =
-            "NetworkAgent was unwanted";
-    private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel";
-    private static final int TOKEN_ALL = Integer.MIN_VALUE;
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30;
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int TEARDOWN_TIMEOUT_SECONDS = 5;
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int SAFEMODE_TIMEOUT_SECONDS = 30;
-    private static final int SAFEMODE_TIMEOUT_SECONDS_TEST_MODE = 10;
-
-    private interface EventInfo {}
-
-    /**
-     * Sent when there are changes to the underlying network (per the UnderlyingNetworkController).
-     *
-     * <p>May indicate an entirely new underlying network, OR a change in network properties.
-     *
-     * <p>Relevant in ALL states.
-     *
-     * <p>In the Connected state, this MAY indicate a mobility even occurred.
-     *
-     * @param arg1 The "all" token; this event is always applicable.
-     * @param obj @NonNull An EventUnderlyingNetworkChangedInfo instance with relevant data.
-     */
-    private static final int EVENT_UNDERLYING_NETWORK_CHANGED = 1;
-
-    private static class EventUnderlyingNetworkChangedInfo implements EventInfo {
-        @Nullable public final UnderlyingNetworkRecord newUnderlying;
-
-        EventUnderlyingNetworkChangedInfo(@Nullable UnderlyingNetworkRecord newUnderlying) {
-            this.newUnderlying = newUnderlying;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(newUnderlying);
-        }
-
-        @Override
-        public boolean equals(@Nullable Object other) {
-            if (!(other instanceof EventUnderlyingNetworkChangedInfo)) {
-                return false;
-            }
-
-            final EventUnderlyingNetworkChangedInfo rhs = (EventUnderlyingNetworkChangedInfo) other;
-            return Objects.equals(newUnderlying, rhs.newUnderlying);
-        }
-    }
-
-    /**
-     * Sent (delayed) to trigger an attempt to reestablish the tunnel.
-     *
-     * <p>Only relevant in the Retry-timeout state, discarded in all other states.
-     *
-     * <p>Upon receipt of this signal, the state machine will transition from the Retry-timeout
-     * state to the Connecting state.
-     *
-     * @param arg1 The "all" token; no sessions are active in the RetryTimeoutState.
-     */
-    private static final int EVENT_RETRY_TIMEOUT_EXPIRED = 2;
-
-    /**
-     * Sent when a gateway connection has been lost, either due to a IKE or child failure.
-     *
-     * <p>Relevant in all states that have an IKE session.
-     *
-     * <p>Upon receipt of this signal, the state machine will (unless loss of the session is
-     * expected) transition to the Disconnecting state, to ensure IKE session closure before
-     * retrying, or fully shutting down.
-     *
-     * @param arg1 The session token for the IKE Session that was lost, used to prevent out-of-date
-     *     signals from propagating.
-     * @param obj @NonNull An EventSessionLostInfo instance with relevant data.
-     */
-    private static final int EVENT_SESSION_LOST = 3;
-
-    private static class EventSessionLostInfo implements EventInfo {
-        @Nullable public final Exception exception;
-
-        EventSessionLostInfo(@NonNull Exception exception) {
-            this.exception = exception;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(exception);
-        }
-
-        @Override
-        public boolean equals(@Nullable Object other) {
-            if (!(other instanceof EventSessionLostInfo)) {
-                return false;
-            }
-
-            final EventSessionLostInfo rhs = (EventSessionLostInfo) other;
-            return Objects.equals(exception, rhs.exception);
-        }
-    }
-
-    /**
-     * Sent when an IKE session has completely closed.
-     *
-     * <p>Relevant only in the Disconnecting State, used to identify that a session being torn down
-     * was fully closed. If this event is not fired within a timely fashion, the IKE session will be
-     * forcibly terminated.
-     *
-     * <p>Upon receipt of this signal, the state machine will (unless closure of the session is
-     * expected) transition to the Disconnected or RetryTimeout states, depending on whether the
-     * GatewayConnection is being fully torn down.
-     *
-     * @param arg1 The session token for the IKE Session that was lost, used to prevent out-of-date
-     *     signals from propagating.
-     * @param obj @NonNull An EventSessionLostInfo instance with relevant data.
-     */
-    private static final int EVENT_SESSION_CLOSED = 4;
-
-    /**
-     * Sent when an IKE Child Transform was created, and should be applied to the tunnel.
-     *
-     * <p>Only relevant in the Connecting, Connected and Migrating states. This callback MUST be
-     * handled in the Connected or Migrating states, and should be deferred if necessary.
-     *
-     * @param arg1 The session token for the IKE Session that had a new child created, used to
-     *     prevent out-of-date signals from propagating.
-     * @param obj @NonNull An EventTransformCreatedInfo instance with relevant data.
-     */
-    private static final int EVENT_TRANSFORM_CREATED = 5;
-
-    private static class EventTransformCreatedInfo implements EventInfo {
-        @PolicyDirection public final int direction;
-        @NonNull public final IpSecTransform transform;
-
-        EventTransformCreatedInfo(
-                @PolicyDirection int direction, @NonNull IpSecTransform transform) {
-            this.direction = direction;
-            this.transform = Objects.requireNonNull(transform);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(direction, transform);
-        }
-
-        @Override
-        public boolean equals(@Nullable Object other) {
-            if (!(other instanceof EventTransformCreatedInfo)) {
-                return false;
-            }
-
-            final EventTransformCreatedInfo rhs = (EventTransformCreatedInfo) other;
-            return direction == rhs.direction && Objects.equals(transform, rhs.transform);
-        }
-    }
-
-    /**
-     * Sent when an IKE Child Session was completely opened and configured successfully.
-     *
-     * <p>Only relevant in the Connected and Migrating states.
-     *
-     * @param arg1 The session token for the IKE Session for which a child was opened and configured
-     *     successfully, used to prevent out-of-date signals from propagating.
-     * @param obj @NonNull An EventSetupCompletedInfo instance with relevant data.
-     */
-    private static final int EVENT_SETUP_COMPLETED = 6;
-
-    private static class EventSetupCompletedInfo implements EventInfo {
-        @NonNull public final VcnChildSessionConfiguration childSessionConfig;
-
-        EventSetupCompletedInfo(@NonNull VcnChildSessionConfiguration childSessionConfig) {
-            this.childSessionConfig = Objects.requireNonNull(childSessionConfig);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(childSessionConfig);
-        }
-
-        @Override
-        public boolean equals(@Nullable Object other) {
-            if (!(other instanceof EventSetupCompletedInfo)) {
-                return false;
-            }
-
-            final EventSetupCompletedInfo rhs = (EventSetupCompletedInfo) other;
-            return Objects.equals(childSessionConfig, rhs.childSessionConfig);
-        }
-    }
-
-    /**
-     * Sent when conditions (internal or external) require a disconnect.
-     *
-     * <p>Relevant in all states except the Disconnected state.
-     *
-     * <p>This signal is often fired with a timeout in order to prevent disconnecting during
-     * transient conditions, such as network switches. Upon the transient passing, the signal is
-     * canceled based on the disconnect reason.
-     *
-     * <p>Upon receipt of this signal, the state machine MUST tear down all active sessions, cancel
-     * any pending work items, and move to the Disconnected state.
-     *
-     * @param arg1 The "all" token; this signal is always honored.
-     * @param obj @NonNull An EventDisconnectRequestedInfo instance with relevant data.
-     */
-    private static final int EVENT_DISCONNECT_REQUESTED = 7;
-
-    private static class EventDisconnectRequestedInfo implements EventInfo {
-        /** The reason why the disconnect was requested. */
-        @NonNull public final String reason;
-
-        public final boolean shouldQuit;
-
-        EventDisconnectRequestedInfo(@NonNull String reason, boolean shouldQuit) {
-            this.reason = Objects.requireNonNull(reason);
-            this.shouldQuit = shouldQuit;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(reason, shouldQuit);
-        }
-
-        @Override
-        public boolean equals(@Nullable Object other) {
-            if (!(other instanceof EventDisconnectRequestedInfo)) {
-                return false;
-            }
-
-            final EventDisconnectRequestedInfo rhs = (EventDisconnectRequestedInfo) other;
-            return reason.equals(rhs.reason) && shouldQuit == rhs.shouldQuit;
-        }
-    }
-
-    /**
-     * Sent (delayed) to trigger a forcible close of an IKE session.
-     *
-     * <p>Only relevant in the Disconnecting state, discarded in all other states.
-     *
-     * <p>Upon receipt of this signal, the state machine will transition from the Disconnecting
-     * state to the Disconnected state.
-     *
-     * @param arg1 The session token for the IKE Session that is being torn down, used to prevent
-     *     out-of-date signals from propagating.
-     */
-    private static final int EVENT_TEARDOWN_TIMEOUT_EXPIRED = 8;
-
-    /**
-     * Sent when this VcnGatewayConnection is notified of a change in TelephonySubscriptions.
-     *
-     * <p>Relevant in all states.
-     *
-     * @param arg1 The "all" token; this signal is always honored.
-     */
-    // TODO(b/178426520): implement handling of this event
-    private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9;
-
-    /**
-     * Sent when this VcnGatewayConnection has entered safe mode.
-     *
-     * <p>A VcnGatewayConnection enters safe mode when it takes over {@link
-     * #SAFEMODE_TIMEOUT_SECONDS} to enter {@link ConnectedState}.
-     *
-     * <p>When a VcnGatewayConnection enters safe mode, it will fire {@link
-     * VcnGatewayStatusCallback#onEnteredSafeMode()} to notify its Vcn. The Vcn will then shut down
-     * its VcnGatewayConnectin(s).
-     *
-     * <p>Relevant in DisconnectingState, ConnectingState, ConnectedState (if the Vcn Network is not
-     * validated yet), and RetryTimeoutState.
-     *
-     * @param arg1 The "all" token; this signal is always honored.
-     */
-    private static final int EVENT_SAFE_MODE_TIMEOUT_EXCEEDED = 10;
-
-    /**
-     * Sent when an IKE has completed migration, and created updated transforms for application.
-     *
-     * <p>Only relevant in the Connected state.
-     *
-     * @param arg1 The session token for the IKE Session that completed migration, used to prevent
-     *     out-of-date signals from propagating.
-     * @param obj @NonNull An EventMigrationCompletedInfo instance with relevant data.
-     */
-    private static final int EVENT_MIGRATION_COMPLETED = 11;
-
-    private static class EventMigrationCompletedInfo implements EventInfo {
-        @NonNull public final IpSecTransform inTransform;
-        @NonNull public final IpSecTransform outTransform;
-
-        EventMigrationCompletedInfo(
-                @NonNull IpSecTransform inTransform, @NonNull IpSecTransform outTransform) {
-            this.inTransform = Objects.requireNonNull(inTransform);
-            this.outTransform = Objects.requireNonNull(outTransform);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(inTransform, outTransform);
-        }
-
-        @Override
-        public boolean equals(@Nullable Object other) {
-            if (!(other instanceof EventMigrationCompletedInfo)) {
-                return false;
-            }
-
-            final EventMigrationCompletedInfo rhs = (EventMigrationCompletedInfo) other;
-            return Objects.equals(inTransform, rhs.inTransform)
-                    && Objects.equals(outTransform, rhs.outTransform);
-        }
-    }
-
-    /**
-     * Sent when an IKE session connection information has changed.
-     *
-     * <p>This signal is always fired before EVENT_SETUP_COMPLETED and EVENT_MIGRATION_COMPLETED.
-     *
-     * <p>Only relevant in the Connecting and Connected state.
-     *
-     * @param arg1 The session token for the IKE Session whose connection information has changed,
-     *     used to prevent out-of-date signals from propagating.
-     * @param obj @NonNull An EventIkeConnectionInfoChangedInfo instance with relevant data.
-     */
-    private static final int EVENT_IKE_CONNECTION_INFO_CHANGED = 12;
-
-    private static class EventIkeConnectionInfoChangedInfo implements EventInfo {
-        @NonNull public final IkeSessionConnectionInfo ikeConnectionInfo;
-
-        EventIkeConnectionInfoChangedInfo(@NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
-            this.ikeConnectionInfo = ikeConnectionInfo;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(ikeConnectionInfo);
-        }
-
-        @Override
-        public boolean equals(@Nullable Object other) {
-            if (!(other instanceof EventIkeConnectionInfoChangedInfo)) {
-                return false;
-            }
-
-            final EventIkeConnectionInfoChangedInfo rhs = (EventIkeConnectionInfoChangedInfo) other;
-            return Objects.equals(ikeConnectionInfo, rhs.ikeConnectionInfo);
-        }
-    }
-
-    /**
-     * Sent when there is a suspected data stall on a network
-     *
-     * <p>Only relevant in the Connected state.
-     *
-     * @param arg1 The "all" token; this signal is always honored.
-     * @param obj @NonNull An EventDataStallSuspectedInfo instance with relevant data.
-     */
-    private static final int EVENT_DATA_STALL_SUSPECTED = 13;
-
-    private static class EventDataStallSuspectedInfo implements EventInfo {
-        @NonNull public final Network network;
-
-        EventDataStallSuspectedInfo(@NonNull Network network) {
-            this.network = network;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(network);
-        }
-
-        @Override
-        public boolean equals(@Nullable Object other) {
-            if (!(other instanceof EventDataStallSuspectedInfo)) {
-                return false;
-            }
-
-            final EventDataStallSuspectedInfo rhs = (EventDataStallSuspectedInfo) other;
-            return Objects.equals(network, rhs.network);
-        }
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    @NonNull
-    final DisconnectedState mDisconnectedState = new DisconnectedState();
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    @NonNull
-    final DisconnectingState mDisconnectingState = new DisconnectingState();
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    @NonNull
-    final ConnectingState mConnectingState = new ConnectingState();
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    @NonNull
-    final ConnectedState mConnectedState = new ConnectedState();
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    @NonNull
-    final RetryTimeoutState mRetryTimeoutState = new RetryTimeoutState();
-
-    @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
-
-    @NonNull private final VcnContext mVcnContext;
-    @NonNull private final ParcelUuid mSubscriptionGroup;
-    @NonNull private final UnderlyingNetworkController mUnderlyingNetworkController;
-    @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
-    @NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback;
-    @NonNull private final Dependencies mDeps;
-
-    @NonNull
-    private final VcnUnderlyingNetworkControllerCallback mUnderlyingNetworkControllerCallback;
-
-    @NonNull private final VcnConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback;
-
-    private final boolean mIsMobileDataEnabled;
-
-    @NonNull private final IpSecManager mIpSecManager;
-    @NonNull private final ConnectivityManager mConnectivityManager;
-    @NonNull private final ConnectivityDiagnosticsManager mConnectivityDiagnosticsManager;
-
-    @Nullable private IpSecTunnelInterface mTunnelIface = null;
-
-    /**
-     * WakeLock to be held when processing messages on the Handler queue.
-     *
-     * <p>Used to prevent the device from going to sleep while there are VCN-related events to
-     * process for this VcnGatewayConnection.
-     *
-     * <p>Obtain a WakeLock when enquing messages onto the Handler queue. Once all messages in the
-     * Handler queue have been processed, the WakeLock can be released and cleared.
-     *
-     * <p>This WakeLock is also used for handling delayed messages by using WakeupMessages to send
-     * delayed messages to the Handler. When the WakeupMessage fires, it will obtain the WakeLock
-     * before enquing the delayed event to the Handler.
-     */
-    @NonNull private final VcnWakeLock mWakeLock;
-
-    /**
-     * Whether the VcnGatewayConnection is in the process of irreversibly quitting.
-     *
-     * <p>This variable is false for the lifecycle of the VcnGatewayConnection, until a command to
-     * teardown has been received. This may be flipped due to events such as the Network becoming
-     * unwanted, the owning VCN entering safe mode, or an irrecoverable internal failure.
-     *
-     * <p>WARNING: Assignments to this MUST ALWAYS (except for testing) use the or operator ("|="),
-     * otherwise the flag may be flipped back to false after having been set to true. This could
-     * lead to a case where the Vcn parent instance has commanded a teardown, but a spurious
-     * non-quitting disconnect request could flip this back to true.
-     */
-    private OneWayBoolean mIsQuitting = new OneWayBoolean();
-
-    /**
-     * Whether the VcnGatewayConnection is in safe mode.
-     *
-     * <p>Upon hitting the safe mode timeout, this will be set to {@code true}. In safe mode, this
-     * VcnGatewayConnection will continue attempting to connect, and if a successful connection is
-     * made, safe mode will be exited.
-     */
-    private boolean mIsInSafeMode = false;
-
-    /**
-     * The token used by the primary/current/active session.
-     *
-     * <p>This token MUST be updated when a new stateful/async session becomes the
-     * primary/current/active session. Example cases where the session changes are:
-     *
-     * <ul>
-     *   <li>Switching to an IKE session as the primary session
-     * </ul>
-     *
-     * <p>In the migrating state, where two sessions may be active, this value MUST represent the
-     * primary session. This is USUALLY the existing session, and is only switched to the new
-     * session when:
-     *
-     * <ul>
-     *   <li>The new session connects successfully, and becomes the primary session
-     *   <li>The existing session is lost, and the remaining (new) session becomes the primary
-     *       session
-     * </ul>
-     */
-    private int mCurrentToken = -1;
-
-    /**
-     * The number of unsuccessful attempts since the last successful connection.
-     *
-     * <p>This number MUST be incremented each time the RetryTimeout state is entered, and cleared
-     * each time the Connected state is entered.
-     */
-    private int mFailedAttempts = 0;
-
-    /**
-     * The current underlying network.
-     *
-     * <p>Set in any states, always @NonNull in all states except Disconnected, null otherwise.
-     */
-    private UnderlyingNetworkRecord mUnderlying;
-
-    /**
-     * The current IKE Session connection information
-     *
-     * <p>Set in Connected and Migrating states, always @NonNull in Connected, Migrating
-     * states, @Nullable otherwise.
-     */
-    private IkeSessionConnectionInfo mIkeConnectionInfo;
-
-    /**
-     * The active IKE session.
-     *
-     * <p>Set in Connecting or Migrating States, always @NonNull in Connecting, Connected, and
-     * Migrating states, null otherwise.
-     */
-    private VcnIkeSession mIkeSession;
-
-    /**
-     * The last known child configuration.
-     *
-     * <p>Set in Connected and Migrating states, always @NonNull in Connected, Migrating
-     * states, @Nullable otherwise.
-     */
-    private VcnChildSessionConfiguration mChildConfig;
-
-    /**
-     * The active network agent.
-     *
-     * <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable
-     * otherwise.
-     */
-    private VcnNetworkAgent mNetworkAgent;
-
-    @Nullable private WakeupMessage mTeardownTimeoutAlarm;
-    @Nullable private WakeupMessage mDisconnectRequestAlarm;
-    @Nullable private WakeupMessage mRetryTimeoutAlarm;
-    @Nullable private WakeupMessage mSafeModeTimeoutAlarm;
-
-    public VcnGatewayConnection(
-            @NonNull VcnContext vcnContext,
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull TelephonySubscriptionSnapshot snapshot,
-            @NonNull VcnGatewayConnectionConfig connectionConfig,
-            @NonNull VcnGatewayStatusCallback gatewayStatusCallback,
-            boolean isMobileDataEnabled) {
-        this(
-                vcnContext,
-                subscriptionGroup,
-                snapshot,
-                connectionConfig,
-                gatewayStatusCallback,
-                isMobileDataEnabled,
-                new Dependencies());
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    VcnGatewayConnection(
-            @NonNull VcnContext vcnContext,
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull TelephonySubscriptionSnapshot snapshot,
-            @NonNull VcnGatewayConnectionConfig connectionConfig,
-            @NonNull VcnGatewayStatusCallback gatewayStatusCallback,
-            boolean isMobileDataEnabled,
-            @NonNull Dependencies deps) {
-        super(TAG, Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
-        mVcnContext = vcnContext;
-        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
-        mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
-        mGatewayStatusCallback =
-                Objects.requireNonNull(gatewayStatusCallback, "Missing gatewayStatusCallback");
-        mIsMobileDataEnabled = isMobileDataEnabled;
-        mDeps = Objects.requireNonNull(deps, "Missing deps");
-
-        mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
-
-        mUnderlyingNetworkControllerCallback = new VcnUnderlyingNetworkControllerCallback();
-
-        mWakeLock =
-                mDeps.newWakeLock(mVcnContext.getContext(), PowerManager.PARTIAL_WAKE_LOCK, TAG);
-
-        mUnderlyingNetworkController =
-                mDeps.newUnderlyingNetworkController(
-                        mVcnContext,
-                        mConnectionConfig,
-                        subscriptionGroup,
-                        mLastSnapshot,
-                        mUnderlyingNetworkControllerCallback);
-        mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
-        mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
-        mConnectivityDiagnosticsManager =
-                mVcnContext.getContext().getSystemService(ConnectivityDiagnosticsManager.class);
-
-        mConnectivityDiagnosticsCallback = new VcnConnectivityDiagnosticsCallback();
-
-        if (mConnectionConfig.hasGatewayOption(
-                VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY)) {
-            final NetworkRequest diagRequest =
-                    new NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build();
-            mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(
-                    diagRequest,
-                    new HandlerExecutor(new Handler(vcnContext.getLooper())),
-                    mConnectivityDiagnosticsCallback);
-        }
-
-        addState(mDisconnectedState);
-        addState(mDisconnectingState);
-        addState(mConnectingState);
-        addState(mConnectedState);
-        addState(mRetryTimeoutState);
-
-        setInitialState(mDisconnectedState);
-        setDbg(VDBG);
-        start();
-    }
-
-    /** Queries whether this VcnGatewayConnection is in safe mode. */
-    public boolean isInSafeMode() {
-        // Accessing internal state; must only be done on looper thread.
-        mVcnContext.ensureRunningOnLooperThread();
-
-        return mIsInSafeMode;
-    }
-
-    /**
-     * Asynchronously tears down this GatewayConnection, and any resources used.
-     *
-     * <p>Once torn down, this VcnTunnel CANNOT be started again.
-     */
-    public void teardownAsynchronously() {
-        logDbg("Triggering async teardown");
-        sendDisconnectRequestedAndAcquireWakelock(
-                DISCONNECT_REASON_TEARDOWN, true /* shouldQuit */);
-    }
-
-    @Override
-    protected void onQuitting() {
-        logInfo("Quitting VcnGatewayConnection");
-
-        if (mNetworkAgent != null) {
-            logWtf("NetworkAgent was non-null in onQuitting");
-            mNetworkAgent.unregister();
-            mNetworkAgent = null;
-        }
-
-        if (mIkeSession != null) {
-            logWtf("IkeSession was non-null in onQuitting");
-            mIkeSession.kill();
-            mIkeSession = null;
-        }
-
-        // No need to call setInterfaceDown(); the IpSecInterface is being fully torn down.
-        if (mTunnelIface != null) {
-            mTunnelIface.close();
-        }
-
-        releaseWakeLock();
-
-        cancelTeardownTimeoutAlarm();
-        cancelDisconnectRequestAlarm();
-        cancelRetryTimeoutAlarm();
-        cancelSafeModeAlarm();
-
-        mUnderlyingNetworkController.teardown();
-
-        mGatewayStatusCallback.onQuit();
-
-        mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
-                mConnectivityDiagnosticsCallback);
-    }
-
-    /**
-     * Notify this Gateway that subscriptions have changed.
-     *
-     * <p>This snapshot should be used to update any keepalive requests necessary for potential
-     * underlying Networks in this Gateway's subscription group.
-     */
-    public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
-        Objects.requireNonNull(snapshot, "Missing snapshot");
-        mVcnContext.ensureRunningOnLooperThread();
-
-        mLastSnapshot = snapshot;
-        mUnderlyingNetworkController.updateSubscriptionSnapshot(mLastSnapshot);
-
-        sendMessageAndAcquireWakeLock(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
-    }
-
-    private class VcnConnectivityDiagnosticsCallback extends ConnectivityDiagnosticsCallback {
-        @Override
-        public void onDataStallSuspected(ConnectivityDiagnosticsManager.DataStallReport report) {
-            mVcnContext.ensureRunningOnLooperThread();
-
-            final Network network = report.getNetwork();
-            logInfo("Data stall suspected on " + network);
-            sendMessageAndAcquireWakeLock(
-                    EVENT_DATA_STALL_SUSPECTED,
-                    TOKEN_ALL,
-                    new EventDataStallSuspectedInfo(network));
-        }
-    }
-
-    private class VcnUnderlyingNetworkControllerCallback
-            implements UnderlyingNetworkControllerCallback {
-        @Override
-        public void onSelectedUnderlyingNetworkChanged(
-                @Nullable UnderlyingNetworkRecord underlying) {
-            // TODO(b/180132994): explore safely removing this Thread check
-            mVcnContext.ensureRunningOnLooperThread();
-
-            if (!UnderlyingNetworkRecord.isSameNetwork(mUnderlying, underlying)) {
-                logInfo(
-                        "Selected underlying network changed: "
-                                + (underlying == null ? null : underlying.network));
-            }
-
-            // TODO(b/179091925): Move the delayed-message handling to BaseState
-
-            // If underlying is null, all underlying networks have been lost. Disconnect VCN after a
-            // timeout (or immediately if in airplane mode, since the device user has indicated that
-            // the radios should all be turned off).
-            if (underlying == null) {
-                if (mDeps.isAirplaneModeOn(mVcnContext)) {
-                    sendMessageAndAcquireWakeLock(
-                            EVENT_UNDERLYING_NETWORK_CHANGED,
-                            TOKEN_ALL,
-                            new EventUnderlyingNetworkChangedInfo(null));
-                    sendDisconnectRequestedAndAcquireWakelock(
-                            DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */);
-                    return;
-                }
-
-                setDisconnectRequestAlarm();
-            } else {
-                // Received a new Network so any previous alarm is irrelevant - cancel + clear it,
-                // and cancel any queued EVENT_DISCONNECT_REQUEST messages
-                cancelDisconnectRequestAlarm();
-            }
-
-            sendMessageAndAcquireWakeLock(
-                    EVENT_UNDERLYING_NETWORK_CHANGED,
-                    TOKEN_ALL,
-                    new EventUnderlyingNetworkChangedInfo(underlying));
-        }
-    }
-
-    private void acquireWakeLock() {
-        mVcnContext.ensureRunningOnLooperThread();
-
-        if (!mIsQuitting.getValue()) {
-            mWakeLock.acquire();
-
-            logVdbg("Wakelock acquired: " + mWakeLock);
-        }
-    }
-
-    private void releaseWakeLock() {
-        mVcnContext.ensureRunningOnLooperThread();
-
-        mWakeLock.release();
-
-        logVdbg("Wakelock released: " + mWakeLock);
-    }
-
-    /**
-     * Attempt to release mWakeLock - this can only be done if the Handler is null (meaning the
-     * StateMachine has been shutdown and thus has no business keeping the WakeLock) or if there are
-     * no more messags left to process in the Handler queue (at which point the WakeLock can be
-     * released until more messages must be processed).
-     */
-    private void maybeReleaseWakeLock() {
-        final Handler handler = getHandler();
-        if (handler == null || !handler.hasMessagesOrCallbacks()) {
-            releaseWakeLock();
-        }
-    }
-
-    @Override
-    public void sendMessage(int what) {
-        logWtf(
-                "sendMessage should not be used in VcnGatewayConnection. See"
-                        + " sendMessageAndAcquireWakeLock()");
-        super.sendMessage(what);
-    }
-
-    @Override
-    public void sendMessage(int what, Object obj) {
-        logWtf(
-                "sendMessage should not be used in VcnGatewayConnection. See"
-                        + " sendMessageAndAcquireWakeLock()");
-        super.sendMessage(what, obj);
-    }
-
-    @Override
-    public void sendMessage(int what, int arg1) {
-        logWtf(
-                "sendMessage should not be used in VcnGatewayConnection. See"
-                        + " sendMessageAndAcquireWakeLock()");
-        super.sendMessage(what, arg1);
-    }
-
-    @Override
-    public void sendMessage(int what, int arg1, int arg2) {
-        logWtf(
-                "sendMessage should not be used in VcnGatewayConnection. See"
-                        + " sendMessageAndAcquireWakeLock()");
-        super.sendMessage(what, arg1, arg2);
-    }
-
-    @Override
-    public void sendMessage(int what, int arg1, int arg2, Object obj) {
-        logWtf(
-                "sendMessage should not be used in VcnGatewayConnection. See"
-                        + " sendMessageAndAcquireWakeLock()");
-        super.sendMessage(what, arg1, arg2, obj);
-    }
-
-    @Override
-    public void sendMessage(Message msg) {
-        logWtf(
-                "sendMessage should not be used in VcnGatewayConnection. See"
-                        + " sendMessageAndAcquireWakeLock()");
-        super.sendMessage(msg);
-    }
-
-    // TODO(b/180146061): also override and Log.wtf() other Message handling methods
-    // In mind are sendMessageDelayed(), sendMessageAtFrontOfQueue, removeMessages, and
-    // removeDeferredMessages
-
-    /**
-     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
-     * go to sleep before processing the sent message.
-     */
-    private void sendMessageAndAcquireWakeLock(int what, int token) {
-        acquireWakeLock();
-        super.sendMessage(what, token);
-    }
-
-    /**
-     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
-     * go to sleep before processing the sent message.
-     */
-    private void sendMessageAndAcquireWakeLock(int what, int token, EventInfo data) {
-        acquireWakeLock();
-        super.sendMessage(what, token, ARG_NOT_PRESENT, data);
-    }
-
-    /**
-     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
-     * go to sleep before processing the sent message.
-     */
-    private void sendMessageAndAcquireWakeLock(int what, int token, int arg2, EventInfo data) {
-        acquireWakeLock();
-        super.sendMessage(what, token, arg2, data);
-    }
-
-    /**
-     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
-     * go to sleep before processing the sent message.
-     */
-    private void sendMessageAndAcquireWakeLock(Message msg) {
-        acquireWakeLock();
-        super.sendMessage(msg);
-    }
-
-    /**
-     * Removes all messages matching the given parameters, and attempts to release mWakeLock if the
-     * Handler is empty.
-     *
-     * @param what the Message.what value to be removed
-     */
-    private void removeEqualMessages(int what) {
-        removeEqualMessages(what, null /* obj */);
-    }
-
-    /**
-     * Removes all messages matching the given parameters, and attempts to release mWakeLock if the
-     * Handler is empty.
-     *
-     * @param what the Message.what value to be removed
-     * @param obj the Message.obj to to be removed, or null if all messages matching Message.what
-     *     should be removed
-     */
-    private void removeEqualMessages(int what, @Nullable Object obj) {
-        final Handler handler = getHandler();
-        if (handler != null) {
-            handler.removeEqualMessages(what, obj);
-        }
-
-        maybeReleaseWakeLock();
-    }
-
-    private WakeupMessage createScheduledAlarm(
-            @NonNull String cmdName, Message delayedMessage, long delay) {
-        final Handler handler = getHandler();
-        if (handler == null) {
-            logWarn(
-                    "Attempted to schedule alarm after StateMachine has quit",
-                    new IllegalStateException());
-            return null; // StateMachine has already quit.
-        }
-
-        // WakeupMessage uses Handler#dispatchMessage() to immediately handle the specified Runnable
-        // at the scheduled time. dispatchMessage() immediately executes and there may be queued
-        // events that resolve the scheduled alarm pending in the queue. So, use the Runnable to
-        // place the alarm event at the end of the queue with sendMessageAndAcquireWakeLock (which
-        // guarantees the device will stay awake).
-        final WakeupMessage alarm =
-                mDeps.newWakeupMessage(
-                        mVcnContext,
-                        handler,
-                        cmdName,
-                        () -> sendMessageAndAcquireWakeLock(delayedMessage));
-        alarm.schedule(mDeps.getElapsedRealTime() + delay);
-        return alarm;
-    }
-
-    private void setTeardownTimeoutAlarm() {
-        logVdbg("Setting teardown timeout alarm; mCurrentToken: " + mCurrentToken);
-
-        // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
-        // either case, there is nothing to cancel.
-        if (mTeardownTimeoutAlarm != null) {
-            logWtf(
-                    "mTeardownTimeoutAlarm should be null before being set; mCurrentToken: "
-                            + mCurrentToken);
-        }
-
-        final Message delayedMessage = obtainMessage(EVENT_TEARDOWN_TIMEOUT_EXPIRED, mCurrentToken);
-        mTeardownTimeoutAlarm =
-                createScheduledAlarm(
-                        TEARDOWN_TIMEOUT_ALARM,
-                        delayedMessage,
-                        TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
-    }
-
-    private void cancelTeardownTimeoutAlarm() {
-        logVdbg("Cancelling teardown timeout alarm; mCurrentToken: " + mCurrentToken);
-
-        if (mTeardownTimeoutAlarm != null) {
-            mTeardownTimeoutAlarm.cancel();
-            mTeardownTimeoutAlarm = null;
-        }
-
-        // Cancel any existing teardown timeouts
-        removeEqualMessages(EVENT_TEARDOWN_TIMEOUT_EXPIRED);
-    }
-
-    private void setDisconnectRequestAlarm() {
-        logVdbg(
-                "Setting alarm to disconnect due to underlying network loss;"
-                        + " mCurrentToken: "
-                        + mCurrentToken);
-
-        // Only schedule a NEW alarm if none is already set.
-        if (mDisconnectRequestAlarm != null) {
-            return;
-        }
-
-        final Message delayedMessage =
-                obtainMessage(
-                        EVENT_DISCONNECT_REQUESTED,
-                        TOKEN_ALL,
-                        0 /* arg2 */,
-                        new EventDisconnectRequestedInfo(
-                                DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */));
-        mDisconnectRequestAlarm =
-                createScheduledAlarm(
-                        DISCONNECT_REQUEST_ALARM,
-                        delayedMessage,
-                        TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS));
-    }
-
-    private void cancelDisconnectRequestAlarm() {
-        logVdbg(
-                "Cancelling alarm to disconnect due to underlying network loss;"
-                        + " mCurrentToken: "
-                        + mCurrentToken);
-
-        if (mDisconnectRequestAlarm != null) {
-            mDisconnectRequestAlarm.cancel();
-            mDisconnectRequestAlarm = null;
-        }
-
-        // Cancel any existing disconnect due to previous loss of underlying network
-        removeEqualMessages(
-                EVENT_DISCONNECT_REQUESTED,
-                new EventDisconnectRequestedInfo(
-                        DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */));
-    }
-
-    private void setRetryTimeoutAlarm(long delay) {
-        logVdbg("Setting retry alarm; mCurrentToken: " + mCurrentToken);
-
-        // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
-        // either case, there is nothing to cancel.
-        if (mRetryTimeoutAlarm != null) {
-            logWtf(
-                    "mRetryTimeoutAlarm should be null before being set; mCurrentToken: "
-                            + mCurrentToken);
-        }
-
-        final Message delayedMessage = obtainMessage(EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken);
-        mRetryTimeoutAlarm = createScheduledAlarm(RETRY_TIMEOUT_ALARM, delayedMessage, delay);
-    }
-
-    private void cancelRetryTimeoutAlarm() {
-        logVdbg("Cancel retry alarm; mCurrentToken: " + mCurrentToken);
-
-        if (mRetryTimeoutAlarm != null) {
-            mRetryTimeoutAlarm.cancel();
-            mRetryTimeoutAlarm = null;
-        }
-
-        removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void setSafeModeAlarm() {
-        final boolean isFlagSafeModeConfigEnabled = mVcnContext.getFeatureFlags().safeModeConfig();
-        logVdbg("isFlagSafeModeConfigEnabled " + isFlagSafeModeConfigEnabled);
-
-        if (isFlagSafeModeConfigEnabled && !mConnectionConfig.isSafeModeEnabled()) {
-            logVdbg("setSafeModeAlarm: safe mode disabled");
-            return;
-        }
-
-        logVdbg("Setting safe mode alarm; mCurrentToken: " + mCurrentToken);
-
-        // Only schedule a NEW alarm if none is already set.
-        if (mSafeModeTimeoutAlarm != null) {
-            return;
-        }
-
-        final Message delayedMessage = obtainMessage(EVENT_SAFE_MODE_TIMEOUT_EXCEEDED, TOKEN_ALL);
-        mSafeModeTimeoutAlarm =
-                createScheduledAlarm(
-                        SAFEMODE_TIMEOUT_ALARM,
-                        delayedMessage,
-                        getSafeModeTimeoutMs(mVcnContext, mLastSnapshot, mSubscriptionGroup));
-    }
-
-    /** Gets the safe mode timeout */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static long getSafeModeTimeoutMs(
-            VcnContext vcnContext, TelephonySubscriptionSnapshot snapshot, ParcelUuid subGrp) {
-        final int defaultSeconds =
-                vcnContext.isInTestMode()
-                        ? SAFEMODE_TIMEOUT_SECONDS_TEST_MODE
-                        : SAFEMODE_TIMEOUT_SECONDS;
-
-        final PersistableBundleWrapper carrierConfig = snapshot.getCarrierConfigForSubGrp(subGrp);
-        int resultSeconds = defaultSeconds;
-
-        if (carrierConfig != null) {
-            resultSeconds =
-                    carrierConfig.getInt(
-                            VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, defaultSeconds);
-        }
-
-        return TimeUnit.SECONDS.toMillis(resultSeconds);
-    }
-
-    private void cancelSafeModeAlarm() {
-        logVdbg("Cancel safe mode alarm; mCurrentToken: " + mCurrentToken);
-
-        if (mSafeModeTimeoutAlarm != null) {
-            mSafeModeTimeoutAlarm.cancel();
-            mSafeModeTimeoutAlarm = null;
-        }
-
-        removeEqualMessages(EVENT_SAFE_MODE_TIMEOUT_EXCEEDED);
-    }
-
-    private void sessionLostWithoutCallback(int token, @Nullable Exception exception) {
-        sendMessageAndAcquireWakeLock(
-                EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
-    }
-
-    private void sessionLost(int token, @Nullable Exception exception) {
-        // Only notify mGatewayStatusCallback if the session was lost with an error. All
-        // authentication and DNS failures are sent through
-        // IkeSessionCallback.onClosedExceptionally(), which calls sessionClosed()
-        if (exception != null) {
-            mGatewayStatusCallback.onGatewayConnectionError(
-                    mConnectionConfig.getGatewayConnectionName(),
-                    VCN_ERROR_CODE_INTERNAL_ERROR,
-                    RuntimeException.class.getName(),
-                    "Received "
-                            + exception.getClass().getSimpleName()
-                            + " with message: "
-                            + exception.getMessage());
-        }
-
-        sessionLostWithoutCallback(token, exception);
-    }
-
-    private static boolean isIkeAuthFailure(@NonNull Exception exception) {
-        if (!(exception instanceof IkeProtocolException)) {
-            return false;
-        }
-
-        return ((IkeProtocolException) exception).getErrorType()
-                == ERROR_TYPE_AUTHENTICATION_FAILED;
-    }
-
-    private void notifyStatusCallbackForSessionClosed(@NonNull Exception exception) {
-        final int errorCode;
-        final String exceptionClass;
-        final String exceptionMessage;
-
-        if (isIkeAuthFailure(exception)) {
-            errorCode = VCN_ERROR_CODE_CONFIG_ERROR;
-            exceptionClass = exception.getClass().getName();
-            exceptionMessage = exception.getMessage();
-        } else if (exception instanceof IkeInternalException
-                && exception.getCause() instanceof IOException) {
-            errorCode = VCN_ERROR_CODE_NETWORK_ERROR;
-            exceptionClass = IOException.class.getName();
-            exceptionMessage = exception.getCause().getMessage();
-        } else {
-            errorCode = VCN_ERROR_CODE_INTERNAL_ERROR;
-            exceptionClass = RuntimeException.class.getName();
-            exceptionMessage =
-                    "Received "
-                            + exception.getClass().getSimpleName()
-                            + " with message: "
-                            + exception.getMessage();
-        }
-
-        logDbg(
-                "Encountered error; code="
-                        + errorCode
-                        + ", exceptionClass="
-                        + exceptionClass
-                        + ", exceptionMessage="
-                        + exceptionMessage);
-
-        mGatewayStatusCallback.onGatewayConnectionError(
-                mConnectionConfig.getGatewayConnectionName(),
-                errorCode,
-                exceptionClass,
-                exceptionMessage);
-    }
-
-    private void ikeConnectionInfoChanged(
-            int token, @NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
-        sendMessageAndAcquireWakeLock(
-                EVENT_IKE_CONNECTION_INFO_CHANGED,
-                token,
-                new EventIkeConnectionInfoChangedInfo(ikeConnectionInfo));
-    }
-
-    private void sessionClosed(int token, @Nullable Exception exception) {
-        if (exception != null) {
-            notifyStatusCallbackForSessionClosed(exception);
-        }
-
-        // SESSION_LOST MUST be sent before SESSION_CLOSED to ensure that the SM moves to the
-        // Disconnecting state.
-        sessionLostWithoutCallback(token, exception);
-        sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, token);
-    }
-
-    private void migrationCompleted(
-            int token, @NonNull IpSecTransform inTransform, @NonNull IpSecTransform outTransform) {
-        sendMessageAndAcquireWakeLock(
-                EVENT_MIGRATION_COMPLETED,
-                token,
-                new EventMigrationCompletedInfo(inTransform, outTransform));
-    }
-
-    private void childTransformCreated(
-            int token, @NonNull IpSecTransform transform, int direction) {
-        sendMessageAndAcquireWakeLock(
-                EVENT_TRANSFORM_CREATED,
-                token,
-                new EventTransformCreatedInfo(direction, transform));
-    }
-
-    private void childOpened(int token, @NonNull VcnChildSessionConfiguration childConfig) {
-        sendMessageAndAcquireWakeLock(
-                EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig));
-    }
-
-    private abstract class BaseState extends State {
-        @Override
-        public void enter() {
-            try {
-                enterState();
-            } catch (Exception e) {
-                logWtf("Uncaught exception", e);
-                sendDisconnectRequestedAndAcquireWakelock(
-                        DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
-            }
-        }
-
-        protected void enterState() throws Exception {}
-
-        /**
-         * Returns whether the given token is valid.
-         *
-         * <p>By default, States consider any and all token to be 'valid'.
-         *
-         * <p>States should override this method if they want to restrict message handling to
-         * specific tokens.
-         */
-        protected boolean isValidToken(int token) {
-            return true;
-        }
-
-        /**
-         * Top-level processMessage with safeguards to prevent crashing the System Server on non-eng
-         * builds.
-         *
-         * <p>Here be dragons: processMessage() is final to ensure that mWakeLock is released once
-         * the Handler queue is empty. Future changes (or overrides) to processMessage() to MUST
-         * ensure that mWakeLock is correctly released.
-         */
-        @Override
-        public final boolean processMessage(Message msg) {
-            final int token = msg.arg1;
-            if (!isValidToken(token)) {
-                logDbg("Message called with obsolete token: " + token + "; what: " + msg.what);
-                return HANDLED;
-            }
-
-            try {
-                processStateMsg(msg);
-            } catch (Exception e) {
-                logWtf("Uncaught exception", e);
-                sendDisconnectRequestedAndAcquireWakelock(
-                        DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
-            }
-
-            // Attempt to release the WakeLock - only possible if the Handler queue is empty
-            maybeReleaseWakeLock();
-
-            return HANDLED;
-        }
-
-        protected abstract void processStateMsg(Message msg) throws Exception;
-
-        @Override
-        public void exit() {
-            try {
-                exitState();
-            } catch (Exception e) {
-                logWtf("Uncaught exception", e);
-                sendDisconnectRequestedAndAcquireWakelock(
-                        DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldQuit */);
-            }
-        }
-
-        protected void exitState() throws Exception {}
-
-        protected void logUnhandledMessage(Message msg) {
-            // Log as unexpected all known messages, and log all else as unknown.
-            switch (msg.what) {
-                case EVENT_UNDERLYING_NETWORK_CHANGED: // Fallthrough
-                case EVENT_RETRY_TIMEOUT_EXPIRED: // Fallthrough
-                case EVENT_SESSION_LOST: // Fallthrough
-                case EVENT_SESSION_CLOSED: // Fallthrough
-                case EVENT_TRANSFORM_CREATED: // Fallthrough
-                case EVENT_SETUP_COMPLETED: // Fallthrough
-                case EVENT_DISCONNECT_REQUESTED: // Fallthrough
-                case EVENT_TEARDOWN_TIMEOUT_EXPIRED: // Fallthrough
-                case EVENT_SUBSCRIPTIONS_CHANGED: // Fallthrough
-                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: // Fallthrough
-                case EVENT_MIGRATION_COMPLETED: // Fallthrough
-                case EVENT_IKE_CONNECTION_INFO_CHANGED: // Fallthrough
-                case EVENT_DATA_STALL_SUSPECTED:
-                    logUnexpectedEvent(msg.what);
-                    break;
-                default:
-                    logWtfUnknownEvent(msg.what);
-                    break;
-            }
-        }
-
-        protected void teardownNetwork() {
-            if (mNetworkAgent != null) {
-                mNetworkAgent.unregister();
-                mNetworkAgent = null;
-            }
-        }
-
-        protected void handleDisconnectRequested(EventDisconnectRequestedInfo info) {
-            // TODO(b/180526152): notify VcnStatusCallback for Network loss
-
-            logInfo("Tearing down. Cause: " + info.reason + "; quitting = " + info.shouldQuit);
-            if (info.shouldQuit) {
-                mIsQuitting.setTrue();
-            }
-
-            teardownNetwork();
-
-            if (mIkeSession == null) {
-                // Already disconnected, go straight to DisconnectedState
-                transitionTo(mDisconnectedState);
-            } else {
-                // Still need to wait for full closure
-                transitionTo(mDisconnectingState);
-            }
-        }
-
-        protected void handleSafeModeTimeoutExceeded() {
-            mSafeModeTimeoutAlarm = null;
-            logInfo("Entering safe mode after timeout exceeded");
-
-            // Connectivity for this GatewayConnection is broken; tear down the Network.
-            teardownNetwork();
-            mIsInSafeMode = true;
-            mGatewayStatusCallback.onSafeModeStatusChanged();
-        }
-
-        protected void logUnexpectedEvent(int what) {
-            logVdbg(
-                    "Unexpected event code "
-                            + what
-                            + " in state "
-                            + this.getClass().getSimpleName());
-        }
-
-        protected void logWtfUnknownEvent(int what) {
-            logWtf("Unknown event code " + what + " in state " + this.getClass().getSimpleName());
-        }
-    }
-
-    /**
-     * State representing the a disconnected VCN tunnel.
-     *
-     * <p>This is also is the initial state.
-     */
-    private class DisconnectedState extends BaseState {
-        @Override
-        protected void enterState() {
-            if (mIsQuitting.getValue()) {
-                quitNow(); // Ignore all queued events; cleanup is complete.
-            }
-
-            if (mIkeSession != null || mNetworkAgent != null) {
-                logWtf("Active IKE Session or NetworkAgent in DisconnectedState");
-            }
-
-            cancelSafeModeAlarm();
-        }
-
-        @Override
-        protected void processStateMsg(Message msg) {
-            switch (msg.what) {
-                case EVENT_UNDERLYING_NETWORK_CHANGED:
-                    // First network found; start tunnel
-                    mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
-
-                    if (mUnderlying != null) {
-                        transitionTo(mConnectingState);
-                    }
-                    break;
-                case EVENT_DISCONNECT_REQUESTED:
-                    if (((EventDisconnectRequestedInfo) msg.obj).shouldQuit) {
-                        mIsQuitting.setTrue();
-
-                        quitNow();
-                    }
-                    break;
-                default:
-                    logUnhandledMessage(msg);
-                    break;
-            }
-        }
-
-        @Override
-        protected void exitState() {
-            // Safe to blindly set up, as it is cancelled and cleared on entering this state
-            setSafeModeAlarm();
-        }
-    }
-
-    private abstract class ActiveBaseState extends BaseState {
-        @Override
-        protected boolean isValidToken(int token) {
-            return (token == TOKEN_ALL || token == mCurrentToken);
-        }
-    }
-
-    /**
-     * Transitive state representing a VCN that is tearing down an IKE session.
-     *
-     * <p>In this state, the IKE session is in the process of being torn down. If the IKE session
-     * does not complete teardown in a timely fashion, it will be killed (forcibly closed).
-     */
-    private class DisconnectingState extends ActiveBaseState {
-        /**
-         * Whether to skip the RetryTimeoutState and go straight to the ConnectingState.
-         *
-         * <p>This is used when an underlying network change triggered a restart on a new network.
-         *
-         * <p>Reset (to false) upon exit of the DisconnectingState.
-         */
-        private boolean mSkipRetryTimeout = false;
-
-        // TODO(b/178441390): Remove this in favor of resetting retry timers on UND_NET change.
-        public void setSkipRetryTimeout(boolean shouldSkip) {
-            mSkipRetryTimeout = shouldSkip;
-        }
-
-        @Override
-        protected void enterState() throws Exception {
-            if (mIkeSession == null) {
-                logWtf("IKE session was already closed when entering Disconnecting state.");
-                sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, mCurrentToken);
-                return;
-            }
-
-            // If underlying network has already been lost, save some time and just kill the session
-            if (mUnderlying == null) {
-                // Will trigger a EVENT_SESSION_CLOSED as IkeSession shuts down.
-                mIkeSession.kill();
-                return;
-            }
-
-            mIkeSession.close();
-
-            // Safe to blindly set up, as it is cancelled and cleared on exiting this state
-            setTeardownTimeoutAlarm();
-        }
-
-        @Override
-        protected void processStateMsg(Message msg) {
-            switch (msg.what) {
-                case EVENT_UNDERLYING_NETWORK_CHANGED: // Fallthrough
-                    mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
-
-                    // If we received a new underlying network, continue.
-                    if (mUnderlying != null) {
-                        break;
-                    }
-
-                    // Fallthrough; no network exists to send IKE close session requests.
-                case EVENT_TEARDOWN_TIMEOUT_EXPIRED:
-                    // Grace period ended. Kill session, triggering EVENT_SESSION_CLOSED
-                    mIkeSession.kill();
-
-                    break;
-                case EVENT_DISCONNECT_REQUESTED:
-                    EventDisconnectRequestedInfo info = ((EventDisconnectRequestedInfo) msg.obj);
-                    if (info.shouldQuit) {
-                        mIsQuitting.setTrue();
-                    }
-
-                    teardownNetwork();
-
-                    if (info.reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) {
-                        // TODO(b/180526152): notify VcnStatusCallback for Network loss
-
-                        // Will trigger EVENT_SESSION_CLOSED immediately.
-                        mIkeSession.kill();
-                        break;
-                    }
-
-                    // Otherwise we are already in the process of shutting down.
-                    break;
-                case EVENT_SESSION_CLOSED:
-                    mIkeSession = null;
-
-                    if (!mIsQuitting.getValue() && mUnderlying != null) {
-                        transitionTo(mSkipRetryTimeout ? mConnectingState : mRetryTimeoutState);
-                    } else {
-                        teardownNetwork();
-                        transitionTo(mDisconnectedState);
-                    }
-                    break;
-                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
-                    handleSafeModeTimeoutExceeded();
-                    break;
-                default:
-                    logUnhandledMessage(msg);
-                    break;
-            }
-        }
-
-        @Override
-        protected void exitState() throws Exception {
-            mSkipRetryTimeout = false;
-
-            cancelTeardownTimeoutAlarm();
-        }
-    }
-
-    /**
-     * Transitive state representing a VCN that is making an primary (non-handover) connection.
-     *
-     * <p>This state starts IKE negotiation, but defers transform application & network setup to the
-     * Connected state.
-     */
-    private class ConnectingState extends ActiveBaseState {
-        @Override
-        protected void enterState() {
-            if (mIkeSession != null) {
-                logWtf("ConnectingState entered with active session");
-
-                // Attempt to recover.
-                mIkeSession.kill();
-                mIkeSession = null;
-            }
-
-            mIkeSession = buildIkeSession(mUnderlying.network);
-        }
-
-        @Override
-        protected void processStateMsg(Message msg) {
-            switch (msg.what) {
-                case EVENT_UNDERLYING_NETWORK_CHANGED:
-                    final UnderlyingNetworkRecord oldUnderlying = mUnderlying;
-                    mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
-
-                    if (oldUnderlying == null) {
-                        // This should never happen, but if it does, there's likely a nasty bug.
-                        logWtf("Old underlying network was null in connected state. Bug?");
-                    }
-
-                    // If new underlying is null, all underlying networks have been lost; disconnect
-                    if (mUnderlying == null) {
-                        transitionTo(mDisconnectingState);
-                        break;
-                    }
-
-                    if (oldUnderlying != null
-                            && mUnderlying.network.equals(oldUnderlying.network)) {
-                        break; // Only network properties have changed; continue connecting.
-                    }
-                    // Else, retry on the new network.
-
-                    // Immediately come back to the ConnectingState (skip RetryTimeout, since this
-                    // isn't a failure)
-                    mDisconnectingState.setSkipRetryTimeout(true);
-
-                    // fallthrough - disconnect, and retry on new network.
-                case EVENT_SESSION_LOST:
-                    transitionTo(mDisconnectingState);
-                    break;
-                case EVENT_SESSION_CLOSED:
-                    // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this
-                    // message may not be posted again. Defer to ensure immediate shutdown.
-                    deferMessage(msg);
-
-                    transitionTo(mDisconnectingState);
-                    break;
-                case EVENT_SETUP_COMPLETED: // fallthrough
-                case EVENT_IKE_CONNECTION_INFO_CHANGED: // fallthrough
-                case EVENT_TRANSFORM_CREATED:
-                    // Child setup complete; move to ConnectedState for NetworkAgent registration
-                    deferMessage(msg);
-                    transitionTo(mConnectedState);
-                    break;
-                case EVENT_DISCONNECT_REQUESTED:
-                    handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
-                    break;
-                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
-                    handleSafeModeTimeoutExceeded();
-                    break;
-                default:
-                    logUnhandledMessage(msg);
-                    break;
-            }
-        }
-    }
-
-    private abstract class ConnectedStateBase extends ActiveBaseState {
-        protected void updateNetworkAgent(
-                @NonNull IpSecTunnelInterface tunnelIface,
-                @NonNull VcnNetworkAgent agent,
-                @NonNull VcnChildSessionConfiguration childConfig,
-                @NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
-            final NetworkCapabilities caps =
-                    buildNetworkCapabilities(mConnectionConfig, mUnderlying, mIsMobileDataEnabled);
-            final LinkProperties lp =
-                    buildConnectedLinkProperties(
-                            mConnectionConfig,
-                            tunnelIface,
-                            childConfig,
-                            mUnderlying,
-                            ikeConnectionInfo);
-
-            agent.sendNetworkCapabilities(caps);
-            agent.sendLinkProperties(lp);
-
-            agent.setUnderlyingNetworks(
-                    mUnderlying == null ? null : Collections.singletonList(mUnderlying.network));
-        }
-
-        protected VcnNetworkAgent buildNetworkAgent(
-                @NonNull IpSecTunnelInterface tunnelIface,
-                @NonNull VcnChildSessionConfiguration childConfig,
-                @NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
-            final NetworkCapabilities caps =
-                    buildNetworkCapabilities(mConnectionConfig, mUnderlying, mIsMobileDataEnabled);
-            final LinkProperties lp =
-                    buildConnectedLinkProperties(
-                            mConnectionConfig,
-                            tunnelIface,
-                            childConfig,
-                            mUnderlying,
-                            ikeConnectionInfo);
-            final NetworkAgentConfig nac =
-                    new NetworkAgentConfig.Builder()
-                            .setLegacyType(ConnectivityManager.TYPE_MOBILE)
-                            .setLegacyTypeName(NETWORK_INFO_NETWORK_TYPE_STRING)
-                            .setLegacySubType(TelephonyManager.NETWORK_TYPE_UNKNOWN)
-                            .setLegacySubTypeName(NETWORK_TYPE_STRING_UNKNOWN)
-                            .setLegacyExtraInfo(NETWORK_INFO_EXTRA_INFO)
-                            .build();
-
-            final VcnNetworkAgent agent =
-                    mDeps.newNetworkAgent(
-                            mVcnContext,
-                            TAG,
-                            caps,
-                            lp,
-                            Vcn.getNetworkScore(),
-                            nac,
-                            mVcnContext.getVcnNetworkProvider(),
-                            (agentRef) -> {
-                                // Only trigger teardown if the NetworkAgent hasn't been replaced or
-                                // changed. This guards against two cases - the first where
-                                // unwanted() may be called as a result of the
-                                // NetworkAgent.unregister() call, which might trigger a teardown
-                                // instead of just a Network disconnect, as well as the case where a
-                                // new NetworkAgent replaces an old one before the unwanted() call
-                                // is processed.
-                                if (mNetworkAgent != agentRef) {
-                                    logDbg("unwanted() called on stale NetworkAgent");
-                                    return;
-                                }
-
-                                logInfo("NetworkAgent was unwanted");
-                                teardownAsynchronously();
-                            } /* networkUnwantedCallback */,
-                            (status) -> {
-                                if (mIsQuitting.getValue()) {
-                                    return; // Ignore; VcnGatewayConnection quitting or already quit
-                                }
-
-                                switch (status) {
-                                    case NetworkAgent.VALIDATION_STATUS_VALID:
-                                        clearFailedAttemptCounterAndSafeModeAlarm();
-                                        break;
-                                    case NetworkAgent.VALIDATION_STATUS_NOT_VALID:
-                                        // Trigger re-validation of underlying networks; if it
-                                        // fails, the VCN will attempt to migrate away.
-                                        if (mUnderlying != null) {
-                                            mConnectivityManager.reportNetworkConnectivity(
-                                                    mUnderlying.network,
-                                                    false /* hasConnectivity */);
-                                        }
-
-                                        // Will only set a new alarm if no safe mode alarm is
-                                        // currently scheduled.
-                                        setSafeModeAlarm();
-                                        break;
-                                    default:
-                                        logWtf(
-                                                "Unknown validation status "
-                                                        + status
-                                                        + "; ignoring");
-                                        break;
-                                }
-                            } /* validationStatusCallback */);
-
-            agent.register();
-            agent.markConnected();
-
-            return agent;
-        }
-
-        protected void clearFailedAttemptCounterAndSafeModeAlarm() {
-            mVcnContext.ensureRunningOnLooperThread();
-
-            // Validated connection, clear failed attempt counter
-            mFailedAttempts = 0;
-            cancelSafeModeAlarm();
-
-            mIsInSafeMode = false;
-            mGatewayStatusCallback.onSafeModeStatusChanged();
-        }
-
-        protected void applyTransform(
-                int token,
-                @NonNull IpSecTunnelInterface tunnelIface,
-                @NonNull Network underlyingNetwork,
-                @NonNull IpSecTransform transform,
-                int direction) {
-            if (direction != IpSecManager.DIRECTION_IN && direction != IpSecManager.DIRECTION_OUT) {
-                logWtf("Applying transform for unexpected direction: " + direction);
-            }
-
-            try {
-                tunnelIface.setUnderlyingNetwork(underlyingNetwork);
-
-                // Transforms do not need to be persisted; the IkeSession will keep them alive
-                mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
-
-                if (direction == IpSecManager.DIRECTION_IN) {
-                    mUnderlyingNetworkController.updateInboundTransform(mUnderlying, transform);
-                }
-
-                // For inbound transforms, additionally allow forwarded traffic to bridge to DUN (as
-                // needed)
-                final Set<Integer> exposedCaps = mConnectionConfig.getAllExposedCapabilities();
-                if (direction == IpSecManager.DIRECTION_IN
-                        && exposedCaps.contains(NET_CAPABILITY_DUN)) {
-                    mIpSecManager.applyTunnelModeTransform(
-                            tunnelIface, IpSecManager.DIRECTION_FWD, transform);
-                }
-            } catch (IOException | IllegalArgumentException e) {
-                logInfo("Transform application failed for network " + token, e);
-                sessionLost(token, e);
-            }
-        }
-
-        protected void setupInterface(
-                int token,
-                @NonNull IpSecTunnelInterface tunnelIface,
-                @NonNull VcnChildSessionConfiguration childConfig,
-                @Nullable VcnChildSessionConfiguration oldChildConfig) {
-            try {
-                final Set<LinkAddress> newAddrs =
-                        new ArraySet<>(childConfig.getInternalAddresses());
-                final Set<LinkAddress> existingAddrs = new ArraySet<>();
-                if (oldChildConfig != null) {
-                    existingAddrs.addAll(oldChildConfig.getInternalAddresses());
-                }
-
-                final Set<LinkAddress> toAdd = new ArraySet<>();
-                toAdd.addAll(newAddrs);
-                toAdd.removeAll(existingAddrs);
-
-                final Set<LinkAddress> toRemove = new ArraySet<>();
-                toRemove.addAll(existingAddrs);
-                toRemove.removeAll(newAddrs);
-
-                for (LinkAddress address : toAdd) {
-                    tunnelIface.addAddress(address.getAddress(), address.getPrefixLength());
-                }
-
-                for (LinkAddress address : toRemove) {
-                    tunnelIface.removeAddress(address.getAddress(), address.getPrefixLength());
-                }
-            } catch (IOException e) {
-                logInfo("Adding address to tunnel failed for token " + token, e);
-                sessionLost(token, e);
-            }
-        }
-    }
-
-    /**
-     * Stable state representing a VCN that has a functioning connection to the mobility anchor.
-     *
-     * <p>This state handles IPsec transform application (initial and rekey), NetworkAgent setup,
-     * and monitors for mobility events.
-     */
-    class ConnectedState extends ConnectedStateBase {
-        @Override
-        protected void enterState() throws Exception {
-            if (mTunnelIface == null) {
-                try {
-                    // Requires a real Network object in order to be created; doing this any earlier
-                    // means not having a real Network object, or picking an incorrect Network.
-                    mTunnelIface =
-                            mIpSecManager.createIpSecTunnelInterface(
-                                    DUMMY_ADDR, DUMMY_ADDR, mUnderlying.network);
-                } catch (IOException | ResourceUnavailableException e) {
-                    teardownAsynchronously();
-                }
-            }
-        }
-
-        @Override
-        protected void processStateMsg(Message msg) {
-            switch (msg.what) {
-                case EVENT_UNDERLYING_NETWORK_CHANGED:
-                    handleUnderlyingNetworkChanged(msg);
-                    break;
-                case EVENT_SESSION_CLOSED:
-                    // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this
-                    // message may not be posted again. Defer to ensure immediate shutdown.
-                    deferMessage(msg);
-                    transitionTo(mDisconnectingState);
-                    break;
-                case EVENT_SESSION_LOST:
-                    transitionTo(mDisconnectingState);
-                    break;
-                case EVENT_TRANSFORM_CREATED:
-                    final EventTransformCreatedInfo transformCreatedInfo =
-                            (EventTransformCreatedInfo) msg.obj;
-
-                    applyTransform(
-                            mCurrentToken,
-                            mTunnelIface,
-                            mUnderlying.network,
-                            transformCreatedInfo.transform,
-                            transformCreatedInfo.direction);
-                    break;
-                case EVENT_SETUP_COMPLETED:
-                    final VcnChildSessionConfiguration oldChildConfig = mChildConfig;
-                    mChildConfig = ((EventSetupCompletedInfo) msg.obj).childSessionConfig;
-
-                    setupInterfaceAndNetworkAgent(
-                            mCurrentToken,
-                            mTunnelIface,
-                            mChildConfig,
-                            oldChildConfig,
-                            mIkeConnectionInfo);
-
-                    // Create opportunistic child SAs; this allows SA aggregation in the downlink,
-                    // reducing lock/atomic contention in high throughput scenarios. All SAs will
-                    // share the same UDP encap socket (and keepalives) as necessary, and are
-                    // effectively free.
-                    final int parallelTunnelCount =
-                            mDeps.getParallelTunnelCount(mLastSnapshot, mSubscriptionGroup);
-                    logInfo("Parallel tunnel count: " + parallelTunnelCount);
-
-                    for (int i = 0; i < parallelTunnelCount - 1; i++) {
-                        mIkeSession.openChildSession(
-                                buildOpportunisticChildParams(),
-                                new VcnChildSessionCallback(
-                                        mCurrentToken, true /* isOpportunistic */));
-                    }
-
-                    break;
-                case EVENT_DISCONNECT_REQUESTED:
-                    handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
-                    break;
-                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
-                    handleSafeModeTimeoutExceeded();
-                    break;
-                case EVENT_MIGRATION_COMPLETED:
-                    final EventMigrationCompletedInfo migrationCompletedInfo =
-                            (EventMigrationCompletedInfo) msg.obj;
-
-                    handleMigrationCompleted(migrationCompletedInfo);
-                    break;
-                case EVENT_IKE_CONNECTION_INFO_CHANGED:
-                    mIkeConnectionInfo =
-                            ((EventIkeConnectionInfoChangedInfo) msg.obj).ikeConnectionInfo;
-                    break;
-                case EVENT_DATA_STALL_SUSPECTED:
-                    final Network networkWithDataStall =
-                            ((EventDataStallSuspectedInfo) msg.obj).network;
-                    handleDataStallSuspected(networkWithDataStall);
-                    break;
-                default:
-                    logUnhandledMessage(msg);
-                    break;
-            }
-        }
-
-        private void handleMigrationCompleted(EventMigrationCompletedInfo migrationCompletedInfo) {
-            logInfo("Migration completed: " + mUnderlying.network);
-
-            applyTransform(
-                    mCurrentToken,
-                    mTunnelIface,
-                    mUnderlying.network,
-                    migrationCompletedInfo.inTransform,
-                    IpSecManager.DIRECTION_IN);
-
-            applyTransform(
-                    mCurrentToken,
-                    mTunnelIface,
-                    mUnderlying.network,
-                    migrationCompletedInfo.outTransform,
-                    IpSecManager.DIRECTION_OUT);
-
-            updateNetworkAgent(mTunnelIface, mNetworkAgent, mChildConfig, mIkeConnectionInfo);
-
-            // Trigger re-validation after migration events.
-            mConnectivityManager.reportNetworkConnectivity(
-                    mNetworkAgent.getNetwork(), false /* hasConnectivity */);
-        }
-
-        private void handleUnderlyingNetworkChanged(@NonNull Message msg) {
-            final UnderlyingNetworkRecord oldUnderlying = mUnderlying;
-            mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
-
-            if (mUnderlying == null) {
-                logInfo("Underlying network lost");
-
-                // Ignored for now; a new network may be coming up. If none does, the delayed
-                // NETWORK_LOST disconnect will be fired, and tear down the session + network.
-                return;
-            }
-
-            // mUnderlying assumed non-null, given check above.
-            // If network changed, migrate. Otherwise, update any existing networkAgent.
-            if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) {
-                logInfo("Migrating to new network: " + mUnderlying.network);
-                mIkeSession.setNetwork(mUnderlying.network);
-            } else {
-                // oldUnderlying is non-null & underlying network itself has not changed
-                // (only network properties were changed).
-
-                // Network not yet set up, or child not yet connected.
-                if (mNetworkAgent != null && mChildConfig != null) {
-                    // If only network properties changed and agent is active, update properties
-                    updateNetworkAgent(
-                            mTunnelIface, mNetworkAgent, mChildConfig, mIkeConnectionInfo);
-                }
-            }
-        }
-
-        private void handleDataStallSuspected(Network networkWithDataStall) {
-            if (mUnderlying != null
-                    && mNetworkAgent != null
-                    && mNetworkAgent.getNetwork().equals(networkWithDataStall)) {
-                logInfo("Perform Mobility update to recover from suspected data stall");
-                mIkeSession.setNetwork(mUnderlying.network);
-            }
-        }
-
-        protected void setupInterfaceAndNetworkAgent(
-                int token,
-                @NonNull IpSecTunnelInterface tunnelIface,
-                @NonNull VcnChildSessionConfiguration childConfig,
-                @NonNull VcnChildSessionConfiguration oldChildConfig,
-                @NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
-            setupInterface(token, tunnelIface, childConfig, oldChildConfig);
-
-            if (mNetworkAgent == null) {
-                mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig, ikeConnectionInfo);
-            } else {
-                updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig, ikeConnectionInfo);
-
-                // mNetworkAgent not null, so the VCN Network has already been established. Clear
-                // the failed attempt counter and safe mode alarm since this transition is complete.
-                clearFailedAttemptCounterAndSafeModeAlarm();
-            }
-        }
-
-        @Override
-        protected void exitState() {
-            // Will only set a new alarm if no safe mode alarm is currently scheduled.
-            setSafeModeAlarm();
-        }
-    }
-
-    /**
-     * Transitive state representing a VCN that failed to establish a connection, and will retry.
-     *
-     * <p>This state will be exited upon a new underlying network being found, or timeout expiry.
-     */
-    class RetryTimeoutState extends ActiveBaseState {
-        @Override
-        protected void enterState() throws Exception {
-            // Reset upon entry to ConnectedState
-            mFailedAttempts++;
-
-            if (mUnderlying == null) {
-                logWtf("Underlying network was null in retry state");
-                teardownNetwork();
-                transitionTo(mDisconnectedState);
-            } else {
-                // Safe to blindly set up, as it is cancelled and cleared on exiting this state
-                setRetryTimeoutAlarm(getNextRetryIntervalsMs());
-            }
-        }
-
-        @Override
-        protected void processStateMsg(Message msg) {
-            switch (msg.what) {
-                case EVENT_UNDERLYING_NETWORK_CHANGED:
-                    final UnderlyingNetworkRecord oldUnderlying = mUnderlying;
-                    mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
-
-                    // If new underlying is null, all networks were lost; go back to disconnected.
-                    if (mUnderlying == null) {
-                        teardownNetwork();
-                        transitionTo(mDisconnectedState);
-                        return;
-                    } else if (oldUnderlying != null
-                            && mUnderlying.network.equals(oldUnderlying.network)) {
-                        // If the network has not changed, do nothing.
-                        return;
-                    }
-
-                    // Fallthrough
-                case EVENT_RETRY_TIMEOUT_EXPIRED:
-                    transitionTo(mConnectingState);
-                    break;
-                case EVENT_DISCONNECT_REQUESTED:
-                    handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
-                    break;
-                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
-                    handleSafeModeTimeoutExceeded();
-                    break;
-                default:
-                    logUnhandledMessage(msg);
-                    break;
-            }
-        }
-
-        @Override
-        public void exitState() {
-            cancelRetryTimeoutAlarm();
-        }
-
-        private long getNextRetryIntervalsMs() {
-            final int retryDelayIndex = mFailedAttempts - 1;
-            final long[] retryIntervalsMs = mConnectionConfig.getRetryIntervalsMillis();
-
-            // Repeatedly use last item in retry timeout list.
-            if (retryDelayIndex >= retryIntervalsMs.length) {
-                return retryIntervalsMs[retryIntervalsMs.length - 1];
-            }
-
-            return retryIntervalsMs[retryDelayIndex];
-        }
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static NetworkCapabilities buildNetworkCapabilities(
-            @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig,
-            @Nullable UnderlyingNetworkRecord underlying,
-            boolean isMobileDataEnabled) {
-        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
-
-        builder.addTransportType(TRANSPORT_CELLULAR);
-        builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
-        builder.addCapability(NET_CAPABILITY_NOT_CONGESTED);
-        builder.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
-
-        // Add exposed capabilities
-        for (int cap : gatewayConnectionConfig.getAllExposedCapabilities()) {
-            // Skip adding INTERNET or DUN if mobile data is disabled.
-            if (!isMobileDataEnabled
-                    && (cap == NET_CAPABILITY_INTERNET || cap == NET_CAPABILITY_DUN)) {
-                continue;
-            }
-
-            builder.addCapability(cap);
-        }
-
-        if (underlying != null) {
-            final NetworkCapabilities underlyingCaps = underlying.networkCapabilities;
-
-            // Mirror merged capabilities.
-            for (int cap : MERGED_CAPABILITIES) {
-                if (underlyingCaps.hasCapability(cap)) {
-                    builder.addCapability(cap);
-                }
-            }
-
-            // Set admin UIDs for ConnectivityDiagnostics use.
-            final int[] underlyingAdminUids = underlyingCaps.getAdministratorUids();
-            Arrays.sort(underlyingAdminUids); // Sort to allow contains check below.
-
-            int[] adminUids;
-            if (underlyingCaps.getOwnerUid() > 0 // No owner UID specified
-                    && 0 > Arrays.binarySearch(// Owner UID not found in admin UID list.
-                            underlyingAdminUids, underlyingCaps.getOwnerUid())) {
-                adminUids = Arrays.copyOf(underlyingAdminUids, underlyingAdminUids.length + 1);
-                adminUids[adminUids.length - 1] = underlyingCaps.getOwnerUid();
-                Arrays.sort(adminUids);
-            } else {
-                adminUids = underlyingAdminUids;
-            }
-
-            // Set owner & administrator UID
-            builder.setOwnerUid(Process.myUid());
-            adminUids = Arrays.copyOf(adminUids, adminUids.length + 1);
-            adminUids[adminUids.length - 1] = Process.myUid();
-            builder.setAdministratorUids(adminUids);
-
-            builder.setLinkUpstreamBandwidthKbps(underlyingCaps.getLinkUpstreamBandwidthKbps());
-            builder.setLinkDownstreamBandwidthKbps(underlyingCaps.getLinkDownstreamBandwidthKbps());
-
-            // Set TransportInfo for SysUI use (never parcelled out of SystemServer).
-            if (underlyingCaps.hasTransport(TRANSPORT_WIFI)
-                    && underlyingCaps.getTransportInfo() instanceof WifiInfo) {
-                final WifiInfo wifiInfo = (WifiInfo) underlyingCaps.getTransportInfo();
-                builder.setTransportInfo(
-                        new VcnTransportInfo(
-                                wifiInfo,
-                                gatewayConnectionConfig.getMinUdpPort4500NatTimeoutSeconds()));
-            } else if (underlyingCaps.hasTransport(TRANSPORT_CELLULAR)
-                    && underlyingCaps.getNetworkSpecifier() instanceof TelephonyNetworkSpecifier) {
-                final TelephonyNetworkSpecifier telNetSpecifier =
-                        (TelephonyNetworkSpecifier) underlyingCaps.getNetworkSpecifier();
-                builder.setTransportInfo(
-                        new VcnTransportInfo(
-                                telNetSpecifier.getSubscriptionId(),
-                                gatewayConnectionConfig.getMinUdpPort4500NatTimeoutSeconds()));
-            } else {
-                Slog.wtf(
-                        TAG,
-                        "Unknown transport type or missing TransportInfo/NetworkSpecifier for"
-                                + " non-null underlying network");
-            }
-            builder.setUnderlyingNetworks(List.of(underlying.network));
-        } else {
-            Slog.wtf(
-                    TAG,
-                    "No underlying network while building network capabilities",
-                    new IllegalStateException());
-        }
-
-        return builder.build();
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    LinkProperties buildConnectedLinkProperties(
-            @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig,
-            @NonNull IpSecTunnelInterface tunnelIface,
-            @NonNull VcnChildSessionConfiguration childConfig,
-            @Nullable UnderlyingNetworkRecord underlying,
-            @NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
-        final IkeTunnelConnectionParams ikeTunnelParams =
-                gatewayConnectionConfig.getTunnelConnectionParams();
-        final LinkProperties lp = new LinkProperties();
-
-        lp.setInterfaceName(tunnelIface.getInterfaceName());
-        for (LinkAddress addr : childConfig.getInternalAddresses()) {
-            lp.addLinkAddress(addr);
-        }
-        for (InetAddress addr : childConfig.getInternalDnsServers()) {
-            lp.addDnsServer(addr);
-        }
-
-        lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null /*gateway*/,
-                null /*iface*/, RouteInfo.RTN_UNICAST));
-        lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null /*gateway*/,
-                null /*iface*/, RouteInfo.RTN_UNICAST));
-
-        int underlyingMtu = 0;
-        if (underlying != null) {
-            final LinkProperties underlyingLp = underlying.linkProperties;
-
-            lp.setTcpBufferSizes(underlyingLp.getTcpBufferSizes());
-            underlyingMtu = underlyingLp.getMtu();
-
-            // WiFi LinkProperties uses DHCP as the sole source of MTU information, and as a result
-            // often lists MTU as 0 (see b/184678973). Use the interface MTU as retrieved by
-            // NetworkInterface APIs.
-            if (underlyingMtu == 0 && underlyingLp.getInterfaceName() != null) {
-                underlyingMtu = mDeps.getUnderlyingIfaceMtu(underlyingLp.getInterfaceName());
-            }
-        } else {
-            Slog.wtf(
-                    TAG,
-                    "No underlying network while building link properties",
-                    new IllegalStateException());
-        }
-        lp.setMtu(
-                MtuUtils.getMtu(
-                        ikeTunnelParams.getTunnelModeChildSessionParams().getSaProposals(),
-                        gatewayConnectionConfig.getMaxMtu(),
-                        underlyingMtu,
-                        ikeConnectionInfo.getLocalAddress() instanceof Inet4Address));
-
-        return lp;
-    }
-
-    private class IkeSessionCallbackImpl implements IkeSessionCallback {
-        private final int mToken;
-
-        IkeSessionCallbackImpl(int token) {
-            mToken = token;
-        }
-
-        @Override
-        public void onOpened(@NonNull IkeSessionConfiguration ikeSessionConfig) {
-            logDbg("IkeOpened for token " + mToken);
-            ikeConnectionInfoChanged(mToken, ikeSessionConfig.getIkeSessionConnectionInfo());
-        }
-
-        @Override
-        public void onClosed() {
-            logDbg("IkeClosed for token " + mToken);
-            sessionClosed(mToken, null);
-        }
-
-        @Override
-        public void onClosedExceptionally(@NonNull IkeException exception) {
-            logInfo("IkeClosedExceptionally for token " + mToken, exception);
-            sessionClosed(mToken, exception);
-        }
-
-        @Override
-        public void onError(@NonNull IkeProtocolException exception) {
-            logInfo("IkeError for token " + mToken, exception);
-            // Non-fatal, log and continue.
-        }
-
-        @Override
-        public void onIkeSessionConnectionInfoChanged(
-                @NonNull IkeSessionConnectionInfo connectionInfo) {
-            logDbg("onIkeSessionConnectionInfoChanged for token " + mToken);
-            ikeConnectionInfoChanged(mToken, connectionInfo);
-        }
-    }
-
-    /** Implementation of ChildSessionCallback, exposed for testing. */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public class VcnChildSessionCallback implements ChildSessionCallback {
-        private final int mToken;
-        private final boolean mIsOpportunistic;
-
-        private boolean mIsChildOpened = false;
-
-        VcnChildSessionCallback(int token) {
-            this(token, false /* isOpportunistic */);
-        }
-
-        /**
-         * Creates a ChildSessionCallback
-         *
-         * <p>If configured as opportunistic, transforms will not report initial startup, or
-         * associated startup failures. This serves the dual purposes of ensuring that if the server
-         * does not support connection multiplexing, new child SA negotiations will be ignored, and
-         * at the same time, will notify the VCN session if a successfully negotiated opportunistic
-         * child SA is subsequently torn down, which could impact uplink traffic if the SA in use
-         * for outbound/uplink traffic is this opportunistic SA.
-         *
-         * <p>While inbound SAs can be used in parallel, the IPsec stack explicitly selects the last
-         * applied outbound transform for outbound traffic. This means that unlike inbound traffic,
-         * outbound does not benefit from these parallel SAs in the same manner.
-         */
-        VcnChildSessionCallback(int token, boolean isOpportunistic) {
-            mToken = token;
-            mIsOpportunistic = isOpportunistic;
-        }
-
-        /** Internal proxy method for injecting of mocked ChildSessionConfiguration */
-        @VisibleForTesting(visibility = Visibility.PRIVATE)
-        void onOpened(@NonNull VcnChildSessionConfiguration childConfig) {
-            logDbg("ChildOpened for token " + mToken);
-
-            if (mIsOpportunistic) {
-                logDbg("ChildOpened for opportunistic child; suppressing event message");
-                mIsChildOpened = true;
-                return;
-            }
-
-            childOpened(mToken, childConfig);
-        }
-
-        @Override
-        public void onOpened(@NonNull ChildSessionConfiguration childConfig) {
-            onOpened(new VcnChildSessionConfiguration(childConfig));
-        }
-
-        @Override
-        public void onClosed() {
-            logDbg("ChildClosed for token " + mToken);
-
-            if (mIsOpportunistic && !mIsChildOpened) {
-                logDbg("ChildClosed for unopened opportunistic child; ignoring");
-                return;
-            }
-
-            sessionLost(mToken, null);
-        }
-
-        @Override
-        public void onClosedExceptionally(@NonNull IkeException exception) {
-            logInfo("ChildClosedExceptionally for token " + mToken, exception);
-
-            if (mIsOpportunistic && !mIsChildOpened) {
-                logInfo("ChildClosedExceptionally for unopened opportunistic child; ignoring");
-                return;
-            }
-
-            sessionLost(mToken, exception);
-        }
-
-        @Override
-        public void onIpSecTransformCreated(@NonNull IpSecTransform transform, int direction) {
-            logDbg("ChildTransformCreated; Direction: " + direction + "; token " + mToken);
-            childTransformCreated(mToken, transform, direction);
-        }
-
-        @Override
-        public void onIpSecTransformsMigrated(
-                @NonNull IpSecTransform inIpSecTransform,
-                @NonNull IpSecTransform outIpSecTransform) {
-            logDbg("ChildTransformsMigrated; token " + mToken);
-            migrationCompleted(mToken, inIpSecTransform, outIpSecTransform);
-        }
-
-        @Override
-        public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) {
-            // Nothing to be done; no references to the IpSecTransform are held, and this transform
-            // will be closed by the IKE library.
-            logDbg("ChildTransformDeleted; Direction: " + direction + "; for token " + mToken);
-        }
-    }
-
-    // Used in Vcn.java, but must be public for mockito to mock this.
-    public String getLogPrefix() {
-        return "("
-                + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
-                + "-"
-                + mConnectionConfig.getGatewayConnectionName()
-                + "-"
-                + System.identityHashCode(this)
-                + ") ";
-    }
-
-    private String getTagLogPrefix() {
-        return "[ " + TAG + " " + getLogPrefix() + "]";
-    }
-
-    private void logVdbg(String msg) {
-        if (VDBG) {
-            Slog.v(TAG, getLogPrefix() + msg);
-        }
-    }
-
-    private void logDbg(String msg) {
-        Slog.d(TAG, getLogPrefix() + msg);
-    }
-
-    private void logDbg(String msg, Throwable tr) {
-        Slog.d(TAG, getLogPrefix() + msg, tr);
-    }
-
-    private void logInfo(String msg) {
-        Slog.i(TAG, getLogPrefix() + msg);
-        LOCAL_LOG.log("[INFO] " + getTagLogPrefix() + msg);
-    }
-
-    private void logInfo(String msg, Throwable tr) {
-        Slog.i(TAG, getLogPrefix() + msg, tr);
-        LOCAL_LOG.log("[INFO] " + getTagLogPrefix() + msg + tr);
-    }
-
-    private void logWarn(String msg) {
-        Slog.w(TAG, getLogPrefix() + msg);
-        LOCAL_LOG.log("[WARN] " + getTagLogPrefix() + msg);
-    }
-
-    private void logWarn(String msg, Throwable tr) {
-        Slog.w(TAG, getLogPrefix() + msg, tr);
-        LOCAL_LOG.log("[WARN] " + getTagLogPrefix() + msg + tr);
-    }
-
-    private void logErr(String msg) {
-        Slog.e(TAG, getLogPrefix() + msg);
-        LOCAL_LOG.log("[ERR ] " + getTagLogPrefix() + msg);
-    }
-
-    private void logErr(String msg, Throwable tr) {
-        Slog.e(TAG, getLogPrefix() + msg, tr);
-        LOCAL_LOG.log("[ERR ] " + getTagLogPrefix() + msg + tr);
-    }
-
-    private void logWtf(String msg) {
-        Slog.wtf(TAG, getLogPrefix() + msg);
-        LOCAL_LOG.log("[WTF ] " + msg);
-    }
-
-    private void logWtf(String msg, Throwable tr) {
-        Slog.wtf(TAG, getLogPrefix() + msg, tr);
-        LOCAL_LOG.log("[WTF ] " + msg + tr);
-    }
-
-    /**
-     * Dumps the state of this VcnGatewayConnection for logging and debugging purposes.
-     *
-     * <p>PII and credentials MUST NEVER be dumped here.
-     *
-     * <p>This method is not thread safe and MUST run on the VCN thread.
-     */
-    public void dump(IndentingPrintWriter pw) {
-        mVcnContext.ensureRunningOnLooperThread();
-
-        pw.println("VcnGatewayConnection (" + mConnectionConfig.getGatewayConnectionName() + "):");
-        pw.increaseIndent();
-
-        pw.println(
-                "Current state: "
-                        + (getCurrentState() == null
-                                ? null
-                                : getCurrentState().getClass().getSimpleName()));
-        pw.println("mIsQuitting: " + mIsQuitting.getValue());
-        pw.println("mIsInSafeMode: " + mIsInSafeMode);
-        pw.println("mCurrentToken: " + mCurrentToken);
-        pw.println("mFailedAttempts: " + mFailedAttempts);
-        pw.println(
-                "mNetworkAgent.getNetwork(): "
-                        + (mNetworkAgent == null ? null : mNetworkAgent.getNetwork()));
-        pw.println();
-
-        mUnderlyingNetworkController.dump(pw);
-        pw.println();
-
-        if (mIkeSession == null) {
-            pw.println("mIkeSession: null");
-        } else {
-            pw.println("mIkeSession:");
-
-            // Add a try catch block in case IkeSession#dump is not thread-safe
-            try {
-                mIkeSession.dump(pw);
-            } catch (Exception e) {
-                Slog.wtf(TAG, "Failed to dump IkeSession: " + e);
-            }
-        }
-
-        pw.decreaseIndent();
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void setTunnelInterface(IpSecTunnelInterface tunnelIface) {
-        mTunnelIface = tunnelIface;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    UnderlyingNetworkControllerCallback getUnderlyingNetworkControllerCallback() {
-        return mUnderlyingNetworkControllerCallback;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    ConnectivityDiagnosticsCallback getConnectivityDiagnosticsCallback() {
-        return mConnectivityDiagnosticsCallback;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    UnderlyingNetworkRecord getUnderlyingNetwork() {
-        return mUnderlying;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void setUnderlyingNetwork(@Nullable UnderlyingNetworkRecord record) {
-        mUnderlying = record;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    IkeSessionConnectionInfo getIkeConnectionInfo() {
-        return mIkeConnectionInfo;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    boolean isQuitting() {
-        return mIsQuitting.getValue();
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void setQuitting() {
-        mIsQuitting.setTrue();
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    VcnIkeSession getIkeSession() {
-        return mIkeSession;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void setIkeSession(@Nullable VcnIkeSession session) {
-        mIkeSession = session;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    VcnNetworkAgent getNetworkAgent() {
-        return mNetworkAgent;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void setNetworkAgent(@Nullable VcnNetworkAgent networkAgent) {
-        mNetworkAgent = networkAgent;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void sendDisconnectRequestedAndAcquireWakelock(String reason, boolean shouldQuit) {
-        sendMessageAndAcquireWakeLock(
-                EVENT_DISCONNECT_REQUESTED,
-                TOKEN_ALL,
-                new EventDisconnectRequestedInfo(reason, shouldQuit));
-    }
-
-    private IkeSessionParams buildIkeParams(@NonNull Network network) {
-        final IkeTunnelConnectionParams ikeTunnelConnectionParams =
-                mConnectionConfig.getTunnelConnectionParams();
-        final IkeSessionParams.Builder builder =
-                new IkeSessionParams.Builder(ikeTunnelConnectionParams.getIkeSessionParams());
-        builder.setNetwork(network);
-        return builder.build();
-    }
-
-    private ChildSessionParams buildChildParams() {
-        return mConnectionConfig.getTunnelConnectionParams().getTunnelModeChildSessionParams();
-    }
-
-    private ChildSessionParams buildOpportunisticChildParams() {
-        final ChildSessionParams baseParams =
-                mConnectionConfig.getTunnelConnectionParams().getTunnelModeChildSessionParams();
-
-        final TunnelModeChildSessionParams.Builder builder =
-                new TunnelModeChildSessionParams.Builder();
-        for (ChildSaProposal proposal : baseParams.getChildSaProposals()) {
-            builder.addChildSaProposal(proposal);
-        }
-
-        for (IkeTrafficSelector inboundSelector : baseParams.getInboundTrafficSelectors()) {
-            builder.addInboundTrafficSelectors(inboundSelector);
-        }
-
-        for (IkeTrafficSelector outboundSelector : baseParams.getOutboundTrafficSelectors()) {
-            builder.addOutboundTrafficSelectors(outboundSelector);
-        }
-
-        builder.setLifetimeSeconds(
-                baseParams.getHardLifetimeSeconds(), baseParams.getSoftLifetimeSeconds());
-
-        return builder.build();
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    VcnIkeSession buildIkeSession(@NonNull Network network) {
-        final int token = ++mCurrentToken;
-
-        return mDeps.newIkeSession(
-                mVcnContext,
-                buildIkeParams(network),
-                buildChildParams(),
-                new IkeSessionCallbackImpl(token),
-                new VcnChildSessionCallback(token));
-    }
-
-    /** External dependencies used by VcnGatewayConnection, for injection in tests */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class Dependencies {
-        /** Builds a new UnderlyingNetworkController. */
-        public UnderlyingNetworkController newUnderlyingNetworkController(
-                VcnContext vcnContext,
-                VcnGatewayConnectionConfig connectionConfig,
-                ParcelUuid subscriptionGroup,
-                TelephonySubscriptionSnapshot snapshot,
-                UnderlyingNetworkControllerCallback callback) {
-            return new UnderlyingNetworkController(
-                    vcnContext, connectionConfig, subscriptionGroup, snapshot, callback);
-        }
-
-        /** Builds a new IkeSession. */
-        public VcnIkeSession newIkeSession(
-                VcnContext vcnContext,
-                IkeSessionParams ikeSessionParams,
-                ChildSessionParams childSessionParams,
-                IkeSessionCallback ikeSessionCallback,
-                ChildSessionCallback childSessionCallback) {
-            return new VcnIkeSession(
-                    vcnContext,
-                    ikeSessionParams,
-                    childSessionParams,
-                    ikeSessionCallback,
-                    childSessionCallback);
-        }
-
-        /** Builds a new WakeLock. */
-        public VcnWakeLock newWakeLock(
-                @NonNull Context context, int wakeLockFlag, @NonNull String wakeLockTag) {
-            return new VcnWakeLock(context, wakeLockFlag, wakeLockTag);
-        }
-
-        /** Builds a new WakeupMessage. */
-        public WakeupMessage newWakeupMessage(
-                @NonNull VcnContext vcnContext,
-                @NonNull Handler handler,
-                @NonNull String tag,
-                @NonNull Runnable runnable) {
-            return new WakeupMessage(vcnContext.getContext(), handler, tag, runnable);
-        }
-
-        /** Builds a new VcnNetworkAgent. */
-        public VcnNetworkAgent newNetworkAgent(
-                @NonNull VcnContext vcnContext,
-                @NonNull String tag,
-                @NonNull NetworkCapabilities caps,
-                @NonNull LinkProperties lp,
-                @NonNull NetworkScore score,
-                @NonNull NetworkAgentConfig nac,
-                @NonNull NetworkProvider provider,
-                @NonNull Consumer<VcnNetworkAgent> networkUnwantedCallback,
-                @NonNull Consumer<Integer> validationStatusCallback) {
-            return new VcnNetworkAgent(
-                    vcnContext,
-                    tag,
-                    caps,
-                    lp,
-                    score,
-                    nac,
-                    provider,
-                    networkUnwantedCallback,
-                    validationStatusCallback);
-        }
-
-        /** Checks if airplane mode is enabled. */
-        public boolean isAirplaneModeOn(@NonNull VcnContext vcnContext) {
-            return Settings.Global.getInt(vcnContext.getContext().getContentResolver(),
-                    Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
-        }
-
-        /** Gets the elapsed real time since boot, in millis. */
-        public long getElapsedRealTime() {
-            return SystemClock.elapsedRealtime();
-        }
-
-        /** Gets the MTU for the given underlying interface. */
-        public int getUnderlyingIfaceMtu(String ifaceName) {
-            try {
-                final NetworkInterface underlyingIface = NetworkInterface.getByName(ifaceName);
-                return underlyingIface == null ? 0 : underlyingIface.getMTU();
-            } catch (IOException e) {
-                Slog.d(TAG, "Could not get MTU of underlying network", e);
-                return 0;
-            }
-        }
-
-        /** Gets the max number of parallel tunnels allowed for tunnel aggregation. */
-        public int getParallelTunnelCount(
-                TelephonySubscriptionSnapshot snapshot, ParcelUuid subGrp) {
-            PersistableBundleWrapper carrierConfig = snapshot.getCarrierConfigForSubGrp(subGrp);
-            int result = TUNNEL_AGGREGATION_SA_COUNT_MAX_DEFAULT;
-
-            if (carrierConfig != null) {
-                result =
-                        carrierConfig.getInt(
-                                VcnManager.VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY,
-                                TUNNEL_AGGREGATION_SA_COUNT_MAX_DEFAULT);
-            }
-
-            // Guard against tunnel count < 1
-            return Math.max(1, result);
-        }
-    }
-
-    /**
-     * Proxy implementation of Child Session Configuration, used for testing.
-     *
-     * <p>This wrapper allows mocking of the final, parcelable ChildSessionConfiguration object for
-     * testing purposes. This is the unfortunate result of mockito-inline (for mocking final
-     * classes) not working properly with system services & associated classes.
-     *
-     * <p>This class MUST EXCLUSIVELY be a passthrough, proxying calls directly to the actual
-     * ChildSessionConfiguration.
-     */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class VcnChildSessionConfiguration {
-        private final ChildSessionConfiguration mChildConfig;
-
-        public VcnChildSessionConfiguration(ChildSessionConfiguration childConfig) {
-            mChildConfig = childConfig;
-        }
-
-        /** Retrieves the addresses to be used inside the tunnel. */
-        public List<LinkAddress> getInternalAddresses() {
-            return mChildConfig.getInternalAddresses();
-        }
-
-        /** Retrieves the DNS servers to be used inside the tunnel. */
-        public List<InetAddress> getInternalDnsServers() {
-            return mChildConfig.getInternalDnsServers();
-        }
-    }
-
-    /** Proxy implementation of IKE session, used for testing. */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class VcnIkeSession {
-        private final IkeSession mImpl;
-
-        public VcnIkeSession(
-                VcnContext vcnContext,
-                IkeSessionParams ikeSessionParams,
-                ChildSessionParams childSessionParams,
-                IkeSessionCallback ikeSessionCallback,
-                ChildSessionCallback childSessionCallback) {
-            mImpl =
-                    new IkeSession(
-                            vcnContext.getContext(),
-                            ikeSessionParams,
-                            childSessionParams,
-                            new HandlerExecutor(new Handler(vcnContext.getLooper())),
-                            ikeSessionCallback,
-                            childSessionCallback);
-        }
-
-        /** Creates a new IKE Child session. */
-        public void openChildSession(
-                @NonNull ChildSessionParams childSessionParams,
-                @NonNull ChildSessionCallback childSessionCallback) {
-            mImpl.openChildSession(childSessionParams, childSessionCallback);
-        }
-
-        /** Closes an IKE session as identified by the ChildSessionCallback. */
-        public void closeChildSession(@NonNull ChildSessionCallback childSessionCallback) {
-            mImpl.closeChildSession(childSessionCallback);
-        }
-
-        /** Gracefully closes this IKE Session, waiting for remote acknowledgement. */
-        public void close() {
-            mImpl.close();
-        }
-
-        /** Forcibly kills this IKE Session, without waiting for a closure confirmation. */
-        public void kill() {
-            mImpl.kill();
-        }
-
-        /** Sets the underlying network used by the IkeSession. */
-        public void setNetwork(@NonNull Network network) {
-            mImpl.setNetwork(network);
-        }
-
-        /** Dumps the state of the IkeSession */
-        public void dump(@NonNull IndentingPrintWriter pw) {
-            mImpl.dump(pw);
-        }
-    }
-
-    /** Proxy Implementation of WakeLock, used for testing. */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class VcnWakeLock {
-        private final WakeLock mImpl;
-
-        public VcnWakeLock(@NonNull Context context, int flags, @NonNull String tag) {
-            final PowerManager powerManager = context.getSystemService(PowerManager.class);
-            mImpl = powerManager.newWakeLock(flags, tag);
-            mImpl.setReferenceCounted(false /* isReferenceCounted */);
-        }
-
-        /**
-         * Acquire this WakeLock.
-         *
-         * <p>Synchronize this action to minimize locking around WakeLock use.
-         */
-        public synchronized void acquire() {
-            mImpl.acquire();
-        }
-
-        /**
-         * Release this Wakelock.
-         *
-         * <p>Synchronize this action to minimize locking around WakeLock use.
-         */
-        public synchronized void release() {
-            mImpl.release();
-        }
-    }
-
-    /** Proxy Implementation of NetworkAgent, used for testing. */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class VcnNetworkAgent {
-        private final NetworkAgent mImpl;
-
-        public VcnNetworkAgent(
-                @NonNull VcnContext vcnContext,
-                @NonNull String tag,
-                @NonNull NetworkCapabilities caps,
-                @NonNull LinkProperties lp,
-                @NonNull NetworkScore score,
-                @NonNull NetworkAgentConfig nac,
-                @NonNull NetworkProvider provider,
-                @NonNull Consumer<VcnNetworkAgent> networkUnwantedCallback,
-                @NonNull Consumer<Integer> validationStatusCallback) {
-            mImpl =
-                    new NetworkAgent(
-                            vcnContext.getContext(),
-                            vcnContext.getLooper(),
-                            tag,
-                            caps,
-                            lp,
-                            score,
-                            nac,
-                            provider) {
-                        @Override
-                        public void onNetworkUnwanted() {
-                            networkUnwantedCallback.accept(VcnNetworkAgent.this);
-                        }
-
-                        @Override
-                        public void onValidationStatus(int status, @Nullable Uri redirectUri) {
-                            validationStatusCallback.accept(status);
-                        }
-                    };
-        }
-
-        /** Registers the underlying NetworkAgent */
-        public void register() {
-            mImpl.register();
-        }
-
-        /** Marks the underlying NetworkAgent as connected */
-        public void markConnected() {
-            mImpl.markConnected();
-        }
-
-        /** Unregisters the underlying NetworkAgent */
-        public void unregister() {
-            mImpl.unregister();
-        }
-
-        /** Sends new NetworkCapabilities for the underlying NetworkAgent */
-        public void sendNetworkCapabilities(@NonNull NetworkCapabilities caps) {
-            mImpl.sendNetworkCapabilities(caps);
-        }
-
-        /** Sends new LinkProperties for the underlying NetworkAgent */
-        public void sendLinkProperties(@NonNull LinkProperties lp) {
-            mImpl.sendLinkProperties(lp);
-        }
-
-        /** Sends new NetworkCapabilities for the underlying NetworkAgent */
-        public void setUnderlyingNetworks(@Nullable List<Network> underlyingNetworks) {
-            mImpl.setUnderlyingNetworks(underlyingNetworks);
-        }
-
-        /** Retrieves the Network for the underlying NetworkAgent */
-        @Nullable
-        public Network getNetwork() {
-            return mImpl.getNetwork();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
deleted file mode 100644
index 78ff432..0000000
--- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.vcn;
-
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-
-import static com.android.server.VcnManagementService.VDBG;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkCapabilities;
-import android.net.NetworkProvider;
-import android.net.NetworkRequest;
-import android.net.NetworkScore;
-import android.net.vcn.VcnGatewayConnectionConfig;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.Looper;
-import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.annotations.VisibleForTesting.Visibility;
-
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.Executor;
-
-/**
- * VCN Network Provider routes NetworkRequests to listeners to bring up tunnels as needed.
- *
- * <p>The VcnNetworkProvider provides a caching layer to ensure that all listeners receive all
- * active NetworkRequest(s), including ones that were filed prior to listener registration.
- *
- * @hide
- */
-public class VcnNetworkProvider extends NetworkProvider {
-    private static final String TAG = VcnNetworkProvider.class.getSimpleName();
-
-    private final Set<NetworkRequestListener> mListeners = new ArraySet<>();
-
-    private final Context mContext;
-    private final Handler mHandler;
-    private final Dependencies mDeps;
-
-    /**
-     * Cache of NetworkRequest(s).
-     *
-     * <p>NetworkRequests are immutable once created, and therefore can be used as stable keys.
-     */
-    private final Set<NetworkRequest> mRequests = new ArraySet<>();
-
-    public VcnNetworkProvider(@NonNull Context context, @NonNull Looper looper) {
-        this(context, looper, new Dependencies());
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public VcnNetworkProvider(
-            @NonNull Context context, @NonNull Looper looper, @NonNull Dependencies dependencies) {
-        super(
-                Objects.requireNonNull(context, "Missing context"),
-                Objects.requireNonNull(looper, "Missing looper"),
-                TAG);
-
-        mContext = context;
-        mHandler = new Handler(looper);
-        mDeps = Objects.requireNonNull(dependencies, "Missing dependencies");
-    }
-
-    /** Registers this VcnNetworkProvider and a generic network offer with ConnectivityService. */
-    public void register() {
-        mContext.getSystemService(ConnectivityManager.class).registerNetworkProvider(this);
-        mDeps.registerNetworkOffer(
-                this,
-                Vcn.getNetworkScore(), // score filter
-                buildCapabilityFilter(),
-                new HandlerExecutor(mHandler),
-                new NetworkOfferCallback() {
-                    @Override
-                    public void onNetworkNeeded(@NonNull NetworkRequest request) {
-                        handleNetworkRequested(request);
-                    }
-
-                    @Override
-                    public void onNetworkUnneeded(@NonNull NetworkRequest request) {
-                        handleNetworkRequestWithdrawn(request);
-                    }
-                });
-    }
-
-    /** Builds the filter for NetworkRequests that can be served by the VcnNetworkProvider. */
-    private NetworkCapabilities buildCapabilityFilter() {
-        final NetworkCapabilities.Builder builder =
-                new NetworkCapabilities.Builder()
-                        .addTransportType(TRANSPORT_CELLULAR)
-                        .addCapability(NET_CAPABILITY_TRUSTED)
-                        .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
-                        .addCapability(NET_CAPABILITY_NOT_VPN)
-                        .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
-
-        for (int cap : VcnGatewayConnectionConfig.ALLOWED_CAPABILITIES) {
-            builder.addCapability(cap);
-        }
-
-        return builder.build();
-    }
-
-    /**
-     * Registers a NetworkRequestListener with this NetworkProvider.
-     *
-     * <p>Upon registering, the provided listener will receive all cached requests.
-     */
-    @VisibleForTesting(visibility = Visibility.PACKAGE)
-    public void registerListener(@NonNull NetworkRequestListener listener) {
-        mListeners.add(listener);
-
-        // Send listener all cached requests
-        resendAllRequests(listener);
-    }
-
-    /** Unregisters the specified listener from receiving future NetworkRequests. */
-    @VisibleForTesting(visibility = Visibility.PACKAGE)
-    public void unregisterListener(@NonNull NetworkRequestListener listener) {
-        mListeners.remove(listener);
-    }
-
-    /** Sends all cached NetworkRequest(s) to the specified listener. */
-    @VisibleForTesting(visibility = Visibility.PACKAGE)
-    public void resendAllRequests(@NonNull NetworkRequestListener listener) {
-        for (NetworkRequest request : mRequests) {
-            notifyListenerForEvent(listener, request);
-        }
-    }
-
-    private void notifyListenerForEvent(
-            @NonNull NetworkRequestListener listener, @NonNull NetworkRequest request) {
-        listener.onNetworkRequested(request);
-    }
-
-    private void handleNetworkRequested(@NonNull NetworkRequest request) {
-        if (VDBG) {
-            Slog.v(TAG, "Network requested: Request = " + request);
-        }
-
-        mRequests.add(request);
-
-        // TODO(b/176939047): Intelligently route requests to prioritized VcnInstances (based on
-        // Default Data Sub, or similar)
-        for (NetworkRequestListener listener : mListeners) {
-            notifyListenerForEvent(listener, request);
-        }
-    }
-
-    private void handleNetworkRequestWithdrawn(@NonNull NetworkRequest request) {
-        if (VDBG) {
-            Slog.v(TAG, "Network request withdrawn: Request = " + request);
-        }
-
-        mRequests.remove(request);
-    }
-
-    // package-private
-    interface NetworkRequestListener {
-        void onNetworkRequested(@NonNull NetworkRequest request);
-    }
-
-    /**
-     * Dumps the state of this VcnNetworkProvider for logging and debugging purposes.
-     *
-     * <p>PII and credentials MUST NEVER be dumped here.
-     */
-    public void dump(IndentingPrintWriter pw) {
-        pw.println("VcnNetworkProvider:");
-        pw.increaseIndent();
-
-        pw.println("mListeners:");
-        pw.increaseIndent();
-        for (NetworkRequestListener listener : mListeners) {
-            pw.println(listener);
-        }
-        pw.decreaseIndent();
-        pw.println();
-
-        pw.println("mRequests:");
-        pw.increaseIndent();
-        for (NetworkRequest request : mRequests) {
-            pw.println(request);
-        }
-        pw.decreaseIndent();
-        pw.println();
-
-        pw.decreaseIndent();
-    }
-
-    /** Proxy class for dependencies used for testing. */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class Dependencies {
-        /** Registers a given network offer for the given provider. */
-        public void registerNetworkOffer(
-                @NonNull VcnNetworkProvider provider,
-                @NonNull NetworkScore score,
-                @NonNull NetworkCapabilities capabilitiesFilter,
-                @NonNull Executor executor,
-                @NonNull NetworkOfferCallback callback) {
-            provider.registerNetworkOffer(score, capabilitiesFilter, executor, callback);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
deleted file mode 100644
index 16ab51e..0000000
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ /dev/null
@@ -1,592 +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.server.vcn.routeselection;
-
-import static com.android.internal.annotations.VisibleForTesting.Visibility;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.IpSecTransformState;
-import android.net.Network;
-import android.net.vcn.VcnManager;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.OutcomeReceiver;
-import android.os.PowerManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.server.vcn.VcnContext;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.util.BitSet;
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-
-/**
- * IpSecPacketLossDetector is responsible for continuously monitoring IPsec packet loss
- *
- * <p>When the packet loss rate surpass the threshold, IpSecPacketLossDetector will report it to the
- * caller
- *
- * <p>IpSecPacketLossDetector will start monitoring when the network being monitored is selected AND
- * an inbound IpSecTransform has been applied to this network.
- *
- * <p>This class is flag gated by "network_metric_monitor" and "ipsec_tramsform_state"
- */
-public class IpSecPacketLossDetector extends NetworkMetricMonitor {
-    private static final String TAG = IpSecPacketLossDetector.class.getSimpleName();
-
-    private static final int PACKET_LOSS_PERCENT_UNAVAILABLE = -1;
-
-    // Ignore the packet loss detection result if the expected packet number is smaller than 10.
-    // Solarwinds NPM uses 10 ICMP echos to calculate packet loss rate (as per
-    // https://thwack.solarwinds.com/products/network-performance-monitor-npm/f/forum/63829/how-is-packet-loss-calculated)
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int MIN_VALID_EXPECTED_RX_PACKET_NUM = 10;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            prefix = {"PACKET_LOSS_"},
-            value = {
-                PACKET_LOSS_RATE_VALID,
-                PACKET_LOSS_RATE_INVALID,
-                PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP,
-            })
-    @Target({ElementType.TYPE_USE})
-    private @interface PacketLossResultType {}
-
-    /** Indicates a valid packet loss rate is available */
-    private static final int PACKET_LOSS_RATE_VALID = 0;
-
-    /**
-     * Indicates that the detector cannot get a valid packet loss rate due to one of the following
-     * reasons:
-     *
-     * <ul>
-     *   <li>The replay window did not proceed and thus all packets might have been delivered out of
-     *       order
-     *   <li>The expected received packet number is too small and thus the detection result is not
-     *       reliable
-     *   <li>There are unexpected errors
-     * </ul>
-     */
-    private static final int PACKET_LOSS_RATE_INVALID = 1;
-
-    /**
-     * The sequence number increase is unusually large and might be caused an intentional leap on
-     * the server's downlink
-     *
-     * <p>Inbound sequence number will not always increase consecutively. During load balancing the
-     * server might add a big leap on the sequence number intentionally. In such case a high packet
-     * loss rate does not always indicate a lossy network
-     */
-    private static final int PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP = 2;
-
-    // For VoIP, losses between 5% and 10% of the total packet stream will affect the quality
-    // significantly (as per "Computer Networking for LANS to WANS: Hardware, Software and
-    // Security"). For audio and video streaming, above 10-12% packet loss is unacceptable (as per
-    // "ICTP-SDU: About PingER"). Thus choose 12% as a conservative default threshold to declare a
-    // 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
-    private static final int MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED = -1;
-
-    private long mPollIpSecStateIntervalMs;
-    private int mPacketLossRatePercentThreshold;
-    private int mMaxSeqNumIncreasePerSecond;
-
-    @NonNull private final Handler mHandler;
-    @NonNull private final PowerManager mPowerManager;
-    @NonNull private final ConnectivityManager mConnectivityManager;
-    @NonNull private final Object mCancellationToken = new Object();
-    @NonNull private final PacketLossCalculator mPacketLossCalculator;
-
-    @Nullable private IpSecTransformWrapper mInboundTransform;
-    @Nullable private IpSecTransformState mLastIpSecTransformState;
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public IpSecPacketLossDetector(
-            @NonNull VcnContext vcnContext,
-            @NonNull Network network,
-            @Nullable PersistableBundleWrapper carrierConfig,
-            @NonNull NetworkMetricMonitorCallback callback,
-            @NonNull Dependencies deps)
-            throws IllegalAccessException {
-        super(vcnContext, network, carrierConfig, callback);
-
-        Objects.requireNonNull(deps, "Missing deps");
-
-        mHandler = new Handler(getVcnContext().getLooper());
-
-        mPowerManager = getVcnContext().getContext().getSystemService(PowerManager.class);
-        mConnectivityManager =
-                getVcnContext().getContext().getSystemService(ConnectivityManager.class);
-
-        mPacketLossCalculator = deps.getPacketLossCalculator();
-
-        mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
-        mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
-        mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
-
-        // Register for system broadcasts to monitor idle mode change
-        final IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
-        getVcnContext()
-                .getContext()
-                .registerReceiver(
-                        new BroadcastReceiver() {
-                            @Override
-                            public void onReceive(Context context, Intent intent) {
-                                if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(
-                                                intent.getAction())
-                                        && mPowerManager.isDeviceIdleMode()) {
-                                    mLastIpSecTransformState = null;
-                                }
-                            }
-                        },
-                        intentFilter,
-                        null /* broadcastPermission not required */,
-                        mHandler);
-    }
-
-    public IpSecPacketLossDetector(
-            @NonNull VcnContext vcnContext,
-            @NonNull Network network,
-            @Nullable PersistableBundleWrapper carrierConfig,
-            @NonNull NetworkMetricMonitorCallback callback)
-            throws IllegalAccessException {
-        this(vcnContext, network, carrierConfig, callback, new Dependencies());
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class Dependencies {
-        public PacketLossCalculator getPacketLossCalculator() {
-            return new PacketLossCalculator();
-        }
-    }
-
-    private static long getPollIpSecStateIntervalMs(
-            @Nullable PersistableBundleWrapper carrierConfig) {
-        final int seconds;
-
-        if (carrierConfig != null) {
-            seconds =
-                    carrierConfig.getInt(
-                            VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
-                            POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT);
-        } else {
-            seconds = POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT;
-        }
-
-        return TimeUnit.SECONDS.toMillis(seconds);
-    }
-
-    private static int getPacketLossRatePercentThreshold(
-            @Nullable PersistableBundleWrapper carrierConfig) {
-        if (carrierConfig != null) {
-            return carrierConfig.getInt(
-                    VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
-                    IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT);
-        }
-        return IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static int getMaxSeqNumIncreasePerSecond(@Nullable PersistableBundleWrapper carrierConfig) {
-        int maxSeqNumIncrease = MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
-        if (carrierConfig != null) {
-            maxSeqNumIncrease =
-                    carrierConfig.getInt(
-                            VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
-                            MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED);
-        }
-
-        if (maxSeqNumIncrease < MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
-            logE(TAG, "Invalid value of MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY " + maxSeqNumIncrease);
-            return MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
-        }
-
-        return maxSeqNumIncrease;
-    }
-
-    @Override
-    protected void onSelectedUnderlyingNetworkChanged() {
-        if (!isSelectedUnderlyingNetwork()) {
-            mInboundTransform = null;
-            stop();
-        }
-
-        // No action when the underlying network got selected. Wait for the inbound transform to
-        // start the monitor
-    }
-
-    @Override
-    public void setInboundTransformInternal(@NonNull IpSecTransformWrapper inboundTransform) {
-        Objects.requireNonNull(inboundTransform, "inboundTransform is null");
-
-        if (Objects.equals(inboundTransform, mInboundTransform)) {
-            return;
-        }
-
-        if (!isSelectedUnderlyingNetwork()) {
-            logWtf("setInboundTransform called but network not selected");
-            return;
-        }
-
-        // When multiple parallel inbound transforms are created, NetworkMetricMonitor will be
-        // enabled on the last one as a sample
-        mInboundTransform = inboundTransform;
-
-        if (canStart()) {
-            start();
-        }
-    }
-
-    @Override
-    public void setCarrierConfig(@Nullable PersistableBundleWrapper carrierConfig) {
-        // The already scheduled event will not be affected. The followup events will be scheduled
-        // with the new interval
-        mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
-
-        mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
-        mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
-
-        if (canStart() != isStarted()) {
-            if (canStart()) {
-                start();
-            } else {
-                stop();
-            }
-        }
-    }
-
-    @Override
-    public void onLinkPropertiesOrCapabilitiesChanged() {
-        if (!isStarted()) return;
-
-        reschedulePolling();
-    }
-
-    private void reschedulePolling() {
-        mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
-        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();
-        clearTransformStateAndPollingEvents();
-        mHandler.postDelayed(new PollIpSecStateRunnable(), mCancellationToken, 0L);
-    }
-
-    @Override
-    public void stop() {
-        super.stop();
-        clearTransformStateAndPollingEvents();
-    }
-
-    private void clearTransformStateAndPollingEvents() {
-        mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
-        mLastIpSecTransformState = null;
-    }
-
-    @Override
-    public void close() {
-        super.close();
-
-        if (mInboundTransform != null) {
-            mInboundTransform.close();
-        }
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    @Nullable
-    public IpSecTransformState getLastTransformState() {
-        return mLastIpSecTransformState;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PROTECTED)
-    @Nullable
-    public IpSecTransformWrapper getInboundTransformInternal() {
-        return mInboundTransform;
-    }
-
-    private class PollIpSecStateRunnable implements Runnable {
-        @Override
-        public void run() {
-            if (!isStarted()) {
-                logWtf("Monitor stopped but PollIpSecStateRunnable not removed from Handler");
-                return;
-            }
-
-            getInboundTransformInternal()
-                    .requestIpSecTransformState(
-                            new HandlerExecutor(mHandler), new IpSecTransformStateReceiver());
-
-            // Schedule for next poll
-            mHandler.postDelayed(
-                    new PollIpSecStateRunnable(), mCancellationToken, mPollIpSecStateIntervalMs);
-        }
-    }
-
-    private class IpSecTransformStateReceiver
-            implements OutcomeReceiver<IpSecTransformState, RuntimeException> {
-        @Override
-        public void onResult(@NonNull IpSecTransformState state) {
-            getVcnContext().ensureRunningOnLooperThread();
-
-            if (!isStarted()) {
-                return;
-            }
-
-            onIpSecTransformStateReceived(state);
-        }
-
-        @Override
-        public void onError(@NonNull RuntimeException error) {
-            getVcnContext().ensureRunningOnLooperThread();
-
-            // Nothing we can do here
-            logW("TransformStateReceiver#onError " + error.toString());
-        }
-    }
-
-    private void onIpSecTransformStateReceived(@NonNull IpSecTransformState state) {
-        if (mLastIpSecTransformState == null) {
-            // This is first time to poll the state
-            mLastIpSecTransformState = state;
-            return;
-        }
-
-        final PacketLossCalculationResult calculateResult =
-                mPacketLossCalculator.getPacketLossRatePercentage(
-                        mLastIpSecTransformState,
-                        state,
-                        mMaxSeqNumIncreasePerSecond,
-                        getLogPrefix());
-
-        if (calculateResult.getResultType() == PACKET_LOSS_RATE_INVALID) {
-            return;
-        }
-
-        final String logMsg =
-                "calculateResult: "
-                        + calculateResult
-                        + "% in the past "
-                        + (state.getTimestampMillis()
-                                - mLastIpSecTransformState.getTimestampMillis())
-                        + "ms";
-
-        mLastIpSecTransformState = state;
-        if (calculateResult.getPacketLossRatePercent() < mPacketLossRatePercentThreshold) {
-            logV(logMsg);
-
-            // In both "valid" or "unusual_seq_num_leap" cases, notify that the network has passed
-            // the validation
-            onValidationResultReceivedInternal(false /* isFailed */);
-        } else {
-            logInfo(logMsg);
-
-            if (calculateResult.getResultType() == PACKET_LOSS_RATE_VALID) {
-                onValidationResultReceivedInternal(true /* isFailed */);
-            }
-
-            // In both "invalid" and "unusual_seq_num_leap" cases, trigger network validation. If
-            // validation fails, the VCN will attempt to migrate away.
-            mConnectivityManager.reportNetworkConnectivity(
-                    getNetwork(), false /* hasConnectivity */);
-        }
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class PacketLossCalculator {
-        /** Calculate the packet loss rate between two timestamps */
-        public PacketLossCalculationResult getPacketLossRatePercentage(
-                @NonNull IpSecTransformState oldState,
-                @NonNull IpSecTransformState newState,
-                int maxSeqNumIncreasePerSecond,
-                String logPrefix) {
-            logVIpSecTransform("oldState", oldState, logPrefix);
-            logVIpSecTransform("newState", newState, logPrefix);
-
-            final int replayWindowSize = oldState.getReplayBitmap().length * 8;
-            final long oldSeqHi = oldState.getRxHighestSequenceNumber();
-            final long oldSeqLow = Math.max(0L, oldSeqHi - replayWindowSize + 1);
-            final long newSeqHi = newState.getRxHighestSequenceNumber();
-            final long newSeqLow = Math.max(0L, newSeqHi - replayWindowSize + 1);
-
-            if (oldSeqHi == newSeqHi || newSeqHi < replayWindowSize) {
-                // The replay window did not proceed and all packets might have been delivered out
-                // of order
-                return PacketLossCalculationResult.invalid();
-            }
-
-            boolean isUnusualSeqNumLeap = false;
-
-            // Handle sequence number leap
-            if (maxSeqNumIncreasePerSecond != MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
-                final long timeDiffMillis =
-                        newState.getTimestampMillis() - oldState.getTimestampMillis();
-                final long maxSeqNumIncrease = timeDiffMillis * maxSeqNumIncreasePerSecond / 1000;
-
-                // Sequence numbers are unsigned 32-bit values. If maxSeqNumIncrease overflows,
-                // isUnusualSeqNumLeap can never be true.
-                if (maxSeqNumIncrease >= 0 && newSeqHi - oldSeqHi >= maxSeqNumIncrease) {
-                    isUnusualSeqNumLeap = true;
-                }
-            }
-
-            // Get the expected packet count by assuming there is no packet loss. In this case, SA
-            // should receive all packets whose sequence numbers are smaller than the lower bound of
-            // the replay window AND the packets received within the window.
-            // When the lower bound is 0, it's not possible to tell whether packet with seqNo 0 is
-            // received or not. For simplicity just assume that packet is received.
-            final long newExpectedPktCnt = newSeqLow + getPacketCntInReplayWindow(newState);
-            final long oldExpectedPktCnt = oldSeqLow + getPacketCntInReplayWindow(oldState);
-
-            final long expectedPktCntDiff = newExpectedPktCnt - oldExpectedPktCnt;
-            final long actualPktCntDiff = newState.getPacketCount() - oldState.getPacketCount();
-
-            logV(
-                    TAG,
-                    logPrefix
-                            + " expectedPktCntDiff: "
-                            + expectedPktCntDiff
-                            + " actualPktCntDiff: "
-                            + actualPktCntDiff);
-
-            if (expectedPktCntDiff < MIN_VALID_EXPECTED_RX_PACKET_NUM) {
-                // The sample size is too small to ensure a reliable detection result
-                return PacketLossCalculationResult.invalid();
-            }
-
-            if (expectedPktCntDiff < 0
-                    || expectedPktCntDiff == 0
-                    || actualPktCntDiff < 0
-                    || actualPktCntDiff > expectedPktCntDiff) {
-                logWtf(TAG, "Impossible values for expectedPktCntDiff or" + " actualPktCntDiff");
-                return PacketLossCalculationResult.invalid();
-            }
-
-            final int percent = 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
-            return isUnusualSeqNumLeap
-                    ? PacketLossCalculationResult.unusualSeqNumLeap(percent)
-                    : PacketLossCalculationResult.valid(percent);
-        }
-    }
-
-    private static void logVIpSecTransform(
-            String transformTag, IpSecTransformState state, String logPrefix) {
-        final String stateString =
-                " seqNo: "
-                        + state.getRxHighestSequenceNumber()
-                        + " | pktCnt: "
-                        + state.getPacketCount()
-                        + " | pktCntInWindow: "
-                        + getPacketCntInReplayWindow(state);
-        logV(TAG, logPrefix + " " + transformTag + stateString);
-    }
-
-    /** Get the number of received packets within the replay window */
-    private static long getPacketCntInReplayWindow(@NonNull IpSecTransformState state) {
-        return BitSet.valueOf(state.getReplayBitmap()).cardinality();
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class PacketLossCalculationResult {
-        @PacketLossResultType private final int mResultType;
-        private final int mPacketLossRatePercent;
-
-        private PacketLossCalculationResult(@PacketLossResultType int type, int percent) {
-            mResultType = type;
-            mPacketLossRatePercent = percent;
-        }
-
-        /** Construct an instance that contains a valid packet loss rate */
-        public static PacketLossCalculationResult valid(int percent) {
-            return new PacketLossCalculationResult(PACKET_LOSS_RATE_VALID, percent);
-        }
-
-        /** Construct an instance indicating the inability to get a valid packet loss rate */
-        public static PacketLossCalculationResult invalid() {
-            return new PacketLossCalculationResult(
-                    PACKET_LOSS_RATE_INVALID, PACKET_LOSS_PERCENT_UNAVAILABLE);
-        }
-
-        /** Construct an instance indicating that there is an unusual sequence number leap */
-        public static PacketLossCalculationResult unusualSeqNumLeap(int percent) {
-            return new PacketLossCalculationResult(PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP, percent);
-        }
-
-        @PacketLossResultType
-        public int getResultType() {
-            return mResultType;
-        }
-
-        public int getPacketLossRatePercent() {
-            return mPacketLossRatePercent;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mResultType, mPacketLossRatePercent);
-        }
-
-        @Override
-        public boolean equals(@Nullable Object other) {
-            if (!(other instanceof PacketLossCalculationResult)) {
-                return false;
-            }
-
-            final PacketLossCalculationResult rhs = (PacketLossCalculationResult) other;
-            return mResultType == rhs.mResultType
-                    && mPacketLossRatePercent == rhs.mPacketLossRatePercent;
-        }
-
-        @Override
-        public String toString() {
-            return "mResultType: "
-                    + mResultType
-                    + " | mPacketLossRatePercent: "
-                    + mPacketLossRatePercent;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
deleted file mode 100644
index 0d4c373..0000000
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
+++ /dev/null
@@ -1,278 +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.server.vcn.routeselection;
-
-import static com.android.server.VcnManagementService.LOCAL_LOG;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.IpSecTransform;
-import android.net.IpSecTransformState;
-import android.net.Network;
-import android.os.OutcomeReceiver;
-import android.util.CloseGuard;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.server.vcn.VcnContext;
-
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * NetworkMetricMonitor is responsible for managing metric monitoring and tracking validation
- * results.
- *
- * <p>This class is flag gated by "network_metric_monitor"
- */
-public abstract class NetworkMetricMonitor implements AutoCloseable {
-    private static final String TAG = NetworkMetricMonitor.class.getSimpleName();
-
-    private static final boolean VDBG = false; // STOPSHIP: if true
-
-    @NonNull private final CloseGuard mCloseGuard = new CloseGuard();
-
-    @NonNull private final VcnContext mVcnContext;
-    @NonNull private final Network mNetwork;
-    @NonNull private final NetworkMetricMonitorCallback mCallback;
-
-    private boolean mIsSelectedUnderlyingNetwork;
-    private boolean mIsStarted;
-    private boolean mIsValidationFailed;
-
-    protected NetworkMetricMonitor(
-            @NonNull VcnContext vcnContext,
-            @NonNull Network network,
-            @Nullable PersistableBundleWrapper carrierConfig,
-            @NonNull NetworkMetricMonitorCallback callback)
-            throws IllegalAccessException {
-        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
-        mNetwork = Objects.requireNonNull(network, "Missing network");
-        mCallback = Objects.requireNonNull(callback, "Missing callback");
-
-        mIsSelectedUnderlyingNetwork = false;
-        mIsStarted = false;
-        mIsValidationFailed = false;
-    }
-
-    /** Callback to notify caller of the validation result */
-    public interface NetworkMetricMonitorCallback {
-        /** Called when there is a validation result is ready */
-        void onValidationResultReceived();
-    }
-
-    /**
-     * Start monitoring
-     *
-     * <p>This method might be called on a an already started monitor for updating monitor
-     * properties (e.g. IpSecTransform, carrier config)
-     *
-     * <p>Subclasses MUST call super.start() when overriding this method
-     */
-    protected void start() {
-        mIsStarted = true;
-    }
-
-    /**
-     * Stop monitoring
-     *
-     * <p>Subclasses MUST call super.stop() when overriding this method
-     */
-    public void stop() {
-        mIsValidationFailed = false;
-        mIsStarted = false;
-    }
-
-    /** Called by the subclasses when the validation result is ready */
-    protected void onValidationResultReceivedInternal(boolean isFailed) {
-        mIsValidationFailed = isFailed;
-        mCallback.onValidationResultReceived();
-    }
-
-    /** Called when the underlying network changes to selected or unselected */
-    protected abstract void onSelectedUnderlyingNetworkChanged();
-
-    /**
-     * Mark the network being monitored selected or unselected
-     *
-     * <p>Subclasses MUST call super when overriding this method
-     */
-    public void setIsSelectedUnderlyingNetwork(boolean isSelectedUnderlyingNetwork) {
-        if (mIsSelectedUnderlyingNetwork == isSelectedUnderlyingNetwork) {
-            return;
-        }
-
-        mIsSelectedUnderlyingNetwork = isSelectedUnderlyingNetwork;
-        onSelectedUnderlyingNetworkChanged();
-    }
-
-    /** Wrapper that allows injection for testing purposes */
-    @VisibleForTesting(visibility = Visibility.PROTECTED)
-    public static class IpSecTransformWrapper {
-        @NonNull public final IpSecTransform ipSecTransform;
-
-        public IpSecTransformWrapper(@NonNull IpSecTransform ipSecTransform) {
-            this.ipSecTransform = ipSecTransform;
-        }
-
-        /** Poll an IpSecTransformState */
-        public void requestIpSecTransformState(
-                @NonNull Executor executor,
-                @NonNull OutcomeReceiver<IpSecTransformState, RuntimeException> callback) {
-            ipSecTransform.requestIpSecTransformState(executor, callback);
-        }
-
-        /** Close this instance and release the underlying resources */
-        public void close() {
-            ipSecTransform.close();
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(ipSecTransform);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof IpSecTransformWrapper)) {
-                return false;
-            }
-
-            final IpSecTransformWrapper other = (IpSecTransformWrapper) o;
-
-            return Objects.equals(ipSecTransform, other.ipSecTransform);
-        }
-    }
-
-    /** Set the IpSecTransform that applied to the Network being monitored */
-    public void setInboundTransform(@NonNull IpSecTransform inTransform) {
-        setInboundTransformInternal(new IpSecTransformWrapper(inTransform));
-    }
-
-    /**
-     * Set the IpSecTransform that applied to the Network being monitored *
-     *
-     * <p>Subclasses MUST call super when overriding this method
-     */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public void setInboundTransformInternal(@NonNull IpSecTransformWrapper inTransform) {
-        // Subclasses MUST override it if they care
-    }
-
-    /** Update the carrierconfig */
-    public void setCarrierConfig(@Nullable PersistableBundleWrapper carrierConfig) {
-        // Subclasses MUST override it if they care
-    }
-
-    /** Called when LinkProperties or NetworkCapabilities have changed */
-    public void onLinkPropertiesOrCapabilitiesChanged() {
-        // Subclasses MUST override it if they care
-    }
-
-    public boolean isValidationFailed() {
-        return mIsValidationFailed;
-    }
-
-    public boolean isSelectedUnderlyingNetwork() {
-        return mIsSelectedUnderlyingNetwork;
-    }
-
-    public boolean isStarted() {
-        return mIsStarted;
-    }
-
-    @NonNull
-    public VcnContext getVcnContext() {
-        return mVcnContext;
-    }
-
-    @NonNull
-    public Network getNetwork() {
-        return mNetwork;
-    }
-
-    // Override methods for AutoCloseable. Subclasses MUST call super when overriding this method
-    @Override
-    public void close() {
-        mCloseGuard.close();
-
-        stop();
-    }
-
-    // Override #finalize() to use closeGuard for flagging that #close() was not called
-    @SuppressWarnings("Finalize")
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            if (mCloseGuard != null) {
-                mCloseGuard.warnIfOpen();
-            }
-            close();
-        } finally {
-            super.finalize();
-        }
-    }
-
-    private String getClassName() {
-        return this.getClass().getSimpleName();
-    }
-
-    protected String getLogPrefix() {
-        return " [Network " + mNetwork + "] ";
-    }
-
-    protected void logV(String msg) {
-        if (VDBG) {
-            Slog.v(getClassName(), getLogPrefix() + msg);
-            LOCAL_LOG.log("[VERBOSE ] " + getClassName() + getLogPrefix() + msg);
-        }
-    }
-
-    protected void logInfo(String msg) {
-        Slog.i(getClassName(), getLogPrefix() + msg);
-        LOCAL_LOG.log("[INFO ] " + getClassName() + getLogPrefix() + msg);
-    }
-
-    protected void logW(String msg) {
-        Slog.w(getClassName(), getLogPrefix() + msg);
-        LOCAL_LOG.log("[WARN ] " + getClassName() + getLogPrefix() + msg);
-    }
-
-    protected void logWtf(String msg) {
-        Slog.wtf(getClassName(), getLogPrefix() + msg);
-        LOCAL_LOG.log("[WTF ] " + getClassName() + getLogPrefix() + msg);
-    }
-
-    protected static void logV(String className, String msgWithPrefix) {
-        if (VDBG) {
-            Slog.wtf(className, msgWithPrefix);
-            LOCAL_LOG.log("[VERBOSE ] " + className + msgWithPrefix);
-        }
-    }
-
-    protected static void logE(String className, String msgWithPrefix) {
-        Slog.w(className, msgWithPrefix);
-        LOCAL_LOG.log("[ERROR ] " + className + msgWithPrefix);
-    }
-
-    protected static void logWtf(String className, String msgWithPrefix) {
-        Slog.wtf(className, msgWithPrefix);
-        LOCAL_LOG.log("[WTF ] " + className + msgWithPrefix);
-    }
-}
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
deleted file mode 100644
index d32e5cc..0000000
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.vcn.routeselection;
-
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_TEST;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
-
-import static com.android.server.VcnManagementService.LOCAL_LOG;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.NetworkCapabilities;
-import android.net.TelephonyNetworkSpecifier;
-import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
-import android.net.vcn.VcnManager;
-import android.net.vcn.VcnUnderlyingNetworkTemplate;
-import android.net.vcn.VcnWifiUnderlyingNetworkTemplate;
-import android.os.ParcelUuid;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.VcnContext;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/** @hide */
-class NetworkPriorityClassifier {
-    @NonNull private static final String TAG = NetworkPriorityClassifier.class.getSimpleName();
-    /**
-     * Minimum signal strength for a WiFi network to be eligible for switching to
-     *
-     * <p>A network that satisfies this is eligible to become the selected underlying network with
-     * no additional conditions
-     */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT = -70;
-    /**
-     * Minimum signal strength to continue using a WiFi network
-     *
-     * <p>A network that satisfies the conditions may ONLY continue to be used if it is already
-     * selected as the underlying network. A WiFi network satisfying this condition, but NOT the
-     * prospective-network RSSI threshold CANNOT be switched to.
-     */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74;
-
-    /**
-     * Priority for networks that VCN can fall back to.
-     *
-     * <p>If none of the network candidates are validated or match any template, VCN will fall back
-     * to any INTERNET network.
-     */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_FALLBACK = Integer.MAX_VALUE;
-
-    /**
-     * Priority for networks that cannot be selected as VCN's underlying networks.
-     *
-     * <p>VCN MUST never select a non-INTERNET network that are unvalidated or fail to match any
-     * template as the underlying network.
-     */
-    static final int PRIORITY_INVALID = -1;
-
-    /** Gives networks a priority class, based on configured VcnGatewayConnectionConfig */
-    public static int calculatePriorityClass(
-            VcnContext vcnContext,
-            UnderlyingNetworkRecord networkRecord,
-            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            ParcelUuid subscriptionGroup,
-            TelephonySubscriptionSnapshot snapshot,
-            boolean isSelected,
-            PersistableBundleWrapper carrierConfig) {
-        // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED
-
-        if (networkRecord.isBlocked) {
-            logWtf("Network blocked for System Server: " + networkRecord.network);
-            return PRIORITY_INVALID;
-        }
-
-        if (snapshot == null) {
-            logWtf("Got null snapshot");
-            return PRIORITY_INVALID;
-        }
-
-        int priorityIndex = 0;
-        for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkTemplates) {
-            if (checkMatchesPriorityRule(
-                    vcnContext,
-                    nwPriority,
-                    networkRecord,
-                    subscriptionGroup,
-                    snapshot,
-                    isSelected,
-                    carrierConfig)) {
-                return priorityIndex;
-            }
-            priorityIndex++;
-        }
-
-        final NetworkCapabilities caps = networkRecord.networkCapabilities;
-        if (caps.hasCapability(NET_CAPABILITY_INTERNET)
-                || (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST))) {
-            return PRIORITY_FALLBACK;
-        }
-        return PRIORITY_INVALID;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static boolean checkMatchesPriorityRule(
-            VcnContext vcnContext,
-            VcnUnderlyingNetworkTemplate networkPriority,
-            UnderlyingNetworkRecord networkRecord,
-            ParcelUuid subscriptionGroup,
-            TelephonySubscriptionSnapshot snapshot,
-            boolean isSelected,
-            PersistableBundleWrapper carrierConfig) {
-        final NetworkCapabilities caps = networkRecord.networkCapabilities;
-
-        final int meteredMatch = networkPriority.getMetered();
-        final boolean isMetered = !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
-        if (meteredMatch == MATCH_REQUIRED && !isMetered
-                || meteredMatch == MATCH_FORBIDDEN && isMetered) {
-            return false;
-        }
-
-        // Fails bandwidth requirements if either (a) less than exit threshold, or (b), not
-        // selected, but less than entry threshold
-        if (caps.getLinkUpstreamBandwidthKbps() < networkPriority.getMinExitUpstreamBandwidthKbps()
-                || (caps.getLinkUpstreamBandwidthKbps()
-                                < networkPriority.getMinEntryUpstreamBandwidthKbps()
-                        && !isSelected)) {
-            return false;
-        }
-
-        if (caps.getLinkDownstreamBandwidthKbps()
-                        < networkPriority.getMinExitDownstreamBandwidthKbps()
-                || (caps.getLinkDownstreamBandwidthKbps()
-                                < networkPriority.getMinEntryDownstreamBandwidthKbps()
-                        && !isSelected)) {
-            return false;
-        }
-
-        for (Map.Entry<Integer, Integer> entry :
-                networkPriority.getCapabilitiesMatchCriteria().entrySet()) {
-            final int cap = entry.getKey();
-            final int matchCriteria = entry.getValue();
-
-            if (matchCriteria == MATCH_REQUIRED && !caps.hasCapability(cap)) {
-                return false;
-            } else if (matchCriteria == MATCH_FORBIDDEN && caps.hasCapability(cap)) {
-                return false;
-            }
-        }
-
-        if (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST)) {
-            return true;
-        }
-
-        if (networkPriority instanceof VcnWifiUnderlyingNetworkTemplate) {
-            return checkMatchesWifiPriorityRule(
-                    (VcnWifiUnderlyingNetworkTemplate) networkPriority,
-                    networkRecord,
-                    isSelected,
-                    carrierConfig);
-        }
-
-        if (networkPriority instanceof VcnCellUnderlyingNetworkTemplate) {
-            return checkMatchesCellPriorityRule(
-                    vcnContext,
-                    (VcnCellUnderlyingNetworkTemplate) networkPriority,
-                    networkRecord,
-                    subscriptionGroup,
-                    snapshot);
-        }
-
-        logWtf(
-                "Got unknown VcnUnderlyingNetworkTemplate class: "
-                        + networkPriority.getClass().getSimpleName());
-        return false;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static boolean checkMatchesWifiPriorityRule(
-            VcnWifiUnderlyingNetworkTemplate networkPriority,
-            UnderlyingNetworkRecord networkRecord,
-            boolean isSelected,
-            PersistableBundleWrapper carrierConfig) {
-        final NetworkCapabilities caps = networkRecord.networkCapabilities;
-
-        if (!caps.hasTransport(TRANSPORT_WIFI)) {
-            return false;
-        }
-
-        // TODO: Move the Network Quality check to the network metric monitor framework.
-        if (!isWifiRssiAcceptable(networkRecord, isSelected, carrierConfig)) {
-            return false;
-        }
-
-        if (!networkPriority.getSsids().isEmpty()
-                && !networkPriority.getSsids().contains(caps.getSsid())) {
-            return false;
-        }
-
-        return true;
-    }
-
-    private static boolean isWifiRssiAcceptable(
-            UnderlyingNetworkRecord networkRecord,
-            boolean isSelected,
-            PersistableBundleWrapper carrierConfig) {
-        final NetworkCapabilities caps = networkRecord.networkCapabilities;
-
-        if (isSelected && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
-            return true;
-        }
-
-        if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
-            return true;
-        }
-
-        return false;
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static boolean checkMatchesCellPriorityRule(
-            VcnContext vcnContext,
-            VcnCellUnderlyingNetworkTemplate networkPriority,
-            UnderlyingNetworkRecord networkRecord,
-            ParcelUuid subscriptionGroup,
-            TelephonySubscriptionSnapshot snapshot) {
-        final NetworkCapabilities caps = networkRecord.networkCapabilities;
-
-        if (!caps.hasTransport(TRANSPORT_CELLULAR)) {
-            return false;
-        }
-
-        final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
-                ((TelephonyNetworkSpecifier) caps.getNetworkSpecifier());
-        if (telephonyNetworkSpecifier == null) {
-            logWtf("Got null NetworkSpecifier");
-            return false;
-        }
-
-        final int subId = telephonyNetworkSpecifier.getSubscriptionId();
-        final TelephonyManager subIdSpecificTelephonyMgr =
-                vcnContext
-                        .getContext()
-                        .getSystemService(TelephonyManager.class)
-                        .createForSubscriptionId(subId);
-
-        if (!networkPriority.getOperatorPlmnIds().isEmpty()) {
-            final String plmnId = subIdSpecificTelephonyMgr.getNetworkOperator();
-            if (!networkPriority.getOperatorPlmnIds().contains(plmnId)) {
-                return false;
-            }
-        }
-
-        if (!networkPriority.getSimSpecificCarrierIds().isEmpty()) {
-            final int carrierId = subIdSpecificTelephonyMgr.getSimSpecificCarrierId();
-            if (!networkPriority.getSimSpecificCarrierIds().contains(carrierId)) {
-                return false;
-            }
-        }
-
-        final int roamingMatch = networkPriority.getRoaming();
-        final boolean isRoaming = !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING);
-        if (roamingMatch == MATCH_REQUIRED && !isRoaming
-                || roamingMatch == MATCH_FORBIDDEN && isRoaming) {
-            return false;
-        }
-
-        final int opportunisticMatch = networkPriority.getOpportunistic();
-        final boolean isOpportunistic = isOpportunistic(snapshot, caps.getSubscriptionIds());
-        if (opportunisticMatch == MATCH_REQUIRED) {
-            if (!isOpportunistic) {
-                return false;
-            }
-
-            // If this carrier is the active data provider, ensure that opportunistic is only
-            // ever prioritized if it is also the active data subscription. This ensures that
-            // if an opportunistic subscription is still in the process of being switched to,
-            // or switched away from, the VCN does not attempt to continue using it against the
-            // decision made at the telephony layer. Failure to do so may result in the modem
-            // switching back and forth.
-            //
-            // Allow the following two cases:
-            // 1. Active subId is NOT in the group that this VCN is supporting
-            // 2. This opportunistic subscription is for the active subId
-            if (snapshot.getAllSubIdsInGroup(subscriptionGroup)
-                            .contains(SubscriptionManager.getActiveDataSubscriptionId())
-                    && !caps.getSubscriptionIds()
-                            .contains(SubscriptionManager.getActiveDataSubscriptionId())) {
-                return false;
-            }
-        } else if (opportunisticMatch == MATCH_FORBIDDEN && !isOpportunistic) {
-            return false;
-        }
-
-        return true;
-    }
-
-    static boolean isOpportunistic(
-            @NonNull TelephonySubscriptionSnapshot snapshot, Set<Integer> subIds) {
-        if (snapshot == null) {
-            logWtf("Got null snapshot");
-            return false;
-        }
-        for (int subId : subIds) {
-            if (snapshot.isOpportunistic(subId)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    static int getWifiEntryRssiThreshold(@Nullable PersistableBundleWrapper carrierConfig) {
-        if (carrierConfig != null) {
-            return carrierConfig.getInt(
-                    VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
-                    WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT);
-        }
-        return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT;
-    }
-
-    static int getWifiExitRssiThreshold(@Nullable PersistableBundleWrapper carrierConfig) {
-        if (carrierConfig != null) {
-            return carrierConfig.getInt(
-                    VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
-                    WIFI_EXIT_RSSI_THRESHOLD_DEFAULT);
-        }
-        return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
-    }
-
-    private static void logWtf(String msg) {
-        Slog.wtf(TAG, msg);
-        LOCAL_LOG.log(TAG + " WTF: " + msg);
-    }
-}
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
deleted file mode 100644
index 3eeeece..0000000
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ /dev/null
@@ -1,753 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.vcn.routeselection;
-
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
-import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
-import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
-
-import static com.android.server.VcnManagementService.LOCAL_LOG;
-import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiEntryRssiThreshold;
-import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiExitRssiThreshold;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.IpSecTransform;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.net.TelephonyNetworkSpecifier;
-import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
-import android.net.vcn.VcnGatewayConnectionConfig;
-import android.net.vcn.VcnUnderlyingNetworkTemplate;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.ParcelUuid;
-import android.telephony.TelephonyCallback;
-import android.telephony.TelephonyManager;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.VcnContext;
-import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
-import com.android.server.vcn.util.LogUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.TreeSet;
-
-/**
- * Tracks a set of Networks underpinning a VcnGatewayConnection.
- *
- * <p>A single UnderlyingNetworkController is built to serve a SINGLE VCN Gateway Connection, and
- * MUST be torn down with the VcnGatewayConnection in order to ensure underlying networks are
- * allowed to be reaped.
- *
- * @hide
- */
-public class UnderlyingNetworkController {
-    @NonNull private static final String TAG = UnderlyingNetworkController.class.getSimpleName();
-
-    @NonNull private final VcnContext mVcnContext;
-    @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
-    @NonNull private final ParcelUuid mSubscriptionGroup;
-    @NonNull private final UnderlyingNetworkControllerCallback mCb;
-    @NonNull private final Dependencies mDeps;
-    @NonNull private final Handler mHandler;
-    @NonNull private final ConnectivityManager mConnectivityManager;
-    @NonNull private final TelephonyCallback mActiveDataSubIdListener =
-            new VcnActiveDataSubscriptionIdListener();
-
-    private final Map<Network, UnderlyingNetworkEvaluator> mUnderlyingNetworkRecords =
-            new ArrayMap<>();
-
-    @NonNull private final List<NetworkCallback> mCellBringupCallbacks = new ArrayList<>();
-    @Nullable private NetworkCallback mWifiBringupCallback;
-    @Nullable private NetworkCallback mWifiEntryRssiThresholdCallback;
-    @Nullable private NetworkCallback mWifiExitRssiThresholdCallback;
-    @Nullable private UnderlyingNetworkListener mRouteSelectionCallback;
-
-    @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
-    @Nullable private PersistableBundleWrapper mCarrierConfig;
-    private boolean mIsQuitting = false;
-
-    @Nullable private UnderlyingNetworkRecord mCurrentRecord;
-    @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress;
-
-    public UnderlyingNetworkController(
-            @NonNull VcnContext vcnContext,
-            @NonNull VcnGatewayConnectionConfig connectionConfig,
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull TelephonySubscriptionSnapshot snapshot,
-            @NonNull UnderlyingNetworkControllerCallback cb) {
-        this(vcnContext, connectionConfig, subscriptionGroup, snapshot, cb, new Dependencies());
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    UnderlyingNetworkController(
-            @NonNull VcnContext vcnContext,
-            @NonNull VcnGatewayConnectionConfig connectionConfig,
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull TelephonySubscriptionSnapshot snapshot,
-            @NonNull UnderlyingNetworkControllerCallback cb,
-            @NonNull Dependencies deps) {
-        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
-        mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
-        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
-        mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
-        mCb = Objects.requireNonNull(cb, "Missing cb");
-        mDeps = Objects.requireNonNull(deps, "Missing deps");
-
-        mHandler = new Handler(mVcnContext.getLooper());
-
-        mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
-        mVcnContext
-                .getContext()
-                .getSystemService(TelephonyManager.class)
-                .registerTelephonyCallback(new HandlerExecutor(mHandler), mActiveDataSubIdListener);
-
-        mCarrierConfig = mLastSnapshot.getCarrierConfigForSubGrp(mSubscriptionGroup);
-
-        registerOrUpdateNetworkRequests();
-    }
-
-    private static class CapabilityMatchCriteria {
-        public final int capability;
-        public final int matchCriteria;
-
-        CapabilityMatchCriteria(int capability, int matchCriteria) {
-            this.capability = capability;
-            this.matchCriteria = matchCriteria;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(capability, matchCriteria);
-        }
-
-        @Override
-        public boolean equals(@Nullable Object other) {
-            if (!(other instanceof CapabilityMatchCriteria)) {
-                return false;
-            }
-
-            final CapabilityMatchCriteria rhs = (CapabilityMatchCriteria) other;
-            return capability == rhs.capability && matchCriteria == rhs.matchCriteria;
-        }
-    }
-
-    private static Set<Set<CapabilityMatchCriteria>> dedupAndGetCapRequirementsForCell(
-            VcnGatewayConnectionConfig connectionConfig) {
-        final Set<Set<CapabilityMatchCriteria>> dedupedCapsMatchSets = new ArraySet<>();
-
-        for (VcnUnderlyingNetworkTemplate template :
-                connectionConfig.getVcnUnderlyingNetworkPriorities()) {
-            if (template instanceof VcnCellUnderlyingNetworkTemplate) {
-                final Set<CapabilityMatchCriteria> capsMatchSet = new ArraySet<>();
-
-                for (Map.Entry<Integer, Integer> entry :
-                        ((VcnCellUnderlyingNetworkTemplate) template)
-                                .getCapabilitiesMatchCriteria()
-                                .entrySet()) {
-
-                    final int capability = entry.getKey();
-                    final int matchCriteria = entry.getValue();
-                    if (matchCriteria != MATCH_ANY) {
-                        capsMatchSet.add(new CapabilityMatchCriteria(capability, matchCriteria));
-                    }
-                }
-
-                dedupedCapsMatchSets.add(capsMatchSet);
-            }
-        }
-
-        dedupedCapsMatchSets.add(
-                Collections.singleton(
-                        new CapabilityMatchCriteria(
-                                NetworkCapabilities.NET_CAPABILITY_INTERNET, MATCH_REQUIRED)));
-        return dedupedCapsMatchSets;
-    }
-
-    private void registerOrUpdateNetworkRequests() {
-        NetworkCallback oldRouteSelectionCallback = mRouteSelectionCallback;
-        NetworkCallback oldWifiCallback = mWifiBringupCallback;
-        NetworkCallback oldWifiEntryRssiThresholdCallback = mWifiEntryRssiThresholdCallback;
-        NetworkCallback oldWifiExitRssiThresholdCallback = mWifiExitRssiThresholdCallback;
-        List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks);
-        mCellBringupCallbacks.clear();
-
-        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
-            evaluator.close();
-        }
-
-        mUnderlyingNetworkRecords.clear();
-
-        // Register new callbacks. Make-before-break; always register new callbacks before removal
-        // of old callbacks
-        if (!mIsQuitting) {
-            mRouteSelectionCallback = new UnderlyingNetworkListener();
-            mConnectivityManager.registerNetworkCallback(
-                    getRouteSelectionRequest(), mRouteSelectionCallback, mHandler);
-
-            mWifiEntryRssiThresholdCallback = new NetworkBringupCallback();
-            mConnectivityManager.registerNetworkCallback(
-                    getWifiEntryRssiThresholdNetworkRequest(),
-                    mWifiEntryRssiThresholdCallback,
-                    mHandler);
-
-            mWifiExitRssiThresholdCallback = new NetworkBringupCallback();
-            mConnectivityManager.registerNetworkCallback(
-                    getWifiExitRssiThresholdNetworkRequest(),
-                    mWifiExitRssiThresholdCallback,
-                    mHandler);
-
-            mWifiBringupCallback = new NetworkBringupCallback();
-            mConnectivityManager.requestBackgroundNetwork(
-                    getWifiNetworkRequest(), mWifiBringupCallback, mHandler);
-
-            for (final int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
-                for (Set<CapabilityMatchCriteria> capsMatchCriteria :
-                        dedupAndGetCapRequirementsForCell(mConnectionConfig)) {
-                    final NetworkBringupCallback cb = new NetworkBringupCallback();
-                    mCellBringupCallbacks.add(cb);
-
-                    mConnectivityManager.requestBackgroundNetwork(
-                            getCellNetworkRequestForSubId(subId, capsMatchCriteria), cb, mHandler);
-                }
-            }
-        } else {
-            mRouteSelectionCallback = null;
-            mWifiBringupCallback = null;
-            mWifiEntryRssiThresholdCallback = null;
-            mWifiExitRssiThresholdCallback = null;
-            // mCellBringupCallbacks already cleared above.
-        }
-
-        // Unregister old callbacks (as necessary)
-        if (oldRouteSelectionCallback != null) {
-            mConnectivityManager.unregisterNetworkCallback(oldRouteSelectionCallback);
-        }
-        if (oldWifiCallback != null) {
-            mConnectivityManager.unregisterNetworkCallback(oldWifiCallback);
-        }
-        if (oldWifiEntryRssiThresholdCallback != null) {
-            mConnectivityManager.unregisterNetworkCallback(oldWifiEntryRssiThresholdCallback);
-        }
-        if (oldWifiExitRssiThresholdCallback != null) {
-            mConnectivityManager.unregisterNetworkCallback(oldWifiExitRssiThresholdCallback);
-        }
-        for (NetworkCallback cellBringupCallback : oldCellCallbacks) {
-            mConnectivityManager.unregisterNetworkCallback(cellBringupCallback);
-        }
-    }
-
-    /**
-     * Builds the Route selection request
-     *
-     * <p>This request is guaranteed to select carrier-owned, non-VCN underlying networks by virtue
-     * of a populated set of subIds as expressed in NetworkCapabilities#getSubscriptionIds(). Only
-     * carrier owned networks may be selected, as the request specifies only subIds in the VCN's
-     * subscription group, while the VCN networks are excluded by virtue of not having subIds set on
-     * the VCN-exposed networks.
-     *
-     * <p>If the VCN that this UnderlyingNetworkController belongs to is in test-mode, this will
-     * return a NetworkRequest that only matches Test Networks.
-     */
-    private NetworkRequest getRouteSelectionRequest() {
-        if (mVcnContext.isInTestMode()) {
-            return getTestNetworkRequest(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup));
-        }
-
-        return getBaseNetworkRequestBuilder()
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
-                .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))
-                .build();
-    }
-
-    private NetworkRequest.Builder getBaseWifiNetworkRequestBuilder() {
-        return getBaseNetworkRequestBuilder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .setSubscriptionIds(mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup));
-    }
-
-    /**
-     * Builds the WiFi bringup request
-     *
-     * <p>This request is built specifically to match only carrier-owned WiFi networks, but is also
-     * built to ONLY keep Carrier WiFi Networks alive (but never bring them up). This is a result of
-     * the WifiNetworkFactory not advertising a list of subIds, and therefore not accepting this
-     * request. As such, it will bind to a Carrier WiFi Network that has already been brought up,
-     * but will NEVER bring up a Carrier WiFi network itself.
-     */
-    private NetworkRequest getWifiNetworkRequest() {
-        return getBaseWifiNetworkRequestBuilder().build();
-    }
-
-    /**
-     * Builds the WiFi entry threshold signal strength request
-     *
-     * <p>This request ensures that WiFi reports the crossing of the wifi entry RSSI threshold.
-     * Without this request, WiFi rate-limits, and reports signal strength changes at too slow a
-     * pace to effectively select a short-lived WiFi offload network.
-     */
-    private NetworkRequest getWifiEntryRssiThresholdNetworkRequest() {
-        return getBaseWifiNetworkRequestBuilder()
-                // Ensure wifi updates signal strengths when crossing this threshold.
-                .setSignalStrength(getWifiEntryRssiThreshold(mCarrierConfig))
-                .build();
-    }
-
-    /**
-     * Builds the WiFi exit threshold signal strength request
-     *
-     * <p>This request ensures that WiFi reports the crossing of the wifi exit RSSI threshold.
-     * Without this request, WiFi rate-limits, and reports signal strength changes at too slow a
-     * pace to effectively select away from a failing WiFi network.
-     */
-    private NetworkRequest getWifiExitRssiThresholdNetworkRequest() {
-        return getBaseWifiNetworkRequestBuilder()
-                // Ensure wifi updates signal strengths when crossing this threshold.
-                .setSignalStrength(getWifiExitRssiThreshold(mCarrierConfig))
-                .build();
-    }
-
-    /**
-     * Builds a Cellular bringup request for a given subId
-     *
-     * <p>This request is filed in order to ensure that the Telephony stack always has a
-     * NetworkRequest to bring up a VCN underlying cellular network. It is required in order to
-     * ensure that even when a VCN (appears as Cellular) satisfies the default request, Telephony
-     * will bring up additional underlying Cellular networks.
-     *
-     * <p>Since this request MUST make it to the TelephonyNetworkFactory, subIds are not specified
-     * in the NetworkCapabilities, but rather in the TelephonyNetworkSpecifier.
-     */
-    private NetworkRequest getCellNetworkRequestForSubId(
-            int subId, Set<CapabilityMatchCriteria> capsMatchCriteria) {
-        final NetworkRequest.Builder nrBuilder =
-                getBaseNetworkRequestBuilder()
-                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                        .setNetworkSpecifier(
-                                new TelephonyNetworkSpecifier.Builder()
-                                        .setSubscriptionId(subId)
-                                        .build());
-
-        for (CapabilityMatchCriteria capMatchCriteria : capsMatchCriteria) {
-            final int cap = capMatchCriteria.capability;
-            final int matchCriteria = capMatchCriteria.matchCriteria;
-
-            if (matchCriteria == MATCH_REQUIRED) {
-                nrBuilder.addCapability(cap);
-            } else if (matchCriteria == MATCH_FORBIDDEN) {
-                nrBuilder.addForbiddenCapability(cap);
-            }
-        }
-
-        return nrBuilder.build();
-    }
-
-    /**
-     * Builds and returns a NetworkRequest builder common to all Underlying Network requests
-     */
-    private NetworkRequest.Builder getBaseNetworkRequestBuilder() {
-        return new NetworkRequest.Builder()
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
-    }
-
-    /** Builds and returns a NetworkRequest for the given subIds to match Test Networks. */
-    private NetworkRequest getTestNetworkRequest(@NonNull Set<Integer> subIds) {
-        return new NetworkRequest.Builder()
-                .clearCapabilities()
-                .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
-                .setSubscriptionIds(subIds)
-                .build();
-    }
-
-    /**
-     * Update this UnderlyingNetworkController's TelephonySubscriptionSnapshot.
-     *
-     * <p>Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkController to
-     * reevaluate its NetworkBringupCallbacks. This may result in NetworkRequests being registered
-     * or unregistered if the subIds mapped to the this Tracker's SubscriptionGroup change.
-     */
-    public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot newSnapshot) {
-        Objects.requireNonNull(newSnapshot, "Missing newSnapshot");
-
-        final TelephonySubscriptionSnapshot oldSnapshot = mLastSnapshot;
-        mLastSnapshot = newSnapshot;
-
-        // Update carrier config
-        mCarrierConfig = mLastSnapshot.getCarrierConfigForSubGrp(mSubscriptionGroup);
-
-        // Make sure all evaluators use the same updated TelephonySubscriptionSnapshot and carrier
-        // config to calculate their cached priority classes. For simplicity, the
-        // UnderlyingNetworkController does not listen for changes in VCN-related carrier config
-        // keys, and changes are applied at restart of the VcnGatewayConnection
-        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
-            evaluator.reevaluate(
-                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
-                    mSubscriptionGroup,
-                    mLastSnapshot,
-                    mCarrierConfig);
-        }
-
-        // Only trigger re-registration if subIds in this group have changed
-        if (oldSnapshot
-                .getAllSubIdsInGroup(mSubscriptionGroup)
-                .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) {
-            reevaluateNetworks();
-            return;
-        }
-        registerOrUpdateNetworkRequests();
-    }
-
-    /**
-     * Pass the IpSecTransform of the VCN to UnderlyingNetworkController for metric monitoring
-     *
-     * <p>Caller MUST call it when IpSecTransforms have been created for VCN creation or migration
-     */
-    public void updateInboundTransform(
-            @NonNull UnderlyingNetworkRecord currentNetwork, @NonNull IpSecTransform transform) {
-        Objects.requireNonNull(currentNetwork, "currentNetwork is null");
-        Objects.requireNonNull(transform, "transform is null");
-
-        if (mCurrentRecord == null
-                || mRouteSelectionCallback == null
-                || !Objects.equals(currentNetwork.network, mCurrentRecord.network)) {
-            // The caller (VcnGatewayConnection) is out-of-dated. Ignore this call.
-            return;
-        }
-
-        mUnderlyingNetworkRecords.get(mCurrentRecord.network).setInboundTransform(transform);
-    }
-
-    /** Tears down this Tracker, and releases all underlying network requests. */
-    public void teardown() {
-        mVcnContext.ensureRunningOnLooperThread();
-        mIsQuitting = true;
-
-        // Will unregister all existing callbacks, but not register new ones due to quitting flag.
-        registerOrUpdateNetworkRequests();
-
-        mVcnContext
-                .getContext()
-                .getSystemService(TelephonyManager.class)
-                .unregisterTelephonyCallback(mActiveDataSubIdListener);
-    }
-
-    private TreeSet<UnderlyingNetworkEvaluator> getSortedUnderlyingNetworks() {
-        TreeSet<UnderlyingNetworkEvaluator> sorted =
-                new TreeSet<>(UnderlyingNetworkEvaluator.getComparator(mVcnContext));
-
-        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
-            if (evaluator.getPriorityClass() != NetworkPriorityClassifier.PRIORITY_INVALID) {
-                sorted.add(evaluator);
-            }
-        }
-
-        return sorted;
-    }
-
-    private void reevaluateNetworks() {
-        if (mIsQuitting || mRouteSelectionCallback == null) {
-            return; // UnderlyingNetworkController has quit.
-        }
-
-        TreeSet<UnderlyingNetworkEvaluator> sorted = getSortedUnderlyingNetworks();
-
-        UnderlyingNetworkEvaluator candidateEvaluator = sorted.isEmpty() ? null : sorted.first();
-        UnderlyingNetworkRecord candidate =
-                candidateEvaluator == null ? null : candidateEvaluator.getNetworkRecord();
-        if (Objects.equals(mCurrentRecord, candidate)) {
-            return;
-        }
-
-        String allNetworkPriorities = "";
-        for (UnderlyingNetworkEvaluator recordEvaluator : sorted) {
-            if (!allNetworkPriorities.isEmpty()) {
-                allNetworkPriorities += ", ";
-            }
-            allNetworkPriorities +=
-                    recordEvaluator.getNetwork() + ": " + recordEvaluator.getPriorityClass();
-        }
-
-        if (!UnderlyingNetworkRecord.isSameNetwork(mCurrentRecord, candidate)) {
-            logInfo(
-                    "Selected network changed to "
-                            + (candidate == null ? null : candidate.network)
-                            + ", selected from list: "
-                            + allNetworkPriorities);
-        }
-
-        mCurrentRecord = candidate;
-        mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord);
-
-        // Need to update all evaluators to ensure the previously selected one is unselected
-        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
-            evaluator.setIsSelected(
-                    candidateEvaluator == evaluator,
-                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
-                    mSubscriptionGroup,
-                    mLastSnapshot,
-                    mCarrierConfig);
-        }
-    }
-
-    /**
-     * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped.
-     *
-     * <p>NetworkBringupCallback only exists to prevent matching (VCN-managed) Networks from being
-     * reaped, and no action is taken on any events firing.
-     */
-    @VisibleForTesting
-    class NetworkBringupCallback extends NetworkCallback {}
-
-    /**
-     * RouteSelectionCallback is used to select the "best" underlying Network.
-     *
-     * <p>The "best" network is determined by ConnectivityService, which is treated as a source of
-     * truth.
-     */
-    @VisibleForTesting
-    class UnderlyingNetworkListener extends NetworkCallback {
-        UnderlyingNetworkListener() {
-            super(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO);
-        }
-
-        @Override
-        public void onAvailable(@NonNull Network network) {
-            mUnderlyingNetworkRecords.put(
-                    network,
-                    mDeps.newUnderlyingNetworkEvaluator(
-                            mVcnContext,
-                            network,
-                            mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
-                            mSubscriptionGroup,
-                            mLastSnapshot,
-                            mCarrierConfig,
-                            new NetworkEvaluatorCallbackImpl()));
-        }
-
-        @Override
-        public void onLost(@NonNull Network network) {
-            mUnderlyingNetworkRecords.get(network).close();
-            mUnderlyingNetworkRecords.remove(network);
-
-            reevaluateNetworks();
-        }
-
-        @Override
-        public void onCapabilitiesChanged(
-                @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
-            final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
-            if (evaluator == null) {
-                logWtf("Got capabilities change for unknown key: " + network);
-                return;
-            }
-
-            evaluator.setNetworkCapabilities(
-                    networkCapabilities,
-                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
-                    mSubscriptionGroup,
-                    mLastSnapshot,
-                    mCarrierConfig);
-
-            if (evaluator.isValid()) {
-                reevaluateNetworks();
-            }
-        }
-
-        @Override
-        public void onLinkPropertiesChanged(
-                @NonNull Network network, @NonNull LinkProperties linkProperties) {
-            final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
-            if (evaluator == null) {
-                logWtf("Got link properties change for unknown key: " + network);
-                return;
-            }
-
-            evaluator.setLinkProperties(
-                    linkProperties,
-                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
-                    mSubscriptionGroup,
-                    mLastSnapshot,
-                    mCarrierConfig);
-
-            if (evaluator.isValid()) {
-                reevaluateNetworks();
-            }
-        }
-
-        @Override
-        public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) {
-            final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
-            if (evaluator == null) {
-                logWtf("Got blocked status change for unknown key: " + network);
-                return;
-            }
-
-            evaluator.setIsBlocked(
-                    isBlocked,
-                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
-                    mSubscriptionGroup,
-                    mLastSnapshot,
-                    mCarrierConfig);
-
-            if (evaluator.isValid()) {
-                reevaluateNetworks();
-            }
-        }
-    }
-
-    @VisibleForTesting
-    class NetworkEvaluatorCallbackImpl implements NetworkEvaluatorCallback {
-        @Override
-        public void onEvaluationResultChanged() {
-            mVcnContext.ensureRunningOnLooperThread();
-            reevaluateNetworks();
-        }
-    }
-
-    private String getLogPrefix() {
-        return "("
-                + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
-                + "-"
-                + mConnectionConfig.getGatewayConnectionName()
-                + "-"
-                + System.identityHashCode(this)
-                + ") ";
-    }
-
-    private String getTagLogPrefix() {
-        return "[ " + TAG + " " + getLogPrefix() + "]";
-    }
-
-    private void logInfo(String msg) {
-        Slog.i(TAG, getLogPrefix() + msg);
-        LOCAL_LOG.log("[INFO] " + getTagLogPrefix() + msg);
-    }
-
-    private void logInfo(String msg, Throwable tr) {
-        Slog.i(TAG, getLogPrefix() + msg, tr);
-        LOCAL_LOG.log("[INFO] " + getTagLogPrefix() + msg + tr);
-    }
-
-    private void logWtf(String msg) {
-        Slog.wtf(TAG, msg);
-        LOCAL_LOG.log(TAG + "[WTF ] " + getTagLogPrefix() + msg);
-    }
-
-    private void logWtf(String msg, Throwable tr) {
-        Slog.wtf(TAG, msg, tr);
-        LOCAL_LOG.log(TAG + "[WTF ] " + getTagLogPrefix() + msg + tr);
-    }
-
-    /** Dumps the state of this record for logging and debugging purposes. */
-    public void dump(IndentingPrintWriter pw) {
-        pw.println("UnderlyingNetworkController:");
-        pw.increaseIndent();
-
-        pw.println("Carrier WiFi Entry Threshold: " + getWifiEntryRssiThreshold(mCarrierConfig));
-        pw.println("Carrier WiFi Exit Threshold: " + getWifiExitRssiThreshold(mCarrierConfig));
-        pw.println(
-                "Currently selected: " + (mCurrentRecord == null ? null : mCurrentRecord.network));
-
-        pw.println("VcnUnderlyingNetworkTemplate list:");
-        pw.increaseIndent();
-        int index = 0;
-        for (VcnUnderlyingNetworkTemplate priority :
-                mConnectionConfig.getVcnUnderlyingNetworkPriorities()) {
-            pw.println("Priority index: " + index);
-            priority.dump(pw);
-            index++;
-        }
-        pw.decreaseIndent();
-        pw.println();
-
-        pw.println("Underlying networks:");
-        pw.increaseIndent();
-        if (mRouteSelectionCallback != null) {
-            for (UnderlyingNetworkEvaluator recordEvaluator : getSortedUnderlyingNetworks()) {
-                recordEvaluator.dump(pw);
-            }
-        }
-        pw.decreaseIndent();
-        pw.println();
-
-        pw.decreaseIndent();
-    }
-
-    private class VcnActiveDataSubscriptionIdListener extends TelephonyCallback
-            implements ActiveDataSubscriptionIdListener {
-        @Override
-        public void onActiveDataSubscriptionIdChanged(int subId) {
-            reevaluateNetworks();
-        }
-    }
-
-    /** Callbacks for being notified of the changes in, or to the selected underlying network. */
-    public interface UnderlyingNetworkControllerCallback {
-        /**
-         * Fired when a new underlying network is selected, or properties have changed.
-         *
-         * <p>This callback does NOT signal a mobility event.
-         *
-         * @param underlyingNetworkRecord The details of the new underlying network
-         */
-        void onSelectedUnderlyingNetworkChanged(
-                @Nullable UnderlyingNetworkRecord underlyingNetworkRecord);
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class Dependencies {
-        public UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator(
-                @NonNull VcnContext vcnContext,
-                @NonNull Network network,
-                @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-                @NonNull ParcelUuid subscriptionGroup,
-                @NonNull TelephonySubscriptionSnapshot lastSnapshot,
-                @Nullable PersistableBundleWrapper carrierConfig,
-                @NonNull NetworkEvaluatorCallback evaluatorCallback) {
-            return new UnderlyingNetworkEvaluator(
-                    vcnContext,
-                    network,
-                    underlyingNetworkTemplates,
-                    subscriptionGroup,
-                    lastSnapshot,
-                    carrierConfig,
-                    evaluatorCallback);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
deleted file mode 100644
index 08be11e..0000000
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
+++ /dev/null
@@ -1,437 +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.server.vcn.routeselection;
-
-import static com.android.server.VcnManagementService.LOCAL_LOG;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.IpSecTransform;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.vcn.VcnManager;
-import android.net.vcn.VcnUnderlyingNetworkTemplate;
-import android.os.Handler;
-import android.os.ParcelUuid;
-import android.util.IndentingPrintWriter;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.VcnContext;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-
-/**
- * UnderlyingNetworkEvaluator evaluates the quality and priority class of a network candidate for
- * route selection.
- *
- * @hide
- */
-public class UnderlyingNetworkEvaluator {
-    private static final String TAG = UnderlyingNetworkEvaluator.class.getSimpleName();
-
-    private static final int[] PENALTY_TIMEOUT_MINUTES_DEFAULT = new int[] {5};
-
-    @NonNull private final VcnContext mVcnContext;
-    @NonNull private final Handler mHandler;
-    @NonNull private final Object mCancellationToken = new Object();
-
-    @NonNull private final UnderlyingNetworkRecord.Builder mNetworkRecordBuilder;
-
-    @NonNull private final NetworkEvaluatorCallback mEvaluatorCallback;
-    @NonNull private final List<NetworkMetricMonitor> mMetricMonitors = new ArrayList<>();
-
-    @NonNull private final Dependencies mDependencies;
-
-    // TODO: Support back-off timeouts
-    private long mPenalizedTimeoutMs;
-
-    private boolean mIsSelected;
-    private boolean mIsPenalized;
-    private int mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID;
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public UnderlyingNetworkEvaluator(
-            @NonNull VcnContext vcnContext,
-            @NonNull Network network,
-            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
-            @Nullable PersistableBundleWrapper carrierConfig,
-            @NonNull NetworkEvaluatorCallback evaluatorCallback,
-            @NonNull Dependencies dependencies) {
-        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
-        mHandler = new Handler(mVcnContext.getLooper());
-
-        mDependencies = Objects.requireNonNull(dependencies, "Missing dependencies");
-        mEvaluatorCallback = Objects.requireNonNull(evaluatorCallback, "Missing deps");
-
-        Objects.requireNonNull(underlyingNetworkTemplates, "Missing underlyingNetworkTemplates");
-        Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
-        Objects.requireNonNull(lastSnapshot, "Missing lastSnapshot");
-
-        mNetworkRecordBuilder =
-                new UnderlyingNetworkRecord.Builder(
-                        Objects.requireNonNull(network, "Missing network"));
-        mIsSelected = false;
-        mIsPenalized = false;
-        mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig);
-
-        updatePriorityClass(
-                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
-
-        try {
-            mMetricMonitors.add(
-                    mDependencies.newIpSecPacketLossDetector(
-                            mVcnContext,
-                            mNetworkRecordBuilder.getNetwork(),
-                            carrierConfig,
-                            new MetricMonitorCallbackImpl()));
-        } catch (IllegalAccessException e) {
-            // No action. Do not add anything to mMetricMonitors
-        }
-    }
-
-    public UnderlyingNetworkEvaluator(
-            @NonNull VcnContext vcnContext,
-            @NonNull Network network,
-            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
-            @Nullable PersistableBundleWrapper carrierConfig,
-            @NonNull NetworkEvaluatorCallback evaluatorCallback) {
-        this(
-                vcnContext,
-                network,
-                underlyingNetworkTemplates,
-                subscriptionGroup,
-                lastSnapshot,
-                carrierConfig,
-                evaluatorCallback,
-                new Dependencies());
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class Dependencies {
-        /** Get an IpSecPacketLossDetector instance */
-        public IpSecPacketLossDetector newIpSecPacketLossDetector(
-                @NonNull VcnContext vcnContext,
-                @NonNull Network network,
-                @Nullable PersistableBundleWrapper carrierConfig,
-                @NonNull NetworkMetricMonitor.NetworkMetricMonitorCallback callback)
-                throws IllegalAccessException {
-            return new IpSecPacketLossDetector(vcnContext, network, carrierConfig, callback);
-        }
-    }
-
-    /** Callback to notify caller to reevaluate network selection */
-    public interface NetworkEvaluatorCallback {
-        /**
-         * Called when mIsPenalized changed
-         *
-         * <p>When receiving this call, UnderlyingNetworkController should reevaluate all network
-         * candidates for VCN underlying network selection
-         */
-        void onEvaluationResultChanged();
-    }
-
-    private class MetricMonitorCallbackImpl
-            implements NetworkMetricMonitor.NetworkMetricMonitorCallback {
-        public void onValidationResultReceived() {
-            mVcnContext.ensureRunningOnLooperThread();
-
-            handleValidationResult();
-        }
-    }
-
-    private void updatePriorityClass(
-            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
-            @Nullable PersistableBundleWrapper carrierConfig) {
-        if (mNetworkRecordBuilder.isValid()) {
-            mPriorityClass =
-                    NetworkPriorityClassifier.calculatePriorityClass(
-                            mVcnContext,
-                            mNetworkRecordBuilder.build(),
-                            underlyingNetworkTemplates,
-                            subscriptionGroup,
-                            lastSnapshot,
-                            mIsSelected,
-                            carrierConfig);
-        } else {
-            mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID;
-        }
-    }
-
-    /** Get the comparator for UnderlyingNetworkEvaluator */
-    public static Comparator<UnderlyingNetworkEvaluator> getComparator(VcnContext vcnContext) {
-        return (left, right) -> {
-            if (left.mIsPenalized != right.mIsPenalized) {
-                // A penalized network should have lower priority which means a larger index
-                return left.mIsPenalized ? 1 : -1;
-            }
-
-            final int leftIndex = left.mPriorityClass;
-            final int rightIndex = right.mPriorityClass;
-
-            // In the case of networks in the same priority class, prioritize based on other
-            // criteria (eg. actively selected network, link metrics, etc)
-            if (leftIndex == rightIndex) {
-                // TODO: Improve the strategy of network selection when both UnderlyingNetworkRecord
-                // fall into the same priority class.
-                if (left.mIsSelected) {
-                    return -1;
-                }
-                if (right.mIsSelected) {
-                    return 1;
-                }
-            }
-            return Integer.compare(leftIndex, rightIndex);
-        };
-    }
-
-    private static long getPenaltyTimeoutMs(@Nullable PersistableBundleWrapper carrierConfig) {
-        final int[] timeoutMinuteList;
-
-        if (carrierConfig != null) {
-            timeoutMinuteList =
-                    carrierConfig.getIntArray(
-                            VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
-                            PENALTY_TIMEOUT_MINUTES_DEFAULT);
-        } else {
-            timeoutMinuteList = PENALTY_TIMEOUT_MINUTES_DEFAULT;
-        }
-
-        // TODO: Add the support of back-off timeouts and return the full list
-        return TimeUnit.MINUTES.toMillis(timeoutMinuteList[0]);
-    }
-
-    private void handleValidationResult() {
-        final boolean wasPenalized = mIsPenalized;
-        mIsPenalized = false;
-        for (NetworkMetricMonitor monitor : mMetricMonitors) {
-            mIsPenalized |= monitor.isValidationFailed();
-        }
-
-        if (wasPenalized == mIsPenalized) {
-            return;
-        }
-
-        logInfo(
-                "#handleValidationResult: wasPenalized "
-                        + wasPenalized
-                        + " mIsPenalized "
-                        + mIsPenalized);
-
-        if (mIsPenalized) {
-            mHandler.postDelayed(
-                    new ExitPenaltyBoxRunnable(), mCancellationToken, mPenalizedTimeoutMs);
-        } else {
-            // Exit the penalty box
-            mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
-        }
-        mEvaluatorCallback.onEvaluationResultChanged();
-    }
-
-    public class ExitPenaltyBoxRunnable implements Runnable {
-        @Override
-        public void run() {
-            if (!mIsPenalized) {
-                logWtf("Evaluator not being penalized but ExitPenaltyBoxRunnable was scheduled");
-                return;
-            }
-
-            // TODO: There might be a future metric monitor (e.g. ping) that will require the
-            // validation to pass before exiting the penalty box.
-            mIsPenalized = false;
-            mEvaluatorCallback.onEvaluationResultChanged();
-        }
-    }
-
-    /** Set the NetworkCapabilities */
-    public void setNetworkCapabilities(
-            @NonNull NetworkCapabilities nc,
-            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
-            @Nullable PersistableBundleWrapper carrierConfig) {
-        mNetworkRecordBuilder.setNetworkCapabilities(nc);
-
-        updatePriorityClass(
-                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
-
-        for (NetworkMetricMonitor monitor : mMetricMonitors) {
-            monitor.onLinkPropertiesOrCapabilitiesChanged();
-        }
-    }
-
-    /** Set the LinkProperties */
-    public void setLinkProperties(
-            @NonNull LinkProperties lp,
-            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
-            @Nullable PersistableBundleWrapper carrierConfig) {
-        mNetworkRecordBuilder.setLinkProperties(lp);
-
-        updatePriorityClass(
-                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
-
-        for (NetworkMetricMonitor monitor : mMetricMonitors) {
-            monitor.onLinkPropertiesOrCapabilitiesChanged();
-        }
-    }
-
-    /** Set whether the network is blocked */
-    public void setIsBlocked(
-            boolean isBlocked,
-            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
-            @Nullable PersistableBundleWrapper carrierConfig) {
-        mNetworkRecordBuilder.setIsBlocked(isBlocked);
-
-        updatePriorityClass(
-                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
-    }
-
-    /** Set whether the network is selected as VCN's underlying network */
-    public void setIsSelected(
-            boolean isSelected,
-            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
-            @Nullable PersistableBundleWrapper carrierConfig) {
-        mIsSelected = isSelected;
-
-        updatePriorityClass(
-                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
-
-        for (NetworkMetricMonitor monitor : mMetricMonitors) {
-            monitor.setIsSelectedUnderlyingNetwork(isSelected);
-        }
-    }
-
-    /**
-     * Update the last TelephonySubscriptionSnapshot and carrier config to reevaluate the network
-     */
-    public void reevaluate(
-            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            @NonNull ParcelUuid subscriptionGroup,
-            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
-            @Nullable PersistableBundleWrapper carrierConfig) {
-        updatePriorityClass(
-                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
-
-        // The already scheduled event will not be affected. The followup events will be scheduled
-        // with the new timeout
-        mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig);
-
-        for (NetworkMetricMonitor monitor : mMetricMonitors) {
-            monitor.setCarrierConfig(carrierConfig);
-        }
-    }
-
-    /** Update the inbound IpSecTransform applied to the network */
-    public void setInboundTransform(@NonNull IpSecTransform transform) {
-        if (!mIsSelected) {
-            logWtf("setInboundTransform on an unselected evaluator");
-            return;
-        }
-
-        for (NetworkMetricMonitor monitor : mMetricMonitors) {
-            monitor.setInboundTransform(transform);
-        }
-    }
-
-    /** Close the evaluator and stop all the underlying network metric monitors */
-    public void close() {
-        mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
-
-        for (NetworkMetricMonitor monitor : mMetricMonitors) {
-            monitor.close();
-        }
-    }
-
-    /** Return whether this network evaluator is valid */
-    public boolean isValid() {
-        return mNetworkRecordBuilder.isValid();
-    }
-
-    /** Return the network */
-    public Network getNetwork() {
-        return mNetworkRecordBuilder.getNetwork();
-    }
-
-    /** Return the network record */
-    public UnderlyingNetworkRecord getNetworkRecord() {
-        return mNetworkRecordBuilder.build();
-    }
-
-    /** Return the priority class for network selection */
-    public int getPriorityClass() {
-        return mPriorityClass;
-    }
-
-    /** Return whether the network is being penalized */
-    public boolean isPenalized() {
-        return mIsPenalized;
-    }
-
-    /** Dump the information of this instance */
-    public void dump(IndentingPrintWriter pw) {
-        pw.println("UnderlyingNetworkEvaluator:");
-        pw.increaseIndent();
-
-        if (mNetworkRecordBuilder.isValid()) {
-            getNetworkRecord().dump(pw);
-        } else {
-            pw.println(
-                    "UnderlyingNetworkRecord incomplete: mNetwork: "
-                            + mNetworkRecordBuilder.getNetwork());
-        }
-
-        pw.println("mIsSelected: " + mIsSelected);
-        pw.println("mPriorityClass: " + mPriorityClass);
-        pw.println("mIsPenalized: " + mIsPenalized);
-
-        pw.decreaseIndent();
-    }
-
-    private String getLogPrefix() {
-        return "[Network " + mNetworkRecordBuilder.getNetwork() + "] ";
-    }
-
-    private void logInfo(String msg) {
-        Slog.i(TAG, getLogPrefix() + msg);
-        LOCAL_LOG.log("[INFO ] " + TAG + getLogPrefix() + msg);
-    }
-
-    private void logWtf(String msg) {
-        Slog.wtf(TAG, getLogPrefix() + msg);
-        LOCAL_LOG.log("[WTF ] " + TAG + getLogPrefix() + msg);
-    }
-}
diff --git a/services/core/java/com/android/server/vcn/util/LogUtils.java b/services/core/java/com/android/server/vcn/util/LogUtils.java
deleted file mode 100644
index 93728ce..0000000
--- a/services/core/java/com/android/server/vcn/util/LogUtils.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.vcn.util;
-
-import android.annotation.Nullable;
-import android.os.ParcelUuid;
-
-import com.android.internal.util.HexDump;
-
-/** @hide */
-public class LogUtils {
-    /**
-     * Returns the hash of the subscription group in hexadecimal format.
-     *
-     * @return the hexadecimal encoded string if uuid was non-null, else {@code null}
-     */
-    @Nullable
-    public static String getHashedSubscriptionGroup(@Nullable ParcelUuid uuid) {
-        if (uuid == null) {
-            return null;
-        }
-
-        return HexDump.toHexString(uuid.hashCode());
-    }
-}
diff --git a/services/core/java/com/android/server/vcn/util/MtuUtils.java b/services/core/java/com/android/server/vcn/util/MtuUtils.java
deleted file mode 100644
index 356c71f..0000000
--- a/services/core/java/com/android/server/vcn/util/MtuUtils.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.vcn.util;
-
-import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_3DES;
-import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
-import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CTR;
-import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
-import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
-import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
-import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_CHACHA20_POLY1305;
-import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_CMAC_96;
-import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96;
-import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96;
-import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
-import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192;
-import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256;
-import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_NONE;
-
-import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
-
-import static java.lang.Math.max;
-import static java.util.Collections.unmodifiableMap;
-
-import android.annotation.NonNull;
-import android.net.ipsec.ike.ChildSaProposal;
-import android.util.ArrayMap;
-import android.util.Pair;
-import android.util.Slog;
-
-import java.util.List;
-import java.util.Map;
-
-/** @hide */
-public class MtuUtils {
-    private static final String TAG = MtuUtils.class.getSimpleName();
-    /**
-     * Max ESP overhead possible
-     *
-     * <p>60 (Outer IPv4 + options) + 8 (UDP encap) + 4 (SPI) + 4 (Seq) + 2 (Pad Length + Next
-     * Header). Note: Payload data, Pad Length and Next Header will need to be padded to be multiple
-     * of the block size of a cipher, and at the same time be aligned on a 4-byte boundary.
-     */
-    private static final int GENERIC_ESP_OVERHEAD_MAX_V4 = 78;
-
-    /**
-     * Max ESP overhead possible
-     *
-     * <p>40 (Outer IPv6) + 4 (SPI) + 4 (Seq) + 2 (Pad Length + Next Header). Note: Payload data,
-     * Pad Length and Next Header will need to be padded to be multiple of the block size of a
-     * cipher, and at the same time be aligned on a 4-byte boundary.
-     */
-    private static final int GENERIC_ESP_OVERHEAD_MAX_V6 = 50;
-
-    /** Maximum overheads of authentication algorithms, keyed on IANA-defined constants */
-    private static final Map<Integer, Integer> AUTH_ALGORITHM_OVERHEAD;
-
-    static {
-        final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(INTEGRITY_ALGORITHM_NONE, 0);
-        map.put(INTEGRITY_ALGORITHM_HMAC_SHA1_96, 12);
-        map.put(INTEGRITY_ALGORITHM_AES_XCBC_96, 12);
-        map.put(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128, 32);
-        map.put(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192, 48);
-        map.put(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256, 64);
-        map.put(INTEGRITY_ALGORITHM_AES_CMAC_96, 12);
-
-        AUTH_ALGORITHM_OVERHEAD = unmodifiableMap(map);
-    }
-
-    /** Maximum overheads of encryption algorithms, keyed on IANA-defined constants */
-    private static final Map<Integer, Integer> CRYPT_ALGORITHM_OVERHEAD;
-
-    static {
-        final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(ENCRYPTION_ALGORITHM_3DES, 15); // 8 (IV) + 7 (Max pad)
-        map.put(ENCRYPTION_ALGORITHM_AES_CBC, 31); // 16 (IV) + 15 (Max pad)
-        map.put(ENCRYPTION_ALGORITHM_AES_CTR, 11); // 8 (IV) + 3 (Max pad)
-
-        CRYPT_ALGORITHM_OVERHEAD = unmodifiableMap(map);
-    }
-
-    /** Maximum overheads of combined mode algorithms, keyed on IANA-defined constants */
-    private static final Map<Integer, Integer> AUTHCRYPT_ALGORITHM_OVERHEAD;
-
-    static {
-        final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(ENCRYPTION_ALGORITHM_AES_GCM_8, 19); // 8 (IV) + 3 (Max pad) + 8 (ICV)
-        map.put(ENCRYPTION_ALGORITHM_AES_GCM_12, 23); // 8 (IV) + 3 (Max pad) + 12 (ICV)
-        map.put(ENCRYPTION_ALGORITHM_AES_GCM_16, 27); // 8 (IV) + 3 (Max pad) + 16 (ICV)
-        map.put(ENCRYPTION_ALGORITHM_CHACHA20_POLY1305, 27); // 8 (IV) + 3 (Max pad) + 16 (ICV)
-
-        AUTHCRYPT_ALGORITHM_OVERHEAD = unmodifiableMap(map);
-    }
-
-    /**
-     * Calculates the MTU of the inner interface based on the parameters provided
-     *
-     * <p>The MTU of the inner interface will be the minimum of the following:
-     *
-     * <ul>
-     *   <li>The MTU of the outer interface, minus the greatest ESP overhead (based on proposed
-     *       algorithms).
-     *   <li>The maximum MTU as provided in the arguments.
-     * </ul>
-     */
-    public static int getMtu(
-            @NonNull List<ChildSaProposal> childProposals,
-            int maxMtu,
-            int underlyingMtu,
-            boolean isIpv4) {
-        if (underlyingMtu <= 0) {
-            return IPV6_MIN_MTU;
-        }
-
-        int maxAuthOverhead = 0;
-        int maxCryptOverhead = 0;
-        int maxAuthCryptOverhead = 0;
-
-        for (ChildSaProposal proposal : childProposals) {
-            for (Pair<Integer, Integer> encryptionAlgoPair : proposal.getEncryptionAlgorithms()) {
-                final int algo = encryptionAlgoPair.first;
-
-                if (AUTHCRYPT_ALGORITHM_OVERHEAD.containsKey(algo)) {
-                    maxAuthCryptOverhead =
-                            max(maxAuthCryptOverhead, AUTHCRYPT_ALGORITHM_OVERHEAD.get(algo));
-                    continue;
-                } else if (CRYPT_ALGORITHM_OVERHEAD.containsKey(algo)) {
-                    maxCryptOverhead = max(maxCryptOverhead, CRYPT_ALGORITHM_OVERHEAD.get(algo));
-                    continue;
-                }
-
-                Slog.wtf(TAG, "Unknown encryption algorithm requested: " + algo);
-                return IPV6_MIN_MTU;
-            }
-
-            for (int algo : proposal.getIntegrityAlgorithms()) {
-                if (AUTH_ALGORITHM_OVERHEAD.containsKey(algo)) {
-                    maxAuthOverhead = max(maxAuthOverhead, AUTH_ALGORITHM_OVERHEAD.get(algo));
-                    continue;
-                }
-
-                Slog.wtf(TAG, "Unknown integrity algorithm requested: " + algo);
-                return IPV6_MIN_MTU;
-            }
-        }
-
-        final int genericEspOverheadMax =
-                isIpv4 ? GENERIC_ESP_OVERHEAD_MAX_V4 : GENERIC_ESP_OVERHEAD_MAX_V6;
-
-        // Return minimum of maxMtu, and the adjusted MTUs based on algorithms.
-        final int combinedModeMtu = underlyingMtu - maxAuthCryptOverhead - genericEspOverheadMax;
-        final int normalModeMtu =
-                underlyingMtu - maxCryptOverhead - maxAuthOverhead - genericEspOverheadMax;
-        return Math.min(Math.min(maxMtu, combinedModeMtu), normalModeMtu);
-    }
-}
diff --git a/services/core/java/com/android/server/vcn/util/OneWayBoolean.java b/services/core/java/com/android/server/vcn/util/OneWayBoolean.java
deleted file mode 100644
index e79bb2d..0000000
--- a/services/core/java/com/android/server/vcn/util/OneWayBoolean.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.vcn.util;
-
-/**
- * OneWayBoolean is an abstraction for a boolean that MUST only ever be flipped from false to true
- *
- * <p>This class allows the providing of a guarantee that a flag will never be flipped back after
- * being set.
- *
- * @hide
- */
-public class OneWayBoolean {
-    private boolean mValue = false;
-
-    /** Get boolean value. */
-    public boolean getValue() {
-        return mValue;
-    }
-
-    /** Sets the value to true. */
-    public void setTrue() {
-        mValue = true;
-    }
-}
diff --git a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
deleted file mode 100644
index d6761a2..0000000
--- a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
+++ /dev/null
@@ -1,582 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.vcn.util;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.ParcelUuid;
-import android.os.PersistableBundle;
-
-import com.android.internal.util.HexDump;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
-import java.util.TreeSet;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-/** @hide */
-public class PersistableBundleUtils {
-    private static final String LIST_KEY_FORMAT = "LIST_ITEM_%d";
-    private static final String COLLECTION_SIZE_KEY = "COLLECTION_LENGTH";
-    private static final String MAP_KEY_FORMAT = "MAP_KEY_%d";
-    private static final String MAP_VALUE_FORMAT = "MAP_VALUE_%d";
-
-    private static final String PARCEL_UUID_KEY = "PARCEL_UUID";
-    private static final String BYTE_ARRAY_KEY = "BYTE_ARRAY_KEY";
-    private static final String INTEGER_KEY = "INTEGER_KEY";
-    private static final String STRING_KEY = "STRING_KEY";
-
-    /**
-     * Functional interface to convert an object of the specified type to a PersistableBundle.
-     *
-     * @param <T> the type of the source object
-     */
-    public interface Serializer<T> {
-        /**
-         * Converts this object to a PersistableBundle.
-         *
-         * @return the PersistableBundle representation of this object
-         */
-        PersistableBundle toPersistableBundle(T obj);
-    }
-
-    /**
-     * Functional interface used to create an object of the specified type from a PersistableBundle.
-     *
-     * @param <T> the type of the resultant object
-     */
-    public interface Deserializer<T> {
-        /**
-         * Creates an instance of specified type from a PersistableBundle representation.
-         *
-         * @param in the PersistableBundle representation
-         * @return an instance of the specified type
-         */
-        T fromPersistableBundle(PersistableBundle in);
-    }
-
-    /** Serializer to convert an integer to a PersistableBundle. */
-    public static final Serializer<Integer> INTEGER_SERIALIZER =
-            (i) -> {
-                final PersistableBundle result = new PersistableBundle();
-                result.putInt(INTEGER_KEY, i);
-                return result;
-            };
-
-    /** Deserializer to convert a PersistableBundle to an integer. */
-    public static final Deserializer<Integer> INTEGER_DESERIALIZER =
-            (bundle) -> {
-                Objects.requireNonNull(bundle, "PersistableBundle is null");
-                return bundle.getInt(INTEGER_KEY);
-            };
-
-    /** Serializer to convert s String to a PersistableBundle. */
-    public static final Serializer<String> STRING_SERIALIZER =
-            (i) -> {
-                final PersistableBundle result = new PersistableBundle();
-                result.putString(STRING_KEY, i);
-                return result;
-            };
-
-    /** Deserializer to convert a PersistableBundle to a String. */
-    public static final Deserializer<String> STRING_DESERIALIZER =
-            (bundle) -> {
-                Objects.requireNonNull(bundle, "PersistableBundle is null");
-                return bundle.getString(STRING_KEY);
-            };
-
-    /**
-     * Converts a ParcelUuid to a PersistableBundle.
-     *
-     * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
-     * PersistableBundle object.
-     *
-     * @param uuid a ParcelUuid instance to persist
-     * @return the PersistableBundle instance
-     */
-    public static PersistableBundle fromParcelUuid(ParcelUuid uuid) {
-        final PersistableBundle result = new PersistableBundle();
-
-        result.putString(PARCEL_UUID_KEY, uuid.toString());
-
-        return result;
-    }
-
-    /**
-     * Converts from a PersistableBundle to a ParcelUuid.
-     *
-     * @param bundle the PersistableBundle containing the ParcelUuid
-     * @return the ParcelUuid instance
-     */
-    public static ParcelUuid toParcelUuid(PersistableBundle bundle) {
-        return ParcelUuid.fromString(bundle.getString(PARCEL_UUID_KEY));
-    }
-
-    /**
-     * Converts from a list of Persistable objects to a single PersistableBundle.
-     *
-     * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
-     * PersistableBundle object.
-     *
-     * @param <T> the type of the objects to convert to the PersistableBundle
-     * @param in the list of objects to be serialized into a PersistableBundle
-     * @param serializer an implementation of the {@link Serializer} functional interface that
-     *     converts an object of type T to a PersistableBundle
-     */
-    @NonNull
-    public static <T> PersistableBundle fromList(
-            @NonNull List<T> in, @NonNull Serializer<T> serializer) {
-        final PersistableBundle result = new PersistableBundle();
-
-        result.putInt(COLLECTION_SIZE_KEY, in.size());
-        for (int i = 0; i < in.size(); i++) {
-            final String key = String.format(LIST_KEY_FORMAT, i);
-            result.putPersistableBundle(key, serializer.toPersistableBundle(in.get(i)));
-        }
-        return result;
-    }
-
-    /**
-     * Converts from a PersistableBundle to a list of objects.
-     *
-     * @param <T> the type of the objects to convert from a PersistableBundle
-     * @param in the PersistableBundle containing the persisted list
-     * @param deserializer an implementation of the {@link Deserializer} functional interface that
-     *     builds the relevant type of objects.
-     */
-    @NonNull
-    public static <T> List<T> toList(
-            @NonNull PersistableBundle in, @NonNull Deserializer<T> deserializer) {
-        final int listLength = in.getInt(COLLECTION_SIZE_KEY);
-        final ArrayList<T> result = new ArrayList<>(listLength);
-
-        for (int i = 0; i < listLength; i++) {
-            final String key = String.format(LIST_KEY_FORMAT, i);
-            final PersistableBundle item = in.getPersistableBundle(key);
-
-            result.add(deserializer.fromPersistableBundle(item));
-        }
-        return result;
-    }
-
-    // TODO: b/170513329 Delete #fromByteArray and #toByteArray once BaseBundle#putByteArray and
-    // BaseBundle#getByteArray are exposed.
-
-    /**
-     * Converts a byte array to a PersistableBundle.
-     *
-     * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
-     * PersistableBundle object.
-     *
-     * @param array a byte array instance to persist
-     * @return the PersistableBundle instance
-     */
-    public static PersistableBundle fromByteArray(byte[] array) {
-        final PersistableBundle result = new PersistableBundle();
-
-        result.putString(BYTE_ARRAY_KEY, HexDump.toHexString(array));
-
-        return result;
-    }
-
-    /**
-     * Converts from a PersistableBundle to a byte array.
-     *
-     * @param bundle the PersistableBundle containing the byte array
-     * @return the byte array instance
-     */
-    public static byte[] toByteArray(PersistableBundle bundle) {
-        Objects.requireNonNull(bundle, "PersistableBundle is null");
-
-        String hex = bundle.getString(BYTE_ARRAY_KEY);
-        if (hex == null || hex.length() % 2 != 0) {
-            throw new IllegalArgumentException("PersistableBundle contains invalid byte array");
-        }
-
-        return HexDump.hexStringToByteArray(hex);
-    }
-
-    /**
-     * Converts from a Map of Persistable objects to a single PersistableBundle.
-     *
-     * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
-     * PersistableBundle object.
-     *
-     * @param <K> the type of the map-key to convert to the PersistableBundle
-     * @param <V> the type of the map-value to convert to the PersistableBundle
-     * @param in the Map of objects implementing the {@link Persistable} interface
-     * @param keySerializer an implementation of the {@link Serializer} functional interface that
-     *     converts a map-key of type T to a PersistableBundle
-     * @param valueSerializer an implementation of the {@link Serializer} functional interface that
-     *     converts a map-value of type E to a PersistableBundle
-     */
-    @NonNull
-    public static <K, V> PersistableBundle fromMap(
-            @NonNull Map<K, V> in,
-            @NonNull Serializer<K> keySerializer,
-            @NonNull Serializer<V> valueSerializer) {
-        final PersistableBundle result = new PersistableBundle();
-
-        result.putInt(COLLECTION_SIZE_KEY, in.size());
-        int i = 0;
-        for (Entry<K, V> entry : in.entrySet()) {
-            final String keyKey = String.format(MAP_KEY_FORMAT, i);
-            final String valueKey = String.format(MAP_VALUE_FORMAT, i);
-            result.putPersistableBundle(keyKey, keySerializer.toPersistableBundle(entry.getKey()));
-            result.putPersistableBundle(
-                    valueKey, valueSerializer.toPersistableBundle(entry.getValue()));
-
-            i++;
-        }
-
-        return result;
-    }
-
-    /**
-     * Converts from a PersistableBundle to a Map of objects.
-     *
-     * <p>In an attempt to preserve ordering, the returned map will be a LinkedHashMap. However, the
-     * guarantees on the ordering can only ever be as strong as the map that was serialized in
-     * {@link fromMap()}. If the initial map that was serialized had no ordering guarantees, the
-     * deserialized map similarly may be of a non-deterministic order.
-     *
-     * @param <K> the type of the map-key to convert from a PersistableBundle
-     * @param <V> the type of the map-value to convert from a PersistableBundle
-     * @param in the PersistableBundle containing the persisted Map
-     * @param keyDeserializer an implementation of the {@link Deserializer} functional interface
-     *     that builds the relevant type of map-key.
-     * @param valueDeserializer an implementation of the {@link Deserializer} functional interface
-     *     that builds the relevant type of map-value.
-     * @return An instance of the parsed map as a LinkedHashMap (in an attempt to preserve
-     *     ordering).
-     */
-    @NonNull
-    public static <K, V> LinkedHashMap<K, V> toMap(
-            @NonNull PersistableBundle in,
-            @NonNull Deserializer<K> keyDeserializer,
-            @NonNull Deserializer<V> valueDeserializer) {
-        final int mapSize = in.getInt(COLLECTION_SIZE_KEY);
-        final LinkedHashMap<K, V> result = new LinkedHashMap<>(mapSize);
-
-        for (int i = 0; i < mapSize; i++) {
-            final String keyKey = String.format(MAP_KEY_FORMAT, i);
-            final String valueKey = String.format(MAP_VALUE_FORMAT, i);
-            final PersistableBundle keyBundle = in.getPersistableBundle(keyKey);
-            final PersistableBundle valueBundle = in.getPersistableBundle(valueKey);
-
-            final K key = keyDeserializer.fromPersistableBundle(keyBundle);
-            final V value = valueDeserializer.fromPersistableBundle(valueBundle);
-            result.put(key, value);
-        }
-        return result;
-    }
-
-    /**
-     * Converts a PersistableBundle into a disk-stable byte array format
-     *
-     * @param bundle the PersistableBundle to be converted to a disk-stable format
-     * @return the byte array representation of the PersistableBundle
-     */
-    @Nullable
-    public static byte[] toDiskStableBytes(@NonNull PersistableBundle bundle) throws IOException {
-        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        bundle.writeToStream(outputStream);
-        return outputStream.toByteArray();
-    }
-
-    /**
-     * Converts from a disk-stable byte array format to a PersistableBundle
-     *
-     * @param bytes the disk-stable byte array
-     * @return the PersistableBundle parsed from this byte array.
-     */
-    public static PersistableBundle fromDiskStableBytes(@NonNull byte[] bytes) throws IOException {
-        final ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
-        return PersistableBundle.readFromStream(inputStream);
-    }
-
-    /**
-     * Ensures safe reading and writing of {@link PersistableBundle}s to and from disk.
-     *
-     * <p>This class will enforce exclusion between reads and writes using the standard semantics of
-     * a ReadWriteLock. Specifically, concurrent readers ARE allowed, but reads/writes from/to the
-     * file are mutually exclusive. In other words, for an unbounded number n, the acceptable states
-     * are n readers, OR 1 writer (but not both).
-     */
-    public static class LockingReadWriteHelper {
-        private final ReadWriteLock mDiskLock = new ReentrantReadWriteLock();
-        private final String mPath;
-
-        public LockingReadWriteHelper(@NonNull String path) {
-            mPath = Objects.requireNonNull(path, "fileName was null");
-        }
-
-        /**
-         * Reads the {@link PersistableBundle} from the disk.
-         *
-         * @return the PersistableBundle, if the file existed, or null otherwise
-         */
-        @Nullable
-        public PersistableBundle readFromDisk() throws IOException {
-            try {
-                mDiskLock.readLock().lock();
-                final File file = new File(mPath);
-                if (!file.exists()) {
-                    return null;
-                }
-
-                try (FileInputStream fis = new FileInputStream(file)) {
-                    return PersistableBundle.readFromStream(fis);
-                }
-            } finally {
-                mDiskLock.readLock().unlock();
-            }
-        }
-
-        /**
-         * Writes a {@link PersistableBundle} to disk.
-         *
-         * @param bundle the {@link PersistableBundle} to write to disk
-         */
-        public void writeToDisk(@NonNull PersistableBundle bundle) throws IOException {
-            Objects.requireNonNull(bundle, "bundle was null");
-
-            try {
-                mDiskLock.writeLock().lock();
-                final File file = new File(mPath);
-                if (!file.exists()) {
-                    file.getParentFile().mkdirs();
-                }
-
-                try (FileOutputStream fos = new FileOutputStream(file)) {
-                    bundle.writeToStream(fos);
-                }
-            } finally {
-                mDiskLock.writeLock().unlock();
-            }
-        }
-    }
-
-    /**
-     * Returns a copy of the persistable bundle with only the specified keys
-     *
-     * <p>This allows for holding minimized copies for memory-saving purposes.
-     */
-    @NonNull
-    public static PersistableBundle minimizeBundle(
-            @NonNull PersistableBundle bundle, String... keys) {
-        final PersistableBundle minimized = new PersistableBundle();
-
-        if (bundle == null) {
-            return minimized;
-        }
-
-        for (String key : keys) {
-            if (bundle.containsKey(key)) {
-                final Object value = bundle.get(key);
-                if (value == null) {
-                    continue;
-                }
-
-                if (value instanceof Boolean) {
-                    minimized.putBoolean(key, (Boolean) value);
-                } else if (value instanceof boolean[]) {
-                    minimized.putBooleanArray(key, (boolean[]) value);
-                } else if (value instanceof Double) {
-                    minimized.putDouble(key, (Double) value);
-                } else if (value instanceof double[]) {
-                    minimized.putDoubleArray(key, (double[]) value);
-                } else if (value instanceof Integer) {
-                    minimized.putInt(key, (Integer) value);
-                } else if (value instanceof int[]) {
-                    minimized.putIntArray(key, (int[]) value);
-                } else if (value instanceof Long) {
-                    minimized.putLong(key, (Long) value);
-                } else if (value instanceof long[]) {
-                    minimized.putLongArray(key, (long[]) value);
-                } else if (value instanceof String) {
-                    minimized.putString(key, (String) value);
-                } else if (value instanceof String[]) {
-                    minimized.putStringArray(key, (String[]) value);
-                } else if (value instanceof PersistableBundle) {
-                    minimized.putPersistableBundle(key, (PersistableBundle) value);
-                } else {
-                    continue;
-                }
-            }
-        }
-
-        return minimized;
-    }
-
-    /** Builds a stable hashcode */
-    public static int getHashCode(@Nullable PersistableBundle bundle) {
-        if (bundle == null) {
-            return -1;
-        }
-
-        int iterativeHashcode = 0;
-        TreeSet<String> treeSet = new TreeSet<>(bundle.keySet());
-        for (String key : treeSet) {
-            Object val = bundle.get(key);
-            if (val instanceof PersistableBundle) {
-                iterativeHashcode =
-                        Objects.hash(iterativeHashcode, key, getHashCode((PersistableBundle) val));
-            } else {
-                iterativeHashcode = Objects.hash(iterativeHashcode, key, val);
-            }
-        }
-
-        return iterativeHashcode;
-    }
-
-    /** Checks for persistable bundle equality */
-    public static boolean isEqual(
-            @Nullable PersistableBundle left, @Nullable PersistableBundle right) {
-        // Check for pointer equality & null equality
-        if (Objects.equals(left, right)) {
-            return true;
-        }
-
-        // If only one of the two is null, but not the other, not equal by definition.
-        if (Objects.isNull(left) != Objects.isNull(right)) {
-            return false;
-        }
-
-        if (!left.keySet().equals(right.keySet())) {
-            return false;
-        }
-
-        for (String key : left.keySet()) {
-            Object leftVal = left.get(key);
-            Object rightVal = right.get(key);
-
-            // Check for equality
-            if (Objects.equals(leftVal, rightVal)) {
-                continue;
-            } else if (Objects.isNull(leftVal) != Objects.isNull(rightVal)) {
-                // If only one of the two is null, but not the other, not equal by definition.
-                return false;
-            } else if (!Objects.equals(leftVal.getClass(), rightVal.getClass())) {
-                // If classes are different, not equal by definition.
-                return false;
-            }
-            if (leftVal instanceof PersistableBundle) {
-                if (!isEqual((PersistableBundle) leftVal, (PersistableBundle) rightVal)) {
-                    return false;
-                }
-            } else if (leftVal.getClass().isArray()) {
-                if (leftVal instanceof boolean[]) {
-                    if (!Arrays.equals((boolean[]) leftVal, (boolean[]) rightVal)) {
-                        return false;
-                    }
-                } else if (leftVal instanceof double[]) {
-                    if (!Arrays.equals((double[]) leftVal, (double[]) rightVal)) {
-                        return false;
-                    }
-                } else if (leftVal instanceof int[]) {
-                    if (!Arrays.equals((int[]) leftVal, (int[]) rightVal)) {
-                        return false;
-                    }
-                } else if (leftVal instanceof long[]) {
-                    if (!Arrays.equals((long[]) leftVal, (long[]) rightVal)) {
-                        return false;
-                    }
-                } else if (!Arrays.equals((Object[]) leftVal, (Object[]) rightVal)) {
-                    return false;
-                }
-            } else {
-                if (!Objects.equals(leftVal, rightVal)) {
-                    return false;
-                }
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Wrapper class around PersistableBundles to allow equality comparisons
-     *
-     * <p>This class exposes the minimal getters to retrieve values.
-     */
-    public static class PersistableBundleWrapper {
-        @NonNull private final PersistableBundle mBundle;
-
-        public PersistableBundleWrapper(@NonNull PersistableBundle bundle) {
-            mBundle = Objects.requireNonNull(bundle, "Bundle was null");
-        }
-
-        /**
-         * Retrieves the integer associated with the provided key.
-         *
-         * @param key the string key to query
-         * @param defaultValue the value to return if key does not exist
-         * @return the int value, or the default
-         */
-        public int getInt(String key, int defaultValue) {
-            return mBundle.getInt(key, defaultValue);
-        }
-
-        /**
-         * Returns the value associated with the given key, or null if no mapping of the desired
-         * type exists for the given key or a null value is explicitly associated with the key.
-         *
-         * @param key a String, or null
-         * @param defaultValue the value to return if key does not exist
-         * @return an int[] value, or null
-         */
-        @Nullable
-        public int[] getIntArray(@Nullable String key, @Nullable int[] defaultValue) {
-            final int[] value = mBundle.getIntArray(key);
-            return value == null ? defaultValue : value;
-        }
-
-        @Override
-        public int hashCode() {
-            return getHashCode(mBundle);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (!(obj instanceof PersistableBundleWrapper)) {
-                return false;
-            }
-
-            final PersistableBundleWrapper other = (PersistableBundleWrapper) obj;
-
-            return isEqual(mBundle, other.mBundle);
-        }
-
-        @Override
-        public String toString() {
-            return mBundle.toString();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
index 9e75cf2..1b6ce9d 100644
--- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -86,6 +86,8 @@
     @GuardedBy("mLock")
     private Status mEndStatusRequest;
     @GuardedBy("mLock")
+    private boolean mEndedByVendor;
+    @GuardedBy("mLock")
     private long mStartTime; // for debugging
     @GuardedBy("mLock")
     private long mEndUptime;
@@ -117,16 +119,19 @@
 
     @Override
     public void finishSession() {
+        Slog.d(TAG, "Session finish requested, ending vibration session...");
         // Do not abort session in HAL, wait for ongoing vibration requests to complete.
         // This might take a while to end the session, but it can be aborted by cancelSession.
-        requestEndSession(Status.FINISHED, /* shouldAbort= */ false);
+        requestEndSession(Status.FINISHED, /* shouldAbort= */ false, /* isVendorRequest= */ true);
     }
 
     @Override
     public void cancelSession() {
+        Slog.d(TAG, "Session cancel requested, aborting vibration session...");
         // Always abort session in HAL while cancelling it.
         // This might be triggered after finishSession was already called.
-        requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true);
+        requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
+                /* isVendorRequest= */ true);
     }
 
     @Override
@@ -158,7 +163,7 @@
     public DebugInfo getDebugInfo() {
         synchronized (mLock) {
             return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime,
-                    mEndUptime, mEndTime, mVibrations);
+                    mEndUptime, mEndTime, mEndedByVendor, mVibrations);
         }
     }
 
@@ -172,13 +177,15 @@
     @Override
     public void onCancel() {
         Slog.d(TAG, "Cancellation signal received, cancelling vibration session...");
-        requestEnd(Status.CANCELLED_BY_USER, /* endedBy= */ null, /* immediate= */ false);
+        requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
+                /* isVendorRequest= */ true);
     }
 
     @Override
     public void binderDied() {
         Slog.d(TAG, "Binder died, cancelling vibration session...");
-        requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false);
+        requestEndSession(Status.CANCELLED_BINDER_DIED, /* shouldAbort= */ true,
+                /* isVendorRequest= */ false);
     }
 
     @Override
@@ -207,7 +214,7 @@
         // All requests to end a session should abort it to stop ongoing vibrations, even if
         // immediate flag is false. Only the #finishSession API will not abort and wait for
         // session vibrations to complete, which might take a long time.
-        requestEndSession(status, /* shouldAbort= */ true);
+        requestEndSession(status, /* shouldAbort= */ true, /* isVendorRequest= */ false);
     }
 
     @Override
@@ -223,12 +230,14 @@
     @Override
     public void notifySessionCallback() {
         synchronized (mLock) {
+            Slog.d(TAG, "Session callback received, ending vibration session...");
             // If end was not requested then the HAL has cancelled the session.
-            maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON);
+            maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON,
+                    /* isVendorRequest= */ false);
             maybeSetStatusToRequestedLocked();
             clearVibrationConductor();
+            mHandler.post(() -> mManagerHooks.onSessionReleased(mSessionId));
         }
-        mHandler.post(() -> mManagerHooks.onSessionReleased(mSessionId));
     }
 
     @Override
@@ -335,10 +344,10 @@
         }
     }
 
-    private void requestEndSession(Status status, boolean shouldAbort) {
+    private void requestEndSession(Status status, boolean shouldAbort, boolean isVendorRequest) {
         boolean shouldTriggerSessionHook = false;
         synchronized (mLock) {
-            maybeSetEndRequestLocked(status);
+            maybeSetEndRequestLocked(status, isVendorRequest);
             if (isStarted()) {
                 // Always trigger session hook after it has started, in case new request aborts an
                 // already finishing session. Wait for HAL callback before actually ending here.
@@ -354,12 +363,13 @@
     }
 
     @GuardedBy("mLock")
-    private void maybeSetEndRequestLocked(Status status) {
+    private void maybeSetEndRequestLocked(Status status, boolean isVendorRequest) {
         if (mEndStatusRequest != null) {
             // End already requested, keep first requested status and time.
             return;
         }
         mEndStatusRequest = status;
+        mEndedByVendor = isVendorRequest;
         mEndTime = System.currentTimeMillis();
         mEndUptime = SystemClock.uptimeMillis();
         if (mConductor != null) {
@@ -442,15 +452,18 @@
         private final long mStartTime;
         private final long mEndTime;
         private final long mDurationMs;
+        private final boolean mEndedByVendor;
 
         DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime,
-                long startTime, long endUptime, long endTime, List<DebugInfo> vibrations) {
+                long startTime, long endUptime, long endTime, boolean endedByVendor,
+                List<DebugInfo> vibrations) {
             mStatus = status;
             mCallerInfo = callerInfo;
             mCreateUptime = createUptime;
             mCreateTime = createTime;
             mStartTime = startTime;
             mEndTime = endTime;
+            mEndedByVendor = endedByVendor;
             mDurationMs = endUptime > 0 ? endUptime - createUptime : -1;
             mVibrations = vibrations == null ? new ArrayList<>() : new ArrayList<>(vibrations);
         }
@@ -478,6 +491,15 @@
 
         @Override
         public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+            if (mStartTime > 0) {
+                // Only log sessions that have started.
+                statsLogger.logVibrationVendorSessionStarted(mCallerInfo.uid);
+                statsLogger.logVibrationVendorSessionVibrations(mCallerInfo.uid,
+                        mVibrations.size());
+                if (!mEndedByVendor) {
+                    statsLogger.logVibrationVendorSessionInterrupted(mCallerInfo.uid);
+                }
+            }
             for (DebugInfo vibration : mVibrations) {
                 vibration.logMetrics(statsLogger);
             }
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 27f92b2..2bf4498 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -25,6 +25,7 @@
 import android.os.IBinder;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.RampSegment;
@@ -211,6 +212,11 @@
         public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
             statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
             statsLogger.writeVibrationReportedAsync(mStatsInfo);
+            if (Flags.vendorVibrationEffects()) {
+                // Log effect as it was originally requested.
+                statsLogger.logVibrationCountAndSizeIfVendorEffect(mCallerInfo.uid,
+                        mOriginalEffect != null ? mOriginalEffect : mPlayedEffect);
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
index e9c3894..08da43d 100644
--- a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
@@ -16,8 +16,12 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
 import android.os.Handler;
+import android.os.Parcel;
 import android.os.SystemClock;
+import android.os.VibrationEffect;
 import android.util.Slog;
 import android.view.HapticFeedbackConstants;
 
@@ -58,6 +62,16 @@
             "vibrator.value_vibration_adaptive_haptic_scale",
             new Histogram.UniformOptions(20, 0, 2));
 
+    // Sizes in [1KB, ~4.5MB) defined by scaled buckets.
+    private static final Histogram sVibrationVendorEffectSizeHistogram = new Histogram(
+            "vibrator.value_vibration_vendor_effect_size",
+            new Histogram.ScaledRangeOptions(25, 0, 1, 1.4f));
+
+    // Session vibration count in [0, ~840) defined by scaled buckets.
+    private static final Histogram sVibrationVendorSessionVibrationsHistogram = new Histogram(
+            "vibrator.value_vibration_vendor_session_vibrations",
+            new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f));
+
     private final Object mLock = new Object();
     private final Handler mHandler;
     private final long mVibrationReportedLogIntervalMillis;
@@ -201,4 +215,88 @@
             Counter.logIncrementWithUid("vibrator.value_perform_haptic_feedback_keyboard", uid);
         }
     }
+
+    /** Logs when a vendor vibration session successfully started. */
+    public void logVibrationVendorSessionStarted(int uid) {
+        Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_started", uid);
+    }
+
+    /**
+     * Logs when a vendor vibration session is interrupted by the platform.
+     *
+     * <p>A vendor session is interrupted if it has successfully started and its end was not
+     * requested by the vendor. This could be the vibrator service interrupting an ongoing session,
+     * the vibrator HAL triggering the session completed callback early.
+     */
+    public void logVibrationVendorSessionInterrupted(int uid) {
+        Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_interrupted", uid);
+    }
+
+    /** Logs the number of vibrations requested for a single vendor vibration session. */
+    public void logVibrationVendorSessionVibrations(int uid, int vibrationCount) {
+        sVibrationVendorSessionVibrationsHistogram.logSampleWithUid(uid, vibrationCount);
+    }
+
+    /**
+     * Logs if given vibration contains at least one {@link VibrationEffect.VendorEffect}.
+     *
+     * <p>Each {@link VibrationEffect.VendorEffect} will also log the parcel data size for the
+     * {@link VibrationEffect.VendorEffect#getVendorData()} it holds.
+     */
+    public void logVibrationCountAndSizeIfVendorEffect(int uid,
+            @Nullable CombinedVibration vibration) {
+        if (vibration == null) {
+            return;
+        }
+        boolean hasVendorEffects = logVibrationSizeOfVendorEffects(uid, vibration);
+        if (hasVendorEffects) {
+            // Increment CombinedVibration with one or more vendor effects only once.
+            Counter.logIncrementWithUid("vibrator.value_vibration_vendor_effect_requests", uid);
+        }
+    }
+
+    private static boolean logVibrationSizeOfVendorEffects(int uid, CombinedVibration vibration) {
+        if (vibration instanceof CombinedVibration.Mono mono) {
+            if (mono.getEffect() instanceof VibrationEffect.VendorEffect effect) {
+                logVibrationVendorEffectSize(uid, effect);
+                return true;
+            }
+            return false;
+        }
+        if (vibration instanceof CombinedVibration.Stereo stereo) {
+            boolean hasVendorEffects = false;
+            for (int i = 0; i < stereo.getEffects().size(); i++) {
+                if (stereo.getEffects().valueAt(i) instanceof VibrationEffect.VendorEffect effect) {
+                    logVibrationVendorEffectSize(uid, effect);
+                    hasVendorEffects = true;
+                }
+            }
+            return hasVendorEffects;
+        }
+        if (vibration instanceof CombinedVibration.Sequential sequential) {
+            boolean hasVendorEffects = false;
+            for (int i = 0; i < sequential.getEffects().size(); i++) {
+                hasVendorEffects |= logVibrationSizeOfVendorEffects(uid,
+                        sequential.getEffects().get(i));
+            }
+            return hasVendorEffects;
+        }
+        // Unknown combined vibration, skip metrics.
+        return false;
+    }
+
+    private static void logVibrationVendorEffectSize(int uid, VibrationEffect.VendorEffect effect) {
+        int dataSize;
+        Parcel vendorData = Parcel.obtain();
+        try {
+            // Measure data size as it'll be sent to the HAL via binder, not the serialization size.
+            // PersistableBundle creates an XML representation for the data in writeToStream, so it
+            // might be larger than the actual data that is transferred between processes.
+            effect.getVendorData().writeToParcel(vendorData, /* flags= */ 0);
+            dataSize = vendorData.dataSize();
+        } finally {
+            vendorData.recycle();
+        }
+        sVibrationVendorEffectSizeHistogram.logSampleWithUid(uid, dataSize);
+    }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index cc163db..ae726c1 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1197,7 +1197,7 @@
                 new VendorVibrationSession.DebugInfoImpl(status, callerInfo,
                         SystemClock.uptimeMillis(), System.currentTimeMillis(),
                         /* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0,
-                        /* vibrations= */ null));
+                        /* endedByVendor= */ false, /* vibrations= */ null));
     }
 
     private void logAndRecordVibration(DebugInfo info) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index d019516..415896b 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -50,6 +50,7 @@
 import static com.android.window.flags.Flags.offloadColorExtraction;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
@@ -1513,11 +1514,15 @@
             }
             if (wallpaper.getComponent() != null
                     && isPackageModified(wallpaper.getComponent().getPackageName())) {
+                ServiceInfo serviceInfo = null;
                 try {
-                    mContext.getPackageManager().getServiceInfo(wallpaper.getComponent(),
-                            PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
-                } catch (NameNotFoundException e) {
+                    serviceInfo = mIPackageManager.getServiceInfo(
+                            wallpaper.getComponent(), PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, wallpaper.userId);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to call IPackageManager.getServiceInfo", e);
+                }
+                if (serviceInfo == null) {
                     Slog.e(TAG, "Wallpaper component gone, removing: "
                             + wallpaper.getComponent());
                     clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
@@ -2483,7 +2488,8 @@
     @Override
     public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which, int userId) {
         if (liveWallpaperContentHandling()) {
-            return getWallpaperInstance(which, userId, false).getInfo();
+            WallpaperInstance instance = getWallpaperInstance(which, userId, false);
+            return (instance != null) ? instance.getInfo() : null;
         }
 
         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
@@ -2505,7 +2511,7 @@
         return null;
     }
 
-    @NonNull
+    @Nullable
     @Override
     public WallpaperInstance getWallpaperInstance(@SetWallpaperFlags int which, int userId) {
         return getWallpaperInstance(which, userId, true);
@@ -2513,28 +2519,27 @@
 
     private WallpaperInstance getWallpaperInstance(@SetWallpaperFlags int which, int userId,
             boolean requireReadWallpaper) {
-        final WallpaperInstance defaultInstance = new WallpaperInstance(null,
-                new WallpaperDescription.Builder().build());
         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                 Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);
         synchronized (mLock) {
             WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
                     : mWallpaperMap.get(userId);
-            if (wallpaper == null
-                    || wallpaper.connection == null
-                    || wallpaper.connection.mInfo == null) {
-                return defaultInstance;
-            }
+            if (wallpaper == null || wallpaper.connection == null) return null;
 
             WallpaperInfo info = wallpaper.connection.mInfo;
-            boolean canQueryPackage = mPackageManagerInternal.canQueryPackage(
+            boolean canQueryPackage = (info == null) ||  mPackageManagerInternal.canQueryPackage(
                     Binder.getCallingUid(), info.getComponent().getPackageName());
             if (hasPermission(READ_WALLPAPER_INTERNAL)
                     || (canQueryPackage && !requireReadWallpaper)) {
-                return new WallpaperInstance(info, wallpaper.getDescription());
+                // TODO(b/380245309) Remove this when crops are part of the description.
+                WallpaperDescription description =
+                        wallpaper.getDescription().toBuilder().setCropHints(
+                                wallpaper.mCropHints).build();
+                return new WallpaperInstance(info, description);
+            } else {
+                return null;
             }
         }
-        return defaultInstance;
     }
 
     @Override
@@ -3177,7 +3182,7 @@
                     throw new IllegalArgumentException("Invalid crop rect supplied: " + crop);
                 }
                 int orientation = screenOrientations[i];
-                if (orientation == ORIENTATION_UNKNOWN && cropMap.size() > 1) {
+                if (orientation == ORIENTATION_UNKNOWN && crops.size() > 1) {
                     throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN"
                             + "screen orientation should only be used in a singleton map");
                 }
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 92ce251..1798661 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -34,6 +34,7 @@
 import android.util.Slog;
 import android.webkit.UserPackage;
 import android.webkit.WebViewFactory;
+import android.webkit.WebViewFactoryProvider;
 import android.webkit.WebViewProviderInfo;
 import android.webkit.WebViewZygote;
 
@@ -246,6 +247,11 @@
     }
 
     @Override
+    public boolean isCompatibleImplementationPackage(PackageInfo packageInfo) {
+        return WebViewFactoryProvider.isCompatibleImplementationPackage(packageInfo);
+    }
+
+    @Override
     public List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo configInfo) {
         return UserPackage.getPackageInfosAllUsers(mContext, configInfo.packageName, PACKAGE_FLAGS);
     }
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index 6710554..d9e1a3a 100644
--- a/services/core/java/com/android/server/webkit/SystemInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -47,6 +47,9 @@
     boolean systemIsDebuggable();
     PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
             throws NameNotFoundException;
+    /** Check if the given package is a compatible WebView implementation for the OS. */
+    boolean isCompatibleImplementationPackage(PackageInfo packageInfo);
+
     /**
      * Get the PackageInfos of all users for the package represented by {@param configInfo}.
      * @return an array of UserPackages for a certain package, each UserPackage being belonging to a
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index a5a02cd..9e8dc26 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -27,6 +27,7 @@
 import android.util.Slog;
 import android.webkit.UserPackage;
 import android.webkit.WebViewFactory;
+import android.webkit.WebViewFactoryProvider;
 import android.webkit.WebViewProviderInfo;
 import android.webkit.WebViewProviderResponse;
 
@@ -79,7 +80,7 @@
     private static final long NS_PER_MS = 1000000;
 
     private static final int VALIDITY_OK = 0;
-    private static final int VALIDITY_INCORRECT_SDK_VERSION = 1;
+    private static final int VALIDITY_OS_INCOMPATIBLE = 1;
     private static final int VALIDITY_INCORRECT_VERSION_CODE = 2;
     private static final int VALIDITY_INCORRECT_SIGNATURE = 3;
     private static final int VALIDITY_NO_LIBRARY_FLAG = 4;
@@ -587,9 +588,9 @@
     }
 
     private int validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo) {
-        // Ensure the provider targets this framework release (or a later one).
-        if (!UserPackage.hasCorrectTargetSdkVersion(packageInfo)) {
-            return VALIDITY_INCORRECT_SDK_VERSION;
+        // Ensure the provider is compatible with this framework release.
+        if (!mSystemInterface.isCompatibleImplementationPackage(packageInfo)) {
+            return VALIDITY_OS_INCOMPATIBLE;
         }
         if (!versionCodeGE(packageInfo.getLongVersionCode(), getMinimumVersionCode())
                 && !mSystemInterface.systemIsDebuggable()) {
@@ -712,7 +713,8 @@
             }
             pw.println(
                     TextUtils.formatSimple(
-                            "  Minimum targetSdkVersion: %d", UserPackage.MINIMUM_SUPPORTED_SDK));
+                            "  %s",
+                            WebViewFactoryProvider.describeCompatibleImplementationPackage()));
             pw.println(
                     TextUtils.formatSimple(
                             "  Minimum WebView version code: %d", mMinimumVersionCode));
@@ -786,8 +788,8 @@
 
     private static String getInvalidityReason(int invalidityReason) {
         switch (invalidityReason) {
-            case VALIDITY_INCORRECT_SDK_VERSION:
-                return "SDK version too low";
+            case VALIDITY_OS_INCOMPATIBLE:
+                return "Not compatible with this OS version";
             case VALIDITY_INCORRECT_VERSION_CODE:
                 return "Version code too low";
             case VALIDITY_INCORRECT_SIGNATURE:
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 4b7e74a..dd76917 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -25,7 +25,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
 
-import static com.android.internal.util.DumpUtils.dumpSparseArray;
 import static com.android.internal.util.DumpUtils.dumpSparseArrayValues;
 import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY;
 import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER;
@@ -49,23 +48,15 @@
 import static com.android.server.wm.WindowTracingLegacy.WINSCOPE_EXT;
 
 import android.accessibilityservice.AccessibilityTrace;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Application;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
-import android.graphics.BLASTBufferQueue;
-import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Matrix;
-import android.graphics.Paint;
 import android.graphics.Path;
-import android.graphics.PixelFormat;
 import android.graphics.Point;
-import android.graphics.PorterDuff.Mode;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
@@ -83,25 +74,16 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
-import android.util.TypedValue;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.MagnificationSpec;
 import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
-import android.view.SurfaceControl;
 import android.view.ViewConfiguration;
 import android.view.WindowInfo;
 import android.view.WindowManager;
 import android.view.WindowManager.TransitionFlags;
 import android.view.WindowManager.TransitionType;
-import android.view.WindowManagerPolicyConstants;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
 
-import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.TraceBuffer;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -111,7 +93,6 @@
 import com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal;
 import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
 import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
-import com.android.window.flags.Flags;
 
 import java.io.File;
 import java.io.IOException;
@@ -301,36 +282,6 @@
         }
     }
 
-    /** It is only used by unit test. */
-    @VisibleForTesting
-    Surface forceShowMagnifierSurface(int displayId) {
-        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
-        if (displayMagnifier != null) {
-            displayMagnifier.mMagnifiedViewport.mWindow.setAlpha(DisplayMagnifier.MagnifiedViewport
-                    .ViewportWindow.AnimationController.MAX_ALPHA);
-            return displayMagnifier.mMagnifiedViewport.mWindow.mSurface;
-        }
-        return null;
-    }
-
-    void onWindowLayersChanged(int displayId) {
-        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
-                | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
-            mAccessibilityTracing.logTrace(TAG + ".onWindowLayersChanged",
-                    FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
-                    "displayId=" + displayId);
-        }
-        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
-        if (displayMagnifier != null) {
-            displayMagnifier.onWindowLayersChanged();
-        }
-        final WindowsForAccessibilityObserver windowsForA11yObserver =
-                mWindowsForAccessibilityObserver.get(displayId);
-        if (windowsForA11yObserver != null) {
-            windowsForA11yObserver.scheduleComputeChangedWindows();
-        }
-    }
-
     void onDisplaySizeChanged(DisplayContent displayContent) {
 
         if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
@@ -563,9 +514,6 @@
     }
 
     void dump(PrintWriter pw, String prefix) {
-        dumpSparseArray(pw, prefix, mDisplayMagnifiers, "magnification display",
-                (index, key) -> pw.printf("%sDisplay #%d:", prefix + "  ", key),
-                dm -> dm.dump(pw, ""));
         dumpSparseArrayValues(pw, prefix, mWindowsForAccessibilityObserver,
                 "windows for accessibility observer");
         mAccessibilityWindowsPopulator.dump(pw, prefix);
@@ -623,7 +571,6 @@
 
         private final Context mDisplayContext;
         private final WindowManagerService mService;
-        private final MagnifiedViewport mMagnifiedViewport;
         private final Handler mHandler;
         private final DisplayContent mDisplayContent;
         private final Display mDisplay;
@@ -660,8 +607,6 @@
             mDisplay = display;
             mHandler = new MyHandler(mService.mH.getLooper());
             mUserContextChangedNotifier = new UserContextChangedNotifier(mHandler);
-            mMagnifiedViewport = Flags.alwaysDrawMagnificationFullscreenBorder()
-                    ? null : new MagnifiedViewport();
             mAccessibilityTracing =
                     AccessibilityController.getAccessibilityControllerInternal(mService);
             mLongAnimationDuration = mDisplayContext.getResources().getInteger(
@@ -703,10 +648,6 @@
             } else {
                 mMagnificationSpec.clear();
             }
-
-            if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
-                mMagnifiedViewport.setShowMagnifiedBorderIfNeeded();
-            }
         }
 
         void setFullscreenMagnificationActivated(boolean activated) {
@@ -715,10 +656,6 @@
                         FLAGS_MAGNIFICATION_CALLBACK, "activated=" + activated);
             }
             mIsFullscreenMagnificationActivated = activated;
-            if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
-                mMagnifiedViewport.setMagnifiedRegionBorderShown(activated, true);
-                mMagnifiedViewport.showMagnificationBoundsIfNeeded();
-            }
         }
 
         boolean isFullscreenMagnificationActivated() {
@@ -729,18 +666,6 @@
             return mIsFullscreenMagnificationActivated;
         }
 
-        void onWindowLayersChanged() {
-            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
-                mAccessibilityTracing.logTrace(
-                        LOG_TAG + ".onWindowLayersChanged", FLAGS_MAGNIFICATION_CALLBACK);
-            }
-            if (DEBUG_LAYERS) {
-                Slog.i(LOG_TAG, "Layers changed.");
-            }
-            recomputeBounds();
-            mService.scheduleAnimationLocked();
-        }
-
         void onDisplaySizeChanged(DisplayContent displayContent) {
             if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
                 mAccessibilityTracing.logTrace(LOG_TAG + ".onDisplaySizeChanged",
@@ -753,9 +678,6 @@
             }
 
             recomputeBounds();
-            if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
-                mMagnifiedViewport.onDisplaySizeChanged();
-            }
             mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED);
         }
 
@@ -926,10 +848,6 @@
             if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
                 mAccessibilityTracing.logTrace(LOG_TAG + ".destroy", FLAGS_MAGNIFICATION_CALLBACK);
             }
-
-            if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
-                mMagnifiedViewport.destroyWindow();
-            }
         }
 
         void recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded() {
@@ -939,10 +857,6 @@
                         FLAGS_MAGNIFICATION_CALLBACK);
             }
             recomputeBounds();
-
-            if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
-                mMagnifiedViewport.drawWindowIfNeeded();
-            }
         }
 
         void recomputeBounds() {
@@ -1050,16 +964,9 @@
             }
             visibleWindows.clear();
 
-            if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
-                mMagnifiedViewport.intersectWithDrawBorderInset(screenWidth, screenHeight);
-            }
-
             final boolean magnifiedChanged =
                     !mOldMagnificationRegion.equals(mMagnificationRegion);
             if (magnifiedChanged) {
-                if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
-                    mMagnifiedViewport.updateBorderDrawingStatus(screenWidth, screenHeight);
-                }
                 mOldMagnificationRegion.set(mMagnificationRegion);
                 final SomeArgs args = SomeArgs.obtain();
                 args.arg1 = Region.obtain(mMagnificationRegion);
@@ -1139,420 +1046,11 @@
             outSize.set(bounds.width(), bounds.height());
         }
 
-        void dump(PrintWriter pw, String prefix) {
-            if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
-                mMagnifiedViewport.dump(pw, prefix);
-            }
-        }
-
-        private final class MagnifiedViewport {
-
-            private final float mBorderWidth;
-            private final int mHalfBorderWidth;
-            private final int mDrawBorderInset;
-
-            @Nullable private final ViewportWindow mWindow;
-
-            private boolean mFullRedrawNeeded;
-
-            MagnifiedViewport() {
-                mBorderWidth = mDisplayContext.getResources().getDimension(
-                        com.android.internal.R.dimen.accessibility_magnification_indicator_width);
-                mHalfBorderWidth = (int) Math.ceil(mBorderWidth / 2);
-                mDrawBorderInset = (int) mBorderWidth / 2;
-                mWindow = new ViewportWindow(mDisplayContext);
-            }
-
-            void updateBorderDrawingStatus(int screenWidth, int screenHeight) {
-                mWindow.setBounds(mMagnificationRegion);
-                final Rect dirtyRect = mTempRect1;
-                if (mFullRedrawNeeded) {
-                    mFullRedrawNeeded = false;
-                    dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
-                            screenWidth - mDrawBorderInset,
-                            screenHeight - mDrawBorderInset);
-                    mWindow.invalidate(dirtyRect);
-                } else {
-                    final Region dirtyRegion = mTempRegion3;
-                    dirtyRegion.set(mMagnificationRegion);
-                    dirtyRegion.op(mOldMagnificationRegion, Region.Op.XOR);
-                    dirtyRegion.getBounds(dirtyRect);
-                    mWindow.invalidate(dirtyRect);
-                }
-            }
-
-            void setShowMagnifiedBorderIfNeeded() {
-                // If this message is pending, we are in a rotation animation and do not want
-                // to show the border. We will do so when the pending message is handled.
-                if (!mHandler.hasMessages(
-                        MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
-                    setMagnifiedRegionBorderShown(
-                            isFullscreenMagnificationActivated(), true);
-                }
-            }
-
-            // Can be called outside of a surface transaction
-            void showMagnificationBoundsIfNeeded() {
-                if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
-                    mAccessibilityTracing.logTrace(LOG_TAG + ".showMagnificationBoundsIfNeeded",
-                            FLAGS_MAGNIFICATION_CALLBACK);
-                }
-                mHandler.obtainMessage(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)
-                        .sendToTarget();
-            }
-
-            void intersectWithDrawBorderInset(int screenWidth, int screenHeight) {
-                mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset,
-                        screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset,
-                        Region.Op.INTERSECT);
-            }
-
-            void onDisplaySizeChanged() {
-                // If fullscreen magnification is activated, hide the border immediately so
-                // the user does not see strange artifacts during display size changed caused by
-                // rotation or folding/unfolding the device. In the rotation case, the
-                // screenshot used for rotation already has the border. After the rotation is
-                // completed we will show the border.
-                if (isFullscreenMagnificationActivated()) {
-                    setMagnifiedRegionBorderShown(false, false);
-                    final long delay = (long) (mLongAnimationDuration
-                            * mService.getWindowAnimationScaleLocked());
-                    Message message = mHandler.obtainMessage(
-                            MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
-                    mHandler.sendMessageDelayed(message, delay);
-                }
-                mWindow.updateSize();
-            }
-
-            void setMagnifiedRegionBorderShown(boolean shown, boolean animate) {
-                if (mWindow.setShown(shown, animate)) {
-                    mFullRedrawNeeded = true;
-                    // Clear the old region, so recomputeBounds will refresh the current region.
-                    mOldMagnificationRegion.set(0, 0, 0, 0);
-                }
-            }
-
-            void drawWindowIfNeeded() {
-                mWindow.postDrawIfNeeded();
-            }
-
-            void destroyWindow() {
-                mWindow.releaseSurface();
-            }
-
-            void dump(PrintWriter pw, String prefix) {
-                mWindow.dump(pw, prefix);
-            }
-
-            // TODO(291891390): Remove this class when we clean up the flag
-            //  alwaysDrawMagnificationFullscreenBorder
-            private final class ViewportWindow implements Runnable {
-                private static final String SURFACE_TITLE = "Magnification Overlay";
-
-                private final Region mBounds = new Region();
-                private final Rect mDirtyRect = new Rect();
-                private final Paint mPaint = new Paint();
-
-                private final SurfaceControl mSurfaceControl;
-                /** After initialization, it should only be accessed from animation thread. */
-                private final SurfaceControl.Transaction mTransaction;
-                private final BLASTBufferQueue mBlastBufferQueue;
-                private final Surface mSurface;
-
-                private final AnimationController mAnimationController;
-
-                private boolean mShown;
-                private boolean mLastSurfaceShown;
-                private int mAlpha;
-                private int mPreviousAlpha;
-
-                private volatile boolean mInvalidated;
-
-                ViewportWindow(Context context) {
-                    SurfaceControl surfaceControl = null;
-                    try {
-                        surfaceControl = mDisplayContent
-                                .makeOverlay()
-                                .setName(SURFACE_TITLE)
-                                .setBLASTLayer()
-                                .setFormat(PixelFormat.TRANSLUCENT)
-                                .setCallsite("ViewportWindow")
-                                .build();
-                    } catch (OutOfResourcesException oore) {
-                        /* ignore */
-                    }
-                    mSurfaceControl = surfaceControl;
-                    mDisplay.getRealSize(mScreenSize);
-                    mBlastBufferQueue = new BLASTBufferQueue(SURFACE_TITLE, mSurfaceControl,
-                            mScreenSize.x, mScreenSize.y, PixelFormat.RGBA_8888);
-
-                    final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
-                    final int layer =
-                            mService.mPolicy.getWindowLayerFromTypeLw(TYPE_MAGNIFICATION_OVERLAY) *
-                                    WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
-                    t.setLayer(mSurfaceControl, layer).setPosition(mSurfaceControl, 0, 0);
-                    InputMonitor.setTrustedOverlayInputInfo(mSurfaceControl, t,
-                            mDisplayContent.getDisplayId(), "Magnification Overlay");
-                    t.apply();
-                    mTransaction = t;
-                    mSurface = mBlastBufferQueue.createSurface();
-
-                    mAnimationController = new AnimationController(context,
-                            mService.mH.getLooper());
-
-                    TypedValue typedValue = new TypedValue();
-                    context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
-                            typedValue, true);
-                    final int borderColor = context.getColor(typedValue.resourceId);
-
-                    mPaint.setStyle(Paint.Style.STROKE);
-                    mPaint.setStrokeWidth(mBorderWidth);
-                    mPaint.setColor(borderColor);
-
-                    mInvalidated = true;
-                }
-
-                /** Returns {@code true} if the state is changed to shown. */
-                boolean setShown(boolean shown, boolean animate) {
-                    synchronized (mService.mGlobalLock) {
-                        if (mShown == shown) {
-                            return false;
-                        }
-                        mShown = shown;
-                        mAnimationController.onFrameShownStateChanged(shown, animate);
-                        if (DEBUG_VIEWPORT_WINDOW) {
-                            Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown);
-                        }
-                    }
-                    return shown;
-                }
-
-                @SuppressWarnings("unused")
-                // Called reflectively from an animator.
-                int getAlpha() {
-                    synchronized (mService.mGlobalLock) {
-                        return mAlpha;
-                    }
-                }
-
-                void setAlpha(int alpha) {
-                    synchronized (mService.mGlobalLock) {
-                        if (mAlpha == alpha) {
-                            return;
-                        }
-                        mAlpha = alpha;
-                        invalidate(null);
-                        if (DEBUG_VIEWPORT_WINDOW) {
-                            Slog.i(LOG_TAG, "ViewportWindow set alpha: " + alpha);
-                        }
-                    }
-                }
-
-                void setBounds(Region bounds) {
-                    synchronized (mService.mGlobalLock) {
-                        if (mBounds.equals(bounds)) {
-                            return;
-                        }
-                        mBounds.set(bounds);
-                        invalidate(mDirtyRect);
-                        if (DEBUG_VIEWPORT_WINDOW) {
-                            Slog.i(LOG_TAG, "ViewportWindow set bounds: " + bounds);
-                        }
-                    }
-                }
-
-                void updateSize() {
-                    synchronized (mService.mGlobalLock) {
-                        getDisplaySizeLocked(mScreenSize);
-                        mBlastBufferQueue.update(mSurfaceControl, mScreenSize.x, mScreenSize.y,
-                                PixelFormat.RGBA_8888);
-                        invalidate(mDirtyRect);
-                    }
-                }
-
-                void invalidate(Rect dirtyRect) {
-                    if (dirtyRect != null) {
-                        mDirtyRect.set(dirtyRect);
-                    } else {
-                        mDirtyRect.setEmpty();
-                    }
-                    mInvalidated = true;
-                    mService.scheduleAnimationLocked();
-                }
-
-                void postDrawIfNeeded() {
-                    if (mInvalidated) {
-                        mService.mAnimationHandler.post(this);
-                    }
-                }
-
-                @Override
-                public void run() {
-                    drawOrRemoveIfNeeded();
-                }
-
-                /**
-                 * This method must only be called by animation handler directly to make sure
-                 * thread safe and there is no lock held outside.
-                 */
-                private void drawOrRemoveIfNeeded() {
-                    // Drawing variables (alpha, dirty rect, and bounds) access is synchronized
-                    // using WindowManagerGlobalLock. Grab copies of these values before
-                    // drawing on the canvas so that drawing can be performed outside of the lock.
-                    int alpha;
-                    boolean redrawBounds;
-                    Rect drawingRect = null;
-                    Region drawingBounds = null;
-                    synchronized (mService.mGlobalLock) {
-                        if (mBlastBufferQueue.mNativeObject == 0) {
-                            // Complete removal since releaseSurface has been called.
-                            if (mSurface.isValid()) {
-                                mTransaction.remove(mSurfaceControl).apply();
-                                mSurface.release();
-                            }
-                            return;
-                        }
-                        if (!mInvalidated) {
-                            return;
-                        }
-                        mInvalidated = false;
-
-                        alpha = mAlpha;
-                        // For b/325863281, we should ensure the drawn border path is cleared when
-                        // alpha = 0. Therefore, we cache the last used alpha when drawing as
-                        // mPreviousAlpha and check it here. If mPreviousAlpha > 0, which means
-                        // the border is showing now, then we should still redraw the clear path
-                        // on the canvas so the border is cleared.
-                        redrawBounds = mAlpha > 0 || mPreviousAlpha > 0;
-                        if (redrawBounds) {
-                            drawingBounds = new Region(mBounds);
-                            // Empty dirty rectangle means unspecified.
-                            if (mDirtyRect.isEmpty()) {
-                                mBounds.getBounds(mDirtyRect);
-                            }
-                            mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth);
-                            drawingRect = new Rect(mDirtyRect);
-                            if (DEBUG_VIEWPORT_WINDOW) {
-                                Slog.i(LOG_TAG, "ViewportWindow bounds: " + mBounds);
-                                Slog.i(LOG_TAG, "ViewportWindow dirty rect: " + mDirtyRect);
-                            }
-                        }
-                    }
-
-                    final boolean showSurface;
-                    // Draw without holding WindowManagerGlobalLock.
-                    if (redrawBounds) {
-                        Canvas canvas = null;
-                        try {
-                            canvas = mSurface.lockCanvas(drawingRect);
-                        } catch (IllegalArgumentException | OutOfResourcesException e) {
-                            /* ignore */
-                        }
-                        if (canvas == null) {
-                            return;
-                        }
-                        canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
-                        mPaint.setAlpha(alpha);
-                        canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
-                        mSurface.unlockCanvasAndPost(canvas);
-                        mPreviousAlpha = alpha;
-                    }
-
-                    showSurface = alpha > 0;
-
-                    if (showSurface && !mLastSurfaceShown) {
-                        mTransaction.show(mSurfaceControl).apply();
-                        mLastSurfaceShown = true;
-                    } else if (!showSurface && mLastSurfaceShown) {
-                        mTransaction.hide(mSurfaceControl).apply();
-                        mLastSurfaceShown = false;
-                    }
-                }
-
-                @GuardedBy("mService.mGlobalLock")
-                void releaseSurface() {
-                    mBlastBufferQueue.destroy();
-                    // Post to perform cleanup on the thread which handles mSurface.
-                    mService.mAnimationHandler.post(this);
-                }
-
-                void dump(PrintWriter pw, String prefix) {
-                    pw.println(prefix
-                            + " mBounds= " + mBounds
-                            + " mDirtyRect= " + mDirtyRect
-                            + " mWidth= " + mScreenSize.x
-                            + " mHeight= " + mScreenSize.y);
-                }
-
-                private final class AnimationController extends Handler {
-                    private static final String PROPERTY_NAME_ALPHA = "alpha";
-
-                    private static final int MIN_ALPHA = 0;
-                    private static final int MAX_ALPHA = 255;
-
-                    private static final int MSG_FRAME_SHOWN_STATE_CHANGED = 1;
-
-                    private final ValueAnimator mShowHideFrameAnimator;
-
-                    AnimationController(Context context, Looper looper) {
-                        super(looper);
-                        mShowHideFrameAnimator = ObjectAnimator.ofInt(ViewportWindow.this,
-                                PROPERTY_NAME_ALPHA, MIN_ALPHA, MAX_ALPHA);
-
-                        Interpolator interpolator = new DecelerateInterpolator(2.5f);
-                        final long longAnimationDuration = context.getResources().getInteger(
-                                com.android.internal.R.integer.config_longAnimTime);
-
-                        mShowHideFrameAnimator.setInterpolator(interpolator);
-                        mShowHideFrameAnimator.setDuration(longAnimationDuration);
-                    }
-
-                    void onFrameShownStateChanged(boolean shown, boolean animate) {
-                        obtainMessage(MSG_FRAME_SHOWN_STATE_CHANGED,
-                                shown ? 1 : 0, animate ? 1 : 0).sendToTarget();
-                    }
-
-                    @Override
-                    public void handleMessage(Message message) {
-                        switch (message.what) {
-                            case MSG_FRAME_SHOWN_STATE_CHANGED: {
-                                final boolean shown = message.arg1 == 1;
-                                final boolean animate = message.arg2 == 1;
-
-                                if (animate) {
-                                    if (mShowHideFrameAnimator.isRunning()) {
-                                        mShowHideFrameAnimator.reverse();
-                                    } else {
-                                        if (shown) {
-                                            mShowHideFrameAnimator.start();
-                                        } else {
-                                            mShowHideFrameAnimator.reverse();
-                                        }
-                                    }
-                                } else {
-                                    mShowHideFrameAnimator.cancel();
-                                    if (shown) {
-                                        setAlpha(MAX_ALPHA);
-                                    } else {
-                                        setAlpha(MIN_ALPHA);
-                                    }
-                                }
-                            } break;
-                        }
-                    }
-                }
-            }
-        }
-
         private class MyHandler extends Handler {
             public static final int MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED = 1;
             public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
             public static final int MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED = 4;
-
-            // TODO(291891390): Remove this field when we clean up the flag
-            //  alwaysDrawMagnificationFullscreenBorder
-            public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
-            public static final int MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED = 6;
+            public static final int MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED = 5;
 
             MyHandler(Looper looper) {
                 super(looper);
@@ -1576,17 +1074,6 @@
                         mCallbacks.onDisplaySizeChanged();
                     } break;
 
-                    case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
-                        synchronized (mService.mGlobalLock) {
-                            if (isFullscreenMagnificationActivated()) {
-                                if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
-                                    mMagnifiedViewport.setMagnifiedRegionBorderShown(true, true);
-                                }
-                                mService.scheduleAnimationLocked();
-                            }
-                        }
-                    } break;
-
                     case MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED: {
                         final boolean shown = message.arg1 == 1;
                         mCallbacks.onImeWindowVisibilityChanged(shown);
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index 7fc11e6..3c0e058 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -150,11 +150,7 @@
     @Override
     public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
             DisplayInfo[] displayInfos) {
-        if (com.android.server.accessibility.Flags.removeOnWindowInfosChangedHandler()) {
-            onWindowInfosChangedInternal(windowHandles, displayInfos);
-        } else {
-            mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos));
-        }
+        onWindowInfosChangedInternal(windowHandles, displayInfos);
     }
 
     private void onWindowInfosChangedInternal(InputWindowHandle[] windowHandles,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f70dec1..9956d85 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3212,26 +3212,46 @@
      * will be ignored.
      */
     boolean isUniversalResizeable() {
-        if (info.applicationInfo.category == ApplicationInfo.CATEGORY_GAME) {
-            return false;
-        }
-        final boolean compatEnabled = Flags.universalResizableByDefault()
-                && mDisplayContent != null && mDisplayContent.getConfiguration()
-                    .smallestScreenWidthDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
-                && mDisplayContent.getIgnoreOrientationRequest()
-                && info.isChangeEnabled(ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT);
-        if (!compatEnabled && !mWmService.mConstants.mIgnoreActivityOrientationRequest) {
-            return false;
-        }
-        if (mWmService.mConstants.isPackageOptOutIgnoreActivityOrientationRequest(packageName)) {
+        final boolean isLargeScreen = mDisplayContent != null && mDisplayContent.getConfiguration()
+                .smallestScreenWidthDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
+                && mDisplayContent.getIgnoreOrientationRequest();
+        if (!canBeUniversalResizeable(info.applicationInfo, mWmService, isLargeScreen,
+                true /* forActivity */)) {
             return false;
         }
         if (mAppCompatController.mAllowRestrictedResizability.getAsBoolean()) {
             return false;
         }
         // If the user preference respects aspect ratio, then it becomes non-resizable.
-        return !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
-                .shouldApplyUserMinAspectRatioOverride();
+        return mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
+                .userPreferenceCompatibleWithNonResizability();
+    }
+
+    /**
+     * Returns {@code true} if the fixed orientation, aspect ratio, resizability of the application
+     * can be ignored.
+     */
+    static boolean canBeUniversalResizeable(ApplicationInfo appInfo, WindowManagerService wms,
+            boolean isLargeScreen, boolean forActivity) {
+        if (appInfo.category == ApplicationInfo.CATEGORY_GAME) {
+            return false;
+        }
+        final boolean compatEnabled = isLargeScreen && Flags.universalResizableByDefault()
+                && appInfo.isChangeEnabled(ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT);
+        final boolean configEnabled = (isLargeScreen
+                ? wms.mConstants.mIgnoreActivityOrientationRequestLargeScreen
+                : wms.mConstants.mIgnoreActivityOrientationRequestSmallScreen)
+                && !wms.mConstants.isPackageOptOutIgnoreActivityOrientationRequest(
+                        appInfo.packageName);
+        if (!compatEnabled && !configEnabled) {
+            return false;
+        }
+        if (forActivity) {
+            // The caller will check both application and activity level property.
+            return true;
+        }
+        return !AppCompatController.allowRestrictedResizability(wms.mContext.getPackageManager(),
+                appInfo.packageName);
     }
 
     boolean isResizeable() {
@@ -3667,16 +3687,6 @@
 
             pauseKeyDispatchingLocked();
 
-            // We are finishing the top focused activity and its task has nothing to be focused so
-            // the next focusable task should be focused.
-            if (mayAdjustTop && task.topRunningActivity(true /* focusableOnly */)
-                    == null) {
-                task.adjustFocusToNextFocusableTask("finish-top", false /* allowFocusSelf */,
-                            shouldAdjustGlobalFocus);
-            }
-
-            finishActivityResults(resultCode, resultData, resultGrants);
-
             final boolean endTask = task.getTopNonFinishingActivity() == null
                     && !task.isClearingToReuseTask();
             final WindowContainer<?> trigger = endTask ? task : this;
@@ -3687,6 +3697,16 @@
             if (transition != null) {
                 transition.collectClose(trigger);
             }
+            // We are finishing the top focused activity and its task has nothing to be focused so
+            // the next focusable task should be focused.
+            if (mayAdjustTop && task.topRunningActivity(true /* focusableOnly */)
+                    == null) {
+                task.adjustFocusToNextFocusableTask("finish-top", false /* allowFocusSelf */,
+                            shouldAdjustGlobalFocus);
+            }
+
+            finishActivityResults(resultCode, resultData, resultGrants);
+
             if (isState(RESUMED)) {
                 if (endTask) {
                     mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted(
@@ -4575,12 +4595,35 @@
                 // at #postWindowRemoveCleanupLocked
                 return false;
             }
+
+            // Link the fixed rotation transform to this activity since we are transferring the
+            // starting window.
+            if (fromActivity.hasFixedRotationTransform()) {
+                mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(this,
+                        false /* checkOpening */);
+            }
             // Do not transfer if the orientation doesn't match, redraw starting window while it is
             // on top will cause flicker.
-            if (fromActivity.getRequestedConfigurationOrientation()
-                    != getRequestedConfigurationOrientation()) {
+            final int fromOrientation = fromActivity.getConfiguration().orientation;
+            final int requestedOrientation = getRequestedConfigurationOrientation();
+            if (requestedOrientation == ORIENTATION_UNDEFINED) {
+                if (fromOrientation != getConfiguration().orientation) {
+                    return false;
+                }
+            } else if (fromOrientation != requestedOrientation) {
                 return false;
             }
+
+            // If another activity above the activity which has starting window, allows to steal the
+            // starting window if the above activity isn't drawn.
+            if (task.getChildCount() >= 3
+                    && fromActivity.mStartingData.mAssociatedTask == null) {
+                final ActivityRecord aboveFrom = task.getActivityAbove(fromActivity);
+                if (aboveFrom != null && aboveFrom != this && !aboveFrom.mReportedDrawn) {
+                    return false;
+                }
+            }
+
             // In this case, the starting icon has already been displayed, so start
             // letting windows get shown immediately without any more transitions.
             if (fromActivity.mVisible) {
@@ -4592,13 +4635,6 @@
 
             final long origId = Binder.clearCallingIdentity();
             try {
-                // Link the fixed rotation transform to this activity since we are transferring the
-                // starting window.
-                if (fromActivity.hasFixedRotationTransform()) {
-                    mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(this,
-                            false /* checkOpening */);
-                }
-
                 // Transfer the starting window over to the new token.
                 mStartingData = fromActivity.mStartingData;
                 mStartingSurface = fromActivity.mStartingSurface;
@@ -4611,6 +4647,16 @@
                 tStartingWindow.mToken = this;
                 tStartingWindow.mActivityRecord = this;
 
+                if (mStartingData.mRemoveAfterTransaction == AFTER_TRANSACTION_REMOVE_DIRECTLY) {
+                    // The removal of starting window should wait for window drawn of current
+                    // activity.
+                    final WindowState mainWin = findMainWindow(false /* includeStartingApp */);
+                    if (mainWin == null || !mainWin.isDrawn()) {
+                        mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
+                        mStartingData.mPrepareRemoveAnimation = false;
+                    }
+                }
+
                 ProtoLog.v(WM_DEBUG_ADD_REMOVE,
                         "Removing starting %s from %s", tStartingWindow, fromActivity);
                 mTransitionController.collect(tStartingWindow);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 2e2ca14..90d3834 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1838,7 +1838,7 @@
                     remoteTransition, null /* displayChange */);
         } else if (result == START_SUCCESS && mStartActivity.isState(RESUMED)) {
             // Do nothing if the activity is started and is resumed directly.
-        } else if (isStarted) {
+        } else if (isStarted && (mBalCode != BAL_BLOCK || mDoResume)) {
             // Make the collecting transition wait until this request is ready.
             if (transition != null) {
                 transition.setReady(started, false);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 70a8f56..a077a0b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2054,6 +2054,8 @@
                 break;
             }
         }
+        long timeRemaining = endTime - System.currentTimeMillis();
+        mWindowManager.mSnapshotController.mTaskSnapshotController.waitFlush(timeRemaining);
 
         // Force checkReadyForSleep to complete.
         checkReadyForSleepLocked(false /* allowDelay */);
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 90c0866..086b11c 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -135,6 +135,12 @@
                 && aspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
     }
 
+    boolean userPreferenceCompatibleWithNonResizability() {
+        final int aspectRatio = getUserMinAspectRatioOverrideCode();
+        return aspectRatio == USER_MIN_ASPECT_RATIO_UNSET
+                || aspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
+    }
+
     boolean shouldApplyUserFullscreenOverride() {
         if (isUserFullscreenOverrideEnabled()) {
             final int aspectRatio = getUserMinAspectRatioOverrideCode();
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
index 9754595..47d30c9 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -164,12 +164,14 @@
      * <p>The treatment is enabled when the following conditions are met:
      * <ul>
      * <li>Feature flag gating the camera compatibility free-form treatment is enabled.
-     * <li>Activity is opted in by the device manufacturer with override.
+     * <li>Activity is opted-in using per-app override, or the treatment is enabled for all apps.
      * </ul>
      */
     boolean shouldApplyFreeformTreatmentForCameraCompat() {
-        return Flags.enableCameraCompatForDesktopWindowing() && isChangeEnabled(mActivityRecord,
-                OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT);
+        return Flags.enableCameraCompatForDesktopWindowing() && (isChangeEnabled(mActivityRecord,
+                OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)
+                || mActivityRecord.mWmService.mAppCompatConfiguration
+                    .isCameraCompatFreeformWindowingTreatmentEnabled());
     }
 
     boolean isOverrideOrientationOnlyForCameraEnabled() {
diff --git a/services/core/java/com/android/server/wm/AppCompatConfiguration.java b/services/core/java/com/android/server/wm/AppCompatConfiguration.java
index 38c6de1..9a15c4a 100644
--- a/services/core/java/com/android/server/wm/AppCompatConfiguration.java
+++ b/services/core/java/com/android/server/wm/AppCompatConfiguration.java
@@ -304,6 +304,11 @@
     // See RefreshCallbackItem for context.
     private boolean mIsCameraCompatRefreshCycleThroughStopEnabled = true;
 
+    // Whether camera compat freeform treatment should be enabled for all eligible activities.
+    // This has the same effect as enabling the per-app override
+    // ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT for every app.
+    private boolean mIsCameraCompatFreeformWindowingTreatmentEnabled = false;
+
     // Whether should ignore app requested orientation in response to an app
     // calling Activity#setRequestedOrientation. See
     // LetterboxUiController#shouldIgnoreRequestedOrientation for details.
@@ -1351,6 +1356,30 @@
     }
 
     /**
+     * Sets whether the camera compatibility treatment in freeform windowing mode is enabled for
+     * all fixed-orientation apps when using camera.
+     */
+    void setIsCameraCompatFreeformWindowingTreatmentEnabled(boolean enabled) {
+        mIsCameraCompatFreeformWindowingTreatmentEnabled = enabled;
+    }
+
+    /**
+     * Whether the camera compatibility treatment in freeform windowing mode is enabled for all
+     * fixed-orientation apps when using camera.
+     */
+    boolean isCameraCompatFreeformWindowingTreatmentEnabled() {
+        return mIsCameraCompatFreeformWindowingTreatmentEnabled;
+    }
+
+    /**
+     * Resets whether the camera compatibility treatment in freeform windowing mode is enabled for
+     * all fixed-orientation apps when using camera.
+     */
+    void resetIsCameraCompatFreeformWindowingTreatmentEnabled() {
+        mIsCameraCompatFreeformWindowingTreatmentEnabled = false;
+    }
+
+    /**
      * Checks whether rotation compat policy for immersive apps that prevents auto rotation
      * into non-optimal screen orientation while in fullscreen is enabled at build time. This is
      * used when we need to safely initialize a component before the {@link DeviceConfig} flag
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 145a376..330283f 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -76,6 +76,11 @@
         mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(mActivityRecord,
                 mAppCompatOverrides);
         mAllowRestrictedResizability = AppCompatUtils.asLazy(() -> {
+            // Application level.
+            if (allowRestrictedResizability(packageManager, mActivityRecord.packageName)) {
+                return true;
+            }
+            // Activity level.
             try {
                 return packageManager.getPropertyAsUser(
                         PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
@@ -88,6 +93,15 @@
         });
     }
 
+    static boolean allowRestrictedResizability(PackageManager pm, String packageName) {
+        try {
+            return pm.getProperty(PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, packageName)
+                    .getBoolean();
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
     @NonNull
     TransparentPolicy getTransparentPolicy() {
         return mTransparentPolicy;
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index ebb50db..a418324 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -173,7 +173,8 @@
             appCompatTaskInfo.topActivityLetterboxHeight = bounds.height();
             appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width();
             appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height();
-
+            // TODO(b/379824541) Remove duplicate information.
+            appCompatTaskInfo.topActivityLetterboxBounds = bounds;
             // We need to consider if letterboxed or pillarboxed.
             // TODO(b/336807329) Encapsulate reachability logic
             appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides
@@ -282,6 +283,7 @@
         info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
         info.topActivityLetterboxAppHeight = TaskInfo.PROPERTY_VALUE_UNSET;
         info.topActivityLetterboxAppWidth = TaskInfo.PROPERTY_VALUE_UNSET;
+        info.topActivityLetterboxBounds = null;
         info.cameraCompatTaskInfo.freeformCameraCompatMode =
                 CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
         info.clearTopActivityFlags();
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index fcaab2c..601b17c 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -32,6 +32,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.os.Build;
@@ -40,6 +41,8 @@
 import android.os.Message;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.system.Os;
+import android.system.OsConstants;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -76,6 +79,7 @@
     public static final int FLAG_HIDE_COMPILE_SDK = 0x02;
     public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04;
     public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08;
+    public static final int FLAG_HIDE_PAGE_SIZE_MISMATCH = 0x10;
 
     /**
      * Map of package flags for each user.
@@ -101,6 +105,7 @@
     private SparseArray<UnsupportedCompileSdkDialog> mUnsupportedCompileSdkDialogs;
     private SparseArray<DeprecatedTargetSdkVersionDialog> mDeprecatedTargetSdkVersionDialogs;
     private SparseArray<DeprecatedAbiDialog> mDeprecatedAbiDialogs;
+    private SparseArray<PageSizeMismatchDialog> mPageSizeMismatchDialogs;
 
     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
     private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
@@ -250,6 +255,19 @@
         }
     }
 
+    public void showPageSizeMismatchDialogIfNeeded(ActivityRecord r) {
+        // Don't show dialog if the app compat is enabled using property
+        final boolean appCompatEnabled = SystemProperties.getBoolean(
+                "bionic.linker.16kb.app_compat.enabled", false);
+        if (appCompatEnabled) {
+            return;
+        }
+        boolean is16KbDevice = Os.sysconf(OsConstants._SC_PAGESIZE) == 16384;
+        if (is16KbDevice) {
+            mUiHandler.showPageSizeMismatchDialog(r);
+        }
+    }
+
     /**
      * Called when an activity is being started.
      *
@@ -260,6 +278,9 @@
         showUnsupportedDisplaySizeDialogIfNeeded(r);
         showDeprecatedTargetDialogIfNeeded(r);
         showDeprecatedAbiDialogIfNeeded(r);
+        if (Flags.appCompatOption16kb()) {
+            showPageSizeMismatchDialogIfNeeded(r);
+        }
     }
 
     /**
@@ -457,6 +478,41 @@
         }
     }
 
+    @UiThread
+    private void showPageSizeMismatchDialogUiThread(@NonNull ActivityRecord ar) {
+        String warning =
+                mAtm.mContext
+                        .getPackageManager()
+                        .getPageSizeCompatWarningMessage(ar.info.packageName);
+        if (warning == null) {
+            return;
+        }
+
+        final int userId = getUserIdForActivity(ar);
+        PageSizeMismatchDialog pageSizeMismatchDialog;
+        if (mPageSizeMismatchDialogs != null) {
+            pageSizeMismatchDialog = mPageSizeMismatchDialogs.get(userId);
+            if (pageSizeMismatchDialog != null) {
+                pageSizeMismatchDialog.dismiss();
+                mPageSizeMismatchDialogs.remove(userId);
+            }
+        }
+        if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_PAGE_SIZE_MISMATCH)) {
+            pageSizeMismatchDialog =
+                    new PageSizeMismatchDialog(
+                            AppWarnings.this,
+                            getUiContextForActivity(ar),
+                            ar.info.applicationInfo,
+                            userId,
+                            warning);
+            pageSizeMismatchDialog.show();
+            if (mPageSizeMismatchDialogs == null) {
+                mPageSizeMismatchDialogs = new SparseArray<>();
+            }
+            mPageSizeMismatchDialogs.put(userId, pageSizeMismatchDialog);
+        }
+    }
+
     /**
      * Dismisses all warnings for the given package.
      * <p>
@@ -510,6 +566,16 @@
                 mDeprecatedAbiDialogs.remove(userId);
             }
         }
+
+        // Hides the "page size app compat" dialog if necessary.
+        if (mPageSizeMismatchDialogs != null) {
+            PageSizeMismatchDialog pageSizeMismatchDialog = mPageSizeMismatchDialogs.get(userId);
+            if (pageSizeMismatchDialog != null
+                    && (name == null || name.equals(pageSizeMismatchDialog.mPackageName))) {
+                pageSizeMismatchDialog.dismiss();
+                mPageSizeMismatchDialogs.remove(userId);
+            }
+        }
     }
 
     /**
@@ -649,6 +715,7 @@
         private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4;
         private static final int MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG = 5;
         private static final int MSG_SHOW_DEPRECATED_ABI_DIALOG = 6;
+        private static final int MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG = 7;
 
         public UiHandler(Looper looper) {
             super(looper, null, true);
@@ -681,6 +748,10 @@
                     final ActivityRecord ar = (ActivityRecord) msg.obj;
                     showDeprecatedAbiDialogUiThread(ar);
                 } break;
+                case MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG: {
+                    final ActivityRecord ar = (ActivityRecord) msg.obj;
+                    showPageSizeMismatchDialogUiThread(ar);
+                } break;
             }
         }
 
@@ -712,6 +783,11 @@
         public void hideDialogsForPackage(String name, int userId) {
             obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, userId, 0, name).sendToTarget();
         }
+
+        public void showPageSizeMismatchDialog(ActivityRecord r) {
+            removeMessages(MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG);
+            obtainMessage(MSG_SHOW_PAGE_SIZE_APP_MISMATCH_DIALOG, r).sendToTarget();
+        }
     }
 
     static class BaseDialog {
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index f0a6e9e..dd1af0a 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -653,7 +653,9 @@
             // by drawing the rotated content before applying projection transaction of display.
             // And it will fade in after the display transition is finished.
             if (mTransitionOp == OP_APP_SWITCH && !mIsStartTransactionCommitted
-                    && canBeAsync(w.mToken) && !mDisplayContent.hasFixedRotationTransientLaunch()) {
+                    && canBeAsync(w.mToken) && !mDisplayContent.hasFixedRotationTransientLaunch()
+                    && !mService.mAtmService.mBackNavigationController.hasFixedRotationAnimation(
+                            mDisplayContent)) {
                 hideImmediately(w.mToken, Operation.ACTION_FADE);
                 if (DEBUG) Slog.d(TAG, "Hide on finishDrawing " + w.mToken.getTopChild());
             }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 4ed8b09..3968b52 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -622,6 +622,15 @@
         }
     }
 
+    boolean hasFixedRotationAnimation(@NonNull DisplayContent displayContent) {
+        if (!mAnimationHandler.mComposed) {
+            return false;
+        }
+        final ActivityRecord openActivity = mAnimationHandler.mOpenActivities[0];
+        return displayContent == openActivity.mDisplayContent
+                && displayContent.isFixedRotationLaunchingApp(openActivity);
+    }
+
     private boolean isWaitBackTransition() {
         // Ignore mWaitTransition while flag is enabled.
         return mAnimationHandler.mComposed && (Flags.migratePredictiveBackTransition()
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index 506477f..1676bfa 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -65,6 +65,9 @@
     @NonNull
     private final CameraStateMonitor mCameraStateMonitor;
 
+    // TODO(b/380840084): Consider moving this to the CameraStateMonitor, and keeping track of
+    // all current camera activities, especially when the camera access is switching from one app to
+    // another.
     @Nullable
     private Task mCameraTask;
 
@@ -123,8 +126,7 @@
     }
 
     @Override
-    public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
-            @NonNull String cameraId) {
+    public void onCameraOpened(@NonNull ActivityRecord cameraActivity) {
         // Do not check orientation outside of the config recompute, as the app's orientation intent
         // might be obscured by a fullscreen override. Especially for apps which have a camera
         // functionality which is not the main focus of the app: while most of the app might work
@@ -136,18 +138,15 @@
             return;
         }
 
-        cameraActivity.recomputeConfiguration();
-        cameraActivity.getTask().dispatchTaskInfoChangedIfNeeded(/* force= */ true);
-        cameraActivity.ensureActivityConfiguration(/* ignoreVisibility= */ false);
+        mCameraTask = cameraActivity.getTask();
+        updateAndDispatchCameraConfiguration();
     }
 
     @Override
-    public boolean onCameraClosed(@NonNull String cameraId) {
+    public boolean canCameraBeClosed(@NonNull String cameraId) {
         // Top activity in the same task as the camera activity, or `null` if the task is
         // closed.
-        final ActivityRecord topActivity = mCameraTask != null
-                ? mCameraTask.getTopActivity(/* isFinishing */ false, /* includeOverlays */ false)
-                : null;
+        final ActivityRecord topActivity = getTopActivityFromCameraTask();
         if (topActivity != null) {
             if (isActivityForCameraIdRefreshing(topActivity, cameraId)) {
                 ProtoLog.v(WmProtoLogGroups.WM_DEBUG_STATES,
@@ -157,16 +156,44 @@
                 return false;
             }
         }
-        mCameraTask = null;
         return true;
     }
 
+    @Override
+    public void onCameraClosed() {
+        // Top activity in the same task as the camera activity, or `null` if the task is
+        // closed.
+        final ActivityRecord topActivity = getTopActivityFromCameraTask();
+        // Only clean up if the camera is not running - this close signal could be from switching
+        // cameras (e.g. back to front camera, and vice versa).
+        if (topActivity == null || !mCameraStateMonitor.isCameraRunningForActivity(topActivity)) {
+            updateAndDispatchCameraConfiguration();
+            mCameraTask = null;
+        }
+    }
+
+    private void updateAndDispatchCameraConfiguration() {
+        if (mCameraTask == null) {
+            return;
+        }
+        final ActivityRecord activity = getTopActivityFromCameraTask();
+        if (activity != null) {
+            activity.recomputeConfiguration();
+            mCameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true);
+            activity.ensureActivityConfiguration(/* ignoreVisibility= */ true);
+        } else {
+            mCameraTask.dispatchTaskInfoChangedIfNeeded(/* force= */ true);
+        }
+    }
+
     boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
         return isCameraRunningAndWindowingModeEligible(activity);
     }
 
     boolean isCameraRunningAndWindowingModeEligible(@NonNull ActivityRecord activity) {
-        return activity.inFreeformWindowingMode()
+        return  activity.mAppCompatController.getAppCompatCameraOverrides()
+                .shouldApplyFreeformTreatmentForCameraCompat()
+                && activity.inFreeformWindowingMode()
                 && mCameraStateMonitor.isCameraRunningForActivity(activity);
     }
 
@@ -262,10 +289,17 @@
                 && !activity.isEmbedded();
     }
 
+    @Nullable
+    private ActivityRecord getTopActivityFromCameraTask() {
+        return mCameraTask != null
+                ? mCameraTask.getTopActivity(/* isFinishing */ false, /* includeOverlays */ false)
+                : null;
+    }
+
     private boolean isActivityForCameraIdRefreshing(@NonNull ActivityRecord topActivity,
             @NonNull String cameraId) {
         if (!isTreatmentEnabledForActivity(topActivity, /* checkOrientation= */ true)
-                || mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
+                || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
             return false;
         }
         return topActivity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
diff --git a/services/core/java/com/android/server/wm/CameraStateMonitor.java b/services/core/java/com/android/server/wm/CameraStateMonitor.java
index 3b6e30a..3aa3558 100644
--- a/services/core/java/com/android/server/wm/CameraStateMonitor.java
+++ b/services/core/java/com/android/server/wm/CameraStateMonitor.java
@@ -67,6 +67,10 @@
     // when camera connection is closed and we need to clean up our records.
     private final CameraIdPackageNameBiMapping mCameraIdPackageBiMapping =
             new CameraIdPackageNameBiMapping();
+    // TODO(b/380840084): Consider making this a set of CameraId/PackageName pairs. This is to
+    // keep track of camera-closed signals when apps are switching camera access, so that the policy
+    // can restore app configuration when an app closes camera (e.g. loses camera access due to
+    // another app).
     private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>();
 
     // TODO(b/336474959): should/can this go in the compat listeners?
@@ -163,15 +167,14 @@
             if (cameraActivity == null || cameraActivity.getTask() == null) {
                 return;
             }
-            notifyListenersCameraOpened(cameraActivity, cameraId);
+            notifyListenersCameraOpened(cameraActivity);
         }
     }
 
-    private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity,
-            @NonNull String cameraId) {
+    private void notifyListenersCameraOpened(@NonNull ActivityRecord cameraActivity) {
         for (int i = 0; i < mCameraStateListeners.size(); i++) {
             CameraCompatStateListener listener = mCameraStateListeners.get(i);
-            listener.onCameraOpened(cameraActivity, cameraId);
+            listener.onCameraOpened(cameraActivity);
         }
     }
 
@@ -224,11 +227,11 @@
                 // Already reconnected to this camera, no need to clean up.
                 return;
             }
-
-            final boolean closeSuccessfulForAllListeners = notifyListenersCameraClosed(cameraId);
-            if (closeSuccessfulForAllListeners) {
+            final boolean canClose = checkCanCloseForAllListeners(cameraId);
+            if (canClose) {
                 // Finish cleaning up.
                 mCameraIdPackageBiMapping.removeCameraId(cameraId);
+                notifyListenersCameraClosed();
             } else {
                 // Not ready to process closure yet - the camera activity might be refreshing.
                 // Try again later.
@@ -238,15 +241,21 @@
     }
 
     /**
-     * @return {@code false} if any listeners have reported issues processing the close.
+     * @return {@code false} if any listener has reported that they cannot process camera close now.
      */
-    private boolean notifyListenersCameraClosed(@NonNull String cameraId) {
-        boolean closeSuccessfulForAllListeners = true;
+    private boolean checkCanCloseForAllListeners(@NonNull String cameraId) {
         for (int i = 0; i < mCameraStateListeners.size(); i++) {
-            closeSuccessfulForAllListeners &= mCameraStateListeners.get(i).onCameraClosed(cameraId);
+            if (!mCameraStateListeners.get(i).canCameraBeClosed(cameraId)) {
+                return false;
+            }
         }
+        return true;
+    }
 
-        return closeSuccessfulForAllListeners;
+    private void notifyListenersCameraClosed() {
+        for (int i = 0; i < mCameraStateListeners.size(); i++) {
+            mCameraStateListeners.get(i).onCameraClosed();
+        }
     }
 
     // TODO(b/335165310): verify that this works in multi instance and permission dialogs.
@@ -297,14 +306,18 @@
         /**
          * Notifies the compat listener that an activity has opened camera.
          */
-        // TODO(b/336474959): try to decouple `cameraId` from the listeners.
-        void onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId);
+        void onCameraOpened(@NonNull ActivityRecord cameraActivity);
         /**
-         * Notifies the compat listener that camera is closed.
+         * Checks whether a listener is ready to do a cleanup when camera is closed.
          *
-         * @return true if cleanup has been successful - the notifier might try again if false.
+         * <p>The notifier might try again if false is returned.
          */
         // TODO(b/336474959): try to decouple `cameraId` from the listeners.
-        boolean onCameraClosed(@NonNull String cameraId);
+        boolean canCameraBeClosed(@NonNull String cameraId);
+
+        /**
+         * Notifies the compat listener that camera is closed.
+         */
+        void onCameraClosed();
     }
 }
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 0b5872b..93ccd74 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -32,6 +32,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.media.projection.IMediaProjectionManager;
+import android.media.projection.StopReason;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -226,6 +227,7 @@
                                 + "size %s",
                         mDisplayContent.getDisplayId(), recordedContentBounds,
                         recordedContentOrientation, surfaceSize);
+
                 updateMirroredSurface(mRecordedWindowContainer.getSyncTransaction(),
                         recordedContentBounds, surfaceSize);
             } else {
@@ -295,12 +297,12 @@
      * Ensure recording does not fall back to the display stack; ensure the recording is stopped
      * and the client notified by tearing down the virtual display.
      */
-    private void stopMediaProjection() {
+    private void stopMediaProjection(@StopReason int stopReason) {
         ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                 "Content Recording: Stop MediaProjection on virtual display %d",
                 mDisplayContent.getDisplayId());
         if (mMediaProjectionManager != null) {
-            mMediaProjectionManager.stopActiveProjection();
+            mMediaProjectionManager.stopActiveProjection(stopReason);
         }
     }
 
@@ -507,7 +509,7 @@
         if (shouldExitTaskRecording) {
             // Clean up the cached session first to ensure recording doesn't re-start, since
             // tearing down the display will generate display events which will trickle back here.
-            stopMediaProjection();
+            stopMediaProjection(StopReason.STOP_ERROR);
         }
     }
 
@@ -599,9 +601,13 @@
         mLastRecordedBounds = new Rect(recordedContentBounds);
         mLastConsumingSurfaceSize.x = surfaceSize.x;
         mLastConsumingSurfaceSize.y = surfaceSize.y;
-        // Request to notify the client about the resize.
-        mMediaProjectionManager.notifyActiveProjectionCapturedContentResized(
-                mLastRecordedBounds.width(), mLastRecordedBounds.height());
+
+        // Request to notify the client about the updated bounds.
+        mMediaProjectionManager.notifyCaptureBoundsChanged(
+                mContentRecordingSession.getContentToRecord(),
+                mContentRecordingSession.getTargetUid(),
+                mLastRecordedBounds
+        );
     }
 
     /**
@@ -641,7 +647,7 @@
         clearContentRecordingSession();
         // Clean up the cached session first to ensure recording doesn't re-start, since
         // tearing down the display will generate display events which will trickle back here.
-        stopMediaProjection();
+        stopMediaProjection(StopReason.STOP_TARGET_REMOVED);
     }
 
     // WindowContainerListener
@@ -674,10 +680,10 @@
     }
 
     @VisibleForTesting interface MediaProjectionManagerWrapper {
-        void stopActiveProjection();
-        void notifyActiveProjectionCapturedContentResized(int width, int height);
+        void stopActiveProjection(@StopReason int stopReason);
         void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible);
         void notifyWindowingModeChanged(int contentToRecord, int targetUid, int windowingMode);
+        void notifyCaptureBoundsChanged(int contentToRecord, int targetUid, Rect captureBounds);
     }
 
     private static final class RemoteMediaProjectionManagerWrapper implements
@@ -691,7 +697,7 @@
         }
 
         @Override
-        public void stopActiveProjection() {
+        public void stopActiveProjection(@StopReason int stopReason) {
             fetchMediaProjectionManager();
             if (mIMediaProjectionManager == null) {
                 return;
@@ -700,7 +706,7 @@
                 ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
                         "Content Recording: stopping active projection for display %d",
                         mDisplayId);
-                mIMediaProjectionManager.stopActiveProjection();
+                mIMediaProjectionManager.stopActiveProjection(stopReason);
             } catch (RemoteException e) {
                 ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
                         "Content Recording: Unable to tell MediaProjectionManagerService to stop "
@@ -710,23 +716,6 @@
         }
 
         @Override
-        public void notifyActiveProjectionCapturedContentResized(int width, int height) {
-            fetchMediaProjectionManager();
-            if (mIMediaProjectionManager == null) {
-                return;
-            }
-            try {
-                mIMediaProjectionManager.notifyActiveProjectionCapturedContentResized(width,
-                        height);
-            } catch (RemoteException e) {
-                ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
-                        "Content Recording: Unable to tell MediaProjectionManagerService about "
-                                + "resizing the active projection: %s",
-                        e);
-            }
-        }
-
-        @Override
         public void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible) {
             fetchMediaProjectionManager();
             if (mIMediaProjectionManager == null) {
@@ -759,6 +748,22 @@
             }
         }
 
+        @Override
+        public void notifyCaptureBoundsChanged(int contentToRecord, int targetUid,
+                Rect captureBounds) {
+            fetchMediaProjectionManager();
+            if (mIMediaProjectionManager == null) {
+                return;
+            }
+            try {
+                mIMediaProjectionManager.notifyCaptureBoundsChanged(
+                        contentToRecord, targetUid, captureBounds);
+            } catch (RemoteException e) {
+                ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+                        "Content Recording: Unable to tell log bounds change: %s", e);
+            }
+        }
+
         private void fetchMediaProjectionManager() {
             if (mIMediaProjectionManager != null) {
                 return;
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 6f8c17a..37e8f62 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -403,6 +403,7 @@
                 || first.renderFrameRate != second.renderFrameRate
                 || first.hasArrSupport != second.hasArrSupport
                 || !Objects.equals(first.frameRateCategoryRate, second.frameRateCategoryRate)
+                || !Arrays.equals(first.supportedRefreshRates, second.supportedRefreshRates)
                 || first.defaultModeId != second.defaultModeId
                 || first.userPreferredModeId != second.userPreferredModeId
                 || !Arrays.equals(first.supportedModes, second.supportedModes)
@@ -423,6 +424,7 @@
                 || first.brightnessMinimum != second.brightnessMinimum
                 || first.brightnessMaximum != second.brightnessMaximum
                 || first.brightnessDefault != second.brightnessDefault
+                || first.brightnessDim != second.brightnessDim
                 || first.installOrientation != second.installOrientation
                 || first.isForceSdr != second.isForceSdr
                 || !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate)
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index c8cb621..43855aa 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -196,10 +196,11 @@
 
         if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
                 && !AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord)) {
-            if (mActivityRecord.isUniversalResizeable()) {
+            final float minAspectRatio = info.getMinAspectRatio();
+            if (minAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) {
                 return 0;
             }
-            return info.getMinAspectRatio();
+            return minAspectRatio;
         }
 
         if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
@@ -242,10 +243,11 @@
         if (mTransparentPolicy.isRunning()) {
             return mTransparentPolicy.getInheritedMaxAspectRatio();
         }
-        if (mActivityRecord.isUniversalResizeable()) {
+        final float maxAspectRatio = mActivityRecord.info.getMaxAspectRatio();
+        if (maxAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) {
             return 0;
         }
-        return mActivityRecord.info.getMaxAspectRatio();
+        return maxAspectRatio;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e9e550e..e190963 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -791,6 +791,12 @@
     private WindowState mLastWakeLockHoldingWindow;
 
     /**
+     * Whether display is allowed to ignore all activity size restrictions.
+     * @see #isDisplayIgnoreActivitySizeRestrictions
+     */
+    private final boolean mIgnoreActivitySizeRestrictions;
+
+    /**
      * The helper of policy controller.
      *
      * @see DisplayWindowPolicyControllerHelper
@@ -1220,6 +1226,8 @@
 
         setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
+        mIgnoreActivitySizeRestrictions =
+                mWmService.mDisplayWindowSettings.isIgnoreActivitySizeRestrictionsLocked(this);
 
         // Sets the initial touch mode state.
         mInTouchMode = mWmService.mContext.getResources().getBoolean(
@@ -4267,7 +4275,8 @@
             return DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
         }
         final int imePolicy = mWmService.mDisplayWindowSettings.getImePolicyLocked(this);
-        if (imePolicy == DISPLAY_IME_POLICY_FALLBACK_DISPLAY && forceDesktopMode()) {
+        if (imePolicy == DISPLAY_IME_POLICY_FALLBACK_DISPLAY
+                && isPublicSecondaryDisplayWithDesktopModeForceEnabled()) {
             // If the display has not explicitly requested for the IME to be hidden then it shall
             // show the IME locally.
             return DISPLAY_IME_POLICY_LOCAL;
@@ -4275,10 +4284,6 @@
         return imePolicy;
     }
 
-    boolean forceDesktopMode() {
-        return mWmService.mForceDesktopModeOnExternalDisplays && !isDefaultDisplay && !isPrivate();
-    }
-
     /** @see WindowManagerInternal#onToggleImeRequested */
     void onShowImeRequested() {
         if (mInputMethodWindow == null) {
@@ -4871,7 +4876,7 @@
 
     /** @return {@code true} if there is window to wait before enabling the screen. */
     boolean shouldWaitForSystemDecorWindowsOnBoot() {
-        if (!isDefaultDisplay && !supportsSystemDecorations()) {
+        if (!isDefaultDisplay && !isSystemDecorationsSupported()) {
             // Nothing to wait because the secondary display doesn't support system decorations,
             // there is no wallpaper, keyguard (status bar) or application (home) window to show
             // during booting.
@@ -5502,14 +5507,18 @@
             // Attach the SystemUiContext to this DisplayContent the get latest configuration.
             // Note that the SystemUiContext will be removed automatically if this DisplayContent
             // is detached.
-            final WindowProcessController wpc = mAtmService.getProcessController(
-                    getDisplayUiContext().getIApplicationThread());
-            mWmService.mWindowContextListenerController.registerWindowContainerListener(
-                    wpc, getDisplayUiContext().getWindowContextToken(), this,
-                    INVALID_WINDOW_TYPE, null /* options */);
+            registerSystemUiContext();
         }
     }
 
+    private void registerSystemUiContext() {
+        final WindowProcessController wpc = mAtmService.getProcessController(
+                getDisplayUiContext().getIApplicationThread());
+        mWmService.mWindowContextListenerController.registerWindowContainerListener(
+                wpc, getDisplayUiContext().getWindowContextToken(), this,
+                INVALID_WINDOW_TYPE, null /* options */);
+    }
+
     @Override
     void assignChildLayers(SurfaceControl.Transaction t) {
         assignRelativeLayerForIme(t, false /* forceUpdate */);
@@ -5746,22 +5755,48 @@
     /**
      * @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
      */
-    boolean supportsSystemDecorations() {
-        boolean forceDesktopModeOnDisplay = forceDesktopMode();
-
-        if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) {
-            // System decorations should not be forced on a rear display due to security policies.
-            forceDesktopModeOnDisplay =
-                    forceDesktopModeOnDisplay && ((mDisplay.getFlags() & Display.FLAG_REAR) == 0);
+    boolean isSystemDecorationsSupported() {
+        if (mDisplayId == mWmService.mVr2dDisplayId) {
+            // VR virtual display will be used to run and render 2D app within a VR experience.
+            return false;
         }
+        if (!isTrusted()) {
+            // Do not show system decorations on untrusted virtual display.
+            return false;
+        }
+        if (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
+                || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) {
+            // This display is configured to show system decorations.
+            return true;
+        }
+        if (isPublicSecondaryDisplayWithDesktopModeForceEnabled()) {
+            if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) {
+                // System decorations should not be forced on a rear display due to security
+                // policies.
+                return (mDisplay.getFlags() & Display.FLAG_REAR) == 0;
+            }
+            // If the display is forced to desktop mode, treat it the same as it is configured to
+            // show system decorations.
+            return true;
+        }
+        return false;
+    }
 
-        return (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
-                || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
-                || forceDesktopModeOnDisplay)
-                // VR virtual display will be used to run and render 2D app within a VR experience.
-                && mDisplayId != mWmService.mVr2dDisplayId
-                // Do not show system decorations on untrusted virtual display.
-                && isTrusted();
+    /**
+     * This is the development option to force enable desktop mode on all secondary public displays
+     * that are not owned by a virtual device.
+     * When this is enabled, it also force enable system decorations on those displays.
+     *
+     * If we need a per-display config to enable desktop mode for production, that config should
+     * also check {@link #isSystemDecorationsSupported()} to avoid breaking any security policy.
+     */
+    boolean isPublicSecondaryDisplayWithDesktopModeForceEnabled() {
+        if (!mWmService.mForceDesktopModeOnExternalDisplays || isDefaultDisplay || isPrivate()) {
+            return false;
+        }
+        // Desktop mode is not supported on virtual devices.
+        int deviceId = mRootWindowContainer.mTaskSupervisor.getDeviceIdForDisplayId(mDisplayId);
+        return deviceId == Context.DEVICE_ID_DEFAULT;
     }
 
     /**
@@ -5772,7 +5807,7 @@
      */
     boolean isHomeSupported() {
         return (mWmService.mDisplayWindowSettings.isHomeSupportedLocked(this) && isTrusted())
-                || supportsSystemDecorations();
+                || isSystemDecorationsSupported();
     }
 
     /**
@@ -5783,7 +5818,7 @@
      * {@link VirtualDisplayConfig.Builder#setIgnoreActivitySizeRestrictions}.</p>
      */
     boolean isDisplayIgnoreActivitySizeRestrictions() {
-        return mWmService.mDisplayWindowSettings.isIgnoreActivitySizeRestrictionsLocked(this);
+        return mIgnoreActivitySizeRestrictions;
     }
 
     /**
@@ -7057,12 +7092,15 @@
         }
 
         @Override
-        public void setImeInputTargetRequestedVisibility(boolean visible) {
+        public void setImeInputTargetRequestedVisibility(boolean visible,
+                @NonNull ImeTracker.Token statsToken) {
             if (android.view.inputmethod.Flags.refactorInsetsController()) {
                 // TODO(b/353463205) we won't have the statsToken in all cases, but should still log
                 try {
-                    mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible);
+                    mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible,
+                            statsToken);
                 } catch (RemoteException e) {
+                    // TODO(b/353463205) fail statsToken
                     Slog.w(TAG, "Failed to deliver setImeInputTargetRequestedVisibility", e);
                 }
             }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 76e8a70..659bb67 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -659,7 +659,7 @@
             }
         } else {
             mHasStatusBar = false;
-            mHasNavigationBar = mDisplayContent.supportsSystemDecorations();
+            mHasNavigationBar = mDisplayContent.isSystemDecorationsSupported();
         }
 
         mRefreshRatePolicy = new RefreshRatePolicy(mService,
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 4cf1fb4..f53bc70 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -295,7 +295,7 @@
                 && mDeviceStateController
                         .shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()) {
             mDisplayRotationCoordinator.setDefaultDisplayRotationChangedCallback(
-                    mDefaultDisplayRotationChangedCallback);
+                    displayContent.getDisplayId(), mDefaultDisplayRotationChangedCallback);
         }
 
         if (isDefaultDisplay) {
@@ -445,7 +445,8 @@
         final boolean isTv = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_LEANBACK);
         mDefaultFixedToUserRotation =
-                (isCar || isTv || mService.mIsPc || mDisplayContent.forceDesktopMode()
+                (isCar || isTv || mService.mIsPc
+                        || mDisplayContent.isPublicSecondaryDisplayWithDesktopModeForceEnabled()
                         || !mDisplayContent.shouldRotateWithContent())
                 // For debug purposes the next line turns this feature off with:
                 // $ adb shell setprop config.override_forced_orient true
@@ -485,6 +486,9 @@
             if (isDefaultDisplay) {
                 updateOrientationListenerLw();
             }
+        } else if (mCompatPolicyForImmersiveApps != null
+                && mCompatPolicyForImmersiveApps.deferOrientationUpdate()) {
+            return false;
         }
         return updateRotationUnchecked(forceUpdate);
     }
@@ -1656,7 +1660,8 @@
 
     void removeDefaultDisplayRotationChangedCallback() {
         if (DisplayRotationCoordinator.isSecondaryInternalDisplay(mDisplayContent)) {
-            mDisplayRotationCoordinator.removeDefaultDisplayRotationChangedCallback();
+            mDisplayRotationCoordinator.removeDefaultDisplayRotationChangedCallback(
+                    mDefaultDisplayRotationChangedCallback);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 0ccc0fe..3c199db 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -71,6 +71,9 @@
     @NonNull
     private final ActivityRefresher mActivityRefresher;
 
+    // TODO(b/380840084): Consider moving this to the CameraStateMonitor, and keeping track of
+    // all current camera activities, especially when the camera access is switching from one app to
+    // another.
     @Nullable
     private Task mCameraTask;
 
@@ -327,8 +330,7 @@
     }
 
     @Override
-    public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
-            @NonNull String cameraId) {
+    public void onCameraOpened(@NonNull ActivityRecord cameraActivity) {
         mCameraTask = cameraActivity.getTask();
         // Checking whether an activity in fullscreen rather than the task as this camera
         // compat treatment doesn't cover activity embedding.
@@ -374,16 +376,9 @@
     }
 
     @Override
-    public boolean onCameraClosed(@NonNull String cameraId) {
-        final ActivityRecord topActivity;
-        if (Flags.cameraCompatFullscreenPickSameTaskActivity()) {
-            topActivity = mCameraTask != null ? mCameraTask.getTopActivity(
-                    /* includeFinishing= */ true, /* includeOverlays= */ false) : null;
-        } else {
-            topActivity = mDisplayContent.topRunningActivity(/* considerKeyguardState= */ true);
-        }
+    public boolean canCameraBeClosed(@NonNull String cameraId) {
+        final ActivityRecord topActivity = getTopActivity();
 
-        mCameraTask = null;
         if (topActivity == null) {
             return true;
         }
@@ -399,6 +394,23 @@
                 return false;
             }
         }
+        return true;
+    }
+
+    @Override
+    public void onCameraClosed() {
+        final ActivityRecord topActivity = getTopActivity();
+
+        // Only clean up if the camera is not running - this close signal could be from switching
+        // cameras (e.g. back to front camera, and vice versa).
+        if (topActivity == null || !mCameraStateMonitor.isCameraRunningForActivity(topActivity)) {
+            // Call after getTopActivity(), as that method might use the activity from mCameraTask.
+            mCameraTask = null;
+        }
+
+        if (topActivity == null) {
+            return;
+        }
 
         ProtoLog.v(WM_DEBUG_ORIENTATION,
                 "Display id=%d is notified that Camera is closed, updating rotation.",
@@ -406,11 +418,10 @@
         // Checking whether an activity in fullscreen rather than the task as this camera compat
         // treatment doesn't cover activity embedding.
         if (topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
-            return true;
+            return;
         }
         recomputeConfigurationForCameraCompatIfNeeded(topActivity);
         mDisplayContent.updateOrientation();
-        return true;
     }
 
     // TODO(b/336474959): Do we need cameraId here?
@@ -430,6 +441,16 @@
         }
     }
 
+    @Nullable
+    private ActivityRecord getTopActivity() {
+        if (Flags.cameraCompatFullscreenPickSameTaskActivity()) {
+            return mCameraTask != null ? mCameraTask.getTopActivity(
+                    /* includeFinishing= */ true, /* includeOverlays= */ false) : null;
+        } else {
+            return mDisplayContent.topRunningActivity(/* considerKeyguardState= */ true);
+        }
+    }
+
     /**
      * @return {@code true} if the configuration needs to be recomputed after a camera state update.
      */
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java b/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
index ae3787c..01e1b13 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.util.Slog;
 import android.view.Display;
 import android.view.Surface;
 
@@ -40,6 +41,7 @@
     @Nullable
     @VisibleForTesting
     Runnable mDefaultDisplayRotationChangedCallback;
+    private int mCallbackDisplayId = Display.INVALID_DISPLAY;
 
     @Surface.Rotation
     private int mDefaultDisplayCurrentRotation;
@@ -68,12 +70,15 @@
      * Register a callback to be notified when the default display's rotation changes. Clients can
      * query the default display's current rotation via {@link #getDefaultDisplayCurrentRotation()}.
      */
-    void setDefaultDisplayRotationChangedCallback(@NonNull Runnable callback) {
-        if (mDefaultDisplayRotationChangedCallback != null) {
-            throw new UnsupportedOperationException("Multiple clients unsupported");
+    void setDefaultDisplayRotationChangedCallback(int displayId, @NonNull Runnable callback) {
+        if (mDefaultDisplayRotationChangedCallback != null && displayId != mCallbackDisplayId) {
+            throw new UnsupportedOperationException("Multiple clients unsupported"
+                    + ". Incoming displayId: " + displayId
+                    + ", existing displayId: " + mCallbackDisplayId);
         }
 
         mDefaultDisplayRotationChangedCallback = callback;
+        mCallbackDisplayId = displayId;
 
         if (mDefaultDisplayCurrentRotation != mDefaultDisplayDefaultRotation) {
             callback.run();
@@ -82,10 +87,17 @@
 
     /**
      * Removes the callback that was added via
-     * {@link #setDefaultDisplayRotationChangedCallback(Runnable)}.
+     * {@link #setDefaultDisplayRotationChangedCallback(int, Runnable)}.
      */
-    void removeDefaultDisplayRotationChangedCallback() {
+    void removeDefaultDisplayRotationChangedCallback(@NonNull Runnable callback) {
+        if (callback != mDefaultDisplayRotationChangedCallback) {
+            Slog.w(TAG, "Attempted to remove non-matching callback."
+                    + " DisplayId: " + mCallbackDisplayId);
+            return;
+        }
+
         mDefaultDisplayRotationChangedCallback = null;
+        mCallbackDisplayId = Display.INVALID_DISPLAY;
     }
 
     static boolean isSecondaryInternalDisplay(@NonNull DisplayContent displayContent) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java
index 094434d..046ed61 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java
@@ -17,10 +17,13 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 
+import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.Configuration.Orientation;
@@ -66,6 +69,37 @@
     }
 
     /**
+     * Returns {@code true} if the orientation update should be skipped and it will update when
+     * transition is done. This is to keep the orientation which was preserved by
+     * {@link #isRotationLockEnforced} from being changed by a transient launch (i.e. recents).
+     */
+    boolean deferOrientationUpdate() {
+        if (mDisplayRotation.getUserRotation() != USER_ROTATION_FREE
+                || mDisplayRotation.getLastOrientation() != SCREEN_ORIENTATION_UNSPECIFIED) {
+            return false;
+        }
+        final WindowOrientationListener orientationListener =
+                mDisplayRotation.getOrientationListener();
+        if (orientationListener == null
+                || orientationListener.getProposedRotation() == mDisplayRotation.getRotation()) {
+            return false;
+        }
+        // The above conditions mean that isRotationLockEnforced might have taken effect:
+        // Auto-rotation is enabled and the proposed rotation is not applied.
+        // Then the update should defer until the transition idle to avoid disturbing animation.
+        if (!mDisplayContent.mTransitionController.hasTransientLaunch(mDisplayContent)) {
+            return false;
+        }
+        mDisplayContent.mTransitionController.mStateValidators.add(() -> {
+            if (!isRotationLockEnforcedLocked(orientationListener.getProposedRotation())) {
+                mDisplayContent.mWmService.updateRotation(false /* alwaysSendConfiguration */,
+                        false /* forceRelayout */);
+            }
+        });
+        return true;
+    }
+
+    /**
      * Decides whether it is necessary to lock screen rotation, preventing auto rotation, based on
      * the top activity configuration and proposed screen rotation.
      *
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index c87b811..f6d05d0 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -143,7 +143,7 @@
         }
         // No record is present so use default windowing mode policy.
         final boolean forceFreeForm = mService.mAtmService.mSupportsFreeformWindowManagement
-                && (mService.mIsPc || dc.forceDesktopMode());
+                && (mService.mIsPc || dc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
         if (forceFreeForm) {
             return WindowConfiguration.WINDOWING_MODE_FREEFORM;
         }
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 49f717e..4230cd8 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -40,6 +40,7 @@
 import android.view.inputmethod.ImeTracker;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.protolog.ProtoLog;
 
 import java.io.PrintWriter;
@@ -285,7 +286,12 @@
                 if (isImeInputTarget(caller)) {
                     reportImeInputTargetStateToControlTarget(caller, controlTarget, statsToken);
                 } else {
-                    // TODO(b/353463205) add ImeTracker?
+                    ProtoLog.w(WM_DEBUG_IME,
+                            "Tried to update client visibility for non-IME input target %s "
+                                    + "(current target: %s)",
+                            caller, mDisplayContent.getImeInputTarget());
+                    ImeTracker.forLogging().onFailed(statsToken,
+                            ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
                 }
             }
             return false;
@@ -316,15 +322,21 @@
         if (Flags.refactorInsetsController() && target != null) {
             InsetsControlTarget imeControlTarget = getControlTarget();
             if (target != imeControlTarget) {
-                // TODO(b/353463205): start new request here?
+                // TODO(b/353463205): check if fromUser=false is correct here
+                boolean imeVisible = target.isRequestedVisible(WindowInsets.Type.ime());
+                ImeTracker.Token statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+                        ImeTracker.ORIGIN_SERVER,
+                        imeVisible ? SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED
+                                : SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED,
+                        false /* fromUser */);
                 reportImeInputTargetStateToControlTarget(target, imeControlTarget,
-                        null /* statsToken */);
+                        statsToken);
             }
         }
     }
 
     private void reportImeInputTargetStateToControlTarget(@NonNull InsetsTarget imeInsetsTarget,
-            InsetsControlTarget controlTarget, @Nullable ImeTracker.Token statsToken) {
+            InsetsControlTarget controlTarget, @NonNull ImeTracker.Token statsToken) {
         // In case of the multi window mode, update the requestedVisibleTypes from
         // the controlTarget (=RemoteInsetsControlTarget) via DisplayImeController.
         // Then, trigger onRequestedVisibleTypesChanged for the controlTarget with
@@ -333,7 +345,7 @@
         if (controlTarget != null) {
             ImeTracker.forLogging().onProgress(statsToken,
                     ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
-            controlTarget.setImeInputTargetRequestedVisibility(imeVisible);
+            controlTarget.setImeInputTargetRequestedVisibility(imeVisible, statsToken);
         } else if (imeInsetsTarget instanceof InsetsControlTarget) {
             // In case of a virtual display that cannot show the IME, the
             // controlTarget will be null here, as no controlTarget was set yet. In
@@ -345,13 +357,16 @@
             if (controlTarget != imeInsetsTarget) {
                 ImeTracker.forLogging().onProgress(statsToken,
                         ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
-                controlTarget.setImeInputTargetRequestedVisibility(imeVisible);
+                controlTarget.setImeInputTargetRequestedVisibility(imeVisible, statsToken);
+                // not all virtual displays have an ImeInsetsSourceProvider, so it is not
+                // guaranteed that the IME will be started when the control target reports its
+                // requested visibility back. Thus, invoking the listener here.
+                invokeOnImeRequestedChangedListener(imeInsetsTarget, statsToken);
             } else {
                 ImeTracker.forLogging().onFailed(statsToken,
                         ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
             }
         }
-        invokeOnImeRequestedChangedListener(imeInsetsTarget, statsToken);
     }
 
     // TODO(b/353463205) check callers to see if we can make statsToken @NonNull
@@ -387,9 +402,9 @@
         WindowToken imeToken = mWindowContainer.asWindowState() != null
                 ? mWindowContainer.asWindowState().mToken : null;
         final var rotationController = mDisplayContent.getAsyncRotationController();
-        if ((rotationController != null && rotationController.isTargetToken(imeToken))
-                || (imeToken != null && imeToken.isSelfAnimating(
-                        0 /* flags */, SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM))) {
+        if ((rotationController != null && rotationController.isTargetToken(imeToken)) || (
+                imeToken != null && imeToken.isSelfAnimating(0 /* flags */,
+                        SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM))) {
             // Skip reporting IME drawn state when the control target is in fixed
             // rotation, AsyncRotationController will report after the animation finished.
             return;
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index 7043aacf..cee4967 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
@@ -90,8 +91,10 @@
     /**
      * @param visible the requested visibility for the IME, used for
      * {@link com.android.server.wm.DisplayContent.RemoteInsetsControlTarget}
+     * @param statsToken the token tracking the current IME request
      */
-    default void setImeInputTargetRequestedVisibility(boolean visible) {
+    default void setImeInputTargetRequestedVisibility(boolean visible,
+            @NonNull ImeTracker.Token statsToken) {
     }
 
     /** Returns {@code target.getWindow()}, or null if {@code target} is {@code null}. */
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 2401f90..98521d3 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -19,6 +19,8 @@
 wilsonshih@google.com
 jiamingliu@google.com
 pdwilliams@google.com
+charlesccchen@google.com
+marziana@google.com
 
 # Files related to background activity launches
 per-file Background*Start* = set noparent
diff --git a/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.java
new file mode 100644
index 0000000..8c50913
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PageSizeMismatchDialog.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.wm;
+
+import static android.text.Html.FROM_HTML_MODE_COMPACT;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.text.Html;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+
+/**
+ * Show warning dialog when
+ * - Uncompressed libs inside apk are not aligned to page size
+ * - ELF Load segments are not page size aligned
+ * This dialog will be shown everytime when app is launched. Apps can choose to override
+ * by setting compat mode pageSizeCompat="enabled" in manifest or "disabled" to opt out.
+ * Both cases will skip the PageSizeMismatchDialog.
+ *
+ */
+class PageSizeMismatchDialog extends AppWarnings.BaseDialog {
+    PageSizeMismatchDialog(
+            final AppWarnings manager,
+            Context context,
+            ApplicationInfo appInfo,
+            int userId,
+            String warning) {
+        super(manager, context, appInfo.packageName, userId);
+
+        final PackageManager pm = context.getPackageManager();
+        final CharSequence label =
+                appInfo.loadSafeLabel(
+                        pm,
+                        PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
+                        PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE
+                                | PackageItemInfo.SAFE_LABEL_FLAG_TRIM);
+
+        final AlertDialog.Builder builder =
+                new AlertDialog.Builder(context)
+                        .setPositiveButton(
+                                R.string.ok,
+                                (dialog, which) -> {/* Do nothing */})
+                        .setMessage(Html.fromHtml(warning, FROM_HTML_MODE_COMPACT))
+                        .setTitle(label);
+
+        mDialog = builder.create();
+        mDialog.create();
+
+        final Window window = mDialog.getWindow();
+        window.setType(WindowManager.LayoutParams.TYPE_PHONE);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index f0e12fe..27f82d9 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -495,11 +495,18 @@
         mTaskNotificationController.notifyTaskListUpdated();
     }
 
-    private void notifyTaskRemoved(Task task, boolean wasTrimmed, boolean killProcess) {
+    private void notifyTaskRemoved(Task task, boolean wasTrimmed, boolean killProcess,
+            boolean removedForAddTask) {
         for (int i = 0; i < mCallbacks.size(); i++) {
             mCallbacks.get(i).onRecentTaskRemoved(task, wasTrimmed, killProcess);
         }
         mTaskNotificationController.notifyTaskListUpdated();
+        if (removedForAddTask) {
+            mTaskNotificationController.notifyRecentTaskRemovedForAddTask(task.mTaskId);
+        }
+    }
+    private void notifyTaskRemoved(Task task, boolean wasTrimmed, boolean killProcess) {
+        notifyTaskRemoved(task, wasTrimmed, killProcess, false /* removedForAddTask */);
     }
 
     /**
@@ -1635,7 +1642,8 @@
                 // from becoming dangling.
                 mHiddenTasks.add(0, removedTask);
             }
-            notifyTaskRemoved(removedTask, false /* wasTrimmed */, false /* killProcess */);
+            notifyTaskRemoved(removedTask, false /* wasTrimmed */, false /* killProcess */,
+                    true /* removedForAddTask */);
             if (DEBUG_RECENTS_TRIM_TASKS) {
                 Slog.d(TAG, "Trimming task=" + removedTask
                         + " for addition of task=" + task);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c89feb4..46312af 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2853,11 +2853,9 @@
     }
 
     void prepareForShutdown() {
+        mWindowManager.mSnapshotController.mTaskSnapshotController.prepareShutdown();
         for (int i = 0; i < getChildCount(); i++) {
-            final int displayId = getChildAt(i).mDisplayId;
-            mWindowManager.mSnapshotController.mTaskSnapshotController
-                    .snapshotForShutdown(displayId);
-            createSleepToken("shutdown", displayId);
+            createSleepToken("shutdown", getChildAt(i).mDisplayId);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index 70b214c..88e5343 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -361,10 +361,13 @@
         }
 
         // If launched from bubble is specified, then ensure that the caller is system or sysui.
-        if (options.getLaunchedFromBubble() && !isSystemOrSystemUI(callingPid, callingUid)) {
+        if ((options.getLaunchedFromBubble() || options.getTaskAlwaysOnTop())
+                && !isSystemOrSystemUI(callingPid, callingUid)) {
             final String msg = "Permission Denial: starting " + getIntentString(intent)
                     + " from " + callerApp + " (pid=" + callingPid
-                    + ", uid=" + callingUid + ") with launchedFromBubble=true";
+                    + ", uid=" + callingUid + ") with"
+                    + (options.getLaunchedFromBubble() ? " launchedFromBubble=true" : "")
+                    + (options.getTaskAlwaysOnTop() ? " taskAlwaysOnTop=true" : "");
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index bd8e8f4..8b63ecf7 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -103,12 +103,42 @@
     }
 
     /**
-     * Write out everything in the queue because of shutdown.
+     * Prepare to enqueue all visible task snapshots because of shutdown.
      */
-    void shutdown() {
+    void prepareShutdown() {
         synchronized (mLock) {
             mShutdown = true;
-            mLock.notifyAll();
+        }
+    }
+
+    private boolean isQueueEmpty() {
+        synchronized (mLock) {
+            return mWriteQueue.isEmpty() || mQueueIdling || mPaused;
+        }
+    }
+
+    void waitFlush(long timeout) {
+        if (timeout <= 0) {
+            return;
+        }
+        final long endTime = System.currentTimeMillis() + timeout;
+        while (true) {
+            if (!isQueueEmpty()) {
+                long timeRemaining = endTime - System.currentTimeMillis();
+                if (timeRemaining > 0) {
+                    synchronized (mLock) {
+                        try {
+                            mLock.wait(timeRemaining);
+                        } catch (InterruptedException e) {
+                        }
+                    }
+                } else {
+                    Slog.w(TAG, "Snapshot Persist Queue flush timed out");
+                    break;
+                }
+            } else {
+                break;
+            }
         }
     }
 
@@ -139,7 +169,9 @@
             mWriteQueue.addLast(item);
         }
         item.onQueuedLocked();
-        ensureStoreQueueDepthLocked();
+        if (!mShutdown) {
+            ensureStoreQueueDepthLocked();
+        }
         if (!mPaused) {
             mLock.notifyAll();
         }
@@ -213,6 +245,9 @@
                     if (!writeQueueEmpty && !mPaused) {
                         continue;
                     }
+                    if (mShutdown && writeQueueEmpty) {
+                        mLock.notifyAll();
+                    }
                     try {
                         mQueueIdling = writeQueueEmpty;
                         mLock.wait();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 87e472a..f090ef1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -6292,12 +6292,6 @@
         return mAnimatingActivityRegistry;
     }
 
-    @Override
-    void executeAppTransition(ActivityOptions options) {
-        mDisplayContent.executeAppTransition();
-        ActivityOptions.abort(options);
-    }
-
     private Rect getRawBounds() {
         return super.getBounds();
     }
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 586f3c3..c3649fe 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -62,6 +62,8 @@
     private static final int NOTIFY_TASK_MOVED_TO_BACK_LISTENERS_MSG = 27;
     private static final int NOTIFY_LOCK_TASK_MODE_CHANGED_MSG = 28;
     private static final int NOTIFY_TASK_SNAPSHOT_INVALIDATED_LISTENERS_MSG = 29;
+    private static final int NOTIFY_RECENT_TASK_REMOVED_FOR_ADD_TASK_LISTENERS_MSG = 30;
+
 
     // Delay in notifying task stack change listeners (in millis)
     private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -167,6 +169,10 @@
         l.onRecentTaskListFrozenChanged(m.arg1 != 0);
     };
 
+    private final TaskStackConsumer mNotifyRecentTaskRemovedForAddTask = (l, m) -> {
+        l.onRecentTaskRemovedForAddTask(m.arg1);
+    };
+
     private final TaskStackConsumer mNotifyTaskFocusChanged = (l, m) -> {
         l.onTaskFocusChanged(m.arg1, m.arg2 != 0);
     };
@@ -261,6 +267,9 @@
                 case NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG:
                     forAllRemoteListeners(mNotifyTaskListFrozen, msg);
                     break;
+                case NOTIFY_RECENT_TASK_REMOVED_FOR_ADD_TASK_LISTENERS_MSG:
+                    forAllRemoteListeners(mNotifyRecentTaskRemovedForAddTask, msg);
+                    break;
                 case NOTIFY_TASK_FOCUS_CHANGED_MSG:
                     forAllRemoteListeners(mNotifyTaskFocusChanged, msg);
                     break;
@@ -541,6 +550,15 @@
         msg.sendToTarget();
     }
 
+    /** Called when a task is removed from the recent tasks list. */
+    void notifyRecentTaskRemovedForAddTask(int taskId) {
+        final Message msg = mHandler.obtainMessage(
+                NOTIFY_RECENT_TASK_REMOVED_FOR_ADD_TASK_LISTENERS_MSG, taskId,
+                0 /* unused */);
+        forAllLocalListeners(mNotifyRecentTaskRemovedForAddTask, msg);
+        msg.sendToTarget();
+    }
+
     /** @see ITaskStackListener#onTaskFocusChanged(int, boolean) */
     void notifyTaskFocusChanged(int taskId, boolean focused) {
         final Message msg = mHandler.obtainMessage(NOTIFY_TASK_FOCUS_CHANGED_MSG,
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index e090b19..51b8bd1 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2129,7 +2129,8 @@
     }
 
     void executeAppTransition(ActivityOptions options) {
-        // No app transition applied to the task fragment.
+        mDisplayContent.executeAppTransition();
+        ActivityOptions.abort(options);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 9fe3f756..c130931 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -309,23 +309,31 @@
     /**
      * Record task snapshots before shutdown.
      */
-    void snapshotForShutdown(int displayId) {
+    void prepareShutdown() {
         if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
             return;
         }
-        final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
-        if (displayContent == null) {
+        // Make write items run in a batch.
+        mPersister.mSnapshotPersistQueue.setPaused(true);
+        mPersister.mSnapshotPersistQueue.prepareShutdown();
+        for (int i = 0; i < mService.mRoot.getChildCount(); i++) {
+            mService.mRoot.getChildAt(i).forAllLeafTasks(task -> {
+                if (task.isVisible() && !task.isActivityTypeHome()) {
+                    final TaskSnapshot snapshot = captureSnapshot(task);
+                    if (snapshot != null) {
+                        mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+                    }
+                }
+            }, true /* traverseTopToBottom */);
+        }
+        mPersister.mSnapshotPersistQueue.setPaused(false);
+    }
+
+    void waitFlush(long timeout) {
+        if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
             return;
         }
-        displayContent.forAllLeafTasks(task -> {
-            if (task.isVisible() && !task.isActivityTypeHome()) {
-                final TaskSnapshot snapshot = captureSnapshot(task);
-                if (snapshot != null) {
-                    mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
-                }
-            }
-        }, true /* traverseTopToBottom */);
-        mPersister.mSnapshotPersistQueue.shutdown();
+        mPersister.mSnapshotPersistQueue.waitFlush(timeout);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 20481f2..1fc609b7 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1429,7 +1429,16 @@
         // Commit wallpaper visibility after activity, because usually the wallpaper target token is
         // an activity, and wallpaper's visibility depends on activity's visibility.
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
-            final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
+            final WindowContainer<?> wc = mParticipants.valueAt(i);
+            WallpaperWindowToken wt = wc.asWallpaperToken();
+            if (!Flags.ensureWallpaperInTransitions()) {
+                if (wt == null) {
+                    final WindowState windowState = wc.asWindowState();
+                    if (windowState != null) {
+                        wt = windowState.mToken.asWallpaperToken();
+                    }
+                }
+            }
             if (wt == null) continue;
             final WindowState target = wt.mDisplayContent.mWallpaperController.getWallpaperTarget();
             final boolean isTargetInvisible = target == null || !target.mToken.isVisible();
diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java
index 31ca24c..3ad9b62 100644
--- a/services/core/java/com/android/server/wm/WindowManagerConstants.java
+++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java
@@ -36,7 +36,15 @@
  */
 final class WindowManagerConstants {
 
-    /** The orientation of activity will be always "unspecified" except for game apps. */
+    /**
+     * The orientation of activity will be always "unspecified" except for game apps.
+     * <p>Possible values:
+     * <ul>
+     * <li>false: applies to no apps (default)</li>
+     * <li>true: applies to all apps</li>
+     * <li>large: applies to all apps but only on large screens</li>
+     * </ul>
+     */
     private static final String KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST =
             "ignore_activity_orientation_request";
 
@@ -69,7 +77,8 @@
     boolean mSystemGestureExcludedByPreQStickyImmersive;
 
     /** @see #KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST */
-    boolean mIgnoreActivityOrientationRequest;
+    boolean mIgnoreActivityOrientationRequestLargeScreen;
+    boolean mIgnoreActivityOrientationRequestSmallScreen;
 
     /** @see #KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST */
     private ArraySet<String> mOptOutIgnoreActivityOrientationRequestPackages;
@@ -177,9 +186,12 @@
     }
 
     private void updateIgnoreActivityOrientationRequest() {
-        mIgnoreActivityOrientationRequest = mDeviceConfig.getBoolean(
+        final String value = mDeviceConfig.getProperty(
                 DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-                KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST, false);
+                KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST);
+        mIgnoreActivityOrientationRequestSmallScreen = Boolean.parseBoolean(value);
+        mIgnoreActivityOrientationRequestLargeScreen = mIgnoreActivityOrientationRequestSmallScreen
+                || ("large".equals(value));
     }
 
     private void updateOptOutIgnoreActivityOrientationRequestList() {
@@ -196,8 +208,7 @@
     }
 
     boolean isPackageOptOutIgnoreActivityOrientationRequest(String packageName) {
-        return mIgnoreActivityOrientationRequest
-                && mOptOutIgnoreActivityOrientationRequestPackages != null
+        return mOptOutIgnoreActivityOrientationRequestPackages != null
                 && mOptOutIgnoreActivityOrientationRequestPackages.contains(packageName);
     }
 
@@ -211,7 +222,8 @@
         pw.print("  "); pw.print(KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE);
         pw.print("="); pw.println(mSystemGestureExcludedByPreQStickyImmersive);
         pw.print("  "); pw.print(KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST);
-        pw.print("="); pw.println(mIgnoreActivityOrientationRequest);
+        pw.print("="); pw.println(mIgnoreActivityOrientationRequestSmallScreen ? "true"
+                : mIgnoreActivityOrientationRequestLargeScreen ? "large" : "false");
         if (mOptOutIgnoreActivityOrientationRequestPackages != null) {
             pw.print("  "); pw.print(KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST);
             pw.print("="); pw.println(mOptOutIgnoreActivityOrientationRequestPackages);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8268cae..00ade80 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -69,6 +69,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY;
 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
@@ -101,6 +102,7 @@
 import static android.view.flags.Flags.sensitiveContentAppProtection;
 import static android.window.WindowProviderService.isWindowProviderService;
 
+import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BOOT;
@@ -157,9 +159,9 @@
 import static com.android.server.wm.WindowManagerServiceDumpProto.POLICY;
 import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CONTAINER;
 import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
+import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
 import static com.android.window.flags.Flags.multiCrop;
 import static com.android.window.flags.Flags.setScPropertiesInClient;
-import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
 
 import android.Manifest;
 import android.Manifest.permission;
@@ -3030,8 +3032,8 @@
 
                 mWindowContextListenerController.unregisterWindowContainerListener(clientToken);
 
-                final WindowToken token = wc.asWindowToken();
-                if (token != null && token.isFromClient()) {
+                final WindowToken token = wc != null ? wc.asWindowToken() : null;
+                if (token != null && token.isFromClient() && token.getDisplayContent() != null) {
                     removeWindowToken(token.token, token.getDisplayContent().getDisplayId());
                 }
             }
@@ -4668,21 +4670,25 @@
 
     @EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
     @Override
-    public void updateDisplayWindowRequestedVisibleTypes(
-            int displayId, @InsetsType int requestedVisibleTypes) {
+    public void updateDisplayWindowRequestedVisibleTypes(int displayId,
+            @InsetsType int requestedVisibleTypes, @Nullable ImeTracker.Token statsToken) {
         updateDisplayWindowRequestedVisibleTypes_enforcePermission();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
                 final DisplayContent dc = mRoot.getDisplayContent(displayId);
                 if (dc == null || dc.mRemoteInsetsControlTarget == null) {
+                    ImeTracker.forLogging().onFailed(statsToken,
+                            ImeTracker.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES);
                     return;
                 }
+                ImeTracker.forLogging().onProgress(statsToken,
+                        ImeTracker.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES);
                 dc.mRemoteInsetsControlTarget.setRequestedVisibleTypes(requestedVisibleTypes);
                 // TODO(b/353463205) the statsToken shouldn't be null as it is used later in the
-                //  IME provider. Check if we have to create a new request here
+                //  IME provider. Check if we have to create a new request here, if null.
                 dc.getInsetsStateController().onRequestedVisibleTypesChanged(
-                        dc.mRemoteInsetsControlTarget, null /* statsToken */);
+                        dc.mRemoteInsetsControlTarget, statsToken);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -7696,7 +7702,7 @@
                         + "not exist: %d", displayId);
                 return false;
             }
-            return displayContent.supportsSystemDecorations();
+            return displayContent.isSystemDecorationsSupported();
         }
     }
 
@@ -7943,43 +7949,46 @@
         @Override
         public void waitForAllWindowsDrawn(Message message, long timeout, int displayId) {
             Objects.requireNonNull(message.getTarget());
-            final WindowContainer<?> container = displayId == INVALID_DISPLAY
-                    ? mRoot : mRoot.getDisplayContent(displayId);
-            if (container == null) {
-                // The waiting container doesn't exist, no need to wait to run the callback. Run and
-                // return;
-                message.sendToTarget();
-                return;
-            }
             boolean allWindowsDrawn = false;
             synchronized (mGlobalLock) {
-                if (displayId == INVALID_DISPLAY
-                        && mRoot.getDefaultDisplay().mDisplayUpdater.waitForTransition(message)) {
-                    // Use the ready-to-play of transition as the signal.
-                    return;
-                }
-                container.waitForAllWindowsDrawn();
-                mWindowPlacerLocked.requestTraversal();
-                mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, container);
-                if (container.mWaitingForDrawn.isEmpty()) {
-                    allWindowsDrawn = true;
-                } else {
-                    if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
-                        for (int i = 0; i < container.mWaitingForDrawn.size(); i++) {
-                            traceStartWaitingForWindowDrawn(container.mWaitingForDrawn.get(i));
-                        }
-                    }
-
-                    mWaitingForDrawnCallbacks.put(container, message);
-                    mH.sendNewMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, container, timeout);
-                    checkDrawnWindowsLocked();
-                }
+                allWindowsDrawn = waitForAllWindowsDrawnLocked(message, timeout, displayId);
             }
             if (allWindowsDrawn) {
                 message.sendToTarget();
             }
         }
 
+        /** Return {@code true} if all windows have been drawn. */
+        private boolean waitForAllWindowsDrawnLocked(Message message, long timeout, int displayId) {
+            final WindowContainer<?> container = displayId == INVALID_DISPLAY
+                    ? mRoot : mRoot.getDisplayContent(displayId);
+            if (container == null) {
+                // The waiting container doesn't exist, no need to wait. Treat as drawn.
+                return true;
+            }
+            if (displayId == INVALID_DISPLAY
+                    && mRoot.getDefaultDisplay().mDisplayUpdater.waitForTransition(message)) {
+                // Use the ready-to-play of transition as the signal.
+                return false;
+            }
+            container.waitForAllWindowsDrawn();
+            mWindowPlacerLocked.requestTraversal();
+            mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, container);
+            if (container.mWaitingForDrawn.isEmpty()) {
+                return true;
+            }
+            if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+                for (int i = 0; i < container.mWaitingForDrawn.size(); i++) {
+                    traceStartWaitingForWindowDrawn(container.mWaitingForDrawn.get(i));
+                }
+            }
+
+            mWaitingForDrawnCallbacks.put(container, message);
+            mH.sendNewMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, container, timeout);
+            checkDrawnWindowsLocked();
+            return false;
+        }
+
         @Override
         public void setForcedDisplaySize(int displayId, int width, int height) {
             WindowManagerService.this.setForcedDisplaySize(displayId, width, height);
@@ -9217,6 +9226,25 @@
                     + "' because it isn't a trusted overlay");
             return inputFeatures & ~INPUT_FEATURE_SENSITIVE_FOR_PRIVACY;
         }
+
+        // You need OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission to be able
+        // to set INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS.
+        if (overridePowerKeyBehaviorInFocusedWindow()
+                && (inputFeatures
+                & INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS)
+                != 0) {
+            final int powerPermissionResult =
+                    mContext.checkPermission(
+                            permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW,
+                            callingPid,
+                            callingUid);
+            if (powerPermissionResult != PackageManager.PERMISSION_GRANTED) {
+                throw new IllegalArgumentException(
+                        "Cannot use INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS from" + windowName
+                                + " because it doesn't have the"
+                                + " OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission");
+            }
+        }
         return inputFeatures;
     }
 
@@ -10314,7 +10342,7 @@
             mH.post(() -> {
                 Toast.makeText(mContext, Looper.getMainLooper(),
                                 mContext.getString(R.string.screen_not_shared_sensitive_content),
-                                Toast.LENGTH_SHORT)
+                                Toast.LENGTH_LONG)
                         .show();
             });
             // If blocked due to notification protection (null window token) log protection applied
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index fe2bcc7..44f5f51 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1166,6 +1166,10 @@
                 case "--cameraCompatAspectRatio":
                     runSetCameraCompatAspectRatio(pw);
                     break;
+                case "--isCameraCompatFreeformWindowingTreatmentEnabled":
+                    runSetBooleanFlag(pw, mAppCompatConfiguration
+                            ::setIsCameraCompatFreeformWindowingTreatmentEnabled);
+                    break;
                 default:
                     getErrPrintWriter().println(
                             "Error: Unrecognized letterbox style option: " + arg);
@@ -1260,6 +1264,10 @@
                     case "cameraCompatAspectRatio":
                         mAppCompatConfiguration.resetCameraCompatAspectRatio();
                         break;
+                    case "isCameraCompatFreeformWindowingTreatmentEnabled":
+                        mAppCompatConfiguration
+                                .resetIsCameraCompatFreeformWindowingTreatmentEnabled();
+                        break;
                     default:
                         getErrPrintWriter().println(
                                 "Error: Unrecognized letterbox style option: " + arg);
@@ -1371,6 +1379,7 @@
             mAppCompatConfiguration.resetCameraCompatRefreshEnabled();
             mAppCompatConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled();
             mAppCompatConfiguration.resetCameraCompatAspectRatio();
+            mAppCompatConfiguration.resetIsCameraCompatFreeformWindowingTreatmentEnabled();
         }
     }
 
@@ -1445,6 +1454,10 @@
                     + mAppCompatConfiguration.isUserAppAspectRatioSettingsEnabled());
             pw.println("Is the fullscreen option in user aspect ratio settings enabled: "
                     + mAppCompatConfiguration.isUserAppAspectRatioFullscreenEnabled());
+            pw.println("Default aspect ratio for camera compat freeform: "
+                    + mAppCompatConfiguration.getCameraCompatAspectRatio());
+            pw.println("Is camera compatibility freeform treatment enabled for all apps: "
+                    + mAppCompatConfiguration.isCameraCompatFreeformWindowingTreatmentEnabled());
         }
         return 0;
     }
@@ -1701,10 +1714,13 @@
         pw.println("        happen using the \"stopped -> resumed\" cycle rather than");
         pw.println("        \"paused -> resumed\" cycle.");
         pw.println("      --cameraCompatAspectRatio aspectRatio");
-        pw.println("        Aspect ratio of letterbox for fixed-orientation camera apps, during ");
+        pw.println("        Aspect ratio of letterbox for fixed-orientation camera apps, during");
         pw.println("        freeform camera compat mode. If aspectRatio <= "
                 + AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
         pw.println("        it will be ignored.");
+        pw.println("      --isCameraCompatFreeformWindowingTreatmentEnabled [true|1|false|0]");
+        pw.println("        Whether camera compat treatment is enabled in freeform mode for all");
+        pw.println("        eligible apps.");
         pw.println("  reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
         pw.println("      |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
         pw.println("      |horizontalPositionMultiplier|verticalPositionMultiplier");
@@ -1714,7 +1730,8 @@
         pw.println("      |persistentPositionMultiplierForHorizontalReachability");
         pw.println("      |persistentPositionMultiplierForVerticalReachability");
         pw.println("      |defaultPositionMultiplierForVerticalReachability");
-        pw.println("      |cameraCompatAspectRatio]");
+        pw.println("      |cameraCompatAspectRatio");
+        pw.println("      |isCameraCompatFreeformWindowingTreatmentEnabled]");
         pw.println("    Resets overrides to default values for specified properties separated");
         pw.println("    by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
         pw.println("    If no arguments provided, all values will be reset.");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index c42aa37..ddff24d 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1851,14 +1851,13 @@
     }
 
     private int applyKeyguardState(@NonNull WindowContainerTransaction.HierarchyOp hop) {
-        int effects = TRANSACT_EFFECTS_NONE;
+        int effects = TRANSACT_EFFECTS_LIFECYCLE;
 
         final KeyguardState keyguardState = hop.getKeyguardState();
         if (keyguardState != null) {
-            int displayId = keyguardState.getDisplayId();
             boolean keyguardShowing = keyguardState.getKeyguardShowing();
             boolean aodShowing = keyguardState.getAodShowing();
-            mService.mKeyguardController.setKeyguardShown(displayId, keyguardShowing, aodShowing);
+            mService.setLockScreenShown(keyguardShowing, aodShowing);
         }
         return effects;
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 8294737..cebe790 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2757,7 +2757,7 @@
      * Expands the given rectangle by the region of window resize handle for freeform window.
      * @param inOutRect The rectangle to update.
      */
-    private void adjustRegionInFreefromWindowMode(Rect inOutRect) {
+    private void adjustRegionInFreeformWindowMode(Rect inOutRect) {
         if (!inFreeformWindowingMode()) {
             return;
         }
@@ -2808,7 +2808,7 @@
                 }
             }
         }
-        adjustRegionInFreefromWindowMode(mTmpRect);
+        adjustRegionInFreeformWindowMode(mTmpRect);
         outRegion.set(mTmpRect);
         cropRegionToRootTaskBoundsIfNeeded(outRegion);
     }
@@ -3608,7 +3608,7 @@
         }
 
         rootTask.getDimBounds(mTmpRect);
-        adjustRegionInFreefromWindowMode(mTmpRect);
+        adjustRegionInFreeformWindowMode(mTmpRect);
         region.op(mTmpRect, Region.Op.INTERSECT);
     }
 
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index eaa3a37..4c0cee4 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -38,7 +38,6 @@
         "com_android_server_adb_AdbDebuggingManager.cpp",
         "com_android_server_am_BatteryStatsService.cpp",
         "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
-        "com_android_server_ConsumerIrService.cpp",
         "com_android_server_companion_virtual_InputController.cpp",
         "com_android_server_companion_virtual_VirtualDeviceImpl.cpp",
         "com_android_server_devicepolicy_CryptoTestHelper.cpp",
@@ -63,7 +62,6 @@
         "com_android_server_SystemServer.cpp",
         "com_android_server_tv_TvUinputBridge.cpp",
         "com_android_server_tv_TvInputHal.cpp",
-        "com_android_server_vr_VrManagerService.cpp",
         "com_android_server_UsbAlsaJackDetector.cpp",
         "com_android_server_UsbAlsaMidiDevice.cpp",
         "com_android_server_UsbDeviceManager.cpp",
@@ -75,14 +73,13 @@
         "com_android_server_am_LowMemDetector.cpp",
         "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
         "com_android_server_sensor_SensorService.cpp",
-        "com_android_server_utils_LazyJniRegistrar.cpp",
         "com_android_server_wm_TaskFpsCallbackController.cpp",
         "onload.cpp",
         ":lib_cachedAppOptimizer_native",
         ":lib_freezer_native",
-        ":lib_gameManagerService_native",
         ":lib_oomConnection_native",
         ":lib_anrTimer_native",
+        ":lib_lazilyRegisteredServices_native",
     ],
 
     include_dirs: [
@@ -248,13 +245,6 @@
 }
 
 filegroup {
-    name: "lib_gameManagerService_native",
-    srcs: [
-        "com_android_server_app_GameManagerService.cpp",
-    ],
-}
-
-filegroup {
     name: "lib_oomConnection_native",
     srcs: ["com_android_server_am_OomConnection.cpp"],
 }
@@ -265,3 +255,13 @@
         "com_android_server_utils_AnrTimer.cpp",
     ],
 }
+
+filegroup {
+    name: "lib_lazilyRegisteredServices_native",
+    srcs: [
+        "com_android_server_ConsumerIrService.cpp",
+        "com_android_server_app_GameManagerService.cpp",
+        "com_android_server_utils_LazyJniRegistrar.cpp",
+        "com_android_server_vr_VrManagerService.cpp",
+    ],
+}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index dece612..9430194 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -348,6 +348,7 @@
     void setShouldNotifyTouchpadHardwareState(bool enabled);
     void setTouchpadRightClickZoneEnabled(bool enabled);
     void setTouchpadThreeFingerTapShortcutEnabled(bool enabled);
+    void setTouchpadSystemGesturesEnabled(bool enabled);
     void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
     void setShowTouches(bool enabled);
     void setNonInteractiveDisplays(const std::set<ui::LogicalDisplayId>& displayIds);
@@ -518,6 +519,9 @@
         // middle-click.
         bool touchpadThreeFingerTapShortcutEnabled{false};
 
+        // True to enable system gestures (three- and four-finger swipes) on touchpads.
+        bool touchpadSystemGesturesEnabled{true};
+
         // True if a pointer icon should be shown for stylus pointers.
         bool stylusPointerIconEnabled{false};
 
@@ -790,6 +794,7 @@
         outConfig->touchpadRightClickZoneEnabled = mLocked.touchpadRightClickZoneEnabled;
         outConfig->touchpadThreeFingerTapShortcutEnabled =
                 mLocked.touchpadThreeFingerTapShortcutEnabled;
+        outConfig->touchpadSystemGesturesEnabled = mLocked.touchpadSystemGesturesEnabled;
 
         outConfig->disabledDevices = mLocked.disabledInputDevices;
 
@@ -1528,6 +1533,22 @@
             InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
 }
 
+void NativeInputManager::setTouchpadSystemGesturesEnabled(bool enabled) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+
+        if (mLocked.touchpadSystemGesturesEnabled == enabled) {
+            return;
+        }
+
+        ALOGI("Setting touchpad system gestures enabled to %s.", toString(enabled));
+        mLocked.touchpadSystemGesturesEnabled = enabled;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
 void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
     bool refresh = false;
 
@@ -2481,6 +2502,13 @@
     getNativeInputManager(env, nativeImplObj)->setTouchpadThreeFingerTapShortcutEnabled(enabled);
 }
 
+static void nativeSetTouchpadSystemGesturesEnabled(JNIEnv* env, jobject nativeImplObj,
+                                                   jboolean enabled) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+    im->setTouchpadSystemGesturesEnabled(enabled);
+}
+
 static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
@@ -3169,6 +3197,7 @@
         {"setTouchpadRightClickZoneEnabled", "(Z)V", (void*)nativeSetTouchpadRightClickZoneEnabled},
         {"setTouchpadThreeFingerTapShortcutEnabled", "(Z)V",
          (void*)nativeSetTouchpadThreeFingerTapShortcutEnabled},
+        {"setTouchpadSystemGesturesEnabled", "(Z)V", (void*)nativeSetTouchpadSystemGesturesEnabled},
         {"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
         {"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays},
         {"reloadCalibration", "()V", (void*)nativeReloadCalibration},
diff --git a/services/core/jni/com_android_server_utils_LazyJniRegistrar.cpp b/services/core/jni/com_android_server_utils_LazyJniRegistrar.cpp
index ad7781e..0c0f8b0 100644
--- a/services/core/jni/com_android_server_utils_LazyJniRegistrar.cpp
+++ b/services/core/jni/com_android_server_utils_LazyJniRegistrar.cpp
@@ -22,6 +22,7 @@
 
 // Forward declared per-class registration methods.
 int register_android_server_ConsumerIrService(JNIEnv* env);
+int register_android_server_app_GameManagerService(JNIEnv* env);
 int register_android_server_vr_VrManagerService(JNIEnv* env);
 
 namespace {
@@ -33,12 +34,17 @@
     register_android_server_ConsumerIrService(env);
 }
 
+void registerGameManagerService(JNIEnv* env, jclass) {
+    register_android_server_app_GameManagerService(env);
+}
+
 void registerVrManagerService(JNIEnv* env, jclass) {
     register_android_server_vr_VrManagerService(env);
 }
 
 static const JNINativeMethod sJniRegistrarMethods[] = {
         {"registerConsumerIrService", "()V", (void*)registerConsumerIrService},
+        {"registerGameManagerService", "()V", (void*)registerGameManagerService},
         {"registerVrManagerService", "()V", (void*)registerVrManagerService},
 };
 
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index c170ae9..df37ec3 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -65,7 +65,6 @@
 int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
 int register_android_server_companion_virtual_InputController(JNIEnv* env);
 int register_android_server_companion_virtual_VirtualDeviceImpl(JNIEnv* env);
-int register_android_server_app_GameManagerService(JNIEnv* env);
 int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
 int register_com_android_server_display_DisplayControl(JNIEnv* env);
 int register_com_android_server_SystemClockTime(JNIEnv* env);
@@ -131,7 +130,6 @@
     register_android_server_sensor_SensorService(vm, env);
     register_android_server_companion_virtual_InputController(env);
     register_android_server_companion_virtual_VirtualDeviceImpl(env);
-    register_android_server_app_GameManagerService(env);
     register_com_android_server_wm_TaskFpsCallbackController(env);
     register_com_android_server_display_DisplayControl(env);
     register_com_android_server_SystemClockTime(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index cb333f0..1c8d06e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -152,19 +152,20 @@
         mAdminPolicySize = new SparseArray<>();
     }
 
-    private void maybeForceEnforcementRefreshLocked(@NonNull PolicyDefinition<?> policyDefinition) {
+    private void forceEnforcementRefreshIfUserRestrictionLocked(
+            @NonNull PolicyDefinition<?> policyDefinition) {
         try {
-            if (shouldForceEnforcementRefresh(policyDefinition)) {
+            if (isUserRestrictionPolicy(policyDefinition)) {
                 // This is okay because it's only true for user restrictions which are all <Boolean>
                 forceEnforcementRefreshLocked((PolicyDefinition<Boolean>) policyDefinition);
             }
         } catch (Throwable e) {
             // Catch any possible exceptions just to be on the safe side
-            Log.e(TAG, "Exception throw during maybeForceEnforcementRefreshLocked", e);
+            Log.e(TAG, "Exception thrown during forceEnforcementRefreshIfUserRestrictionLocked", e);
         }
     }
 
-    private boolean shouldForceEnforcementRefresh(@NonNull PolicyDefinition<?> policyDefinition) {
+    private boolean isUserRestrictionPolicy(@NonNull PolicyDefinition<?> policyDefinition) {
         // These are all "not nullable" but for the purposes of maximum safety for a lightly tested
         // change we check here
         if (policyDefinition == null) {
@@ -257,7 +258,7 @@
             // No need to notify admins as no new policy is actually enforced, we're just filling in
             // the data structures.
             if (!skipEnforcePolicy) {
-                maybeForceEnforcementRefreshLocked(policyDefinition);
+                forceEnforcementRefreshIfUserRestrictionLocked(policyDefinition);
                 if (policyChanged) {
                     onLocalPolicyChangedLocked(policyDefinition, enforcingAdmin, userId);
                 }
@@ -347,7 +348,7 @@
         Objects.requireNonNull(enforcingAdmin);
 
         synchronized (mLock) {
-            maybeForceEnforcementRefreshLocked(policyDefinition);
+            forceEnforcementRefreshIfUserRestrictionLocked(policyDefinition);
             if (!hasLocalPolicyLocked(policyDefinition, userId)) {
                 return;
             }
@@ -517,7 +518,7 @@
             // No need to notify admins as no new policy is actually enforced, we're just filling in
             // the data structures.
             if (!skipEnforcePolicy) {
-                maybeForceEnforcementRefreshLocked(policyDefinition);
+                forceEnforcementRefreshIfUserRestrictionLocked(policyDefinition);
                 if (policyChanged) {
                     onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
                 }
@@ -570,7 +571,7 @@
 
             boolean policyChanged = policyState.removePolicy(enforcingAdmin);
 
-            maybeForceEnforcementRefreshLocked(policyDefinition);
+            forceEnforcementRefreshIfUserRestrictionLocked(policyDefinition);
             if (policyChanged) {
                 onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
             }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index dde213d..3ccde06 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -117,6 +117,7 @@
 import static android.app.admin.DeviceAdminInfo.USES_POLICY_WIPE_DATA;
 import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
 import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
+import static android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY;
 import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_FINANCING_STATE_CHANGED;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
@@ -578,6 +579,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
@@ -591,6 +593,7 @@
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Implementation of the device policy APIs.
@@ -9082,9 +9085,13 @@
         }
         CallerIdentity caller = getCallerIdentity(who);
 
-        Objects.requireNonNull(who, "ComponentName is null");
-        Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+        if (Flags.setAutoTimeEnabledCoexistence()) {
+            Preconditions.checkCallAuthorization(hasPermission(SET_TIME, caller.getPackageName()));
+        } else {
+            Objects.requireNonNull(who, "ComponentName is null");
+            Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
                 || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
+        }
 
         return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
     }
@@ -9161,7 +9168,6 @@
         }
 
         CallerIdentity caller = getCallerIdentity(who);
-
         Objects.requireNonNull(who, "ComponentName is null");
         Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
                 || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
@@ -9186,10 +9192,15 @@
         }
 
         CallerIdentity caller = getCallerIdentity(who);
-        Objects.requireNonNull(who, "ComponentName is null");
-        Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+        if (Flags.setAutoTimeZoneEnabledCoexistence()) {
+            Preconditions.checkCallAuthorization(
+                hasPermission(SET_TIME_ZONE, caller.getPackageName()));
+        } else {
+            Objects.requireNonNull(who, "ComponentName is null");
+            Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
                 || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
                 caller));
+        }
         return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
     }
 
@@ -16234,6 +16245,10 @@
                     result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
                             admin.info.getComponent());
                     return result;
+                } else if (android.security.Flags.aapmApi()) {
+                    result = new Bundle();
+                    result.putInt(Intent.EXTRA_USER_ID, userId);
+                    return result;
                 }
                 return null;
             } finally {
@@ -16243,6 +16258,54 @@
         return null;
     }
 
+    private android.app.admin.EnforcingAdmin getEnforcingAdminInternal(int userId,
+            String identifier) {
+        Objects.requireNonNull(identifier);
+
+        Set<EnforcingAdmin> admins = getEnforcingAdminsForIdentifier(userId, identifier);
+        if (admins.isEmpty()) {
+            return null;
+        }
+
+        final EnforcingAdmin admin;
+        if (admins.size() == 1) {
+            admin = admins.iterator().next();
+        } else {
+            Optional<EnforcingAdmin> dpc = admins.stream()
+                    .filter(a -> a.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)).findFirst();
+            admin = dpc.orElseGet(() -> admins.stream().findFirst().get());
+        }
+        return admin == null ? null : admin.getParcelableAdmin();
+    }
+
+    private <V> Set<EnforcingAdmin> getEnforcingAdminsForIdentifier(int userId, String identifier) {
+        // For POLICY_SUSPEND_PACKAGES return PO or DO to keep the behavior same as
+        // before the bug fix for b/192245204.
+        if (DevicePolicyManager.POLICY_SUSPEND_PACKAGES.equals(identifier)) {
+            EnforcingAdmin admin = getProfileOrDeviceOwnerEnforcingAdmin(userId);
+            return admin == null ? Collections.emptySet() : Collections.singleton(admin);
+        }
+
+        long ident = mInjector.binderClearCallingIdentity();
+        try {
+            final PolicyDefinition<V> policyDefinition = getPolicyDefinitionForIdentifier(
+                    identifier);
+            V value = mDevicePolicyEngine.getResolvedPolicy(policyDefinition, userId);
+            if (value == null) {
+                return Collections.emptySet();
+            }
+            return Stream.concat(mDevicePolicyEngine.getGlobalPoliciesSetByAdmins(policyDefinition)
+                                    .entrySet().stream(),
+                            mDevicePolicyEngine.getLocalPoliciesSetByAdmins(policyDefinition,
+                                    userId).entrySet().stream())
+                    .filter(entry -> value.equals(entry.getValue().getValue()))
+                    .map(Map.Entry::getKey)
+                    .collect(Collectors.toSet());
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
+    }
+
     /**
      * @param restriction The restriction enforced by admin. It could be any user restriction or
      *                    policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA},
@@ -16257,20 +16320,9 @@
         // before the bug fix for b/192245204.
         if (DevicePolicyManager.POLICY_SUSPEND_PACKAGES.equals(
                 restriction)) {
-            ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
-            if (profileOwner != null) {
-                EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
-                        profileOwner, userId);
+            EnforcingAdmin admin = getProfileOrDeviceOwnerEnforcingAdmin(userId);
+            if (admin != null) {
                 admins.add(admin.getParcelableAdmin());
-                return admins;
-            }
-            final Pair<Integer, ComponentName> deviceOwner =
-                    mOwners.getDeviceOwnerUserIdAndComponent();
-            if (deviceOwner != null && deviceOwner.first == userId) {
-                EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
-                        deviceOwner.second, deviceOwner.first);
-                admins.add(admin.getParcelableAdmin());
-                return admins;
             }
         } else {
             long ident = mInjector.binderClearCallingIdentity();
@@ -16319,6 +16371,29 @@
         }
     }
 
+    private static <V> PolicyDefinition<V> getPolicyDefinitionForIdentifier(
+            @NonNull String identifier) {
+        Objects.requireNonNull(identifier);
+        if (Flags.setMtePolicyCoexistence() && MEMORY_TAGGING_POLICY.equals(identifier)) {
+            return (PolicyDefinition<V>) PolicyDefinition.MEMORY_TAGGING;
+        } else {
+            return (PolicyDefinition<V>) getPolicyDefinitionForRestriction(identifier);
+        }
+    }
+
+    private EnforcingAdmin getProfileOrDeviceOwnerEnforcingAdmin(int userId) {
+        ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
+        if (profileOwner != null) {
+            return EnforcingAdmin.createEnterpriseEnforcingAdmin(profileOwner, userId);
+        }
+        final Pair<Integer, ComponentName> deviceOwner = mOwners.getDeviceOwnerUserIdAndComponent();
+        if (deviceOwner != null && deviceOwner.first == userId) {
+            return EnforcingAdmin.createEnterpriseEnforcingAdmin(deviceOwner.second,
+                    deviceOwner.first);
+        }
+        return null;
+    }
+
     private static String userRestrictionSourceToString(@UserRestrictionSource int source) {
         return DebugUtils.flagsToString(UserManager.class, "RESTRICTION_", source);
     }
@@ -16336,6 +16411,12 @@
     }
 
     @Override
+    public android.app.admin.EnforcingAdmin getEnforcingAdmin(int userId, String identifier) {
+        Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()));
+        return getEnforcingAdminInternal(userId, identifier);
+    }
+
+    @Override
     public List<android.app.admin.EnforcingAdmin> getEnforcingAdminsForRestriction(
             int userId, String restriction) {
         Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()));
@@ -23178,6 +23259,10 @@
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_KEYGUARD,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
+        if (Flags.lockNowCoexistence()) {
+            CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK,
+                    MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
+        }
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
 
@@ -23252,8 +23337,10 @@
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCATION,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
-        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK,
-                MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+        if (!Flags.lockNowCoexistence()) {
+            CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK,
+                    MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+        }
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MODIFY_USERS,
@@ -23708,24 +23795,7 @@
     }
 
     public void setMtePolicy(int flags, String callerPackageName) {
-        final Set<Integer> allowedModes =
-                Set.of(
-                        DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY,
-                        DevicePolicyManager.MTE_DISABLED,
-                        DevicePolicyManager.MTE_ENABLED);
-        Preconditions.checkArgument(
-                allowedModes.contains(flags), "Provided mode is not one of the allowed values.");
-        // In general, this API should be available when "bootctl_settings_toggle" is set, which
-        // signals that there is a control for MTE in the user settings and this API fundamentally
-        // is a way for the device admin to override that setting.
-        // Allow bootctl_device_policy_manager as an override, e.g. to offer the
-        // DevicePolicyManager only without a visible user setting.
-        if (!mInjector.systemPropertiesGetBoolean(
-                "ro.arm64.memtag.bootctl_device_policy_manager",
-                mInjector.systemPropertiesGetBoolean(
-                        "ro.arm64.memtag.bootctl_settings_toggle", false))) {
-            throw new UnsupportedOperationException("device does not support MTE");
-        }
+        checkMteSupportedAndAllowedPolicy(flags);
         final CallerIdentity caller = getCallerIdentity(callerPackageName);
         // For now we continue to restrict the DISABLED setting to device owner - we might need
         // another permission for this in future.
@@ -23783,6 +23853,53 @@
     }
 
     @Override
+    public void setMtePolicyBySystem(
+            @NonNull String systemEntity, int policy) {
+        Objects.requireNonNull(systemEntity);
+        checkMteSupportedAndAllowedPolicy(policy);
+
+        Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()),
+                "Only system services can call setMtePolicyBySystem");
+
+        if (!Flags.setMtePolicyCoexistence()) {
+            throw new UnsupportedOperationException("System can not set MTE policy only");
+        }
+
+        EnforcingAdmin admin = EnforcingAdmin.createSystemEnforcingAdmin(systemEntity);
+        if (policy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+            mDevicePolicyEngine.setGlobalPolicy(
+                    PolicyDefinition.MEMORY_TAGGING,
+                    admin,
+                    new IntegerPolicyValue(policy));
+        } else {
+            mDevicePolicyEngine.removeGlobalPolicy(
+                    PolicyDefinition.MEMORY_TAGGING,
+                    admin);
+        }
+    }
+
+    private void checkMteSupportedAndAllowedPolicy(int policy) {
+        final Set<Integer> allowedModes =
+                Set.of(
+                        DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY,
+                        DevicePolicyManager.MTE_DISABLED,
+                        DevicePolicyManager.MTE_ENABLED);
+        Preconditions.checkArgument(
+                allowedModes.contains(policy), "Provided mode is not one of the allowed values.");
+        // In general, this API should be available when "bootctl_settings_toggle" is set, which
+        // signals that there is a control for MTE in the user settings and this API fundamentally
+        // is a way for the device admin to override that setting.
+        // Allow bootctl_device_policy_manager as an override, e.g. to offer the
+        // DevicePolicyManager only without a visible user setting.
+        if (!mInjector.systemPropertiesGetBoolean(
+                "ro.arm64.memtag.bootctl_device_policy_manager",
+                mInjector.systemPropertiesGetBoolean(
+                        "ro.arm64.memtag.bootctl_settings_toggle", false))) {
+            throw new UnsupportedOperationException("device does not support MTE");
+        }
+    }
+
+    @Override
     public int getMtePolicy(String callerPackageName) {
         final CallerIdentity caller = getCallerIdentity(callerPackageName);
         if (Flags.setMtePolicyCoexistence()) {
@@ -24161,6 +24278,7 @@
         String supervisionBackupId = "36.2.supervision-support";
         boolean supervisionMigrated = maybeMigrateResetPasswordTokenLocked(supervisionBackupId);
         supervisionMigrated |= maybeMigrateSuspendedPackagesLocked(supervisionBackupId);
+        supervisionMigrated |= maybeMigrateSetKeyguardDisabledFeatures(supervisionBackupId);
         if (supervisionMigrated) {
             Slogf.i(LOG_TAG, "Backup made: " + supervisionBackupId);
         }
@@ -24174,6 +24292,38 @@
         // Additional migration steps should repeat the pattern above with a new backupId.
     }
 
+    @GuardedBy("getLockObject()")
+    private boolean maybeMigrateSetKeyguardDisabledFeatures(String backupId) {
+        Slog.i(LOG_TAG, "Migrating set keyguard disabled features to policy engine");
+        if (!Flags.setKeyguardDisabledFeaturesCoexistence()) {
+            return false;
+        }
+        if (mOwners.isSetKeyguardDisabledFeaturesMigrated()) {
+            return false;
+        }
+        // Create backup if none exists
+        mDevicePolicyEngine.createBackup(backupId);
+        try {
+            iterateThroughDpcAdminsLocked((admin, enforcingAdmin) -> {
+                if (admin.disabledKeyguardFeatures == 0) {
+                    return;
+                }
+                int userId = enforcingAdmin.getUserId();
+                mDevicePolicyEngine.setLocalPolicy(
+                        PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
+                        enforcingAdmin,
+                        new IntegerPolicyValue(admin.disabledKeyguardFeatures),
+                        userId);
+            });
+        } catch (Exception e) {
+            Slog.wtf(LOG_TAG, "Failed to migrate set keyguard disabled to policy engine", e);
+        }
+
+        Slog.i(LOG_TAG, "Marking set keyguard disabled features migration complete");
+        mOwners.markSetKeyguardDisabledFeaturesMigrated();
+        return true;
+    }
+
     private void migratePermissionGrantStatePolicies() {
         Slogf.i(LOG_TAG, "Migrating PERMISSION_GRANT policy to device policy engine.");
         for (UserInfo userInfo : mUserManager.getUsers()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 1fd628a..5a0b079 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -321,8 +321,10 @@
             authority = DpcAuthority.DPC_AUTHORITY;
         } else if (mAuthorities.contains(DEVICE_ADMIN_AUTHORITY)) {
             authority = DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY;
+        } else if (mIsSystemAuthority) {
+            // For now, System Authority returns UnknownAuthority.
+            authority = new UnknownAuthority(mSystemEntity);
         } else {
-            // For now, System Authority returns UNKNOWN_AUTHORITY.
             authority = UnknownAuthority.UNKNOWN_AUTHORITY;
         }
         return new android.app.admin.EnforcingAdmin(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index be4eea4..1c75f2f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -695,6 +695,19 @@
         }
     }
 
+    void markSetKeyguardDisabledFeaturesMigrated() {
+        synchronized (mData) {
+            mData.mSetKeyguardDisabledFeaturesMigrated = true;
+            mData.writeDeviceOwner();
+        }
+    }
+
+    boolean isSetKeyguardDisabledFeaturesMigrated() {
+        synchronized (mData) {
+            return mData.mSetKeyguardDisabledFeaturesMigrated;
+        }
+    }
+
     @GuardedBy("mData")
     void pushToAppOpsLocked() {
         if (!mSystemReady) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 1cae924..caaf096 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -95,6 +95,8 @@
             "resetPasswordWithTokenMigrated";
     private static final String ATTR_MEMORY_TAGGING_MIGRATED =
             "memoryTaggingMigrated";
+    private static final String ATTR_SET_KEYGUARD_DISABLED_FEATURES_MIGRATED =
+            "setKeyguardDisabledFeaturesMigrated";
 
     private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade";
 
@@ -129,6 +131,7 @@
     boolean mSuspendedPackagesMigrated = false;
     boolean mResetPasswordWithTokenMigrated = false;
     boolean mMemoryTaggingMigrated = false;
+    boolean mSetKeyguardDisabledFeaturesMigrated = false;
 
     boolean mPoliciesMigratedPostUpdate = false;
 
@@ -434,6 +437,10 @@
                 out.attributeBoolean(null, ATTR_MEMORY_TAGGING_MIGRATED,
                         mMemoryTaggingMigrated);
             }
+            if (Flags.setKeyguardDisabledFeaturesCoexistence()) {
+                out.attributeBoolean(null, ATTR_SET_KEYGUARD_DISABLED_FEATURES_MIGRATED,
+                        mSetKeyguardDisabledFeaturesMigrated);
+            }
             out.endTag(null, TAG_POLICY_ENGINE_MIGRATION);
 
         }
@@ -510,6 +517,10 @@
                     mMemoryTaggingMigrated = Flags.setMtePolicyCoexistence()
                             && parser.getAttributeBoolean(null,
                             ATTR_MEMORY_TAGGING_MIGRATED, false);
+                    mSetKeyguardDisabledFeaturesMigrated =
+                            Flags.setKeyguardDisabledFeaturesCoexistence()
+                                    && parser.getAttributeBoolean(null,
+                                    ATTR_SET_KEYGUARD_DISABLED_FEATURES_MIGRATED, false);
                     break;
                 default:
                     Slog.e(TAG, "Unexpected tag: " + tag);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java
index 40cf0e9..c8c953d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSuspender.java
@@ -19,6 +19,7 @@
 import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
 
 import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
 import android.content.pm.PackageManagerInternal;
 import android.util.ArraySet;
 
@@ -64,7 +65,7 @@
     /**
      * Suspend packages that are requested by a single admin
      *
-     * @return a list of packages that the admin has requested to suspend but could not be
+     * @return an array of packages that the admin has requested to suspend but could not be
      * suspended, due to DPM and PackageManager exemption list.
      *
      */
@@ -87,7 +88,7 @@
     /**
      * Suspend packages considering the exemption list.
      *
-     * @return the list of packages that couldn't be suspended, either due to the exemption list,
+     * @return the set of packages that couldn't be suspended, either due to the exemption list,
      * or due to failures from PackageManagerInternal itself.
      */
     private Set<String> suspendWithExemption(Set<String> packages) {
@@ -112,15 +113,15 @@
     /**
      * Unsuspend packages that are requested by a single admin
      *
-     * @return a list of packages that the admin has requested to unsuspend but could not be
-     * unsuspended, due to other amdin's policy or PackageManager restriction.
+     * @return an array of packages that the admin has requested to unsuspend but could not be
+     * unsuspended, due to other admin's policy or PackageManager restriction.
      *
      */
     public String[] unsuspend(Set<String> packages) {
-        // Unlike suspend(), when unsuspending, call PackageManager with the delta of resolved
-        // suspended packages list and not what the admin has requested. This is because some
-        // packages might still be subject to another admin's suspension request.
-        Set<String> packagesToUnsuspend = new ArraySet<>(mSuspendedPackageBefore);
+        // Unlike suspend(), when unsuspending, take suspension by other admins into account: only
+        // packages not suspended by other admins are passed to PackageManager.
+        Set<String> packagesToUnsuspend = new ArraySet<>(
+                Flags.unsuspendNotSuspended() ? packages : mSuspendedPackageBefore);
         packagesToUnsuspend.removeAll(mSuspendedPackageAfter);
 
         // To calculate the result (which packages are not unsuspended), start with packages that
@@ -139,7 +140,7 @@
     /**
      * Unsuspend packages considering the exemption list.
      *
-     * @return the list of packages that couldn't be unsuspended, either due to the exemption list,
+     * @return the set of packages that couldn't be unsuspended, either due to the exemption list,
      * or due to failures from PackageManagerInternal itself.
      */
     private Set<String> unsuspendWithExemption(Set<String> packages) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 82d49fc..aa63c4a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import static android.media.tv.flags.Flags.mediaQualityFw;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
@@ -422,10 +423,14 @@
             "com.android.server.wifi.aware.WifiAwareService";
     private static final String WIFI_P2P_SERVICE_CLASS =
             "com.android.server.wifi.p2p.WifiP2pService";
+    private static final String WIFI_USD_SERVICE_CLASS =
+            "com.android.server.wifi.usd.UsdService";
     private static final String CONNECTIVITY_SERVICE_APEX_PATH =
             "/apex/com.android.tethering/javalib/service-connectivity.jar";
     private static final String CONNECTIVITY_SERVICE_INITIALIZER_CLASS =
             "com.android.server.ConnectivityServiceInitializer";
+    private static final String CONNECTIVITY_SERVICE_INITIALIZER_B_CLASS =
+            "com.android.server.ConnectivityServiceInitializerB";
     private static final String NETWORK_STATS_SERVICE_INITIALIZER_CLASS =
             "com.android.server.NetworkStatsServiceInitializer";
     private static final String UWB_APEX_SERVICE_JAR_PATH =
@@ -1483,7 +1488,6 @@
         IStorageManager storageManager = null;
         NetworkManagementService networkManagement = null;
         VpnManagerService vpnManager = null;
-        VcnManagementService vcnManagement = null;
         NetworkPolicyManagerService networkPolicy = null;
         WindowManagerService wm = null;
         NetworkTimeUpdateService networkTimeUpdater = null;
@@ -2145,6 +2149,13 @@
                 mSystemServiceManager.startServiceFromJar(
                         WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
                 t.traceEnd();
+                // Start USD service
+                if (android.net.wifi.flags.Flags.usd()) {
+                    t.traceBegin("StartUsd");
+                    mSystemServiceManager.startServiceFromJar(
+                            WIFI_USD_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
+                    t.traceEnd();
+                }
             }
 
             if (context.getPackageManager().hasSystemFeature(
@@ -2222,8 +2233,10 @@
 
             t.traceBegin("StartVcnManagementService");
             try {
-                vcnManagement = VcnManagementService.create(context);
-                ServiceManager.addService(Context.VCN_MANAGEMENT_SERVICE, vcnManagement);
+                // TODO: b/375213246 When VCN is in mainline module, load it from the apex path.
+                // Whether VCN will be in apex or in the platform will be gated by a build system
+                // flag.
+                mSystemServiceManager.startService(CONNECTIVITY_SERVICE_INITIALIZER_B_CLASS);
             } catch (Throwable e) {
                 reportWtf("starting VCN Management Service", e);
             }
@@ -2607,9 +2620,11 @@
                 t.traceEnd();
             }
 
-            t.traceBegin("StartMediaQuality");
-            mSystemServiceManager.startService(MediaQualityService.class);
-            t.traceEnd();
+            if (mediaQualityFw() && isTv) {
+                t.traceBegin("StartMediaQuality");
+                mSystemServiceManager.startService(MediaQualityService.class);
+                t.traceEnd();
+            }
 
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
                 t.traceBegin("StartMediaResourceMonitor");
@@ -3147,7 +3162,6 @@
         final MediaRouterService mediaRouterF = mediaRouter;
         final MmsServiceBroker mmsServiceF = mmsService;
         final VpnManagerService vpnManagerF = vpnManager;
-        final VcnManagementService vcnManagementF = vcnManagement;
         final WindowManagerService windowManagerF = wm;
         final ConnectivityManager connectivityF = (ConnectivityManager)
                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -3274,15 +3288,6 @@
                 reportWtf("making VpnManagerService ready", e);
             }
             t.traceEnd();
-            t.traceBegin("MakeVcnManagementServiceReady");
-            try {
-                if (vcnManagementF != null) {
-                    vcnManagementF.systemReady();
-                }
-            } catch (Throwable e) {
-                reportWtf("making VcnManagementService ready", e);
-            }
-            t.traceEnd();
             t.traceBegin("MakeNetworkPolicyServiceReady");
             try {
                 if (networkPolicyF != null) {
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
index 114fe32..9457205 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -4,4 +4,9 @@
         <version>2</version>
         <fqname>IAltitudeService/default</fqname>
     </hal>
+    <hal format="aidl">
+        <name>android.frameworks.devicestate</name>
+        <version>1</version>
+        <fqname>IDeviceStateService/default</fqname>
+    </hal>
 </manifest>
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index e307e52..228e32e 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -70,6 +70,7 @@
     private int mUsageSetting;
     private boolean mUploadEnabled;
 
+    private static boolean sVerityEnforced;
     private boolean mAdbActive;
 
     private IProfCollectd mIProfcollect;
@@ -117,6 +118,13 @@
             mUsageSetting = -1;
         }
 
+        // Check verity, disable profile upload if not enforced.
+        final String verityMode = SystemProperties.get("ro.boot.veritymode");
+        sVerityEnforced = verityMode.equals("enforcing");
+        if (!sVerityEnforced) {
+            Log.d(LOG_TAG, "verity is not enforced: " + verityMode);
+        }
+
         mUploadEnabled =
             context.getResources().getBoolean(R.bool.config_profcollectReportUploaderEnabled);
 
@@ -373,6 +381,10 @@
                 Log.i(LOG_TAG, "Upload is not enabled.");
                 return;
             }
+            if (!sVerityEnforced) {
+                Log.i(LOG_TAG, "Verity is not enforced.");
+                return;
+            }
             Intent intent = new Intent()
                     .setPackage("com.android.shell")
                     .setAction("com.android.shell.action.PROFCOLLECT_UPLOAD")
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 02e0bbf..eb61a40 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -30,13 +30,10 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 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.when;
 import static org.robolectric.Shadows.shadowOf;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.expectThrows;
 
 import android.app.backup.BackupManager;
@@ -90,7 +87,6 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -110,7 +106,6 @@
     private static final String TAG = "BMSTest";
     private static final String PACKAGE_1 = "some.package.1";
     private static final String PACKAGE_2 = "some.package.2";
-    private static final String USER_FACING_PACKAGE = "user.facing.package";
     private static final int USER_ID = 10;
 
     @Mock private TransportManager mTransportManager;
@@ -1213,47 +1208,6 @@
                 eq(packageTrackingReceiver), eq(UserHandle.of(USER_ID)), any(), any(), any());
     }
 
-    @Test
-    public void testFilterUserFacingPackages_shouldSkipUserFacing_filtersUserFacing() {
-        List<PackageInfo> packages = Arrays.asList(getPackageInfo(USER_FACING_PACKAGE),
-                getPackageInfo(PACKAGE_1));
-        UserBackupManagerService backupManagerService = spy(
-                createUserBackupManagerServiceAndRunTasks());
-        when(backupManagerService.shouldSkipUserFacingData()).thenReturn(true);
-        when(backupManagerService.shouldSkipPackage(eq(USER_FACING_PACKAGE))).thenReturn(true);
-
-        List<PackageInfo> filteredPackages = backupManagerService.filterUserFacingPackages(
-                packages);
-
-        assertFalse(containsPackage(filteredPackages, USER_FACING_PACKAGE));
-        assertTrue(containsPackage(filteredPackages, PACKAGE_1));
-    }
-
-    @Test
-    public void testFilterUserFacingPackages_shouldNotSkipUserFacing_doesNotFilterUserFacing() {
-        List<PackageInfo> packages = Arrays.asList(getPackageInfo(USER_FACING_PACKAGE),
-                getPackageInfo(PACKAGE_1));
-        UserBackupManagerService backupManagerService = spy(
-                createUserBackupManagerServiceAndRunTasks());
-        when(backupManagerService.shouldSkipUserFacingData()).thenReturn(false);
-        when(backupManagerService.shouldSkipPackage(eq(USER_FACING_PACKAGE))).thenReturn(true);
-
-        List<PackageInfo> filteredPackages = backupManagerService.filterUserFacingPackages(
-                packages);
-
-        assertTrue(containsPackage(filteredPackages, USER_FACING_PACKAGE));
-        assertTrue(containsPackage(filteredPackages, PACKAGE_1));
-    }
-
-    private static boolean containsPackage(List<PackageInfo> packages, String targetPackage) {
-        for (PackageInfo packageInfo : packages) {
-            if (targetPackage.equals(packageInfo.packageName)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private UserBackupManagerService createUserBackupManagerServiceAndRunTasks() {
         return BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks(
                 USER_ID, mContext, mBackupThread, mBaseStateDir, mDataDir, mTransportManager);
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index aeb1ba9..de16b7e 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -865,7 +865,8 @@
         runTask(task);
 
         verify(mBackupManagerService).setWorkSource(null);
-        verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
+        verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(PACKAGE_1)),
+                eq(false));
     }
 
     @Test
@@ -1101,7 +1102,8 @@
         runTask(task);
 
         verify(agentMock.agentBinder).fail(any());
-        verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
+        verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(PACKAGE_1)),
+                eq(false));
     }
 
     @Test
@@ -1423,7 +1425,7 @@
         assertCleansUpFiles(mTransport, PM_PACKAGE);
         // We don't unbind PM
         verify(mBackupAgentConnectionManager, never()).unbindAgent(
-                argThat(applicationInfo(PM_PACKAGE)));
+                argThat(applicationInfo(PM_PACKAGE)), eq(false));
     }
 
     @Test
@@ -1445,7 +1447,7 @@
         runTask(task);
 
         verify(mBackupAgentConnectionManager, never()).unbindAgent(
-                argThat(applicationInfo(PM_PACKAGE)));
+                argThat(applicationInfo(PM_PACKAGE)), eq(false));
     }
 
     @Test
@@ -1651,7 +1653,7 @@
         InOrder inOrder = inOrder(agentMock.agent, mBackupAgentConnectionManager);
         inOrder.verify(agentMock.agent).onQuotaExceeded(anyLong(), eq(1234L));
         inOrder.verify(mBackupAgentConnectionManager).unbindAgent(
-                argThat(applicationInfo(PACKAGE_1)));
+                argThat(applicationInfo(PACKAGE_1)), eq(false));
     }
 
     @Test
@@ -2983,7 +2985,8 @@
 
     private void assertCleansUpFilesAndAgent(TransportData transport, PackageData packageData) {
         assertCleansUpFiles(transport, packageData);
-        verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(packageData)));
+        verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(packageData)),
+                eq(false));
     }
 
     private void assertCleansUpFiles(TransportData transport, PackageData packageData) {
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index 53a25dd..3093c42 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -19,11 +19,16 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.supervision.ISupervisionManager;
 import android.app.supervision.SupervisionManagerInternal;
+import android.app.supervision.flags.Flags;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.UserInfo;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
@@ -33,6 +38,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
@@ -42,6 +48,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.List;
 
 /** Service for handling system supervision. */
 public class SupervisionService extends ISupervisionManager.Stub {
@@ -65,15 +72,6 @@
         mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
     }
 
-    void syncStateWithDevicePolicyManager(TargetUser user) {
-        if (user.isPreCreated()) return;
-
-        // Ensure that supervision is enabled when supervision app is the profile owner.
-        if (android.app.admin.flags.Flags.enableSupervisionServiceSync() && isProfileOwner(user)) {
-            setSupervisionEnabledForUser(user.getUserIdentifier(), true);
-        }
-    }
-
     @Override
     public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
         synchronized (getLockObject()) {
@@ -103,7 +101,7 @@
             pw.println("SupervisionService state:");
             pw.increaseIndent();
 
-            var users = mUserManagerInternal.getUsers(false);
+            List<UserInfo> users = mUserManagerInternal.getUsers(false);
             synchronized (getLockObject()) {
                 for (var user : users) {
                     getUserDataLocked(user.id).dump(pw);
@@ -135,9 +133,21 @@
         }
     }
 
+    /** Ensures that supervision is enabled when supervision app is the profile owner. */
+    private void syncStateWithDevicePolicyManager(@UserIdInt int userId) {
+        if (isProfileOwner(userId)) {
+            setSupervisionEnabledForUser(userId, true);
+        } else {
+            // TODO(b/381428475): Avoid disabling supervision when the app is not the profile owner.
+            // This might only be possible after introducing specific and public APIs to enable
+            // supervision.
+            setSupervisionEnabledForUser(userId, false);
+        }
+    }
+
     /** Returns whether the supervision app has profile owner status. */
-    private boolean isProfileOwner(TargetUser user) {
-        ComponentName profileOwner = mDpmInternal.getProfileOwnerAsUser(user.getUserIdentifier());
+    private boolean isProfileOwner(@UserIdInt int userId) {
+        ComponentName profileOwner = mDpmInternal.getProfileOwnerAsUser(userId);
         if (profileOwner == null) {
             return false;
         }
@@ -154,15 +164,46 @@
             mSupervisionService = new SupervisionService(context);
         }
 
+        @VisibleForTesting
+        Lifecycle(Context context, SupervisionService supervisionService) {
+            super(context);
+            mSupervisionService = supervisionService;
+        }
+
         @Override
         public void onStart() {
             publishLocalService(SupervisionManagerInternal.class, mSupervisionService.mInternal);
             publishBinderService(Context.SUPERVISION_SERVICE, mSupervisionService);
+            if (Flags.enableSyncWithDpm()) {
+                registerProfileOwnerListener();
+            }
+        }
+
+        @VisibleForTesting
+        void registerProfileOwnerListener() {
+            IntentFilter poIntentFilter = new IntentFilter();
+            poIntentFilter.addAction(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED);
+            poIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+            getContext()
+                    .registerReceiverForAllUsers(
+                            new ProfileOwnerBroadcastReceiver(),
+                            poIntentFilter,
+                            /* brodcastPermission= */ null,
+                            /* scheduler= */ null);
         }
 
         @Override
         public void onUserStarting(@NonNull TargetUser user) {
-            mSupervisionService.syncStateWithDevicePolicyManager(user);
+            if (Flags.enableSyncWithDpm() && !user.isPreCreated()) {
+                mSupervisionService.syncStateWithDevicePolicyManager(user.getUserIdentifier());
+            }
+        }
+
+        private final class ProfileOwnerBroadcastReceiver extends BroadcastReceiver {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mSupervisionService.syncStateWithDevicePolicyManager(getSendingUserId());
+            }
         }
     }
 
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
index a93e8ad..97f1bd4 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
@@ -574,57 +574,16 @@
         assertTrue(state.isInstallAllowed());
     }
 
-    public void testAreAllVerificationsComplete_onlyVerificationPasses() {
+    public void testAreAllVerificationsComplete() {
         PackageVerificationState state = new PackageVerificationState(null);
         state.addRequiredVerifierUid(REQUIRED_UID_1);
         assertFalse(state.areAllVerificationsComplete());
 
         state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW);
 
-        assertFalse(state.areAllVerificationsComplete());
-    }
-
-    public void testAreAllVerificationsComplete_onlyIntegrityCheckPasses() {
-        PackageVerificationState state = new PackageVerificationState(null);
-        state.addRequiredVerifierUid(REQUIRED_UID_1);
-        assertFalse(state.areAllVerificationsComplete());
-
-        state.setIntegrityVerificationResult(PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
-
-        assertFalse(state.areAllVerificationsComplete());
-    }
-
-    public void testAreAllVerificationsComplete_bothPasses() {
-        PackageVerificationState state = new PackageVerificationState(null);
-        state.addRequiredVerifierUid(REQUIRED_UID_1);
-        assertFalse(state.areAllVerificationsComplete());
-
-        state.setIntegrityVerificationResult(PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
-        state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW);
-
         assertTrue(state.areAllVerificationsComplete());
     }
 
-    public void testAreAllVerificationsComplete_onlyVerificationFails() {
-        PackageVerificationState state = new PackageVerificationState(null);
-        state.addRequiredVerifierUid(REQUIRED_UID_1);
-        assertFalse(state.areAllVerificationsComplete());
-
-        state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_REJECT);
-
-        assertFalse(state.areAllVerificationsComplete());
-    }
-
-    public void testAreAllVerificationsComplete_onlyIntegrityCheckFails() {
-        PackageVerificationState state = new PackageVerificationState(null);
-        state.addRequiredVerifierUid(REQUIRED_UID_1);
-        assertFalse(state.areAllVerificationsComplete());
-
-        state.setIntegrityVerificationResult(PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
-
-        assertFalse(state.areAllVerificationsComplete());
-    }
-
     private void processOnTimeout(PackageVerificationState state, int code, int uid) {
         // CHECK_PENDING_VERIFICATION handler.
         assertFalse("Verification should not be marked as complete yet",
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index c1271bb..9a61492 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -511,7 +511,7 @@
                 .addUsesPermission(
                         new ParsedUsesPermissionImpl(Manifest.permission.FACTORY_TEST, 0));
 
-        final ScanResult scanResult = ScanPackageUtils.scanPackageOnlyLI(
+        final ScanResult scanResult = ScanPackageUtils.scanPackageOnly(
                 createBasicScanRequestBuilder(basicPackage).build(),
                 mMockInjector,
                 true /*isUnderFactoryTest*/,
@@ -559,7 +559,7 @@
 
     private ScanResult executeScan(
             ScanRequest scanRequest) throws PackageManagerException {
-        ScanResult result = ScanPackageUtils.scanPackageOnlyLI(
+        ScanResult result = ScanPackageUtils.scanPackageOnly(
                 scanRequest,
                 mMockInjector,
                 false /*isUnderFactoryTest*/,
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 3fdb53f..31f0370 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -289,6 +289,7 @@
         AndroidPackage::getEmergencyInstaller,
         AndroidPackage::isAllowCrossUidActivitySwitchFromBelow,
         AndroidPackage::getIntentMatchingFlags,
+        AndroidPackage::getPageSizeAppCompatFlags,
     )
 
     override fun extraParams() = listOf(
diff --git a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
index 9772ef9..5db6a8f 100644
--- a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
+++ b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
@@ -145,6 +145,7 @@
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.net.ipsec.ike.exceptions.IkeTimeoutException;
 import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.util.PersistableBundleUtils;
 import android.net.wifi.WifiInfo;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -179,7 +180,6 @@
 import com.android.server.DeviceIdleInternal;
 import com.android.server.IpSecService;
 import com.android.server.VpnTestBase;
-import com.android.server.vcn.util.PersistableBundleUtils;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
index 44c7dec..cd0bd41 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -130,34 +130,64 @@
     }
 
     @Test
-    public void testStartStopTrackerScreenOnOff() {
+    public void testStartStopTrackerScreenStates() {
         mInjector.mInteractive = false;
+        mInjector.mDisplayState = Display.STATE_OFF;
         startTracker(mTracker);
         assertNull(mInjector.mSensorListener);
         assertNotNull(mInjector.mBroadcastReceiver);
         assertTrue(mInjector.mIdleScheduled);
-        mInjector.sendScreenChange(/* screenOn= */ true);
+        mInjector.sendInteractivityChange(true);
+        mInjector.setDisplayState(Display.STATE_ON);
         assertNotNull(mInjector.mSensorListener);
         assertTrue(mInjector.mColorSamplingEnabled);
+        assertNotNull(mInjector.mDisplayListener);
 
-        mInjector.sendScreenChange(/* screenOn= */ false);
+        mInjector.setDisplayState(Display.STATE_OFF);
         assertNull(mInjector.mSensorListener);
         assertFalse(mInjector.mColorSamplingEnabled);
 
-        // Turn screen on while brightness mode is manual
+        mInjector.setDisplayState(Display.STATE_DOZE);
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        mInjector.setDisplayState(Display.STATE_DOZE_SUSPEND);
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        mInjector.setDisplayState(Display.STATE_ON_SUSPEND);
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        // Screen on while device is not interactive
+        mInjector.setDisplayState(Display.STATE_ON);
+        mInjector.sendInteractivityChange(false);
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+
+        // Device becomes interactive while brightness mode is manual
         mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
-        mInjector.sendScreenChange(/* screenOn= */ true);
+        mInjector.sendInteractivityChange(true);
         assertNull(mInjector.mSensorListener);
         assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
 
         // Set brightness mode to automatic while screen is off.
-        mInjector.sendScreenChange(/* screenOn= */ false);
         mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
+        mInjector.setDisplayState(Display.STATE_OFF);
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNotNull(mInjector.mDisplayListener);
+
+        // Set brightness mode to automatic while screen is in doze.
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
+        mInjector.setDisplayState(Display.STATE_DOZE);
         assertNull(mInjector.mSensorListener);
         assertFalse(mInjector.mColorSamplingEnabled);
 
         // Turn on screen while brightness mode is automatic.
-        mInjector.sendScreenChange(/* screenOn= */ true);
+        mInjector.setDisplayState(Display.STATE_ON);
         assertNotNull(mInjector.mSensorListener);
         assertTrue(mInjector.mColorSamplingEnabled);
 
@@ -166,11 +196,11 @@
         assertNull(mInjector.mBroadcastReceiver);
         assertFalse(mInjector.mIdleScheduled);
         assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
     }
 
     @Test
     public void testModifyBrightnessConfiguration() {
-        mInjector.mInteractive = true;
         // Start with tracker not listening for color samples.
         startTracker(mTracker, DEFAULT_INITIAL_BRIGHTNESS, /* collectColorSamples= */ false);
         assertFalse(mInjector.mColorSamplingEnabled);
@@ -186,13 +216,17 @@
         assertFalse(mInjector.mColorSamplingEnabled);
 
         // Pretend screen is off, update config to turn on color sampling.
-        mInjector.sendScreenChange(/* screenOn= */ false);
+        mInjector.setDisplayState(Display.STATE_OFF);
         mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
         mInjector.waitForHandler();
         assertFalse(mInjector.mColorSamplingEnabled);
 
+        // Pretend screen is in doze
+        mInjector.setDisplayState(Display.STATE_DOZE);
+        assertFalse(mInjector.mColorSamplingEnabled);
+
         // Pretend screen is on.
-        mInjector.sendScreenChange(/* screenOn= */ true);
+        mInjector.setDisplayState(Display.STATE_ON);
         assertTrue(mInjector.mColorSamplingEnabled);
 
         mTracker.stop();
@@ -208,7 +242,6 @@
                         mInjector.mDefaultSamplingAttributes.getComponentMask());
         startTracker(mTracker);
         assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
     }
 
     @Test
@@ -220,7 +253,6 @@
                         0x2);
         startTracker(mTracker);
         assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
     }
 
     @Test
@@ -228,14 +260,12 @@
         mInjector.mDefaultSamplingAttributes = null;
         startTracker(mTracker);
         assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
     }
 
     @Test
     public void testColorSampling_FrameRateChange() {
         startTracker(mTracker);
         assertTrue(mInjector.mColorSamplingEnabled);
-        assertNotNull(mInjector.mDisplayListener);
         int noFramesSampled = mInjector.mNoColorSamplingFrames;
         mInjector.mFrameRate = 120.0f;
         // Wrong display
@@ -248,7 +278,6 @@
 
     @Test
     public void testAdaptiveOnOff() {
-        mInjector.mInteractive = true;
         mInjector.mIsBrightnessModeAutomatic = false;
         startTracker(mTracker);
         assertNull(mInjector.mSensorListener);
@@ -256,7 +285,6 @@
         assertNotNull(mInjector.mContentObserver);
         assertTrue(mInjector.mIdleScheduled);
         assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
 
         mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
         assertNotNull(mInjector.mSensorListener);
@@ -835,12 +863,14 @@
         mInjector.waitForHandler();
         assertNull(mInjector.mSensorListener);
         assertNull(mInjector.mLightSensor);
+        assertNull(mInjector.mDisplayListener);
 
         // Resetting sensor should start listener again
         mTracker.setLightSensor(mLightSensorFake);
         mInjector.waitForHandler();
         assertNotNull(mInjector.mSensorListener);
         assertEquals(mInjector.mLightSensor, mLightSensorFake);
+        assertNotNull(mInjector.mDisplayListener);
 
         Sensor secondSensor = new Sensor(mInputSensorInfoMock);
         // Setting a different listener should keep things working
@@ -848,6 +878,7 @@
         mInjector.waitForHandler();
         assertNotNull(mInjector.mSensorListener);
         assertEquals(mInjector.mLightSensor, secondSensor);
+        assertNotNull(mInjector.mDisplayListener);
     }
 
     @Test
@@ -862,6 +893,7 @@
         startTracker(mTracker);
         assertNull(mInjector.mSensorListener);
         assertNull(mInjector.mLightSensor);
+        assertNull(mInjector.mDisplayListener);
     }
 
     @Test
@@ -895,6 +927,7 @@
         assertNull(mInjector.mContentObserver);
         assertNull(mInjector.mBroadcastReceiver);
         assertFalse(mInjector.mIdleScheduled);
+        assertNull(mInjector.mDisplayListener);
 
         // mInjector asserts that we aren't removing a null receiver
         mTracker.stop();
@@ -1017,6 +1050,7 @@
         Handler mHandler;
         boolean mIdleScheduled;
         boolean mInteractive = true;
+        int mDisplayState = Display.STATE_ON;
         int[] mProfiles;
         ContentObserver mContentObserver;
         boolean mIsBrightnessModeAutomatic = true;
@@ -1042,14 +1076,20 @@
             waitForHandler();
         }
 
-        void sendScreenChange(boolean screenOn) {
-            mInteractive = screenOn;
+        void sendInteractivityChange(boolean interactive) {
+            mInteractive = interactive;
             Intent intent = new Intent();
-            intent.setAction(screenOn ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
+            intent.setAction(interactive ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
             mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), intent);
             waitForHandler();
         }
 
+        void setDisplayState(int state) {
+            mDisplayState = state;
+            mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
+            waitForHandler();
+        }
+
         void waitForHandler() {
             Idle idle = new Idle();
             mHandler.getLooper().getQueue().addIdleHandler(idle);
@@ -1183,6 +1223,11 @@
         }
 
         @Override
+        public int getDisplayState(Context context) {
+            return mDisplayState;
+        }
+
+        @Override
         public int getNightDisplayColorTemperature(Context context) {
             return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
                     mDefaultNightModeColorTemperature);
@@ -1239,7 +1284,7 @@
         }
 
         @Override
-        public void unRegisterDisplayListener(Context context,
+        public void unregisterDisplayListener(Context context,
                 DisplayManager.DisplayListener listener) {
             mDisplayListener = null;
         }
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 759976f..365cbae 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.Manifest.permission.ADD_MIRROR_DISPLAY;
 import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
 import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
 import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
@@ -232,6 +233,7 @@
     private static final String DISPLAY_GROUP_EVENT_ADDED = "DISPLAY_GROUP_EVENT_ADDED";
     private static final String DISPLAY_GROUP_EVENT_REMOVED = "DISPLAY_GROUP_EVENT_REMOVED";
     private static final String DISPLAY_GROUP_EVENT_CHANGED = "DISPLAY_GROUP_EVENT_CHANGED";
+    private static final String TOPOLOGY_CHANGED_EVENT = "TOPOLOGY_CHANGED_EVENT";
 
     @Rule(order = 0)
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -1385,19 +1387,23 @@
     }
 
     /**
-     * Tests that it's not allowed to create an auto-mirror virtual display without
-     * CAPTURE_VIDEO_OUTPUT permission or a virtual device that can mirror displays
+     * Tests that it is not allowed to create an auto-mirror virtual display for a virtual device
+     * without ADD_MIRROR_DISPLAY permission / without the mirror display capability.
      */
     @Test
-    public void createAutoMirrorDisplay_withoutPermissionOrAllowedVirtualDevice_throwsException()
-            throws Exception {
+    public void createAutoMirrorDisplay_withoutPermission_throwsException() throws Exception {
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
         registerDefaultDisplays(displayManager);
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
         IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
         when(virtualDevice.getDeviceId()).thenReturn(1);
-        when(virtualDevice.canCreateMirrorDisplays()).thenReturn(false);
+        if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+            when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+                    .thenReturn(PackageManager.PERMISSION_DENIED);
+        } else {
+            when(virtualDevice.canCreateMirrorDisplays()).thenReturn(false);
+        }
         when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
         when(mContext.checkCallingPermission(CAPTURE_VIDEO_OUTPUT)).thenReturn(
                 PackageManager.PERMISSION_DENIED);
@@ -1428,7 +1434,12 @@
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
         IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
         when(virtualDevice.getDeviceId()).thenReturn(1);
-        when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+        if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+            when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+                    .thenReturn(PackageManager.PERMISSION_GRANTED);
+        } else {
+            when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+        }
         when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
 
         // Create an auto-mirror virtual display using a virtual device.
@@ -1461,7 +1472,12 @@
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
         IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
         when(virtualDevice.getDeviceId()).thenReturn(1);
-        when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+        if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+            when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+                    .thenReturn(PackageManager.PERMISSION_GRANTED);
+        } else {
+            when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+        }
         when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
 
         // Create an auto-mirror virtual display using a virtual device.
@@ -1528,7 +1544,12 @@
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
         IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
         when(virtualDevice.getDeviceId()).thenReturn(1);
-        when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+        if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+            when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+                    .thenReturn(PackageManager.PERMISSION_GRANTED);
+        } else {
+            when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+        }
         when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
         when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -1564,7 +1585,12 @@
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
         IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
         when(virtualDevice.getDeviceId()).thenReturn(1);
-        when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+        if (android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole()) {
+            when(mContext.checkCallingPermission(ADD_MIRROR_DISPLAY))
+                    .thenReturn(PackageManager.PERMISSION_GRANTED);
+        } else {
+            when(virtualDevice.canCreateMirrorDisplays()).thenReturn(true);
+        }
         when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
 
         // Create an auto-mirror virtual display using a virtual device.
@@ -3691,8 +3717,7 @@
                 DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 1);
         manageDisplaysPermission(/* granted= */ true);
         when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
         DisplayManagerService.BinderService displayManagerBinderService =
                 displayManager.new BinderService();
@@ -3710,8 +3735,7 @@
     public void testGetDisplayTopology_NullIfFlagDisabled() {
         manageDisplaysPermission(/* granted= */ true);
         when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(false);
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
         DisplayManagerService.BinderService displayManagerBinderService =
                 displayManager.new BinderService();
@@ -3725,8 +3749,7 @@
     @Test
     public void testGetDisplayTopology_withoutPermission_shouldThrowException() {
         when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
         DisplayManagerService.BinderService displayManagerBinderService =
                 displayManager.new BinderService();
@@ -3740,8 +3763,7 @@
     public void testSetDisplayTopology() {
         manageDisplaysPermission(/* granted= */ true);
         when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
         DisplayManagerService.BinderService displayManagerBinderService =
                 displayManager.new BinderService();
@@ -3754,8 +3776,7 @@
     @Test
     public void testSetDisplayTopology_withoutPermission_shouldThrowException() {
         when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
         DisplayManagerService.BinderService displayManagerBinderService =
                 displayManager.new BinderService();
@@ -3766,6 +3787,49 @@
                 () -> displayManagerBinderService.setDisplayTopology(new DisplayTopology()));
     }
 
+    @Test
+    public void testShouldNotifyTopologyChanged() {
+        manageDisplaysPermission(/* granted= */ true);
+        when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        displayManagerBinderService.registerCallbackWithEventMask(callback,
+                DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED);
+        waitForIdleHandler(handler);
+
+        displayManagerBinderService.setDisplayTopology(new DisplayTopology());
+        waitForIdleHandler(handler);
+
+        assertThat(callback.receivedEvents()).containsExactly(TOPOLOGY_CHANGED_EVENT);
+    }
+
+    @Test
+    public void testShouldNotNotifyTopologyChanged_WhenClientIsNotSubscribed() {
+        manageDisplaysPermission(/* granted= */ true);
+        when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+
+        // Only subscribe to display events, not topology events
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        displayManagerBinderService.registerCallbackWithEventMask(callback,
+                STANDARD_DISPLAY_EVENTS);
+        waitForIdleHandler(handler);
+
+        displayManagerBinderService.setDisplayTopology(new DisplayTopology());
+        waitForIdleHandler(handler);
+
+        assertThat(callback.receivedEvents()).isEmpty();
+    }
+
     private void initDisplayPowerController(DisplayManagerInternal localService) {
         localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
             @Override
@@ -4217,6 +4281,12 @@
             eventSeen(DISPLAY_GROUP_EVENT_CHANGED);
         }
 
+        @Override
+        public void onTopologyChanged(DisplayTopology topology) {
+            mReceivedEvents.add(TOPOLOGY_CHANGED_EVENT);
+            eventSeen(TOPOLOGY_CHANGED_EVENT);
+        }
+
         public void clear() {
             mReceivedEvents.clear();
         }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
index a2d2a81..5d42713 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
@@ -20,22 +20,26 @@
 import android.util.DisplayMetrics
 import android.view.Display
 import android.view.DisplayInfo
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.mockito.ArgumentMatchers.anyFloat
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
-import java.util.function.BooleanSupplier
 
 class DisplayTopologyCoordinatorTest {
     private lateinit var coordinator: DisplayTopologyCoordinator
     private val displayInfo = DisplayInfo()
+    private val topologyChangeExecutor = Runnable::run
 
     private val mockTopology = mock<DisplayTopology>()
-    private val mockIsExtendedDisplayEnabled = mock<BooleanSupplier>()
+    private val mockTopologyCopy = mock<DisplayTopology>()
+    private val mockIsExtendedDisplayEnabled = mock<() -> Boolean>()
+    private val mockTopologyChangedCallback = mock<(DisplayTopology) -> Unit>()
 
     @Before
     fun setUp() {
@@ -47,13 +51,14 @@
         val injector = object : DisplayTopologyCoordinator.Injector() {
             override fun getTopology() = mockTopology
         }
-        coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayEnabled)
+        whenever(mockIsExtendedDisplayEnabled()).thenReturn(true)
+        whenever(mockTopology.copy()).thenReturn(mockTopologyCopy)
+        coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayEnabled,
+            mockTopologyChangedCallback, topologyChangeExecutor, DisplayManagerService.SyncRoot())
     }
 
     @Test
     fun addDisplay() {
-        whenever(mockIsExtendedDisplayEnabled.asBoolean).thenReturn(true)
-
         coordinator.onDisplayAdded(displayInfo)
 
         val widthDp = displayInfo.logicalWidth * (DisplayMetrics.DENSITY_DEFAULT.toFloat()
@@ -61,24 +66,43 @@
         val heightDp = displayInfo.logicalHeight * (DisplayMetrics.DENSITY_DEFAULT.toFloat()
                 / displayInfo.logicalDensityDpi)
         verify(mockTopology).addDisplay(displayInfo.displayId, widthDp, heightDp)
+        verify(mockTopologyChangedCallback).invoke(mockTopologyCopy)
     }
 
     @Test
     fun addDisplay_extendedDisplaysDisabled() {
-        whenever(mockIsExtendedDisplayEnabled.asBoolean).thenReturn(false)
+        whenever(mockIsExtendedDisplayEnabled()).thenReturn(false)
 
         coordinator.onDisplayAdded(displayInfo)
 
         verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
+        verify(mockTopologyChangedCallback, never()).invoke(any())
     }
 
     @Test
     fun addDisplay_notInDefaultDisplayGroup() {
-        whenever(mockIsExtendedDisplayEnabled.asBoolean).thenReturn(true)
         displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1
 
         coordinator.onDisplayAdded(displayInfo)
 
         verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat())
+        verify(mockTopologyChangedCallback, never()).invoke(any())
+    }
+
+    @Test
+    fun getTopology_copy() {
+        assertThat(coordinator.topology).isEqualTo(mockTopologyCopy)
+    }
+
+    @Test
+    fun setTopology_normalize() {
+        val topology = mock<DisplayTopology>()
+        val topologyCopy = mock<DisplayTopology>()
+        whenever(topology.copy()).thenReturn(topologyCopy)
+
+        coordinator.topology = topology
+
+        verify(topology).normalize()
+        verify(mockTopologyChangedCallback).invoke(topologyCopy)
     }
 }
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index b002a1f..241dc10 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -327,6 +329,23 @@
     }
 
     @Test
+    public void testBrightnessConfigurationFromDisplayDevice() {
+        mDisplayDeviceInfo.brightnessMinimum = 0.12f;
+        mDisplayDeviceInfo.brightnessDim = 0.34f;
+        mDisplayDeviceInfo.brightnessDefault = 0.56f;
+        mDisplayDeviceInfo.brightnessMaximum = 0.78f;
+
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
+        mLogicalDisplay.updateLocked(mDeviceRepo, mSyntheticModeManager);
+
+        DisplayInfo info = mLogicalDisplay.getDisplayInfoLocked();
+        assertThat(info.brightnessMinimum).isEqualTo(0.12f);
+        assertThat(info.brightnessDim).isEqualTo(0.34f);
+        assertThat(info.brightnessDefault).isEqualTo(0.56f);
+        assertThat(info.brightnessMaximum).isEqualTo(0.78f);
+    }
+
+    @Test
     public void testGetDisplayPosition() {
         Point expectedPosition = new Point(0, 0);
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
index e0b0fec..dbd5c65 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -25,10 +27,12 @@
 import android.hardware.display.VirtualDisplayConfig;
 import android.media.projection.IMediaProjection;
 import android.os.IBinder;
+import android.os.PowerManager;
 import android.os.Process;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.testing.TestableContext;
+import android.view.Display;
 import android.view.Surface;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -56,6 +60,9 @@
     private static final int MAX_DEVICES = 3;
     private static final int MAX_DEVICES_PER_PACKAGE = 2;
 
+    private static final float DEFAULT_BRIGHTNESS = 0.34f;
+    private static final float DIM_BRIGHTNESS = 0.12f;
+
     @Rule
     public final TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getInstrumentation().getContext());
@@ -123,6 +130,64 @@
     }
 
     @Test
+    public void testCreateVirtualDisplay_createDisplayDeviceInfoFromDefaults() {
+        VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
+                "testDisplayName", /* width= */ 640, /* height= */ 480, /* densityDpi= */ 240)
+                .build();
+
+        final String packageName = "testpackage";
+        final String displayUniqueId = VirtualDisplayAdapter.generateDisplayUniqueId(
+                packageName, Process.myUid(), config);
+
+        DisplayDevice displayDevice = mAdapter.createVirtualDisplayLocked(
+                mMockCallback, /* projection= */ null, /* ownerUid= */ 10,
+                packageName, displayUniqueId, /* surface= */ null, /* flags= */ 0, config);
+
+        assertNotNull(displayDevice);
+        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+        assertNotNull(info);
+
+        assertThat(info.width).isEqualTo(640);
+        assertThat(info.height).isEqualTo(480);
+        assertThat(info.densityDpi).isEqualTo(240);
+        assertThat(info.xDpi).isEqualTo(240);
+        assertThat(info.yDpi).isEqualTo(240);
+        assertThat(info.name).isEqualTo("testDisplayName");
+        assertThat(info.uniqueId).isEqualTo(displayUniqueId);
+        assertThat(info.ownerPackageName).isEqualTo(packageName);
+        assertThat(info.ownerUid).isEqualTo(10);
+        assertThat(info.type).isEqualTo(Display.TYPE_VIRTUAL);
+        assertThat(info.brightnessMinimum).isEqualTo(PowerManager.BRIGHTNESS_MIN);
+        assertThat(info.brightnessMaximum).isEqualTo(PowerManager.BRIGHTNESS_MAX);
+        assertThat(info.brightnessDefault).isEqualTo(PowerManager.BRIGHTNESS_MIN);
+        assertThat(info.brightnessDim).isEqualTo(PowerManager.BRIGHTNESS_INVALID);
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_createDisplayDeviceInfoFromVirtualDisplayConfig() {
+        VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
+                "testDisplayName", /* width= */ 640, /* height= */ 480, /* densityDpi= */ 240)
+                .setDefaultBrightness(DEFAULT_BRIGHTNESS)
+                .setDimBrightness(DIM_BRIGHTNESS)
+                .build();
+
+        final String packageName = "testpackage";
+        final String displayUniqueId = VirtualDisplayAdapter.generateDisplayUniqueId(
+                packageName, Process.myUid(), config);
+
+        DisplayDevice displayDevice = mAdapter.createVirtualDisplayLocked(
+                mMockCallback, /* projection= */ null, /* ownerUid= */ 10,
+                packageName, displayUniqueId, /* surface= */ null, /* flags= */ 0, config);
+
+        assertNotNull(displayDevice);
+        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+        assertNotNull(info);
+
+        assertThat(info.brightnessDefault).isEqualTo(DEFAULT_BRIGHTNESS);
+        assertThat(info.brightnessDim).isEqualTo(DIM_BRIGHTNESS);
+    }
+
+    @Test
     public void testCreatesVirtualDisplay_checkGeneratedDisplayUniqueIdPrefix() {
         VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("test", /* width= */ 1,
                 /* height= */ 1, /* densityDpi= */ 1).build();
@@ -335,10 +400,6 @@
             @Override
             public void onStopped() {
             }
-
-            @Override
-            public void onRequestedBrightnessChanged(float brightness) {
-            }
         };
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 2aafdfa..66e9c98 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -19,8 +19,9 @@
 import static android.view.Display.STATE_OFF;
 import static android.view.Display.STATE_ON;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
@@ -33,7 +34,6 @@
 import android.hardware.SensorManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Handler;
-import android.os.PowerManager;
 import android.provider.DeviceConfig;
 import android.testing.TestableContext;
 
@@ -83,8 +83,6 @@
     @Mock
     private LightSensorController mMockLightSensorController;
     @Mock
-    private BrightnessClamper<BrightnessClamperController.DisplayDeviceData> mMockClamper;
-    @Mock
     private DisplayManagerFlags mFlags;
     @Mock
     private BrightnessModifier mMockModifier;
@@ -93,6 +91,8 @@
     @Mock
     private TestDisplayListenerModifier mMockDisplayListenerModifier;
     @Mock
+    private TestDeviceConfigListenerModifier mMockDeviceConfigListenerModifier;
+    @Mock
     private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
 
     @Mock
@@ -104,8 +104,9 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mTestInjector = new TestInjector(List.of(mMockClamper),
-                List.of(mMockModifier, mMockStatefulModifier, mMockDisplayListenerModifier));
+        mTestInjector = new TestInjector(
+                List.of(mMockModifier, mMockStatefulModifier,
+                        mMockDisplayListenerModifier, mMockDeviceConfigListenerModifier));
         when(mMockDisplayDeviceData.getDisplayId()).thenReturn(DISPLAY_ID);
         when(mMockDisplayDeviceData.getAmbientLightSensor()).thenReturn(mMockSensorData);
 
@@ -150,7 +151,7 @@
     }
 
     @Test
-    public void testDelegatesPropertiesChangeToClamper() {
+    public void testDelegatesPropertiesChangeToDeviceConfigLisener() {
         ArgumentCaptor<DeviceConfig.OnPropertiesChangedListener> captor = ArgumentCaptor.forClass(
                 DeviceConfig.OnPropertiesChangedListener.class);
         verify(mMockDeviceConfigParameterProvider)
@@ -158,14 +159,7 @@
 
         captor.getValue().onPropertiesChanged(mMockProperties);
 
-        verify(mMockClamper).onDeviceConfigChanged();
-    }
-
-    @Test
-    public void testOnDisplayChanged_DelegatesToClamper() {
-        mClamperController.onDisplayChanged(mMockDisplayDeviceData);
-
-        verify(mMockClamper).onDisplayChanged(mMockDisplayDeviceData);
+        verify(mMockDeviceConfigListenerModifier).onDeviceConfigChanged();
     }
 
     @Test
@@ -225,105 +219,6 @@
     }
 
     @Test
-    public void testClamp_inactiveClamperNotApplied() {
-        float initialBrightness = 0.8f;
-        boolean initialSlowChange = true;
-        float clampedBrightness = 0.6f;
-        float customAnimationRate = 0.01f;
-        when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
-        when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
-        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
-        when(mMockClamper.isActive()).thenReturn(false);
-        mTestInjector.mCapturedChangeListener.onChanged();
-        mTestHandler.flush();
-
-        DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState,
-                mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
-
-        assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
-        assertEquals(PowerManager.BRIGHTNESS_MAX, state.getMaxBrightness(), FLOAT_TOLERANCE);
-        assertEquals(0,
-                state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
-        assertEquals(-1, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
-        assertEquals(initialSlowChange, state.isSlowChange());
-    }
-
-    @Test
-    public void testClamp_activeClamperApplied_brightnessAboveMax() {
-        float initialBrightness = 0.8f;
-        boolean initialSlowChange = true;
-        float clampedBrightness = 0.6f;
-        float customAnimationRate = 0.01f;
-        when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
-        when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
-        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
-        when(mMockClamper.isActive()).thenReturn(true);
-        mTestInjector.mCapturedChangeListener.onChanged();
-        mTestHandler.flush();
-
-        DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState,
-                mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
-
-        assertEquals(clampedBrightness, state.getBrightness(), FLOAT_TOLERANCE);
-        assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
-        assertEquals(BrightnessReason.MODIFIER_THROTTLED,
-                state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
-        assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
-        assertFalse(state.isSlowChange());
-    }
-
-    @Test
-    public void testClamp_activeClamperApplied_brightnessBelowMax() {
-        float initialBrightness = 0.6f;
-        boolean initialSlowChange = true;
-        float clampedBrightness = 0.8f;
-        float customAnimationRate = 0.01f;
-        when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
-        when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
-        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
-        when(mMockClamper.isActive()).thenReturn(true);
-        mTestInjector.mCapturedChangeListener.onChanged();
-        mTestHandler.flush();
-
-        DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState,
-                mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
-
-        assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
-        assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
-        assertEquals(BrightnessReason.MODIFIER_THROTTLED,
-                state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
-        assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
-        assertFalse(state.isSlowChange());
-    }
-
-    @Test
-    public void testClamp_activeClamperAppliedTwoTimes_keepsSlowChange() {
-        float initialBrightness = 0.8f;
-        boolean initialSlowChange = true;
-        float clampedBrightness = 0.6f;
-        float customAnimationRate = 0.01f;
-        when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
-        when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER);
-        when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
-        when(mMockClamper.isActive()).thenReturn(true);
-        mTestInjector.mCapturedChangeListener.onChanged();
-        mTestHandler.flush();
-        // first call of clamp method
-        mClamperController.clamp(mDisplayBrightnessState, mMockRequest, initialBrightness,
-                initialSlowChange, STATE_ON);
-        // immediately second call of clamp method
-        DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState,
-                mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
-
-        assertEquals(clampedBrightness, state.getBrightness(), FLOAT_TOLERANCE);
-        assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
-        assertEquals(BrightnessReason.MODIFIER_THROTTLED,
-                state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
-        assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
-        assertEquals(initialSlowChange, state.isSlowChange());
-    }
-
-    @Test
     public void testClamp_activeClamperApplied_confirmBrightnessOverrideStateReturned() {
         float initialBrightness = 0.8f;
         boolean initialSlowChange = false;
@@ -352,7 +247,6 @@
         mClamperController.stop();
         verify(mMockLightSensorController).stop();
         verify(mMockModifier).stop();
-        verify(mMockClamper).stop();
     }
 
     @Test
@@ -377,6 +271,24 @@
         verify(mMockExternalListener).onChanged();
     }
 
+    @Test
+    public void test_doesNotScheduleRecalculateBeforeStart() {
+        mTestInjector = new TestInjector(List.of()) {
+            @Override
+            List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
+                    Handler handler, BrightnessClamperController.ClamperChangeListener listener,
+                    BrightnessClamperController.DisplayDeviceData displayDeviceData,
+                    float currentBrightness) {
+                listener.onChanged();
+                return super.getModifiers(flags, context, handler, listener, displayDeviceData,
+                        currentBrightness);
+            }
+        };
+        mClamperController = createBrightnessClamperController();
+
+        assertThat(mTestHandler.getPendingMessages()).isEmpty();
+    }
+
     private BrightnessClamperController createBrightnessClamperController() {
         return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener,
                 mMockDisplayDeviceData, mMockContext, mFlags, mSensorManager, 0);
@@ -390,20 +302,18 @@
             BrightnessClamperController.StatefulModifier {
     }
 
+    interface TestDeviceConfigListenerModifier extends  BrightnessStateModifier,
+            BrightnessClamperController.DeviceConfigListener {
+
+    }
+
     private class TestInjector extends BrightnessClamperController.Injector {
 
-        private final List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>>
-                mClampers;
         private final List<BrightnessStateModifier> mModifiers;
-
         private BrightnessClamperController.ClamperChangeListener mCapturedChangeListener;
         private LightSensorController.LightSensorListener mCapturedLightSensorListener;
 
-        private TestInjector(
-                List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>>
-                        clampers,
-                List<BrightnessStateModifier> modifiers) {
-            mClampers = clampers;
+        private TestInjector(List<BrightnessStateModifier> modifiers) {
             mModifiers = modifiers;
         }
 
@@ -413,19 +323,11 @@
         }
 
         @Override
-        List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>> getClampers(
-                Handler handler,
-                BrightnessClamperController.ClamperChangeListener clamperChangeListener,
-                BrightnessClamperController.DisplayDeviceData data,
-                DisplayManagerFlags flags, Context context, float currentBrightness) {
-            mCapturedChangeListener = clamperChangeListener;
-            return mClampers;
-        }
-
-        @Override
         List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
                 Handler handler, BrightnessClamperController.ClamperChangeListener listener,
-                BrightnessClamperController.DisplayDeviceData displayDeviceData) {
+                BrightnessClamperController.DisplayDeviceData displayDeviceData,
+                float currentBrightness) {
+            mCapturedChangeListener = listener;
             return mModifiers;
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
deleted file mode 100644
index c4898da..0000000
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
+++ /dev/null
@@ -1,239 +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.server.display.brightness.clamper;
-
-import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-import android.os.IThermalService;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.Temperature;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.server.display.DisplayDeviceConfig;
-import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
-import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
-import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
-import com.android.server.display.feature.DeviceConfigParameterProvider;
-import com.android.server.testutils.FakeDeviceConfigInterface;
-import com.android.server.testutils.TestHandler;
-
-import junitparams.JUnitParamsRunner;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-@RunWith(JUnitParamsRunner.class)
-public class BrightnessPowerClamperTest {
-    private static final String TAG = "BrightnessPowerClamperTest";
-    private static final float FLOAT_TOLERANCE = 0.001f;
-
-    private static final String DISPLAY_ID = "displayId";
-    @Mock
-    private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
-    private TestPmicMonitor mPmicMonitor;
-    private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
-            new FakeDeviceConfigInterface();
-    private final TestHandler mTestHandler = new TestHandler(null);
-    private final TestInjector mTestInjector = new TestInjector();
-    private BrightnessPowerClamper mClamper;
-    private final float mCurrentBrightness = 0.6f;
-    private PowerChangeListener mPowerChangeListener;
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mClamper = new BrightnessPowerClamper(mTestInjector, mTestHandler,
-                mMockClamperChangeListener, new TestPowerData(), mCurrentBrightness);
-        mPowerChangeListener = mClamper.getPowerChangeListener();
-        mPmicMonitor = mTestInjector.getPmicMonitor(mPowerChangeListener, null, 5, 10);
-        mPmicMonitor.setPowerChangeListener(mPowerChangeListener);
-        mTestHandler.flush();
-    }
-
-    @Test
-    public void testTypeIsPower() {
-        assertEquals(BrightnessClamper.Type.POWER, mClamper.getType());
-    }
-
-    @Test
-    public void testNoThrottlingData() {
-        assertFalse(mClamper.isActive());
-        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-    }
-
-    @Test
-    public void testPowerThrottlingWithThermalLevelLight() throws RemoteException {
-        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
-        mTestHandler.flush();
-        assertFalse(mClamper.isActive());
-        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
-        // update a new device config for power-throttling.
-        mClamper.onDisplayChanged(new TestPowerData(
-                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f))));
-
-        mPmicMonitor.setAvgPowerConsumed(200f);
-        float expectedBrightness = 0.5f;
-        expectedBrightness = expectedBrightness * mCurrentBrightness;
-
-        mTestHandler.flush();
-        // Assume current brightness as max, as there is no throttling.
-        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-    }
-
-    @Test
-    public void testPowerThrottlingWithThermalLevelSevere() throws RemoteException {
-        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
-        mTestHandler.flush();
-        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
-        // update a new device config for power-throttling.
-        mClamper.onDisplayChanged(new TestPowerData(
-                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f))));
-
-        mPmicMonitor.setAvgPowerConsumed(200f);
-        float expectedBrightness = 0.5f;
-        expectedBrightness = expectedBrightness * mCurrentBrightness;
-        mTestHandler.flush();
-        // Assume current brightness as max, as there is no throttling.
-        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-    }
-
-    @Test
-    public void testPowerThrottlingRemoveBrightnessCap() throws RemoteException {
-        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
-        mTestHandler.flush();
-        assertFalse(mClamper.isActive());
-        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-
-        // update a new device config for power-throttling.
-        mClamper.onDisplayChanged(new TestPowerData(
-                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f))));
-
-        mPmicMonitor.setAvgPowerConsumed(200f);
-        float expectedBrightness = 0.5f;
-        expectedBrightness = expectedBrightness * mCurrentBrightness;
-        mTestHandler.flush();
-
-        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_NONE);
-
-        mPmicMonitor.setAvgPowerConsumed(100f);
-        // No cap applied for Temperature.THROTTLING_NONE
-        expectedBrightness = PowerManager.BRIGHTNESS_MAX;
-        mTestHandler.flush();
-
-        // clamper should not be active anymore.
-        assertFalse(mClamper.isActive());
-        // Assume current brightness as max, as there is no throttling.
-        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-    }
-
-
-    private static class TestPmicMonitor extends PmicMonitor {
-        private Temperature mCurrentTemperature;
-        private PowerChangeListener mListener;
-        TestPmicMonitor(PowerChangeListener listener,
-                        IThermalService thermalService,
-                        int pollingTimeMax, int pollingTimeMin) {
-            super(listener, thermalService, pollingTimeMax, pollingTimeMin);
-        }
-        public void setAvgPowerConsumed(float power) {
-            int status = mCurrentTemperature.getStatus();
-            mListener.onChanged(power, status);
-        }
-        public void setThermalStatus(@Temperature.ThrottlingStatus int status) {
-            mCurrentTemperature = new Temperature(100, Temperature.TYPE_SKIN, "test_temp", status);
-        }
-        public void setPowerChangeListener(PowerChangeListener listener) {
-            mListener = listener;
-        }
-    }
-
-    private class TestInjector extends BrightnessPowerClamper.Injector {
-        @Override
-        TestPmicMonitor getPmicMonitor(PowerChangeListener listener,
-                                       IThermalService thermalService,
-                                       int minPollingTimeMillis, int maxPollingTimeMillis) {
-            mPmicMonitor = new TestPmicMonitor(listener, thermalService, maxPollingTimeMillis,
-                    minPollingTimeMillis);
-            return mPmicMonitor;
-        }
-
-        @Override
-        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
-            return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
-        }
-    }
-
-    private static class TestPowerData implements BrightnessPowerClamper.PowerData {
-
-        private final String mUniqueDisplayId;
-        private final String mDataId;
-        private final PowerThrottlingData mData;
-        private final PowerThrottlingConfigData mConfigData;
-
-        private TestPowerData() {
-            this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null);
-        }
-
-        private TestPowerData(List<ThrottlingLevel> data) {
-            this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data);
-        }
-
-        private TestPowerData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) {
-            mUniqueDisplayId = uniqueDisplayId;
-            mDataId = dataId;
-            mData = PowerThrottlingData.create(data);
-            mConfigData = new PowerThrottlingConfigData(0.1f, 10, 20, 10);
-        }
-
-        @NonNull
-        @Override
-        public String getUniqueDisplayId() {
-            return mUniqueDisplayId;
-        }
-
-        @NonNull
-        @Override
-        public String getPowerThrottlingDataId() {
-            return mDataId;
-        }
-
-        @Nullable
-        @Override
-        public PowerThrottlingData getPowerThrottlingData() {
-            return mData;
-        }
-
-        @Nullable
-        @Override
-        public PowerThrottlingConfigData getPowerThrottlingConfigData() {
-            return mConfigData;
-        }
-    }
-}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerModifierTest.java
new file mode 100644
index 0000000..b438d74
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerModifierTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.server.display.brightness.clamper;
+
+import static android.os.PowerManager.BRIGHTNESS_MAX;
+
+import static com.android.server.display.DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+import static com.android.server.display.brightness.clamper.BrightnessPowerModifier.PowerChangeListener;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.IBinder;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.Temperature;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.testutils.FakeDeviceConfigInterface;
+import com.android.server.testutils.TestHandler;
+
+import junitparams.JUnitParamsRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(JUnitParamsRunner.class)
+public class BrightnessPowerModifierTest {
+    private static final String DISPLAY_ID = "displayId";
+    private static final int NO_MODIFIER = 0;
+    private static final float CUSTOM_ANIMATION_RATE = 10f;
+    private static final PowerThrottlingConfigData DEFAULT_CONFIG = new PowerThrottlingConfigData(
+            0.1f, CUSTOM_ANIMATION_RATE, 20, 10);
+    private static final float DEFAULT_BRIGHTNESS = 0.6f;
+
+    @Mock
+    private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
+    @Mock
+    private DisplayDeviceConfig mMockDisplayDeviceConfig;
+    @Mock
+    private IBinder mMockBinder;
+    @Mock
+    private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+    private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
+            new FakeDeviceConfigInterface();
+    private final TestHandler mTestHandler = new TestHandler(null);
+    private final TestInjector mTestInjector = new TestInjector();
+    private BrightnessPowerModifier mModifier;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockDisplayDeviceConfig.getPowerThrottlingConfigData()).thenReturn(DEFAULT_CONFIG);
+        mModifier = new BrightnessPowerModifier(mTestInjector, mTestHandler,
+                mMockClamperChangeListener, ClamperTestUtilsKt.createDisplayDeviceData(
+                mMockDisplayDeviceConfig, mMockBinder, DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID,
+                DisplayDeviceConfig.DEFAULT_ID), DEFAULT_BRIGHTNESS);
+        mTestHandler.flush();
+    }
+
+    @Test
+    public void testNoThrottlingData() {
+        assertModifierState(DEFAULT_BRIGHTNESS,
+                BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+    }
+
+    @Test
+    public void testPowerThrottlingWithThermalLevelLight() throws RemoteException {
+        mTestInjector.mCapturedPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
+        mTestHandler.flush();
+        // no config yet, modifier inactive
+        assertModifierState(DEFAULT_BRIGHTNESS,
+                BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+
+        // update a new device config for power-throttling.
+        float powerQuota = 100f;
+        float avgPowerConsumed = 200f;
+        onDisplayChanged(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, powerQuota)));
+        mTestInjector.mCapturedPmicMonitor.setAvgPowerConsumed(avgPowerConsumed);
+
+        float expectedBrightnessCap = (powerQuota / avgPowerConsumed) * DEFAULT_BRIGHTNESS;
+        mTestHandler.flush();
+
+        assertModifierState(DEFAULT_BRIGHTNESS,
+                expectedBrightnessCap, expectedBrightnessCap, CUSTOM_ANIMATION_RATE, true);
+    }
+
+    @Test
+    public void testPowerThrottlingWithThermalLevelSevere() throws RemoteException {
+        mTestInjector.mCapturedPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
+        mTestHandler.flush();
+        // no config yet, modifier inactive
+        assertModifierState(DEFAULT_BRIGHTNESS,
+                BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+
+        // update a new device config for power-throttling.
+        float powerQuota = 100f;
+        float avgPowerConsumed = 200f;
+        onDisplayChanged(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, powerQuota)));
+
+        mTestInjector.mCapturedPmicMonitor.setAvgPowerConsumed(avgPowerConsumed);
+        float expectedBrightnessCap = (powerQuota / avgPowerConsumed) * DEFAULT_BRIGHTNESS;
+        mTestHandler.flush();
+        // Assume current brightness as max, as there is no throttling.
+        assertModifierState(DEFAULT_BRIGHTNESS,
+                expectedBrightnessCap, expectedBrightnessCap, CUSTOM_ANIMATION_RATE, true);
+    }
+
+    @Test
+    public void testPowerThrottlingRemoveBrightnessCap() throws RemoteException {
+        mTestInjector.mCapturedPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
+        mTestHandler.flush();
+        // no config yet, modifier inactive
+        assertModifierState(DEFAULT_BRIGHTNESS,
+                BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+
+        // update a new device config for power-throttling.
+        onDisplayChanged(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f)));
+        mTestInjector.mCapturedPmicMonitor.setAvgPowerConsumed(200f);
+
+        mTestInjector.mCapturedPmicMonitor.setThermalStatus(Temperature.THROTTLING_NONE);
+        // No cap applied for Temperature.THROTTLING_NONE
+        mTestHandler.flush();
+
+        // Modifier should not be active anymore, no throttling
+        assertModifierState(DEFAULT_BRIGHTNESS,
+                BRIGHTNESS_MAX, DEFAULT_BRIGHTNESS, CUSTOM_ANIMATION_RATE_NOT_SET, false);
+    }
+
+    private void onDisplayChanged(List<ThrottlingLevel> throttlingLevels) {
+        Map<String, PowerThrottlingData> throttlingLevelsMap = new HashMap<>();
+        throttlingLevelsMap.put(DisplayDeviceConfig.DEFAULT_ID,
+                PowerThrottlingData.create(throttlingLevels));
+        when(mMockDisplayDeviceConfig.getPowerThrottlingDataMapByThrottlingId())
+                .thenReturn(throttlingLevelsMap);
+        mModifier.onDisplayChanged(ClamperTestUtilsKt.createDisplayDeviceData(
+                mMockDisplayDeviceConfig, mMockBinder, DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID,
+                DisplayDeviceConfig.DEFAULT_ID));
+    }
+
+    private void assertModifierState(
+            float currentBrightness,
+            float maxBrightness, float brightness, float customAnimationRate,
+            boolean isActive) {
+        ModifiersAggregatedState modifierState = new ModifiersAggregatedState();
+        DisplayBrightnessState.Builder stateBuilder = DisplayBrightnessState.builder();
+        stateBuilder.setBrightness(currentBrightness);
+
+        int maxBrightnessReason = isActive ? BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC
+                : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+        int modifier = isActive ? BrightnessReason.MODIFIER_THROTTLED : NO_MODIFIER;
+
+        mModifier.applyStateChange(modifierState);
+        assertThat(modifierState.mMaxBrightness).isEqualTo(maxBrightness);
+        assertThat(modifierState.mMaxBrightnessReason).isEqualTo(maxBrightnessReason);
+
+        mModifier.apply(mMockRequest, stateBuilder);
+
+        assertThat(stateBuilder.getMaxBrightness())
+                .isWithin(BrightnessSynchronizer.EPSILON).of(maxBrightness);
+        assertThat(stateBuilder.getBrightness())
+                .isWithin(BrightnessSynchronizer.EPSILON).of(brightness);
+        assertThat(stateBuilder.getBrightnessMaxReason()).isEqualTo(maxBrightnessReason);
+        assertThat(stateBuilder.getBrightnessReason().getModifier()).isEqualTo(modifier);
+        assertThat(stateBuilder.getCustomAnimationRate()).isEqualTo(customAnimationRate);
+    }
+
+    private static class TestPmicMonitor extends PmicMonitor {
+        private Temperature mCurrentTemperature;
+        private float mCurrentAvgPower;
+
+        private final PowerChangeListener mListener;
+        TestPmicMonitor(PowerChangeListener listener,
+                        IThermalService thermalService,
+                        int pollingTimeMax, int pollingTimeMin) {
+            super(listener, thermalService, pollingTimeMax, pollingTimeMin);
+            mListener = listener;
+        }
+        public void setAvgPowerConsumed(float power) {
+            mCurrentAvgPower = power;
+            mListener.onChanged(mCurrentAvgPower, mCurrentTemperature.getStatus());
+        }
+        public void setThermalStatus(@Temperature.ThrottlingStatus int status) {
+            mCurrentTemperature = new Temperature(100, Temperature.TYPE_SKIN, "test_temp", status);
+            mListener.onChanged(mCurrentAvgPower, mCurrentTemperature.getStatus());
+        }
+    }
+
+    private class TestInjector extends BrightnessPowerModifier.Injector {
+        private TestPmicMonitor mCapturedPmicMonitor;
+        @NonNull
+        @Override
+        TestPmicMonitor getPmicMonitor(PowerChangeListener listener, IThermalService thermalService,
+                                       int minPollingTimeMillis, int maxPollingTimeMillis) {
+            mCapturedPmicMonitor = new TestPmicMonitor(listener, thermalService,
+                    maxPollingTimeMillis, minPollingTimeMillis);
+            return mCapturedPmicMonitor;
+        }
+
+        @Override
+        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+            return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
index be4e7c7..7e4042e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
@@ -44,6 +44,8 @@
     private static final float MIN_DIM_AMOUNT = 0.05f;
     private static final float DIM_CONFIG = 0.4f;
 
+    private static final int DISPLAY_ID = 3;
+
     @Mock
     private Context mMockContext;
 
@@ -66,9 +68,9 @@
                 R.dimen.config_screenBrightnessMinimumDimAmountFloat)).thenReturn(MIN_DIM_AMOUNT);
         when(mMockContext.getSystemService(PowerManager.class)).thenReturn(mMockPowerManager);
         when(mMockPowerManager.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)).thenReturn(DIM_CONFIG);
+                DISPLAY_ID, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)).thenReturn(DIM_CONFIG);
 
-        mModifier = new DisplayDimModifier(mMockContext);
+        mModifier = new DisplayDimModifier(DISPLAY_ID, mMockContext);
         mRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
     }
 
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 993569f..0d25426 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -392,3 +392,10 @@
     ],
     include_filters: ["com.android.server.StorageManagerServiceTest"],
 }
+
+test_module_config {
+    name: "FrameworksMockingServicesTests_service_batteryServiceTest",
+    base: "FrameworksMockingServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.BatteryServiceTest"],
+}
diff --git a/services/tests/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp
index 00543a8..94d4b95 100644
--- a/services/tests/mockingservicestests/jni/Android.bp
+++ b/services/tests/mockingservicestests/jni/Android.bp
@@ -22,8 +22,8 @@
     srcs: [
         ":lib_cachedAppOptimizer_native",
         ":lib_freezer_native",
-        ":lib_gameManagerService_native",
         ":lib_oomConnection_native",
+        ":lib_lazilyRegisteredServices_native",
         "onload.cpp",
     ],
 
@@ -54,6 +54,8 @@
         "android.hardware.graphics.bufferqueue@2.0",
         "android.hardware.graphics.common@1.2",
         "android.hardware.graphics.mapper@4.0",
+        "android.hardware.ir@1.0",
+        "android.hardware.vr@1.0",
         "android.hidl.token@1.0-utils",
     ],
 }
diff --git a/services/tests/mockingservicestests/jni/onload.cpp b/services/tests/mockingservicestests/jni/onload.cpp
index cb246d1..9b4c817 100644
--- a/services/tests/mockingservicestests/jni/onload.cpp
+++ b/services/tests/mockingservicestests/jni/onload.cpp
@@ -26,8 +26,8 @@
 namespace android {
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
 int register_android_server_am_Freezer(JNIEnv* env);
-int register_android_server_app_GameManagerService(JNIEnv* env);
 int register_android_server_am_OomConnection(JNIEnv* env);
+int register_android_server_utils_LazyJniRegistrar(JNIEnv* env);
 };
 
 using namespace android;
@@ -44,7 +44,7 @@
     ALOG_ASSERT(env, "Could not retrieve the env!");
     register_android_server_am_CachedAppOptimizer(env);
     register_android_server_am_Freezer(env);
-    register_android_server_app_GameManagerService(env);
     register_android_server_am_OomConnection(env);
+    register_android_server_utils_LazyJniRegistrar(env);
     return JNI_VERSION_1_4;
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
index 5e2f80b..1fbd53a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
@@ -79,6 +79,8 @@
     private static final int UPDATED_BATTERY_HEALTH = 3;
     private static final int CURRENT_CHARGE_COUNTER = 4680000;
     private static final int UPDATED_CHARGE_COUNTER = 4218000;
+    private static final int CURRENT_MAX_CHARGING_CURRENT = 298125;
+    private static final int UPDATED_MAX_CHARGING_CURRENT = 398125;
     private static final int HANDLER_IDLE_TIME_MS = 5000;
     @Rule
     public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
@@ -143,7 +145,7 @@
     @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
     public void onlyVoltageUpdated_lessThenOnePercent_broadcastNotSent() {
         mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
-                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
 
         waitForHandlerToExecute();
 
@@ -156,7 +158,8 @@
         mBatteryService.update(
                 createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
                         CURRENT_CHARGE_COUNTER,
-                        CURRENT_BATTERY_HEALTH));
+                        CURRENT_BATTERY_HEALTH,
+                        CURRENT_MAX_CHARGING_CURRENT));
 
         waitForHandlerToExecute();
 
@@ -165,13 +168,17 @@
 
     @Test
     @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
-    public void onlyVoltageUpdated_broadcastSent() {
+    public void voltageUpdated_withUpdateInChargingCurrent_broadcastSent() {
         mBatteryService.mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime() - 20000;
+        long lastChargingCurrentUpdateTime =
+                mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime;
         mBatteryService.update(createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
-                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH, UPDATED_MAX_CHARGING_CURRENT));
 
         waitForHandlerToExecute();
 
+        assertTrue(lastChargingCurrentUpdateTime
+                < mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime);
         verifyNumberOfTimesBroadcastSent(1);
     }
 
@@ -180,7 +187,8 @@
     public void onlyTempUpdated_lessThenOneDegreeCelsius_broadcastNotSent() {
         mBatteryService.update(
                 createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
-                        CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+                        CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH,
+                        CURRENT_MAX_CHARGING_CURRENT));
 
         waitForHandlerToExecute();
 
@@ -191,23 +199,31 @@
     @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
     public void tempUpdated_broadcastSent() {
         long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+        long lastChargingCurrentUpdateTime =
+                mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime;
         mBatteryService.update(
                 createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, TEMP_MORE_THEN_ONE_DEGREE_CELSIUS,
-                        UPDATED_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+                        UPDATED_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH,
+                        UPDATED_MAX_CHARGING_CURRENT));
 
         waitForHandlerToExecute();
 
         assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
+        assertTrue(lastChargingCurrentUpdateTime
+                < mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime);
         verifyNumberOfTimesBroadcastSent(1);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
-    public void batteryHealthUpdated_voltageAndTempConst_broadcastSent() {
+    public void batteryHealthUpdated_withOtherExtrasConstant_broadcastSent() {
+        long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+        long lastChargingCurrentUpdateTime =
+                mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime;
         mBatteryService.update(
-                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+                createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
                         CURRENT_CHARGE_COUNTER,
-                        UPDATED_BATTERY_HEALTH));
+                        UPDATED_BATTERY_HEALTH, UPDATED_MAX_CHARGING_CURRENT));
 
         waitForHandlerToExecute();
 
@@ -217,10 +233,13 @@
         mBatteryService.update(
                 createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
                         UPDATED_CHARGE_COUNTER,
-                        UPDATED_BATTERY_HEALTH));
+                        UPDATED_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
 
         waitForHandlerToExecute();
 
+        assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
+        assertTrue(lastChargingCurrentUpdateTime
+                < mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime);
         verifyNumberOfTimesBroadcastSent(1);
     }
 
@@ -228,7 +247,7 @@
     @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
     public void voltageUpdated_lessThanOnePercent_flagDisabled_broadcastSent() {
         mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
-                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
 
         waitForHandlerToExecute();
 
@@ -241,7 +260,7 @@
         mBatteryService.update(
                 createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
                         UPDATED_CHARGE_COUNTER,
-                        CURRENT_BATTERY_HEALTH));
+                        CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
 
         waitForHandlerToExecute();
 
@@ -254,7 +273,7 @@
         mBatteryService.update(
                 createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
                         UPDATED_CHARGE_COUNTER,
-                        CURRENT_BATTERY_HEALTH));
+                        CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
 
         waitForHandlerToExecute();
 
@@ -267,18 +286,51 @@
         mBatteryService.update(
                 createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
                         UPDATED_CHARGE_COUNTER,
-                        CURRENT_BATTERY_HEALTH));
+                        CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
 
         waitForHandlerToExecute();
 
         verifyNumberOfTimesBroadcastSent(1);
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void onlyMaxChargingCurrentUpdated_beforeFiveSeconds_broadcastNotSent() {
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+                        CURRENT_CHARGE_COUNTER,
+                        CURRENT_BATTERY_HEALTH,
+                        UPDATED_MAX_CHARGING_CURRENT));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(0);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void maxChargingCurrentUpdated_afterFiveSeconds_broadcastSent() {
+        mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime =
+                SystemClock.elapsedRealtime() - 5000;
+        long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+        mBatteryService.update(
+                createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+                        CURRENT_CHARGE_COUNTER,
+                        CURRENT_BATTERY_HEALTH,
+                        UPDATED_MAX_CHARGING_CURRENT));
+
+        waitForHandlerToExecute();
+
+        assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
+        verifyNumberOfTimesBroadcastSent(1);
+    }
+
     private HealthInfo createHealthInfo(
             int batteryVoltage,
             int batteryTemperature,
             int batteryChargeCounter,
-            int batteryHealth) {
+            int batteryHealth,
+            int maxChargingCurrent) {
         HealthInfo h = new HealthInfo();
         h.batteryVoltageMillivolts = batteryVoltage;
         h.batteryTemperatureTenthsCelsius = batteryTemperature;
@@ -287,7 +339,7 @@
         h.batteryHealth = batteryHealth;
         h.batteryPresent = true;
         h.batteryLevel = 100;
-        h.maxChargingCurrentMicroamps = 298125;
+        h.maxChargingCurrentMicroamps = maxChargingCurrent;
         h.batteryCurrentAverageMicroamps = -2812;
         h.batteryCurrentMicroamps = 298125;
         h.maxChargingVoltageMicrovolts = 3000;
@@ -308,7 +360,8 @@
         mBatteryService.update(
                 createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
                         CURRENT_CHARGE_COUNTER,
-                        CURRENT_BATTERY_HEALTH));
+                        CURRENT_BATTERY_HEALTH,
+                        CURRENT_MAX_CHARGING_CURRENT));
 
         waitForHandlerToExecute();
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index ace6aae..6defadf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -47,8 +47,10 @@
 import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK;
 import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE;
 import static com.android.server.am.ProcessList.NETWORK_STATE_UNBLOCK;
+
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -87,6 +89,7 @@
 import android.app.NotificationChannel;
 import android.app.SyncNotedAppOp;
 import android.app.backup.BackupAnnotations;
+import android.content.ClipData;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -98,6 +101,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ServiceInfo;
+import android.graphics.Rect;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
@@ -105,6 +109,7 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.IProgressListener;
+import android.os.IpcDataCache;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
@@ -201,6 +206,16 @@
 
     private static final String TEST_AUTHORITY = "test_authority";
     private static final String TEST_MIME_TYPE = "application/test_type";
+    private static final Uri TEST_URI = Uri.parse("content://com.example/people");
+    private static final int TEST_CREATOR_UID = 12345;
+    private static final String TEST_CREATOR_PACKAGE = "android.content.testCreatorPackage";
+    private static final String TEST_TYPE = "testType";
+    private static final String TEST_IDENTIFIER = "testIdentifier";
+    private static final String TEST_CATEGORY = "testCategory";
+    private static final String TEST_LAUNCH_TOKEN = "testLaunchToken";
+    private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PACKAGE,
+            "TestClass");
+    private static final int ALL_SET_FLAG = 0xFFFFFFFF;
 
     private static final int[] UID_RECORD_CHANGES = {
         UidRecord.CHANGE_PROCSTATE,
@@ -959,28 +974,37 @@
     @Test
     @SuppressWarnings("GuardedBy")
     public void testBroadcastStickyIntent_verifyTypeNotResolved() throws Exception {
-        final Intent intent = new Intent(TEST_ACTION1);
-        final Uri uri = new Uri.Builder()
-                .scheme(SCHEME_CONTENT)
-                .authority(TEST_AUTHORITY)
-                .path("green")
-                .build();
-        intent.setData(uri);
-        broadcastIntent(intent, null, true, TEST_MIME_TYPE, USER_ALL);
-        assertStickyBroadcasts(mAms.getStickyBroadcastsForTest(TEST_ACTION1, USER_ALL),
-                StickyBroadcast.create(intent, false, Process.myUid(), PROCESS_STATE_UNKNOWN,
-                        TEST_MIME_TYPE));
-        when(mContentResolver.getType(uri)).thenReturn(TEST_MIME_TYPE);
+        MockitoSession mockitoSession =
+                ExtendedMockito.mockitoSession().mockStatic(IpcDataCache.class).startMocking();
 
-        addUidRecord(TEST_UID, TEST_PACKAGE);
-        final ProcessRecord procRecord = mAms.getProcessRecordLocked(TEST_PACKAGE, TEST_UID);
-        final IntentFilter intentFilter = new IntentFilter(TEST_ACTION1);
-        intentFilter.addDataType(TEST_MIME_TYPE);
-        final Intent resultIntent = mAms.registerReceiverWithFeature(procRecord.getThread(),
-                TEST_PACKAGE, null, null, null, intentFilter, null, TEST_USER,
-                Context.RECEIVER_EXPORTED);
-        assertNotNull(resultIntent);
-        verify(mContentResolver, never()).getType(any());
+        try {
+            final Intent intent = new Intent(TEST_ACTION1);
+            final Uri uri = new Uri.Builder()
+                    .scheme(SCHEME_CONTENT)
+                    .authority(TEST_AUTHORITY)
+                    .path("green")
+                    .build();
+            intent.setData(uri);
+            broadcastIntent(intent, null, true, TEST_MIME_TYPE, USER_ALL);
+            assertStickyBroadcasts(mAms.getStickyBroadcastsForTest(TEST_ACTION1, USER_ALL),
+                    StickyBroadcast.create(intent, false, Process.myUid(), PROCESS_STATE_UNKNOWN,
+                            TEST_MIME_TYPE));
+            when(mContentResolver.getType(uri)).thenReturn(TEST_MIME_TYPE);
+            ExtendedMockito.doNothing().when(
+                    () -> IpcDataCache.invalidateCache(anyString(), anyString()));
+
+            addUidRecord(TEST_UID, TEST_PACKAGE);
+            final ProcessRecord procRecord = mAms.getProcessRecordLocked(TEST_PACKAGE, TEST_UID);
+            final IntentFilter intentFilter = new IntentFilter(TEST_ACTION1);
+            intentFilter.addDataType(TEST_MIME_TYPE);
+            final Intent resultIntent = mAms.registerReceiverWithFeature(procRecord.getThread(),
+                    TEST_PACKAGE, null, null, null, intentFilter, null, TEST_USER,
+                    Context.RECEIVER_EXPORTED);
+            assertNotNull(resultIntent);
+            verify(mContentResolver, never()).getType(any());
+        } finally {
+            mockitoSession.finishMocking();
+        }
     }
 
     @SuppressWarnings("GuardedBy")
@@ -1402,6 +1426,34 @@
                 & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
     }
 
+    @Test
+    public void testUseCloneForCreatorTokenAndOriginalIntent_createSameIntentCreatorToken() {
+        Intent testIntent = new Intent(TEST_ACTION1)
+                .setComponent(TEST_COMPONENT)
+                .setDataAndType(TEST_URI, TEST_TYPE)
+                .setIdentifier(TEST_IDENTIFIER)
+                .addCategory(TEST_CATEGORY);
+        testIntent.setOriginalIntent(new Intent(TEST_ACTION2));
+        testIntent.setSelector(new Intent(TEST_ACTION3));
+        testIntent.setSourceBounds(new Rect(0, 0, 100, 100));
+        testIntent.setLaunchToken(TEST_LAUNCH_TOKEN);
+        testIntent.addFlags(ALL_SET_FLAG)
+                .addExtendedFlags(ALL_SET_FLAG);
+        ClipData testClipData = ClipData.newHtmlText("label", "text", "<html/>");
+        testClipData.addItem(new ClipData.Item(new Intent(TEST_ACTION1)));
+        testClipData.addItem(new ClipData.Item(TEST_URI));
+        testIntent.putExtra(TEST_EXTRA_KEY1, TEST_EXTRA_VALUE1);
+
+        ActivityManagerService.IntentCreatorToken tokenForFullIntent =
+                new ActivityManagerService.IntentCreatorToken(TEST_CREATOR_UID,
+                        TEST_CREATOR_PACKAGE, testIntent);
+        ActivityManagerService.IntentCreatorToken tokenForCloneIntent =
+                new ActivityManagerService.IntentCreatorToken(TEST_CREATOR_UID,
+                        TEST_CREATOR_PACKAGE, testIntent.cloneForCreatorToken());
+
+        assertThat(tokenForFullIntent.getKeyFields()).isEqualTo(tokenForCloneIntent.getKeyFields());
+    }
+
     private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
             long lastNetworkUpdatedProcStateSeq,
             final long procStateSeqToWait, boolean expectWait) throws Exception {
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 a9569b4..1efe470 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -105,6 +105,7 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArrayMap;
@@ -3260,6 +3261,24 @@
                 "cch-empty");
     }
 
+    @SuppressWarnings("GuardedBy")
+    @Test
+    @EnableFlags(Flags.FLAG_FIX_APPLY_OOMADJ_ORDER)
+    public void testUpdateOomAdj_ApplyOomAdjInCorrectOrder() {
+        final int numberOfApps = 5;
+        final ProcessRecord[] apps = new ProcessRecord[numberOfApps];
+        for (int i = 0; i < numberOfApps; i++) {
+            apps[i] = spy(makeDefaultProcessRecord(MOCKAPP_PID + i, MOCKAPP_UID + i,
+                    MOCKAPP_PROCESSNAME + i, MOCKAPP_PACKAGENAME + i, true));
+        }
+        updateOomAdj(apps);
+        for (int i = 1; i < numberOfApps; i++) {
+            final int pre = mInjector.mSetOomAdjAppliedAt.get(apps[i - 1].mPid);
+            final int cur = mInjector.mSetOomAdjAppliedAt.get(apps[i].mPid);
+            assertTrue("setOomAdj is called in wrong order", pre < cur);
+        }
+    }
+
     private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName,
             String packageName, boolean hasShownUi) {
         return new ProcessRecordBuilder(pid, uid, processName, packageName).setHasShownUi(
@@ -3589,9 +3608,16 @@
         long mTimeOffsetMillis = 0;
         private SparseIntArray mLastSetOomAdj = new SparseIntArray();
 
+        // A sequence number that increases every time setOomAdj is called
+        int mLastAppliedAt = 0;
+        // Holds the last sequence number setOomAdj is called for a pid
+        private SparseIntArray mSetOomAdjAppliedAt = new SparseIntArray();
+
         void reset() {
             mTimeOffsetMillis = 0;
             mLastSetOomAdj.clear();
+            mLastAppliedAt = 0;
+            mSetOomAdjAppliedAt.clear();
         }
 
         void jumpUptimeAheadTo(long uptimeMillis) {
@@ -3616,6 +3642,7 @@
                 final int pid = proc.getPid();
                 if (pid <= 0) continue;
                 mLastSetOomAdj.put(pid, proc.mState.getCurAdj());
+                mSetOomAdjAppliedAt.put(pid, mLastAppliedAt++);
             }
         }
 
@@ -3623,6 +3650,7 @@
         void setOomAdj(int pid, int uid, int adj) {
             if (pid <= 0) return;
             mLastSetOomAdj.put(pid, adj);
+            mSetOomAdjAppliedAt.put(pid, mLastAppliedAt++);
         }
 
         @Override
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 c77ab0f..7454248 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -21,10 +21,14 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 
+import static org.mockito.Mockito.verify;
+
 import android.content.ContentResolver;
 import android.os.SystemProperties;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 
@@ -34,6 +38,7 @@
 import org.junit.Test;
 import org.mockito.Answers;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 import org.mockito.stubbing.Answer;
@@ -111,6 +116,20 @@
     }
 
     @Test
+    public void testClearAconfigStorageOverride() {
+        SettingsToPropertiesMapper spyMapper = Mockito.spy(new SettingsToPropertiesMapper(
+                mMockContentResolver, TEST_MAPPING, new String[] {}, new String[] {}));
+        HashMap flags = new HashMap();
+        flags.put("test_package.test_flag", null);
+        DeviceConfig.Properties props = new DeviceConfig.Properties("test_namespace", flags);
+
+        spyMapper.setLocalOverridesInNewStorage(props);
+
+        verify(spyMapper).writeFlagOverrideRemovalRequest(new ProtoOutputStream(),
+                "test_package", "test_flag", true);
+    }
+
+    @Test
     public void validateRegisteredGlobalSettings() {
         HashSet<String> hashSet = new HashSet<>();
         for (String globalSetting : SettingsToPropertiesMapper.sGlobalSettings) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
index 19e43b6..8aaa723 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
@@ -119,6 +119,8 @@
 
         mTestApplicationInfo = new ApplicationInfo();
         mTestApplicationInfo.packageName = TEST_PACKAGE;
+        mTestApplicationInfo.processName = TEST_PACKAGE;
+        mTestApplicationInfo.uid = Process.FIRST_APPLICATION_UID + 1;
 
         mBackupAgentResult = null;
         mTestThread = null;
@@ -134,8 +136,8 @@
     @Test
     public void bindToAgentSynchronous_amReturnsFailure_returnsNullAndClearsPendingBackups()
             throws Exception {
-        when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(),
-                anyInt(), anyBoolean())).thenReturn(false);
+        when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+                anyBoolean())).thenReturn(false);
 
         IBackupAgent result = mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
                 ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
@@ -147,8 +149,8 @@
     @Test
     public void bindToAgentSynchronous_agentDisconnectedCalled_returnsNullAndClearsPendingBackups()
             throws Exception {
-        when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(),
-                anyInt(), anyBoolean())).thenReturn(true);
+        when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+                anyBoolean())).thenReturn(true);
         // This is so that IBackupAgent.Stub.asInterface() works.
         when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub);
         when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
@@ -172,8 +174,17 @@
 
     @Test
     public void bindToAgentSynchronous_agentConnectedCalled_returnsBackupAgent() throws Exception {
-        when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(),
-                anyInt(), anyBoolean())).thenReturn(true);
+        bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_FULL);
+
+        assertThat(mBackupAgentResult).isEqualTo(mBackupAgentStub);
+        verify(mActivityManagerInternal, never()).clearPendingBackup(anyInt());
+    }
+
+    @Test
+    public void bindToAgentSynchronous_unexpectedAgentConnected_doesNotReturnWrongAgent()
+            throws Exception {
+        when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+                anyBoolean())).thenReturn(true);
         // This is so that IBackupAgent.Stub.asInterface() works.
         when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub);
         when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
@@ -188,11 +199,10 @@
         // Give the testThread a head start, otherwise agentConnected() might run before
         // bindToAgentSynchronous() is called.
         Thread.sleep(500);
-        mConnectionManager.agentConnected(TEST_PACKAGE, mBackupAgentStub);
-        testThread.join();
+        mConnectionManager.agentConnected("com.other.package", mBackupAgentStub);
+        testThread.join(100); // Avoid waiting the full timeout.
 
-        assertThat(mBackupAgentResult).isEqualTo(mBackupAgentStub);
-        verify(mActivityManagerInternal, never()).clearPendingBackup(anyInt());
+        assertThat(mBackupAgentResult).isNull();
     }
 
     @Test
@@ -345,6 +355,7 @@
 
     @Test
     public void agentDisconnected_cancelsCurrentOperations() throws Exception {
+        when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
         when(mOperationStorage.operationTokensForPackage(eq(TEST_PACKAGE))).thenReturn(
                 ImmutableSet.of(123, 456, 789));
         when(mConnectionManager.getThreadForCancellation(any())).thenAnswer(invocation -> {
@@ -364,11 +375,75 @@
 
     @Test
     public void unbindAgent_callsAmUnbindBackupAgent() throws Exception {
-        mConnectionManager.unbindAgent(mTestApplicationInfo);
+        mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ false);
 
         verify(mActivityManager).unbindBackupAgent(eq(mTestApplicationInfo));
     }
 
+    @Test
+    public void unbindAgent_doNotAllowKill_doesNotKillApp() throws Exception {
+        mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ false);
+
+        verify(mActivityManager, never()).killApplicationProcess(any(), anyInt());
+    }
+
+    @Test
+    public void unbindAgent_allowKill_isCoreApp_doesNotKillApp() throws Exception {
+        mTestApplicationInfo.uid = 1000;
+
+        mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+        verify(mActivityManager, never()).killApplicationProcess(any(), anyInt());
+    }
+
+    @Test
+    public void unbindAgent_allowKill_notCurrentConnection_killsApp() throws Exception {
+        mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+        verify(mActivityManager).killApplicationProcess(eq(TEST_PACKAGE), anyInt());
+    }
+
+    @Test
+    public void unbindAgent_allowKill_inRestrictedMode_killsApp() throws Exception {
+        bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_FULL);
+
+        mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+        verify(mActivityManager).killApplicationProcess(eq(TEST_PACKAGE), anyInt());
+    }
+
+    @Test
+    public void unbindAgent_allowKill_notInRestrictedMode_doesNotKillApp() throws Exception {
+        bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
+
+        mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+        verify(mActivityManager, never()).killApplicationProcess(any(), anyInt());
+    }
+
+    @Test
+    public void unbindAgent_allowKill_isRestore_noKillAfterRestore_doesNotKillApp()
+            throws Exception {
+        bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_RESTORE);
+        mTestApplicationInfo.flags = 0;
+        verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+                /* useRestrictedMode= */ eq(false));
+
+        mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+        verify(mActivityManager, never()).killApplicationProcess(any(), anyInt());
+    }
+
+    @Test
+    public void unbindAgent_allowKill_isRestore_killAfterRestore_killsApp() throws Exception {
+        bindAndConnectToTestAppAgent(ApplicationThreadConstants.BACKUP_MODE_RESTORE);
+        mTestApplicationInfo.flags |= ApplicationInfo.FLAG_KILL_AFTER_RESTORE;
+
+        mConnectionManager.unbindAgent(mTestApplicationInfo, /* allowKill= */ true);
+
+        verify(mActivityManager).killApplicationProcess(eq(TEST_PACKAGE), anyInt());
+    }
+
     // Needed because variables can't be assigned directly inside lambdas in Java.
     private void setBackupAgentResult(IBackupAgent result) {
         mBackupAgentResult = result;
@@ -378,4 +453,23 @@
     private void setTestThread(Thread thread) {
         mTestThread = thread;
     }
+
+    private void bindAndConnectToTestAppAgent(int backupMode) throws Exception {
+        when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+                anyBoolean())).thenReturn(true);
+        // This is going to block until it receives the callback so we need to run it on a
+        // separate thread.
+        Thread testThread = new Thread(() -> setBackupAgentResult(
+                mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo, backupMode,
+                        BackupDestination.CLOUD)), "backup-agent-connection-manager-test");
+        testThread.start();
+        // Give the testThread a head start, otherwise agentConnected() might run before
+        // bindToAgentSynchronous() is called.
+        Thread.sleep(500);
+        when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
+        // This is so that IBackupAgent.Stub.asInterface() works.
+        when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub);
+        mConnectionManager.agentConnected(TEST_PACKAGE, mBackupAgentStub);
+        testThread.join();
+    }
 }
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 dd7ce21..c831475 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.job;
 
+import static android.app.job.Flags.FLAG_HANDLE_ABANDONED_JOBS;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
@@ -82,6 +83,7 @@
 import android.os.SystemClock;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
@@ -1056,6 +1058,75 @@
     /**
      * Confirm that
      * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
+     * returns a job with the correct delay for abandoned jobs.
+     */
+    @Test
+    @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+    public void testGetRescheduleJobForFailure_abandonedJob() {
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+        final long initialBackoffMs = MINUTE_IN_MILLIS;
+        mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO = 3;
+
+        JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure",
+                createJobInfo()
+                        .setBackoffCriteria(initialBackoffMs, JobInfo.BACKOFF_POLICY_LINEAR));
+        assertEquals(JobStatus.NO_EARLIEST_RUNTIME, originalJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_LATEST_RUNTIME, originalJob.getLatestRunTimeElapsed());
+
+        // failure = 1, systemStop = 0, abandoned = 1
+        JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
+                JobParameters.STOP_REASON_DEVICE_STATE,
+                JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+        assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+        // failure = 2, systemstop = 0, abandoned = 2
+        rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+                JobParameters.STOP_REASON_DEVICE_STATE,
+                JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+        assertEquals(nowElapsed + (2 * initialBackoffMs), rescheduledJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+        // failure = 3, systemstop = 0, abandoned = 3
+        rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+                JobParameters.STOP_REASON_DEVICE_STATE,
+                JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+        assertEquals(nowElapsed + (3 * initialBackoffMs), rescheduledJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+        // failure = 4, systemstop = 0, abandoned = 4
+        rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+                JobParameters.STOP_REASON_DEVICE_STATE,
+                JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+        assertEquals(
+                nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 3)),
+                rescheduledJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+        // failure = 4, systemstop = 1, abandoned = 4
+        rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+                JobParameters.STOP_REASON_DEVICE_STATE,
+                JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
+        assertEquals(
+                nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 3)),
+                rescheduledJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+        // failure = 4, systemStop =  4  / SYSTEM_STOP_TO_FAILURE_RATIO, abandoned = 4
+        for (int i = 0; i < mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO; ++i) {
+            rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+                    JobParameters.STOP_REASON_SYSTEM_PROCESSING,
+                    JobParameters.INTERNAL_STOP_REASON_RTC_UPDATED);
+        }
+        assertEquals(
+                nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 4)),
+                rescheduledJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+    }
+
+    /**
+     * Confirm that
+     * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
      * returns a job that is correctly marked as demoted by the user.
      */
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index c6a6865..c64973a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -706,14 +706,14 @@
                 // "True" start is nowElapsed + HOUR_IN_MILLIS
                 nowElapsed + HOUR_IN_MILLIS + adjustmentMs,
                 nowElapsed + 2 * HOUR_IN_MILLIS,
-                0 /* numFailures */, 0 /* numSystemStops */,
+                0 /* numFailures */, 0 /* numAbandonedFailures */, 0 /* numSystemStops */,
                 JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
                 0, 0);
         jsFlex = new JobStatus(jsFlex,
                 // "True" start is nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS
                 nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS + adjustmentMs,
                 nowElapsed + 2 * HOUR_IN_MILLIS,
-                0 /* numFailures */, 0 /* numSystemStops */,
+                0 /* numFailures */, 0 /* numAbandonedFailures */, 0 /* numSystemStops */,
                 JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
                 0, 0);
 
@@ -726,13 +726,13 @@
         jsBasic = new JobStatus(jsBasic,
                 nowElapsed + 30 * MINUTE_IN_MILLIS,
                 NO_LATEST_RUNTIME,
-                1 /* numFailures */, 1 /* numSystemStops */,
+                1 /* numFailures */, 0 /* numAbandonedFailures */, 1 /* numSystemStops */,
                 JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
                 0, 0);
         jsFlex = new JobStatus(jsFlex,
                 nowElapsed + 30 * MINUTE_IN_MILLIS,
                 NO_LATEST_RUNTIME,
-                1 /* numFailures */, 1 /* numSystemStops */,
+                1 /* numFailures */, 0 /* numAbandonedFailures */, 1 /* numSystemStops */,
                 JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
                 0, 0);
 
@@ -847,21 +847,24 @@
         JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
         JobStatus js = createJobStatus("time", jb);
         js = new JobStatus(
-                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 0,
+                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2,
+                0 /* numAbandonedFailures */, /* numSystemStops */ 0,
                 0, FROZEN_TIME, FROZEN_TIME);
 
         assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
 
         js = new JobStatus(
-                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 1,
+                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2,
+                0 /* numAbandonedFailures */, /* numSystemStops */ 1,
                 0, FROZEN_TIME, FROZEN_TIME);
 
         assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
 
         js = new JobStatus(
-                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 10,
+                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0,
+                0 /* numAbandonedFailures */, /* numSystemStops */ 10,
                 0, FROZEN_TIME, FROZEN_TIME);
         assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
@@ -1092,11 +1095,13 @@
         JobInfo.Builder jb = createJob(0);
         JobStatus js = createJobStatus("time", jb);
         js = new JobStatus(
-                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1, /* numSystemStops */ 0,
+                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1,
+                /* numAbandonedFailures */ 0, /* numSystemStops */ 0,
                 0, FROZEN_TIME, FROZEN_TIME);
         assertFalse(js.hasFlexibilityConstraint());
         js = new JobStatus(
-                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 1,
+                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0,
+                /* numAbandonedFailures */ 0, /* numSystemStops */ 1,
                 0, FROZEN_TIME, FROZEN_TIME);
         assertFalse(js.hasFlexibilityConstraint());
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 2d0f4b6..86101cf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -459,35 +459,35 @@
         int numFailures = 1;
         int numSystemStops = 0;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
 
         // 2+ failures, priority should be lowered as much as possible.
         numFailures = 2;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
         numFailures = 5;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
         numFailures = 8;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
 
         // System stops shouldn't factor in the downgrade.
         numSystemStops = 10;
         numFailures = 0;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
 
         // Less than 2 failures, but job is downgraded.
         numFailures = 1;
         numSystemStops = 0;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
         assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
     }
@@ -505,44 +505,44 @@
         int numFailures = 1;
         int numSystemStops = 0;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
 
         // Failures in [2,4), priority should be lowered slightly.
         numFailures = 2;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
         numFailures = 3;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
 
         // Failures in [4,6), priority should be lowered more.
         numFailures = 4;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
         numFailures = 5;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
 
         // 6+ failures, priority should be lowered as much as possible.
         numFailures = 6;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
         numFailures = 12;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
 
         // System stops shouldn't factor in the downgrade.
         numSystemStops = 10;
         numFailures = 0;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
     }
 
@@ -563,32 +563,32 @@
         int numFailures = 1;
         int numSystemStops = 0;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
         numFailures = 4;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
         numFailures = 5;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
 
         // 6+ failures, priority should be lowered as much as possible.
         numFailures = 6;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
         numFailures = 12;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
 
         // System stops shouldn't factor in the downgrade.
         numSystemStops = 10;
         numFailures = 0;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
     }
 
@@ -606,28 +606,28 @@
         int numFailures = 1;
         int numSystemStops = 0;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
 
         // 2+ failures, priority shouldn't be affected while job is still a UI job
         numFailures = 2;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
         numFailures = 5;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
         numFailures = 8;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
 
         // System stops shouldn't factor in the downgrade.
         numSystemStops = 10;
         numFailures = 0;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
 
         // Job can no long run as user-initiated. Downgrades should be effective.
@@ -641,28 +641,28 @@
         numFailures = 1;
         numSystemStops = 0;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
 
         // 2+ failures, priority should start getting lower
         numFailures = 2;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
         numFailures = 5;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
         numFailures = 8;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
 
         // System stops shouldn't factor in the downgrade.
         numSystemStops = 10;
         numFailures = 0;
         job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
-                numSystemStops, 0, 0, 0);
+                0, numSystemStops, 0, 0, 0);
         assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
     }
 
@@ -772,14 +772,14 @@
         assertTrue(job.shouldTreatAsUserInitiatedJob());
 
         JobStatus rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
-                0, 0, 0, 0, 0);
+                0, 0, 0, 0, 0, 0);
         assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
 
         job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
         assertFalse(job.shouldTreatAsUserInitiatedJob());
 
         rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
-                0, 0, 0, 0, 0);
+                0, 0, 0, 0, 0, 0);
         assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob());
 
         rescheduledJob.removeInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
@@ -797,14 +797,14 @@
         assertTrue(job.shouldTreatAsUserInitiatedJob());
 
         JobStatus rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
-                0, 0, 0, 0, 0);
+                0, 0, 0, 0, 0, 0);
         assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
 
         job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
         assertFalse(job.shouldTreatAsUserInitiatedJob());
 
         rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
-                0, 0, 0, 0, 0);
+                0, 0, 0, 0, 0, 0);
         assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob());
 
         rescheduledJob.removeInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
index a1937ce..9b8a7cc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
@@ -23,6 +23,8 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.MockitoAnnotations.initMocks;
 
@@ -33,20 +35,24 @@
 import android.location.ILocationListener;
 import android.location.LocationManagerInternal;
 import android.location.LocationRequest;
+import android.location.flags.Flags;
 import android.location.provider.ProviderRequest;
 import android.os.IBinder;
 import android.os.PowerManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.server.LocalServices;
+import com.android.server.location.fudger.LocationFudgerCache;
 import com.android.server.location.injector.FakeUserInfoHelper;
 import com.android.server.location.injector.TestInjector;
 import com.android.server.location.provider.AbstractLocationProvider;
 import com.android.server.location.provider.LocationProviderManager;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 
 import com.google.common.util.concurrent.MoreExecutors;
@@ -54,6 +60,7 @@
 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;
 import org.mockito.Mock;
@@ -75,8 +82,12 @@
     private TestInjector mInjector;
     private LocationManagerService mLocationManagerService;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Spy private FakeAbstractLocationProvider mProviderWithPermission;
     @Spy private FakeAbstractLocationProvider mProviderWithoutPermission;
+    @Mock private ProxyPopulationDensityProvider mPopulationDensityProvider;
     @Mock private ILocationListener mLocationListener;
     @Mock private IBinder mBinder;
     @Mock private Context mContext;
@@ -172,6 +183,32 @@
     }
 
     @Test
+    public void testSetLocationFudgerCache_withFeatureFlagDisabled_isNotCalled() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+        LocationProviderManager manager = mock(LocationProviderManager.class);
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        mLocationManagerService.addLocationProviderManager(manager, /* provider = */ null);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+        mLocationManagerService.setLocationFudgerCache(cache);
+
+        verify(manager, never()).setLocationFudgerCache(any());
+    }
+
+    @Test
+    public void testSetLocationFudgerCache_withFeatureFlagEnabled_isCalled() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+        LocationProviderManager manager = mock(LocationProviderManager.class);
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        mLocationManagerService.addLocationProviderManager(manager, /* provider = */ null);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+        mLocationManagerService.setLocationFudgerCache(cache);
+
+        verify(manager).setLocationFudgerCache(cache);
+    }
+
+    @Test
     public void testHasProvider_noPermission() {
         assertThat(mLocationManagerService.hasProvider(PROVIDER_WITHOUT_PERMISSION)).isFalse();
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java
new file mode 100644
index 0000000..6b7eda2
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java
@@ -0,0 +1,341 @@
+/*
+ * 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.location.fudger;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.location.flags.Flags;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+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 androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.location.geometry.S2CellIdUtils;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+public class LocationFudgerCacheTest {
+
+    private static final String TAG = "LocationFudgerCacheTest";
+
+    private static final long TIMES_SQUARE_S2_ID =
+            S2CellIdUtils.fromLatLngDegrees(40.758896, -73.985130);
+
+    private static final double[] POINT_IN_TIMES_SQUARE = {40.75889599346095, -73.9851300385147};
+
+    private static final double[] POINT_OUTSIDE_TIMES_SQUARE = {48.858093, 2.294694};
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Test
+    public void hasDefaultValue_isInitiallyFalse()
+            throws RemoteException {
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+        assertThat(cache.hasDefaultValue()).isFalse();
+    }
+
+    @Test
+    public void hasDefaultValue_uponQueryError_isStillFalse()
+            throws RemoteException {
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+        ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+                IS2LevelCallback.class);
+        verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+        IS2LevelCallback cb = argumentCaptor.getValue();
+        cb.onError();
+
+        assertThat(cache.hasDefaultValue()).isFalse();
+    }
+
+    @Test
+    public void hasDefaultValue_afterSuccessfulQuery_isTrue()
+            throws RemoteException {
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+        ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+                IS2LevelCallback.class);
+        verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+        IS2LevelCallback cb = argumentCaptor.getValue();
+        cb.onResult(10);
+
+        assertThat(cache.hasDefaultValue()).isTrue();
+    }
+
+    @Test
+    public void locationFudgerCache_whenQueriedOutsideOfCache_returnsDefault()
+            throws RemoteException {
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+        int level = 10;
+        int defaultLevel = 2;
+        Long s2Cell = S2CellIdUtils.getParent(TIMES_SQUARE_S2_ID, level);
+
+        ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+                IS2LevelCallback.class);
+        verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+        IS2LevelCallback cb = argumentCaptor.getValue();
+        cb.onResult(defaultLevel);
+
+        cache.addToCache(s2Cell);
+
+        assertThat(cache.getCoarseningLevel(POINT_OUTSIDE_TIMES_SQUARE[0],
+                POINT_OUTSIDE_TIMES_SQUARE[1])).isEqualTo(defaultLevel);
+    }
+
+    @Test
+    public void locationFudgerCache_whenQueriedValueIsCached_returnsCachedValue() {
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+        int level = 10;
+        Long s2Cell = S2CellIdUtils.getParent(TIMES_SQUARE_S2_ID, level);
+
+        cache.addToCache(s2Cell);
+
+        assertThat(cache.getCoarseningLevel(POINT_IN_TIMES_SQUARE[0], POINT_IN_TIMES_SQUARE[1]))
+                .isEqualTo(level);
+    }
+
+    @Test
+    public void locationFudgerCache_whenStarting_queriesDefaultValue() {
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+        verify(provider).getDefaultCoarseningLevel(any());
+    }
+
+    @Test
+    public void locationFudgerCache_ifDidntGetDefaultValue_queriesItAgain() {
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+        verify(provider, times(1)).getDefaultCoarseningLevel(any());
+
+        cache.getCoarseningLevel(90.0, 0.0);
+
+        verify(provider, times(2)).getDefaultCoarseningLevel(any());
+    }
+
+    @Test
+    public void locationFudgerCache_ifReceivedDefaultValue_doesNotQueriesIt()
+            throws RemoteException {
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+        ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+                IS2LevelCallback.class);
+        verify(provider, times(1)).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+        IS2LevelCallback cb = argumentCaptor.getValue();
+        cb.onResult(10);
+
+        cache.getCoarseningLevel(90.0, 0.0);
+
+        // Verify getDefaultCoarseningLevel did not get called again
+        verify(provider, times(1)).getDefaultCoarseningLevel(any());
+    }
+
+    @Test
+    public void locationFudgerCache_whenSuccessfullyQueriesDefaultValue_storesResult()
+            throws RemoteException {
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+        int level = 10;
+
+        ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+                IS2LevelCallback.class);
+        verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+        IS2LevelCallback cb = argumentCaptor.getValue();
+        cb.onResult(level);
+
+        // Query any uncached location
+        assertThat(cache.getCoarseningLevel(0.0, 0.0)).isEqualTo(level);
+    }
+
+    @Test
+    public void locationFudgerCache_whenQueryingDefaultValueFails_returnsDefault()
+            throws RemoteException {
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+        ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+                IS2LevelCallback.class);
+        verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+        IS2LevelCallback cb = argumentCaptor.getValue();
+        cb.onError();
+
+        // Query any uncached location. The default value is 0
+        assertThat(cache.getCoarseningLevel(0.0, 0.0)).isEqualTo(0);
+    }
+
+    @Test
+    public void locationFudgerCache_whenQueryIsNotCached_queriesProvider() {
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+        cache.getCoarseningLevel(POINT_IN_TIMES_SQUARE[0], POINT_IN_TIMES_SQUARE[1]);
+
+        verify(provider).getCoarsenedS2Cells(eq(POINT_IN_TIMES_SQUARE[0]),
+                eq(POINT_IN_TIMES_SQUARE[1]), anyInt(), any());
+    }
+
+    @Test
+    public void locationFudgerCache_whenProviderIsQueried_resultIsCached() throws RemoteException {
+        double lat = POINT_IN_TIMES_SQUARE[0];
+        double lng = POINT_IN_TIMES_SQUARE[1];
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+        int level = cache.getCoarseningLevel(lat, lng);
+        assertThat(level).isEqualTo(0);  // default value
+
+        ArgumentCaptor<IS2CellIdsCallback> argumentCaptor = ArgumentCaptor.forClass(
+                IS2CellIdsCallback.class);
+        verify(provider).getCoarsenedS2Cells(eq(POINT_IN_TIMES_SQUARE[0]),
+                eq(POINT_IN_TIMES_SQUARE[1]), anyInt(), argumentCaptor.capture());
+
+        // Results from the proxy should set the cache
+        int expectedLevel = 4;
+        long leafCell = S2CellIdUtils.fromLatLngDegrees(lat, lng);
+        Long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevel);
+        IS2CellIdsCallback cb = argumentCaptor.getValue();
+        long[] answer = new long[] {s2CellId};
+        cb.onResult(answer);
+
+        int level2 = cache.getCoarseningLevel(lat, lng);
+        assertThat(level2).isEqualTo(expectedLevel);
+    }
+
+    @Test
+    public void locationFudgerCache_whenQueryIsCached_doesNotRefreshIt() {
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+        cache.addToCache(TIMES_SQUARE_S2_ID);
+
+        verify(provider, never()).getCoarsenedS2Cells(anyDouble(), anyDouble(), anyInt(), any());
+    }
+
+    @Test
+    public void locationFudgerCache_whenQueryIsCached_askForMaxCacheSizeElems() {
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+        int numAdditionalCells = cache.MAX_CACHE_SIZE - 1;
+
+        cache.getCoarseningLevel(POINT_IN_TIMES_SQUARE[0], POINT_IN_TIMES_SQUARE[1]);
+
+        verify(provider).getCoarsenedS2Cells(eq(POINT_IN_TIMES_SQUARE[0]),
+                eq(POINT_IN_TIMES_SQUARE[1]), eq(numAdditionalCells), any());
+    }
+
+
+    @Test
+    public void locationFudgerCache_canContainUpToMaxSizeItems() {
+        // This test has two sequences of arrange-act-assert.
+        // The first checks that the cache correctly store up to MAX_CACHE_SIZE items.
+        // The second checks that any new element replaces the oldest in the cache.
+
+        // Arrange.
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+        int size = cache.MAX_CACHE_SIZE;
+
+        double[][] latlngs = new double[size][2];
+        long[] cells = new long[size];
+        int[] expectedLevels = new int[size];
+
+        for (int i = 0; i < size; i++) {
+            // Create arbitrary lat/lngs.
+            latlngs[i][0] = 10.0 * i;
+            latlngs[i][1] = 10.0 * i;
+
+            expectedLevels[i] = 10;  // we set some arbitrary S2 level for each latlng.
+
+            long leafCell = S2CellIdUtils.fromLatLngDegrees(latlngs[i][0], latlngs[i][1]);
+            long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevels[i]);
+            cells[i] = s2CellId;
+        }
+
+        // Act.
+        cache.addToCache(cells);
+
+        // Assert: check that the cache contains these latlngs and returns the correct level.
+        for (int i = 0; i < size; i++) {
+            assertThat(cache.getCoarseningLevel(latlngs[i][0], latlngs[i][1]))
+                    .isEqualTo(expectedLevels[i]);
+        }
+
+        // Second assertion: A new value evicts the oldest one.
+
+        // Arrange.
+        int expectedLevel = 25;
+        long leafCell = S2CellIdUtils.fromLatLngDegrees(-10.0, -180.0);
+        long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevel);
+
+        // Act.
+        cache.addToCache(s2CellId);
+
+        // Assert: the new point is in the cache.
+        assertThat(cache.getCoarseningLevel(-10.0, -180.0)).isEqualTo(expectedLevel);
+        // Assert: all but the oldest point are still in cache.
+        for (int i = 0; i < size - 1; i++) {
+            assertThat(cache.getCoarseningLevel(latlngs[i][0], latlngs[i][1]))
+                    .isEqualTo(expectedLevels[i]);
+        }
+        // Assert: the oldest point has been evicted.
+        assertThat(cache.getCoarseningLevel(latlngs[size - 1][0], latlngs[size - 1][1]))
+                .isEqualTo(0);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
index 4e9b6c7..d58e772 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
@@ -22,14 +22,23 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
 import android.location.Location;
+import android.location.flags.Flags;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.Log;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -55,6 +64,9 @@
 
     private LocationFudger mFudger;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     public void setUp() {
         long seed = System.currentTimeMillis();
@@ -162,4 +174,64 @@
                 input.getLongitude() + deltaYM / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR,
                 0);
     }
+
+    @Test
+    public void testDensityBasedCoarsening_ifFeatureIsDisabled_cacheIsNotUsed() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+        LocationFudgerCache cache = mock(LocationFudgerCache.class);
+
+        mFudger.setLocationFudgerCache(cache);
+
+        mFudger.createCoarse(createLocation("test", mRandom));
+
+        verify(cache, never()).getCoarseningLevel(anyDouble(), anyDouble());
+    }
+
+    @Test
+    public void testDensityBasedCoarsening_ifFeatureIsEnabledButNotDefault_cacheIsNotUsed() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+        LocationFudgerCache cache = mock(LocationFudgerCache.class);
+        doReturn(false).when(cache).hasDefaultValue();
+
+        mFudger.setLocationFudgerCache(cache);
+
+        mFudger.createCoarse(createLocation("test", mRandom));
+
+        verify(cache, never()).getCoarseningLevel(anyDouble(), anyDouble());
+    }
+
+    @Test
+    public void testDensityBasedCoarsening_ifFeatureIsEnabledAndDefaultIsSet_cacheIsUsed() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+        LocationFudgerCache cache = mock(LocationFudgerCache.class);
+        doReturn(true).when(cache).hasDefaultValue();
+
+        mFudger.setLocationFudgerCache(cache);
+
+        Location fine = createLocation("test", mRandom);
+        mFudger.createCoarse(fine);
+
+        // We can't verify that the coordinatese of "fine" are passed to the API due to the addition
+        // of the offset. We must use anyDouble().
+        verify(cache).getCoarseningLevel(anyDouble(), anyDouble());
+    }
+
+    @Test
+    public void testDensityBasedCoarsening_newAlgorithm_snapsToCenterOfS2Cell_testVector() {
+        // NB: a complete test vector is in
+        // frameworks/base/services/tests/mockingservicestests/src/com/android/server/...
+        // location/geometry/S2CellIdUtilsTest.java
+
+        mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+        // Arbitrary location in Times Square, NYC
+        double[] latLng = new double[] {40.758896, -73.985130};
+        int s2Level = 1;
+        // The level-2 S2 cell around this location is "8c", its center is:
+        double[] expected = { 21.037511025421814, -67.38013505195958 };
+
+        double[] center = mFudger.snapToCenterOfS2Cell(latLng[0], latLng[1], s2Level);
+
+        assertThat(center[0]).isEqualTo(expected[0]);
+        assertThat(center[1]).isEqualTo(expected[1]);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/geometry/S2CellIdUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/location/geometry/S2CellIdUtilsTest.java
new file mode 100644
index 0000000..9e43b81
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/geometry/S2CellIdUtilsTest.java
@@ -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.server.location.geometry;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.location.geometry.S2CellIdUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class S2CellIdUtilsTest {
+
+    // S2 cell ID of a level-30 cell in Times Square.
+    private static final long TIMES_SQUARE_S2_ID =
+            S2CellIdUtils.fromLatLngDegrees(40.758896, -73.985130);
+
+    // Position of the Eiffel tower (outside of any parent cell from Times Square).
+    private static final double[] EIFFEL_TOWER_LATLNG = {48.858093, 2.294694};
+
+    // Test vector around TIMES_SQUARE_S2_ID: Cell IDs and the centers for levels 0 to 30.
+    // This test vector has been computed using the public S2 library in
+    // external/s2-geometry-library-java
+    private static final CellAndCenter[] TIMES_SQUARE_CELLS = {
+            new CellAndCenter("9", -0.0, -90.0),
+            new CellAndCenter("8c", 21.037511025421814, -67.38013505195958),
+            new CellAndCenter("89", 34.04786296943431, -79.38034472384487),
+            new CellAndCenter("89c", 38.79459515585768, -73.46516214265485),
+            new CellAndCenter("89d", 41.74704688465104, -76.45630866778862),
+            new CellAndCenter("89c4", 40.29416073145462, -74.96763653470293),
+            new CellAndCenter("89c3", 40.827706513259564, -74.21793256064282),
+            new CellAndCenter("89c24", 40.45771021423038, -73.84190634077625),
+            new CellAndCenter("89c25", 40.64307662867646, -74.03001224983848),
+            new CellAndCenter("89c25c", 40.708880489804564, -73.93598211433742),
+            new CellAndCenter("89c259", 40.75509755935301, -73.9830029344863),
+            new CellAndCenter("89c2584", 40.7781887758716, -74.00650903621303),
+            new CellAndCenter("89c2585", 40.766644611813284, -73.99475634561863),
+            new CellAndCenter("89c25854", 40.76087144655763, -73.98887973002674),
+            new CellAndCenter("89c25855", 40.75798459318946, -73.98594135473846),
+            new CellAndCenter("89c25855c", 40.75901097797799, -73.98447215023141),
+            new CellAndCenter("89c25855b", 40.758497791893824, -73.98520675388987),
+            new CellAndCenter("89c25855bc", 40.75875438651343, -73.98483945241185),
+            new CellAndCenter("89c25855b9", 40.758934819692875, -73.98502310323867),
+            new CellAndCenter("89c25855b9c", 40.75887067137071, -73.98511492858621),
+            new CellAndCenter("89c25855b9d", 40.75891577956465, -73.98516084124353),
+            new CellAndCenter("89c25855b9c4", 40.758893225473194, -73.98513788491626),
+            new CellAndCenter("89c25855b9c7", 40.75890124402366, -73.98512640675159),
+            new CellAndCenter("89c25855b9c6c", 40.758897234748815, -73.985132145834),
+            new CellAndCenter("89c25855b9c6d", 40.75889441548664, -73.98512927629281),
+            new CellAndCenter("89c25855b9c6c4", 40.75889582511775, -73.98513071106342),
+            new CellAndCenter("89c25855b9c6c3", 40.758896326277146, -73.98512999367811),
+            new CellAndCenter("89c25855b9c6c3c", 40.75889607569745, -73.98513035237076),
+            new CellAndCenter("89c25855b9c6c39", 40.75889589949357, -73.98513017302443),
+            new CellAndCenter("89c25855b9c6c39c", 40.75889596213849, -73.98513008335128),
+            new CellAndCenter("89c25855b9c6c39f", 40.75889599346095, -73.9851300385147)};
+
+    @Test
+    public void toLatLngDegrees_matchesTestVector() {
+        for (int level = 0; level <= 30; level++) {
+            double[] expected = TIMES_SQUARE_CELLS[level].mCenter;
+            long cellId = S2CellIdUtils.getParent(TIMES_SQUARE_S2_ID, level);
+
+            double[] centerPoint = {0.0, 0.0};
+            S2CellIdUtils.toLatLngDegrees(cellId, centerPoint);
+
+            assertThat(approxEquals(centerPoint[0], expected[0])).isTrue();
+            assertThat(approxEquals(centerPoint[1], expected[1])).isTrue();
+        }
+    }
+
+    private static boolean approxEquals(double a, double b) {
+        return Math.abs(a - b) <= 1e-14;
+    }
+
+    @Test
+    public void containsLatLngDegrees_eachCellContainsItsCenter_works() {
+        for (int level = 0; level <= 30; level++) {
+            long cellId = TIMES_SQUARE_CELLS[level].toCellId();
+            double[] center = TIMES_SQUARE_CELLS[level].mCenter;
+
+            boolean isContained = S2CellIdUtils.containsLatLngDegrees(cellId, center[0], center[1]);
+
+            assertThat(isContained).isTrue();
+        }
+    }
+
+    @Test
+    public void containsLatLngDegrees_testWithOutsidePoint() {
+        for (int level = 0; level <= 30; level++) {
+            long cellId = TIMES_SQUARE_CELLS[level].toCellId();
+
+            assertThat(S2CellIdUtils.containsLatLngDegrees(cellId, EIFFEL_TOWER_LATLNG[0],
+                  EIFFEL_TOWER_LATLNG[1])).isFalse();
+        }
+    }
+
+    // A tuple with a S2 cell id, and a S2LatLng representing its center.
+    private static class CellAndCenter {
+        public String mToken;
+        public double[] mCenter;
+
+        CellAndCenter(String token, double latDegrees, double lngDegrees) {
+            this.mToken = token;
+            this.mCenter = new double[] {latDegrees, lngDegrees};
+        }
+
+        // Converts from hex representation to long format.
+        long toCellId() {
+            long value = 0;
+            for (int pos = 0; pos < mToken.length(); pos++) {
+                int digitValue = Character.digit(mToken.charAt(pos), 16);
+                if (digitValue == -1) {
+                    return -1;
+                }
+                value = value * 16 + digitValue;
+            }
+            value = value << (4 * (16 - mToken.length()));  // remove implicit zeros
+            return value;
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 0928264..6d78def 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -40,6 +40,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyDouble;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -72,6 +73,7 @@
 import android.location.LocationResult;
 import android.location.flags.Flags;
 import android.location.provider.IProviderRequestListener;
+import android.location.provider.IS2LevelCallback;
 import android.location.provider.ProviderProperties;
 import android.location.provider.ProviderRequest;
 import android.location.util.identity.CallerIdentity;
@@ -98,8 +100,10 @@
 import com.android.internal.R;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.location.fudger.LocationFudgerCache;
 import com.android.server.location.injector.FakeUserInfoHelper;
 import com.android.server.location.injector.TestInjector;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
 
 import org.junit.After;
 import org.junit.Before;
@@ -1432,6 +1436,72 @@
                 PERMISSION_FINE)).isEqualTo(location);
     }
 
+    @Test
+    public void testLocationFudger_withFlagDisabled_cacheIsNotSetAndOldAlgoIsUsed() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+        createManager("some-name");
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+        mManager.setLocationFudgerCache(cache);
+
+        Location test = new Location("any-provider");
+        mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+        verify(provider, never()).getCoarsenedS2Cells(anyDouble(), anyDouble(), anyInt(), any());
+    }
+
+    @Test
+    public void testLocationFudger_withFlagEnabledButNoDefaults_oldAlgoIsUsed()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+        createManager("some-other-name");
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+        mManager.setLocationFudgerCache(cache);
+
+        ArgumentCaptor<IS2LevelCallback> captor = ArgumentCaptor.forClass(IS2LevelCallback.class);
+        verify(provider).getDefaultCoarseningLevel(captor.capture());
+
+        IS2LevelCallback cb = captor.getValue();
+
+        // Act: the provider didn't provide a default
+        cb.onError();
+
+        Location test = new Location("any-provider");
+        mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+        verify(provider, never()).getCoarsenedS2Cells(anyDouble(), anyDouble(), anyInt(), any());
+    }
+
+    @Test
+    public void testLocationFudger_withFlagEnabled_cacheIsSetAndNewAlgoIsUsed()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+        createManager("some-other-name");
+        ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+        LocationFudgerCache cache = new LocationFudgerCache(provider);
+        int defaultLevel = 2;
+
+        mManager.setLocationFudgerCache(cache);
+
+        ArgumentCaptor<IS2LevelCallback> captor = ArgumentCaptor.forClass(IS2LevelCallback.class);
+        verify(provider).getDefaultCoarseningLevel(captor.capture());
+
+        IS2LevelCallback cb = captor.getValue();
+        cb.onResult(defaultLevel);
+
+        Location test = new Location("any-provider");
+        test.setLatitude(10.0);
+        test.setLongitude(20.0);
+        mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+        // We can't test that 10.0, 20.0 was passed due to the offset. We only test that a call
+        // happened.
+        verify(provider).getCoarsenedS2Cells(anyDouble(), anyDouble(), anyInt(), any());
+    }
+
     @MediumTest
     @Test
     public void testEnableMsl_expectedBehavior() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
index 20ac078..0304a74 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
@@ -79,12 +79,14 @@
     @Mock private SharedLibrariesImpl mSharedLibraries;
     @Mock private Context mContext;
     @Mock private Computer mComputer;
+    @Mock private PackageInstallerService mPackageInstallerService;
     private InstallDependencyHelper mInstallDependencyHelper;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mInstallDependencyHelper = new InstallDependencyHelper(mContext, mSharedLibraries);
+        mInstallDependencyHelper = new InstallDependencyHelper(mContext, mSharedLibraries,
+                mPackageInstallerService);
     }
 
     @Test
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 0a6edf1..b53dbc8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -218,6 +218,8 @@
         val handler = TestHandler(null)
         val defaultAppProvider: DefaultAppProvider = mock()
         val backgroundHandler = TestHandler(null)
+        val packageInstallerService: PackageInstallerService = mock()
+        val installDependencyHelper: InstallDependencyHelper = mock()
         val updateOwnershipHelper: UpdateOwnershipHelper = mock()
     }
 
@@ -306,6 +308,7 @@
         whenever(mocks.injector.handler) { mocks.handler }
         whenever(mocks.injector.defaultAppProvider) { mocks.defaultAppProvider }
         whenever(mocks.injector.backgroundHandler) { mocks.backgroundHandler }
+        whenever(mocks.injector.packageInstallerService) { mocks.packageInstallerService }
         whenever(mocks.injector.updateOwnershipHelper) { mocks.updateOwnershipHelper }
         whenever(mocks.injector.getSystemService(AppOpsManager::class.java)) { mocks.appOpsManager }
         wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig)
@@ -332,6 +335,8 @@
             DEVICE_PROVISIONING_PACKAGE_NAME
         }
         whenever(mocks.apexManager.activeApexInfos).thenReturn(DEFAULT_ACTIVE_APEX_INFO_LIST)
+        whenever(mocks.packageInstallerService.installDependencyHelper).thenReturn(
+            mocks.installDependencyHelper)
         whenever(mocks.settings.packagesLocked).thenReturn(mSettingsMap)
         whenever(mocks.settings.internalVersion).thenReturn(DEFAULT_VERSION_INFO)
         whenever(mocks.settings.keySetManagerService).thenReturn(mocks.keySetManagerService)
diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
index e631cb6..7248833 100644
--- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -18,7 +18,6 @@
 
 
 import static com.android.server.power.hint.HintManagerService.CLEAN_UP_UID_DELAY_MILLIS;
-import static com.android.server.power.hint.HintManagerService.DEFAULT_HEADROOM_PID;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -53,10 +52,13 @@
 import android.hardware.power.ChannelConfig;
 import android.hardware.power.ChannelMessage;
 import android.hardware.power.CpuHeadroomParams;
+import android.hardware.power.CpuHeadroomResult;
 import android.hardware.power.GpuHeadroomParams;
+import android.hardware.power.GpuHeadroomResult;
 import android.hardware.power.IPower;
 import android.hardware.power.SessionConfig;
 import android.hardware.power.SessionTag;
+import android.hardware.power.SupportInfo;
 import android.hardware.power.WorkDuration;
 import android.os.Binder;
 import android.os.CpuHeadroomParamsInternal;
@@ -158,6 +160,7 @@
 
     private HintManagerService mService;
     private ChannelConfig mConfig;
+    private SupportInfo mSupportInfo;
 
     private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) {
         return new Answer<Long>() {
@@ -178,6 +181,12 @@
         mConfig.eventFlagDescriptor = new MQDescriptor<Byte, Byte>();
         ApplicationInfo applicationInfo = new ApplicationInfo();
         applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
+        mSupportInfo = new SupportInfo();
+        mSupportInfo.headroom = new SupportInfo.HeadroomSupportInfo();
+        mSupportInfo.headroom.isCpuSupported = true;
+        mSupportInfo.headroom.cpuMinIntervalMillis = 2000;
+        mSupportInfo.headroom.isGpuSupported = true;
+        mSupportInfo.headroom.gpuMinIntervalMillis = 2000;
         when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockPackageManager.getNameForUid(anyInt())).thenReturn(TEST_APP_NAME);
         when(mMockPackageManager.getApplicationInfo(eq(TEST_APP_NAME), anyInt()))
@@ -204,6 +213,7 @@
                     SESSION_IDS[2]));
 
         when(mIPowerMock.getInterfaceVersion()).thenReturn(6);
+        when(mIPowerMock.getSupportInfo()).thenReturn(mSupportInfo);
         when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig);
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
         LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
@@ -1246,135 +1256,147 @@
 
     @Test
     public void testCpuHeadroomCache() throws Exception {
-        when(mIPowerMock.getCpuHeadroomMinIntervalMillis()).thenReturn(2000L);
         CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal();
         CpuHeadroomParams halParams1 = new CpuHeadroomParams();
         halParams1.calculationType = CpuHeadroomParams.CalculationType.MIN;
-        halParams1.selectionType = CpuHeadroomParams.SelectionType.ALL;
-        halParams1.pid = Process.myPid();
+        halParams1.tids = new int[]{Process.myPid()};
 
         CpuHeadroomParamsInternal params2 = new CpuHeadroomParamsInternal();
         params2.usesDeviceHeadroom = true;
-        params2.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
-        params2.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
+        params2.calculationType = CpuHeadroomParams.CalculationType.MIN;
         CpuHeadroomParams halParams2 = new CpuHeadroomParams();
-        halParams2.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
-        halParams2.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
-        halParams2.pid = DEFAULT_HEADROOM_PID;
+        halParams2.calculationType = CpuHeadroomParams.CalculationType.MIN;
+        halParams2.tids = new int[]{};
 
-        float[] headroom1 = new float[] {0.1f};
-        when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(headroom1);
-        float[] headroom2 = new float[] {0.1f, 0.5f};
-        when(mIPowerMock.getCpuHeadroom(eq(halParams2))).thenReturn(headroom2);
+        CpuHeadroomParamsInternal params3 = new CpuHeadroomParamsInternal();
+        params3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+        CpuHeadroomParams halParams3 = new CpuHeadroomParams();
+        halParams3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+        halParams3.tids = new int[]{Process.myPid()};
+
+        // this params should not be cached as the window is not default
+        CpuHeadroomParamsInternal params4 = new CpuHeadroomParamsInternal();
+        params4.calculationWindowMillis = 123;
+        CpuHeadroomParams halParams4 = new CpuHeadroomParams();
+        halParams4.calculationType = CpuHeadroomParams.CalculationType.MIN;
+        halParams4.calculationWindowMillis = 123;
+        halParams4.tids = new int[]{Process.myPid()};
+
+        float headroom1 = 0.1f;
+        CpuHeadroomResult halRet1 = CpuHeadroomResult.globalHeadroom(headroom1);
+        when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(halRet1);
+        float headroom2 = 0.2f;
+        CpuHeadroomResult halRet2 = CpuHeadroomResult.globalHeadroom(headroom2);
+        when(mIPowerMock.getCpuHeadroom(eq(halParams2))).thenReturn(halRet2);
+        float headroom3 = 0.3f;
+        CpuHeadroomResult halRet3 = CpuHeadroomResult.globalHeadroom(headroom3);
+        when(mIPowerMock.getCpuHeadroom(eq(halParams3))).thenReturn(halRet3);
+        float headroom4 = 0.4f;
+        CpuHeadroomResult halRet4 = CpuHeadroomResult.globalHeadroom(headroom4);
+        when(mIPowerMock.getCpuHeadroom(eq(halParams4))).thenReturn(halRet4);
 
         HintManagerService service = createService();
         clearInvocations(mIPowerMock);
 
-        service.getBinderServiceInstance().getCpuHeadroomMinIntervalMillis();
-        verify(mIPowerMock, times(0)).getCpuHeadroomMinIntervalMillis();
-        service.getBinderServiceInstance().getCpuHeadroom(params1);
+        assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
-        service.getBinderServiceInstance().getCpuHeadroom(params2);
+        assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2));
+        assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3));
+        assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
 
         // verify cache is working
         clearInvocations(mIPowerMock);
-        assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
-                0.01f);
-        assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
-                0.01f);
-        verify(mIPowerMock, times(0)).getCpuHeadroom(any());
+        assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+        assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
+        assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+        assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+        verify(mIPowerMock, times(1)).getCpuHeadroom(any());
+        verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));
+        verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams2));
+        verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
+        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
 
         // after 1 more second it should be served with cache still
         Thread.sleep(1000);
         clearInvocations(mIPowerMock);
-        assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
-                0.01f);
-        assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
-                0.01f);
-        verify(mIPowerMock, times(0)).getCpuHeadroom(any());
-
-        // after 1.5 more second it should be served with cache still as timer reset
-        Thread.sleep(1500);
-        clearInvocations(mIPowerMock);
-        assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
-                0.01f);
-        assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
-                0.01f);
-        verify(mIPowerMock, times(0)).getCpuHeadroom(any());
+        assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+        assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
+        assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+        assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+        verify(mIPowerMock, times(1)).getCpuHeadroom(any());
+        verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));
+        verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams2));
+        verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
+        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
 
         // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
-        Thread.sleep(2100);
+        Thread.sleep(1100);
         clearInvocations(mIPowerMock);
-        assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
-                0.01f);
-        assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
-                0.01f);
+        assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+        assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
+        assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+        assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+        verify(mIPowerMock, times(4)).getCpuHeadroom(any());
         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2));
+        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3));
+        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
     }
 
     @Test
     public void testGpuHeadroomCache() throws Exception {
-        when(mIPowerMock.getGpuHeadroomMinIntervalMillis()).thenReturn(2000L);
         GpuHeadroomParamsInternal params1 = new GpuHeadroomParamsInternal();
         GpuHeadroomParams halParams1 = new GpuHeadroomParams();
         halParams1.calculationType = GpuHeadroomParams.CalculationType.MIN;
 
         GpuHeadroomParamsInternal params2 = new GpuHeadroomParamsInternal();
+        params2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
+        params2.calculationWindowMillis = 123;
         GpuHeadroomParams halParams2 = new GpuHeadroomParams();
-        params2.calculationType =
-                halParams2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
+        halParams2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
+        halParams2.calculationWindowMillis = 123;
 
         float headroom1 = 0.1f;
-        when(mIPowerMock.getGpuHeadroom(eq(halParams1))).thenReturn(headroom1);
+        GpuHeadroomResult halRet1 = GpuHeadroomResult.globalHeadroom(headroom1);
+        when(mIPowerMock.getGpuHeadroom(eq(halParams1))).thenReturn(halRet1);
         float headroom2 = 0.2f;
-        when(mIPowerMock.getGpuHeadroom(eq(halParams2))).thenReturn(headroom2);
+        GpuHeadroomResult halRet2 = GpuHeadroomResult.globalHeadroom(headroom2);
+        when(mIPowerMock.getGpuHeadroom(eq(halParams2))).thenReturn(halRet2);
         HintManagerService service = createService();
         clearInvocations(mIPowerMock);
 
-        service.getBinderServiceInstance().getGpuHeadroomMinIntervalMillis();
-        verify(mIPowerMock, times(0)).getGpuHeadroomMinIntervalMillis();
-        assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
-                0.01f);
-        assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
-                0.01f);
+        assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+        assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+        verify(mIPowerMock, times(2)).getGpuHeadroom(any());
         verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1));
         verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
 
         // verify cache is working
         clearInvocations(mIPowerMock);
-        assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
-                0.01f);
-        assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
-                0.01f);
-        verify(mIPowerMock, times(0)).getGpuHeadroom(any());
+        assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+        assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+        verify(mIPowerMock, times(1)).getGpuHeadroom(any());
+        verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
+        verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
 
         // after 1 more second it should be served with cache still
         Thread.sleep(1000);
         clearInvocations(mIPowerMock);
-        assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
-                0.01f);
-        assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
-                0.01f);
-        verify(mIPowerMock, times(0)).getGpuHeadroom(any());
-
-        // after 1.5 more second it should be served with cache still as timer reset
-        Thread.sleep(1500);
-        clearInvocations(mIPowerMock);
-        assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
-                0.01f);
-        assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
-                0.01f);
-        verify(mIPowerMock, times(0)).getGpuHeadroom(any());
+        assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+        assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+        verify(mIPowerMock, times(1)).getGpuHeadroom(any());
+        verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
+        verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
 
         // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
-        Thread.sleep(2100);
+        Thread.sleep(1100);
         clearInvocations(mIPowerMock);
-        assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
-                0.01f);
-        assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
-                0.01f);
+        assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+        assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+        verify(mIPowerMock, times(2)).getGpuHeadroom(any());
         verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1));
         verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
     }
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 b48c2d7..376091e 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -1257,6 +1257,36 @@
                 .isEqualTo(WAKEFULNESS_DOZING);
     }
 
+    @EnableFlags({
+            android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER,
+            android.companion.virtualdevice.flags.Flags.FLAG_DISPLAY_POWER_MANAGER_APIS})
+    @Test
+    public void getBrightnessConstraint_valuesMatchDisplayInfo() {
+        final int displayId = 7;
+        final DisplayInfo info = new DisplayInfo();
+        info.brightnessMinimum = 0.12f;
+        info.brightnessDim = 0.34f;
+        info.brightnessDefault = 0.56f;
+        info.brightnessMaximum = 0.78f;
+        when(mDisplayManagerInternalMock.getDisplayInfo(displayId)).thenReturn(info);
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+                displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM))
+                .isEqualTo(info.brightnessMinimum);
+        assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+                displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM))
+                .isEqualTo(info.brightnessMaximum);
+        assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+                displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT))
+                .isEqualTo(info.brightnessDefault);
+        assertThat(mService.getBinderServiceInstance().getBrightnessConstraint(
+                displayId, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM))
+                .isEqualTo(info.brightnessDim);
+    }
+
     @SuppressWarnings("GuardedBy")
     @Test
     public void testAmbientSuppression_disablesDreamingAndWakesDevice() {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
index c0be865..4b91d84 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
@@ -79,8 +79,6 @@
         // 100,000,00 uC / 1000 (micro-/milli-) / 360 (seconds/hour) = 27.777778 mAh
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
                 .isWithin(PRECISION).of(27.777778);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
     @Test
@@ -140,8 +138,6 @@
         // (seconds/hour) = 27.777778 mAh
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
                 .isWithin(PRECISION).of(83.33333);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
     @Test
@@ -163,8 +159,6 @@
                 .isEqualTo(90 * MINUTE_IN_MS);
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
                 .isWithin(PRECISION).of(15.0);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -195,7 +189,5 @@
                 .isEqualTo(120 * MINUTE_IN_MS);
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
                 .isWithin(PRECISION).of(35.0);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
index 5d50e6c..9da89fc 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
@@ -311,10 +311,6 @@
                 bus.getAggregateBatteryConsumer(AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE),
                 proto.deviceBatteryConsumer);
 
-        for (int i = 0; i < BatteryConsumer.POWER_COMPONENT_COUNT; i++) {
-            assertPowerComponentModel(i, abc.getPowerModel(i), proto);
-        }
-
         // Now for the UidBatteryConsumers.
         final List<android.os.UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers();
         uidConsumers.sort((a, b) -> a.getUid() - b.getUid());
@@ -450,34 +446,6 @@
         }
     }
 
-    /**
-     * Validates the PowerComponentModel object that matches powerComponent.
-     */
-    private void assertPowerComponentModel(int powerComponent,
-            @BatteryConsumer.PowerModel int powerModel, BatteryUsageStatsAtomsProto proto) {
-        boolean found = false;
-        for (BatteryUsageStatsAtomsProto.PowerComponentModel powerComponentModel :
-                proto.componentModels) {
-            if (powerComponentModel.component == powerComponent) {
-                if (found) {
-                    fail("Power component " + BatteryConsumer.powerComponentIdToString(
-                            powerComponent) + " found multiple times in the proto");
-                }
-                found = true;
-                final int expectedPowerModel = BatteryConsumer.powerModelToProtoEnum(powerModel);
-                assertEquals(expectedPowerModel, powerComponentModel.powerModel);
-            }
-        }
-        if (!found) {
-            final int model = BatteryConsumer.powerModelToProtoEnum(powerModel);
-            assertEquals(
-                    "Power component " + BatteryConsumer.powerComponentIdToString(powerComponent)
-                            + " was not found in the proto but has a defined power model.",
-                    BatteryUsageStatsAtomsProto.PowerComponentModel.UNDEFINED,
-                    model);
-        }
-    }
-
     /** Converts charge from milliamp hours (mAh) to decicoulombs (dC). */
     private long convertMahToDc(double powerMah) {
         return (long) (powerMah * 36 + 0.5);
@@ -486,7 +454,6 @@
     private BatteryUsageStats buildBatteryUsageStats() {
         final BatteryUsageStats.Builder builder =
                 new BatteryUsageStats.Builder(new String[]{"CustomConsumer1", "CustomConsumer2"},
-                        /* includePowerModels */ true,
                         /* includeProcessStats */ true,
                         /* includeScreenStateData */ false,
                         /* includePowerStateData */ false,
@@ -524,13 +491,13 @@
         final BatteryConsumer.Key keyCached = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU,
                 BatteryConsumer.PROCESS_STATE_CACHED);
 
-        uidBuilder.addConsumedPower(keyFg, 9100, BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+        uidBuilder.addConsumedPower(keyFg, 9100)
                 .addUsageDurationMillis(keyFg, 8100)
-                .addConsumedPower(keyBg, 9200, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
+                .addConsumedPower(keyBg, (double) 9200)
                 .addUsageDurationMillis(keyBg, 8200)
-                .addConsumedPower(keyFgs, 9300, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
+                .addConsumedPower(keyFgs, (double) 9300)
                 .addUsageDurationMillis(keyFgs, 8300)
-                .addConsumedPower(keyCached, 9400, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
+                .addConsumedPower(keyCached, (double) 9400)
                 .addUsageDurationMillis(keyCached, 8400);
 
         final BatteryConsumer.Key keyCustomFg = uidBuilder.getKey(
@@ -539,10 +506,8 @@
         final BatteryConsumer.Key keyCustomBg = uidBuilder.getKey(
                 BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
                 BatteryConsumer.PROCESS_STATE_BACKGROUND);
-        uidBuilder.addConsumedPower(
-                keyCustomFg, 100, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
-        uidBuilder.addConsumedPower(
-                keyCustomBg, 350, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+        uidBuilder.addConsumedPower(keyCustomFg, 100);
+        uidBuilder.addConsumedPower(keyCustomBg, 350);
 
         builder.getOrCreateUidBatteryConsumerBuilder(UID_1)
                 .setPackageWithHighestDrain("myPackage1")
@@ -557,14 +522,11 @@
         builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                 .addConsumedPower(30000)
                 .addConsumedPower(
-                        BatteryConsumer.POWER_COMPONENT_CPU, 20100,
-                        BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+                        BatteryConsumer.POWER_COMPONENT_CPU, 20100)
                 .addConsumedPower(
-                        BatteryConsumer.POWER_COMPONENT_AUDIO, 0,
-                        BatteryConsumer.POWER_MODEL_POWER_PROFILE) // Empty
+                        BatteryConsumer.POWER_COMPONENT_AUDIO, 0) // Empty
                 .addConsumedPower(
-                        BatteryConsumer.POWER_COMPONENT_CAMERA, 20150,
-                        BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
+                        BatteryConsumer.POWER_COMPONENT_CAMERA, 20150)
                 .addConsumedPower(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20200)
                 .addUsageDurationMillis(
@@ -576,8 +538,7 @@
         builder.getAggregateBatteryConsumerBuilder(
                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
                 .addConsumedPower(
-                        BatteryConsumer.POWER_COMPONENT_CPU, 10100,
-                        BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+                        BatteryConsumer.POWER_COMPONENT_CPU, 10100)
                 .addConsumedPower(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200);
 
@@ -587,7 +548,7 @@
     @Test
     public void testLargeAtomTruncated() throws Exception {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[0], true, false, false, false, 0);
+                new BatteryUsageStats.Builder(new String[0], false, false, false, 0);
         // If not truncated, this BatteryUsageStats object would generate a proto buffer
         // significantly larger than 50 Kb
         for (int i = 0; i < 3000; i++) {
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 383616e..a3c7ece 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
@@ -423,8 +423,6 @@
             mBatteryUsageStats = null;
         }
         final String[] customPowerComponentNames = mBatteryStats.getCustomEnergyConsumerNames();
-        final boolean includePowerModels = (query.getFlags()
-                & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
         final boolean includeProcessStateData = (query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0;
         final boolean includeScreenStateData = (query.getFlags()
@@ -433,7 +431,7 @@
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE) != 0;
         final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
-                customPowerComponentNames, includePowerModels, includeProcessStateData,
+                customPowerComponentNames, includeProcessStateData,
                 includeScreenStateData, includePowerStateData, minConsumedPowerThreshold);
         SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats();
         for (int i = 0; i < uidStats.size(); i++) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 9771da5..dd50431 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -17,8 +17,6 @@
 package com.android.server.power.stats;
 
 import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
-import static android.os.BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION;
-import static android.os.BatteryConsumer.POWER_MODEL_UNDEFINED;
 import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
@@ -237,7 +235,7 @@
         final BatteryUsageStats stats1 = buildBatteryUsageStats1(false).build();
         final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[]{"FOO"}, true).build();
         final BatteryUsageStats sum =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0)
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, 0)
                         .add(stats1)
                         .add(stats2)
                         .build();
@@ -248,15 +246,13 @@
         for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
             if (uidBatteryConsumer.getUid() == APP_UID1) {
                 assertUidBatteryConsumer(uidBatteryConsumer, 1200 + 924, null,
-                        5321, 6900, 532, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400 + 345,
-                        POWER_MODEL_UNDEFINED,
+                        5321, 6900, 532, 423, 400 + 345,
                         500 + 456, 1167, 1478,
                         true, 3554, 4732, 3998, 444, 3554, 15542, 3776, 17762, 3998, 19982,
                         444, 1110);
             } else if (uidBatteryConsumer.getUid() == APP_UID2) {
                 assertUidBatteryConsumer(uidBatteryConsumer, 1332, "bar",
-                        1111, 2220, 2, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444,
-                        BatteryConsumer.POWER_MODEL_POWER_PROFILE,
+                        1111, 2220, 2, 333, 444,
                         555, 666, 777,
                         true, 1777, 2443, 1999, 321, 1777, 7771, 1888, 8881, 1999, 9991,
                         321, 654);
@@ -280,7 +276,7 @@
     @Test
     public void testAdd_customComponentMismatch() throws Exception {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0);
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, 0);
         final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"BAR"}, false).build();
 
         assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
@@ -291,7 +287,7 @@
     @Test
     public void testAdd_processStateDataMismatch() throws Exception {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0);
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, 0);
         final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"FOO"}, false).build();
 
         assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
@@ -328,7 +324,7 @@
         final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
 
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true,
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, true,
                         includeScreenState, includePowerState, 0)
                         .setBatteryCapacity(4000)
                         .setDischargePercentage(20)
@@ -339,8 +335,8 @@
 
         addUidBatteryConsumer(builder, batteryStats, APP_UID1, "foo",
                 1000, 1500, 500,
-                300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400,
-                BatteryConsumer.POWER_MODEL_POWER_PROFILE, 500, 600, 800,
+                300, 400,
+                500, 600, 800,
                 1777, 7771, 1888, 8881, 1999, 9991, 123, 456);
 
         addAggregateBatteryConsumer(builder,
@@ -373,7 +369,7 @@
         final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
 
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(customPowerComponentNames, true,
+                new BatteryUsageStats.Builder(customPowerComponentNames,
                         includeProcessStateData, true, true, 0);
         builder.setDischargePercentage(30)
                 .setDischargedPowerRange(1234, 2345)
@@ -382,14 +378,14 @@
 
         addUidBatteryConsumer(builder, batteryStats, APP_UID1, null,
                 4321, 5400, 32,
-                123, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 345, POWER_MODEL_ENERGY_CONSUMPTION,
+                123, 345,
                 456, 567, 678,
                 1777, 7771, 1888, 8881, 1999, 9991, 321, 654);
 
         addUidBatteryConsumer(builder, batteryStats, APP_UID2, "bar",
                 1111, 2220, 2,
-                333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444,
-                BatteryConsumer.POWER_MODEL_POWER_PROFILE, 555, 666, 777,
+                333, 444,
+                555, 666, 777,
                 1777, 7771, 1888, 8881, 1999, 9991, 321, 654);
 
         addAggregateBatteryConsumer(builder,
@@ -409,7 +405,7 @@
             MockBatteryStatsImpl batteryStats, int uid, String packageWithHighestDrain,
             int timeInProcessStateForeground, int timeInProcessStateBackground,
             int timeInProcessStateForegroundService, double screenPower,
-            int screenPowerModel, double cpuPower, int cpuPowerModel, double customComponentPower,
+            double cpuPower, double customComponentPower,
             int cpuDuration, int customComponentDuration, double cpuPowerForeground,
             int cpuDurationForeground, double cpuPowerBackground, int cpuDurationBackground,
             double cpuPowerFgs, int cpuDurationFgs, double cpuPowerCached, long cpuDurationCached) {
@@ -423,9 +419,9 @@
                 .setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE,
                         timeInProcessStateForegroundService)
                 .addConsumedPower(
-                        BatteryConsumer.POWER_COMPONENT_SCREEN, screenPower, screenPowerModel)
+                        BatteryConsumer.POWER_COMPONENT_SCREEN, screenPower)
                 .addConsumedPower(
-                        BatteryConsumer.POWER_COMPONENT_CPU, cpuPower, cpuPowerModel)
+                        BatteryConsumer.POWER_COMPONENT_CPU, cpuPower)
                 .addConsumedPower(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentPower)
                 .addUsageDurationMillis(
@@ -460,21 +456,15 @@
                     : uidBuilder.getKey(
                             BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
                             BatteryConsumer.PROCESS_STATE_BACKGROUND);
-            uidBuilder
-                    .addConsumedPower(cpuFgKey, cpuPowerForeground,
-                            BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+            uidBuilder.addConsumedPower(cpuFgKey, cpuPowerForeground)
                     .addUsageDurationMillis(cpuFgKey, cpuDurationForeground)
-                    .addConsumedPower(cpuBgKey, cpuPowerBackground,
-                            BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+                    .addConsumedPower(cpuBgKey, cpuPowerBackground)
                     .addUsageDurationMillis(cpuBgKey, cpuDurationBackground)
-                    .addConsumedPower(cpuFgsKey, cpuPowerFgs,
-                            BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+                    .addConsumedPower(cpuFgsKey, cpuPowerFgs)
                     .addUsageDurationMillis(cpuFgsKey, cpuDurationFgs)
-                    .addConsumedPower(cachedKey, cpuPowerCached,
-                            BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+                    .addConsumedPower(cachedKey, cpuPowerCached)
                     .addUsageDurationMillis(cachedKey, cpuDurationCached)
-                    .addConsumedPower(customBgKey, customComponentPower,
-                            BatteryConsumer.POWER_MODEL_UNDEFINED)
+                    .addConsumedPower(customBgKey, customComponentPower)
                     .addUsageDurationMillis(customBgKey, customComponentDuration);
         }
     }
@@ -518,18 +508,13 @@
                     BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
                     BatteryConsumer.SCREEN_STATE_OTHER,
                     BatteryConsumer.POWER_STATE_OTHER);
-            aggBuilder
-                    .addConsumedPower(cpuBatScrOn, cpuPowerBatScrOn,
-                            BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+            aggBuilder.addConsumedPower(cpuBatScrOn, cpuPowerBatScrOn)
                     .addUsageDurationMillis(cpuBatScrOn, cpuDurationBatScrOn)
-                    .addConsumedPower(cpuBatScrOff, cpuPowerBatScrOff,
-                            BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+                    .addConsumedPower(cpuBatScrOff, cpuPowerBatScrOff)
                     .addUsageDurationMillis(cpuBatScrOff, cpuDurationBatScrOff)
-                    .addConsumedPower(cpuChgScrOn, cpuPowerChgScrOn,
-                            BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+                    .addConsumedPower(cpuChgScrOn, cpuPowerChgScrOn)
                     .addUsageDurationMillis(cpuChgScrOn, cpuDurationChgScrOn)
-                    .addConsumedPower(cpuChgScrOff, cpuPowerChgScrOff,
-                            BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+                    .addConsumedPower(cpuChgScrOff, cpuPowerChgScrOff)
                     .addUsageDurationMillis(cpuChgScrOff, cpuDurationChgScrOff);
         }
     }
@@ -544,8 +529,7 @@
         for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
             if (uidBatteryConsumer.getUid() == APP_UID1) {
                 assertUidBatteryConsumer(uidBatteryConsumer, 1200, "foo",
-                        1000, 1500, 500, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400,
-                        BatteryConsumer.POWER_MODEL_POWER_PROFILE,
+                        1000, 1500, 500, 300, 400,
                         500, 600, 800,
                         true, 1777, 2388, 1999, 123, 1777, 7771, 1888, 8881, 1999, 9991, 123, 456);
             } else {
@@ -596,8 +580,8 @@
     private void assertUidBatteryConsumer(UidBatteryConsumer uidBatteryConsumer,
             double consumedPower, String packageWithHighestDrain, int timeInProcessStateForeground,
             int timeInProcessStateBackground, int timeInProcessStateForegroundService,
-            int screenPower, int screenPowerModel, double cpuPower,
-            int cpuPowerModel, double customComponentPower, int cpuDuration,
+            int screenPower, double cpuPower,
+            double customComponentPower, int cpuDuration,
             int customComponentDuration, boolean processStateDataIncluded,
             double totalPowerForeground, double totalPowerBackground, double totalPowerFgs,
             double totalPowerCached, double cpuPowerForeground, int cpuDurationForeground,
@@ -620,12 +604,8 @@
                 PROCESS_STATE_FOREGROUND_SERVICE)).isEqualTo(timeInProcessStateForegroundService);
         assertThat(uidBatteryConsumer.getConsumedPower(
                 BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(screenPower);
-        assertThat(uidBatteryConsumer.getPowerModel(
-                BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(screenPowerModel);
         assertThat(uidBatteryConsumer.getConsumedPower(
                 BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPower);
-        assertThat(uidBatteryConsumer.getPowerModel(
-                BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPowerModel);
         assertThat(uidBatteryConsumer.getConsumedPower(
                 BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(customComponentPower);
         assertThat(uidBatteryConsumer.getUsageDurationMillis(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
index fe6424f..c9cb0df 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
@@ -82,16 +82,16 @@
 
         assertBluetoothPowerAndDuration(
                 mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
-                0.06944, 3000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                0.06944, 3000);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getUidBatteryConsumer(APP_UID),
-                0.19444, 9000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                0.19444, 9000);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getDeviceBatteryConsumer(),
-                0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                0.26388, 12000);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getAppsBatteryConsumer(),
-                0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                0.26388, 12000);
     }
 
     @Test
@@ -144,8 +144,6 @@
                 .isEqualTo(6166);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
                 .isWithin(PRECISION).of(0.1226666);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         final BatteryConsumer.Key foreground = uidConsumer.getKey(
                 BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
@@ -178,16 +176,16 @@
 
         assertBluetoothPowerAndDuration(
                 mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
-                0.08216, 3583, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                0.08216, 3583);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getUidBatteryConsumer(APP_UID),
-                0.18169, 8416, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                0.18169, 8416);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getDeviceBatteryConsumer(),
-                0.30030, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                0.30030, 12000);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getAppsBatteryConsumer(),
-                0.26386, 11999, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                0.26386, 11999);
     }
 
     @Test
@@ -202,16 +200,16 @@
 
         assertBluetoothPowerAndDuration(
                 mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
-                0.10378, 3583, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+                0.10378, 3583);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getUidBatteryConsumer(APP_UID),
-                0.22950, 8416, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+                0.22950, 8416);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getDeviceBatteryConsumer(),
-                0.33333, 12000, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+                0.33333, 12000);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getAppsBatteryConsumer(),
-                0.33329, 11999, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+                0.33329, 11999);
     }
 
     @Test
@@ -264,8 +262,6 @@
                 .isEqualTo(6166);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
                 .isWithin(PRECISION).of(0.8220561);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         final BatteryConsumer.Key foreground = uidConsumer.getKey(
                 BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
@@ -299,16 +295,16 @@
 
         assertBluetoothPowerAndDuration(
                 mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
-                0.08216, 3583, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                0.08216, 3583);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getUidBatteryConsumer(APP_UID),
-                0.18169, 8416, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                0.18169, 8416);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getDeviceBatteryConsumer(),
-                0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                0.26388, 12000);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getAppsBatteryConsumer(),
-                0.26386, 11999, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                0.26386, 11999);
     }
 
     private void setupBluetoothEnergyInfo(long reportedEnergyUc, long consumedEnergyUc) {
@@ -326,14 +322,12 @@
     }
 
     private void assertBluetoothPowerAndDuration(@Nullable BatteryConsumer batteryConsumer,
-            double powerMah, int durationMs, @BatteryConsumer.PowerModel int powerModel) {
+            double powerMah, int durationMs) {
         assertThat(batteryConsumer).isNotNull();
 
         double consumedPower = batteryConsumer.getConsumedPower(
                 BatteryConsumer.POWER_COMPONENT_BLUETOOTH);
         assertThat(consumedPower).isWithin(PRECISION).of(powerMah);
-        assertThat(batteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
-                .isEqualTo(powerModel);
 
         long usageDurationMillis = batteryConsumer.getUsageDurationMillis(
                 BatteryConsumer.POWER_COMPONENT_BLUETOOTH);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
index 7225f2d..4cd3857 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
@@ -70,16 +70,12 @@
                 .isEqualTo(1000);
         assertThat(app1Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                 .isWithin(PRECISION).of(0.1);
-        assertThat(app1Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         UidBatteryConsumer app2Consumer = mStatsRule.getUidBatteryConsumer(APP2_UID);
         assertThat(app2Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
                 .isEqualTo(2000);
         assertThat(app2Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                 .isWithin(PRECISION).of(0.2);
-        assertThat(app2Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceBatteryConsumer
@@ -87,8 +83,6 @@
                 .isEqualTo(3000);
         assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                 .isWithin(PRECISION).of(0.3);
-        assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsBatteryConsumer
@@ -96,8 +90,6 @@
                 .isEqualTo(3000);
         assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                 .isWithin(PRECISION).of(0.3);
-        assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -122,16 +114,12 @@
                 .isEqualTo(1000);
         assertThat(app1Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                 .isWithin(PRECISION).of(0.2);
-        assertThat(app1Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         UidBatteryConsumer app2Consumer = mStatsRule.getUidBatteryConsumer(APP2_UID);
         assertThat(app2Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
                 .isEqualTo(2000);
         assertThat(app2Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                 .isWithin(PRECISION).of(0.3);
-        assertThat(app2Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceBatteryConsumer
@@ -139,8 +127,6 @@
                 .isEqualTo(3000);
         assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                 .isWithin(PRECISION).of(0.5);
-        assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsBatteryConsumer
@@ -148,7 +134,5 @@
                 .isEqualTo(3000);
         assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                 .isWithin(PRECISION).of(0.5);
-        assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
index 4cea728..527db67 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
@@ -192,8 +192,6 @@
                 .isEqualTo(3333);
         assertThat(uidConsumer1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
                 .isWithin(PRECISION).of(1.031677);
-        assertThat(uidConsumer1.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
         assertThat(uidConsumer1.getPackageWithHighestDrain()).isEqualTo("bar");
 
         UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
@@ -201,21 +199,15 @@
                 .isEqualTo(7777);
         assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
                 .isWithin(PRECISION).of(2.489544);
-        assertThat(uidConsumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
         assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
 
         final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
                 .isWithin(PRECISION).of(3.52122);
-        assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
                 .isWithin(PRECISION).of(3.52122);
-        assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -264,8 +256,6 @@
                 .isEqualTo(3333);
         assertThat(uidConsumer1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
                 .isWithin(PRECISION).of(3.18877);
-        assertThat(uidConsumer1.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
         assertThat(uidConsumer1.getPackageWithHighestDrain()).isEqualTo("bar");
 
         UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
@@ -273,21 +263,15 @@
                 .isEqualTo(7777);
         assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
                 .isWithin(PRECISION).of(7.44072);
-        assertThat(uidConsumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
         assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
 
         final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
                 .isWithin(PRECISION).of(10.62949);
-        assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         final BatteryConsumer appsBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
                 .isWithin(PRECISION).of(10.62949);
-        assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
     @Test
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 005ceee..c7fad76 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
@@ -29,8 +29,6 @@
 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.RavenwoodFlagsValueProvider;
-import android.platform.test.ravenwood.RavenwoodRule;
 import android.provider.DeviceConfig;
 
 import androidx.test.InstrumentationRegistry;
@@ -59,13 +57,8 @@
 @LargeTest
 @android.platform.test.annotations.DisabledOnRavenwood(reason = "Integration test")
 public class CpuPowerStatsCollectorValidationTest {
-    @Rule(order = 0)
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
-    @Rule(order = 1)
-    public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood()
-            ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
-            : DeviceFlagsValueProvider.createCheckFlagsRule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     private static final int WORK_DURATION_MS = 2000;
     private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp";
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
index 3b5658c..506bab4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
@@ -68,20 +68,14 @@
                 .isEqualTo(1000);
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
                 .isWithin(PRECISION).of(0.1);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
                 .isWithin(PRECISION).of(0.1);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
                 .isWithin(PRECISION).of(0.1);
-        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -107,27 +101,19 @@
                 .isEqualTo(1000);
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
                 .isWithin(PRECISION).of(2.77777);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         UidBatteryConsumer consumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
         assertThat(consumer2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_GNSS))
                 .isEqualTo(2000);
         assertThat(consumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
                 .isWithin(PRECISION).of(5.55555);
-        assertThat(consumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
                 .isWithin(PRECISION).of(8.333333);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
                 .isWithin(PRECISION).of(8.333333);
-        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
index 9b810bc..eba820e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
@@ -164,8 +164,6 @@
         // =    4604000 mA-ms or 1.27888 mA-h
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(1.27888);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
         // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
@@ -178,22 +176,16 @@
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(0.94);
-        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         // 3/4 of total packets were sent by APP_UID so 75% of total
         UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(0.705);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         // Rest should go to the other app
         UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
         assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(0.235);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -321,8 +313,6 @@
         // =    5177753 mA-ms or 1.43826 mA-h total consumption
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(1.43826);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
         // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
@@ -335,22 +325,16 @@
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(1.09938);
-        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         // 3/4 of total packets were sent by APP_UID so 75% of total
         UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(0.82453);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         // Rest should go to the other app
         UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
         assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(0.27484);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -441,8 +425,6 @@
         // =    4604000 mA-ms or 1.27888 mA-h
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(1.27888);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
         // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
@@ -455,22 +437,16 @@
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(0.94);
-        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         // 3/4 of total packets were sent by APP_UID so 75% of total
         UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(0.705);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         // Rest should go to the other app
         UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
         assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(0.235);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -648,24 +624,16 @@
         // 200ms phone on duration / 2000 total duration *  2.77778 mAh = 0.27777
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(2.5);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE))
                 .isWithin(PRECISION).of(0.27778);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_PHONE))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(1.38541);
-        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(1.38541);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
     @Test
@@ -782,12 +750,8 @@
         // 1000ms phone on duration / 10000 total duration *  2.77778 mAh = 0.27777
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(2.5);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE))
                 .isWithin(PRECISION).of(0.27778);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_PHONE))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         // CDMA2000 [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
         //   [720, 1080, 1440, 1800, 2160, 1440] mA . [10, 11, 12, 13, 14, 15] ms = 111600 mA-ms
@@ -817,8 +781,6 @@
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(1.91094);
-        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         // 240 ms Rx Time, 1110 ms Tx Time, 1350 ms active time
         // 150 App 1 Rx Packets, 10 App 1 Tx packets
@@ -841,15 +803,11 @@
         UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(1.27574);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         // Rest should go to the other app
         UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
         assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(0.63520);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
     @Test
@@ -936,12 +894,8 @@
         // 1000ms phone on duration / 10000 total duration *  2.77778 mAh = 0.27777
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(2.5);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE))
                 .isWithin(PRECISION).of(0.27778);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_PHONE))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         // Estimated Rx/Tx modem consumption = 0.94 mAh
@@ -949,14 +903,10 @@
         // 2.5 * 0.94 / 1.27888 = 1.83754 mAh
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(1.83754);
-        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(1.83754);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
     @Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
index 2da98e8..7f20035 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
@@ -104,8 +104,6 @@
         // Uid1 charge = 200000000 + 5 / 45 * 300000000 mAs = 64.81 mAh
         assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(64.81481);
-        assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
         assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -116,8 +114,6 @@
         // Uid2 charge = 40 / 45 * 300000000 + 100000000 mAs = 101.85 mAh
         assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(101.85185);
-        assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -126,8 +122,6 @@
         // 600000000 uAs * (1 mA / 1000 uA) * (1 h / 3600 s)  = 166.66666 mAh
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(166.66666);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -135,11 +129,8 @@
 
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(166.66666);
-        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
-
     @Test
     public void testMeasuredEnergyBasedModel_multiDisplay() {
         mStatsRule.initMeasuredEnergyStatsLocked()
@@ -202,8 +193,6 @@
         // (600000000 + 800000000) uAs * (1 mA / 1000 uA) * (1 h / 3600 s)  = 166.66666 mAh
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(388.88888);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
         assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -214,8 +203,6 @@
         // Uid1 charge = 20 / 80 * 600000000 mAs = 41.66666 mAh
         assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(41.66666);
-        assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
         assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -226,17 +213,12 @@
         // Uid1 charge = 60 / 80 * 600000000 + 800000000 mAs = 347.22222 mAh
         assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(347.22222);
-        assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isEqualTo(110 * MINUTE_IN_MS);
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(388.88888);
-        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
-
     }
 
     @Test
@@ -277,8 +259,6 @@
         // Uid1 charge = 20 / 80 * 92.0 = 23.0 mAh
         assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(23.0);
-        assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
         assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -288,27 +268,20 @@
         // Uid2 charge = 60 / 80 * 92.0 = 69.0 mAh
         assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(69.0);
-        assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isEqualTo(80 * MINUTE_IN_MS);
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(92);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isEqualTo(80 * MINUTE_IN_MS);
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(92);
-        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
-
     @Test
     public void testPowerProfileBasedModel_multiDisplay() {
         mStatsRule.setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 1, 60.0)
@@ -364,8 +337,6 @@
         // 92 + 60 * 0.5 + 10 * 0.1 + 90 * 0.2 + 30 * 0.2 = 147
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(147);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
         assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -375,8 +346,6 @@
         // Uid1 charge = 20 / 110 * 147.0 = 23.0 mAh
         assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(26.72727);
-        assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
         assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -386,17 +355,12 @@
         // Uid2 charge = 90 / 110 * 92.0 = 69.0 mAh
         assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(120.272727);
-        assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isEqualTo(110 * MINUTE_IN_MS);
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(147);
-        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-
     }
 
     private void setProcState(int uid, int procState, boolean resumed, long realtimeMs,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
index ef0b570..1ff347f 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
@@ -31,8 +31,6 @@
 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.RavenwoodFlagsValueProvider;
-import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 
@@ -58,18 +56,13 @@
 @SuppressWarnings("GuardedBy")
 public class SystemServicePowerCalculatorTest {
     @Rule(order = 0)
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
-    @Rule(order = 1)
-    public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood()
-            ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule()
-            : DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     private static final double PRECISION = 0.000001;
     private static final int APP_UID1 = 100;
     private static final int APP_UID2 = 200;
 
-    @Rule(order = 2)
+    @Rule(order = 1)
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
             .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
             .setCpuScalingPolicy(0, new int[]{0, 1}, new int[]{100, 200})
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
index 8e221be..827d2f8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
@@ -159,22 +159,16 @@
                 .isEqualTo(2473);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(0.3964);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isEqualTo(4001);
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(0.86666);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         BatteryConsumer appsConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(0.866666);
-        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -214,8 +208,6 @@
                 .isEqualTo(12423);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(2.0214666);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         final BatteryConsumer.Key foreground = uidConsumer.getKey(
                 BatteryConsumer.POWER_COMPONENT_WIFI,
@@ -248,22 +240,16 @@
         /* Same ratio as in testPowerControllerBasedModel_nonMeasured but scaled by 1_000_000uC. */
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(0.2214666 / (0.2214666 + 0.645200) * 1_000_000 / 3600000);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isEqualTo(4002);
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(0.27777);
-        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer appsConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(0.277777);
-        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
     @Test
@@ -302,8 +288,6 @@
                 .isEqualTo(12423);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(1.0325211);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         final BatteryConsumer.Key foreground = uidConsumer.getKey(
                 BatteryConsumer.POWER_COMPONENT_WIFI,
@@ -349,8 +333,6 @@
                 .isEqualTo(1000);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(0.8231573);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -371,8 +353,6 @@
         /* Same ratio as in testTimerBasedModel_nonMeasured but scaled by 1_000_000uC. */
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(0.8231573 / (0.8231573 + 0.8759216) * 1_000_000 / 3600000);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
     private WifiActivityEnergyInfo buildWifiActivityEnergyInfo(long timeSinceBoot,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java
index f7a1638..cca6033 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java
@@ -176,7 +176,6 @@
                 powerStatsAggregator, /* batterySessionTimeSpanSlackMillis */ 0);
 
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(new String[0],
-                /* includePowerModels */ false,
                 /* includeProcessStateData */ true,
                 /* includeScreenStateData */ true,
                 /* includesPowerStateData */ true,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
index 4643ddd..38fe613 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
@@ -342,7 +342,7 @@
         PowerStatsExporter exporter = new PowerStatsExporter(mPowerStatsStore,
                 mPowerStatsAggregator, /* batterySessionTimeSpanSlackMillis */ 0);
 
-        BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(new String[0], false,
+        BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(new String[0],
                 includeProcessStateData, includeScreenStateData, includesPowerStateData, 0);
         exporter.populateBatteryUsageStatsBuilder(builder, aps);
         return builder.build();
@@ -361,7 +361,7 @@
     private void breakdownByProcState_fullRange(boolean includeScreenStateData,
             boolean includePowerStateData) throws Exception {
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
-                new String[]{"cu570m"}, /* includePowerModels */ false,
+                new String[]{"cu570m"},
                 /* includeProcessStateData */ true, includeScreenStateData,
                 includePowerStateData, /* powerThreshold */ 0);
         exportAggregatedPowerStats(builder, 1000, 10000);
@@ -406,7 +406,7 @@
     @Test
     public void breakdownByProcState_subRange() throws Exception {
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
-                new String[]{"cu570m"}, /* includePowerModels */ false,
+                new String[]{"cu570m"},
                 /* includeProcessStateData */ true, true, true, /* powerThreshold */ 0);
         exportAggregatedPowerStats(builder, 3700, 6700);
 
@@ -438,7 +438,7 @@
     @Test
     public void combinedProcessStates() throws Exception {
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
-                new String[]{"cu570m"}, /* includePowerModels */ false,
+                new String[]{"cu570m"},
                 /* includeProcessStateData */ false, true, true, /* powerThreshold */ 0);
         exportAggregatedPowerStats(builder, 1000, 10000);
 
diff --git a/services/tests/security/intrusiondetection/Android.bp b/services/tests/security/intrusiondetection/Android.bp
index 00ac908..8d674b1 100644
--- a/services/tests/security/intrusiondetection/Android.bp
+++ b/services/tests/security/intrusiondetection/Android.bp
@@ -19,15 +19,20 @@
         "androidx.test.rules",
         "androidx.test.runner",
         "compatibility-device-util-axt",
+        "coretests-aidl",
         "frameworks-base-testutils",
         "junit",
         "platform-test-annotations",
+        "servicestests-utils",
         "services.core",
         "truth",
         "Nene",
         "Harrier",
         "TestApp",
     ],
+    data: [
+        ":TestIntrusionDetectionApp",
+    ],
 
     platform_apis: true,
 
diff --git a/services/tests/security/intrusiondetection/AndroidManifest.xml b/services/tests/security/intrusiondetection/AndroidManifest.xml
index f388e7e..b30710d 100644
--- a/services/tests/security/intrusiondetection/AndroidManifest.xml
+++ b/services/tests/security/intrusiondetection/AndroidManifest.xml
@@ -18,12 +18,25 @@
   package="com.android.server.security.intrusiondetection.tests">
 
        <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING"     />
-       <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+       <uses-permission android:name="android.permission.INTERNET"/>
 
-    <application android:testOnly="true">
+    <application android:testOnly="true" android:debuggable="true" android:usesCleartextTraffic="true">
       <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.server.security.intrusiondetection.IntrusionDetectionAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
+            <meta-data android:name="android.app.device_admin"
+                 android:resource="@xml/device_admin"/>
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+            </intent-filter>
+        </receiver>
     </application>
 
+    <queries>
+        <package android:name="com.android.coretests.apps.testapp" />
+    </queries>
+
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
          android:targetPackage="com.android.server.security.intrusiondetection.tests"
          android:label="Frameworks IntrusionDetection Services Tests"/>
diff --git a/services/tests/security/intrusiondetection/AndroidTest.xml b/services/tests/security/intrusiondetection/AndroidTest.xml
index 42cb9e3..6489dea4a 100644
--- a/services/tests/security/intrusiondetection/AndroidTest.xml
+++ b/services/tests/security/intrusiondetection/AndroidTest.xml
@@ -20,6 +20,7 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
         <option name="test-file-name" value="IntrusionDetectionServiceTests.apk"/>
+        <option name="test-file-name" value="TestIntrusionDetectionApp.apk"/>
         <option name="install-arg" value="-t" />
     </target_preparer>
 
diff --git a/services/tests/security/intrusiondetection/res/xml/device_admin.xml b/services/tests/security/intrusiondetection/res/xml/device_admin.xml
new file mode 100644
index 0000000..f8cd8f0
--- /dev/null
+++ b/services/tests/security/intrusiondetection/res/xml/device_admin.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
+</device-admin>
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
index bc854cf..e505ebe 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
@@ -16,14 +16,19 @@
 
 package com.android.server.security.intrusiondetection;
 
+import static android.Manifest.permission.INTERNET;
 import static android.Manifest.permission.MANAGE_INTRUSION_DETECTION_STATE;
 import static android.Manifest.permission.READ_INTRUSION_DETECTION_STATE;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 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.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
@@ -33,8 +38,14 @@
 import android.annotation.SuppressLint;
 import android.app.admin.ConnectEvent;
 import android.app.admin.DnsEvent;
+import android.app.admin.SecurityLog;
 import android.app.admin.SecurityLog.SecurityEvent;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.Bundle;
 import android.os.Looper;
 import android.os.PermissionEnforcer;
 import android.os.RemoteException;
@@ -43,19 +54,47 @@
 import android.security.intrusiondetection.IIntrusionDetectionServiceCommandCallback;
 import android.security.intrusiondetection.IIntrusionDetectionServiceStateCallback;
 import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.util.Log;
 
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.annotations.AfterClass;
+import com.android.bedstead.harrier.annotations.BeforeClass;
+import com.android.bedstead.multiuser.annotations.RequireRunOnSystemUser;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.devicepolicy.DeviceOwner;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.permissions.CommonPermissions;
+import com.android.bedstead.permissions.PermissionContext;
+import com.android.bedstead.permissions.annotations.EnsureHasPermission;
+import com.android.coretests.apps.testapp.LocalIntrusionDetectionEventTransport;
+import com.android.internal.infra.AndroidFuture;
 import com.android.server.ServiceThread;
 
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
+@RunWith(BedsteadJUnit4.class)
 public class IntrusionDetectionServiceTest {
     private static final int STATE_UNKNOWN =
             IIntrusionDetectionServiceStateCallback.State.UNKNOWN;
@@ -73,20 +112,49 @@
     private static final int ERROR_DATA_SOURCE_UNAVAILABLE =
             IIntrusionDetectionServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE;
 
+    private static DeviceOwner sDeviceOwner;
+
     private Context mContext;
     private IntrusionDetectionEventTransportConnection mIntrusionDetectionEventTransportConnection;
     private DataAggregator mDataAggregator;
     private IntrusionDetectionService mIntrusionDetectionService;
+    private IBinder mService;
     private TestLooper mTestLooper;
     private Looper mLooper;
     private TestLooper mTestLooperOfDataAggregator;
     private Looper mLooperOfDataAggregator;
     private FakePermissionEnforcer mPermissionEnforcer;
+    private boolean mBoundToLoggingService = false;
+    private static final String TEST_PKG =
+        "com.android.coretests.apps.testapp";
+    private static final String TEST_SERVICE = TEST_PKG + ".TestLoggingService";
+
+    @BeforeClass
+    public static void setDeviceOwner() {
+        ComponentName admin =
+                new ComponentName(
+                        ApplicationProvider.getApplicationContext(),
+                        IntrusionDetectionAdminReceiver.class);
+        try {
+            sDeviceOwner = TestApis.devicePolicy().setDeviceOwner(admin);
+        } catch (NeneException e) {
+            fail("Failed to set device owner " + admin.flattenToString() + ": " + e);
+        }
+    }
+
+    @AfterClass
+    public static void removeDeviceOwner() {
+        try {
+            sDeviceOwner.remove();
+        } catch (NeneException e) {
+            fail("Failed to remove device owner : " + e);
+        }
+    }
 
     @SuppressLint("VisibleForTests")
     @Before
-    public void setUp() {
-        mContext = spy(ApplicationProvider.getApplicationContext());
+    public void setUp() throws Exception {
+        mContext = ApplicationProvider.getApplicationContext();
 
         mPermissionEnforcer = new FakePermissionEnforcer();
         mPermissionEnforcer.grant(READ_INTRUSION_DETECTION_STATE);
@@ -343,6 +411,234 @@
         assertNotNull(receivedEvents.get(2).getDnsEvent());
     }
 
+    @Test
+    @RequireRunOnSystemUser
+    public void testDataSources_Initialize_HasDeviceOwner() throws Exception {
+        NetworkLogSource networkLogSource = new NetworkLogSource(mContext, mDataAggregator);
+        SecurityLogSource securityLogSource = new SecurityLogSource(mContext, mDataAggregator);
+
+        assertTrue(networkLogSource.initialize());
+        assertTrue(securityLogSource.initialize());
+    }
+
+    @Test
+    @RequireRunOnSystemUser
+    public void testDataSources_Initialize_NoDeviceOwner() throws Exception {
+        NetworkLogSource networkLogSource = new NetworkLogSource(mContext, mDataAggregator);
+        SecurityLogSource securityLogSource = new SecurityLogSource(mContext, mDataAggregator);
+        ComponentName admin = sDeviceOwner.componentName();
+
+        try {
+            sDeviceOwner.remove();
+            assertFalse(networkLogSource.initialize());
+            assertFalse(securityLogSource.initialize());
+        } finally {
+            sDeviceOwner = TestApis.devicePolicy().setDeviceOwner(admin);
+        }
+    }
+
+    @Test
+    @RequireRunOnSystemUser
+    @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+    public void testDataAggregator_AddSecurityEvent() throws Exception {
+        mIntrusionDetectionService.setState(STATE_ENABLED);
+        ServiceThread mockThread = spy(ServiceThread.class);
+        mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+        assertTrue(mDataAggregator.initialize());
+
+        // SecurityLogging generates a number of events and callbacks, so create a latch to wait for
+        // the given event.
+        String eventString = this.getClass().getName() + ".testSecurityEvent";
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        // TODO: Replace this mock when the IntrusionDetectionEventTransportConnection is ready.
+        doAnswer(
+                    new Answer<Boolean>() {
+                        @Override
+                        public Boolean answer(InvocationOnMock input) {
+                            List<IntrusionDetectionEvent> receivedEvents =
+                                    (List<IntrusionDetectionEvent>) input.getArguments()[0];
+                            for (IntrusionDetectionEvent event : receivedEvents) {
+                                if (event.getType() == IntrusionDetectionEvent.SECURITY_EVENT) {
+                                    SecurityEvent securityEvent = event.getSecurityEvent();
+                                    Object[] eventData = (Object[]) securityEvent.getData();
+                                    if (securityEvent.getTag() == SecurityLog.TAG_KEY_GENERATED
+                                            && eventData[1].equals(eventString)) {
+                                        latch.countDown();
+                                    }
+                                }
+                            }
+                            return true;
+                        }
+                    })
+            .when(mIntrusionDetectionEventTransportConnection).addData(any());
+        mDataAggregator.enable();
+
+        // Generate the security event.
+        generateSecurityEvent(eventString);
+        TestApis.devicePolicy().forceSecurityLogs();
+
+        // Verify the event is received.
+        mTestLooper.startAutoDispatch();
+        assertTrue(latch.await(1, TimeUnit.SECONDS));
+        mTestLooper.stopAutoDispatch();
+
+        mDataAggregator.disable();
+    }
+
+    @Test
+    @RequireRunOnSystemUser
+    @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+    public void testDataAggregator_AddNetworkEvent() throws Exception {
+        mIntrusionDetectionService.setState(STATE_ENABLED);
+        ServiceThread mockThread = spy(ServiceThread.class);
+        mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+        assertTrue(mDataAggregator.initialize());
+
+        // Network logging may log multiple and callbacks, so create a latch to wait for
+        // the given event.
+        // eventServer must be a valid domain to generate a network log event.
+        String eventServer = "google.com";
+        final CountDownLatch latch = new CountDownLatch(1);
+        // TODO: Replace this mock when the IntrusionDetectionEventTransportConnection is ready.
+        doAnswer(
+                    new Answer<Boolean>() {
+                        @Override
+                        public Boolean answer(InvocationOnMock input) {
+                            List<IntrusionDetectionEvent> receivedEvents =
+                                    (List<IntrusionDetectionEvent>) input.getArguments()[0];
+                            for (IntrusionDetectionEvent event : receivedEvents) {
+                                if (event.getType()
+                                        == IntrusionDetectionEvent.NETWORK_EVENT_DNS) {
+                                    DnsEvent dnsEvent = event.getDnsEvent();
+                                    if (dnsEvent.getHostname().equals(eventServer)) {
+                                        latch.countDown();
+                                    }
+                                }
+                            }
+                            return true;
+                        }
+                    })
+            .when(mIntrusionDetectionEventTransportConnection).addData(any());
+        mDataAggregator.enable();
+
+        // Generate the network event.
+        generateNetworkEvent(eventServer);
+        TestApis.devicePolicy().forceNetworkLogs();
+
+        // Verify the event is received.
+        mTestLooper.startAutoDispatch();
+        assertTrue(latch.await(1, TimeUnit.SECONDS));
+        mTestLooper.stopAutoDispatch();
+
+        mDataAggregator.disable();
+    }
+
+    /** Emits a given string into security log (if enabled). */
+    private void generateSecurityEvent(String eventString)
+            throws IllegalArgumentException, GeneralSecurityException, IOException {
+        if (eventString == null || eventString.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Error generating security event: eventString must not be empty");
+        }
+
+        final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
+        keyGen.initialize(
+                new KeyGenParameterSpec.Builder(eventString, KeyProperties.PURPOSE_SIGN).build());
+        // Emit key generation event.
+        final KeyPair keyPair = keyGen.generateKeyPair();
+        assertNotNull(keyPair);
+
+        final KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
+        ks.load(null);
+        // Emit key destruction event.
+        ks.deleteEntry(eventString);
+    }
+
+    /** Emits a given string into network log (if enabled). */
+    private void generateNetworkEvent(String server) throws IllegalArgumentException, IOException {
+        if (server == null || server.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Error generating network event: server must not be empty");
+        }
+
+        HttpURLConnection urlConnection = null;
+        int connectionTimeoutMS = 2_000;
+        try (PermissionContext p = TestApis.permissions().withPermission(INTERNET)) {
+            final URL url = new URL("http://" + server);
+            urlConnection = (HttpURLConnection) url.openConnection();
+            urlConnection.setConnectTimeout(connectionTimeoutMS);
+            urlConnection.setReadTimeout(connectionTimeoutMS);
+            urlConnection.getResponseCode();
+        } finally {
+            if (urlConnection != null) {
+                urlConnection.disconnect();
+            }
+        }
+    }
+
+    @Test
+    public void test_StartIntrusionDetectionEventTransportService() {
+        final String TAG = "test_StartIntrusionDetectionEventTransportService";
+        ServiceConnection serviceConnection = null;
+
+        assertEquals(false, mBoundToLoggingService);
+        try {
+            serviceConnection = startTestService();
+            assertEquals(true, mBoundToLoggingService);
+            assertNotNull(serviceConnection);
+        } catch (SecurityException e) {
+            Log.e(TAG, "SecurityException while starting: ", e);
+            fail("Exception thrown while connecting to service");
+        } catch (InterruptedException e) {
+            Log.e(TAG, "InterruptedException while starting: ", e);
+            fail("Interrupted while connecting to service");
+        } finally {
+            mContext.unbindService(serviceConnection);
+        }
+    }
+
+    private ServiceConnection startTestService() throws SecurityException, InterruptedException {
+        final String TAG = "startTestService";
+        final CountDownLatch latch = new CountDownLatch(1);
+        LocalIntrusionDetectionEventTransport transport =
+                new LocalIntrusionDetectionEventTransport();
+
+        ServiceConnection serviceConnection = new ServiceConnection() {
+            // Called when connection with the service is established.
+            @Override
+            public void onServiceConnected(ComponentName className, IBinder service) {
+                mService = transport.getBinder();
+                mBoundToLoggingService = true;
+                latch.countDown();
+            }
+
+            // Called when the connection with the service disconnects unexpectedly.
+            @Override
+            public void onServiceDisconnected(ComponentName className) {
+                Log.d(TAG, "onServiceDisconnected");
+                mBoundToLoggingService = false;
+            }
+        };
+
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName(TEST_PKG, TEST_SERVICE));
+        mContext.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+        latch.await(5, TimeUnit.SECONDS);
+
+        // call the methods on the transport object
+        IntrusionDetectionEvent event =
+                new IntrusionDetectionEvent(new SecurityEvent(123, new byte[15]));
+        List<IntrusionDetectionEvent> events = new ArrayList<>();
+        events.add(event);
+        assertTrue(transport.initialize());
+        assertTrue(transport.addData(events));
+        assertTrue(transport.release());
+        assertEquals(1, transport.getEvents().size());
+
+        return serviceConnection;
+    }
+
     private class MockInjector implements IntrusionDetectionService.Injector {
         private final Context mContext;
 
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp
new file mode 100644
index 0000000..ca5952b
--- /dev/null
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 {
+    default_team: "trendy_team_platform_security",
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "TestIntrusionDetectionApp",
+
+    static_libs: [
+        "frameworks-base-testutils",
+        "services.core",
+        "servicestests-utils",
+    ],
+
+    srcs: ["**/*.java"],
+
+    platform_apis: true,
+    certificate: "platform",
+    dxflags: ["--multi-dex"],
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
new file mode 100644
index 0000000..7cc75ab
--- /dev/null
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.coretests.apps.testapp">
+
+    <application>
+        <service android:name=".TestLoggingService"
+                  android:exported="true" />
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.java
new file mode 100644
index 0000000..f0012da
--- /dev/null
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/LocalIntrusionDetectionEventTransport.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 com.android.coretests.apps.testapp;
+
+import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.security.intrusiondetection.IntrusionDetectionEventTransport;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class that extends {@link IntrusionDetectionEventTransport} to provide a
+ * local transport mechanism for testing purposes. This implementation overrides
+ * the {@link #initialize()}, {@link #addData(List)}, and {@link #release()} methods
+ * to manage events locally within the test environment.
+ *
+ * For now, the implementation returns true for all methods since we don't
+ * have a real data source to send events to.
+ */
+public class LocalIntrusionDetectionEventTransport extends IntrusionDetectionEventTransport {
+    private List<IntrusionDetectionEvent> mEvents = new ArrayList<>();
+
+    @Override
+    public boolean initialize() {
+        return true;
+    }
+
+    @Override
+    public boolean addData(List<IntrusionDetectionEvent> events) {
+        mEvents.addAll(events);
+        return true;
+    }
+
+    @Override
+    public boolean release() {
+        return true;
+    }
+
+    public List<IntrusionDetectionEvent> getEvents() {
+        return mEvents;
+    }
+}
\ No newline at end of file
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java
new file mode 100644
index 0000000..e4bf987
--- /dev/null
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/TestLoggingService.java
@@ -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.coretests.apps.testapp;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Process;
+
+import com.android.internal.infra.AndroidFuture;
+
+
+public class TestLoggingService extends Service {
+    private static final String TAG = "TestLoggingService";
+    private LocalIntrusionDetectionEventTransport mLocalIntrusionDetectionEventTransport;
+
+    public TestLoggingService() {
+        mLocalIntrusionDetectionEventTransport = new LocalIntrusionDetectionEventTransport();
+    }
+
+    // Binder given to clients.
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mLocalIntrusionDetectionEventTransport.getBinder();
+    }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 0c058df..009ce88 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -897,16 +897,6 @@
 }
 
 test_module_config {
-    name: "FrameworksServicesTests_server_accessibility",
-    base: "FrameworksServicesTests",
-    test_suites: [
-        "automotive-tests",
-        "device-tests",
-    ],
-    include_filters: ["com.android.server.accessibility"],
-}
-
-test_module_config {
     name: "FrameworksServicesTests_server_binarytransparencyservicetest",
     base: "FrameworksServicesTests",
     test_suites: [
diff --git a/services/tests/servicestests/jni/Android.bp b/services/tests/servicestests/jni/Android.bp
index 0a31037..e738c19 100644
--- a/services/tests/servicestests/jni/Android.bp
+++ b/services/tests/servicestests/jni/Android.bp
@@ -22,9 +22,9 @@
     srcs: [
         ":lib_cachedAppOptimizer_native",
         ":lib_freezer_native",
-        ":lib_gameManagerService_native",
         ":lib_oomConnection_native",
         ":lib_anrTimer_native",
+        ":lib_lazilyRegisteredServices_native",
         "onload.cpp",
     ],
 
@@ -55,6 +55,8 @@
         "android.hardware.graphics.bufferqueue@2.0",
         "android.hardware.graphics.common@1.2",
         "android.hardware.graphics.mapper@4.0",
+        "android.hardware.ir@1.0",
+        "android.hardware.vr@1.0",
         "android.hidl.token@1.0-utils",
     ],
 }
diff --git a/services/tests/servicestests/jni/onload.cpp b/services/tests/servicestests/jni/onload.cpp
index 25487c5..ad979c6 100644
--- a/services/tests/servicestests/jni/onload.cpp
+++ b/services/tests/servicestests/jni/onload.cpp
@@ -25,9 +25,9 @@
 
 namespace android {
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
-int register_android_server_app_GameManagerService(JNIEnv* env);
 int register_android_server_am_OomConnection(JNIEnv* env);
 int register_android_server_utils_AnrTimer(JNIEnv *env);
+int register_android_server_utils_LazyJniRegistrar(JNIEnv* env);
 };
 
 using namespace android;
@@ -43,8 +43,8 @@
     }
     ALOG_ASSERT(env, "Could not retrieve the env!");
     register_android_server_am_CachedAppOptimizer(env);
-    register_android_server_app_GameManagerService(env);
     register_android_server_am_OomConnection(env);
     register_android_server_utils_AnrTimer(env);
+    register_android_server_utils_LazyJniRegistrar(env);
     return JNI_VERSION_1_4;
 }
diff --git a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
index ae78dfe..cc5be7e 100644
--- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
@@ -20,6 +20,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -40,6 +41,7 @@
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemProperties;
@@ -50,6 +52,12 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.os.IBinaryTransparencyService;
+import com.android.server.pm.BackgroundInstallControlService;
+import com.android.server.pm.BackgroundInstallControlCallbackHelper;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageStateInternal;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -68,6 +76,9 @@
 public class BinaryTransparencyServiceTest {
     private static final String TAG = "BinaryTransparencyServiceTest";
 
+    private static final String TEST_PKG_NAME = "testPackageName";
+    private static final long TEST_VERSION_CODE = 1L;
+
     private Context mContext;
     private BinaryTransparencyService mBinaryTransparencyService;
     private BinaryTransparencyService.BinaryTransparencyServiceImpl mTestInterface;
@@ -83,6 +94,8 @@
     private PackageManager mPackageManager;
     @Mock
     private PackageManagerInternal mPackageManagerInternal;
+    @Mock
+    private BinaryTransparencyService.BicCallbackHandler.IBicAppInfoHelper mBicAppInfoHelper;
 
     @Captor
     private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback>
@@ -91,6 +104,9 @@
     private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback>
             mFaceAuthenticatorsRegisteredCaptor;
 
+    @Captor
+    private ArgumentCaptor<IBinaryTransparencyService.AppInfo> appInfoCaptor;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -262,4 +278,69 @@
                 eq("") /* softwareVersion */
         );
     }
+
+    @Test
+    public void BicCallbackHandler_uploads_mba_metrics() {
+        Bundle data = setupBicCallbackHandlerTest(false,
+            BinaryTransparencyService.MBA_STATUS_NEW_INSTALL);
+
+        BinaryTransparencyService.BicCallbackHandler handler =
+            new BinaryTransparencyService.BicCallbackHandler(mBicAppInfoHelper);
+        handler.sendResult(data);
+
+        verify(mBicAppInfoHelper, times(1)).writeAppInfoToLog(appInfoCaptor.capture());
+        Assert.assertEquals(TEST_PKG_NAME, appInfoCaptor.getValue().packageName);
+        Assert.assertEquals(TEST_VERSION_CODE, appInfoCaptor.getValue().longVersion);
+    }
+
+    @Test
+    public void BicCallbackHandler_uploads_mba_metrics_for_preloads() {
+        Bundle data = setupBicCallbackHandlerTest(true,
+            BinaryTransparencyService.MBA_STATUS_UPDATED_PRELOAD);
+
+        BinaryTransparencyService.BicCallbackHandler handler =
+            new BinaryTransparencyService.BicCallbackHandler(mBicAppInfoHelper);
+        handler.sendResult(data);
+
+        verify(mBicAppInfoHelper, times(1)).writeAppInfoToLog(appInfoCaptor.capture());
+        Assert.assertEquals(TEST_PKG_NAME, appInfoCaptor.getValue().packageName);
+        Assert.assertEquals(TEST_VERSION_CODE, appInfoCaptor.getValue().longVersion);
+    }
+
+    @Test
+    public void BicCallbackHandler_uploads_mba_metrics_for_uninstalls() {
+        Bundle data = new Bundle();
+        data.putString(BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY,
+            TEST_PKG_NAME);
+        data.putInt(BackgroundInstallControlCallbackHelper.INSTALL_EVENT_TYPE_KEY,
+            BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL);
+
+        BinaryTransparencyService.BicCallbackHandler handler =
+                new BinaryTransparencyService.BicCallbackHandler(mBicAppInfoHelper);
+        handler.sendResult(data);
+
+        verify(mBicAppInfoHelper, times(1)).writeAppInfoToLog(appInfoCaptor.capture());
+        Assert.assertEquals(TEST_PKG_NAME ,appInfoCaptor.getValue().packageName);
+        Assert.assertEquals(BinaryTransparencyService.MBA_STATUS_UNINSTALLED,
+            appInfoCaptor.getValue().mbaStatus);
+    }
+
+    private Bundle setupBicCallbackHandlerTest(boolean isUpdatedSystemApp,
+            int expectedBtsMbaStatus) {
+        Bundle data = new Bundle();
+        data.putString(BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY,
+            TEST_PKG_NAME);
+        data.putInt(BackgroundInstallControlCallbackHelper.INSTALL_EVENT_TYPE_KEY,
+            BackgroundInstallControlService.INSTALL_EVENT_TYPE_INSTALL);
+        PackageStateInternal mockPackageState = mock(PackageStateInternal.class);
+        when(mPackageManagerInternal.getPackageStateInternal(TEST_PKG_NAME))
+            .thenReturn(mockPackageState);
+        when(mockPackageState.isUpdatedSystemApp()).thenReturn(isUpdatedSystemApp);
+        IBinaryTransparencyService.AppInfo appInfo = new IBinaryTransparencyService.AppInfo();
+        appInfo.packageName = TEST_PKG_NAME;
+        appInfo.longVersion = TEST_VERSION_CODE;
+        when(mBicAppInfoHelper.collectAppInfo(mockPackageState, expectedBtsMbaStatus))
+            .thenReturn(List.of(appInfo));
+        return data;
+    }
 }
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 492838e9..a2965b3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -38,7 +38,6 @@
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
 import static com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity.EXTRA_TYPE_TO_CHOOSE;
 import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG;
-import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -139,10 +138,12 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.AdditionalAnswers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 import org.mockito.internal.util.reflection.FieldReader;
@@ -260,6 +261,11 @@
                 mMockA11yController);
         when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false);
         when(mMockUserManagerInternal.isUserUnlockingOrUnlocked(anyInt())).thenReturn(true);
+        when(mMockSecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(anyInt()))
+                .then(AdditionalAnswers.returnsFirstArg());
+        when(mMockSecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
+                eq(UserHandle.USER_CURRENT)))
+                .thenReturn(mTestableContext.getUserId());
 
         final ArrayList<Display> displays = new ArrayList<>();
         final Display defaultDisplay = new Display(DisplayManagerGlobal.getInstance(),
@@ -283,14 +289,21 @@
                 mInputFilter,
                 mProxyManager,
                 mFakePermissionEnforcer);
+        mA11yms.switchUser(mTestableContext.getUserId());
+        mTestableLooper.processAllMessages();
 
+        FieldSetter.setField(mA11yms,
+                AccessibilityManagerService.class.getDeclaredField("mHasInputFilter"), true);
+        FieldSetter.setField(mA11yms,
+                AccessibilityManagerService.class.getDeclaredField("mInputFilter"), mInputFilter);
         final AccessibilityUserState userState = new AccessibilityUserState(
-                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(mA11yms.getCurrentUserIdLocked(), userState);
+                mTestableContext.getUserId(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         AccessibilityManager am = mTestableContext.getSystemService(AccessibilityManager.class);
         mA11yManagerServiceOnDevice = (IAccessibilityManager) new FieldReader(am,
                 AccessibilityManager.class.getDeclaredField("mService")).read();
         FieldSetter.setField(am, AccessibilityManager.class.getDeclaredField("mService"), mA11yms);
+        Mockito.clearInvocations(mMockMagnificationConnectionManager);
     }
 
     @After
@@ -601,7 +614,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
     public void testSetConnectionNull_borderFlagEnabled_unregisterFullScreenMagnification()
             throws RemoteException {
         mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
@@ -654,7 +666,6 @@
                 mA11yms.getCurrentUserIdLocked());
         userState.setMagnificationCapabilitiesLocked(
                 ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
-        //userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
         userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
 
         // Invokes client change to trigger onUserStateChanged.
@@ -1027,6 +1038,7 @@
         when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
 
         mA11yms.switchUser(mA11yms.getCurrentUserIdLocked() + 1);
+        mTestableLooper.processAllMessages();
 
         assertThat(lockState.get()).containsExactly(false);
     }
@@ -1116,9 +1128,6 @@
     @Test
     public void enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn()
             throws Exception {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
         setupShortcutTargetServices();
         String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
@@ -1137,9 +1146,6 @@
 
     @Test
     public void enableHardwareShortcutsForTargets_shortcutDialogSetting_isShown() {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         Settings.Secure.putInt(
                 mTestableContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
@@ -1167,9 +1173,6 @@
     @Test
     public void enableShortcutsForTargets_disableSoftwareShortcut_shortcutTurnedOff()
             throws Exception {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
         enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn();
 
@@ -1187,9 +1190,6 @@
 
     @Test
     public void enableShortcutsForTargets_enableSoftwareShortcutWithMagnification_menuSizeIncreased() {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
 
         mA11yms.enableShortcutsForTargets(
@@ -1233,9 +1233,6 @@
     @Test
     public void enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService()
             throws Exception {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
         setupShortcutTargetServices();
 
@@ -1256,9 +1253,6 @@
     @Test
     public void enableShortcutsForTargets_disableAlwaysOnServiceSoftwareShortcut_turnsOffAlwaysOnService()
             throws Exception {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService();
 
         mA11yms.enableShortcutsForTargets(
@@ -1298,9 +1292,6 @@
     @Test
     public void enableShortcutsForTargets_disableStandardServiceSoftwareShortcutWithServiceOn_wontTurnOffService()
             throws Exception {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService();
         AccessibilityUtils.setAccessibilityServiceState(
                 mTestableContext, TARGET_STANDARD_A11Y_SERVICE, /* enabled= */ true);
@@ -1321,9 +1312,6 @@
 
     @Test
     public void enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated() {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
 
         mA11yms.enableShortcutsForTargets(
@@ -1343,9 +1331,6 @@
 
     @Test
     public void enableShortcutsForTargets_disableTripleTapShortcut_settingUpdated() {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated();
 
         mA11yms.enableShortcutsForTargets(
@@ -1364,9 +1349,6 @@
 
     @Test
     public void enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated() {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
 
         mA11yms.enableShortcutsForTargets(
@@ -1386,9 +1368,6 @@
 
     @Test
     public void enableShortcutsForTargets_disableMultiFingerMultiTapsShortcut_settingUpdated() {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated();
 
         mA11yms.enableShortcutsForTargets(
@@ -1408,9 +1387,6 @@
 
     @Test
     public void enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet() {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
         setupShortcutTargetServices();
 
@@ -1430,9 +1406,6 @@
 
     @Test
     public void enableShortcutsForTargets_disableVolumeKeysShortcut_shortcutNotSet() {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet();
 
         mA11yms.enableShortcutsForTargets(
@@ -1452,9 +1425,6 @@
 
     @Test
     public void enableShortcutsForTargets_enableQuickSettings_shortcutSet() {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
         setupShortcutTargetServices();
 
@@ -1480,9 +1450,6 @@
 
     @Test
     public void enableShortcutsForTargets_disableQuickSettings_shortcutNotSet() {
-        // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
-        assumeTrue("The test is setup to run as a user 0",
-                isSameCurrentUser(mA11yms, mTestableContext));
         enableShortcutsForTargets_enableQuickSettings_shortcutSet();
 
         mA11yms.enableShortcutsForTargets(
@@ -1686,14 +1653,17 @@
     @Test
     @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void restoreShortcutTargets_qs_a11yQsTargetsRestored() {
+        // TODO: remove the assumption when we fix b/381294327
+        assumeTrue("The test is setup to run as a user 0",
+                mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
         String daltonizerTile =
                 AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
         String colorInversionTile =
                 AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString();
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
         userState.updateShortcutTargetsLocked(Set.of(daltonizerTile), QUICK_SETTINGS);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
 
         broadcastSettingRestored(
                 ShortcutUtils.convertToKey(QUICK_SETTINGS),
@@ -1709,15 +1679,18 @@
     @Test
     @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void restoreShortcutTargets_qs_a11yQsTargetsNotRestored() {
+        // TODO: remove the assumption when we fix b/381294327
+        assumeTrue("The test is setup to run as a user 0",
+                mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
         String daltonizerTile =
                 AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
         String colorInversionTile =
                 AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString();
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
         userState.updateShortcutTargetsLocked(Set.of(daltonizerTile), QUICK_SETTINGS);
         putShortcutSettingForUser(QUICK_SETTINGS, daltonizerTile, userState.mUserId);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
 
         broadcastSettingRestored(
                 ShortcutUtils.convertToKey(QUICK_SETTINGS),
@@ -1731,7 +1704,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX)
     public void onHandleForceStop_dontDoIt_packageEnabled_returnsTrue() {
         setupShortcutTargetServices();
         AccessibilityUserState userState = mA11yms.getCurrentUserState();
@@ -1742,19 +1714,18 @@
                 ComponentName::getPackageName).toList().toArray(new String[0]);
 
         PackageMonitor monitor = spy(mA11yms.getPackageMonitor());
-        when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM);
+        when(monitor.getChangingUserId()).thenReturn(userState.mUserId);
         mA11yms.setPackageMonitor(monitor);
 
         assertTrue(mA11yms.getPackageMonitor().onHandleForceStop(
                 new Intent(),
                 packages,
-                UserHandle.USER_SYSTEM,
+                userState.mUserId,
                 false
         ));
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX)
     public void onHandleForceStop_doIt_packageEnabled_returnsFalse() {
         setupShortcutTargetServices();
         AccessibilityUserState userState = mA11yms.getCurrentUserState();
@@ -1765,42 +1736,43 @@
                 ComponentName::getPackageName).toList().toArray(new String[0]);
 
         PackageMonitor monitor = spy(mA11yms.getPackageMonitor());
-        when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM);
+        when(monitor.getChangingUserId()).thenReturn(userState.mUserId);
         mA11yms.setPackageMonitor(monitor);
 
         assertFalse(mA11yms.getPackageMonitor().onHandleForceStop(
                 new Intent(),
                 packages,
-                UserHandle.USER_SYSTEM,
+                userState.mUserId,
                 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);
+        when(monitor.getChangingUserId()).thenReturn(mA11yms.getCurrentUserIdLocked());
         mA11yms.setPackageMonitor(monitor);
 
         assertFalse(mA11yms.getPackageMonitor().onHandleForceStop(
                 new Intent(),
                 new String[]{"FOO", "BAR"},
-                UserHandle.USER_SYSTEM,
+                mA11yms.getCurrentUserIdLocked(),
                 false
         ));
     }
 
     @Test
-    @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE)
     public void restoreShortcutTargets_hardware_targetsMerged() {
+        // TODO: remove the assumption when we fix b/381294327
+        assumeTrue("The test is setup to run as a user 0",
+                mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
         mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
         final String servicePrevious = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
         final String otherPrevious = TARGET_MAGNIFICATION;
         final String serviceRestored = TARGET_STANDARD_A11Y_SERVICE_NAME;
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         setupShortcutTargetServices(userState);
         mA11yms.enableShortcutsForTargets(
                 true, HARDWARE, List.of(servicePrevious, otherPrevious), userState.mUserId);
@@ -1817,20 +1789,20 @@
     }
 
     @Test
-    @EnableFlags({
-            android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
-            Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
     public void restoreShortcutTargets_hardware_alreadyHadDefaultService_doesNotClear() {
+        // TODO: remove the assumption when we fix b/381294327
+        assumeTrue("The test is setup to run as a user 0",
+                mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
         final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME;
         mTestableContext.getOrCreateTestableResources().addOverride(
                 R.string.config_defaultAccessibilityService, serviceDefault);
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         setupShortcutTargetServices(userState);
 
         // default is present in userState & setting, so it's not cleared
-        putShortcutSettingForUser(HARDWARE, serviceDefault, UserHandle.USER_SYSTEM);
+        putShortcutSettingForUser(HARDWARE, serviceDefault, userState.mUserId);
         userState.updateShortcutTargetsLocked(Set.of(serviceDefault), HARDWARE);
 
         broadcastSettingRestored(
@@ -1845,10 +1817,10 @@
     }
 
     @Test
-    @EnableFlags({
-            android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
-            Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
     public void restoreShortcutTargets_hardware_didNotHaveDefaultService_clearsDefaultService() {
+        // TODO: remove the assumption when we fix b/381294327
+        assumeTrue("The test is setup to run as a user 0",
+                mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
         final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME;
         final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
         // Restored value from the broadcast contains both default and non-default service.
@@ -1856,8 +1828,8 @@
         mTestableContext.getOrCreateTestableResources().addOverride(
                 R.string.config_defaultAccessibilityService, serviceDefault);
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         setupShortcutTargetServices(userState);
 
         broadcastSettingRestored(ShortcutUtils.convertToKey(HARDWARE),
@@ -1872,10 +1844,10 @@
     }
 
     @Test
-    @EnableFlags({
-            android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
-            Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
     public void restoreShortcutTargets_hardware_nullSetting_clearsDefaultService() {
+        // TODO: remove the assumption when we fix b/381294327
+        assumeTrue("The test is setup to run as a user 0",
+                mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
         final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE_NAME;
         final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
         // Restored value from the broadcast contains both default and non-default service.
@@ -1883,13 +1855,13 @@
         mTestableContext.getOrCreateTestableResources().addOverride(
                 R.string.config_defaultAccessibilityService, serviceDefault);
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         setupShortcutTargetServices(userState);
 
         // UserState has default, but setting is null (this emulates a typical scenario in SUW).
         userState.updateShortcutTargetsLocked(Set.of(serviceDefault), HARDWARE);
-        putShortcutSettingForUser(HARDWARE, null, UserHandle.USER_SYSTEM);
+        putShortcutSettingForUser(HARDWARE, null, userState.mUserId);
 
         broadcastSettingRestored(ShortcutUtils.convertToKey(HARDWARE),
                 /*newValue=*/combinedRestored);
@@ -1907,8 +1879,8 @@
     public void onNavButtonNavigation_migratesGestureTargets() {
         mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         setupShortcutTargetServices(userState);
         userState.updateShortcutTargetsLocked(
                 Set.of(TARGET_STANDARD_A11Y_SERVICE_NAME), SOFTWARE);
@@ -1931,20 +1903,20 @@
     public void onNavButtonNavigation_gestureTargets_noButtonTargets_navBarButtonMode() {
         mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         setupShortcutTargetServices(userState);
         userState.updateShortcutTargetsLocked(Set.of(), SOFTWARE);
         userState.updateShortcutTargetsLocked(
                 Set.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()), GESTURE);
         ShortcutUtils.setButtonMode(
-                mTestableContext, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU, UserHandle.USER_SYSTEM);
+                mTestableContext, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU, userState.mUserId);
 
         Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
                 NAVIGATION_MODE, NAV_BAR_MODE_3BUTTON, userState.mUserId);
         mA11yms.updateShortcutsForCurrentNavigationMode();
 
-        assertThat(ShortcutUtils.getButtonMode(mTestableContext, UserHandle.USER_SYSTEM))
+        assertThat(ShortcutUtils.getButtonMode(mTestableContext, userState.mUserId))
                 .isEqualTo(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
     }
 
@@ -1953,11 +1925,11 @@
     public void onGestureNavigation_floatingMenuMode() {
         mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         setupShortcutTargetServices(userState);
         ShortcutUtils.setButtonMode(
-                mTestableContext, ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_SYSTEM);
+                mTestableContext, ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, userState.mUserId);
 
         Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
                 NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
@@ -1972,8 +1944,8 @@
     public void onNavigation_revertGestureTargets() {
         mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         setupShortcutTargetServices(userState);
         userState.updateShortcutTargetsLocked(
                 Set.of(TARGET_STANDARD_A11Y_SERVICE_NAME), SOFTWARE);
@@ -1996,8 +1968,8 @@
     public void onNavigation_gestureNavigation_gestureButtonMode_migratesTargetsToGesture() {
         mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         setupShortcutTargetServices(userState);
         userState.updateShortcutTargetsLocked(Set.of(
                 TARGET_STANDARD_A11Y_SERVICE_NAME,
@@ -2005,7 +1977,7 @@
         userState.updateShortcutTargetsLocked(Set.of(), GESTURE);
 
         ShortcutUtils.setButtonMode(
-                mTestableContext, ACCESSIBILITY_BUTTON_MODE_GESTURE, UserHandle.USER_SYSTEM);
+                mTestableContext, ACCESSIBILITY_BUTTON_MODE_GESTURE, userState.mUserId);
         Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
                 NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
         mA11yms.updateShortcutsForCurrentNavigationMode();
@@ -2021,11 +1993,11 @@
     @DisableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
     public void onNavigation_gestureNavigation_correctsButtonMode() {
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         setupShortcutTargetServices(userState);
         ShortcutUtils.setButtonMode(
-                mTestableContext, ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_SYSTEM);
+                mTestableContext, ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, userState.mUserId);
 
         Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
                 NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
@@ -2039,11 +2011,11 @@
     @DisableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
     public void onNavigation_navBarNavigation_correctsButtonMode() {
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         setupShortcutTargetServices(userState);
         ShortcutUtils.setButtonMode(
-                mTestableContext, ACCESSIBILITY_BUTTON_MODE_GESTURE, UserHandle.USER_SYSTEM);
+                mTestableContext, ACCESSIBILITY_BUTTON_MODE_GESTURE, userState.mUserId);
 
         Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
                 NAVIGATION_MODE, NAV_BAR_MODE_3BUTTON, userState.mUserId);
@@ -2057,8 +2029,8 @@
     public void showAccessibilityTargetSelection_navBarNavigationMode_softwareExtra() {
         mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
                 NAVIGATION_MODE, NAV_BAR_MODE_3BUTTON, userState.mUserId);
 
@@ -2073,8 +2045,8 @@
     public void showAccessibilityTargetSelection_gestureNavigationMode_softwareExtra() {
         mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
                 NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
 
@@ -2089,8 +2061,8 @@
     public void showAccessibilityTargetSelection_gestureNavigationMode_gestureExtra() {
         mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
                 NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
 
@@ -2124,18 +2096,19 @@
     public void switchUser_callsUserInitializationCompleteCallback() throws RemoteException {
         mA11yms.mUserInitializationCompleteCallbacks.add(mUserInitializationCompleteCallback);
 
-        mA11yms.switchUser(UserHandle.MIN_SECONDARY_USER_ID);
+        int newUserId = mA11yms.getCurrentUserIdLocked() + 1;
+        mA11yms.switchUser(newUserId);
+        mTestableLooper.processAllMessages();
 
-        verify(mUserInitializationCompleteCallback).onUserInitializationComplete(
-                UserHandle.MIN_SECONDARY_USER_ID);
+        verify(mUserInitializationCompleteCallback).onUserInitializationComplete(newUserId);
     }
 
     @Test
     @DisableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
     public void getShortcutTypeForGenericShortcutCalls_softwareType() {
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
 
         assertThat(mA11yms.getShortcutTypeForGenericShortcutCalls(userState.mUserId))
                 .isEqualTo(SOFTWARE);
@@ -2145,8 +2118,8 @@
     @EnableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
     public void getShortcutTypeForGenericShortcutCalls_gestureNavigationMode_gestureType() {
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
                 NAVIGATION_MODE, NAV_BAR_MODE_GESTURAL, userState.mUserId);
 
@@ -2158,8 +2131,8 @@
     @EnableFlags(android.provider.Flags.FLAG_A11Y_STANDALONE_GESTURE_ENABLED)
     public void getShortcutTypeForGenericShortcutCalls_buttonNavigationMode_softwareType() {
         final AccessibilityUserState userState = new AccessibilityUserState(
-                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
-        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
+        mA11yms.mUserStates.put(userState.mUserId, userState);
         Settings.Secure.putIntForUser(mTestableContext.getContentResolver(),
                 NAVIGATION_MODE, NAV_BAR_MODE_3BUTTON, userState.mUserId);
 
@@ -2350,13 +2323,13 @@
     private Set<String> readStringsFromSetting(String setting) {
         final Set<String> result = new ArraySet<>();
         mA11yms.readColonDelimitedSettingToSet(
-                setting, UserHandle.USER_SYSTEM, str -> str, result);
+                setting, mA11yms.getCurrentUserIdLocked(), str -> str, result);
         return result;
     }
 
     private void writeStringsToSetting(Set<String> strings, String setting) {
         mA11yms.persistColonDelimitedSetToSettingLocked(
-                setting, UserHandle.USER_SYSTEM, strings, str -> str);
+                setting, mA11yms.getCurrentUserIdLocked(), strings, str -> str);
     }
 
     private void broadcastSettingRestored(String setting, String newValue) {
@@ -2527,10 +2500,6 @@
         }
     }
 
-    private static boolean isSameCurrentUser(AccessibilityManagerService service, Context context) {
-        return service.getCurrentUserIdLocked() == context.getUserId();
-    }
-
     private void putShortcutSettingForUser(@UserShortcutType int shortcutType,
             String shortcutValue, int userId) {
         Settings.Secure.putStringForUser(
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 8914696..d4f2dcc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -448,4 +448,14 @@
         mConnection.binderDied();
         assertThat(mConnection.getServiceInfo().flags & flag).isEqualTo(0);
     }
+
+    @Test
+    public void setInputMethodEnabled_checksAccessWithProvidedImeIdAndUserId() {
+        final String imeId = "test_ime_id";
+        final int callingUserId = UserHandle.getCallingUserId();
+        mConnection.setInputMethodEnabled(imeId, true);
+
+        verify(mMockSecurityPolicy).canEnableDisableInputMethod(
+                eq(imeId), any(), eq(callingUserId));
+    }
 }
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 52b33db..f371823 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
@@ -51,9 +51,6 @@
 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;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArraySet;
 import android.view.KeyEvent;
@@ -98,9 +95,6 @@
     private static final int STREAMED_CALLING_UID = 9876;
 
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
-    @Rule
     public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Mock private Context mMockContext;
@@ -243,7 +237,6 @@
      * app changes to the proxy device.
      */
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_PROXY_USE_APPS_ON_VIRTUAL_DEVICE_LISTENER)
     public void testUpdateProxyOfRunningAppsChange_changedUidIsStreamedApp_propagatesChange() {
         final VirtualDeviceManagerInternal localVdm =
                 Mockito.mock(VirtualDeviceManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 7e0c12a..ac27a97 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -21,7 +21,6 @@
 import static com.android.server.accessibility.Flags.FLAG_MAGNIFICATION_ENLARGE_POINTER_BUGFIX;
 import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
 import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY;
-import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -308,7 +307,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
     public void testSetScale_noConnection_doNothing() {
         register(TEST_DISPLAY);
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index f0d3456..c878799 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -57,10 +57,6 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
-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.test.mock.MockContentResolver;
 import android.testing.DexmakerShareClassLoaderRule;
@@ -82,7 +78,6 @@
 import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
-import com.android.window.flags.Flags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -101,9 +96,6 @@
 @RunWith(AndroidJUnit4.class)
 public class MagnificationControllerTest {
 
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
     private static final int TEST_SERVICE_ID = 1;
     private static final Region INITIAL_SCREEN_MAGNIFICATION_REGION =
@@ -1365,8 +1357,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
-    public void onFullscreenMagnificationActivationState_systemUiBorderFlagOn_notifyConnection() {
+    public void onFullscreenMagnificationActivationState_notifyConnection() {
         mMagnificationController.onFullScreenMagnificationActivationState(
                 TEST_DISPLAY, /* activated= */ true);
 
@@ -1374,17 +1365,6 @@
                 .onFullscreenMagnificationActivationChanged(TEST_DISPLAY, /* activated= */ true);
     }
 
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
-    public void
-            onFullscreenMagnificationActivationState_systemUiBorderFlagOff_neverNotifyConnection() {
-        mMagnificationController.onFullScreenMagnificationActivationState(
-                TEST_DISPLAY, /* activated= */ true);
-
-        verify(mMagnificationConnectionManager, never())
-                .onFullscreenMagnificationActivationChanged(TEST_DISPLAY, /* activated= */ true);
-    }
-
     private void setMagnificationEnabled(int mode) throws RemoteException {
         setMagnificationEnabled(mode, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
     }
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 9cd3186..605fed0 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -38,6 +38,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -92,6 +93,8 @@
 
     private static final String TEST_OP_PACKAGE_NAME = "test_package";
 
+    private final @UserIdInt int mUserId = UserHandle.getCallingUserId();
+
     private AuthService mAuthService;
 
     @Rule
@@ -257,12 +260,11 @@
         final Binder token = new Binder();
         final PromptInfo promptInfo = new PromptInfo();
         final long sessionId = 0;
-        final int userId = 0;
 
         mAuthService.mImpl.authenticate(
                 token,
                 sessionId,
-                userId,
+                mUserId,
                 mReceiver,
                 TEST_OP_PACKAGE_NAME,
                 promptInfo);
@@ -270,7 +272,7 @@
         verify(mBiometricService).authenticate(
                 eq(token),
                 eq(sessionId),
-                eq(userId),
+                eq(mUserId),
                 eq(mReceiver),
                 eq(TEST_OP_PACKAGE_NAME),
                 eq(promptInfo));
@@ -286,12 +288,11 @@
         final Binder token = new Binder();
         final PromptInfo promptInfo = new PromptInfo();
         final long sessionId = 0;
-        final int userId = 0;
 
         mAuthService.mImpl.authenticate(
                 token,
                 sessionId,
-                userId,
+                mUserId,
                 mReceiver,
                 TEST_OP_PACKAGE_NAME,
                 promptInfo);
@@ -299,7 +300,7 @@
         verify(mBiometricService, never()).authenticate(
                 eq(token),
                 eq(sessionId),
-                eq(userId),
+                eq(mUserId),
                 eq(mReceiver),
                 eq(TEST_OP_PACKAGE_NAME),
                 eq(promptInfo));
@@ -313,12 +314,11 @@
 
         final PromptInfo promptInfo = new PromptInfo();
         final long sessionId = 0;
-        final int userId = 0;
 
         mAuthService.mImpl.authenticate(
                 null /* token */,
                 sessionId,
-                userId,
+                mUserId,
                 mReceiver,
                 TEST_OP_PACKAGE_NAME,
                 promptInfo);
@@ -338,7 +338,7 @@
         mAuthService.mImpl.authenticate(
                 token,
                 0, /* sessionId */
-                0, /* userId */
+                mUserId,
                 mReceiver,
                 TEST_OP_PACKAGE_NAME,
                 new PromptInfo());
@@ -356,7 +356,7 @@
         mAuthService.mImpl.authenticate(
                 token,
                 0, /* sessionId */
-                0, /* userId */
+                mUserId,
                 mReceiver,
                 TEST_OP_PACKAGE_NAME,
                 new PromptInfo());
@@ -414,20 +414,19 @@
         mAuthService = new AuthService(mContext, mInjector);
         mAuthService.onStart();
 
-        final int userId = 0;
         final int expectedResult = BIOMETRIC_SUCCESS;
         final int authenticators = 0;
         when(mBiometricService.canAuthenticate(anyString(), anyInt(), anyInt(), anyInt()))
                 .thenReturn(expectedResult);
 
         final int result = mAuthService.mImpl
-                .canAuthenticate(TEST_OP_PACKAGE_NAME, userId, authenticators);
+                .canAuthenticate(TEST_OP_PACKAGE_NAME, mUserId, authenticators);
 
         assertEquals(expectedResult, result);
         waitForIdle();
         verify(mBiometricService).canAuthenticate(
                 eq(TEST_OP_PACKAGE_NAME),
-                eq(userId),
+                eq(mUserId),
                 eq(UserHandle.getCallingUserId()),
                 eq(authenticators));
     }
@@ -440,18 +439,17 @@
         mAuthService = new AuthService(mContext, mInjector);
         mAuthService.onStart();
 
-        final int userId = 0;
         final boolean expectedResult = true;
         when(mBiometricService.hasEnrolledBiometrics(anyInt(), anyString())).thenReturn(
                 expectedResult);
 
-        final boolean result = mAuthService.mImpl.hasEnrolledBiometrics(userId,
+        final boolean result = mAuthService.mImpl.hasEnrolledBiometrics(mUserId,
                 TEST_OP_PACKAGE_NAME);
 
         assertEquals(expectedResult, result);
         waitForIdle();
         verify(mBiometricService).hasEnrolledBiometrics(
-                eq(userId),
+                eq(mUserId),
                 eq(TEST_OP_PACKAGE_NAME));
     }
 
@@ -528,13 +526,12 @@
         mAuthService = new AuthService(mContext, mInjector);
         mAuthService.onStart();
 
-        final int userId = 0;
         final int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG;
 
-        mAuthService.mImpl.getLastAuthenticationTime(userId, authenticators);
+        mAuthService.mImpl.getLastAuthenticationTime(mUserId, authenticators);
 
         waitForIdle();
-        verify(mBiometricService).getLastAuthenticationTime(eq(userId), eq(authenticators));
+        verify(mBiometricService).getLastAuthenticationTime(eq(mUserId), eq(authenticators));
     }
 
     private static void setInternalAndTestBiometricPermissions(
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index 1a593dd..42b7f4b 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -755,6 +755,7 @@
 
         verify(mActivityListener, after(TIMEOUT_MILLIS).never())
                 .onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
+        verify(mActivityListener, never()).onSecureWindowHidden(eq(DISPLAY_ID));
         verify(mActivityListener, never())
                 .onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
     }
@@ -776,6 +777,10 @@
                 .onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
         verify(mActivityListener, after(TIMEOUT_MILLIS).never())
                 .onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
+
+        assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0, 0)).isTrue();
+
+        verify(mActivityListener, timeout(TIMEOUT_MILLIS)).onSecureWindowHidden(eq(DISPLAY_ID));
     }
 
     @Test
@@ -794,6 +799,7 @@
 
         verify(mActivityListener, after(TIMEOUT_MILLIS).never())
                 .onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
+        verify(mActivityListener, never()).onSecureWindowHidden(eq(DISPLAY_ID));
         verify(mActivityListener, never())
                 .onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
     }
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 727d1b5..32578a7 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
@@ -2076,10 +2076,10 @@
     private AssociationInfo createAssociationInfo(int associationId, String deviceProfile,
             CharSequence displayName) {
         return new AssociationInfo(associationId, /* userId= */ 0, /* packageName=*/ null,
-                /* tag= */ null, MacAddress.BROADCAST_ADDRESS, displayName, deviceProfile,
+                MacAddress.BROADCAST_ADDRESS, displayName, deviceProfile,
                 /* associatedDevice= */ null, /* selfManaged= */ true,
                 /* notifyOnDeviceNearby= */ false, /* revoked= */ false, /* pending= */ false,
                 /* timeApprovedMs= */0, /* lastTimeConnectedMs= */0,
-                /* systemDataSyncFlags= */ -1, /* deviceIcon= */ null);
+                /* systemDataSyncFlags= */ -1, /* deviceIcon= */ null, /* deviceId= */ null);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index cf5dc4b..30aa8ce 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4404,6 +4404,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeEnabledModifiesSetting() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -4415,6 +4416,7 @@
     }
 
     @Test
+    @Ignore("b/277916462")
     public void testSetAutoTimeEnabledWithPOOnUser0() throws Exception {
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         setupProfileOwnerOnUser0();
@@ -4434,7 +4436,7 @@
     }
 
     @Test
-    @Ignore("b/359188869")
+    @Ignore("b/277916462")
     public void testSetAutoTimeEnabledWithPOOfOrganizationOwnedDevice() throws Exception {
         setupProfileOwner();
         configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
@@ -5034,8 +5036,6 @@
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
     public void testSetSecondaryLockscreenEnabled() throws Exception {
-        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
-
         verifySetSecondaryLockscreenEnabled(false);
         verifySetSecondaryLockscreenEnabled(true);
     }
@@ -5043,6 +5043,10 @@
     private void verifySetSecondaryLockscreenEnabled(boolean enabled) throws Exception {
         reset(getServices().supervisionManagerInternal);
 
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        doReturn(DpmMockContext.CALLER_UID).when(getServices().packageManagerInternal)
+                .getPackageUid(any(), anyLong(), anyInt());
+
         dpm.setSecondaryLockscreenEnabled(admin1, enabled);
         verify(getServices().supervisionManagerInternal).setSupervisionLockscreenEnabledForUser(
                 CALLER_USER_HANDLE, enabled, null);
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index ab5a5a9..5127b2d 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -24,9 +24,14 @@
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertThrows;
 
 import android.content.Context;
+import android.frameworks.devicestate.DeviceStateConfiguration;
+import android.frameworks.devicestate.ErrorCode;
+import android.frameworks.devicestate.IDeviceStateListener;
+import android.frameworks.devicestate.IDeviceStateService;
 import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateInfo;
 import android.hardware.devicestate.DeviceStateRequest;
@@ -34,6 +39,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.FlagsParameterization;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -336,6 +342,53 @@
     }
 
     @Test
+    public void halRegisterUnregisterCallback() throws RemoteException {
+        IDeviceStateService halService = mService.getHalBinderService();
+        IDeviceStateListener halListener = new IDeviceStateListener.Stub() {
+            @Override
+            public void onDeviceStateChanged(DeviceStateConfiguration deviceState) { }
+
+            @Override
+            public int getInterfaceVersion() {
+                return IDeviceStateListener.VERSION;
+            }
+
+            @Override
+            public String getInterfaceHash() {
+                return IDeviceStateListener.HASH;
+            }
+        };
+
+        int errorCode = ErrorCode.OK;
+        try {
+            halService.unregisterListener(halListener);
+        } catch(ServiceSpecificException e) {
+            errorCode = e.errorCode;
+        }
+        assertEquals(errorCode, ErrorCode.BAD_INPUT);
+
+        errorCode = ErrorCode.OK;
+        try {
+            halService.unregisterListener(null);
+        } catch(ServiceSpecificException e) {
+            errorCode = e.errorCode;
+        }
+        assertEquals(errorCode, ErrorCode.BAD_INPUT);
+
+        halService.registerListener(halListener);
+
+        errorCode = ErrorCode.OK;
+        try {
+            halService.registerListener(halListener);
+        } catch (ServiceSpecificException e) {
+            errorCode = e.errorCode;
+        }
+        assertEquals(errorCode, ErrorCode.ALREADY_EXISTS);
+
+        halService.unregisterListener(halListener);
+    }
+
+    @Test
     public void registerCallback() throws RemoteException {
         final TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
         mService.getBinderService().registerCallback(callback);
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 c7574bd..30dac9f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -41,11 +41,14 @@
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.tv.flags.Flags;
 import android.os.Binder;
 import android.os.Looper;
 import android.os.RemoteException;
 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.stats.hdmi.HdmiStatsEnums;
 
 import androidx.test.InstrumentationRegistry;
@@ -54,6 +57,7 @@
 import com.android.server.SystemService;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -83,6 +87,9 @@
     private HdmiEarcController mHdmiEarcController;
     private FakeEarcNativeWrapper mEarcNativeWrapper;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     public void setUp() throws RemoteException {
         mHdmiCecAtomWriterSpy = spy(new HdmiCecAtomWriter());
@@ -232,7 +239,8 @@
                         HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
                         HdmiStatsEnums.VOLUME_MUTE,
                         HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
-                        HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN);
+                        HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+                        HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
     }
 
     @Test
@@ -258,7 +266,8 @@
                         HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
                         HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
                         HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
-                        HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN);
+                        HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+                        HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
     }
 
     @Test
@@ -285,7 +294,8 @@
                         HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
                         HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
                         Constants.MESSAGE_RECORD_ON,
-                        HdmiStatsEnums.UNRECOGNIZED_OPCODE);
+                        HdmiStatsEnums.UNRECOGNIZED_OPCODE,
+                        HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
     }
 
     @Test
@@ -311,7 +321,8 @@
                         HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
                         HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
                         HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
-                        HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN);
+                        HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+                        HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
     }
 
     @Test
@@ -337,6 +348,59 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_HDMI_CONTROL_COLLECT_PHYSICAL_ADDRESS})
+    public void testMessageReported_writesAtom_reportPhysicalAddress() {
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                ADDR_PLAYBACK_1, 0x1234, HdmiDeviceInfo.DEVICE_PLAYBACK);
+
+        mHdmiCecAtomWriterSpy.messageReported(
+                message,
+                HdmiStatsEnums.INCOMING,
+                1234);
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .writeHdmiCecMessageReportedAtom(
+                        1234,
+                        HdmiStatsEnums.INCOMING,
+                        Constants.ADDR_PLAYBACK_1,
+                        Constants.ADDR_BROADCAST,
+                        Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS,
+                        HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
+                        HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
+                        HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
+                        HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+                        0x1234);
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_HDMI_CONTROL_COLLECT_PHYSICAL_ADDRESS})
+    public void testMessageReported_writesAtom_reportPhysicalAddress_noParams() {
+        HdmiCecMessage message = HdmiCecMessage.build(
+                Constants.ADDR_PLAYBACK_1,
+                Constants.ADDR_BROADCAST,
+                Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS,
+                new byte[0]);
+
+        mHdmiCecAtomWriterSpy.messageReported(
+                message,
+                HdmiStatsEnums.INCOMING,
+                1234);
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .writeHdmiCecMessageReportedAtom(
+                        1234,
+                        HdmiStatsEnums.INCOMING,
+                        Constants.ADDR_PLAYBACK_1,
+                        Constants.ADDR_BROADCAST,
+                        Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS,
+                        HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN,
+                        HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN,
+                        HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
+                        HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN,
+                        HdmiCecAtomWriter.PHYSICAL_ADDRESS_INVALID);
+    }
+
+    @Test
     public void testDsmStatusChanged_onWakeUp_ArcSupported_writesAtom_logReasonWake() {
         mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_DISABLED);
         Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
@@ -369,4 +433,21 @@
                 .dsmStatusChanged(anyBoolean(), anyBoolean(),
                         eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
     }
+
+    @Test
+    public void testPowerStateChangeOnActiveSourceLostToggled_writesAtom_logReasonSetting() {
+        mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+        Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+        mTestLooper.dispatchAll();
+
+        mHdmiControlServiceSpy.writePowerStateChangeOnActiveSourceLostAtom(true);
+        mTestLooper.dispatchAll();
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .powerStateChangeOnActiveSourceLostChanged(eq(true),
+                        eq(HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_SETTING), anyString(), anyInt(), anyInt());
+        verify(mHdmiCecAtomWriterSpy, never())
+                .powerStateChangeOnActiveSourceLostChanged(eq(true),
+                        eq(HdmiStatsEnums.LOG_REASON_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_TOGGLE_POP_UP), anyString(), anyInt(), anyInt());
+    }
 }
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 077bb03..861e72d 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -95,9 +95,6 @@
     private boolean mActiveMediaSessionsPaused;
     private FakePowerManagerInternalWrapper mPowerManagerInternal =
             new FakePowerManagerInternalWrapper();
-
-    private boolean mIsOnActiveSourceLostPopupActive;
-
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -165,12 +162,12 @@
         mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService) {
             @Override
             void startHdmiCecActiveSourceLostActivity() {
-                mIsOnActiveSourceLostPopupActive = true;
+                setIsActiveSourceLostPopupLaunched(true);
             }
 
             @Override
             void dismissUiOnActiveSourceStatusRecovered() {
-                mIsOnActiveSourceLostPopupActive = false;
+                setIsActiveSourceLostPopupLaunched(false);
             }
         };
         mHdmiCecLocalDevicePlayback.init();
@@ -2389,7 +2386,7 @@
         mTestLooper.dispatchAll();
 
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
-        assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
     }
 
     @Test
@@ -2430,7 +2427,7 @@
 
         // Pop-up is not shown, playback device asserts active source since TV doesn't answer the
         // request.
-        assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
         assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
                 .isEqualTo(mPlaybackLogicalAddress);
@@ -2480,7 +2477,7 @@
         mTestLooper.dispatchAll();
 
         // Pop-up is not shown since playback device is active source.
-        assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
         assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
                 .isEqualTo(mPlaybackLogicalAddress);
@@ -2532,7 +2529,7 @@
 
         // Pop-up is shown, playback device doesn't assert active source since active path is
         // switched to a non-CEC device.
-        assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
                 .isEqualTo(ADDR_INVALID);
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress)
@@ -2569,7 +2566,7 @@
         });
         mTestLooper.dispatchAll();
 
-        assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
         assertThat(mPowerManager.isInteractive()).isTrue();
         assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
@@ -2609,13 +2606,13 @@
         mNativeWrapper.onCecMessage(activeSourceFromTv);
         mTestLooper.dispatchAll();
 
-        assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
 
         assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(setStreamPathToPlayback))
                 .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
 
-        assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
         assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
                 .isEqualTo(mPlaybackLogicalAddress);
@@ -2664,13 +2661,13 @@
         // Pop-up is triggered.
         mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
         mTestLooper.dispatchAll();
-        assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
 
         assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(routingChangeToPlayback))
                 .isEqualTo(Constants.HANDLED);
         mTestLooper.dispatchAll();
 
-        assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
         assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
                 .isEqualTo(mPlaybackLogicalAddress);
@@ -2711,7 +2708,7 @@
         mNativeWrapper.onCecMessage(activeSourceFromTv);
         mTestLooper.dispatchAll();
 
-        assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
         mHdmiControlService.oneTouchPlay(new IHdmiControlCallback() {
             @Override
             public void onComplete(int result) throws RemoteException {
@@ -2724,7 +2721,7 @@
         });
         mTestLooper.dispatchAll();
 
-        assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
         assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
         assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
                 .isEqualTo(mPlaybackLogicalAddress);
@@ -2897,11 +2894,11 @@
             } else {
                 mTestLooper.moveTimeForward(TIMEOUT_MS);
                 mTestLooper.dispatchAll();
-                assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+                assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isFalse();
                 return;
             }
         }
-        assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.isActiveSourceLostPopupLaunched()).isTrue();
 
         mPowerManagerInternal.setIdleDuration(idleDuration);
         mTestLooper.moveTimeForward(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
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 0816e7b..5be4490 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -118,6 +118,7 @@
     private boolean mDisableCecOnStandbyByLowEnergyMode;
     private boolean mWasCecDisabledOnStandbyByLowEnergyMode;
     private boolean mUseHdmiCecPowerStatusController;
+    private boolean mUserEnabledCecInOfflineMode;
 
     private class DeviceEventListener {
         private HdmiDeviceInfo mDevice;
@@ -250,6 +251,11 @@
                     protected void setWasCecDisabledOnStandbyByLowEnergyMode(boolean value) {
                         mWasCecDisabledOnStandbyByLowEnergyMode = value;
                     }
+
+                    @Override
+                    protected boolean userEnabledCecInOfflineMode() {
+                        return mUserEnabledCecInOfflineMode;
+                    }
                 };
 
         mHdmiControlService.setIoLooper(mMyLooper);
@@ -298,6 +304,7 @@
         mWasCecDisabledOnStandbyByLowEnergyMode = false;
         mDisableCecOnStandbyByLowEnergyMode = false;
         mUseHdmiCecPowerStatusController = false;
+        mUserEnabledCecInOfflineMode = false;
         mNativeWrapper.clearResultMessages();
     }
 
@@ -2400,6 +2407,32 @@
         assertTrue(mVendorCommandListeners.contains(vendorCommandListenerInvocationSettingChange));
     }
 
+    @Test
+    public void lowEnergyMode_userEnabledCecInOfflineMode_onStandby_cecStaysEnabled() {
+        mDisableCecOnStandbyByLowEnergyMode = true;
+        mUseHdmiCecPowerStatusController = true;
+        mUserEnabledCecInOfflineMode = true;
+        mPowerManager.setIsLowPowerStandbyEnabled(true);
+
+        assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+                        HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+                HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+                        HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+                HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        assertFalse(mWasCecDisabledOnStandbyByLowEnergyMode);
+        mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+                        HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+                HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        assertFalse(mWasCecDisabledOnStandbyByLowEnergyMode);
+    }
+
     protected static class MockTvDevice extends HdmiCecLocalDeviceTv {
         MockTvDevice(HdmiControlService service) {
             super(service);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
deleted file mode 100644
index fd22118..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity;
-
-import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
-import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
-import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
-import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
-import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
-import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE;
-import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_UID;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.content.pm.ParceledListSlice;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.Settings;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.R;
-import com.android.server.compat.PlatformCompat;
-import com.android.server.testutils.TestUtils;
-
-import org.junit.After;
-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.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Supplier;
-
-/** Unit test for {@link com.android.server.integrity.AppIntegrityManagerServiceImpl} */
-@RunWith(JUnit4.class)
-public class AppIntegrityManagerServiceImplTest {
-    private static final String TEST_APP_PATH =
-            "AppIntegrityManagerServiceImplTest/AppIntegrityManagerServiceTestApp.apk";
-
-    private static final String TEST_APP_TWO_CERT_PATH =
-            "AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk";
-
-    private static final String TEST_APP_SOURCE_STAMP_PATH =
-            "AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk";
-
-    private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
-    private static final String VERSION = "version";
-    private static final String TEST_FRAMEWORK_PACKAGE = "com.android.frameworks.servicestests";
-
-    private static final String PACKAGE_NAME = "com.test.app";
-
-    private static final long VERSION_CODE = 100;
-    private static final String INSTALLER = "com.long.random.test.installer.name";
-
-    // These are obtained by running the test and checking logcat.
-    private static final String APP_CERT =
-            "F14CFECF5070874C05D3D2FA98E046BE20BDE02A0DC74BAF6B59C6A0E4C06850";
-    // We use SHA256 for package names longer than 32 characters.
-    private static final String INSTALLER_SHA256 =
-            "30F41A7CBF96EE736A54DD6DF759B50ED3CC126ABCEF694E167C324F5976C227";
-    private static final String SOURCE_STAMP_CERTIFICATE_HASH =
-            "C6E737809CEF2B08CC6694892215F82A5E8FBC3C2A0F6212770310B90622D2D9";
-
-    private static final String DUMMY_APP_TWO_CERTS_CERT_1 =
-            "C0369C2A1096632429DFA8433068AECEAD00BAC337CA92A175036D39CC9AFE94";
-    private static final String DUMMY_APP_TWO_CERTS_CERT_2 =
-            "94366E0A80F3A3F0D8171A15760B88E228CD6E1101F0414C98878724FBE70147";
-
-    private static final String PLAY_STORE_PKG = "com.android.vending";
-    private static final String ADB_INSTALLER = "adb";
-    private static final String PLAY_STORE_CERT = "play_store_cert";
-
-    @org.junit.Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
-
-    @Mock PackageManagerInternal mPackageManagerInternal;
-    @Mock PlatformCompat mPlatformCompat;
-    @Mock Context mMockContext;
-    @Mock Resources mMockResources;
-    @Mock Handler mHandler;
-
-    private final Context mRealContext = InstrumentationRegistry.getTargetContext();
-
-    private PackageManager mSpyPackageManager;
-    private File mTestApk;
-    private File mTestApkTwoCerts;
-    private File mTestApkSourceStamp;
-
-    // under test
-    private AppIntegrityManagerServiceImpl mService;
-
-    @Before
-    public void setup() throws Exception {
-        mTestApk = File.createTempFile("AppIntegrity", ".apk");
-        try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_PATH)) {
-            Files.copy(inputStream, mTestApk.toPath(), REPLACE_EXISTING);
-        }
-
-        mTestApkTwoCerts = File.createTempFile("AppIntegrityTwoCerts", ".apk");
-        try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_TWO_CERT_PATH)) {
-            Files.copy(inputStream, mTestApkTwoCerts.toPath(), REPLACE_EXISTING);
-        }
-
-        mTestApkSourceStamp = File.createTempFile("AppIntegritySourceStamp", ".apk");
-        try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_SOURCE_STAMP_PATH)) {
-            Files.copy(inputStream, mTestApkSourceStamp.toPath(), REPLACE_EXISTING);
-        }
-
-        mService =
-                new AppIntegrityManagerServiceImpl(
-                        mMockContext,
-                        mPackageManagerInternal,
-                        mHandler);
-
-        mSpyPackageManager = spy(mRealContext.getPackageManager());
-        // setup mocks to prevent NPE
-        when(mMockContext.getPackageManager()).thenReturn(mSpyPackageManager);
-        when(mMockContext.getResources()).thenReturn(mMockResources);
-        when(mMockResources.getStringArray(anyInt())).thenReturn(new String[] {});
-        // These are needed to override the Settings.Global.get result.
-        when(mMockContext.getContentResolver()).thenReturn(mRealContext.getContentResolver());
-        setIntegrityCheckIncludesRuleProvider(true);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mTestApk.delete();
-        mTestApkTwoCerts.delete();
-        mTestApkSourceStamp.delete();
-    }
-
-    @Test
-    public void broadcastReceiverRegistration() throws Exception {
-        allowlistUsAsRuleProvider();
-        makeUsSystemApp();
-        ArgumentCaptor<IntentFilter> intentFilterCaptor =
-                ArgumentCaptor.forClass(IntentFilter.class);
-
-        verify(mMockContext).registerReceiver(any(), intentFilterCaptor.capture(), any(), any());
-        assertEquals(1, intentFilterCaptor.getValue().countActions());
-        assertEquals(
-                Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION,
-                intentFilterCaptor.getValue().getAction(0));
-        assertEquals(1, intentFilterCaptor.getValue().countDataTypes());
-        assertEquals(PACKAGE_MIME_TYPE, intentFilterCaptor.getValue().getDataType(0));
-    }
-
-    @Test
-    public void handleBroadcast_allow() throws Exception {
-        allowlistUsAsRuleProvider();
-        makeUsSystemApp();
-        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mMockContext)
-                .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
-        Intent intent = makeVerificationIntent();
-
-        broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
-        runJobInHandler();
-
-        verify(mPackageManagerInternal)
-                .setIntegrityVerificationResult(
-                        1, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
-    }
-
-    private void allowlistUsAsRuleProvider() {
-        Resources mockResources = mock(Resources.class);
-        when(mockResources.getStringArray(R.array.config_integrityRuleProviderPackages))
-                .thenReturn(new String[] {TEST_FRAMEWORK_PACKAGE});
-        when(mMockContext.getResources()).thenReturn(mockResources);
-    }
-
-    private void runJobInHandler() {
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-        // sendMessageAtTime is the first non-final method in the call chain when "post" is invoked.
-        verify(mHandler).sendMessageAtTime(messageCaptor.capture(), anyLong());
-        messageCaptor.getValue().getCallback().run();
-    }
-
-    private void makeUsSystemApp() throws Exception {
-        makeUsSystemApp(true);
-    }
-
-    private void makeUsSystemApp(boolean isSystemApp) throws Exception {
-        PackageInfo packageInfo =
-                mRealContext.getPackageManager().getPackageInfo(TEST_FRAMEWORK_PACKAGE, 0);
-        if (isSystemApp) {
-            packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
-        } else {
-            packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
-        }
-        doReturn(packageInfo)
-                .when(mSpyPackageManager)
-                .getPackageInfo(eq(TEST_FRAMEWORK_PACKAGE), anyInt());
-        when(mMockContext.getPackageManager()).thenReturn(mSpyPackageManager);
-    }
-
-    private Intent makeVerificationIntent() throws Exception {
-        PackageInfo packageInfo =
-                mRealContext
-                        .getPackageManager()
-                        .getPackageInfo(
-                                TEST_FRAMEWORK_PACKAGE, PackageManager.GET_SIGNING_CERTIFICATES);
-        doReturn(packageInfo).when(mSpyPackageManager).getPackageInfo(eq(INSTALLER), anyInt());
-        doReturn(1).when(mSpyPackageManager).getPackageUid(eq(INSTALLER), anyInt());
-        doReturn(new String[]{INSTALLER}).when(mSpyPackageManager).getPackagesForUid(anyInt());
-        return makeVerificationIntent(INSTALLER);
-    }
-
-    private Intent makeVerificationIntent(String installer) throws Exception {
-        Intent intent = new Intent();
-        intent.setDataAndType(Uri.fromFile(mTestApk), PACKAGE_MIME_TYPE);
-        intent.setAction(Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
-        intent.putExtra(EXTRA_VERIFICATION_ID, 1);
-        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, PACKAGE_NAME);
-        intent.putExtra(EXTRA_VERIFICATION_INSTALLER_PACKAGE, installer);
-        intent.putExtra(
-                EXTRA_VERIFICATION_INSTALLER_UID,
-                mMockContext.getPackageManager().getPackageUid(installer, /* flags= */ 0));
-        intent.putExtra(Intent.EXTRA_LONG_VERSION_CODE, VERSION_CODE);
-        return intent;
-    }
-
-    private void setIntegrityCheckIncludesRuleProvider(boolean shouldInclude) throws Exception {
-        int value = shouldInclude ? 1 : 0;
-        Settings.Global.putInt(
-                mRealContext.getContentResolver(),
-                Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
-                value);
-        assertThat(
-                        Settings.Global.getInt(
-                                        mRealContext.getContentResolver(),
-                                        Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
-                                        -1)
-                                == 1)
-                .isEqualTo(shouldInclude);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index b2a7d20..3ced56a 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -67,10 +67,12 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.ApplicationInfoFlags;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.Rect;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionCallback;
 import android.media.projection.IMediaProjectionWatcherCallback;
 import android.media.projection.ReviewGrantedConsentResult;
+import android.media.projection.StopReason;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Looper;
@@ -549,7 +551,7 @@
         MediaProjectionManagerService.MediaProjection projection =
                 startProjectionPreconditions(service);
 
-        projection.stop();
+        projection.stop(StopReason.STOP_UNKNOWN);
 
         verifyZeroInteractions(mMediaProjectionMetricsLogger);
     }
@@ -562,10 +564,10 @@
                 startProjectionPreconditions(service);
         projection.start(mIMediaProjectionCallback);
 
-        projection.stop();
+        final @StopReason int stopReason = StopReason.STOP_UNKNOWN;
+        projection.stop(stopReason);
 
-        verify(mMediaProjectionMetricsLogger)
-                .logStopped(UID, TARGET_UID_UNKNOWN);
+        verify(mMediaProjectionMetricsLogger).logStopped(UID, TARGET_UID_UNKNOWN, stopReason);
     }
 
     @Test
@@ -580,15 +582,16 @@
                 .setContentRecordingSession(any(ContentRecordingSession.class));
         service.setContentRecordingSession(DISPLAY_SESSION);
 
-        projection.stop();
+        final @StopReason int stopReason = StopReason.STOP_UNKNOWN;
+        projection.stop(stopReason);
 
-        verify(mMediaProjectionMetricsLogger)
-                .logStopped(UID, TARGET_UID_FULL_SCREEN);
+        verify(mMediaProjectionMetricsLogger).logStopped(UID, TARGET_UID_FULL_SCREEN, stopReason);
     }
 
     @Test
     public void stop_taskSession_logsHostUidAndTargetUid() throws Exception {
         int targetUid = 1234;
+        int stopReason = StopReason.STOP_UNKNOWN;
         MediaProjectionManagerService service =
                 new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
         MediaProjectionManagerService.MediaProjection projection =
@@ -601,9 +604,9 @@
         taskSession.setTargetUid(targetUid);
         service.setContentRecordingSession(taskSession);
 
-        projection.stop();
+        projection.stop(stopReason);
 
-        verify(mMediaProjectionMetricsLogger).logStopped(UID, targetUid);
+        verify(mMediaProjectionMetricsLogger).logStopped(UID, targetUid, stopReason);
     }
 
     @Test
@@ -638,7 +641,7 @@
         projection.start(mIMediaProjectionCallback);
         assertThat(projection.isValid()).isTrue();
 
-        projection.stop();
+        projection.stop(StopReason.STOP_UNKNOWN);
 
         // Second start - so not valid.
         projection.start(mIMediaProjectionCallback);
@@ -692,7 +695,7 @@
         MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(
                 service);
         projection.start(mIMediaProjectionCallback);
-        projection.stop();
+        projection.stop(StopReason.STOP_UNKNOWN);
         // Second start - so not valid.
         projection.start(mIMediaProjectionCallback);
 
@@ -708,7 +711,7 @@
         MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(
                 service);
         projection.start(mIMediaProjectionCallback);
-        projection.stop();
+        projection.stop(StopReason.STOP_UNKNOWN);
 
         // Second start - so not valid.
         projection.start(mIMediaProjectionCallback);
@@ -947,6 +950,26 @@
                 projection.uid, targetUid, WINDOWING_MODE_MULTI_WINDOW);
     }
 
+    @Test
+    public void notifyCaptureBoundsChanged_forwardsToLoggerAndResizeCallbacks() throws Exception {
+        int targetUid = 123;
+        mService =
+                new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+
+        ContentRecordingSession taskSession = createTaskSession(mock(IBinder.class));
+        taskSession.setTargetUid(targetUid);
+        mService.setContentRecordingSession(taskSession);
+
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+
+        Rect newBounds = new Rect(0, 0, 1000, 2000);
+        mService.notifyCaptureBoundsChanged(RECORD_CONTENT_TASK, targetUid, newBounds);
+
+        verify(mMediaProjectionMetricsLogger)
+                .logChangedCaptureBounds(RECORD_CONTENT_TASK, projection.uid, targetUid, newBounds);
+    }
+
     /**
      * Executes and validates scenario where the consent result indicates the projection ends.
      */
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
index 72ce9fe..c727bb6 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
@@ -32,6 +32,17 @@
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_DEVICE_LOCK;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_ERROR;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_FOREGROUND_SERVICE_CHANGE;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_HOST_APP_STOP;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_NEW_MEDIA_ROUTE;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_NEW_PROJECTION;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_QS_TILE;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_STATUS_BAR_CHIP_STOP;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_TASK_APP_CLOSE;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_USER_SWITCH;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_APP_TASK;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_DISPLAY;
 import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_TARGET_CHANGED__TARGET_TYPE__TARGET_TYPE_UNKNOWN;
@@ -46,6 +57,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.media.projection.StopReason;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -81,6 +93,7 @@
 
     private static final int TEST_WINDOWING_MODE = 987;
     private static final int TEST_CONTENT_TO_RECORD = 654;
+    private static final int TEST_STOP_SOURCE = 321;
 
     @Mock private FrameworkStatsLogWrapper mFrameworkStatsLogWrapper;
     @Mock private MediaProjectionSessionIdGenerator mSessionIdGenerator;
@@ -136,6 +149,14 @@
     }
 
     @Test
+    public void logInitiated_logsUnknownStopSource() {
+        mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+        verifyStopSourceLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN);
+    }
+
+    @Test
     public void logInitiated_noPreviousSession_logsUnknownTimeSinceLastActive() {
         when(mTimestampStore.timeSinceLastActiveSession()).thenReturn(null);
 
@@ -177,7 +198,7 @@
 
     @Test
     public void logStopped_logsStateChangedAtomId() {
-        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID, TEST_STOP_SOURCE);
 
         verifyStateChangedAtomIdLogged();
     }
@@ -187,42 +208,49 @@
         int currentSessionId = 987;
         when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
 
-        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID, TEST_STOP_SOURCE);
 
         verifySessionIdLogged(currentSessionId);
     }
 
     @Test
     public void logStopped_logsStateStopped() {
-        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID, TEST_STOP_SOURCE);
 
         verifyStateLogged(MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
     }
 
     @Test
     public void logStopped_logsHostUid() {
-        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID, TEST_STOP_SOURCE);
 
         verifyStateChangedHostUidLogged(TEST_HOST_UID);
     }
 
     @Test
     public void logStopped_logsTargetUid() {
-        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID, TEST_STOP_SOURCE);
 
         verifyStageChangedTargetUidLogged(TEST_TARGET_UID);
     }
 
     @Test
+    public void logStopped_logsStopSource() {
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID, StopReason.STOP_UNKNOWN);
+
+        verifyStopSourceLogged(MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN);
+    }
+
+    @Test
     public void logStopped_logsUnknownTimeSinceLastActive() {
-        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID, TEST_STOP_SOURCE);
 
         verifyTimeSinceLastActiveSessionLogged(-1);
     }
 
     @Test
     public void logStopped_logsUnknownSessionCreationSource() {
-        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID, TEST_STOP_SOURCE);
 
         verifyCreationSourceLogged(
                 MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
@@ -230,7 +258,7 @@
 
     @Test
     public void logStopped_logsPreviousState() {
-        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID, TEST_STOP_SOURCE);
         verifyPreviousStateLogged(
                 MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
 
@@ -238,7 +266,7 @@
         verifyPreviousStateLogged(
                 MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
 
-        mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE, TEST_STOP_SOURCE);
         verifyPreviousStateLogged(
                 MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
     }
@@ -247,14 +275,14 @@
     public void logStopped_capturingWasInProgress_registersActiveSessionEnded() {
         mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
 
-        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID, TEST_STOP_SOURCE);
 
         verify(mTimestampStore).registerActiveSessionEnded();
     }
 
     @Test
     public void logStopped_capturingWasNotInProgress_doesNotRegistersActiveSessionEnded() {
-        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+        mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID, TEST_STOP_SOURCE);
 
         verify(mTimestampStore, never()).registerActiveSessionEnded();
     }
@@ -314,6 +342,14 @@
     }
 
     @Test
+    public void logInProgress_logsUnknownSessionStopSource() {
+        mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+        verifyStopSourceLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN);
+    }
+
+    @Test
     public void logInProgress_logsPreviousState() {
         mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
         verifyPreviousStateLogged(
@@ -323,7 +359,7 @@
         verifyPreviousStateLogged(
                 MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
 
-        mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE, TEST_STOP_SOURCE);
         verifyPreviousStateLogged(
                 MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS);
 
@@ -387,6 +423,14 @@
     }
 
     @Test
+    public void logPermissionRequestDisplayed_logsUnknownSessionStopSource() {
+        mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+        verifyStopSourceLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN);
+    }
+
+    @Test
     public void logPermissionRequestDisplayed_logsPreviousState() {
         mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
         verifyPreviousStateLogged(
@@ -396,7 +440,7 @@
         verifyPreviousStateLogged(
                 MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
 
-        mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE, TEST_STOP_SOURCE);
         verifyPreviousStateLogged(
                 MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED);
 
@@ -460,6 +504,14 @@
     }
 
     @Test
+    public void logAppSelectorDisplayed_logsUnknownSessionStopSource() {
+        mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+        verifyStopSourceLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN);
+    }
+
+    @Test
     public void logAppSelectorDisplayed_logsPreviousState() {
         mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
         verifyPreviousStateLogged(
@@ -469,7 +521,7 @@
         verifyPreviousStateLogged(
                 MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
 
-        mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+        mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE, TEST_STOP_SOURCE);
         verifyPreviousStateLogged(
                 MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED);
 
@@ -536,6 +588,14 @@
     }
 
     @Test
+    public void logProjectionPermissionRequestCancelled_logsUnknownStopSource() {
+        mLogger.logProjectionPermissionRequestCancelled(TEST_HOST_UID);
+
+        verifyStopSourceLogged(
+                MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN);
+    }
+
+    @Test
     public void logWindowingModeChanged_logsTargetChangedAtomId() {
         mLogger.logChangedWindowingMode(
                 TEST_CONTENT_TO_RECORD, TEST_HOST_UID, TEST_TARGET_UID, TEST_WINDOWING_MODE);
@@ -614,6 +674,42 @@
                 .isEqualTo(MEDIA_PROJECTION_TARGET_CHANGED__TARGET_WINDOWING_MODE__WINDOWING_MODE_UNKNOWN);
     }
 
+    @Test
+    public void testStopReasonToSessionStopSource() {
+        mExpect.that(mLogger.stopReasonToSessionStopSource(StopReason.STOP_HOST_APP))
+                .isEqualTo(MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_HOST_APP_STOP);
+
+        mExpect.that(mLogger.stopReasonToSessionStopSource(StopReason.STOP_TARGET_REMOVED))
+                .isEqualTo(MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_TASK_APP_CLOSE);
+
+        mExpect.that(mLogger.stopReasonToSessionStopSource(StopReason.STOP_DEVICE_LOCKED))
+                .isEqualTo(MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_DEVICE_LOCK);
+
+        mExpect.that(mLogger.stopReasonToSessionStopSource(StopReason.STOP_PRIVACY_CHIP))
+                .isEqualTo(MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_STATUS_BAR_CHIP_STOP);
+
+        mExpect.that(mLogger.stopReasonToSessionStopSource(StopReason.STOP_QS_TILE))
+                .isEqualTo(MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_QS_TILE);
+
+        mExpect.that(mLogger.stopReasonToSessionStopSource(StopReason.STOP_USER_SWITCH))
+                .isEqualTo(MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_USER_SWITCH);
+
+        mExpect.that(mLogger.stopReasonToSessionStopSource(StopReason.STOP_FOREGROUND_SERVICE_CHANGE))
+                .isEqualTo(MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_FOREGROUND_SERVICE_CHANGE);
+
+        mExpect.that(mLogger.stopReasonToSessionStopSource(StopReason.STOP_NEW_PROJECTION))
+                .isEqualTo(MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_NEW_PROJECTION);
+
+        mExpect.that(mLogger.stopReasonToSessionStopSource(StopReason.STOP_NEW_MEDIA_ROUTE))
+                .isEqualTo(MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_NEW_MEDIA_ROUTE);
+
+        mExpect.that(mLogger.stopReasonToSessionStopSource(StopReason.STOP_ERROR))
+                .isEqualTo(MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_ERROR);
+
+        mExpect.that(mLogger.stopReasonToSessionStopSource(StopReason.STOP_UNKNOWN))
+                .isEqualTo(MEDIA_PROJECTION_STATE_CHANGED__STOP_SOURCE__STOP_SOURCE_UNKNOWN);
+    }
+
     private void verifyStateChangedAtomIdLogged() {
         verify(mFrameworkStatsLogWrapper)
                 .writeStateChanged(
@@ -624,7 +720,8 @@
                         /* hostUid= */ anyInt(),
                         /* targetUid= */ anyInt(),
                         /* timeSinceLastActive= */ anyInt(),
-                        /* creationSource= */ anyInt());
+                        /* creationSource= */ anyInt(),
+                        /* stopSource= */ anyInt());
     }
 
     private void verifyStateLogged(int state) {
@@ -637,7 +734,8 @@
                         /* hostUid= */ anyInt(),
                         /* targetUid= */ anyInt(),
                         /* timeSinceLastActive= */ anyInt(),
-                        /* creationSource= */ anyInt());
+                        /* creationSource= */ anyInt(),
+                        /* stopSource= */ anyInt());
     }
 
     private void verifyStateChangedHostUidLogged(int hostUid) {
@@ -650,7 +748,8 @@
                         eq(hostUid),
                         /* targetUid= */ anyInt(),
                         /* timeSinceLastActive= */ anyInt(),
-                        /* creationSource= */ anyInt());
+                        /* creationSource= */ anyInt(),
+                        /* stopSource= */ anyInt());
     }
 
     private void verifyCreationSourceLogged(int creationSource) {
@@ -663,7 +762,22 @@
                         /* hostUid= */ anyInt(),
                         /* targetUid= */ anyInt(),
                         /* timeSinceLastActive= */ anyInt(),
-                        eq(creationSource));
+                        eq(creationSource),
+                        /* stopSource= */ anyInt());
+    }
+
+    private void verifyStopSourceLogged(int stopSource) {
+        verify(mFrameworkStatsLogWrapper)
+                .writeStateChanged(
+                        /* code= */ anyInt(),
+                        /* sessionId= */ anyInt(),
+                        /* state= */ anyInt(),
+                        /* previousState= */ anyInt(),
+                        /* hostUid= */ anyInt(),
+                        /* targetUid= */ anyInt(),
+                        /* timeSinceLastActive= */ anyInt(),
+                        /* stopSource= */ anyInt(),
+                        eq(stopSource));
     }
 
     private void verifyStageChangedTargetUidLogged(int targetUid) {
@@ -676,7 +790,8 @@
                         /* hostUid= */ anyInt(),
                         eq(targetUid),
                         /* timeSinceLastActive= */ anyInt(),
-                        /* creationSource= */ anyInt());
+                        /* creationSource= */ anyInt(),
+                        /* stopSource= */ anyInt());
     }
 
     private void verifyTimeSinceLastActiveSessionLogged(int timeSinceLastActiveSession) {
@@ -689,7 +804,8 @@
                         /* hostUid= */ anyInt(),
                         /* targetUid= */ anyInt(),
                         /* timeSinceLastActive= */ eq(timeSinceLastActiveSession),
-                        /* creationSource= */ anyInt());
+                        /* creationSource= */ anyInt(),
+                        /* stopSource= */ anyInt());
     }
 
     private void verifySessionIdLogged(int newSessionId) {
@@ -702,7 +818,8 @@
                         /* hostUid= */ anyInt(),
                         /* targetUid= */ anyInt(),
                         /* timeSinceLastActive= */ anyInt(),
-                        /* creationSource= */ anyInt());
+                        /* creationSource= */ anyInt(),
+                        /* stopSource= */ anyInt());
     }
 
     private void verifyPreviousStateLogged(int previousState) {
@@ -715,7 +832,8 @@
                         /* hostUid= */ anyInt(),
                         /* targetUid= */ anyInt(),
                         /* timeSinceLastActive= */ anyInt(),
-                        /* creationSource= */ anyInt());
+                        /* creationSource= */ anyInt(),
+                        /* stopSource= */ anyInt());
     }
 
     private void verifyTargetChangedAtomIdLogged() {
@@ -726,7 +844,12 @@
                         /* targetType= */ anyInt(),
                         /* hostUid= */ anyInt(),
                         /* targetUid= */ anyInt(),
-                        /* targetWindowingMode= */ anyInt());
+                        /* targetWindowingMode= */ anyInt(),
+                        /* width= */ anyInt(),
+                        /* height= */ anyInt(),
+                        /* centerX= */ anyInt(),
+                        /* centerY= */ anyInt(),
+                        /* targetChangeType= */ anyInt());
     }
 
     private void verifyTargetTypeLogged(int targetType) {
@@ -737,7 +860,12 @@
                         eq(targetType),
                         /* hostUid= */ anyInt(),
                         /* targetUid= */ anyInt(),
-                        /* targetWindowingMode= */ anyInt());
+                        /* targetWindowingMode= */ anyInt(),
+                        /* width= */ anyInt(),
+                        /* height= */ anyInt(),
+                        /* centerX= */ anyInt(),
+                        /* centerY= */ anyInt(),
+                        /* targetChangeType= */ anyInt());
     }
 
     private void verifyTargetChangedHostUidLogged(int hostUid) {
@@ -748,7 +876,12 @@
                         /* targetType= */ anyInt(),
                         eq(hostUid),
                         /* targetUid= */ anyInt(),
-                        /* targetWindowingMode= */ anyInt());
+                        /* targetWindowingMode= */ anyInt(),
+                        /* width= */ anyInt(),
+                        /* height= */ anyInt(),
+                        /* centerX= */ anyInt(),
+                        /* centerY= */ anyInt(),
+                        /* targetChangeType= */ anyInt());
     }
 
     private void verifyTargetChangedTargetUidLogged(int targetUid) {
@@ -759,7 +892,12 @@
                         /* targetType= */ anyInt(),
                         /* hostUid= */ anyInt(),
                         eq(targetUid),
-                        /* targetWindowingMode= */ anyInt());
+                        /* targetWindowingMode= */ anyInt(),
+                        /* width= */ anyInt(),
+                        /* height= */ anyInt(),
+                        /* centerX= */ anyInt(),
+                        /* centerY= */ anyInt(),
+                        /* targetChangeType= */ anyInt());
     }
 
     private void verifyWindowingModeLogged(int targetWindowingMode) {
@@ -770,6 +908,11 @@
                         /* targetType= */ anyInt(),
                         /* hostUid= */ anyInt(),
                         /* targetUid= */ anyInt(),
-                        eq(targetWindowingMode));
+                        eq(targetWindowingMode),
+                        /* width= */ anyInt(),
+                        /* height= */ anyInt(),
+                        /* centerX= */ anyInt(),
+                        /* centerY= */ anyInt(),
+                        /* targetChangeType= */ anyInt());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
index fac5c1f..27486b7 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
@@ -40,11 +40,13 @@
 import android.content.IntentFilter;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager.ShareShortcutInfo;
+import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.util.Range;
 
+import com.android.internal.R;
 import com.android.internal.app.ChooserActivity;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.server.people.data.ConversationInfo;
@@ -87,6 +89,7 @@
     private static final IntentFilter INTENT_FILTER = IntentFilter.create("SEND", "text/plain");
 
     @Mock private Context mContext;
+    @Mock private Resources mResources;
     @Mock private DataManager mDataManager;
     @Mock private Consumer<List<AppTarget>> mUpdatePredictionsMethod;
     @Mock private PackageData mPackageData1;
@@ -116,11 +119,14 @@
         when(mDataManager.getShareShortcuts(any(), anyInt())).thenReturn(mShareShortcuts);
         when(mDataManager.getPackage(PACKAGE_1, USER_ID)).thenReturn(mPackageData1);
         when(mDataManager.getPackage(PACKAGE_2, USER_ID)).thenReturn(mPackageData2);
-        when(mContext.createContextAsUser(any(), any())).thenReturn(mContext);
+        when(mContext.createContextAsUser(any(), anyInt())).thenReturn(mContext);
         when(mContext.getSystemServiceName(AppPredictionManager.class)).thenReturn(
                 Context.APP_PREDICTION_SERVICE);
         when(mContext.getSystemService(AppPredictionManager.class))
                 .thenReturn(new AppPredictionManager(mContext));
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getString(R.string.config_chooserActivity))
+                .thenReturn("com.android.intentresolver/.ChooserActivity");
 
         Bundle bundle = new Bundle();
         bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY, INTENT_FILTER);
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
index 8290e1c..a8544f6 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -16,11 +16,20 @@
 
 package com.android.server.supervision
 
+import android.app.Activity
+import android.app.admin.DevicePolicyManager
 import android.app.admin.DevicePolicyManagerInternal
+import android.app.supervision.flags.Flags
+import android.content.BroadcastReceiver
 import android.content.ComponentName
 import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.content.IntentFilter
 import android.content.pm.UserInfo
+import android.os.Handler
 import android.os.PersistableBundle
+import android.os.UserHandle
 import android.platform.test.annotations.RequiresFlagsEnabled
 import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -46,22 +55,20 @@
  */
 @RunWith(AndroidJUnit4::class)
 class SupervisionServiceTest {
-    companion object {
-        const val USER_ID = 100
-    }
-
-    @get:Rule val mocks: MockitoRule = MockitoJUnit.rule()
     @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+    @get:Rule val mocks: MockitoRule = MockitoJUnit.rule()
 
     @Mock private lateinit var mockDpmInternal: DevicePolicyManagerInternal
     @Mock private lateinit var mockUserManagerInternal: UserManagerInternal
 
     private lateinit var context: Context
+    private lateinit var lifecycle: SupervisionService.Lifecycle
     private lateinit var service: SupervisionService
 
     @Before
     fun setUp() {
         context = InstrumentationRegistry.getInstrumentation().context
+        context = SupervisionContextWrapper(context)
 
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal::class.java)
         LocalServices.addService(DevicePolicyManagerInternal::class.java, mockDpmInternal)
@@ -70,43 +77,61 @@
         LocalServices.addService(UserManagerInternal::class.java, mockUserManagerInternal)
 
         service = SupervisionService(context)
+        lifecycle = SupervisionService.Lifecycle(context, service)
+        lifecycle.registerProfileOwnerListener()
     }
 
     @Test
-    @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
-    fun syncStateWithDevicePolicyManager_supervisionAppIsProfileOwner_enablesSupervision() {
-        val supervisionPackageName =
-            context.getResources().getString(R.string.config_systemSupervision)
-
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+    fun onUserStarting_supervisionAppIsProfileOwner_enablesSupervision() {
         whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
-            .thenReturn(ComponentName(supervisionPackageName, "MainActivity"))
+            .thenReturn(ComponentName(systemSupervisionPackage, "MainActivity"))
 
-        service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID))
+        simulateUserStarting(USER_ID)
 
         assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
     }
 
     @Test
-    @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
-    fun syncStateWithDevicePolicyManager_userPreCreated_doesNotEnableSupervision() {
-        val supervisionPackageName =
-            context.getResources().getString(R.string.config_systemSupervision)
-
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+    fun onUserStarting_userPreCreated_doesNotEnableSupervision() {
         whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
-            .thenReturn(ComponentName(supervisionPackageName, "MainActivity"))
+            .thenReturn(ComponentName(systemSupervisionPackage, "MainActivity"))
 
-        service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID, preCreated = true))
+        simulateUserStarting(USER_ID, preCreated = true)
 
         assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
     }
 
     @Test
-    @RequiresFlagsEnabled(android.app.admin.flags.Flags.FLAG_ENABLE_SUPERVISION_SERVICE_SYNC)
-    fun syncStateWithDevicePolicyManager_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+    fun onUserStarting_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
         whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
             .thenReturn(ComponentName("other.package", "MainActivity"))
 
-        service.syncStateWithDevicePolicyManager(newTargetUser(USER_ID))
+        simulateUserStarting(USER_ID)
+
+        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+    fun profileOwnerChanged_supervisionAppIsProfileOwner_enablesSupervision() {
+        whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+            .thenReturn(ComponentName(systemSupervisionPackage, "MainActivity"))
+
+        broadcastProfileOwnerChanged(USER_ID)
+
+        assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+    fun profileOwnerChanged_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
+        whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+            .thenReturn(ComponentName("other.package", "MainActivity"))
+
+        broadcastProfileOwnerChanged(USER_ID)
 
         assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
     }
@@ -150,9 +175,62 @@
         assertThat(userData.supervisionLockScreenOptions).isNull()
     }
 
-    private fun newTargetUser(userId: Int, preCreated: Boolean = false): TargetUser {
+    private val systemSupervisionPackage: String
+        get() = context.getResources().getString(R.string.config_systemSupervision)
+
+    private fun simulateUserStarting(userId: Int, preCreated: Boolean = false) {
         val userInfo = UserInfo(userId, /* name= */ "tempUser", /* flags= */ 0)
         userInfo.preCreated = preCreated
-        return TargetUser(userInfo)
+        lifecycle.onUserStarting(TargetUser(userInfo))
+    }
+
+    private fun broadcastProfileOwnerChanged(userId: Int) {
+        val intent = Intent(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED)
+        context.sendBroadcastAsUser(intent, UserHandle.of(userId))
+    }
+
+    private companion object {
+        const val USER_ID = 100
+    }
+}
+
+/**
+ * A context wrapper that allows broadcast intents to immediately invoke the receivers without
+ * performing checks on the sending user.
+ */
+private class SupervisionContextWrapper(val context: Context) : ContextWrapper(context) {
+    val interceptors = mutableListOf<Pair<BroadcastReceiver, IntentFilter>>()
+
+    override fun registerReceiverForAllUsers(
+        receiver: BroadcastReceiver?,
+        filter: IntentFilter,
+        broadcastPermission: String?,
+        scheduler: Handler?,
+    ): Intent? {
+        if (receiver != null) {
+            interceptors.add(Pair(receiver, filter))
+        }
+        return null
+    }
+
+    override fun sendBroadcastAsUser(intent: Intent, user: UserHandle) {
+        val pendingResult =
+            BroadcastReceiver.PendingResult(
+                Activity.RESULT_OK,
+                /* resultData= */ "",
+                /* resultExtras= */ null,
+                /* type= */ 0,
+                /* ordered= */ true,
+                /* sticky= */ false,
+                /* token= */ null,
+                user.identifier,
+                /* flags= */ 0,
+            )
+        for ((receiver, filter) in interceptors) {
+            if (filter.match(contentResolver, intent, false, "") > 0) {
+                receiver.setPendingResult(pendingResult)
+                receiver.onReceive(context, intent)
+            }
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index 5852af7..d20f73d 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -506,9 +506,9 @@
         assertThat(client0.getProfile().getInUseFrontendHandles())
                 .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle)));
 
-        // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
-        // (client0) to maintain ownership such as requester will not get the resources.
-        client1.getProfile().setResourceHolderRetain(true);
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+        // Holder (client0) to maintain ownership such as requester will not get the resources.
+        client1.getProfile().setResourceOwnershipRetention(true);
 
         request = tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
         assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendHandle))
@@ -520,10 +520,10 @@
                 .isEqualTo(client0.getId());
         assertThat(client0.isReclaimed()).isFalse();
 
-        // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
         // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
         // resources.
-        client1.getProfile().setResourceHolderRetain(false);
+        client1.getProfile().setResourceOwnershipRetention(false);
 
         assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendHandle))
                 .isTrue();
@@ -645,9 +645,9 @@
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId())));
         assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
 
-        // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
-        // to maintain ownership such as requester (client1) will not get the resources.
-        client1.getProfile().setResourceHolderRetain(true);
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+        // Holder to maintain ownership such as requester (client1) will not get the resources.
+        client1.getProfile().setResourceOwnershipRetention(true);
 
         request = casSessionRequest(client1.getId(), 1);
         assertThat(
@@ -663,10 +663,10 @@
         assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
         assertThat(client0.isReclaimed()).isFalse();
 
-        // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
         // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
         // resources.
-        client1.getProfile().setResourceHolderRetain(false);
+        client1.getProfile().setResourceOwnershipRetention(false);
 
         assertThat(
                 mTunerResourceManagerService.requestCasSessionInternal(request, casSessionHandle))
@@ -759,9 +759,9 @@
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId())));
         assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue();
 
-        // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
-        // (client0) to maintain ownership such as requester will not get the resources.
-        client1.getProfile().setResourceHolderRetain(true);
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+        // Holder (client0) to maintain ownership such as requester will not get the resources.
+        client1.getProfile().setResourceOwnershipRetention(true);
 
         request = tunerCiCamRequest(client1.getId(), 1);
         assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle))
@@ -776,10 +776,10 @@
         assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue();
         assertThat(client0.isReclaimed()).isFalse();
 
-        // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
         // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
         // resources.
-        client1.getProfile().setResourceHolderRetain(false);
+        client1.getProfile().setResourceOwnershipRetention(false);
 
         assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle))
                 .isTrue();
@@ -927,9 +927,9 @@
         assertThat(client0.getProfile().getInUseLnbHandles())
                 .isEqualTo(new HashSet<Long>(Arrays.asList(lnbHandles[0])));
 
-        // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
-        // (client0) to maintain ownership such as requester will not get the resources.
-        client1.getProfile().setResourceHolderRetain(true);
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to true to allow the Resource
+        // Holder (client0) to maintain ownership such as requester will not get the resources.
+        client1.getProfile().setResourceOwnershipRetention(true);
 
         request = new TunerLnbRequest();
         request.clientId = client1.getId();
@@ -942,10 +942,10 @@
         assertThat(client0.isReclaimed()).isFalse();
         assertThat(client1.getProfile().getInUseLnbHandles().size()).isEqualTo(0);
 
-        // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+        // setResourceOwnershipRetention sets mResourceOwnerRetention to false to allow the Resource
         // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
         // resources.
-        client1.getProfile().setResourceHolderRetain(false);
+        client1.getProfile().setResourceOwnershipRetention(false);
 
         assertThat(mTunerResourceManagerService.requestLnbInternal(request, lnbHandle)).isTrue();
         assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]);
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index aee9f0f..13a6e4c 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -27,6 +27,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Predicate;
 
 public class TestSystemImpl implements SystemInterface {
     private String mUserProvider = null;
@@ -36,6 +37,7 @@
     Map<String, Map<Integer, PackageInfo>> mPackages = new HashMap();
     private final int mNumRelros;
     private final boolean mIsDebuggable;
+    private Predicate<PackageInfo> mCompatibilityPredicate;
 
     public static final int PRIMARY_USER_ID = 0;
 
@@ -45,6 +47,7 @@
         mNumRelros = numRelros;
         mIsDebuggable = isDebuggable;
         mUsers.add(PRIMARY_USER_ID);
+        mCompatibilityPredicate = pi -> true;
     }
 
     public void addUser(int userId) {
@@ -129,6 +132,15 @@
     }
 
     @Override
+    public boolean isCompatibleImplementationPackage(PackageInfo packageInfo) {
+        return mCompatibilityPredicate.test(packageInfo);
+    }
+
+    public void setCompatibilityPredicate(Predicate<PackageInfo> predicate) {
+        mCompatibilityPredicate = predicate;
+    }
+
+    @Override
     public List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo info) {
         Map<Integer, PackageInfo> userPackages = mPackages.get(info.packageName);
         List<UserPackage> ret = new ArrayList();
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 42eb609..bf99b6a 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -25,10 +25,12 @@
 import android.content.pm.Signature;
 import android.os.Build;
 import android.os.Bundle;
+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.util.Base64;
-import android.webkit.UserPackage;
+import android.webkit.Flags;
 import android.webkit.WebViewFactory;
 import android.webkit.WebViewProviderInfo;
 import android.webkit.WebViewProviderResponse;
@@ -175,8 +177,6 @@
             // no flag means invalid
             p.applicationInfo.metaData.putString(WEBVIEW_LIBRARY_FLAG, "blah");
         }
-        // Default to this package being valid in terms of targetSdkVersion.
-        p.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
         return p;
     }
 
@@ -1238,20 +1238,21 @@
     }
 
     /**
-     * Ensure that packages with a targetSdkVersion targeting the current platform are valid, and
+     * Ensure that packages with a targetSdkVersion targeting the correct platform are valid, and
      * that packages targeting an older version are not valid.
      */
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_USE_B_ENTRY_POINT)
     public void testTargetSdkVersionValidity() {
         PackageInfo newSdkPackage = createPackageInfo("newTargetSdkPackage",
-            true /* enabled */, true /* valid */, true /* installed */);
-        newSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+                true /* enabled */, true /* valid */, true /* installed */);
+        newSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
         PackageInfo currentSdkPackage = createPackageInfo("currentTargetSdkPackage",
-            true /* enabled */, true /* valid */, true /* installed */);
-        currentSdkPackage.applicationInfo.targetSdkVersion = UserPackage.MINIMUM_SUPPORTED_SDK;
+                true /* enabled */, true /* valid */, true /* installed */);
+        currentSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU;
         PackageInfo oldSdkPackage = createPackageInfo("oldTargetSdkPackage",
-            true /* enabled */, true /* valid */, true /* installed */);
-        oldSdkPackage.applicationInfo.targetSdkVersion = UserPackage.MINIMUM_SUPPORTED_SDK - 1;
+                true /* enabled */, true /* valid */, true /* installed */);
+        oldSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.S;
 
         WebViewProviderInfo newSdkProviderInfo =
                 new WebViewProviderInfo(newSdkPackage.packageName, "", true, false, null);
@@ -1264,6 +1265,9 @@
                     newSdkProviderInfo
                 };
         setupWithPackages(packages);
+        // Mock the compatibility predicate, requiring T as targetSdkVersion.
+        mTestSystemImpl.setCompatibilityPredicate(
+                pi -> pi.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU);
         // Start with the setting pointing to the invalid package
         mTestSystemImpl.updateUserSetting(oldSdkPackage.packageName);
 
@@ -1280,6 +1284,54 @@
                 1 /* first preparation phase */);
     }
 
+    /**
+     * Ensure that packages with a versionCode new enough for the current platform are valid, and
+     * that older packages are not valid.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_USE_B_ENTRY_POINT)
+    public void testVersionCodeOSCompatValidity() {
+        PackageInfo newVersionPackage = createPackageInfo("newVersionPackage",
+                true /* enabled */, true /* valid */, true /* installed */);
+        newVersionPackage.setLongVersionCode(200L);
+        PackageInfo currentVersionPackage = createPackageInfo("currentVersionPackage",
+                true /* enabled */, true /* valid */, true /* installed */);
+        currentVersionPackage.setLongVersionCode(100L);
+        PackageInfo oldVersionPackage = createPackageInfo("oldVersionPackage",
+                true /* enabled */, true /* valid */, true /* installed */);
+        oldVersionPackage.setLongVersionCode(50L);
+
+        WebViewProviderInfo newVersionProviderInfo =
+                new WebViewProviderInfo(newVersionPackage.packageName, "", true, false, null);
+        WebViewProviderInfo currentVersionProviderInfo =
+                new WebViewProviderInfo(currentVersionPackage.packageName, "", true, false, null);
+        WebViewProviderInfo[] packages =
+                new WebViewProviderInfo[] {
+                    currentVersionProviderInfo,
+                    new WebViewProviderInfo(oldVersionPackage.packageName, "", true, false, null),
+                    newVersionProviderInfo
+                };
+        setupWithPackages(packages);
+        // Mock the compatibility predicate as requiring 100 as versionCode.
+        mTestSystemImpl.setCompatibilityPredicate(
+                pi -> pi.getLongVersionCode() >= 100L);
+        // Start with the setting pointing to the invalid package
+        mTestSystemImpl.updateUserSetting(oldVersionPackage.packageName);
+
+        mTestSystemImpl.setPackageInfo(newVersionPackage);
+        mTestSystemImpl.setPackageInfo(currentVersionPackage);
+        mTestSystemImpl.setPackageInfo(oldVersionPackage);
+
+        assertArrayEquals(
+                new WebViewProviderInfo[] { currentVersionProviderInfo, newVersionProviderInfo },
+                mWebViewUpdateServiceImpl.getValidWebViewPackages());
+
+        runWebViewBootPreparationOnMainSync();
+
+        checkPreparationPhasesForPackage(currentVersionPackage.packageName,
+                1 /* first preparation phase */);
+    }
+
     @Test
     public void testDefaultWebViewPackageIsTheFirstAvailableByDefault() {
         String nonDefaultPackage = "nonDefaultPackage";
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 3b0cb4a..0404b82 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -1460,9 +1460,12 @@
         verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
     }
 
-    private void testAttentionModeThemeOverlay(boolean modeNight) throws RemoteException {
+    // Test the attention mode overlay with all the possible attention modes and the initial night
+    // mode state. Also tests if the attention mode is turned off when the night mode is toggled by
+    // the user.
+    private void testAttentionModeThemeOverlay(boolean initialNightMode) throws RemoteException {
         //setup
-        if (modeNight) {
+        if (initialNightMode) {
             mService.setNightMode(MODE_NIGHT_YES);
             assertTrue(mUiManagerService.getConfiguration().isNightModeActive());
         } else {
@@ -1472,21 +1475,29 @@
 
         // attention modes with expected night modes
         Map<Integer, Boolean> modes = Map.of(
-                MODE_ATTENTION_THEME_OVERLAY_OFF, modeNight,
+                MODE_ATTENTION_THEME_OVERLAY_OFF, initialNightMode,
                 MODE_ATTENTION_THEME_OVERLAY_DAY, false,
                 MODE_ATTENTION_THEME_OVERLAY_NIGHT, true
         );
 
         // test
-        for (int aMode : modes.keySet()) {
+        for (int attentionMode : modes.keySet()) {
             try {
-                mService.setAttentionModeThemeOverlay(aMode);
+                mService.setAttentionModeThemeOverlay(attentionMode);
 
                 int appliedAMode = mService.getAttentionModeThemeOverlay();
-                boolean nMode = modes.get(aMode);
+                boolean expectedNightMode = modes.get(attentionMode);
 
-                assertEquals(aMode, appliedAMode);
-                assertEquals(isNightModeActivated(), nMode);
+                assertEquals(attentionMode, appliedAMode);
+                assertEquals(expectedNightMode, isNightModeActivated());
+
+                // If attentionMode is active, flip the night mode and assets
+                // the attention mode is disabled
+                if (attentionMode != MODE_ATTENTION_THEME_OVERLAY_OFF) {
+                    mService.setNightModeActivated(!expectedNightMode);
+                    assertEquals(MODE_ATTENTION_THEME_OVERLAY_OFF,
+                            mService.getAttentionModeThemeOverlay());
+                }
             } catch (RemoteException e) {
                 fail("Error communicating with server: " + e.getMessage());
             }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index dd278fc..6cb2429 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -2338,6 +2338,177 @@
     }
 
     @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+    public void testUpdateToUngroupableSection_cleanupUngrouped() {
+        final String pkg = "package";
+        // Post notification w/o group in a valid section
+        NotificationRecord notification = spy(getNotificationRecord(pkg, 0, "", mUser,
+                "", false, IMPORTANCE_LOW));
+        Notification n = mock(Notification.class);
+        StatusBarNotification sbn = spy(getSbn(pkg, 0, "0", UserHandle.SYSTEM));
+        when(notification.getNotification()).thenReturn(n);
+        when(notification.getSbn()).thenReturn(sbn);
+        when(sbn.getNotification()).thenReturn(n);
+        when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+        assertThat(GroupHelper.getSection(notification)).isNotNull();
+        mGroupHelper.onNotificationPosted(notification, false);
+
+        // Update notification to invalid section
+        when(n.isStyle(Notification.CallStyle.class)).thenReturn(true);
+        assertThat(GroupHelper.getSection(notification)).isNull();
+        boolean needsAutogrouping = mGroupHelper.onNotificationPosted(notification, false);
+        assertThat(needsAutogrouping).isFalse();
+
+        // Check that GH internal state (ungrouped list) was cleaned-up
+        // Post AUTOGROUP_AT_COUNT-1 notifications => should not autogroup
+        for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+            int id = 42 + i;
+            notification = getNotificationRecord(pkg, id, "" + id, mUser,
+                null, false, IMPORTANCE_LOW);
+            mGroupHelper.onNotificationPosted(notification, false);
+        }
+
+        verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+                anyString(), anyInt(), any());
+        verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+    }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS,
+            android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+    public void testUpdateToUngroupableSection_afterAutogroup_isUngrouped() {
+        final String pkg = "package";
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        // Post notification w/o group in a valid section
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            NotificationRecord notification = spy(getNotificationRecord(pkg, i, "" + i, mUser,
+                    "", false, IMPORTANCE_LOW));
+            Notification n = mock(Notification.class);
+            StatusBarNotification sbn = spy(getSbn(pkg, i, "" + i, UserHandle.SYSTEM));
+            when(notification.getNotification()).thenReturn(n);
+            when(notification.getSbn()).thenReturn(sbn);
+            when(sbn.getNotification()).thenReturn(n);
+            when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+            assertThat(GroupHelper.getSection(notification)).isNotNull();
+            mGroupHelper.onNotificationPosted(notification, false);
+            notificationList.add(notification);
+        }
+
+        final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+                AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(expectedGroupKey), anyInt(), any());
+        verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(),
+                eq(expectedGroupKey), eq(true));
+
+        // Update a notification to invalid section
+        Mockito.reset(mCallback);
+        final NotificationRecord notifToInvalidate = notificationList.get(0);
+        when(notifToInvalidate.getNotification().isStyle(Notification.CallStyle.class)).thenReturn(
+                true);
+        assertThat(GroupHelper.getSection(notifToInvalidate)).isNull();
+        boolean needsAutogrouping = mGroupHelper.onNotificationPosted(notifToInvalidate, true);
+        assertThat(needsAutogrouping).isFalse();
+
+        // Check that the updated notification was removed from the autogroup
+        verify(mCallback, times(1)).removeAutoGroup(eq(notifToInvalidate.getKey()));
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(),
+                eq(expectedGroupKey), any());
+    }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS,
+            android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+    public void testUpdateToUngroupableSection_onRemoved_isUngrouped() {
+        final String pkg = "package";
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        // Post notification w/o group in a valid section
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            NotificationRecord notification = spy(getNotificationRecord(pkg, i, "" + i, mUser,
+                    "", false, IMPORTANCE_LOW));
+            Notification n = mock(Notification.class);
+            StatusBarNotification sbn = spy(getSbn(pkg, i, "" + i, UserHandle.SYSTEM));
+            when(notification.getNotification()).thenReturn(n);
+            when(notification.getSbn()).thenReturn(sbn);
+            when(sbn.getNotification()).thenReturn(n);
+            when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+            assertThat(GroupHelper.getSection(notification)).isNotNull();
+            mGroupHelper.onNotificationPosted(notification, false);
+            notificationList.add(notification);
+        }
+
+        final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+                AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(expectedGroupKey), anyInt(), any());
+        verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(),
+                eq(expectedGroupKey), eq(true));
+
+        // Update a notification to invalid section and removed it
+        Mockito.reset(mCallback);
+        final NotificationRecord notifToInvalidate = notificationList.get(0);
+        when(notifToInvalidate.getNotification().isStyle(Notification.CallStyle.class)).thenReturn(
+                true);
+        assertThat(GroupHelper.getSection(notifToInvalidate)).isNull();
+        notificationList.remove(notifToInvalidate);
+        mGroupHelper.onNotificationRemoved(notifToInvalidate, notificationList);
+
+        // Check that the autogroup was updated
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(),
+                eq(expectedGroupKey), any());
+    }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
+    public void testUpdateToUngroupableSection_afterForceGrouping_isUngrouped() {
+        final String pkg = "package";
+        final String groupName = "testGroup";
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+        // Post valid section summary notifications without children => force group
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            NotificationRecord notification = spy(getNotificationRecord(mPkg, i, "" + i, mUser,
+                    groupName, true, IMPORTANCE_LOW));
+            Notification n = mock(Notification.class);
+            StatusBarNotification sbn = spy(getSbn(pkg, i, "" + i, UserHandle.SYSTEM, groupName));
+            when(notification.getNotification()).thenReturn(n);
+            when(notification.getSbn()).thenReturn(sbn);
+            when(n.getGroup()).thenReturn(groupName);
+            when(sbn.getNotification()).thenReturn(n);
+            when(n.isStyle(Notification.CallStyle.class)).thenReturn(false);
+            assertThat(GroupHelper.getSection(notification)).isNotNull();
+            notificationList.add(notification);
+            mGroupHelper.onNotificationPostedWithDelay(notification, notificationList,
+                    summaryByGroup);
+        }
+
+        final String expectedGroupKey = GroupHelper.getFullAggregateGroupKey(pkg,
+                AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(expectedGroupKey), anyInt(), any());
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+                eq(expectedGroupKey), eq(true));
+
+        // Update a notification to invalid section
+        Mockito.reset(mCallback);
+        final NotificationRecord notifToInvalidate = notificationList.get(0);
+        when(notifToInvalidate.getNotification().isStyle(Notification.CallStyle.class)).thenReturn(
+                true);
+        assertThat(GroupHelper.getSection(notifToInvalidate)).isNull();
+        boolean needsAutogrouping = mGroupHelper.onNotificationPosted(notifToInvalidate, true);
+        assertThat(needsAutogrouping).isFalse();
+
+        // Check that GH internal state (ungrouped list) was cleaned-up
+        verify(mCallback, times(1)).removeAutoGroup(eq(notifToInvalidate.getKey()));
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(),
+                eq(expectedGroupKey), any());
+    }
+
+    @Test
     @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
     public void testMoveAggregateGroups_updateChannel() {
         final String pkg = "package";
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 863f42f..90bf1d3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6884,10 +6884,16 @@
     public void testReadPolicyXml_backupRestoreLogging() throws Exception {
         BackupRestoreEventLogger logger = mock(BackupRestoreEventLogger.class);
 
+        if (ActivityManager.getCurrentUser() != UserHandle.USER_SYSTEM) {
+            // By default, the ZenModeHelper only has a configuration for the system user.
+            // If the current user is not the system user, the user must be updated.
+            mService.mZenModeHelper.onUserSwitched(ActivityManager.getCurrentUser());
+        }
         UserInfo ui = new UserInfo(ActivityManager.getCurrentUser(), "Clone", UserInfo.FLAG_FULL);
         ui.userType = USER_TYPE_FULL_SYSTEM;
         when(mUmInternal.getUserInfo(ActivityManager.getCurrentUser())).thenReturn(ui);
-        when(mPermissionHelper.getNotificationPermissionValues(0)).thenReturn(new ArrayMap<>());
+        when(mPermissionHelper.getNotificationPermissionValues(ActivityManager.getCurrentUser()))
+                .thenReturn(new ArrayMap<>());
         TypedXmlSerializer serializer = Xml.newFastSerializer();
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
@@ -8931,8 +8937,8 @@
 
     @Test
     public void testAreBubblesEnabled_false() throws Exception {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.NOTIFICATION_BUBBLES, 0);
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.NOTIFICATION_BUBBLES, 0, UserHandle.getUserId(mUid));
         mService.mPreferencesHelper.updateBubblesEnabled();
         assertFalse(mBinderService.areBubblesEnabled(UserHandle.getUserHandleForUid(mUid)));
     }
@@ -13310,6 +13316,7 @@
         when(mPermissionHelper.hasPermission(UID_N_MR1)).thenReturn(false);
         when(mPermissionHelper.hasPermission(UID_P)).thenReturn(true);
 
+        enableInteractAcrossUsers();
         assertThat(mBinderService.getPackagesBypassingDnd(UserHandle.getUserId(UID_P)).getList())
                 .containsExactly(new ZenBypassingApp(PKG_P, false));
     }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index ec83e99..194d48a 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -272,7 +272,8 @@
             for (VendorVibrationSession session : mPendingSessions) {
                 session.cancelSession();
             }
-            mTestLooper.dispatchAll();
+            // Dispatch and wait for all callbacks in test looper to be processed.
+            stopAutoDispatcherAndDispatchAll();
             // Wait until pending vibrations end asynchronously.
             for (HalVibration vibration : mPendingVibrations) {
                 vibration.waitForEnd();
@@ -292,8 +293,6 @@
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.removeServiceForTest(PowerManagerInternal.class);
         LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
-        // Ignore potential exceptions about the looper having never dispatched any messages.
-        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
         if (mInputManagerGlobalSession != null) {
             mInputManagerGlobalSession.close();
         }
@@ -1240,24 +1239,27 @@
 
     @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
     @Test
-    public void vibrate_withOngoingHigherImportanceSession_ignoresEffect() throws Exception {
+    public void vibrate_withOngoingHigherImportanceVendorSession_ignoresEffect() throws Exception {
         mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
         mockVibrators(1);
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
         fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         VibratorManagerService service = createSystemReadyService();
-        IVibrationSessionCallback callback =
-                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+        IVibrationSessionCallback callback = mockSessionCallbacks();
 
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+        // Keep auto-dispatcher that can be used in the session cancellation when vibration starts.
         mTestLooper.dispatchAll();
+
         assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
         verify(callback).onStarted(any(IVibrationSession.class));
 
         HalVibration vibration = vibrateAndWaitUntilFinished(service,
                 VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
                 HAPTIC_FEEDBACK_ATTRS);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
         assertThat(vibration.getStatus()).isEqualTo(Status.IGNORED_FOR_HIGHER_IMPORTANCE);
@@ -1330,24 +1332,28 @@
 
     @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
     @Test
-    public void vibrate_withOngoingLowerImportanceSession_cancelsOngoingSession() throws Exception {
+    public void vibrate_withOngoingLowerImportanceVendorSession_cancelsOngoingSession()
+            throws Exception {
         mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
         mockVibrators(1);
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
         fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         VibratorManagerService service = createSystemReadyService();
-        IVibrationSessionCallback callback =
-                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+        IVibrationSessionCallback callback = mockSessionCallbacks();
 
         VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+        // Keep auto-dispatcher that can be used in the session cancellation when vibration starts.
         mTestLooper.dispatchAll();
+
         assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
         verify(callback).onStarted(any(IVibrationSession.class));
 
         HalVibration vibration = vibrateAndWaitUntilFinished(service,
                 VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
                 HAPTIC_FEEDBACK_ATTRS);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
         assertThat(vibration.getStatus()).isEqualTo(Status.FINISHED);
@@ -2393,16 +2399,16 @@
 
     @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
     @Test
-    public void onExternalVibration_withOngoingHigherImportanceSession_ignoreNewVibration()
+    public void onExternalVibration_withOngoingHigherImportanceVendorSession_ignoreNewVibration()
             throws Exception {
         mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
         VibratorManagerService service = createSystemReadyService();
-        IVibrationSessionCallback callback =
-                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+        IVibrationSessionCallback callback = mockSessionCallbacks();
 
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+        // Keep auto-dispatcher that can be used in the session cancellation when vibration starts.
         mTestLooper.dispatchAll();
         verify(callback).onStarted(any(IVibrationSession.class));
 
@@ -2413,6 +2419,9 @@
         // External vibration is ignored.
         assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
 
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
+
         // Session still running.
         assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
         verify(callback, never()).onFinishing();
@@ -2476,16 +2485,16 @@
 
     @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
     @Test
-    public void onExternalVibration_withOngoingLowerImportanceSession_cancelsOngoingSession()
+    public void onExternalVibration_withOngoingLowerImportanceVendorSession_cancelsOngoingSession()
             throws Exception {
         mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
         VibratorManagerService service = createSystemReadyService();
-        IVibrationSessionCallback callback =
-                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+        IVibrationSessionCallback callback = mockSessionCallbacks();
 
         VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+        // Keep auto-dispatcher that can be used in the session cancellation when vibration starts.
         mTestLooper.dispatchAll();
         verify(callback).onStarted(any(IVibrationSession.class));
 
@@ -2494,7 +2503,9 @@
         ExternalVibrationScale scale =
                 mExternalVibratorService.onExternalVibrationStart(externalVibration);
         assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         // Session is cancelled.
         assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
@@ -2780,9 +2791,17 @@
         assertThrows("Expected starting session without feature flag to fail!",
                 UnsupportedOperationException.class,
                 () -> startSession(service, RINGTONE_ATTRS, callback, vibratorId));
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionStarted(anyInt());
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionInterrupted(anyInt());
         verify(callback, never()).onStarted(any(IVibrationSession.class));
         verify(callback, never()).onFinishing();
         verify(callback, never()).onFinished(anyInt());
@@ -2798,10 +2817,18 @@
         IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS,
                 callback, vibratorId);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
         verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionStarted(anyInt());
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionInterrupted(anyInt());
         verify(callback, never()).onFinishing();
         verify(callback)
                 .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
@@ -2817,10 +2844,18 @@
 
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS,
                 /* callback= */ null, vibratorId);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         assertThat(session).isNull();
         verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionStarted(anyInt());
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionInterrupted(anyInt());
     }
 
     @Test
@@ -2837,11 +2872,18 @@
 
         int[] emptyIds = {};
         session = startSession(service, RINGTONE_ATTRS, callback, emptyIds);
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
+
         assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
-
-        mTestLooper.dispatchAll();
-
         verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionStarted(anyInt());
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionInterrupted(anyInt());
         verify(callback, never()).onFinishing();
         verify(callback, times(2))
                 .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
@@ -2862,10 +2904,18 @@
         doReturn(token).when(callback).asBinder();
 
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 3);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
         verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionStarted(anyInt());
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionInterrupted(anyInt());
         verify(callback, never()).onStarted(any(IVibrationSession.class));
         verify(callback, never()).onFinishing();
         verify(callback)
@@ -2882,7 +2932,9 @@
         IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
 
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
         ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -2901,6 +2953,12 @@
         assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
         verify(callback).onFinishing();
         verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+        verify(mVibratorFrameworkStatsLoggerMock)
+                .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionInterrupted(anyInt());
     }
 
     @Test
@@ -2913,7 +2971,9 @@
         IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
 
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
         ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -2925,6 +2985,12 @@
         assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER);
         verify(callback).onFinishing();
         verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+        verify(mVibratorFrameworkStatsLoggerMock)
+                .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+        verify(mVibratorFrameworkStatsLoggerMock, never())
+                .logVibrationVendorSessionInterrupted(anyInt());
     }
 
     @Test
@@ -2933,12 +2999,12 @@
         mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
         mockVibrators(1, 2);
         VibratorManagerService service = createSystemReadyService();
-        // Delay not applied when session is aborted.
-        IVibrationSessionCallback callback =
-                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+        IVibrationSessionCallback callback = mockSessionCallbacks();
 
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
         ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -2959,12 +3025,12 @@
         mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
         mockVibrators(1, 2);
         VibratorManagerService service = createSystemReadyService();
-        // Delay not applied when session is aborted.
-        IVibrationSessionCallback callback =
-                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+        IVibrationSessionCallback callback = mockSessionCallbacks();
 
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
         ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -2988,6 +3054,7 @@
             throws Exception {
         mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
         mockVibrators(1, 2);
+
         VibratorManagerService service = createSystemReadyService();
         ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor =
                 ArgumentCaptor.forClass(
@@ -2999,7 +3066,9 @@
         IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
         doReturn(token).when(callback).asBinder();
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
         verify(callback).onStarted(any(IVibrationSession.class));
@@ -3011,6 +3080,11 @@
         assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_UNKNOWN_REASON);
         verify(callback).onFinishing();
         verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+        verify(mVibratorFrameworkStatsLoggerMock)
+                .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionInterrupted(anyInt());
     }
 
     @Test
@@ -3019,13 +3093,14 @@
         mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
         mockVibrators(1);
         VibratorManagerService service = createSystemReadyService();
-        IVibrationSessionCallback callback =
-                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+        IVibrationSessionCallback callback = mockSessionCallbacks();
 
         mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
         VendorVibrationSession session1 = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
         VendorVibrationSession session2 = startSession(service, RINGTONE_ATTRS, callback, 1);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
         verify(callback).onStarted(captor.capture());
@@ -3051,8 +3126,7 @@
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
         fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         VibratorManagerService service = createSystemReadyService();
-        IVibrationSessionCallback callback =
-                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+        IVibrationSessionCallback callback = mockSessionCallbacks();
 
         VibrationEffect effect = VibrationEffect.createWaveform(
                 new long[]{10, 10_000}, new int[]{128, 255}, -1);
@@ -3064,7 +3138,9 @@
                 service, TEST_TIMEOUT_MILLIS));
 
         VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         verify(mNativeWrapperMock, never())
                 .startSession(eq(session.getSessionId()), any(int[].class));
@@ -3083,8 +3159,7 @@
         fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         VibratorManagerService service = createSystemReadyService();
-        IVibrationSessionCallback callback =
-                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+        IVibrationSessionCallback callback = mockSessionCallbacks();
 
         VibrationEffect effect = VibrationEffect.createWaveform(
                 new long[]{10, 10_000}, new int[]{128, 255}, -1);
@@ -3098,7 +3173,9 @@
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
         vibration.waitForEnd();
         assertTrue(waitUntil(s -> session.isStarted(), service, TEST_TIMEOUT_MILLIS));
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         assertThat(vibration.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
         assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
@@ -3116,8 +3193,7 @@
         mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         setRingerMode(AudioManager.RINGER_MODE_NORMAL);
         VibratorManagerService service = createSystemReadyService();
-        IVibrationSessionCallback callback =
-                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+        IVibrationSessionCallback callback = mockSessionCallbacks();
 
         IBinder firstToken = mock(IBinder.class);
         IExternalVibrationController controller = mock(IExternalVibrationController.class);
@@ -3128,7 +3204,9 @@
                 mExternalVibratorService.onExternalVibrationStart(externalVibration);
 
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
         // The external vibration should have been cancelled
@@ -3149,9 +3227,10 @@
         VibratorManagerService service = createSystemReadyService();
         int sessionFinishDelayMs = 200;
         IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
-
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
         ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -3186,9 +3265,10 @@
         VibratorManagerService service = createSystemReadyService();
         int sessionFinishDelayMs = 200;
         IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
-
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
         ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -3228,9 +3308,10 @@
         VibratorManagerService service = createSystemReadyService();
         int sessionFinishDelayMs = 200;
         IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
-
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
         ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -3272,9 +3353,10 @@
         VibratorManagerService service = createSystemReadyService();
         int sessionFinishDelayMs = 200;
         IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
-
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
         ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -3305,6 +3387,10 @@
         assertThat(service.isVibrating(2)).isFalse();
         verify(callback).onFinishing();
         verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+        verify(mVibratorFrameworkStatsLoggerMock)
+                .logVibrationVendorSessionVibrations(eq(UID), eq(1));
     }
 
     @Test
@@ -3317,9 +3403,10 @@
         VibratorManagerService service = createSystemReadyService();
         int sessionFinishDelayMs = 200;
         IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
-
         VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
-        mTestLooper.dispatchAll();
+
+        // Make sure all messages are processed before asserting on the session callbacks.
+        stopAutoDispatcherAndDispatchAll();
 
         verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
         ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
@@ -3355,6 +3442,10 @@
         assertThat(service.isVibrating(2)).isFalse();
         verify(callback).onFinishing();
         verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+        verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+        verify(mVibratorFrameworkStatsLoggerMock)
+                .logVibrationVendorSessionVibrations(eq(UID), eq(2));
     }
 
     @Test
@@ -3773,6 +3864,10 @@
         when(mNativeWrapperMock.getVibratorIds()).thenReturn(vibratorIds);
     }
 
+    private IVibrationSessionCallback mockSessionCallbacks() {
+        return mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+    }
+
     private IVibrationSessionCallback mockSessionCallbacks(long delayToEndSessionMillis) {
         Handler handler = new Handler(mTestLooper.getLooper());
         ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor =
@@ -3925,6 +4020,13 @@
         return predicateResult;
     }
 
+    private void stopAutoDispatcherAndDispatchAll() {
+        // Stop auto-dispatcher thread and wait for it to finish processing any messages.
+        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
+        // Dispatch any pending message left.
+        mTestLooper.dispatchAll();
+    }
+
     private void grantPermission(String permission) {
         when(mContextSpy.checkCallingOrSelfPermission(permission))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
index 6dba967..a9a1f93 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
@@ -76,7 +76,7 @@
 
         var apiconfig = new SoundTrigger.RecognitionConfig.Builder()
             .setCaptureRequested(true)
-            .setAllowMultipleTriggers(false) // must be false
+            .setMultipleTriggersAllowed(false) // must be false
             .setKeyphrases(keyphrases)
             .setData(data)
             .setAudioCapabilities(flags)
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index bc03c23..9db76d4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -523,12 +523,12 @@
     }
 
     void prepareBrightnessDecrease(float currentBrightness) {
-        doReturn(0.0f).when(mPowerManager)
-                .getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
-        doReturn(1.0f).when(mPowerManager)
-                .getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
+        doReturn(0.0f).when(mPowerManager).getBrightnessConstraint(
+                DEFAULT_DISPLAY, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+        doReturn(1.0f).when(mPowerManager).getBrightnessConstraint(
+                DEFAULT_DISPLAY, PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
         doReturn(currentBrightness).when(mDisplayManager)
-                .getBrightness(0);
+                .getBrightness(DEFAULT_DISPLAY);
     }
 
     void verifyNewBrightness(float newBrightness) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index fee646d..d4a921c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2670,7 +2670,7 @@
         assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_GAME, true);
 
         // Blanket application, also ignoring Target SDK
-        mWm.mConstants.mIgnoreActivityOrientationRequest = true;
+        mWm.mConstants.mIgnoreActivityOrientationRequestLargeScreen = true;
         assertSetOrientation(Build.VERSION_CODES.VANILLA_ICE_CREAM, CATEGORY_SOCIAL, false);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index d5ed048..1d138e4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -249,6 +249,18 @@
     }
 
     @Test
+    @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    public void testShouldApplyCameraCompatFreeformTreatment_enabledByShellCommand_returnsTrue() {
+        runTestScenario((robot) -> {
+            robot.activity().createActivityWithComponentInNewTask();
+
+            robot.setCameraCompatTreatmentEnabledViaShellCommand(true);
+
+            robot.checkShouldApplyFreeformTreatmentForCameraCompat(true);
+        });
+    }
+
+    @Test
     @EnableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA,
             OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA,
             OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
@@ -350,6 +362,11 @@
             spyOn(displayContent.mAppCompatCameraPolicy);
         }
 
+        void setCameraCompatTreatmentEnabledViaShellCommand(boolean enabled) {
+            activity().top().mWmService.mAppCompatConfiguration
+                    .setIsCameraCompatFreeformWindowingTreatmentEnabled(enabled);
+        }
+
         void checkShouldRefreshActivityForCameraCompat(boolean expected) {
             Assert.assertEquals(getAppCompatCameraOverrides()
                     .shouldRefreshActivityForCameraCompat(), expected);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
index 50c2c2f..a5b2cb3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
@@ -29,6 +29,7 @@
 
 import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
 import android.app.TaskInfo;
+import android.graphics.Rect;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.view.DisplayInfo;
@@ -198,6 +199,34 @@
         });
     }
 
+    @Test
+    public void testTopActivityLetterboxed_hasBounds() {
+        runTestScenario((robot) -> {
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+                a.setIgnoreOrientationRequest(true);
+                a.configureTopActivityBounds(new Rect(20, 30, 520, 630));
+            });
+            robot.setIsLetterboxedForAspectRatioOnly(/* forAspectRatio */ true);
+
+
+            robot.checkTaskInfoTopActivityHasBounds(/* expected */ new Rect(20, 30, 520, 630));
+        });
+    }
+
+    @Test
+    public void testTopActivityNotLetterboxed_hasNoBounds() {
+        runTestScenario((robot) -> {
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(true);
+            });
+
+            robot.checkTaskInfoTopActivityHasBounds(/* expected */ null);
+        });
+    }
+
     /**
      * Runs a test scenario providing a Robot.
      */
@@ -282,6 +311,11 @@
                     .cameraCompatTaskInfo.freeformCameraCompatMode);
         }
 
+        void checkTaskInfoTopActivityHasBounds(Rect bounds) {
+            Assert.assertEquals(bounds, getTopTaskInfo().appCompatTaskInfo
+                    .topActivityLetterboxBounds);
+        }
+
         void setCameraCompatTreatmentEnabledForActivity(boolean enabled) {
             doReturn(enabled).when(activity().displayContent().mAppCompatCameraPolicy
                     .mCameraCompatFreeformPolicy).isTreatmentEnabledForActivity(
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index c427583..2ceff62 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -62,6 +62,7 @@
 import android.graphics.Rect;
 import android.hardware.camera2.CameraManager;
 import android.os.Handler;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.view.DisplayInfo;
@@ -143,6 +144,58 @@
     }
 
     @Test
+    @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+    public void testIsCameraRunningAndWindowingModeEligible_featureDisabled_returnsFalse() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    public void testIsCameraRunningAndWindowingModeEligible_overrideDisabled_returnsFalse() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+    public void testIsCameraRunningAndWindowingModeEligible_cameraNotRunning_returnsFalse() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+    public void testIsCameraRunningAndWindowingModeEligible_notFreeformWindowing_returnsFalse() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+    @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+    public void testIsCameraRunningAndWindowingModeEligible_optInFreeformCameraRunning_true() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertTrue(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+    }
+
+    @Test
     @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
     public void testFullscreen_doesNotActivateCameraCompatMode() {
@@ -444,7 +497,8 @@
         doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
         doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
 
-        doReturn(true).when(mActivity).inFreeformWindowingMode();
+        doReturn(windowingMode == WINDOWING_MODE_FREEFORM).when(mActivity)
+                .inFreeformWindowingMode();
     }
 
     private void assertInCameraCompatMode(@CameraCompatTaskInfo.FreeformCameraCompatMode int mode) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
index ad80f82..4810c7f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraStateMonitorTests.java
@@ -22,6 +22,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -137,6 +139,14 @@
     }
 
     @Test
+    public void testOnCameraOpened_listenerAdded_cameraRegistersAsOpenedDuringTheCallback() {
+        mCameraStateMonitor.addCameraStateListener(mListener);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertTrue(mListener.mIsCameraOpened);
+    }
+
+    @Test
     public void testOnCameraOpened_cameraClosed_notifyCameraClosed() {
         mCameraStateMonitor.addCameraStateListener(mListener);
         // Listener returns true on `onCameraOpened`.
@@ -144,10 +154,21 @@
 
         mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
 
+        assertEquals(1, mListener.mCheckCanCloseCounter);
         assertEquals(1, mListener.mOnCameraClosedCounter);
     }
 
     @Test
+    public void testOnCameraOpenedAndClosed_cameraRegistersAsClosedDuringTheCallback() {
+        mCameraStateMonitor.addCameraStateListener(mListener);
+        // Listener returns true on `onCameraOpened`.
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+        assertFalse(mListener.mIsCameraOpened);
+    }
+
+    @Test
     public void testOnCameraOpened_listenerCannotCloseYet_notifyCameraClosedAgain() {
         mCameraStateMonitor.addCameraStateListener(mListenerCannotClose);
         // Listener returns true on `onCameraOpened`.
@@ -155,7 +176,8 @@
 
         mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
 
-        assertEquals(2, mListenerCannotClose.mOnCameraClosedCounter);
+        assertEquals(2, mListenerCannotClose.mCheckCanCloseCounter);
+        assertEquals(1, mListenerCannotClose.mOnCameraClosedCounter);
     }
 
     @Test
@@ -197,39 +219,49 @@
             CameraStateMonitor.CameraCompatStateListener {
 
         int mOnCameraOpenedCounter = 0;
+        int mCheckCanCloseCounter = 0;
         int mOnCameraClosedCounter = 0;
 
-        private boolean mOnCameraClosedReturnValue = true;
+        boolean mIsCameraOpened;
+
+        private boolean mCheckCanCloseReturnValue = true;
 
         /**
-         * @param simulateUnsuccessfulCloseOnce When false, returns `true` on every
-         *                                      `onCameraClosed`. When true, returns `false` on the
-         *                                      first `onCameraClosed` callback, and `true on the
+         * @param simulateCannotCloseOnce When false, returns `true` on every
+         *                                      `checkCanClose`. When true, returns `false` on the
+         *                                      first `checkCanClose` callback, and `true on the
          *                                      subsequent calls. This fake implementation tests the
          *                                      retry mechanism in {@link CameraStateMonitor}.
          */
-        FakeCameraCompatStateListener(boolean simulateUnsuccessfulCloseOnce) {
-            mOnCameraClosedReturnValue = !simulateUnsuccessfulCloseOnce;
+        FakeCameraCompatStateListener(boolean simulateCannotCloseOnce) {
+            mCheckCanCloseReturnValue = !simulateCannotCloseOnce;
         }
 
         @Override
-        public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
-                @NonNull String cameraId) {
+        public void onCameraOpened(@NonNull ActivityRecord cameraActivity) {
             mOnCameraOpenedCounter++;
+            mIsCameraOpened = mCameraStateMonitor.isCameraRunningForActivity(cameraActivity);
         }
 
         @Override
-        public boolean onCameraClosed(@NonNull String cameraId) {
-            mOnCameraClosedCounter++;
-            boolean returnValue = mOnCameraClosedReturnValue;
+        public boolean canCameraBeClosed(@NonNull String cameraId) {
+            mCheckCanCloseCounter++;
+            final boolean returnValue = mCheckCanCloseReturnValue;
             // If false, return false only the first time, so it doesn't fall in the infinite retry
             // loop.
-            mOnCameraClosedReturnValue = true;
+            mCheckCanCloseReturnValue = true;
             return returnValue;
         }
 
+        @Override
+        public void onCameraClosed() {
+            mOnCameraClosedCounter++;
+            mIsCameraOpened = mCameraStateMonitor.isCameraRunningForActivity(mActivity);
+        }
+
         void resetCounters() {
             mOnCameraOpenedCounter = 0;
+            mCheckCanCloseCounter = 0;
             mOnCameraClosedCounter = 0;
         }
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index c51261f..76b994d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -49,6 +49,7 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.media.projection.StopReason;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
 import android.view.ContentRecordingSession;
@@ -213,7 +214,7 @@
         mContentRecorder.setContentRecordingSession(session);
         mContentRecorder.updateRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
-        verify(mMediaProjectionManagerWrapper).stopActiveProjection();
+        verify(mMediaProjectionManagerWrapper).stopActiveProjection(StopReason.STOP_ERROR);
     }
 
     @Test
@@ -225,7 +226,7 @@
         mContentRecorder.setContentRecordingSession(invalidTaskSession);
         mContentRecorder.updateRecording();
         assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
-        verify(mMediaProjectionManagerWrapper).stopActiveProjection();
+        verify(mMediaProjectionManagerWrapper).stopActiveProjection(StopReason.STOP_ERROR);
     }
 
     @Test
@@ -310,8 +311,7 @@
                 mVirtualDisplayContent.getConfiguration().orientation, WINDOWING_MODE_FULLSCREEN);
 
         // No resize is issued, only the initial transformations when we started recording.
-        verify(mTransaction).setPosition(eq(mRecordedSurface), anyFloat(),
-                anyFloat());
+        verify(mTransaction).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat());
         verify(mTransaction).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
                 anyFloat(), anyFloat());
     }
@@ -386,19 +386,18 @@
 
         // WHEN a configuration change arrives, and the recorded content is a different size.
         Configuration configuration = mTask.getConfiguration();
-        configuration.windowConfiguration.setBounds(new Rect(0, 0, recordedWidth, recordedHeight));
-        configuration.windowConfiguration.setAppBounds(
-                new Rect(0, 0, recordedWidth, recordedHeight));
+        Rect newBounds = new Rect(0, 0, recordedWidth, recordedHeight);
+        configuration.windowConfiguration.setBounds(newBounds);
+        configuration.windowConfiguration.setAppBounds(newBounds);
         mTask.onConfigurationChanged(configuration);
         assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
 
         // THEN content in the captured DisplayArea is scaled to fit the surface size.
-        verify(mTransaction, atLeastOnce()).setMatrix(eq(mRecordedSurface), anyFloat(), eq(0f),
-                eq(0f),
-                anyFloat());
+        verify(mTransaction, atLeastOnce()).setMatrix(
+                eq(mRecordedSurface), anyFloat(), eq(0f), eq(0f), anyFloat());
         // THEN the resize callback is notified.
-        verify(mMediaProjectionManagerWrapper).notifyActiveProjectionCapturedContentResized(
-                recordedWidth, recordedHeight);
+        verify(mMediaProjectionManagerWrapper).notifyCaptureBoundsChanged(
+                mTaskSession.getContentToRecord(), mTaskSession.getTargetUid(), newBounds);
     }
 
     @Test
@@ -649,7 +648,7 @@
 
         mTask.removeImmediately();
 
-        verify(mMediaProjectionManagerWrapper).stopActiveProjection();
+        verify(mMediaProjectionManagerWrapper).stopActiveProjection(StopReason.STOP_TARGET_REMOVED);
     }
 
     @Test
@@ -684,8 +683,8 @@
         int xInset = (mSurfaceSize.x - scaledWidth) / 2;
         verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, xInset, 0);
         // THEN the resize callback is notified.
-        verify(mMediaProjectionManagerWrapper).notifyActiveProjectionCapturedContentResized(
-                displayAreaBounds.width(), displayAreaBounds.height());
+        verify(mMediaProjectionManagerWrapper).notifyCaptureBoundsChanged(
+                mDisplaySession.getContentToRecord(), mDisplaySession.getTargetUid(),  displayAreaBounds);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
index c9c31df..a0f4ae7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
@@ -239,6 +239,9 @@
         } else if (type.equals(FrameRateCategoryRate.class)) {
             field.set(first, new FrameRateCategoryRate(16666667, 11111111));
             field.set(second, new FrameRateCategoryRate(11111111, 8333333));
+        } else if (type.isArray() && type.getComponentType().equals(float.class)) {
+            field.set(first, new float[]{60.0f});
+            field.set(second, new float[]{120.0f});
         } else {
             throw new IllegalArgumentException("Field " + field
                     + " is not supported by this test, please add implementation of setting "
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 9408f90..9cbea2e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -601,12 +601,12 @@
                 TYPE_WALLPAPER, TYPE_APPLICATION);
 
         // Verify not waiting for display without system decorations.
-        doReturn(false).when(secondaryDisplay).supportsSystemDecorations();
+        doReturn(false).when(secondaryDisplay).isSystemDecorationsSupported();
         assertFalse(secondaryDisplay.shouldWaitForSystemDecorWindowsOnBoot());
 
         // Verify waiting for non-drawn windows on display with system decorations.
         reset(secondaryDisplay);
-        doReturn(true).when(secondaryDisplay).supportsSystemDecorations();
+        doReturn(true).when(secondaryDisplay).isSystemDecorationsSupported();
         assertTrue(secondaryDisplay.shouldWaitForSystemDecorWindowsOnBoot());
 
         // Verify not waiting for drawn windows on display with system decorations.
@@ -1865,7 +1865,6 @@
                 mRootWindowContainer.getDisplayRotationCoordinator();
         final DisplayContent defaultDisplayContent = mDisplayContent;
         final DisplayRotation defaultDisplayRotation = defaultDisplayContent.getDisplayRotation();
-        coordinator.removeDefaultDisplayRotationChangedCallback();
 
         DeviceStateController deviceStateController = mock(DeviceStateController.class);
         when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
@@ -1922,7 +1921,6 @@
 
         final DisplayRotationCoordinator coordinator =
                 mRootWindowContainer.getDisplayRotationCoordinator();
-        coordinator.removeDefaultDisplayRotationChangedCallback();
 
         DeviceStateController deviceStateController = mock(DeviceStateController.class);
         when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
@@ -2263,25 +2261,25 @@
     }
 
     @Test
-    public void testForceDesktopMode() {
+    public void testIsPublicSecondaryDisplayWithDesktopModeForceEnabled() {
         mWm.mForceDesktopModeOnExternalDisplays = true;
         // Not applicable for default display
-        assertFalse(mDefaultDisplay.forceDesktopMode());
+        assertFalse(mDefaultDisplay.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
 
         // Not applicable for private secondary display.
         final DisplayInfo displayInfo = new DisplayInfo();
         displayInfo.copyFrom(mDisplayInfo);
         displayInfo.flags = FLAG_PRIVATE;
         final DisplayContent privateDc = createNewDisplay(displayInfo);
-        assertFalse(privateDc.forceDesktopMode());
+        assertFalse(privateDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
 
         // Applicable for public secondary display.
         final DisplayContent publicDc = createNewDisplay();
-        assertTrue(publicDc.forceDesktopMode());
+        assertTrue(publicDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
 
         // Make sure forceDesktopMode() is false when the force config is disabled.
         mWm.mForceDesktopModeOnExternalDisplays = false;
-        assertFalse(publicDc.forceDesktopMode());
+        assertFalse(publicDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
index 4557df0..266ffff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
@@ -40,6 +40,9 @@
 @Presubmit
 public class DisplayRotationCoordinatorTests {
 
+    private static final int FIRST_DISPLAY_ID = 1;
+    private static final int SECOND_DISPLAY_ID = 2;
+
     @NonNull
     private final DisplayRotationCoordinator mCoordinator = new DisplayRotationCoordinator();
 
@@ -50,22 +53,45 @@
     }
 
     @Test (expected = UnsupportedOperationException.class)
-    public void testSecondRegistrationWithoutRemovingFirst() {
+    public void testSecondRegistrationWithoutRemovingFirstWhenDifferentDisplay() {
         Runnable callback1 = mock(Runnable.class);
         Runnable callback2 = mock(Runnable.class);
-        mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
-        mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
+        mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+        mCoordinator.setDefaultDisplayRotationChangedCallback(SECOND_DISPLAY_ID, callback2);
         assertEquals(callback1, mCoordinator.mDefaultDisplayRotationChangedCallback);
     }
 
     @Test
+    public void testSecondRegistrationWithoutRemovingFirstWhenSameDisplay() {
+        Runnable callback1 = mock(Runnable.class);
+        Runnable callback2 = mock(Runnable.class);
+        mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+        mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback2);
+        assertEquals(callback2, mCoordinator.mDefaultDisplayRotationChangedCallback);
+    }
+
+    @Test
+    public void testRemoveIncorrectRegistration() {
+        Runnable callback1 = mock(Runnable.class);
+        Runnable callback2 = mock(Runnable.class);
+        mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+        mCoordinator.removeDefaultDisplayRotationChangedCallback(callback2);
+        assertEquals(callback1, mCoordinator.mDefaultDisplayRotationChangedCallback);
+
+        // FIRST_DISPLAY_ID is still able to register another callback because the previous
+        // removal should not have succeeded.
+        mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback2);
+        assertEquals(callback2, mCoordinator.mDefaultDisplayRotationChangedCallback);
+    }
+
+    @Test
     public void testSecondRegistrationAfterRemovingFirst() {
         Runnable callback1 = mock(Runnable.class);
-        mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
-        mCoordinator.removeDefaultDisplayRotationChangedCallback();
+        mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+        mCoordinator.removeDefaultDisplayRotationChangedCallback(callback1);
 
         Runnable callback2 = mock(Runnable.class);
-        mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
+        mCoordinator.setDefaultDisplayRotationChangedCallback(SECOND_DISPLAY_ID, callback2);
 
         mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
         verify(callback2).run();
@@ -75,7 +101,7 @@
     @Test
     public void testRegisterThenDefaultDisplayRotationChanged() {
         Runnable callback = mock(Runnable.class);
-        mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
+        mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback);
         assertEquals(Surface.ROTATION_0, mCoordinator.getDefaultDisplayCurrentRotation());
         verify(callback, never()).run();
 
@@ -88,7 +114,7 @@
     public void testDefaultDisplayRotationChangedThenRegister() {
         mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
         Runnable callback = mock(Runnable.class);
-        mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
+        mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback);
         verify(callback).run();
         assertEquals(Surface.ROTATION_90, mCoordinator.getDefaultDisplayCurrentRotation());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
index c8fc482..6397334 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
@@ -20,16 +20,19 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
 
 import android.platform.test.annotations.Presubmit;
 import android.view.Surface;
@@ -54,6 +57,7 @@
 
     private DisplayRotationImmersiveAppCompatPolicy mPolicy;
 
+    private DisplayRotation mMockDisplayRotation;
     private AppCompatConfiguration mMockAppCompatConfiguration;
     private ActivityRecord mMockActivityRecord;
     private Task mMockTask;
@@ -98,6 +102,7 @@
         when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_90)).thenReturn(true);
         when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_180)).thenReturn(false);
         when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_270)).thenReturn(true);
+        mMockDisplayRotation = mockDisplayRotation;
 
         return mockDisplayRotation;
     }
@@ -196,6 +201,24 @@
     }
 
     @Test
+    public void testDeferOrientationUpdate() {
+        assertFalse(mPolicy.deferOrientationUpdate());
+
+        doReturn(SCREEN_ORIENTATION_UNSPECIFIED).when(mMockDisplayRotation).getLastOrientation();
+        final WindowOrientationListener orientationListener = mock(WindowOrientationListener.class);
+        doReturn(Surface.ROTATION_90).when(orientationListener).getProposedRotation();
+        doReturn(orientationListener).when(mMockDisplayRotation).getOrientationListener();
+        spyOn(mDisplayContent.mTransitionController);
+        doReturn(true).when(mDisplayContent.mTransitionController)
+                .hasTransientLaunch(mDisplayContent);
+
+        assertTrue(mPolicy.deferOrientationUpdate());
+        mDisplayContent.mTransitionController.mStateValidators.getFirst().run();
+
+        verify(mWm).updateRotation(false, false);
+    }
+
+    @Test
     public void testRotationChoiceEnforcedOnly_nullTopRunningActivity_lockNotEnforced() {
         when(mDisplayContent.topRunningActivity()).thenReturn(null);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index a0c5b54..c016c5e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -69,7 +69,9 @@
     private static final FrameRateVote FRAME_RATE_VOTE_60_PREFERRED =
             new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
                     SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
-
+    private static final float HI_REFRESH_RATE = 90;
+    private static final float MID_REFRESH_RATE = 70;
+    private static final float LOW_REFRESH_RATE = 60;
     WindowState createWindow(String name) {
         WindowState window = createWindow(null, TYPE_APPLICATION, name);
         when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
@@ -82,14 +84,16 @@
         DisplayInfo di = new DisplayInfo(mDisplayInfo);
         Mode defaultMode = di.getDefaultMode();
         Mode hiMode = new Mode(1,
-                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 90);
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), HI_REFRESH_RATE);
         Mode midMode = new Mode(2,
-                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 70);
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), MID_REFRESH_RATE);
         Mode lowMode = new Mode(LOW_MODE_ID,
-                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 60);
+                defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), LOW_REFRESH_RATE);
 
         di.supportedModes = new Mode[] { hiMode, midMode };
         di.appsSupportedModes = new Mode[] { hiMode, midMode, lowMode };
+        di.supportedRefreshRates = new float[] {HI_REFRESH_RATE, MID_REFRESH_RATE,
+                LOW_REFRESH_RATE};
         di.defaultModeId = 1;
         mRefreshRatePolicy = new RefreshRatePolicy(mWm, di, mDenylist);
         when(mDisplayPolicy.getRefreshRatePolicy()).thenReturn(mRefreshRatePolicy);
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 7ed8283..aa99250 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -434,6 +434,7 @@
     @Test
     public void testAddTaskCompatibleWindowingMode_withFreeformAndFullscreen_expectRemove() {
         Task task1 = createTaskBuilder(".Task1")
+                .setTaskId(1)
                 .setFlags(FLAG_ACTIVITY_NEW_TASK)
                 .build();
         doReturn(WINDOWING_MODE_FREEFORM).when(task1).getWindowingMode();
@@ -452,6 +453,10 @@
         assertThat(mCallbacksRecorder.mTrimmed).isEmpty();
         assertThat(mCallbacksRecorder.mRemoved).hasSize(1);
         assertThat(mCallbacksRecorder.mRemoved).contains(task1);
+
+        TaskChangeNotificationController controller =
+                mAtm.getTaskChangeNotificationController();
+        verify(controller, times(1)).notifyRecentTaskRemovedForAddTask(task1.mTaskId);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index cc38f02..73e5f58 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -101,6 +101,8 @@
                 defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), LOW_REFRESH_RATE);
         mDisplayInfo.supportedModes = new Mode[] { hiMode, midMode };
         mDisplayInfo.appsSupportedModes = new Mode[] { hiMode, midMode, lowMode };
+        mDisplayInfo.supportedRefreshRates = new float[] {HI_REFRESH_RATE, MID_REFRESH_RATE,
+                LOW_REFRESH_RATE};
         mDisplayInfo.defaultModeId = HI_MODE_ID;
         mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
index 791b5b5..a92fe3a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
@@ -162,6 +162,10 @@
             verifySecureExceptionThrown(activityOptions, taskSupervisor);
 
             activityOptions = ActivityOptions.makeBasic();
+            activityOptions.setTaskAlwaysOnTop(true);
+            verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+            activityOptions = ActivityOptions.makeBasic();
             activityOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
             verifySecureExceptionThrown(activityOptions, taskSupervisor);
 
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 ad66118..bf96f0eb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4892,13 +4892,16 @@
 
     @Test
     public void testUniversalResizeable() {
-        mWm.mConstants.mIgnoreActivityOrientationRequest = true;
+        mWm.mConstants.mIgnoreActivityOrientationRequestSmallScreen = true;
+        mWm.mConstants.mIgnoreActivityOrientationRequestLargeScreen = true;
         setUpApp(mDisplayContent);
         final float maxAspect = 1.8f;
         final float minAspect = 1.5f;
         prepareLimitedBounds(mActivity, maxAspect, minAspect,
                 ActivityInfo.SCREEN_ORIENTATION_NOSENSOR, true /* isUnresizable */);
 
+        assertTrue(ActivityRecord.canBeUniversalResizeable(mActivity.info.applicationInfo,
+                mWm, true /* isLargeScreen */, false /* forActivity */));
         assertTrue(mActivity.isUniversalResizeable());
         assertTrue(mActivity.isResizeable());
         assertFalse(mActivity.shouldCreateAppCompatDisplayInsets());
@@ -4917,7 +4920,8 @@
         assertEquals(minAspect, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);
 
         // User override can still take effect.
-        doReturn(true).when(aspectRatioOverrides).shouldApplyUserMinAspectRatioOverride();
+        doReturn(USER_MIN_ASPECT_RATIO_3_2).when(aspectRatioOverrides)
+                .getUserMinAspectRatioOverrideCode();
         assertFalse(mActivity.isResizeable());
         assertEquals(maxAspect, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
         assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
@@ -4928,6 +4932,7 @@
         spyOn(pm);
         final PackageManager.Property property = new PackageManager.Property("propertyName",
                 true /* value */, name.getPackageName(), name.getClassName());
+        // Activity level.
         try {
             doReturn(property).when(pm).getPropertyAsUser(
                     WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
@@ -4938,6 +4943,21 @@
         final ActivityRecord optOutActivity = new ActivityBuilder(mAtm)
                 .setComponent(name).setTask(mTask).build();
         assertFalse(optOutActivity.isUniversalResizeable());
+
+        // Application level.
+        try {
+            doReturn(property).when(pm).getProperty(
+                    WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
+                    name.getPackageName());
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+        final ActivityRecord optOutAppActivity = new ActivityBuilder(mAtm)
+                .setComponent(getUniqueComponentName(mContext.getPackageName()))
+                .setTask(mTask).build();
+        assertFalse(optOutAppActivity.isUniversalResizeable());
+        assertFalse(ActivityRecord.canBeUniversalResizeable(mActivity.info.applicationInfo,
+                mWm, true /* isLargeScreen */, false /* forActivity */));
     }
 
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index b595383..f795d93 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -605,7 +605,7 @@
     @Test
     public void testGetOrCreateRootHomeTask_supportedSecondaryDisplay() {
         DisplayContent display = createNewDisplay();
-        doReturn(true).when(display).supportsSystemDecorations();
+        doReturn(true).when(display).isSystemDecorationsSupported();
 
         // Remove the current home root task if it exists so a new one can be created below.
         TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
@@ -622,7 +622,7 @@
     public void testGetOrCreateRootHomeTask_unsupportedSystemDecorations() {
         DisplayContent display = createNewDisplay();
         TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
-        doReturn(false).when(display).supportsSystemDecorations();
+        doReturn(false).when(display).isSystemDecorationsSupported();
 
         assertNull(taskDisplayArea.getRootHomeTask());
         assertNull(taskDisplayArea.getOrCreateRootHomeTask());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 546b1ba..ccce57a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -204,13 +204,13 @@
             final DisplayPolicy displayPolicy = newDisplay.getDisplayPolicy();
             spyOn(displayPolicy);
             if (mSystemDecorations) {
-                doReturn(true).when(newDisplay).supportsSystemDecorations();
+                doReturn(true).when(newDisplay).isSystemDecorationsSupported();
                 doReturn(true).when(displayPolicy).hasNavigationBar();
                 doReturn(true).when(displayPolicy).hasBottomNavigationBar();
             } else {
                 doReturn(false).when(displayPolicy).hasNavigationBar();
                 doReturn(false).when(displayPolicy).hasStatusBar();
-                doReturn(false).when(newDisplay).supportsSystemDecorations();
+                doReturn(false).when(newDisplay).isSystemDecorationsSupported();
             }
             // Update the display policy to make the screen fully turned on so animation is allowed
             displayPolicy.screenTurningOn(null /* screenOnListener */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 8bbba1b..bfa6cb8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -29,6 +29,7 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY;
 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
@@ -42,11 +43,11 @@
 import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.hardware.input.Flags.FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
@@ -76,6 +77,7 @@
 import android.app.ActivityThread;
 import android.app.IApplicationThread;
 import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.os.IBinder;
@@ -85,7 +87,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
-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;
@@ -97,7 +98,6 @@
 import android.view.InputDevice;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
-import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowInsets;
@@ -1139,6 +1139,53 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+    public void testUpdateInputChannel_sanitizeWithoutPermission_ThrowsError() {
+        final Session session = mock(Session.class);
+        final int callingUid = Process.FIRST_APPLICATION_UID;
+        final int callingPid = 1234;
+        final SurfaceControl surfaceControl = mock(SurfaceControl.class);
+        final IBinder window = new Binder();
+        final InputTransferToken inputTransferToken = mock(InputTransferToken.class);
+
+
+        final InputChannel inputChannel = new InputChannel();
+
+        assertThrows(IllegalArgumentException.class, () ->
+                mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY,
+                        surfaceControl, window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE,
+                        0 /* privateFlags */,
+                        INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS,
+                        TYPE_APPLICATION, null /* windowToken */, inputTransferToken,
+                        "TestInputChannel", inputChannel));
+    }
+
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
+    public void testUpdateInputChannel_sanitizeWithPermission_doesNotThrowError() {
+        final Session session = mock(Session.class);
+        final int callingUid = Process.FIRST_APPLICATION_UID;
+        final int callingPid = 1234;
+        final SurfaceControl surfaceControl = mock(SurfaceControl.class);
+        final IBinder window = new Binder();
+        final InputTransferToken inputTransferToken = mock(InputTransferToken.class);
+
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mWm.mContext).checkPermission(
+                android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW,
+                callingPid,
+                callingUid);
+
+        final InputChannel inputChannel = new InputChannel();
+
+        mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl,
+                window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, 0 /* privateFlags */,
+                INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS,
+                TYPE_APPLICATION, null /* windowToken */, inputTransferToken, "TestInputChannel",
+                inputChannel);
+    }
+
+    @Test
     public void testUpdateInputChannel_allowSpyWindowForInputMonitorPermission() {
         final Session session = mock(Session.class);
         final int callingUid = Process.SYSTEM_UID;
@@ -1192,55 +1239,6 @@
                 argThat(h -> (h.inputConfig & InputConfig.SENSITIVE_FOR_PRIVACY) != 0));
     }
 
-    @RequiresFlagsDisabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
-    @Test
-    public void testDrawMagnifiedViewport() {
-        final int displayId = mDisplayContent.mDisplayId;
-        // Use real surface, so ViewportWindow's BlastBufferQueue can be created.
-        final ArrayList<SurfaceControl> surfaceControls = new ArrayList<>();
-        mWm.mSurfaceControlFactory = () -> new SurfaceControl.Builder() {
-            @Override
-            public SurfaceControl build() {
-                final SurfaceControl sc = super.build();
-                surfaceControls.add(sc);
-                return sc;
-            }
-        };
-        mWm.mAccessibilityController.setMagnificationCallbacks(displayId,
-                mock(WindowManagerInternal.MagnificationCallbacks.class));
-        final boolean[] lockCanvasInWmLock = { false };
-        final Surface surface = mWm.mAccessibilityController.forceShowMagnifierSurface(displayId);
-        spyOn(surface);
-        doAnswer(invocationOnMock -> {
-            lockCanvasInWmLock[0] |= Thread.holdsLock(mWm.mGlobalLock);
-            invocationOnMock.callRealMethod();
-            return null;
-        }).when(surface).lockCanvas(any());
-        mWm.mAccessibilityController
-                .recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded(displayId);
-        waitUntilHandlersIdle();
-        try {
-            verify(surface).lockCanvas(any());
-
-            clearInvocations(surface);
-            // Invalidate and redraw.
-            mWm.mAccessibilityController.onDisplaySizeChanged(mDisplayContent);
-            mWm.mAccessibilityController
-                    .recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded(displayId);
-            // Turn off magnification to release surface.
-            mWm.mAccessibilityController.setMagnificationCallbacks(displayId, null);
-            waitUntilHandlersIdle();
-            // lockCanvas must not be called after releasing.
-            verify(surface, never()).lockCanvas(any());
-            verify(surface).release();
-            assertFalse(lockCanvasInWmLock[0]);
-        } finally {
-            for (int i = surfaceControls.size() - 1; i >= 0; --i) {
-                surfaceControls.get(i).release();
-            }
-        }
-    }
-
     @Test
     public void testRequestKeyboardShortcuts_noWindow() {
         doNothing().when(mWm.mContext).enforceCallingOrSelfPermission(anyString(), anyString());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 757c358..78e6cbf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1027,7 +1027,8 @@
             }
 
             @Override
-            public void setImeInputTargetRequestedVisibility(boolean visible) {
+            public void setImeInputTargetRequestedVisibility(boolean visible,
+                    @NonNull ImeTracker.Token statsToken) {
             }
         };
     }
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 5ef0fe3..d976f5ba 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -337,7 +337,7 @@
                                       deviceAddress, hasOutput, hasInput,
                                       isInputHeadset, isOutputHeadset, isDock);
             alsaDevice.setDeviceNameAndDescription(
-                      cardRec.getCardName(), cardRec.getCardDescription());
+                    usbDevice.getProductName(), cardRec.getCardDescription());
             if (IS_MULTI_MODE) {
                 deselectCurrentDevice(alsaDevice.getInputDeviceType());
                 deselectCurrentDevice(alsaDevice.getOutputDeviceType());
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index fb031bd..01ff674 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -842,7 +842,7 @@
             return;
         }
 
-        model.setRequested(config.isAllowMultipleTriggers());
+        model.setRequested(config.isMultipleTriggersAllowed());
         // TODO: Remove this block if the lower layer supports multiple triggers.
         if (model.isRequested()) {
             updateRecognitionLocked(model, true);
@@ -964,7 +964,7 @@
         RecognitionConfig config = modelData.getRecognitionConfig();
         if (config != null) {
             // Whether we should continue by starting this again.
-            modelData.setRequested(config.isAllowMultipleTriggers());
+            modelData.setRequested(config.isMultipleTriggersAllowed());
         }
         // TODO: Remove this block if the lower layer supports multiple triggers.
         if (modelData.isRequested()) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 19a6ddc..e0cdbdd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1445,7 +1445,7 @@
                 runOrAddOperation(new Operation(
                         // always execute:
                         () -> {
-                            if (!mRecognitionConfig.isAllowMultipleTriggers()) {
+                            if (!mRecognitionConfig.isMultipleTriggersAllowed()) {
                                 // Unregister this remoteService once op is done
                                 synchronized (mCallbacksLock) {
                                     mCallbacks.remove(mPuuid.getUuid());
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 377e5f2..e0af223 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3708,7 +3708,7 @@
      * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
      * privilege permission of the subscription.
      *
-     * @param opportunistic whether it’s opportunistic subscription.
+     * @param opportunistic whether it's an opportunistic subscription.
      * @param subId the unique SubscriptionInfo index in database
      * @return {@code true} if the operation is succeed, {@code false} otherwise.
      *
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ad5d42a..65a52da 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5251,6 +5251,36 @@
     }
 
     /**
+     * Returns the Group Identifier Level 2 in hexadecimal format.
+     * @return the Group Identifier Level 2 for the SIM card.
+     *         Return null if it is unavailable.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_GET_GROUP_ID_LEVEL2)
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+    @SystemApi
+    @Nullable
+    public String getGroupIdLevel2() {
+        try {
+            IPhoneSubInfo info = getSubscriberInfoService();
+            if (info == null) {
+                return null;
+            }
+            return info.getGroupIdLevel2ForSubscriber(getSubId(), mContext.getOpPackageName(),
+                    mContext.getAttributionTag());
+        } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return null;
+        }
+    }
+
+    /**
      * Returns the phone number string for line 1, for example, the MSISDN
      * for a GSM phone for a particular subscription. Return null if it is unavailable.
      * <p>
@@ -19606,7 +19636,7 @@
      * Android assigns each carrier with a canonical integer a.k.a. carrier id.
      * The carrier ID is an Android platform-wide identifier for a carrier.
      * AOSP maintains carrier ID assignments in
-     * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb">here</a>
+     * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/main/assets/latest_carrier_id/carrier_list.textpb">here</a>
      *
      * @param carrierIdentifier {@link CarrierIdentifier}
      *
diff --git a/telephony/java/android/telephony/satellite/EarfcnRange.java b/telephony/java/android/telephony/satellite/EarfcnRange.java
index 207b25d6..81c8ed3 100644
--- a/telephony/java/android/telephony/satellite/EarfcnRange.java
+++ b/telephony/java/android/telephony/satellite/EarfcnRange.java
@@ -19,6 +19,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -40,7 +41,8 @@
  *
  * @hide
  */
-@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
 public final class EarfcnRange implements Parcelable {
 
     /**
@@ -74,6 +76,7 @@
      *
      * @param startEarfcn The starting earfcn value.
      * @param endEarfcn   The ending earfcn value.
+     * @hide
      */
     public EarfcnRange(@IntRange(from = 0, to = 65535) int startEarfcn,
             @IntRange(from = 0, to = 65535) int endEarfcn) {
diff --git a/telephony/java/android/telephony/satellite/ISelectedNbIotSatelliteSubscriptionCallback.aidl b/telephony/java/android/telephony/satellite/ISelectedNbIotSatelliteSubscriptionCallback.aidl
new file mode 100644
index 0000000..178bb3c
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ISelectedNbIotSatelliteSubscriptionCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright 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.telephony.satellite;
+
+/**
+ * Interface for selected satellite subscription change callback.
+ *
+ * @hide
+ */
+oneway interface ISelectedNbIotSatelliteSubscriptionCallback {
+    /**
+     * Called when the selected satellite subscription has changed.
+     *
+     * @param selectedSubId The new satellite subscription id.
+     */
+    void onSelectedNbIotSatelliteSubscriptionChanged(in int selectedSubId);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.java b/telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.java
index c1a6ae8..56c92d0 100644
--- a/telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.java
+++ b/telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.java
@@ -17,6 +17,7 @@
 package android.telephony.satellite;
 
 import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -34,7 +35,8 @@
  *
  * @hide
  */
-@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
 public final class SatelliteAccessConfiguration implements Parcelable {
     /**
      * The list of satellites available at the current location.
@@ -54,6 +56,7 @@
      * @param satelliteInfos The list of {@link SatelliteInfo} objects representing the satellites
      *                       accessible with this configuration.
      * @param tagIdList      The list of tag IDs associated with this configuration.
+     * @hide
      */
     public SatelliteAccessConfiguration(@NonNull List<SatelliteInfo> satelliteInfos,
             @NonNull List<Integer> tagIdList) {
@@ -61,12 +64,18 @@
         mTagIdList = tagIdList;
     }
 
+    /**
+     * Constructor for {@link SatelliteAccessConfiguration}.
+     * @param in parcel used to create {@link SatelliteAccessConfiguration} object
+     * @hide
+     */
     public SatelliteAccessConfiguration(Parcel in) {
         mSatelliteInfoList = in.createTypedArrayList(SatelliteInfo.CREATOR);
         mTagIdList = new ArrayList<>();
         in.readList(mTagIdList, Integer.class.getClassLoader(), Integer.class);
     }
 
+    @NonNull
     public static final Creator<SatelliteAccessConfiguration> CREATOR =
             new Creator<SatelliteAccessConfiguration>() {
                 @Override
diff --git a/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
index bffb11f..6291102 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
@@ -18,6 +18,8 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
 
 import com.android.internal.telephony.flags.Flags;
 
@@ -27,19 +29,19 @@
  *
  * @hide
  */
-@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
 public interface SatelliteCommunicationAllowedStateCallback {
 
     /**
      * Telephony does not guarantee that whenever there is a change in communication allowed state,
      * this API will be called. Telephony does its best to detect the changes and notify its
-     * listeners accordingly.
+     * listeners accordingly. Satellite communication is allowed at a location when it is legally
+     * allowed by the local authority and satellite signal coverage is available.
      *
-     * @param isAllowed {@code true} means satellite allow state is changed,
-     *                  {@code false} satellite allow state is not changed
-     * @hide
+     * @param isAllowed {@code true} means satellite is allowed,
+     *                  {@code false} satellite is not allowed.
      */
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onSatelliteCommunicationAllowedStateChanged(boolean isAllowed);
 
     /**
@@ -49,9 +51,7 @@
      *                                       the current location. When satellite is not allowed at
      *                                       the current location,
      *                                       {@code satelliteRegionalConfiguration} will be null.
-     * @hide
      */
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     default void onSatelliteAccessConfigurationChanged(
             @Nullable SatelliteAccessConfiguration satelliteAccessConfiguration) {};
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteInfo.java b/telephony/java/android/telephony/satellite/SatelliteInfo.java
index 7ff2318..ebb4e9c 100644
--- a/telephony/java/android/telephony/satellite/SatelliteInfo.java
+++ b/telephony/java/android/telephony/satellite/SatelliteInfo.java
@@ -17,7 +17,7 @@
 package android.telephony.satellite;
 
 import android.annotation.FlaggedApi;
-import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.ParcelUuid;
 import android.os.Parcelable;
@@ -37,8 +37,9 @@
  *
  * @hide
  */
-@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
-public class SatelliteInfo implements Parcelable {
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+public final class SatelliteInfo implements Parcelable {
     /**
      * Unique identification number for the satellite.
      * This ID is used to distinguish between different satellites in the network.
@@ -47,10 +48,12 @@
     private UUID mId;
 
     /**
-     * Position information of a satellite.
+     * Position information of a geostationary satellite.
      * This includes the longitude and altitude of the satellite.
+     * If the SatellitePosition is invalid,
+     * longitudeDegree and altitudeKm will be represented as DOUBLE.NaN.
      */
-    @Nullable
+    @NonNull
     private SatellitePosition mPosition;
 
     /**
@@ -67,6 +70,9 @@
      */
     private final List<EarfcnRange> mEarfcnRangeList;
 
+    /**
+     * @hide
+     */
     protected SatelliteInfo(Parcel in) {
         ParcelUuid parcelUuid = in.readParcelable(
                 ParcelUuid.class.getClassLoader(), ParcelUuid.class);
@@ -88,8 +94,9 @@
      * @param bandList          The list of frequency bandList supported by the satellite.
      * @param earfcnRanges      The list of {@link EarfcnRange} objects representing the EARFCN
      *                          ranges supported by the satellite.
+     * @hide
      */
-    public SatelliteInfo(@NonNull UUID satelliteId, @Nullable SatellitePosition satellitePosition,
+    public SatelliteInfo(@NonNull UUID satelliteId, @NonNull SatellitePosition satellitePosition,
             @NonNull List<Integer> bandList, @NonNull List<EarfcnRange> earfcnRanges) {
         mId = satelliteId;
         mPosition = satellitePosition;
@@ -97,6 +104,7 @@
         mEarfcnRangeList = earfcnRanges;
     }
 
+    @NonNull
     public static final Creator<SatelliteInfo> CREATOR = new Creator<SatelliteInfo>() {
         @Override
         public SatelliteInfo createFromParcel(Parcel in) {
@@ -134,11 +142,14 @@
 
     /**
      * Returns the position of the satellite.
+     * Position information of a geostationary satellite.
+     * This includes the longitude and altitude of the satellite.
+     * If the SatellitePosition is invalid,
+     * longitudeDegree and altitudeKm will be represented as DOUBLE.NaN.
      *
-     * @return The {@link SatellitePosition} of the satellite, or {@code null} if the position is
-     * not available.
+     * @return The {@link SatellitePosition} of the satellite.
      */
-    @Nullable
+    @NonNull
     public SatellitePosition getSatellitePosition() {
         return mPosition;
     }
@@ -146,6 +157,8 @@
     /**
      * Returns the list of frequency bands supported by the satellite.
      *
+     * Refer specification 3GPP TS 36.101 for detailed information on frequency bands.
+     *
      * @return The list of frequency bands.
      */
     @NonNull
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 19a72c4..1025c15 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -99,16 +99,18 @@
     private static final ConcurrentHashMap<SatelliteSupportedStateCallback,
             ISatelliteSupportedStateCallback> sSatelliteSupportedStateCallbackMap =
             new ConcurrentHashMap<>();
-
     private static final ConcurrentHashMap<SatelliteCommunicationAllowedStateCallback,
             ISatelliteCommunicationAllowedStateCallback>
             sSatelliteCommunicationAllowedStateCallbackMap =
             new ConcurrentHashMap<>();
-
     private static final ConcurrentHashMap<SatelliteDisallowedReasonsCallback,
             ISatelliteDisallowedReasonsCallback>
             sSatelliteDisallowedReasonsCallbackMap =
             new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<SelectedNbIotSatelliteSubscriptionCallback,
+            ISelectedNbIotSatelliteSubscriptionCallback>
+            sSelectedNbIotSatelliteSubscriptionCallbackMap =
+            new ConcurrentHashMap<>();
 
     private final int mSubId;
 
@@ -219,6 +221,14 @@
 
     /**
      * Bundle key to get the response from
+     * {@link #requestSessionStats(Executor, OutcomeReceiver)}.
+     * @hide
+     */
+
+    public static final String KEY_SESSION_STATS_V2 = "session_stats_v2";
+
+    /**
+     * Bundle key to get the response from
      * {@link #requestIsProvisioned(Executor, OutcomeReceiver)}.
      * @hide
      */
@@ -752,8 +762,10 @@
      * config_satellite_gateway_service_package
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED =
-            "android.telephony.action.ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED";
+            "android.telephony.satellite.action.SATELLITE_SUBSCRIBER_ID_LIST_CHANGED";
 
 
     /**
@@ -763,8 +775,10 @@
      * config_satellite_gateway_service_package
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String ACTION_SATELLITE_START_NON_EMERGENCY_SESSION =
-            "android.telephony.action.ACTION_SATELLITE_START_NON_EMERGENCY_SESSION";
+            "android.telephony.satellite.action.SATELLITE_START_NON_EMERGENCY_SESSION";
     /**
      * Meta-data represents whether the application supports P2P SMS over carrier roaming satellite
      * which needs manual trigger to connect to satellite. The messaging applications that supports
@@ -778,6 +792,8 @@
      * }
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT =
             "android.telephony.METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT";
 
@@ -2548,6 +2564,89 @@
     }
 
     /**
+     * Registers for selected satellite subscription changed event from the satellite service.
+     *
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback to handle the selected satellite subscription changed event.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteResult public int registerForSelectedNbIotSatelliteSubscriptionChanged(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SelectedNbIotSatelliteSubscriptionCallback callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ISelectedNbIotSatelliteSubscriptionCallback internalCallback =
+                        new ISelectedNbIotSatelliteSubscriptionCallback.Stub() {
+                            @Override
+                            public void onSelectedNbIotSatelliteSubscriptionChanged(
+                                    int selectedSubId) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSelectedNbIotSatelliteSubscriptionChanged(
+                                                selectedSubId)));
+                            }
+                        };
+                sSelectedNbIotSatelliteSubscriptionCallbackMap.put(callback, internalCallback);
+                return telephony.registerForSelectedNbIotSatelliteSubscriptionChanged(
+                        internalCallback);
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("registerForSelectedNbIotSatelliteSubscriptionChanged() RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+        return SATELLITE_RESULT_REQUEST_FAILED;
+    }
+
+    /**
+     * Unregisters for selected satellite subscription changed event from the satellite service. If
+     * callback was not registered before, the request will be ignored.
+     *
+     * @param callback The callback that was passed to {@link
+     *     #registerForSelectedNbIotSatelliteSubscriptionChanged(Executor,
+     *     SelectedNbIotSatelliteSubscriptionCallback)}.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    public void unregisterForSelectedNbIotSatelliteSubscriptionChanged(
+            @NonNull SelectedNbIotSatelliteSubscriptionCallback callback) {
+        Objects.requireNonNull(callback);
+        ISelectedNbIotSatelliteSubscriptionCallback internalCallback =
+                sSelectedNbIotSatelliteSubscriptionCallbackMap.remove(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                if (internalCallback != null) {
+                    telephony.unregisterForSelectedNbIotSatelliteSubscriptionChanged(
+                            internalCallback);
+                } else {
+                    loge("unregisterForSelectedNbIotSatelliteSubscriptionChanged: " +
+                            "No internal callback.");
+                }
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("unregisterForSelectedNbIotSatelliteSubscriptionChanged() RemoteException: " +
+                    ex);
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Inform whether the device is aligned with the satellite in both real and demo mode.
      *
      * In demo mode, framework will send datagram to modem only when device is aligned with
@@ -3196,13 +3295,14 @@
      * @param executor The executor on which the callback will be called.
      * @param callback The callback to handle the satellite supoprted state changed event.
      *
-     * @return The {@link SatelliteResult} result of the operation.
+     * @return The result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     *
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @SatelliteResult public int registerForSupportedStateChanged(
             @NonNull @CallbackExecutor Executor executor,
@@ -3244,11 +3344,11 @@
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
-     *
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public void unregisterForSupportedStateChanged(
             @NonNull SatelliteSupportedStateCallback callback) {
         Objects.requireNonNull(callback);
@@ -3277,11 +3377,13 @@
      *
      * @param executor The executor on which the callback will be called.
      * @param callback The callback to handle satellite communication allowed state changed event.
-     * @return The {@link SatelliteResult} result of the operation.
+     * @return The result of the operation.
      * @throws SecurityException     if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @SatelliteResult
     public int registerForCommunicationAllowedStateChanged(
@@ -3336,8 +3438,9 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public void unregisterForCommunicationAllowedStateChanged(
             @NonNull SatelliteCommunicationAllowedStateCallback callback) {
         Objects.requireNonNull(callback);
@@ -3377,7 +3480,6 @@
      */
     @RequiresPermission(allOf = {Manifest.permission.PACKAGE_USAGE_STATS,
             Manifest.permission.MODIFY_PHONE_STATE})
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public void requestSessionStats(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<SatelliteSessionStats, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -3390,21 +3492,33 @@
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
                         if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                            SatelliteSessionStats stats;
                             if (resultData.containsKey(KEY_SESSION_STATS)) {
-                                SatelliteSessionStats stats =
-                                        resultData.getParcelable(KEY_SESSION_STATS,
-                                                SatelliteSessionStats.class);
-                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onResult(stats)));
+                                stats = resultData.getParcelable(KEY_SESSION_STATS,
+                                        SatelliteSessionStats.class);
+                                if (resultData.containsKey(KEY_SESSION_STATS_V2)) {
+                                    SatelliteSessionStats stats1 = resultData.getParcelable(
+                                            KEY_SESSION_STATS_V2, SatelliteSessionStats.class);
+                                    if (stats != null && stats1 != null) {
+                                        stats.setSatelliteSessionStats(
+                                                stats1.getSatelliteSessionStats());
+                                        executor.execute(() -> Binder.withCleanCallingIdentity(
+                                                () -> callback.onResult(stats)));
+                                        return;
+                                    }
+                                } else {
+                                    loge("KEY_SESSION_STATS_V2 does not exist.");
+                                }
                             } else {
                                 loge("KEY_SESSION_STATS does not exist.");
-                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onError(new SatelliteException(
-                                                SATELLITE_RESULT_REQUEST_FAILED))));
                             }
+                            executor.execute(() -> Binder.withCleanCallingIdentity(
+                                    () -> callback.onError(new SatelliteException(
+                                            SATELLITE_RESULT_REQUEST_FAILED))));
+
                         } else {
-                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                    callback.onError(new SatelliteException(resultCode))));
+                            executor.execute(() -> Binder.withCleanCallingIdentity(
+                                    () -> callback.onError(new SatelliteException(resultCode))));
                         }
                     }
                 };
@@ -3437,8 +3551,9 @@
      * @throws SecurityException if the caller doesn't have required permission.
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public void requestSatelliteSubscriberProvisionStatus(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<List<SatelliteSubscriberProvisionStatus>,
@@ -3554,8 +3669,9 @@
      * @throws SecurityException if the caller doesn't have required permission.
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public void provisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
@@ -3609,8 +3725,9 @@
      * @throws SecurityException if the caller doesn't have required permission.
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public void deprovisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
diff --git a/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java b/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java
index 5e56f84..d7fa3c9 100644
--- a/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java
+++ b/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java
@@ -16,10 +16,14 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.Objects;
 
 /**
@@ -28,6 +32,8 @@
  *
  * @hide
  */
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
 public final class SatelliteModemEnableRequestAttributes implements Parcelable {
 
     /** {@code true} to enable satellite and {@code false} to disable satellite */
@@ -47,6 +53,9 @@
     /** The subscription related info */
     @NonNull private final SatelliteSubscriptionInfo mSatelliteSubscriptionInfo;
 
+    /**
+     * @hide
+     */
     public SatelliteModemEnableRequestAttributes(boolean isEnabled, boolean isDemoMode,
             boolean isEmergencyMode, @NonNull SatelliteSubscriptionInfo satelliteSubscriptionInfo) {
         mIsEnabled = isEnabled;
@@ -76,6 +85,7 @@
         mSatelliteSubscriptionInfo.writeToParcel(dest, flags);
     }
 
+    @NonNull
     public static final Creator<SatelliteModemEnableRequestAttributes> CREATOR = new Creator<>() {
         @Override
         public SatelliteModemEnableRequestAttributes createFromParcel(Parcel in) {
@@ -114,19 +124,39 @@
         return Objects.hash(mIsEnabled, mIsDemoMode, mIsEmergencyMode, mSatelliteSubscriptionInfo);
     }
 
+
+    /**
+     * Get whether satellite modem needs to be enabled or disabled.
+     * @return {@code true} if the request is to enable satellite, else {@code false} to disable
+     * satellite.
+     */
     public boolean isEnabled() {
         return mIsEnabled;
     }
 
+    /**
+     * Get whether satellite modem is enabled for demo mode.
+     * @return {@code true} if the request is to enable demo mode, else {@code false}.
+     */
     public boolean isDemoMode() {
         return mIsDemoMode;
     }
 
+    /**
+     * Get whether satellite modem is enabled for emergency mode.
+     * @return {@code true} if the request is to enable satellite for emergency mode,
+     * else {@code false}.
+     */
     public boolean isEmergencyMode() {
         return mIsEmergencyMode;
     }
 
-    @NonNull public SatelliteSubscriptionInfo getSatelliteSubscriptionInfo() {
+
+    /**
+     * Return subscription info related to satellite.
+     */
+    @NonNull
+    public SatelliteSubscriptionInfo getSatelliteSubscriptionInfo() {
         return mSatelliteSubscriptionInfo;
     }
 }
diff --git a/telephony/java/android/telephony/satellite/SatellitePosition.java b/telephony/java/android/telephony/satellite/SatellitePosition.java
index dd463e0..b8d138f 100644
--- a/telephony/java/android/telephony/satellite/SatellitePosition.java
+++ b/telephony/java/android/telephony/satellite/SatellitePosition.java
@@ -16,6 +16,7 @@
 package android.telephony.satellite;
 
 import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -34,8 +35,9 @@
  *
  * @hide
  */
-@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
-public class SatellitePosition implements Parcelable {
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+public final class SatellitePosition implements Parcelable {
 
     /**
      * The longitude of the satellite in degrees, ranging from -180 to 180 degrees
@@ -51,6 +53,7 @@
      * Constructor for {@link SatellitePosition} used to create an instance from a {@link Parcel}.
      *
      * @param in The {@link Parcel} to read the satellite position data from.
+     * @hide
      */
     public SatellitePosition(Parcel in) {
         mLongitudeDegree = in.readDouble();
@@ -62,12 +65,14 @@
      *
      * @param longitudeDegree The longitude of the satellite in degrees.
      * @param altitudeKm  The altitude of the satellite in kilometers.
+     * @hide
      */
     public SatellitePosition(double longitudeDegree, double altitudeKm) {
         mLongitudeDegree = longitudeDegree;
         mAltitudeKm = altitudeKm;
     }
 
+    @NonNull
     public static final Creator<SatellitePosition> CREATOR = new Creator<SatellitePosition>() {
         @Override
         public SatellitePosition createFromParcel(Parcel in) {
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index e8ae0f5..6b95eb3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -48,9 +48,8 @@
      *
      * @param satelliteSubscriberProvisionStatus The List contains the latest provisioning states
      *                                           of the SatelliteSubscriberInfos.
-     * @hide
      */
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     default void onSatelliteSubscriptionProvisionStateChanged(
             @NonNull List<SatelliteSubscriberProvisionStatus>
                     satelliteSubscriberProvisionStatus) {};
diff --git a/telephony/java/android/telephony/satellite/SatelliteSessionStats.java b/telephony/java/android/telephony/satellite/SatelliteSessionStats.java
index aabb058..0cdba83 100644
--- a/telephony/java/android/telephony/satellite/SatelliteSessionStats.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSessionStats.java
@@ -19,23 +19,37 @@
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Log;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 
 /**
  * SatelliteSessionStats is used to represent the usage stats of the satellite service.
+ *
  * @hide
  */
-public class SatelliteSessionStats implements Parcelable {
+public final class SatelliteSessionStats implements Parcelable {
     private int mCountOfSuccessfulUserMessages;
     private int mCountOfUnsuccessfulUserMessages;
     private int mCountOfTimedOutUserMessagesWaitingForConnection;
     private int mCountOfTimedOutUserMessagesWaitingForAck;
     private int mCountOfUserMessagesInQueueToBeSent;
+    private long mLatencyOfSuccessfulUserMessages;
+
+    private Map<Integer, SatelliteSessionStats> datagramStats;
+    private long mMaxLatency;
+    private long mLastMessageLatency;
+
+    public SatelliteSessionStats() {
+        this.datagramStats = new HashMap<>();
+    }
 
     /**
      * SatelliteSessionStats constructor
-     * @param  builder Builder to create SatelliteSessionStats object/
+     *
+     * @param builder Builder to create SatelliteSessionStats object/
      */
     public SatelliteSessionStats(@NonNull Builder builder) {
         mCountOfSuccessfulUserMessages = builder.mCountOfSuccessfulUserMessages;
@@ -45,6 +59,7 @@
         mCountOfTimedOutUserMessagesWaitingForAck =
                 builder.mCountOfTimedOutUserMessagesWaitingForAck;
         mCountOfUserMessagesInQueueToBeSent = builder.mCountOfUserMessagesInQueueToBeSent;
+        mLatencyOfSuccessfulUserMessages = builder.mLatencyOfSuccessfulUserMessages;
     }
 
     private SatelliteSessionStats(Parcel in) {
@@ -63,6 +78,19 @@
         out.writeInt(mCountOfTimedOutUserMessagesWaitingForConnection);
         out.writeInt(mCountOfTimedOutUserMessagesWaitingForAck);
         out.writeInt(mCountOfUserMessagesInQueueToBeSent);
+        out.writeLong(mLatencyOfSuccessfulUserMessages);
+        out.writeLong(mMaxLatency);
+        out.writeLong(mLastMessageLatency);
+
+        if (datagramStats != null && !datagramStats.isEmpty()) {
+            out.writeInt(datagramStats.size());
+            for (Map.Entry<Integer, SatelliteSessionStats> entry : datagramStats.entrySet()) {
+                out.writeInt(entry.getKey());
+                out.writeParcelable(entry.getValue(), flags);
+            }
+        } else {
+            out.writeInt(0);
+        }
     }
 
     @NonNull
@@ -83,6 +111,40 @@
     @NonNull
     public String toString() {
         StringBuilder sb = new StringBuilder();
+        if (datagramStats != null) {
+            sb.append(" ====== SatelliteSessionStatsWrapper Info =============");
+            for (Map.Entry<Integer, SatelliteSessionStats> entry : datagramStats.entrySet()) {
+                Integer key = entry.getKey();
+                SatelliteSessionStats value = entry.getValue();
+                sb.append("\n");
+                sb.append("Key:");
+                sb.append(key);
+                sb.append(", SatelliteSessionStats:[");
+                value.getPrintableCounters(sb);
+                sb.append(",");
+                sb.append(" LatencyOfSuccessfulUserMessages:");
+                sb.append(value.mLatencyOfSuccessfulUserMessages);
+                sb.append(",");
+                sb.append(" mMaxLatency:");
+                sb.append(value.mMaxLatency);
+                sb.append(",");
+                sb.append(" mLastMessageLatency:");
+                sb.append(value.mLastMessageLatency);
+                sb.append("]");
+                sb.append("\n");
+            }
+            sb.append(" ============== ================== ===============");
+            sb.append("\n");
+            sb.append("\n");
+        } else {
+            sb.append("\n");
+            getPrintableCounters(sb);
+        }
+        sb.append("\n");
+        return sb.toString();
+    }
+
+    private void getPrintableCounters(StringBuilder sb) {
         sb.append("countOfSuccessfulUserMessages:");
         sb.append(mCountOfSuccessfulUserMessages);
         sb.append(",");
@@ -101,7 +163,6 @@
 
         sb.append("countOfUserMessagesInQueueToBeSent:");
         sb.append(mCountOfUserMessagesInQueueToBeSent);
-        return sb.toString();
     }
 
     @Override
@@ -110,49 +171,176 @@
         if (o == null || getClass() != o.getClass()) return false;
         SatelliteSessionStats that = (SatelliteSessionStats) o;
         return mCountOfSuccessfulUserMessages == that.mCountOfSuccessfulUserMessages
+                && mLatencyOfSuccessfulUserMessages == that.mLatencyOfSuccessfulUserMessages
                 && mCountOfUnsuccessfulUserMessages == that.mCountOfUnsuccessfulUserMessages
                 && mCountOfTimedOutUserMessagesWaitingForConnection
                 == that.mCountOfTimedOutUserMessagesWaitingForConnection
                 && mCountOfTimedOutUserMessagesWaitingForAck
                 == that.mCountOfTimedOutUserMessagesWaitingForAck
-                && mCountOfUserMessagesInQueueToBeSent
-                == that.mCountOfUserMessagesInQueueToBeSent;
+                && mCountOfUserMessagesInQueueToBeSent == that.mCountOfUserMessagesInQueueToBeSent;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mCountOfSuccessfulUserMessages, mCountOfUnsuccessfulUserMessages,
-                mCountOfTimedOutUserMessagesWaitingForConnection,
-                mCountOfTimedOutUserMessagesWaitingForAck,
-                mCountOfUserMessagesInQueueToBeSent);
+        return Objects.hash(mCountOfSuccessfulUserMessages, mLatencyOfSuccessfulUserMessages,
+                mCountOfUnsuccessfulUserMessages, mCountOfTimedOutUserMessagesWaitingForConnection,
+                mCountOfTimedOutUserMessagesWaitingForAck, mCountOfUserMessagesInQueueToBeSent);
     }
 
     public int getCountOfSuccessfulUserMessages() {
         return mCountOfSuccessfulUserMessages;
     }
 
+    public void incrementSuccessfulUserMessageCount() {
+        mCountOfSuccessfulUserMessages++;
+    }
+
     public int getCountOfUnsuccessfulUserMessages() {
         return mCountOfUnsuccessfulUserMessages;
     }
 
+    public void incrementUnsuccessfulUserMessageCount() {
+        mCountOfUnsuccessfulUserMessages++;
+    }
+
     public int getCountOfTimedOutUserMessagesWaitingForConnection() {
         return mCountOfTimedOutUserMessagesWaitingForConnection;
     }
 
+    public void incrementTimedOutUserMessagesWaitingForConnection() {
+        mCountOfTimedOutUserMessagesWaitingForConnection++;
+    }
+
     public int getCountOfTimedOutUserMessagesWaitingForAck() {
         return mCountOfTimedOutUserMessagesWaitingForAck;
     }
 
+    public void incrementTimedOutUserMessagesWaitingForAck() {
+        mCountOfTimedOutUserMessagesWaitingForAck++;
+    }
+
     public int getCountOfUserMessagesInQueueToBeSent() {
         return mCountOfUserMessagesInQueueToBeSent;
     }
 
+    public long getLatencyOfAllSuccessfulUserMessages() {
+        return mLatencyOfSuccessfulUserMessages;
+    }
+
+    public void updateLatencyOfAllSuccessfulUserMessages(long messageLatency) {
+        mLatencyOfSuccessfulUserMessages += messageLatency;
+    }
+
+    public void recordSuccessfulOutgoingDatagramStats(
+            @SatelliteManager.DatagramType int datagramType, long latency) {
+        try {
+            datagramStats.putIfAbsent(datagramType, new SatelliteSessionStats.Builder().build());
+            SatelliteSessionStats data = datagramStats.get(datagramType);
+            data.incrementSuccessfulUserMessageCount();
+            if (data.mMaxLatency < latency) {
+                data.mMaxLatency = latency;
+            }
+            data.mLastMessageLatency = latency;
+            data.updateLatencyOfAllSuccessfulUserMessages(latency);
+        } catch (Exception e) {
+            Log.e("SatelliteSessionStats",
+                    "Error while recordSuccessfulOutgoingDatagramStats: " + e.getMessage());
+        }
+    }
+
+    public int getCountOfSuccessfulOutgoingDatagram(
+            @SatelliteManager.DatagramType int datagramType) {
+        SatelliteSessionStats data = datagramStats.getOrDefault(datagramType,
+                new SatelliteSessionStats());
+        return data.getCountOfSuccessfulUserMessages();
+    }
+
+    public long getMaxLatency() {
+        return this.mMaxLatency;
+    }
+
+    public Long getLatencyOfAllSuccessfulUserMessages(
+            @SatelliteManager.DatagramType int datagramType) {
+        SatelliteSessionStats data = datagramStats.getOrDefault(datagramType,
+                new SatelliteSessionStats());
+        return data.getLatencyOfAllSuccessfulUserMessages();
+    }
+
+
+    public long getLastMessageLatency() {
+        return this.mLastMessageLatency;
+    }
+
+    public void addCountOfUnsuccessfulUserMessages(@SatelliteManager.DatagramType int datagramType,
+            @SatelliteManager.SatelliteResult int resultCode) {
+        try {
+            datagramStats.putIfAbsent(datagramType, new SatelliteSessionStats.Builder().build());
+            SatelliteSessionStats data = datagramStats.get(datagramType);
+            data.incrementUnsuccessfulUserMessageCount();
+            if (resultCode == SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE) {
+                data.incrementTimedOutUserMessagesWaitingForConnection();
+            } else if (resultCode == SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT) {
+                data.incrementTimedOutUserMessagesWaitingForAck();
+            }
+        } catch (Exception e) {
+            Log.e("SatelliteSessionStats",
+                    "Error while addCountOfUnsuccessfulUserMessages: " + e.getMessage());
+        }
+    }
+
+    public int getCountOfUnsuccessfulUserMessages(@SatelliteManager.DatagramType int datagramType) {
+        SatelliteSessionStats data = datagramStats.get(datagramType);
+        return data.getCountOfUnsuccessfulUserMessages();
+    }
+
+    public int getCountOfTimedOutUserMessagesWaitingForConnection(
+            @SatelliteManager.DatagramType int datagramType) {
+        SatelliteSessionStats data = datagramStats.get(datagramType);
+        return data.getCountOfTimedOutUserMessagesWaitingForConnection();
+    }
+
+    public int getCountOfTimedOutUserMessagesWaitingForAck(
+            @SatelliteManager.DatagramType int datagramType) {
+        SatelliteSessionStats data = datagramStats.get(datagramType);
+        return data.getCountOfTimedOutUserMessagesWaitingForAck();
+    }
+
+    public int getCountOfUserMessagesInQueueToBeSent(
+            @SatelliteManager.DatagramType int datagramType) {
+        SatelliteSessionStats data = datagramStats.get(datagramType);
+        return data.getCountOfUserMessagesInQueueToBeSent();
+    }
+
+    public void clear() {
+        datagramStats.clear();
+    }
+
+    public Map<Integer, SatelliteSessionStats> getSatelliteSessionStats() {
+        return datagramStats;
+    }
+
+    public void setSatelliteSessionStats(Map<Integer, SatelliteSessionStats> sessionStats) {
+        this.datagramStats = sessionStats;
+    }
+
     private void readFromParcel(Parcel in) {
         mCountOfSuccessfulUserMessages = in.readInt();
         mCountOfUnsuccessfulUserMessages = in.readInt();
         mCountOfTimedOutUserMessagesWaitingForConnection = in.readInt();
         mCountOfTimedOutUserMessagesWaitingForAck = in.readInt();
         mCountOfUserMessagesInQueueToBeSent = in.readInt();
+        mLatencyOfSuccessfulUserMessages = in.readLong();
+        mMaxLatency = in.readLong();
+        mLastMessageLatency = in.readLong();
+
+        int size = in.readInt();
+        datagramStats = new HashMap<>();
+        for (int i = 0; i < size; i++) {
+            Integer key = in.readInt();
+            SatelliteSessionStats value = in.readParcelable(
+                    SatelliteSessionStats.class.getClassLoader());
+            datagramStats.put(key, value);
+        }
     }
 
     /**
@@ -164,7 +352,10 @@
         private int mCountOfTimedOutUserMessagesWaitingForConnection;
         private int mCountOfTimedOutUserMessagesWaitingForAck;
         private int mCountOfUserMessagesInQueueToBeSent;
+        private long mLatencyOfSuccessfulUserMessages;
 
+        private long mMaxLatency;
+        private long mLastMessageLatency;
         /**
          * Sets countOfSuccessfulUserMessages value of {@link SatelliteSessionStats}
          * and then returns the Builder class.
@@ -215,10 +406,28 @@
             return this;
         }
 
+        @NonNull
+        public Builder setLatencyOfSuccessfulUserMessages(long latency) {
+            mLatencyOfSuccessfulUserMessages = latency;
+            return this;
+        }
+
+        @NonNull
+        public Builder setMaxLatency(long maxLatency) {
+            mMaxLatency = maxLatency;
+            return this;
+        }
+
+        @NonNull
+        public Builder setLastLatency(long lastLatency) {
+            mLastMessageLatency = lastLatency;
+            return this;
+        }
+
         /** Returns SatelliteSessionStats object. */
         @NonNull
         public SatelliteSessionStats build() {
             return new SatelliteSessionStats(this);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
index dbe5ddd..8427057 100644
--- a/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
@@ -19,6 +19,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -37,7 +38,8 @@
  *
  * @hide
  */
-@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
 public final class SatelliteSubscriberInfo implements Parcelable {
     /** provision subscriberId */
     @NonNull
@@ -50,10 +52,8 @@
     private int mSubId;
 
     /** SubscriberId format is the ICCID. */
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public static final int ICCID = 0;
     /** SubscriberId format is the 6 digit of IMSI + MSISDN. */
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public static final int IMSI_MSISDN = 1;
 
     /** Type of subscriber id */
@@ -70,6 +70,9 @@
         readFromParcel(in);
     }
 
+    /**
+     * @hide
+     */
     public SatelliteSubscriberInfo(@NonNull Builder builder) {
         this.mSubscriberId = builder.mSubscriberId;
         this.mCarrierId = builder.mCarrierId;
@@ -80,11 +83,8 @@
 
     /**
      * Builder class for constructing SatelliteSubscriberInfo objects
-     *
-     * @hide
      */
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
-    public static class Builder {
+    public static final class Builder {
         @NonNull private String mSubscriberId;
         private int mCarrierId;
         @NonNull
@@ -95,17 +95,15 @@
 
         /**
          * Set the SubscriberId and returns the Builder class.
-         *
-         * @hide
          */
-        public Builder setSubscriberId(String subscriberId) {
+        @NonNull
+        public Builder setSubscriberId(@NonNull String subscriberId) {
             mSubscriberId = subscriberId;
             return this;
         }
 
         /**
          * Set the CarrierId and returns the Builder class.
-         * @hide
          */
         @NonNull
         public Builder setCarrierId(int carrierId) {
@@ -114,18 +112,19 @@
         }
 
         /**
-         * Set the niddApn and returns the Builder class.
-         * @hide
+         * Set NIDD (Non IP Data) APN can be used for carrier roaming to satellite attachment
+         * and returns the Builder class.
+         *
+         * Refer specification 3GPP TS 23.501 V19.1.0 section 5.31.5
          */
         @NonNull
-        public Builder setNiddApn(String niddApn) {
+        public Builder setNiddApn(@NonNull String niddApn) {
             mNiddApn = niddApn;
             return this;
         }
 
         /**
          * Set the subId and returns the Builder class.
-         * @hide
          */
         @NonNull
         public Builder setSubId(int subId) {
@@ -135,7 +134,6 @@
 
         /**
          * Set the SubscriberIdType and returns the Builder class.
-         * @hide
          */
         @NonNull
         public Builder setSubscriberIdType(@SubscriberIdType int subscriberIdType) {
@@ -145,7 +143,6 @@
 
         /**
          * Returns SatelliteSubscriberInfo object.
-         * @hide
          */
         @NonNull
         public SatelliteSubscriberInfo build() {
@@ -153,11 +150,7 @@
         }
     }
 
-    /**
-     * @hide
-     */
     @Override
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeString(mSubscriberId);
         out.writeInt(mCarrierId);
@@ -166,7 +159,7 @@
         out.writeInt(mSubscriberIdType);
     }
 
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+
     public static final @android.annotation.NonNull Creator<SatelliteSubscriberInfo> CREATOR =
             new Creator<SatelliteSubscriberInfo>() {
                 @Override
@@ -180,56 +173,45 @@
                 }
             };
 
-    /**
-     * @hide
-     */
     @Override
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public int describeContents() {
         return 0;
     }
 
     /**
-     * @return provision subscriberId.
-     * @hide
+     * Return subscriberId which is used to register with satellite gateway service
+     * during provisioning.
      */
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    @NonNull
     public String getSubscriberId() {
         return mSubscriberId;
     }
 
     /**
-     * @return carrierId.
-     * @hide
+     * Return carrierId of the subscription used for provisioning.
      */
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public int getCarrierId() {
         return mCarrierId;
     }
 
     /**
-     * @return niddApn.
-     * @hide
+     * Return the NIDD(Non IP Data) APN which is used for carrier roaming to satellite attachment.
      */
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    @NonNull
     public String getNiddApn() {
         return mNiddApn;
     }
 
     /**
-     * @return subId.
-     * @hide
+     * Return the subscriptionId of the subscription which is used for satellite attachment.
      */
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public int getSubId() {
         return mSubId;
     }
 
     /**
-     * @return subscriberIdType.
-     * @hide
+     * Return the type of subscriberId used for provisioning.
      */
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public @SubscriberIdType int getSubscriberIdType() {
         return mSubscriberIdType;
     }
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java
index 08ef3f2..fb4f89d 100644
--- a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java
@@ -18,6 +18,7 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -30,49 +31,49 @@
  *
  * @hide
  */
-@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
-public class SatelliteSubscriberProvisionStatus implements Parcelable {
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+public final class SatelliteSubscriberProvisionStatus implements Parcelable {
     private SatelliteSubscriberInfo mSubscriberInfo;
     /** {@code true} mean the satellite subscriber is provisioned, {@code false} otherwise. */
-    private boolean mProvisionStatus;
+    private boolean mProvisioned;
 
+    /**
+     * @hide
+     */
     public SatelliteSubscriberProvisionStatus(@NonNull Builder builder) {
         mSubscriberInfo = builder.mSubscriberInfo;
-        mProvisionStatus = builder.mProvisionStatus;
+        mProvisioned = builder.mProvisioned;
     }
 
     /**
      * Builder class for constructing SatelliteSubscriberProvisionStatus objects
-     *
-     * @hide
      */
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
-    public static class Builder {
+    public static final class Builder {
         private SatelliteSubscriberInfo mSubscriberInfo;
-        private boolean mProvisionStatus;
+        private boolean mProvisioned;
 
         /**
          * Set the SatelliteSubscriberInfo and returns the Builder class.
-         * @hide
          */
-        public Builder setSatelliteSubscriberInfo(SatelliteSubscriberInfo satelliteSubscriberInfo) {
+        @NonNull
+        public Builder setSatelliteSubscriberInfo(
+                @NonNull SatelliteSubscriberInfo satelliteSubscriberInfo) {
             mSubscriberInfo = satelliteSubscriberInfo;
             return this;
         }
 
         /**
          * Set the SatelliteSubscriberInfo's provisionStatus and returns the Builder class.
-         * @hide
          */
         @NonNull
-        public Builder setProvisionStatus(boolean provisionStatus) {
-            mProvisionStatus = provisionStatus;
+        public Builder setProvisioned(boolean provisioned) {
+            mProvisioned = provisioned;
             return this;
         }
 
         /**
          * Returns SatelliteSubscriberProvisionStatus object.
-         * @hide
          */
         @NonNull
         public SatelliteSubscriberProvisionStatus build() {
@@ -84,17 +85,12 @@
         readFromParcel(in);
     }
 
-    /**
-     * @hide
-     */
     @Override
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeParcelable(mSubscriberInfo, flags);
-        out.writeBoolean(mProvisionStatus);
+        out.writeBoolean(mProvisioned);
     }
 
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public static final @android.annotation.NonNull Creator<SatelliteSubscriberProvisionStatus>
             CREATOR =
             new Creator<SatelliteSubscriberProvisionStatus>() {
@@ -109,11 +105,7 @@
                 }
             };
 
-    /**
-     * @hide
-     */
     @Override
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public int describeContents() {
         return 0;
     }
@@ -121,9 +113,7 @@
     /**
      * SatelliteSubscriberInfo that has a provisioning state.
      * @return SatelliteSubscriberInfo.
-     * @hide
      */
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public @NonNull SatelliteSubscriberInfo getSatelliteSubscriberInfo() {
         return mSubscriberInfo;
     }
@@ -131,11 +121,9 @@
     /**
      * SatelliteSubscriberInfo's provisioning state.
      * @return {@code true} means provisioning. {@code false} means deprovisioning.
-     * @hide
      */
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
-    public @NonNull  boolean getProvisionStatus() {
-        return mProvisionStatus;
+    public boolean isProvisioned() {
+        return mProvisioned;
     }
 
     @NonNull
@@ -148,13 +136,13 @@
         sb.append(",");
 
         sb.append("ProvisionStatus:");
-        sb.append(mProvisionStatus);
+        sb.append(mProvisioned);
         return sb.toString();
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mSubscriberInfo, mProvisionStatus);
+        return Objects.hash(mSubscriberInfo, mProvisioned);
     }
 
     @Override
@@ -163,12 +151,12 @@
         if (!(o instanceof SatelliteSubscriberProvisionStatus)) return false;
         SatelliteSubscriberProvisionStatus that = (SatelliteSubscriberProvisionStatus) o;
         return Objects.equals(mSubscriberInfo, that.mSubscriberInfo)
-                && mProvisionStatus == that.mProvisionStatus;
+                && mProvisioned == that.mProvisioned;
     }
 
     private void readFromParcel(Parcel in) {
         mSubscriberInfo = in.readParcelable(SatelliteSubscriberInfo.class.getClassLoader(),
                 SatelliteSubscriberInfo.class);
-        mProvisionStatus = in.readBoolean();
+        mProvisioned = in.readBoolean();
     }
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java b/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java
index 2ef19f8..1897a9b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java
@@ -16,10 +16,14 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.Objects;
 
 /**
@@ -28,6 +32,8 @@
  *
  * @hide
  */
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
 public final class SatelliteSubscriptionInfo implements Parcelable {
     /**
      * The ICC ID used for satellite attachment.
@@ -39,6 +45,9 @@
      */
     @NonNull private final String mNiddApn;
 
+    /**
+     * @hide
+     */
     public SatelliteSubscriptionInfo(@NonNull String iccId, @NonNull String niddApn) {
         mIccId = iccId;
         mNiddApn = niddApn;
@@ -60,7 +69,8 @@
         dest.writeString8(mNiddApn);
     }
 
-    @NonNull public static final Creator<SatelliteSubscriptionInfo> CREATOR = new Creator<>() {
+    @NonNull
+    public static final Creator<SatelliteSubscriptionInfo> CREATOR = new Creator<>() {
         @Override
         public SatelliteSubscriptionInfo createFromParcel(Parcel in) {
             return new SatelliteSubscriptionInfo(in);
@@ -94,11 +104,17 @@
         return Objects.hash(getIccId(), getNiddApn());
     }
 
+    /**
+     * Get ICC ID used for satellite attachment.
+     */
     @NonNull
     public String getIccId() {
         return mIccId;
     }
 
+    /**
+     * Get the NIDD(Non IP Data) APN to be used for carrier roaming to satellite attachment.
+     */
     @NonNull
     public String getNiddApn() {
         return mNiddApn;
diff --git a/telephony/java/android/telephony/satellite/SatelliteSupportedStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteSupportedStateCallback.java
index 7e19bd1..5487eb6 100644
--- a/telephony/java/android/telephony/satellite/SatelliteSupportedStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSupportedStateCallback.java
@@ -17,6 +17,7 @@
 package android.telephony.satellite;
 
 import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
 
 import com.android.internal.telephony.flags.Flags;
 
@@ -25,16 +26,14 @@
  *
  * @hide
  */
-@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
 public interface SatelliteSupportedStateCallback {
     /**
      * Called when satellite supported state changes.
      *
      * @param supported The new supported state. {@code true} means satellite is supported,
      * {@code false} means satellite is not supported.
-     *
-     * @hide
      */
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onSatelliteSupportedStateChanged(boolean supported);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index 046ae5f..b5dfb63 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -58,12 +58,11 @@
      * @param state The new send datagram transfer state.
      * @param sendPendingCount The number of datagrams that are currently being sent.
      * @param errorCode If datagram transfer failed, the reason for failure.
-     *
-     * @hide
      */
-    void onSendDatagramStateChanged(@SatelliteManager.DatagramType int datagramType,
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
+    default void onSendDatagramStateChanged(@SatelliteManager.DatagramType int datagramType,
             @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
-            @SatelliteManager.SatelliteResult int errorCode);
+            @SatelliteManager.SatelliteResult int errorCode) {}
     /**
      * Called when satellite datagram receive state changed.
      *
@@ -80,8 +79,7 @@
      * Called when framework receives a request to send a datagram.
      *
      * @param datagramType The type of the requested datagram.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     default void onSendDatagramRequested(@SatelliteManager.DatagramType int datagramType) {}
 }
diff --git a/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java b/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java
new file mode 100644
index 0000000..d896554
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java
@@ -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 android.telephony.satellite;
+
+/**
+ * A callback class for selected satellite subscription changed events.
+ *
+ * @hide
+ */
+public interface SelectedNbIotSatelliteSubscriptionCallback {
+    /**
+     * Called when the selected satellite subscription has changed.
+     *
+     * @param selectedSubId The new satellite subscription id.
+     */
+    void onSelectedNbIotSatelliteSubscriptionChanged(int selectedSubId);
+}
diff --git a/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java b/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java
index 8a5e7f2..61e1e5f 100644
--- a/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java
+++ b/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java
@@ -16,17 +16,26 @@
 
 package android.telephony.satellite;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.IntArray;
 
+import com.android.internal.telephony.flags.Flags;
+
+import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
 
 /**
  * @hide
  */
+@SystemApi
+@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
 public final class SystemSelectionSpecifier implements Parcelable {
 
     /** Network plmn associated with channel information. */
@@ -39,16 +48,26 @@
      * The radio channels to scan as defined in 3GPP TS 25.101 and 36.101.
      * Maximum length of the vector is 32.
      */
-    @NonNull private IntArray mEarfcs;
+    @NonNull private IntArray mEarfcns;
+
+    /* The list of satellites configured for the current location */
+    @Nullable
+    private SatelliteInfo[] mSatelliteInfos;
+
+    /* The list of tag IDs associated with the current location */
+    @Nullable private IntArray mTagIds;
 
     /**
      * @hide
      */
     public SystemSelectionSpecifier(@NonNull String mccmnc, @NonNull IntArray bands,
-            @NonNull IntArray earfcs) {
+            @NonNull IntArray earfcns, @Nullable SatelliteInfo[] satelliteInfos,
+            @Nullable IntArray tagIds) {
         mMccMnc = mccmnc;
         mBands = bands;
-        mEarfcs = earfcs;
+        mEarfcns = earfcns;
+        mSatelliteInfos = satelliteInfos;
+        mTagIds = tagIds;
     }
 
     private SystemSelectionSpecifier(Parcel in) {
@@ -74,10 +93,21 @@
             out.writeInt(0);
         }
 
-        if (mEarfcs != null && mEarfcs.size() > 0) {
-            out.writeInt(mEarfcs.size());
-            for (int i = 0; i < mEarfcs.size(); i++) {
-                out.writeInt(mEarfcs.get(i));
+        if (mEarfcns != null && mEarfcns.size() > 0) {
+            out.writeInt(mEarfcns.size());
+            for (int i = 0; i < mEarfcns.size(); i++) {
+                out.writeInt(mEarfcns.get(i));
+            }
+        } else {
+            out.writeInt(0);
+        }
+
+        out.writeTypedArray(mSatelliteInfos, flags);
+
+        if (mTagIds != null) {
+            out.writeInt(mTagIds.size());
+            for (int i = 0; i < mTagIds.size(); i++) {
+                out.writeInt(mTagIds.get(i));
             }
         } else {
             out.writeInt(0);
@@ -115,14 +145,35 @@
         }
 
         sb.append("earfcs:");
-        if (mEarfcs != null && mEarfcs.size() > 0) {
-            for (int i = 0; i < mEarfcs.size(); i++) {
-                sb.append(mEarfcs.get(i));
+        if (mEarfcns != null && mEarfcns.size() > 0) {
+            for (int i = 0; i < mEarfcns.size(); i++) {
+                sb.append(mEarfcns.get(i));
                 sb.append(",");
             }
         } else {
             sb.append("none");
         }
+
+        sb.append("mSatelliteInfos:");
+        if (mSatelliteInfos != null && mSatelliteInfos.length > 0) {
+            for (SatelliteInfo satelliteInfo : mSatelliteInfos) {
+                sb.append(satelliteInfo);
+                sb.append(",");
+            }
+        } else {
+            sb.append("none");
+        }
+
+        sb.append("mTagIds:");
+        if (mTagIds != null && mTagIds.size() > 0) {
+            for (int i = 0; i < mTagIds.size(); i++) {
+                sb.append(mTagIds.get(i));
+                sb.append(",");
+            }
+        } else {
+            sb.append("none");
+        }
+
         return sb.toString();
     }
 
@@ -133,24 +184,52 @@
         SystemSelectionSpecifier that = (SystemSelectionSpecifier) o;
         return Objects.equals(mMccMnc, that.mMccMnc)
                 && Objects.equals(mBands, that.mBands)
-                && Objects.equals(mEarfcs, that.mEarfcs);
+                && Objects.equals(mEarfcns, that.mEarfcns)
+                && (mSatelliteInfos == null ? that.mSatelliteInfos == null : Arrays.equals(
+                mSatelliteInfos, that.mSatelliteInfos))
+                && Objects.equals(mTagIds, that.mTagIds);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mMccMnc, mBands, mEarfcs);
+        return Objects.hash(mMccMnc, mBands, mEarfcns);
     }
 
+    /** Return network plmn associated with channel information. */
     @NonNull public String getMccMnc() {
         return mMccMnc;
     }
 
-    @NonNull public IntArray getBands() {
-        return mBands;
+    /**
+     * Return the frequency bands to scan.
+     * Maximum length of the vector is 8.
+     */
+    @NonNull public int[] getBands() {
+        return mBands.toArray();
     }
 
-    @NonNull public IntArray getEarfcs() {
-        return mEarfcs;
+    /**
+     * Return the radio channels to scan as defined in 3GPP TS 25.101 and 36.101.
+     * Maximum length of the vector is 32.
+     */
+    @NonNull public int[] getEarfcns() {
+        return mEarfcns.toArray();
+    }
+
+    /** Return the list of satellites configured for the current location. */
+    @NonNull
+    public List<SatelliteInfo> getSatelliteInfos() {
+        return Arrays.stream(mSatelliteInfos).toList();
+    }
+
+    /**
+     * Return the list of tag IDs associated with the current location
+     * Tag Ids are generic IDs an OEM can configure. Each tag ID can map to a region which can be
+     * used by OEM to identify proprietary configuration for that region.
+     */
+    @NonNull
+    public int[] getTagIds() {
+        return mTagIds.toArray();
     }
 
     private void readFromParcel(Parcel in) {
@@ -164,11 +243,20 @@
             }
         }
 
-        mEarfcs = new IntArray();
-        int numEarfcs = in.readInt();
-        if (numEarfcs > 0) {
-            for (int i = 0; i < numEarfcs; i++) {
-                mEarfcs.add(in.readInt());
+        mEarfcns = new IntArray();
+        int numEarfcns = in.readInt();
+        if (numEarfcns > 0) {
+            for (int i = 0; i < numEarfcns; i++) {
+                mEarfcns.add(in.readInt());
+            }
+        }
+
+        mSatelliteInfos = in.createTypedArray(SatelliteInfo.CREATOR);
+
+        int numTagIds = in.readInt();
+        if (numTagIds > 0) {
+            for (int i = 0; i < numTagIds; i++) {
+                mTagIds.add(in.readInt());
             }
         }
     }
diff --git a/telephony/java/android/telephony/satellite/stub/EarfcnRange.aidl b/telephony/java/android/telephony/satellite/stub/EarfcnRange.aidl
new file mode 100644
index 0000000..6e9d19e
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/EarfcnRange.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.telephony.satellite.stub;
+
+/**
+ * @hide
+ */
+parcelable EarfcnRange {
+    /**
+     * The start frequency of the earfcn range and is inclusive in the range
+     */
+    int startEarfcn;
+
+    /**
+     * The end frequency of the earfcn range and is inclusive in the range.
+     */
+    int endEarfcn;
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteInfo.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteInfo.aidl
new file mode 100644
index 0000000..312bd8f
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteInfo.aidl
@@ -0,0 +1,52 @@
+/*
+ * 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.telephony.satellite.stub;
+
+import android.telephony.satellite.stub.UUID;
+import android.telephony.satellite.stub.SatellitePosition;
+import android.telephony.satellite.stub.EarfcnRange;
+
+/**
+ * @hide
+ */
+parcelable SatelliteInfo {
+    /**
+     * Unique identification number for the satellite.
+     * This ID is used to distinguish between different satellites in the network.
+     */
+    UUID id;
+
+    /**
+     * Position information of a geostationary satellite.
+     * This includes the longitude and altitude of the satellite.
+     * If the SatellitePosition is invalid,
+     * longitudeDegree and altitudeKm will be represented as DOUBLE.NaN.
+     */
+    SatellitePosition position;
+
+    /**
+     * The frequency bands to scan.
+     * Bands will be filled only if the whole band is needed.
+     * Maximum length of the vector is 8.
+     */
+    int[] bands;
+
+    /**
+     * The supported frequency ranges. Earfcn ranges and earfcns won't overlap.
+     */
+    EarfcnRange[] earfcnRanges;
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatellitePosition.aidl b/telephony/java/android/telephony/satellite/stub/SatellitePosition.aidl
new file mode 100644
index 0000000..e87b26c
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/SatellitePosition.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.telephony.satellite.stub;
+
+/**
+ * @hide
+ */
+parcelable SatellitePosition {
+    /**
+     * The longitude of the satellite in degrees, ranging from -180 to 180 degrees
+     */
+    double longitudeDegree;
+
+    /**
+     * The distance from the center of the earth to the satellite, measured in kilometers
+     */
+    double altitudeKm;
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SystemSelectionSpecifier.aidl b/telephony/java/android/telephony/satellite/stub/SystemSelectionSpecifier.aidl
index 22240f6..3aff4cb 100644
--- a/telephony/java/android/telephony/satellite/stub/SystemSelectionSpecifier.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SystemSelectionSpecifier.aidl
@@ -16,6 +16,8 @@
 
 package android.telephony.satellite.stub;
 
+import android.telephony.satellite.stub.SatelliteInfo;
+
 /**
  * {@hide}
  */
@@ -24,15 +26,26 @@
     String mMccMnc;
 
     /**
-     * The frequency bands to scan. Bands and earfcns won't overlap.
+     * The frequency bands to scan.
      * Bands will be filled only if the whole band is needed.
      * Maximum length of the vector is 8.
+     * The values are populated from the mBands array within the SatelliteInfo[] array, which is
+     * included in the SystemSelectionSpecifier, for backward compatibility.
      */
     int[] mBands;
 
     /**
      * The radio channels to scan as defined in 3GPP TS 25.101 and 36.101.
+     * The values are populated from the earfcns defined in the EarfcnRange[] array inside
+     * SatelliteInfo[], which is included in the SystemSelectionSpecifier, for backward
+     * compatibility.
      * Maximum length of the vector is 32.
      */
     int[] mEarfcs;
+
+    /* The list of satellites configured for the current location */
+    SatelliteInfo[] satelliteInfos;
+
+    /* The list of tag IDs associated with the current location */
+    int[] tagIds;
 }
diff --git a/telephony/java/android/telephony/satellite/stub/UUID.aidl b/telephony/java/android/telephony/satellite/stub/UUID.aidl
new file mode 100644
index 0000000..004a507
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/UUID.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.telephony.satellite.stub;
+
+/**
+ * @hide
+ */
+parcelable UUID {
+    /*
+     * The most significant 64 bits of this UUID.
+     */
+    long mostSigBits;
+
+    /*
+     * The least significant 64 bits of this UUID.
+     */
+    long leastSigBits;
+}
diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
index 974cc14..71327dc 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -83,6 +83,12 @@
     String getGroupIdLevel1ForSubscriber(int subId, String callingPackage,
             String callingFeatureId);
 
+    /**
+     * Retrieves the Group Identifier Level1 for GSM phones of a subId.
+     */
+    String getGroupIdLevel2ForSubscriber(int subId, String callingPackage,
+            String callingFeatureId);
+
     /** @deprecared Use {@link getIccSerialNumberWithFeature(String, String)} instead */
     @UnsupportedAppUsage
     String getIccSerialNumber(String callingPackage);
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 251fd96..da7669f 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -76,6 +76,7 @@
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteSupportedStateCallback;
 import android.telephony.satellite.ISatelliteModemStateCallback;
+import android.telephony.satellite.ISelectedNbIotSatelliteSubscriptionCallback;
 import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
@@ -3030,6 +3031,30 @@
     void requestSelectedNbIotSatelliteSubscriptionId(in ResultReceiver receiver);
 
     /**
+     * Registers for selected satellite subscription changed event from the satellite service.
+     *
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback to handle the satellite subscription changed event.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    int registerForSelectedNbIotSatelliteSubscriptionChanged(
+            in ISelectedNbIotSatelliteSubscriptionCallback callback);
+
+    /**
+     * Unregisters for selected satellite subscription changed event from the satellite service. If
+     * callback was not registered before, the request will be ignored.
+     *
+     * @param callback The callback that was passed to {@link
+     *     #registerForSelectedNbIotSatelliteSubscriptionChanged(Executor,
+     *     SelectedNbIotSatelliteSubscriptionCallback)}.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void unregisterForSelectedNbIotSatelliteSubscriptionChanged(
+            in ISelectedNbIotSatelliteSubscriptionCallback callback);
+
+    /**
      * Inform whether the device is aligned with the satellite in both real and demo mode.
      *
      * @param isAligned {@true} Device is aligned with the satellite.
@@ -3143,7 +3168,7 @@
      */
     boolean setSatelliteAccessControlOverlayConfigs(in boolean reset, in boolean isAllowed,
             in String s2CellFile, in long locationFreshDurationNanos,
-            in List<String> satelliteCountryCodes);
+            in List<String> satelliteCountryCodes, String satelliteAccessConfigurationFile);
 
     /**
      * This API can be used in only testing to override oem-enabled satellite provision status.
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index b7dd4df..461d157 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -549,6 +549,9 @@
     int RIL_REQUEST_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED = 248;
     int RIL_REQUEST_IS_SECURITY_ALGORITHMS_UPDATED_ENABLED = 249;
     int RIL_REQUEST_GET_SIMULTANEOUS_CALLING_SUPPORT = 250;
+    int RIL_REQUEST_SET_SATELLITE_PLMN = 251;
+    int RIL_REQUEST_SET_SATELLITE_ENABLED_FOR_CARRIER = 252;
+    int RIL_REQUEST_IS_SATELLITE_ENABLED_FOR_CARRIER = 253;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
index bfce3d2..6d818d7 100644
--- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
+++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
@@ -159,7 +159,7 @@
 
     private static BatteryUsageStats buildBatteryUsageStats() {
         final BatteryUsageStats.Builder builder =
-                new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, false, false, 0)
+                new BatteryUsageStats.Builder(new String[]{"FOO"}, false, false, false, 0)
                         .setBatteryCapacity(4000)
                         .setDischargePercentage(20)
                         .setDischargedPowerRange(1000, 2000)
@@ -182,8 +182,7 @@
                             .setTimeInProcessStateMs(UidBatteryConsumer.STATE_BACKGROUND, i * 1000);
             for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
                     componentId++) {
-                consumerBuilder.addConsumedPower(componentId, componentId * 123.0,
-                        BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                consumerBuilder.addConsumedPower(componentId, componentId * 123.0);
                 consumerBuilder.addUsageDurationMillis(componentId, componentId * 1000);
             }
 
diff --git a/tests/BinaryTransparencyHostTest/Android.bp b/tests/BinaryTransparencyHostTest/Android.bp
index e14e5fe..1c8386a 100644
--- a/tests/BinaryTransparencyHostTest/Android.bp
+++ b/tests/BinaryTransparencyHostTest/Android.bp
@@ -31,6 +31,8 @@
     ],
     static_libs: [
         "truth",
+        "flag-junit-host",
+        "android.app.flags-aconfig-java-host",
     ],
     device_common_data: [
         ":BinaryTransparencyTestApp",
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
index 6e5f08a..6d8dbcb 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -24,6 +24,9 @@
 
 import android.platform.test.annotations.LargeTest;
 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.host.HostFlagsValueProvider;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -34,6 +37,7 @@
 import com.android.tradefed.util.CommandStatus;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -49,6 +53,10 @@
     /** Waiting time for the job to be scheduled */
     private static final int JOB_CREATION_MAX_SECONDS = 30;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            HostFlagsValueProvider.createCheckFlagsRule(this::getDevice);
+
     @Before
     public void setUp() throws Exception {
         cancelPendingJob();
@@ -123,6 +131,7 @@
         }
     }
 
+    @RequiresFlagsDisabled(android.app.Flags.FLAG_BACKGROUND_INSTALL_CONTROL_CALLBACK_API)
     @Test
     public void testPreloadUpdateTriggersJobScheduling() throws Exception {
         try {
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
index 8b65efd..685ae9a 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
@@ -45,6 +45,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
index 3382c1e..5f92d7f 100644
--- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
@@ -45,6 +45,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
index e941e79..1b90e99 100644
--- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
@@ -45,6 +45,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
index 4e06dca..ffdbb02 100644
--- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
@@ -45,6 +45,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index 0cadd68..12670cd 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -47,6 +47,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
index f32e8bed..e2ac5a9 100644
--- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
@@ -45,6 +45,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
index 68ae4f1..1a4feb6 100644
--- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
@@ -45,6 +45,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index ec186723..481a8bb 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -45,6 +45,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="test-user-token" value="%TEST_USER%"/>
         <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <!-- Disable AOD -->
+        <option name="run-command" value="settings put secure doze_always_on 0"/>
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
         <option name="run-command" value="settings put system show_touches 1"/>
         <option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt
new file mode 100644
index 0000000..6573c2c
--- /dev/null
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.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.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.tools.traces.parsers.toFlickerComponent
+import com.android.server.wm.flicker.testapp.ActivityOptions
+
+class BottomHalfPipAppHelper(
+    instrumentation: Instrumentation,
+    private val useLaunchingActivity: Boolean = false,
+) : PipAppHelper(
+    instrumentation,
+    appName = ActivityOptions.BottomHalfPip.LABEL,
+    componentNameMatcher = ActivityOptions.BottomHalfPip.COMPONENT
+        .toFlickerComponent()
+) {
+    override val openAppIntent: Intent
+        get() = super.openAppIntent.apply {
+            component = if (useLaunchingActivity) {
+                ActivityOptions.BottomHalfPip.LAUNCHING_APP_COMPONENT
+            } else {
+                ActivityOptions.BottomHalfPip.COMPONENT
+            }
+        }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 6432827..c1c5dc6 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -23,6 +23,7 @@
 import android.graphics.Region
 import android.os.SystemClock
 import android.platform.uiautomatorhelpers.DeviceHelpers
+import android.tools.PlatformConsts
 import android.tools.device.apphelpers.IStandardAppHelper
 import android.tools.helpers.SYSTEMUI_PACKAGE
 import android.tools.traces.parsers.WindowManagerStateHelper
@@ -38,6 +39,7 @@
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.helpers.MotionEventHelper.InputMethod.TOUCH
 import java.time.Duration
+import kotlin.math.abs
 
 /**
  * Wrapper class around App helper classes. This class adds functionality to the apps that the
@@ -163,7 +165,10 @@
             .StateSyncBuilder()
             .withAppTransitionIdle()
             .apply {
-                if (isPip) withPipShown() else withWindowSurfaceDisappeared(innerHelper)
+                if (isPip) withPipShown()
+                else
+                    withWindowSurfaceDisappeared(innerHelper)
+                        .withActivityState(innerHelper, PlatformConsts.STATE_STOPPED)
             }
             .waitForAndVerify()
     }
@@ -218,11 +223,16 @@
         val expectedRect = Rect(displayRect).apply {
             if (toLeft) right -= expectedWidth else left += expectedWidth
         }
-
-        wmHelper
-            .StateSyncBuilder()
+        wmHelper.StateSyncBuilder()
             .withAppTransitionIdle()
-            .withSurfaceVisibleRegion(this, Region(expectedRect))
+            .withSurfaceMatchingVisibleRegion(
+                this,
+                Region(expectedRect),
+                { surfaceRegion, expectedRegion ->
+                    areSnapWindowRegionsMatchingWithinThreshold(
+                        surfaceRegion, expectedRegion, toLeft
+                    )
+                })
             .waitForAndVerify()
     }
 
@@ -456,6 +466,33 @@
     private fun isInDesktopWindowingMode(wmHelper: WindowManagerStateHelper) =
         wmHelper.getWindow(innerHelper)?.windowingMode == WINDOWING_MODE_FREEFORM
 
+    private fun areSnapWindowRegionsMatchingWithinThreshold(
+        surfaceRegion: Region, expectedRegion: Region, toLeft: Boolean
+    ): Boolean {
+        val surfaceBounds = surfaceRegion.bounds
+        val expectedBounds = expectedRegion.bounds
+        // If snapped to left, right bounds will be cut off by the center divider.
+        // Else if snapped to right, the left bounds will be cut off.
+        val leftSideMatching: Boolean
+        val rightSideMatching: Boolean
+        if (toLeft) {
+            leftSideMatching = surfaceBounds.left == expectedBounds.left
+            rightSideMatching =
+                abs(surfaceBounds.right - expectedBounds.right) <=
+                        surfaceBounds.right * SNAP_WINDOW_MAX_THRESHOLD_DIFF
+        } else {
+            leftSideMatching =
+                abs(surfaceBounds.left - expectedBounds.left) <=
+                        surfaceBounds.left * SNAP_WINDOW_MAX_THRESHOLD_DIFF
+            rightSideMatching = surfaceBounds.right == expectedBounds.right
+        }
+
+        return surfaceBounds.top == expectedBounds.top &&
+                surfaceBounds.bottom == expectedBounds.bottom &&
+                leftSideMatching &&
+                rightSideMatching
+    }
+
     private companion object {
         val TIMEOUT: Duration = Duration.ofSeconds(3)
         const val SNAP_RESIZE_DRAG_INSET: Int = 5 // inset to avoid dragging to display edge
@@ -471,5 +508,12 @@
         const val HEADER_EMPTY_VIEW: String = "caption_handle"
         val caption: BySelector
             get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
+        // In DesktopMode, window snap can be done with just a single window. In this case, the
+        // divider tiling between left and right window won't be shown, and hence its states are not
+        // obtainable in test.
+        // As the test should just focus on ensuring window goes to one side of the screen, an
+        // acceptable approach is to ensure snapped window still fills > 95% of either side of the
+        // screen.
+        const val SNAP_WINDOW_MAX_THRESHOLD_DIFF = 0.05
     }
 }
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java
deleted file mode 100644
index eeee7b4..0000000
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.helpers;
-
-import android.annotation.NonNull;
-import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.os.SystemClock;
-import android.view.InputDevice;
-import android.view.InputEvent;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
-
-import androidx.annotation.Nullable;
-
-/**
- * Injects gestures given an {@link Instrumentation} object.
- */
-public class GestureHelper {
-    // Inserted after each motion event injection.
-    private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
-
-    private final UiAutomation mUiAutomation;
-
-    /**
-     * Primary pointer should be cached here for separate release
-     */
-    @Nullable private PointerProperties mPrimaryPtrProp;
-    @Nullable private PointerCoords mPrimaryPtrCoord;
-    private long mPrimaryPtrDownTime;
-
-    /**
-     * A pair of floating point values.
-     */
-    public static class Tuple {
-        public float x;
-        public float y;
-
-        public Tuple(float x, float y) {
-            this.x = x;
-            this.y = y;
-        }
-    }
-
-    public GestureHelper(Instrumentation instrumentation) {
-        mUiAutomation = instrumentation.getUiAutomation();
-    }
-
-    /**
-     * Injects a series of {@link MotionEvent}s to simulate tapping.
-     *
-     * @param point coordinates of pointer to tap
-     * @param times the number of times to tap
-     */
-    public boolean tap(@NonNull Tuple point, int times) throws InterruptedException {
-        PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
-        PointerCoords ptrCoord = getPointerCoord(point.x, point.y, 1, 1);
-
-        for (int i = 0; i <= times; i++) {
-            // If already tapped, inject delay in between movements
-            if (times > 0) {
-                SystemClock.sleep(50L);
-            }
-            if (!primaryPointerDown(ptrProp, ptrCoord, SystemClock.uptimeMillis())) {
-                return false;
-            }
-            // Delay before releasing tap
-            SystemClock.sleep(100L);
-            if (!primaryPointerUp(ptrProp, ptrCoord, SystemClock.uptimeMillis())) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Injects a series of {@link MotionEvent}s to simulate a drag gesture without pointer release.
-     *
-     * Simulates a drag gesture without releasing the primary pointer. The primary pointer info
-     * will be cached for potential release later on by {@code releasePrimaryPointer()}
-     *
-     * @param startPoint initial coordinates of the primary pointer
-     * @param endPoint final coordinates of the primary pointer
-     * @param steps number of steps to take to animate dragging
-     * @return true if gesture is injected successfully
-     */
-    public boolean dragWithoutRelease(@NonNull Tuple startPoint,
-            @NonNull Tuple endPoint, int steps) {
-        PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
-        PointerCoords ptrCoord = getPointerCoord(startPoint.x, startPoint.y, 1, 1);
-
-        PointerProperties[] ptrProps = new PointerProperties[] { ptrProp };
-        PointerCoords[] ptrCoords = new PointerCoords[] { ptrCoord };
-
-        long downTime = SystemClock.uptimeMillis();
-
-        if (!primaryPointerDown(ptrProp, ptrCoord, downTime)) {
-            return false;
-        }
-
-        // cache the primary pointer info for later potential release
-        mPrimaryPtrProp = ptrProp;
-        mPrimaryPtrCoord = ptrCoord;
-        mPrimaryPtrDownTime = downTime;
-
-        return movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint }, downTime, steps);
-    }
-
-    /**
-     * Release primary pointer if previous gesture has cached the primary pointer info.
-     *
-     * @return true if the release was injected successfully
-     */
-    public boolean releasePrimaryPointer() {
-        if (mPrimaryPtrProp != null && mPrimaryPtrCoord != null) {
-            return primaryPointerUp(mPrimaryPtrProp, mPrimaryPtrCoord, mPrimaryPtrDownTime);
-        }
-
-        return false;
-    }
-
-    /**
-     * Injects a series of {@link MotionEvent} objects to simulate a pinch gesture.
-     *
-     * @param startPoint1 initial coordinates of the first pointer
-     * @param startPoint2 initial coordinates of the second pointer
-     * @param endPoint1 final coordinates of the first pointer
-     * @param endPoint2 final coordinates of the second pointer
-     * @param steps number of steps to take to animate pinching
-     * @return true if gesture is injected successfully
-     */
-    public boolean pinch(@NonNull Tuple startPoint1, @NonNull Tuple startPoint2,
-            @NonNull Tuple endPoint1, @NonNull Tuple endPoint2, int steps) {
-        PointerProperties ptrProp1 = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
-        PointerProperties ptrProp2 = getPointerProp(1, MotionEvent.TOOL_TYPE_FINGER);
-
-        PointerCoords ptrCoord1 = getPointerCoord(startPoint1.x, startPoint1.y, 1, 1);
-        PointerCoords ptrCoord2 = getPointerCoord(startPoint2.x, startPoint2.y, 1, 1);
-
-        PointerProperties[] ptrProps = new PointerProperties[] {
-                ptrProp1, ptrProp2
-        };
-
-        PointerCoords[] ptrCoords = new PointerCoords[] {
-                ptrCoord1, ptrCoord2
-        };
-
-        long downTime = SystemClock.uptimeMillis();
-
-        if (!primaryPointerDown(ptrProp1, ptrCoord1, downTime)) {
-            return false;
-        }
-
-        if (!nonPrimaryPointerDown(ptrProps, ptrCoords, downTime, 1)) {
-            return false;
-        }
-
-        if (!movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint1, endPoint2 },
-                downTime, steps)) {
-            return false;
-        }
-
-        if (!nonPrimaryPointerUp(ptrProps, ptrCoords, downTime, 1)) {
-            return false;
-        }
-
-        return primaryPointerUp(ptrProp1, ptrCoord1, downTime);
-    }
-
-    private boolean primaryPointerDown(@NonNull PointerProperties prop,
-            @NonNull PointerCoords coord, long downTime) {
-        MotionEvent event = getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, 1,
-                new PointerProperties[]{ prop }, new PointerCoords[]{ coord });
-
-        return injectEventSync(event);
-    }
-
-    private boolean nonPrimaryPointerDown(@NonNull PointerProperties[] props,
-            @NonNull PointerCoords[] coords, long downTime, int index) {
-        // at least 2 pointers are needed
-        if (props.length != coords.length || coords.length < 2) {
-            return false;
-        }
-
-        long eventTime = SystemClock.uptimeMillis();
-
-        MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_DOWN
-                + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords);
-
-        return injectEventSync(event);
-    }
-
-    private boolean movePointers(@NonNull PointerProperties[] props,
-            @NonNull PointerCoords[] coords, @NonNull Tuple[] endPoints, long downTime, int steps) {
-        // the number of endpoints should be the same as the number of pointers
-        if (props.length != coords.length || coords.length != endPoints.length) {
-            return false;
-        }
-
-        // prevent division by 0 and negative number of steps
-        if (steps < 1) {
-            steps = 1;
-        }
-
-        // save the starting points before updating any pointers
-        Tuple[] startPoints = new Tuple[coords.length];
-
-        for (int i = 0; i < coords.length; i++) {
-            startPoints[i] = new Tuple(coords[i].x, coords[i].y);
-        }
-
-        MotionEvent event;
-        long eventTime;
-
-        for (int i = 0; i < steps; i++) {
-            // inject a delay between movements
-            SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
-
-            // update the coordinates
-            for (int j = 0; j < coords.length; j++) {
-                coords[j].x += (endPoints[j].x - startPoints[j].x) / steps;
-                coords[j].y += (endPoints[j].y - startPoints[j].y) / steps;
-            }
-
-            eventTime = SystemClock.uptimeMillis();
-
-            event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_MOVE,
-                    coords.length, props, coords);
-
-            boolean didInject = injectEventSync(event);
-
-            if (!didInject) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    private boolean primaryPointerUp(@NonNull PointerProperties prop,
-            @NonNull PointerCoords coord, long downTime) {
-        long eventTime = SystemClock.uptimeMillis();
-
-        MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_UP, 1,
-                new PointerProperties[]{ prop }, new PointerCoords[]{ coord });
-
-        return injectEventSync(event);
-    }
-
-    private boolean nonPrimaryPointerUp(@NonNull PointerProperties[] props,
-            @NonNull PointerCoords[] coords, long downTime, int index) {
-        // at least 2 pointers are needed
-        if (props.length != coords.length || coords.length < 2) {
-            return false;
-        }
-
-        long eventTime = SystemClock.uptimeMillis();
-
-        MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_UP
-                + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords);
-
-        return injectEventSync(event);
-    }
-
-    private PointerCoords getPointerCoord(float x, float y, float pressure, float size) {
-        PointerCoords ptrCoord = new PointerCoords();
-        ptrCoord.x = x;
-        ptrCoord.y = y;
-        ptrCoord.pressure = pressure;
-        ptrCoord.size = size;
-        return ptrCoord;
-    }
-
-    private PointerProperties getPointerProp(int id, int toolType) {
-        PointerProperties ptrProp = new PointerProperties();
-        ptrProp.id = id;
-        ptrProp.toolType = toolType;
-        return ptrProp;
-    }
-
-    private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
-            int pointerCount, PointerProperties[] ptrProps, PointerCoords[] ptrCoords) {
-        return MotionEvent.obtain(downTime, eventTime, action, pointerCount,
-                ptrProps, ptrCoords, 0, 0, 1.0f, 1.0f,
-                0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
-    }
-
-    private boolean injectEventSync(InputEvent event) {
-        return mUiAutomation.injectInputEvent(event, true);
-    }
-}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
index fd13d14..d5334cb 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
@@ -21,6 +21,7 @@
 import android.graphics.Region
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.helpers.FIND_TIMEOUT
+import android.tools.helpers.GestureHelper
 import android.tools.helpers.SYSTEMUI_PACKAGE
 import android.tools.traces.component.ComponentNameMatcher
 import android.tools.traces.parsers.WindowManagerStateHelper
@@ -38,7 +39,8 @@
         ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.COMPONENT.toFlickerComponent()
 ) : StandardAppHelper(instr, launcherName, component) {
 
-    private val gestureHelper: GestureHelper = GestureHelper(instrumentation)
+    private val gestureHelper: GestureHelper =
+        GestureHelper(instrumentation)
 
     fun clickRestart(wmHelper: WindowManagerStateHelper) {
         val restartButton =
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 931e4f8..de17bf4 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -18,29 +18,26 @@
 
 import android.app.Instrumentation
 import android.content.Intent
-import android.graphics.Rect
 import android.graphics.Region
 import android.media.session.MediaController
 import android.media.session.MediaSessionManager
-import android.tools.datatypes.coversMoreThan
-import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.apphelpers.BasePipAppHelper
 import android.tools.helpers.FIND_TIMEOUT
 import android.tools.helpers.SYSTEMUI_PACKAGE
 import android.tools.traces.ConditionsFactory
+import android.tools.traces.component.ComponentNameMatcher
 import android.tools.traces.component.IComponentMatcher
 import android.tools.traces.parsers.WindowManagerStateHelper
 import android.tools.traces.parsers.toFlickerComponent
-import android.util.Log
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.testapp.ActivityOptions
 
-open class PipAppHelper(instrumentation: Instrumentation) :
-    StandardAppHelper(
-        instrumentation,
-        ActivityOptions.Pip.LABEL,
-        ActivityOptions.Pip.COMPONENT.toFlickerComponent()
-    ) {
+open class PipAppHelper(
+    instrumentation: Instrumentation,
+    appName: String = ActivityOptions.Pip.LABEL,
+    componentNameMatcher: ComponentNameMatcher = ActivityOptions.Pip.COMPONENT.toFlickerComponent(),
+) : BasePipAppHelper(instrumentation, appName, componentNameMatcher) {
     private val mediaSessionManager: MediaSessionManager
         get() =
             context.getSystemService(MediaSessionManager::class.java)
@@ -52,189 +49,6 @@
                 it.packageName == packageName
             }
 
-    private val gestureHelper: GestureHelper = GestureHelper(instrumentation)
-
-    open fun clickObject(resId: String) {
-        val selector = By.res(packageName, resId)
-        val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object")
-
-        obj.click()
-    }
-
-    /** Drags the PIP window to the provided final coordinates without releasing the pointer. */
-    fun dragPipWindowAwayFromEdgeWithoutRelease(wmHelper: WindowManagerStateHelper, steps: Int) {
-        val initWindowRect = Rect(getWindowRect(wmHelper))
-
-        // initial pointer at the center of the window
-        val initialCoord =
-            GestureHelper.Tuple(
-                initWindowRect.centerX().toFloat(),
-                initWindowRect.centerY().toFloat()
-            )
-
-        // the offset to the right (or left) of the window center to drag the window to
-        val offset = 50
-
-        // the actual final x coordinate with the offset included;
-        // if the pip window is closer to the right edge of the display the offset is negative
-        // otherwise the offset is positive
-        val endX =
-            initWindowRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) -1 else 1)
-        val finalCoord = GestureHelper.Tuple(endX.toFloat(), initWindowRect.centerY().toFloat())
-
-        // drag to the final coordinate
-        gestureHelper.dragWithoutRelease(initialCoord, finalCoord, steps)
-    }
-
-    /**
-     * Releases the primary pointer.
-     *
-     * Injects the release of the primary pointer if the primary pointer info was cached after
-     * another gesture was injected without pointer release.
-     */
-    fun releasePipAfterDragging() {
-        gestureHelper.releasePrimaryPointer()
-    }
-
-    /**
-     * Drags the PIP window away from the screen edge while not crossing the display center.
-     *
-     * @throws IllegalStateException if default display bounds are not available
-     */
-    fun dragPipWindowAwayFromEdge(wmHelper: WindowManagerStateHelper, steps: Int) {
-        val initWindowRect = Rect(getWindowRect(wmHelper))
-
-        // initial pointer at the center of the window
-        val startX = initWindowRect.centerX()
-        val y = initWindowRect.centerY()
-
-        val displayRect =
-            wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
-                ?: throw IllegalStateException("Default display is null")
-
-        // the offset to the right (or left) of the display center to drag the window to
-        val offset = 20
-
-        // the actual final x coordinate with the offset included;
-        // if the pip window is closer to the right edge of the display the offset is positive
-        // otherwise the offset is negative
-        val endX = displayRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) 1 else -1)
-
-        // drag the window to the left but not beyond the center of the display
-        uiDevice.drag(startX, y, endX, y, steps)
-    }
-
-    /**
-     * Returns true if PIP window is closer to the right edge of the display than left.
-     *
-     * @throws IllegalStateException if default display bounds are not available
-     */
-    fun isCloserToRightEdge(wmHelper: WindowManagerStateHelper): Boolean {
-        val windowRect = getWindowRect(wmHelper)
-
-        val displayRect =
-            wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
-                ?: throw IllegalStateException("Default display is null")
-
-        return windowRect.centerX() > displayRect.centerX()
-    }
-
-    /**
-     * Expands the PIP window by using the pinch out gesture.
-     *
-     * @param percent The percentage by which to increase the pip window size.
-     * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f
-     */
-    fun pinchOpenPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) {
-        // the percentage must be between 0.0f and 1.0f
-        if (percent <= 0.0f || percent > 1.0f) {
-            throw IllegalArgumentException("Percent must be between 0.0f and 1.0f")
-        }
-
-        val windowRect = getWindowRect(wmHelper)
-
-        // first pointer's initial x coordinate is halfway between the left edge and the center
-        val initLeftX = (windowRect.centerX() - windowRect.width() / 4).toFloat()
-        // second pointer's initial x coordinate is halfway between the right edge and the center
-        val initRightX = (windowRect.centerX() + windowRect.width() / 4).toFloat()
-
-        // horizontal distance the window should increase by
-        val distIncrease = windowRect.width() * percent
-
-        // final x-coordinates
-        val finalLeftX = initLeftX - (distIncrease / 2)
-        val finalRightX = initRightX + (distIncrease / 2)
-
-        // y-coordinate is the same throughout this animation
-        val yCoord = windowRect.centerY().toFloat()
-
-        var adjustedSteps = MIN_STEPS_TO_ANIMATE
-
-        // if distance per step is at least 1, then we can use the number of steps requested
-        if (distIncrease.toInt() / (steps * 2) >= 1) {
-            adjustedSteps = steps
-        }
-
-        // if the distance per step is less than 1, carry out the animation in two steps
-        gestureHelper.pinch(
-            GestureHelper.Tuple(initLeftX, yCoord),
-            GestureHelper.Tuple(initRightX, yCoord),
-            GestureHelper.Tuple(finalLeftX, yCoord),
-            GestureHelper.Tuple(finalRightX, yCoord),
-            adjustedSteps
-        )
-
-        waitForPipWindowToExpandFrom(wmHelper, Region(windowRect))
-    }
-
-    /**
-     * Minimizes the PIP window by using the pinch in gesture.
-     *
-     * @param percent The percentage by which to decrease the pip window size.
-     * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f
-     */
-    fun pinchInPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) {
-        // the percentage must be between 0.0f and 1.0f
-        if (percent <= 0.0f || percent > 1.0f) {
-            throw IllegalArgumentException("Percent must be between 0.0f and 1.0f")
-        }
-
-        val windowRect = getWindowRect(wmHelper)
-
-        // first pointer's initial x coordinate is halfway between the left edge and the center
-        val initLeftX = (windowRect.centerX() - windowRect.width() / 4).toFloat()
-        // second pointer's initial x coordinate is halfway between the right edge and the center
-        val initRightX = (windowRect.centerX() + windowRect.width() / 4).toFloat()
-
-        // decrease by the distance specified through the percentage
-        val distDecrease = windowRect.width() * percent
-
-        // get the final x-coordinates and make sure they are not passing the center of the window
-        val finalLeftX = Math.min(initLeftX + (distDecrease / 2), windowRect.centerX().toFloat())
-        val finalRightX = Math.max(initRightX - (distDecrease / 2), windowRect.centerX().toFloat())
-
-        // y-coordinate is the same throughout this animation
-        val yCoord = windowRect.centerY().toFloat()
-
-        var adjustedSteps = MIN_STEPS_TO_ANIMATE
-
-        // if distance per step is at least 1, then we can use the number of steps requested
-        if (distDecrease.toInt() / (steps * 2) >= 1) {
-            adjustedSteps = steps
-        }
-
-        // if the distance per step is less than 1, carry out the animation in two steps
-        gestureHelper.pinch(
-            GestureHelper.Tuple(initLeftX, yCoord),
-            GestureHelper.Tuple(initRightX, yCoord),
-            GestureHelper.Tuple(finalLeftX, yCoord),
-            GestureHelper.Tuple(finalRightX, yCoord),
-            adjustedSteps
-        )
-
-        waitForPipWindowToMinimizeFrom(wmHelper, Region(windowRect))
-    }
-
     /**
      * Launches the app through an intent instead of interacting with the launcher and waits until
      * the app window is in PIP mode
@@ -250,18 +64,13 @@
             wmHelper,
             launchedAppComponentMatcherOverride,
             action,
-            stringExtras,
-            waitConditionsBuilder =
-                wmHelper
-                    .StateSyncBuilder()
-                    .add(ConditionsFactory.isWMStateComplete())
-                    .withAppTransitionIdle()
-                    .add(ConditionsFactory.hasPipWindow())
+            stringExtras
         )
 
         wmHelper
             .StateSyncBuilder()
             .withWindowSurfaceAppeared(this)
+            .add(ConditionsFactory.isWMStateComplete())
             .withPipShown()
             .waitForAndVerify()
     }
@@ -336,126 +145,6 @@
         closePipWindow(WindowManagerStateHelper(instrumentation))
     }
 
-    /** Returns the pip window bounds. */
-    fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
-        val windowRegion = wmHelper.getWindowRegion(this)
-        require(!windowRegion.isEmpty) { "Unable to find a PIP window in the current state" }
-        return windowRegion.bounds
-    }
-
-    /** Taps the pip window and dismisses it by clicking on the X button. */
-    open fun closePipWindow(wmHelper: WindowManagerStateHelper) {
-        val windowRect = getWindowRect(wmHelper)
-        uiDevice.click(windowRect.centerX(), windowRect.centerY())
-        // search and interact with the dismiss button
-        val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
-        uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
-        val dismissPipObject =
-            uiDevice.findObject(dismissSelector) ?: error("PIP window dismiss button not found")
-        val dismissButtonBounds = dismissPipObject.visibleBounds
-        uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
-
-        // Wait for animation to complete.
-        wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify()
-    }
-
-    open fun tapPipToShowMenu(wmHelper: WindowManagerStateHelper) {
-        val windowRect = getWindowRect(wmHelper)
-        uiDevice.click(windowRect.centerX(), windowRect.centerY())
-        // search and interact with the dismiss button
-        val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
-        uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
-    }
-
-    /** Close the pip window by pressing the expand button */
-    fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
-        val windowRect = getWindowRect(wmHelper)
-        uiDevice.click(windowRect.centerX(), windowRect.centerY())
-        // search and interact with the expand button
-        val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button")
-        uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT)
-        val expandPipObject =
-            uiDevice.findObject(expandSelector) ?: error("PIP window expand button not found")
-        val expandButtonBounds = expandPipObject.visibleBounds
-        uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY())
-        wmHelper.StateSyncBuilder().withPipGone().withFullScreenApp(this).waitForAndVerify()
-    }
-
-    /** Double click on the PIP window to expand it */
-    fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) {
-        val windowRect = getWindowRect(wmHelper)
-        Log.d(TAG, "First click")
-        uiDevice.click(windowRect.centerX(), windowRect.centerY())
-        Log.d(TAG, "Second click")
-        uiDevice.click(windowRect.centerX(), windowRect.centerY())
-        Log.d(TAG, "Wait for app transition to end")
-        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
-        waitForPipWindowToExpandFrom(wmHelper, Region(windowRect))
-    }
-
-    private fun waitForPipWindowToExpandFrom(
-        wmHelper: WindowManagerStateHelper,
-        windowRect: Region
-    ) {
-        wmHelper
-            .StateSyncBuilder()
-            .add("pipWindowExpanded") {
-                val pipAppWindow =
-                    it.wmState.visibleWindows.firstOrNull { window ->
-                        this.windowMatchesAnyOf(window)
-                    }
-                        ?: return@add false
-                val pipRegion = pipAppWindow.frameRegion
-                return@add pipRegion.coversMoreThan(windowRect)
-            }
-            .waitForAndVerify()
-    }
-
-    private fun waitForPipWindowToMinimizeFrom(
-        wmHelper: WindowManagerStateHelper,
-        windowRect: Region
-    ) {
-        wmHelper
-            .StateSyncBuilder()
-            .add("pipWindowMinimized") {
-                val pipAppWindow =
-                    it.wmState.visibleWindows.firstOrNull { window ->
-                        this.windowMatchesAnyOf(window)
-                    }
-                Log.d(TAG, "window " + pipAppWindow)
-                if (pipAppWindow == null) return@add false
-                val pipRegion = pipAppWindow.frameRegion
-                Log.d(
-                    TAG,
-                    "region " + pipRegion + " covers " + windowRect.coversMoreThan(pipRegion)
-                )
-                return@add windowRect.coversMoreThan(pipRegion)
-            }
-            .waitForAndVerify()
-    }
-
-    /**
-     * Waits until the PIP window snaps horizontally to the provided bounds.
-     *
-     * @param finalBounds the bounds to wait for PIP window to snap to
-     */
-    fun waitForPipToSnapTo(wmHelper: WindowManagerStateHelper, finalBounds: android.graphics.Rect) {
-        wmHelper
-            .StateSyncBuilder()
-            .add("pipWindowSnapped") {
-                val pipAppWindow =
-                    it.wmState.visibleWindows.firstOrNull { window ->
-                        this.windowMatchesAnyOf(window)
-                    }
-                        ?: return@add false
-                val pipRegionBounds = pipAppWindow.frameRegion.bounds
-                return@add pipRegionBounds.left == finalBounds.left &&
-                    pipRegionBounds.right == finalBounds.right
-            }
-            .add(ConditionsFactory.isWMStateComplete())
-            .waitForAndVerify()
-    }
-
     companion object {
         private const val TAG = "PipAppHelper"
         private const val ENTER_PIP_BUTTON_ID = "enter_pip"
@@ -464,8 +153,5 @@
         private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual"
         private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter"
         private const val SOURCE_RECT_HINT = "set_source_rect_hint"
-        // minimum number of steps to take, when animating gestures, needs to be 2
-        // so that there is at least a single intermediate layer that flicker tests can check
-        private const val MIN_STEPS_TO_ANIMATE = 2
     }
-}
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 9ce8e80..7c24a4a 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -347,6 +347,27 @@
                 <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".BottomHalfPipLaunchingActivity"
+            android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity"
+            android:theme="@style/CutoutShortEdges"
+            android:label="BottomHalfPipLaunchingActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".BottomHalfPipActivity"
+            android:resizeableActivity="true"
+            android:supportsPictureInPicture="true"
+            android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity"
+            android:theme="@style/TranslucentTheme"
+            android:label="BottomHalfPipActivity"
+            android:exported="true">
+        </activity>
         <activity android:name=".SplitScreenActivity"
                   android:resizeableActivity="true"
                   android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 47d1137..837d050 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -62,6 +62,12 @@
         <item name="android:backgroundDimEnabled">false</item>
     </style>
 
+    <style name="TranslucentTheme" parent="@style/OptOutEdgeToEdge">
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:backgroundDimEnabled">false</item>
+    </style>
+
     <style name="no_starting_window" parent="@style/OptOutEdgeToEdge">
         <item name="android:windowDisablePreview">true</item>
     </style>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 73625da..0c1ac99 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -241,6 +241,21 @@
                 FLICKER_APP_PACKAGE + ".PipActivity");
     }
 
+    public static class BottomHalfPip {
+        public static final String LAUNCHING_APP_LABEL = "BottomHalfPipLaunchingActivity";
+        // Test App > Bottom Half PIP Activity
+        public static final String LABEL = "BottomHalfPipActivity";
+
+        // Use the bottom half layout for PIP Activity
+        public static final String EXTRA_BOTTOM_HALF_LAYOUT = "bottom_half";
+
+        public static final ComponentName LAUNCHING_APP_COMPONENT = new ComponentName(
+                FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".BottomHalfPipLaunchingActivity");
+
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".BottomHalfPipActivity");
+    }
+
     public static class SplitScreen {
         public static class Primary {
             public static final String LABEL = "SplitScreenPrimaryActivity";
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java
new file mode 100644
index 0000000..3d48655
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.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.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+public class BottomHalfPipActivity extends PipActivity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setTheme(R.style.TranslucentTheme);
+        updateLayout();
+    }
+
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+
+        updateLayout();
+    }
+
+    /**
+     * Sets to match parent layout if the activity is
+     * {@link Activity#isInPictureInPictureMode()}. Otherwise, set to bottom half
+     * layout.
+     *
+     * @see #setToBottomHalfMode(boolean)
+     */
+    private void updateLayout() {
+        setToBottomHalfMode(!isInPictureInPictureMode());
+    }
+
+    /**
+     * Sets `useBottomHalfLayout` to `true` to use the bottom half layout. Use the
+     * [LayoutParams.MATCH_PARENT] layout.
+     */
+    private void setToBottomHalfMode(boolean useBottomHalfLayout) {
+        final WindowManager.LayoutParams attrs = getWindow().getAttributes();
+        if (useBottomHalfLayout) {
+            final int taskHeight = getWindowManager().getCurrentWindowMetrics().getBounds()
+                    .height();
+            attrs.y = taskHeight / 2;
+            attrs.height = taskHeight / 2;
+        } else {
+            attrs.y = 0;
+            attrs.height = LayoutParams.MATCH_PARENT;
+        }
+        getWindow().setAttributes(attrs);
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java
new file mode 100644
index 0000000..d9d4361
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java
@@ -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.server.wm.flicker.testapp;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+public class BottomHalfPipLaunchingActivity extends SimpleActivity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final Intent intent = new Intent(this, BottomHalfPipActivity.class);
+        startActivity(intent);
+    }
+}
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 6eb0045..43844f6 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -156,8 +156,7 @@
                 }
 
                 override fun getKeyboardBacklightController(
-                    nativeService: NativeInputManagerService?,
-                    dataStore: PersistentDataStore?
+                    nativeService: NativeInputManagerService?
                 ): InputManagerService.KeyboardBacklightControllerInterface {
                     return kbdController
                 }
@@ -216,6 +215,7 @@
         verify(native).setShouldNotifyTouchpadHardwareState(anyBoolean())
         verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
         verify(native).setTouchpadThreeFingerTapShortcutEnabled(anyBoolean())
+        verify(native).setTouchpadSystemGesturesEnabled(anyBoolean())
         verify(native).setShowTouches(anyBoolean())
         verify(native).setMotionClassifierEnabled(anyBoolean())
         verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
diff --git a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
index 938e2f8..644d5a0 100644
--- a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -25,6 +25,7 @@
 import android.hardware.input.IKeyboardBacklightState
 import android.hardware.input.InputManager
 import android.hardware.lights.Light
+import android.os.SystemProperties
 import android.os.UEventObserver
 import android.os.test.TestLooper
 import android.platform.test.annotations.Presubmit
@@ -32,16 +33,13 @@
 import android.util.TypedValue
 import androidx.test.annotation.UiThreadTest
 import androidx.test.core.app.ApplicationProvider
+import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.internal.R
+import com.android.modules.utils.testing.ExtendedMockitoRule
 import com.android.server.input.KeyboardBacklightController.DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL
 import com.android.server.input.KeyboardBacklightController.MAX_BRIGHTNESS_CHANGE_STEPS
 import com.android.test.input.MockInputManagerRule
-import java.io.FileNotFoundException
-import java.io.FileOutputStream
-import java.io.IOException
-import java.io.InputStream
 import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNull
@@ -56,7 +54,6 @@
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.`when`
-import org.mockito.junit.MockitoJUnit
 
 private fun createKeyboard(deviceId: Int): InputDevice =
     InputDevice.Builder()
@@ -101,7 +98,8 @@
     }
 
     @get:Rule
-    val rule = MockitoJUnit.rule()!!
+    val extendedMockitoRule =
+        ExtendedMockitoRule.Builder(this).mockStatic(SystemProperties::class.java).build()!!
     @get:Rule
     val inputManagerRule = MockInputManagerRule()
 
@@ -113,7 +111,6 @@
     private lateinit var resources: Resources
     private lateinit var keyboardBacklightController: KeyboardBacklightController
     private lateinit var context: Context
-    private lateinit var dataStore: PersistentDataStore
     private lateinit var testLooper: TestLooper
     private var lightColorMap: HashMap<Int, Int> = HashMap()
     private var lastBacklightState: KeyboardBacklightState? = null
@@ -124,21 +121,8 @@
     fun setup() {
         context = spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
         `when`(context.resources).thenReturn(resources)
-        dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
-            override fun openRead(): InputStream? {
-                throw FileNotFoundException()
-            }
-
-            override fun startWrite(): FileOutputStream? {
-                throw IOException()
-            }
-
-            override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
-        })
         testLooper = TestLooper()
         setupConfig()
-        keyboardBacklightController = KeyboardBacklightController(context, native, dataStore,
-                testLooper.looper, FakeAnimatorFactory(), uEventManager)
         val inputManager = InputManager(context)
         `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
         `when`(inputManagerRule.mock.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
@@ -155,6 +139,7 @@
             sysfsNodeChanges++
         }
     }
+
     private fun setupConfig() {
         val brightnessValues = intArrayOf(100, 200, 0)
         val decreaseThresholds = intArrayOf(-1, 900, 1900)
@@ -180,271 +165,166 @@
             Unit
         }
     }
+
+    private fun setupController() {
+        keyboardBacklightController = KeyboardBacklightController(context, native,
+            testLooper.looper, FakeAnimatorFactory(), uEventManager)
+    }
+
     @Test
     fun testKeyboardBacklightIncrementDecrement() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = false,
-            ambientControlEnabled = false
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        setupController()
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+        `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
 
-            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
-                    DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
-        }
+        assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+                DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
     }
 
     @Test
     fun testKeyboardWithoutBacklight() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = false,
-            ambientControlEnabled = false
-        ).use {
-            val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
-            val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithoutBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        setupController()
+        val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
+        val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
+        `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight)
+        `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
 
-            incrementKeyboardBacklight(DEVICE_ID)
-            assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
-        }
+        incrementKeyboardBacklight(DEVICE_ID)
+        assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
     }
 
     @Test
     fun testKeyboardWithMultipleLight() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = false,
-            ambientControlEnabled = false
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-            val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(
-                listOf(
-                    keyboardBacklight,
-                    keyboardInputLight
-                )
+        setupController()
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+        val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT)
+        `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(
+            listOf(
+                keyboardBacklight,
+                keyboardInputLight
             )
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        )
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
 
-            incrementKeyboardBacklight(DEVICE_ID)
-            assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
-            assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
-            assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
-        }
-    }
-
-    @Test
-    fun testRestoreBacklightOnInputDeviceAdded() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = false,
-            ambientControlEnabled = false
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-
-            for (level in 1 until DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size) {
-                dataStore.setKeyboardBacklightBrightness(
-                    keyboardWithBacklight.descriptor,
-                    LIGHT_ID,
-                    DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level] - 1
-                )
-
-                keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-                keyboardBacklightController.notifyUserActivity()
-                testLooper.dispatchNext()
-                assertEquals(
-                    "Keyboard backlight level should be restored to the level saved in the " +
-                            "data store",
-                    Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
-                    lightColorMap[LIGHT_ID]
-                )
-                keyboardBacklightController.onInputDeviceRemoved(DEVICE_ID)
-            }
-        }
-    }
-
-    @Test
-    fun testRestoreBacklightOnInputDeviceChanged() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = false,
-            ambientControlEnabled = false
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            dataStore.setKeyboardBacklightBrightness(
-                keyboardWithBacklight.descriptor,
-                LIGHT_ID,
-                MAX_BRIGHTNESS
-            )
-
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-            keyboardBacklightController.notifyUserActivity()
-            testLooper.dispatchNext()
-            assertTrue(
-                "Keyboard backlight should not be changed until its added",
-                lightColorMap.isEmpty()
-            )
-
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-            keyboardBacklightController.onInputDeviceChanged(DEVICE_ID)
-            keyboardBacklightController.notifyUserActivity()
-            testLooper.dispatchNext()
-            assertEquals(
-                "Keyboard backlight level should be restored to the level saved in the data store",
-                Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
-                lightColorMap[LIGHT_ID]
-            )
-        }
+        incrementKeyboardBacklight(DEVICE_ID)
+        assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
+        assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
+        assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
     }
 
     @Test
     fun testKeyboardBacklight_registerUnregisterListener() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = false,
-            ambientControlEnabled = false
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-            val maxLevel = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size - 1
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        setupController()
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+        val maxLevel = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size - 1
+        `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
 
-            // Register backlight listener
-            val listener = KeyboardBacklightListener()
-            keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
+        // Register backlight listener
+        val listener = KeyboardBacklightListener()
+        keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
 
-            lastBacklightState = null
-            keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
-            testLooper.dispatchNext()
+        lastBacklightState = null
+        keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
+        testLooper.dispatchNext()
 
-            assertEquals(
-                "Backlight state device Id should be $DEVICE_ID",
-                DEVICE_ID,
-                lastBacklightState!!.deviceId
-            )
-            assertEquals(
-                "Backlight state brightnessLevel should be 1",
-                1,
-                lastBacklightState!!.brightnessLevel
-            )
-            assertEquals(
-                "Backlight state maxBrightnessLevel should be $maxLevel",
-                maxLevel,
-                lastBacklightState!!.maxBrightnessLevel
-            )
-            assertEquals(
-                "Backlight state isTriggeredByKeyPress should be true",
-                true,
-                lastBacklightState!!.isTriggeredByKeyPress
-            )
+        assertEquals(
+            "Backlight state device Id should be $DEVICE_ID",
+            DEVICE_ID,
+            lastBacklightState!!.deviceId
+        )
+        assertEquals(
+            "Backlight state brightnessLevel should be 1",
+            1,
+            lastBacklightState!!.brightnessLevel
+        )
+        assertEquals(
+            "Backlight state maxBrightnessLevel should be $maxLevel",
+            maxLevel,
+            lastBacklightState!!.maxBrightnessLevel
+        )
+        assertEquals(
+            "Backlight state isTriggeredByKeyPress should be true",
+            true,
+            lastBacklightState!!.isTriggeredByKeyPress
+        )
 
-            // Unregister listener
-            keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
+        // Unregister listener
+        keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
 
-            lastBacklightState = null
-            incrementKeyboardBacklight(DEVICE_ID)
+        lastBacklightState = null
+        incrementKeyboardBacklight(DEVICE_ID)
 
-            assertNull("Listener should not receive any updates", lastBacklightState)
-        }
+        assertNull("Listener should not receive any updates", lastBacklightState)
     }
 
     @Test
     fun testKeyboardBacklight_userActivity() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = false,
-            ambientControlEnabled = false
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-            dataStore.setKeyboardBacklightBrightness(
-                keyboardWithBacklight.descriptor,
-                LIGHT_ID,
-                MAX_BRIGHTNESS
-            )
+        setupController()
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+        `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        incrementKeyboardBacklight(DEVICE_ID)
+        assertNotEquals(
+            "Keyboard backlight level should be incremented to a non-zero value",
+            0,
+            lightColorMap[LIGHT_ID]
+        )
 
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-            keyboardBacklightController.notifyUserActivity()
-            testLooper.dispatchNext()
-            assertEquals(
-                "Keyboard backlight level should be restored to the level saved in the data store",
-                Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
-                lightColorMap[LIGHT_ID]
-            )
-
-            testLooper.moveTimeForward((USER_INACTIVITY_THRESHOLD_MILLIS + 1000).toLong())
-            testLooper.dispatchNext()
-            assertEquals(
-                "Keyboard backlight level should be turned off after inactivity",
-                0,
-                lightColorMap[LIGHT_ID]
-            )
-        }
+        testLooper.moveTimeForward((USER_INACTIVITY_THRESHOLD_MILLIS + 1000).toLong())
+        testLooper.dispatchNext()
+        assertEquals(
+            "Keyboard backlight level should be turned off after inactivity",
+            0,
+            lightColorMap[LIGHT_ID]
+        )
     }
 
     @Test
     fun testKeyboardBacklight_displayOnOff() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = false,
-            ambientControlEnabled = false
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-            dataStore.setKeyboardBacklightBrightness(
-                keyboardWithBacklight.descriptor,
-                LIGHT_ID,
-                MAX_BRIGHTNESS
-            )
+        setupController()
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+        `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        incrementKeyboardBacklight(DEVICE_ID)
 
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-            keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */)
-            assertEquals(
-                "Keyboard backlight level should be restored to the level saved in the data " +
-                        "store when display turned on",
-                Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
-                lightColorMap[LIGHT_ID]
-            )
+        val currentValue = lightColorMap[LIGHT_ID]
+        assertNotEquals(
+            "Keyboard backlight level should be incremented to a non-zero value",
+            0,
+            lightColorMap[LIGHT_ID]
+        )
 
-            keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */)
-            assertEquals(
-                "Keyboard backlight level should be turned off after display is turned off",
-                0,
-                lightColorMap[LIGHT_ID]
-            )
-        }
+        keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */)
+        assertEquals(
+            "Keyboard backlight level should be turned off after display is turned off",
+            0,
+            lightColorMap[LIGHT_ID]
+        )
+
+        keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */)
+        assertEquals(
+            "Keyboard backlight level should be turned on after display is turned on",
+            currentValue,
+            lightColorMap[LIGHT_ID]
+        )
     }
 
     @Test
     fun testKeyboardBacklightSysfsNodeAdded_AfterInputDeviceAdded() {
+        setupController()
         var counter = sysfsNodeChanges
         keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
             "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::no_backlight\u0000"
@@ -504,260 +384,160 @@
     @Test
     @UiThreadTest
     fun testKeyboardBacklightAnimation_onChangeLevels() {
-        KeyboardBacklightFlags(
-            animationEnabled = true,
-            customLevelsEnabled = false,
-            ambientControlEnabled = false
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
-            incrementKeyboardBacklight(DEVICE_ID)
-            assertEquals(
-                "Should start animation from level 0",
-                DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[0],
-                lastAnimationValues[0]
-            )
-            assertEquals(
-                "Should start animation to level 1",
-                DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1],
-                lastAnimationValues[1]
-            )
+        ExtendedMockito.doReturn("true").`when` {
+            SystemProperties.get(eq("persist.input.keyboard.backlight_animation.enabled"))
         }
+        setupController()
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+        `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+        incrementKeyboardBacklight(DEVICE_ID)
+        assertEquals(
+            "Should start animation from level 0",
+            DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[0],
+            lastAnimationValues[0]
+        )
+        assertEquals(
+            "Should start animation to level 1",
+            DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1],
+            lastAnimationValues[1]
+        )
     }
 
     @Test
     fun testKeyboardBacklightPreferredLevels() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = true,
-            ambientControlEnabled = false
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val suggestedLevels = intArrayOf(0, 22, 63, 135, 196, 255)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
-                    suggestedLevels)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        setupController()
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val suggestedLevels = intArrayOf(0, 22, 63, 135, 196, 255)
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+                suggestedLevels)
+        `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
 
-            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
-                    suggestedLevels)
-        }
+        assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, suggestedLevels)
     }
 
     @Test
     fun testKeyboardBacklightPreferredLevels_moreThanMax_shouldUseDefault() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = true,
-            ambientControlEnabled = false
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val suggestedLevels = IntArray(MAX_BRIGHTNESS_CHANGE_STEPS + 1) { 10 * (it + 1) }
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
-                    suggestedLevels)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        setupController()
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val suggestedLevels = IntArray(MAX_BRIGHTNESS_CHANGE_STEPS + 1) { 10 * (it + 1) }
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+                suggestedLevels)
+        `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
 
-            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
-                    DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
-        }
+        assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+                DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
     }
 
     @Test
     fun testKeyboardBacklightPreferredLevels_mustHaveZeroAndMaxBrightnessAsBounds() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = true,
-            ambientControlEnabled = false
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val suggestedLevels = intArrayOf(22, 63, 135, 196)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
-                    suggestedLevels)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        setupController()
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val suggestedLevels = intArrayOf(22, 63, 135, 196)
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+                suggestedLevels)
+        `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
 
-            // Framework will add the lowest and maximum levels if not provided via config
-            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
-                    intArrayOf(0, 22, 63, 135, 196, 255))
-        }
+        // Framework will add the lowest and maximum levels if not provided via config
+        assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+                intArrayOf(0, 22, 63, 135, 196, 255))
     }
 
     @Test
     fun testKeyboardBacklightPreferredLevels_dropsOutOfBoundsLevels() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = true,
-            ambientControlEnabled = false
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val suggestedLevels = intArrayOf(22, 63, 135, 400, 196, 1000)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
-                    suggestedLevels)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID))
-                .thenReturn(listOf(keyboardBacklight))
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        setupController()
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val suggestedLevels = intArrayOf(22, 63, 135, 400, 196, 1000)
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+                suggestedLevels)
+        `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
 
-            // Framework will drop out of bound levels in the config
-            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
-                    intArrayOf(0, 22, 63, 135, 196, 255))
-        }
-    }
-
-    @Test
-    fun testAmbientBacklightControl_doesntRestoreBacklightLevel() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = false,
-            ambientControlEnabled = true
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-
-            dataStore.setKeyboardBacklightBrightness(
-                keyboardWithBacklight.descriptor,
-                LIGHT_ID,
-                DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1]
-            )
-
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-            keyboardBacklightController.notifyUserActivity()
-            testLooper.dispatchNext()
-            assertNull(
-                "Keyboard backlight level should not be restored to the saved level",
-                lightColorMap[LIGHT_ID]
-            )
-        }
-    }
-
-    @Test
-    fun testAmbientBacklightControl_doesntBackupBacklightLevel() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = false,
-            ambientControlEnabled = true
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-            incrementKeyboardBacklight(DEVICE_ID)
-            assertFalse(
-                "Light value should not be backed up if ambient control is enabled",
-                dataStore.getKeyboardBacklightBrightness(
-                    keyboardWithBacklight.descriptor, LIGHT_ID
-                ).isPresent
-            )
-        }
+        // Framework will drop out of bound levels in the config
+        assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+                intArrayOf(0, 22, 63, 135, 196, 255))
     }
 
     @Test
     fun testAmbientBacklightControl_incrementLevel_afterAmbientChange() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = false,
-            ambientControlEnabled = true
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-            sendAmbientBacklightValue(1)
-            assertEquals(
-                "Light value should be changed to ambient provided value",
-                Color.argb(1, 0, 0, 0),
-                lightColorMap[LIGHT_ID]
-            )
+        setupController()
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+        `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        sendAmbientBacklightValue(1)
+        assertEquals(
+            "Light value should be changed to ambient provided value",
+            Color.argb(1, 0, 0, 0),
+            lightColorMap[LIGHT_ID]
+        )
 
-            incrementKeyboardBacklight(DEVICE_ID)
+        incrementKeyboardBacklight(DEVICE_ID)
 
-            assertEquals(
-                "Light value for level after increment post Ambient change is mismatched",
-                Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
-                lightColorMap[LIGHT_ID]
-            )
-        }
+        assertEquals(
+            "Light value for level after increment post Ambient change is mismatched",
+            Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
+            lightColorMap[LIGHT_ID]
+        )
     }
 
     @Test
     fun testAmbientBacklightControl_decrementLevel_afterAmbientChange() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = false,
-            ambientControlEnabled = true
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-            sendAmbientBacklightValue(254)
-            assertEquals(
-                "Light value should be changed to ambient provided value",
-                Color.argb(254, 0, 0, 0),
-                lightColorMap[LIGHT_ID]
-            )
+        setupController()
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+        `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        sendAmbientBacklightValue(254)
+        assertEquals(
+            "Light value should be changed to ambient provided value",
+            Color.argb(254, 0, 0, 0),
+            lightColorMap[LIGHT_ID]
+        )
 
-            decrementKeyboardBacklight(DEVICE_ID)
+        decrementKeyboardBacklight(DEVICE_ID)
 
-            val numLevels = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size
-            assertEquals(
-                "Light value for level after decrement post Ambient change is mismatched",
-                Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[numLevels - 2], 0, 0, 0),
-                lightColorMap[LIGHT_ID]
-            )
-        }
+        val numLevels = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size
+        assertEquals(
+            "Light value for level after decrement post Ambient change is mismatched",
+            Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[numLevels - 2], 0, 0, 0),
+            lightColorMap[LIGHT_ID]
+        )
     }
 
     @Test
     fun testAmbientBacklightControl_ambientChanges_afterManualChange() {
-        KeyboardBacklightFlags(
-            animationEnabled = false,
-            customLevelsEnabled = false,
-            ambientControlEnabled = true
-        ).use {
-            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-            `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
-                .thenReturn(keyboardWithBacklight)
-            `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-            incrementKeyboardBacklight(DEVICE_ID)
-            assertEquals(
-                "Light value should be changed to the first level",
-                Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
-                lightColorMap[LIGHT_ID]
-            )
+        setupController()
+        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+        `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+        `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        incrementKeyboardBacklight(DEVICE_ID)
+        assertEquals(
+            "Light value should be changed to the first level",
+            Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
+            lightColorMap[LIGHT_ID]
+        )
 
-            sendAmbientBacklightValue(100)
-            assertNotEquals(
-                "Light value should not change based on ambient changes after manual changes",
-                Color.argb(100, 0, 0, 0),
-                lightColorMap[LIGHT_ID]
-            )
-        }
+        sendAmbientBacklightValue(100)
+        assertNotEquals(
+            "Light value should not change based on ambient changes after manual changes",
+            Color.argb(100, 0, 0, 0),
+            lightColorMap[LIGHT_ID]
+        )
     }
 
     private fun assertIncrementDecrementForLevels(
@@ -774,11 +554,6 @@
                 Color.argb(expectedLevels[level], 0, 0, 0),
                 lightColorMap[lightId]
             )
-            assertEquals(
-                "Light value for level $level must be correctly stored in the datastore",
-                expectedLevels[level],
-                dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
-            )
         }
 
         // Increment above max level
@@ -788,11 +563,6 @@
             Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
             lightColorMap[lightId]
         )
-        assertEquals(
-            "Light value for max level must be correctly stored in the datastore",
-            MAX_BRIGHTNESS,
-            dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
-        )
 
         for (level in expectedLevels.size - 2 downTo 0) {
             decrementKeyboardBacklight(deviceId)
@@ -801,11 +571,6 @@
                 Color.argb(expectedLevels[level], 0, 0, 0),
                 lightColorMap[lightId]
             )
-            assertEquals(
-                "Light value for level $level must be correctly stored in the datastore",
-                expectedLevels[level],
-                dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
-            )
         }
 
         // Decrement below min level
@@ -815,11 +580,6 @@
             Color.argb(0, 0, 0, 0),
             lightColorMap[lightId]
         )
-        assertEquals(
-            "Light value for min level must be correctly stored in the datastore",
-            0,
-            dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
-        )
     }
 
     inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() {
@@ -862,23 +622,6 @@
         val isTriggeredByKeyPress: Boolean
     )
 
-    private inner class KeyboardBacklightFlags constructor(
-        animationEnabled: Boolean,
-        customLevelsEnabled: Boolean,
-        ambientControlEnabled: Boolean
-    ) : AutoCloseable {
-        init {
-            InputFeatureFlagProvider.setKeyboardBacklightAnimationEnabled(animationEnabled)
-            InputFeatureFlagProvider.setKeyboardBacklightCustomLevelsEnabled(customLevelsEnabled)
-            InputFeatureFlagProvider
-                .setAmbientKeyboardBacklightControlEnabled(ambientControlEnabled)
-        }
-
-        override fun close() {
-            InputFeatureFlagProvider.clearOverrides()
-        }
-    }
-
     private inner class FakeAnimatorFactory : KeyboardBacklightController.AnimatorFactory {
         override fun makeIntAnimator(from: Int, to: Int): ValueAnimator {
             lastAnimationValues[0] = from
diff --git a/tests/broadcasts/unit/Android.bp b/tests/broadcasts/unit/Android.bp
new file mode 100644
index 0000000..9e15ac4
--- /dev/null
+++ b/tests/broadcasts/unit/Android.bp
@@ -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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+    default_team: "trendy_team_framework_backstage_power",
+}
+
+android_test {
+    name: "BroadcastUnitTests",
+    srcs: ["src/**/*.java"],
+    defaults: [
+        "modules-utils-extended-mockito-rule-defaults",
+    ],
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "mockito-target-extended-minus-junit4",
+        "truth",
+        "flag-junit",
+        "android.app.flags-aconfig-java",
+        "junit-params",
+    ],
+    certificate: "platform",
+    platform_apis: true,
+    test_suites: ["device-tests"],
+}
diff --git a/tests/broadcasts/unit/AndroidManifest.xml b/tests/broadcasts/unit/AndroidManifest.xml
new file mode 100644
index 0000000..61eb230
--- /dev/null
+++ b/tests/broadcasts/unit/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.broadcasts.unit" >
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.broadcasts.unit"
+        android:label="Broadcasts Unit Tests"/>
+</manifest>
\ No newline at end of file
diff --git a/tests/broadcasts/unit/AndroidTest.xml b/tests/broadcasts/unit/AndroidTest.xml
new file mode 100644
index 0000000..b91e4783
--- /dev/null
+++ b/tests/broadcasts/unit/AndroidTest.xml
@@ -0,0 +1,29 @@
+<!-- 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.
+-->
+<configuration description="Runs Broadcasts tests">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="BroadcastUnitTests" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="BroadcastUnitTests.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.broadcasts.unit" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/broadcasts/unit/OWNERS b/tests/broadcasts/unit/OWNERS
new file mode 100644
index 0000000..f1e450b
--- /dev/null
+++ b/tests/broadcasts/unit/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 316181
+include platform/frameworks/base:/BROADCASTS_OWNERS
\ No newline at end of file
diff --git a/tests/broadcasts/unit/TEST_MAPPING b/tests/broadcasts/unit/TEST_MAPPING
new file mode 100644
index 0000000..b920e25
--- /dev/null
+++ b/tests/broadcasts/unit/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+    "postsubmit": [
+        {
+            "name": "BroadcastUnitTests"
+        }
+    ]
+}
diff --git a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
new file mode 100644
index 0000000..ad032fb
--- /dev/null
+++ b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
@@ -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 android.app;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.os.IpcDataCache;
+import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.annotations.Keep;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+@RunWith(JUnitParamsRunner.class)
+public class BroadcastStickyCacheTest {
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .mockStatic(IpcDataCache.class)
+            .mockStatic(ActivityManager.class)
+            .build();
+
+    @Mock
+    private IActivityManager mActivityManagerMock;
+
+    @Mock
+    private IApplicationThread mIApplicationThreadMock;
+
+    @Keep
+    private static Object stickyBroadcastList() {
+        return BroadcastStickyCache.STICKY_BROADCAST_ACTIONS;
+    }
+
+    @Before
+    public void setUp() {
+        BroadcastStickyCache.clearCacheForTest();
+
+        doNothing().when(() -> IpcDataCache.invalidateCache(anyString(), anyString()));
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+    public void useCache_flagDisabled_returnsFalse() {
+        assertFalse(BroadcastStickyCache.useCache(new IntentFilter(Intent.ACTION_BATTERY_CHANGED)));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+    public void useCache_nullFilter_returnsFalse() {
+        assertFalse(BroadcastStickyCache.useCache(null));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+    public void useCache_filterWithoutAction_returnsFalse() {
+        assertFalse(BroadcastStickyCache.useCache(new IntentFilter()));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+    public void useCache_filterWithoutStickyBroadcastAction_returnsFalse() {
+        assertFalse(BroadcastStickyCache.useCache(new IntentFilter(Intent.ACTION_BOOT_COMPLETED)));
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+    public void invalidateCache_flagDisabled_cacheNotInvalidated() {
+        final String apiName = BroadcastStickyCache.sActionApiNameMap.get(
+                AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
+
+        BroadcastStickyCache.invalidateCache(
+                AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
+
+        ExtendedMockito.verify(
+                () -> IpcDataCache.invalidateCache(eq(IpcDataCache.MODULE_SYSTEM), eq(apiName)),
+                times(0));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+    public void invalidateCache_broadcastNotSticky_cacheNotInvalidated() {
+        BroadcastStickyCache.invalidateCache(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+
+        ExtendedMockito.verify(
+                () -> IpcDataCache.invalidateCache(eq(IpcDataCache.MODULE_SYSTEM), anyString()),
+                times(0));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+    public void invalidateCache_withStickyBroadcast_cacheInvalidated() {
+        final String apiName = BroadcastStickyCache.sActionApiNameMap.get(
+                Intent.ACTION_BATTERY_CHANGED);
+
+        BroadcastStickyCache.invalidateCache(Intent.ACTION_BATTERY_CHANGED);
+
+        ExtendedMockito.verify(
+                () -> IpcDataCache.invalidateCache(eq(IpcDataCache.MODULE_SYSTEM), eq(apiName)),
+                times(1));
+    }
+
+    @Test
+    public void invalidateAllCaches_cacheInvalidated() {
+        BroadcastStickyCache.invalidateAllCaches();
+
+        for (int i = BroadcastStickyCache.sActionApiNameMap.size() - 1; i > -1; i--) {
+            final String apiName = BroadcastStickyCache.sActionApiNameMap.valueAt(i);
+            ExtendedMockito.verify(() -> IpcDataCache.invalidateCache(anyString(),
+                    eq(apiName)), times(1));
+        }
+    }
+
+    @Test
+    @Parameters(method = "stickyBroadcastList")
+    public void getIntent_createNewCache_verifyRegisterReceiverIsCalled(String action)
+            throws RemoteException {
+        setActivityManagerMock(action);
+        final IntentFilter filter = new IntentFilter(action);
+        final Intent intent = queryIntent(filter);
+
+        assertNotNull(intent);
+        assertEquals(intent.getAction(), action);
+        verify(mActivityManagerMock, times(1)).registerReceiverWithFeature(
+                eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(),
+                eq(filter), anyString(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void getIntent_querySameValueTwice_verifyRegisterReceiverIsCalledOnce()
+            throws RemoteException {
+        setActivityManagerMock(Intent.ACTION_DEVICE_STORAGE_LOW);
+        final Intent intent = queryIntent(new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW));
+        final Intent cachedIntent = queryIntent(new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW));
+
+        assertNotNull(intent);
+        assertEquals(intent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW);
+        assertNotNull(cachedIntent);
+        assertEquals(cachedIntent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW);
+
+        verify(mActivityManagerMock, times(1)).registerReceiverWithFeature(
+                eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(),
+                any(), anyString(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void getIntent_querySameActionWithDifferentFilter_verifyRegisterReceiverCalledTwice()
+            throws RemoteException {
+        setActivityManagerMock(Intent.ACTION_DEVICE_STORAGE_LOW);
+        final IntentFilter filter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
+        final Intent intent = queryIntent(filter);
+
+        final IntentFilter newFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
+        newFilter.addDataScheme("file");
+        final Intent newIntent = queryIntent(newFilter);
+
+        assertNotNull(intent);
+        assertEquals(intent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW);
+        assertNotNull(newIntent);
+        assertEquals(newIntent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW);
+
+        verify(mActivityManagerMock, times(1)).registerReceiverWithFeature(
+                eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(),
+                eq(filter), anyString(), anyInt(), anyInt());
+
+        verify(mActivityManagerMock, times(1)).registerReceiverWithFeature(
+                eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(),
+                eq(newFilter), anyString(), anyInt(), anyInt());
+    }
+
+    private Intent queryIntent(IntentFilter filter) {
+        return BroadcastStickyCache.getIntent(
+                mIApplicationThreadMock,
+                "android",
+                "android",
+                filter,
+                "system",
+                0,
+                0
+        );
+    }
+
+    private void setActivityManagerMock(String action) throws RemoteException {
+        when(ActivityManager.getService()).thenReturn(mActivityManagerMock);
+        when(mActivityManagerMock.registerReceiverWithFeature(any(), anyString(),
+                anyString(), anyString(), any(), any(), anyString(), anyInt(),
+                anyInt())).thenReturn(new Intent(action));
+    }
+}
diff --git a/tests/graphics/SilkFX/AndroidManifest.xml b/tests/graphics/SilkFX/AndroidManifest.xml
index 25092b5..c293589 100644
--- a/tests/graphics/SilkFX/AndroidManifest.xml
+++ b/tests/graphics/SilkFX/AndroidManifest.xml
@@ -23,13 +23,12 @@
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
     <application android:label="SilkFX"
-         android:theme="@style/Theme.UsefulDefault">
+         android:theme="@android:style/Theme.Material">
 
         <activity android:name=".Main"
              android:label="SilkFX Demos"
              android:banner="@drawable/background1"
-             android:exported="true"
-             android:theme="@style/Theme.UsefulDefault">
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/tests/graphics/SilkFX/res/layout/activity_background_blur.xml b/tests/graphics/SilkFX/res/layout/activity_background_blur.xml
index f13c088..27eca82 100644
--- a/tests/graphics/SilkFX/res/layout/activity_background_blur.xml
+++ b/tests/graphics/SilkFX/res/layout/activity_background_blur.xml
@@ -13,161 +13,168 @@
   ~ WITHOUT 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"
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/background"
-    android:layout_width="390dp"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:padding="15dp"
-    android:orientation="vertical"
+    android:fitsSystemWindows="true"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     tools:context=".materials.BackgroundBlurActivity">
 
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center_horizontal"
-        android:padding="10dp"
-        android:textColor="#ffffffff"
-        android:text="Hello blurry world!"/>
-
     <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:textColor="#ffffffff"
-            android:text="Background blur"/>
-
-        <SeekBar
-            android:id="@+id/set_background_blur"
-            android:min="0"
-            android:max="300"
-            android:layout_width="160dp"
-            android:layout_height="wrap_content"/>
-        <TextView
-            android:id="@+id/background_blur_radius"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textColor="#ffffffff"
-            android:ems="3"
-            android:gravity="center"
-            android:paddingLeft="10dp"
-            android:paddingRight="10dp"
-            android:text="TODO"/>
-    </LinearLayout>
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:textColor="#ffffffff"
-            android:text="Background alpha"/>
-
-        <SeekBar
-            android:id="@+id/set_background_alpha"
-            android:min="0"
-            android:max="100"
-            android:layout_width="160dp"
-            android:layout_height="wrap_content" />
-        <TextView
-            android:id="@+id/background_alpha"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textColor="#ffffffff"
-            android:ems="3"
-            android:gravity="center"
-            android:paddingLeft="10dp"
-            android:paddingRight="10dp"
-            android:text="TODO"/>
-    </LinearLayout>
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:textColor="#ffffffff"
-            android:text="Blur behind"/>
-
-        <SeekBar
-            android:id="@+id/set_blur_behind"
-            android:min="0"
-            android:max="300"
-            android:layout_width="160dp"
-            android:layout_height="wrap_content" />
-        <TextView
-            android:id="@+id/blur_behind_radius"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:textColor="#ffffffff"
-            android:paddingLeft="10dp"
-            android:paddingRight="10dp"
-            android:ems="3"
-            android:text="TODO"/>
-    </LinearLayout>
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:textColor="#ffffffff"
-            android:text="Dim amount"/>
-
-        <SeekBar
-            android:id="@+id/set_dim_amount"
-            android:min="0"
-            android:max="100"
-            android:layout_width="160dp"
-            android:layout_height="wrap_content" />
-        <TextView
-            android:id="@+id/dim_amount"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:textColor="#ffffffff"
-            android:paddingLeft="10dp"
-            android:paddingRight="10dp"
-            android:ems="3"
-            android:text="TODO"/>
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_parent"
+        android:id="@+id/background"
+        android:layout_width="390dp"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
-        android:layout_marginTop="5dp"
-        android:orientation="vertical"
-        android:gravity="center">
+        android:padding="15dp"
+        android:orientation="vertical">
 
-        <Button
-            android:id="@+id/toggle_blur_enabled"
+        <TextView
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:text="Disable blur"
-            android:onClick="toggleForceBlurDisabled"/>
+            android:gravity="center_horizontal"
+            android:padding="10dp"
+            android:textColor="#ffffffff"
+            android:text="Hello blurry world!"/>
 
-        <Button
-            android:id="@+id/toggle_battery_saving_mode"
+        <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:text="TODO"
-            android:onClick="toggleBatterySavingMode"/>
+            android:orientation="horizontal">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:textColor="#ffffffff"
+                android:text="Background blur"/>
+
+            <SeekBar
+                android:id="@+id/set_background_blur"
+                android:min="0"
+                android:max="300"
+                android:layout_width="160dp"
+                android:layout_height="wrap_content"/>
+            <TextView
+                android:id="@+id/background_blur_radius"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="#ffffffff"
+                android:ems="3"
+                android:gravity="center"
+                android:paddingLeft="10dp"
+                android:paddingRight="10dp"
+                android:text="TODO"/>
+        </LinearLayout>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:textColor="#ffffffff"
+                android:text="Background alpha"/>
+
+            <SeekBar
+                android:id="@+id/set_background_alpha"
+                android:min="0"
+                android:max="100"
+                android:layout_width="160dp"
+                android:layout_height="wrap_content" />
+            <TextView
+                android:id="@+id/background_alpha"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="#ffffffff"
+                android:ems="3"
+                android:gravity="center"
+                android:paddingLeft="10dp"
+                android:paddingRight="10dp"
+                android:text="TODO"/>
+        </LinearLayout>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:textColor="#ffffffff"
+                android:text="Blur behind"/>
+
+            <SeekBar
+                android:id="@+id/set_blur_behind"
+                android:min="0"
+                android:max="300"
+                android:layout_width="160dp"
+                android:layout_height="wrap_content" />
+            <TextView
+                android:id="@+id/blur_behind_radius"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                android:textColor="#ffffffff"
+                android:paddingLeft="10dp"
+                android:paddingRight="10dp"
+                android:ems="3"
+                android:text="TODO"/>
+        </LinearLayout>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:textColor="#ffffffff"
+                android:text="Dim amount"/>
+
+            <SeekBar
+                android:id="@+id/set_dim_amount"
+                android:min="0"
+                android:max="100"
+                android:layout_width="160dp"
+                android:layout_height="wrap_content" />
+            <TextView
+                android:id="@+id/dim_amount"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                android:textColor="#ffffffff"
+                android:paddingLeft="10dp"
+                android:paddingRight="10dp"
+                android:ems="3"
+                android:text="TODO"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginTop="5dp"
+            android:orientation="vertical"
+            android:gravity="center">
+
+            <Button
+                android:id="@+id/toggle_blur_enabled"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Disable blur"
+                android:onClick="toggleForceBlurDisabled"/>
+
+            <Button
+                android:id="@+id/toggle_battery_saving_mode"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="TODO"
+                android:onClick="toggleBatterySavingMode"/>
+        </LinearLayout>
+        <requestFocus/>
+
     </LinearLayout>
-    <requestFocus/>
-
-</LinearLayout>
+</FrameLayout>
diff --git a/tests/graphics/SilkFX/res/layout/activity_glass.xml b/tests/graphics/SilkFX/res/layout/activity_glass.xml
index aa09f27..d591fc4 100644
--- a/tests/graphics/SilkFX/res/layout/activity_glass.xml
+++ b/tests/graphics/SilkFX/res/layout/activity_glass.xml
@@ -19,6 +19,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
     tools:context=".MainActivity">
 
     <ImageView
@@ -300,4 +301,4 @@
     </androidx.constraintlayout.widget.ConstraintLayout>
 
 
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/tests/graphics/SilkFX/res/layout/color_mode_controls.xml b/tests/graphics/SilkFX/res/layout/color_mode_controls.xml
index c0c0bab..9b2b0c8 100644
--- a/tests/graphics/SilkFX/res/layout/color_mode_controls.xml
+++ b/tests/graphics/SilkFX/res/layout/color_mode_controls.xml
@@ -19,6 +19,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:layout_margin="8dp"
     android:orientation="vertical">
 
     <TextView
@@ -61,4 +62,4 @@
 
     </LinearLayout>
 
-</com.android.test.silkfx.common.ColorModeControls>
\ No newline at end of file
+</com.android.test.silkfx.common.ColorModeControls>
diff --git a/tests/graphics/SilkFX/res/layout/common_base.xml b/tests/graphics/SilkFX/res/layout/common_base.xml
index c0eaf9b..ce6d850 100644
--- a/tests/graphics/SilkFX/res/layout/common_base.xml
+++ b/tests/graphics/SilkFX/res/layout/common_base.xml
@@ -18,6 +18,7 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
     android:orientation="vertical">
 
     <include layout="@layout/color_mode_controls" />
@@ -26,4 +27,4 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/tests/graphics/SilkFX/res/layout/hdr_glows.xml b/tests/graphics/SilkFX/res/layout/hdr_glows.xml
index b6050645..f1e553a 100644
--- a/tests/graphics/SilkFX/res/layout/hdr_glows.xml
+++ b/tests/graphics/SilkFX/res/layout/hdr_glows.xml
@@ -18,6 +18,7 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
     android:orientation="vertical">
 
     <include layout="@layout/color_mode_controls" />
@@ -48,4 +49,4 @@
         android:layout_height="50dp"
         android:layout_margin="8dp" />
 
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/tests/graphics/SilkFX/res/values/style.xml b/tests/graphics/SilkFX/res/values/style.xml
index 4dd626d..7550697 100644
--- a/tests/graphics/SilkFX/res/values/style.xml
+++ b/tests/graphics/SilkFX/res/values/style.xml
@@ -16,21 +16,16 @@
   -->
 <!-- Styles for immersive actions UI. -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <style name="Theme.BackgroundBlurTheme" parent= "Theme.AppCompat.Dialog">
+    <style name="Theme.BackgroundBlurTheme" parent="Theme.AppCompat.Dialog">
         <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowBlurBehindEnabled">true</item>
         <item name="android:backgroundDimEnabled">false</item>
         <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/graphics/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
index 6b6d3b8..ad7cde4 100644
--- a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
+++ b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
@@ -72,6 +72,7 @@
         super.onCreate(savedInstanceState)
 
         val list = ExpandableListView(this)
+        list.setFitsSystemWindows(true)
 
         setContentView(list)
 
diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp
index b16ba15..51a300b 100644
--- a/tests/vcn/Android.bp
+++ b/tests/vcn/Android.bp
@@ -14,21 +14,24 @@
 
 android_test {
     name: "FrameworksVcnTests",
+    // For access hidden connectivity methods in tests
+    defaults: ["framework-connectivity-test-defaults"],
     srcs: [
         "java/**/*.java",
         "java/**/*.kt",
     ],
     platform_apis: true,
-    defaults: ["framework-connectivity-test-defaults"],
     test_suites: ["device-tests"],
     certificate: "platform",
     static_libs: [
+        "android.net.vcn.flags-aconfig-java-export",
         "androidx.test.rules",
         "frameworks-base-testutils",
         "framework-protos",
         "mockito-target-minus-junit4",
         "net-tests-utils",
         "platform-test-annotations",
+        "service-connectivity-b-pre-jarjar",
         "services.core",
         "service-connectivity-tiramisu-pre-jarjar",
         "flag-junit",
diff --git a/tests/vcn/java/android/net/vcn/util/MtuUtilsTest.java b/tests/vcn/java/android/net/vcn/util/MtuUtilsTest.java
new file mode 100644
index 0000000..47638b0
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/util/MtuUtilsTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.util;
+
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256;
+import static android.net.vcn.util.MtuUtils.getMtu;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_MTU;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import static java.util.Collections.emptyList;
+
+import android.net.ipsec.ike.ChildSaProposal;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MtuUtilsTest {
+    private void verifyUnderlyingMtuZero(boolean isIpv4) {
+        assertEquals(
+                IPV6_MIN_MTU,
+                getMtu(emptyList(), ETHER_MTU /* maxMtu */, 0 /* underlyingMtu */, isIpv4));
+    }
+
+    @Test
+    public void testUnderlyingMtuZeroV4() {
+        verifyUnderlyingMtuZero(true /* isIpv4 */);
+    }
+
+    @Test
+    public void testUnderlyingMtuZeroV6() {
+        verifyUnderlyingMtuZero(false /* isIpv4 */);
+    }
+
+    private void verifyClampsToMaxMtu(boolean isIpv4) {
+        assertEquals(
+                0, getMtu(emptyList(), 0 /* maxMtu */, IPV6_MIN_MTU /* underlyingMtu */, isIpv4));
+    }
+
+    @Test
+    public void testClampsToMaxMtuV4() {
+        verifyClampsToMaxMtu(true /* isIpv4 */);
+    }
+
+    @Test
+    public void testClampsToMaxMtuV6() {
+        verifyClampsToMaxMtu(false /* isIpv4 */);
+    }
+
+    private List<ChildSaProposal> buildChildSaProposalsWithNormalModeAlgo() {
+        return Arrays.asList(
+                new ChildSaProposal.Builder()
+                        .addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256)
+                        .addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128)
+                        .build());
+    }
+
+    private void verifyNormalModeAlgorithmLessThanUnderlyingMtu(boolean isIpv4) {
+        final int actualMtu =
+                getMtu(
+                        buildChildSaProposalsWithNormalModeAlgo(),
+                        ETHER_MTU /* maxMtu */,
+                        ETHER_MTU /* underlyingMtu */,
+                        isIpv4);
+        assertTrue(ETHER_MTU > actualMtu);
+    }
+
+    @Test
+    public void testNormalModeAlgorithmLessThanUnderlyingMtuV4() {
+        verifyNormalModeAlgorithmLessThanUnderlyingMtu(true /* isIpv4 */);
+    }
+
+    @Test
+    public void testNormalModeAlgorithmLessThanUnderlyingMtuV6() {
+        verifyNormalModeAlgorithmLessThanUnderlyingMtu(false /* isIpv4 */);
+    }
+
+    @Test
+    public void testMtuIpv4LessThanMtuIpv6() {
+        final int actualMtuV4 =
+                getMtu(
+                        buildChildSaProposalsWithNormalModeAlgo(),
+                        ETHER_MTU /* maxMtu */,
+                        ETHER_MTU /* underlyingMtu */,
+                        true /* isIpv4 */);
+
+        final int actualMtuV6 =
+                getMtu(
+                        buildChildSaProposalsWithNormalModeAlgo(),
+                        ETHER_MTU /* maxMtu */,
+                        ETHER_MTU /* underlyingMtu */,
+                        false /* isIpv4 */);
+
+        assertTrue(actualMtuV4 < actualMtuV6);
+    }
+
+    private void verifyCombinedModeAlgorithmLessThanUnderlyingMtu(boolean isIpv4) {
+        final List<ChildSaProposal> saProposals =
+                Arrays.asList(
+                        new ChildSaProposal.Builder()
+                                .addEncryptionAlgorithm(
+                                        ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256)
+                                .addEncryptionAlgorithm(
+                                        ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256)
+                                .addEncryptionAlgorithm(
+                                        ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256)
+                                .build());
+
+        final int actualMtu =
+                getMtu(saProposals, ETHER_MTU /* maxMtu */, ETHER_MTU /* underlyingMtu */, isIpv4);
+        assertTrue(ETHER_MTU > actualMtu);
+    }
+
+    @Test
+    public void testCombinedModeAlgorithmLessThanUnderlyingMtuV4() {
+        verifyCombinedModeAlgorithmLessThanUnderlyingMtu(true /* isIpv4 */);
+    }
+
+    @Test
+    public void testCombinedModeAlgorithmLessThanUnderlyingMtuV6() {
+        verifyCombinedModeAlgorithmLessThanUnderlyingMtu(false /* isIpv4 */);
+    }
+}
diff --git a/tests/vcn/java/android/net/vcn/util/PersistableBundleUtilsTest.java b/tests/vcn/java/android/net/vcn/util/PersistableBundleUtilsTest.java
new file mode 100644
index 0000000..c84e600
--- /dev/null
+++ b/tests/vcn/java/android/net/vcn/util/PersistableBundleUtilsTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.util;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Objects;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PersistableBundleUtilsTest {
+    private static final String TEST_KEY = "testKey";
+    private static final String TEST_STRING_PREFIX = "testString";
+    private static final int[] TEST_INT_ARRAY = new int[] {0, 1, 2, 3, 4};
+
+    private static final int NUM_COLLECTION_ENTRIES = 10;
+
+    private static class TestKey {
+        private static final String TEST_INTEGER_KEY =
+                "mTestInteger"; // Purposely colliding with keys of test class to ensure namespacing
+        private final int mTestInteger;
+
+        TestKey(int testInteger) {
+            mTestInteger = testInteger;
+        }
+
+        TestKey(PersistableBundle in) {
+            mTestInteger = in.getInt(TEST_INTEGER_KEY);
+        }
+
+        public PersistableBundle toPersistableBundle() {
+            final PersistableBundle result = new PersistableBundle();
+
+            result.putInt(TEST_INTEGER_KEY, mTestInteger);
+
+            return result;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mTestInteger);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof TestKey)) {
+                return false;
+            }
+
+            final TestKey other = (TestKey) o;
+            return mTestInteger == other.mTestInteger;
+        }
+    }
+
+    private static class TestClass {
+        private static final String TEST_INTEGER_KEY = "mTestInteger";
+        private final int mTestInteger;
+
+        private static final String TEST_INT_ARRAY_KEY = "mTestIntArray";
+        private final int[] mTestIntArray;
+
+        private static final String TEST_STRING_KEY = "mTestString";
+        private final String mTestString;
+
+        private static final String TEST_PERSISTABLE_BUNDLE_KEY = "mTestPersistableBundle";
+        private final PersistableBundle mTestPersistableBundle;
+
+        TestClass(
+                int testInteger,
+                int[] testIntArray,
+                String testString,
+                PersistableBundle testPersistableBundle) {
+            mTestInteger = testInteger;
+            mTestIntArray = testIntArray;
+            mTestString = testString;
+            mTestPersistableBundle = testPersistableBundle;
+        }
+
+        TestClass(PersistableBundle in) {
+            mTestInteger = in.getInt(TEST_INTEGER_KEY);
+            mTestIntArray = in.getIntArray(TEST_INT_ARRAY_KEY);
+            mTestString = in.getString(TEST_STRING_KEY);
+            mTestPersistableBundle = in.getPersistableBundle(TEST_PERSISTABLE_BUNDLE_KEY);
+        }
+
+        public PersistableBundle toPersistableBundle() {
+            final PersistableBundle result = new PersistableBundle();
+
+            result.putInt(TEST_INTEGER_KEY, mTestInteger);
+            result.putIntArray(TEST_INT_ARRAY_KEY, mTestIntArray);
+            result.putString(TEST_STRING_KEY, mTestString);
+            result.putPersistableBundle(TEST_PERSISTABLE_BUNDLE_KEY, mTestPersistableBundle);
+
+            return result;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(
+                    mTestInteger,
+                    Arrays.hashCode(mTestIntArray),
+                    mTestString,
+                    mTestPersistableBundle);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof TestClass)) {
+                return false;
+            }
+
+            final TestClass other = (TestClass) o;
+
+            // TODO: Add a proper equals() to PersistableBundle. But in the meantime, force
+            // TODO: unparcelling in order to allow test comparison.
+            if (mTestPersistableBundle.size() != other.mTestPersistableBundle.size()) {
+                return false;
+            }
+
+            return mTestInteger == other.mTestInteger
+                    && Arrays.equals(mTestIntArray, other.mTestIntArray)
+                    && mTestString.equals(other.mTestString)
+                    && mTestPersistableBundle.kindofEquals(other.mTestPersistableBundle);
+        }
+    }
+
+    @Test
+    public void testListConversionLossless() throws Exception {
+        final List<TestClass> sourceList = new ArrayList<>();
+        for (int i = 0; i < NUM_COLLECTION_ENTRIES; i++) {
+            final PersistableBundle innerBundle = new PersistableBundle();
+            innerBundle.putInt(TEST_KEY, i);
+
+            sourceList.add(new TestClass(i, TEST_INT_ARRAY, TEST_STRING_PREFIX + i, innerBundle));
+        }
+
+        final PersistableBundle bundled =
+                PersistableBundleUtils.fromList(sourceList, TestClass::toPersistableBundle);
+        final List<TestClass> resultList = PersistableBundleUtils.toList(bundled, TestClass::new);
+
+        assertEquals(sourceList, resultList);
+    }
+
+    @Test
+    public void testMapConversionLossless() throws Exception {
+        final LinkedHashMap<TestKey, TestClass> sourceMap = new LinkedHashMap<>();
+        for (int i = 0; i < NUM_COLLECTION_ENTRIES; i++) {
+            final TestKey key = new TestKey(i * i);
+
+            final PersistableBundle innerBundle = new PersistableBundle();
+            innerBundle.putInt(TEST_KEY, i);
+            final TestClass value =
+                    new TestClass(i, TEST_INT_ARRAY, TEST_STRING_PREFIX + i, innerBundle);
+
+            sourceMap.put(key, value);
+        }
+
+        final PersistableBundle bundled =
+                PersistableBundleUtils.fromMap(
+                        sourceMap, TestKey::toPersistableBundle, TestClass::toPersistableBundle);
+        final LinkedHashMap<TestKey, TestClass> resultList =
+                PersistableBundleUtils.toMap(bundled, TestKey::new, TestClass::new);
+
+        assertEquals(sourceMap, resultList);
+    }
+
+    @Test
+    public void testByteArrayConversionLossless() {
+        final byte[] byteArray = "testByteArrayConversionLossless".getBytes();
+
+        PersistableBundle bundle = PersistableBundleUtils.fromByteArray(byteArray);
+        byte[] result = PersistableBundleUtils.toByteArray(bundle);
+
+        assertArrayEquals(byteArray, result);
+    }
+
+    @Test
+    public void testIntegerConversionLossless() throws Exception {
+        final int testInt = 1;
+        final PersistableBundle integerBundle =
+                PersistableBundleUtils.INTEGER_SERIALIZER.toPersistableBundle(testInt);
+        final int result =
+                PersistableBundleUtils.INTEGER_DESERIALIZER.fromPersistableBundle(integerBundle);
+
+        assertEquals(testInt, result);
+    }
+
+    private PersistableBundle getTestBundle() {
+        final PersistableBundle bundle = new PersistableBundle();
+
+        bundle.putBoolean(TEST_KEY + "Boolean", true);
+        bundle.putBooleanArray(TEST_KEY + "BooleanArray", new boolean[] {true, false});
+        bundle.putDouble(TEST_KEY + "Double", 0.1);
+        bundle.putDoubleArray(TEST_KEY + "DoubleArray", new double[] {0.1, 0.2, 0.3});
+        bundle.putInt(TEST_KEY + "Int", 1);
+        bundle.putIntArray(TEST_KEY + "IntArray", new int[] {1, 2});
+        bundle.putLong(TEST_KEY + "Long", 5L);
+        bundle.putLongArray(TEST_KEY + "LongArray", new long[] {0L, -1L, -2L});
+        bundle.putString(TEST_KEY + "String", "TEST");
+        bundle.putStringArray(TEST_KEY + "StringArray", new String[] {"foo", "bar", "bas"});
+        bundle.putPersistableBundle(
+                TEST_KEY + "PersistableBundle",
+                new TestClass(1, TEST_INT_ARRAY, TEST_STRING_PREFIX, new PersistableBundle())
+                        .toPersistableBundle());
+
+        return bundle;
+    }
+
+    @Test
+    public void testMinimizeBundle() throws Exception {
+        final String[] minimizedKeys =
+                new String[] {
+                    TEST_KEY + "Boolean",
+                    TEST_KEY + "BooleanArray",
+                    TEST_KEY + "Double",
+                    TEST_KEY + "DoubleArray",
+                    TEST_KEY + "Int",
+                    TEST_KEY + "IntArray",
+                    TEST_KEY + "Long",
+                    TEST_KEY + "LongArray",
+                    TEST_KEY + "String",
+                    TEST_KEY + "StringArray",
+                    TEST_KEY + "PersistableBundle"
+                };
+
+        final PersistableBundle testBundle = getTestBundle();
+        testBundle.putBoolean(TEST_KEY + "Boolean2", true);
+
+        final PersistableBundle minimized =
+                PersistableBundleUtils.minimizeBundle(testBundle, minimizedKeys);
+
+        // Verify that the minimized bundle is NOT the same in size OR values due to the extra
+        // Boolean2 key
+        assertFalse(PersistableBundleUtils.isEqual(testBundle, minimized));
+
+        // Verify that removing the extra key from the source bundle results in equality.
+        testBundle.remove(TEST_KEY + "Boolean2");
+        assertTrue(PersistableBundleUtils.isEqual(testBundle, minimized));
+    }
+
+    @Test
+    public void testToFromDiskStableBytes() throws Exception {
+        final PersistableBundle testBundle = getTestBundle();
+        final PersistableBundle result =
+                PersistableBundleUtils.fromDiskStableBytes(
+                        PersistableBundleUtils.toDiskStableBytes(testBundle));
+        assertTrue(PersistableBundleUtils.isEqual(testBundle, result));
+    }
+
+    @Test
+    public void testEquality_identical() throws Exception {
+        final PersistableBundle left = getTestBundle();
+        final PersistableBundle right = getTestBundle();
+
+        assertTrue(PersistableBundleUtils.isEqual(left, right));
+    }
+
+    @Test
+    public void testEquality_different() throws Exception {
+        final PersistableBundle left = getTestBundle();
+        final PersistableBundle right = getTestBundle();
+
+        left.putBoolean(TEST_KEY + "Boolean2", true);
+        assertFalse(PersistableBundleUtils.isEqual(left, right));
+
+        left.remove(TEST_KEY + "Boolean2");
+        assertTrue(PersistableBundleUtils.isEqual(left, right));
+    }
+
+    @Test
+    public void testEquality_null() throws Exception {
+        assertFalse(PersistableBundleUtils.isEqual(getTestBundle(), null));
+        assertFalse(PersistableBundleUtils.isEqual(null, getTestBundle()));
+        assertTrue(PersistableBundleUtils.isEqual(null, null));
+    }
+}
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 4ab8e6a..26a2a06 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -77,6 +77,8 @@
 import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.net.vcn.VcnManager;
 import android.net.vcn.VcnUnderlyingNetworkPolicy;
+import android.net.vcn.util.PersistableBundleUtils;
+import android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
@@ -99,8 +101,6 @@
 import com.android.server.vcn.Vcn;
 import com.android.server.vcn.VcnContext;
 import com.android.server.vcn.VcnNetworkProvider;
-import com.android.server.vcn.util.PersistableBundleUtils;
-import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import org.junit.Before;
 import org.junit.Rule;
diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
index f1f74bc..77f82f0 100644
--- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
@@ -19,6 +19,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.vcn.VcnManager.VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
@@ -26,7 +27,6 @@
 
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -55,7 +55,6 @@
 import android.content.IntentFilter;
 import android.net.vcn.VcnManager;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
 import android.os.test.TestLooper;
@@ -72,6 +71,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.modules.utils.HandlerExecutor;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 20b7f1f..76be232 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -69,6 +69,7 @@
 import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.net.vcn.VcnManager.VcnErrorCode;
 import android.net.vcn.VcnTransportInfo;
+import android.net.vcn.util.MtuUtils;
 import android.os.PersistableBundle;
 
 import androidx.test.filters.SmallTest;
@@ -79,7 +80,6 @@
 import com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
 import com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
 import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
-import com.android.server.vcn.util.MtuUtils;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index 613b926..b9fe76a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -25,13 +25,13 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
 import static com.android.server.vcn.VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS;
 import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
 import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
 import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
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 441a4ae..5db02e3 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -19,11 +19,11 @@
 import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY;
 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 android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 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;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index d85c515..4f34f9f 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -23,13 +23,13 @@
 import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS;
 import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS;
 import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_FALLBACK;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesCellPriorityRule;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesPriorityRule;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
index 1d68721..a315b069 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
@@ -17,9 +17,9 @@
 package com.android.server.vcn.routeselection;
 
 import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY;
+import static android.net.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID;
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java b/tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java
deleted file mode 100644
index e9e7078..0000000
--- a/tests/vcn/java/com/android/server/vcn/util/MtuUtilsTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.vcn.util;
-
-import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
-import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
-import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
-import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
-import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
-import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256;
-
-import static com.android.net.module.util.NetworkStackConstants.ETHER_MTU;
-import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
-import static com.android.server.vcn.util.MtuUtils.getMtu;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import static java.util.Collections.emptyList;
-
-import android.net.ipsec.ike.ChildSaProposal;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MtuUtilsTest {
-    private void verifyUnderlyingMtuZero(boolean isIpv4) {
-        assertEquals(
-                IPV6_MIN_MTU,
-                getMtu(emptyList(), ETHER_MTU /* maxMtu */, 0 /* underlyingMtu */, isIpv4));
-    }
-
-    @Test
-    public void testUnderlyingMtuZeroV4() {
-        verifyUnderlyingMtuZero(true /* isIpv4 */);
-    }
-
-    @Test
-    public void testUnderlyingMtuZeroV6() {
-        verifyUnderlyingMtuZero(false /* isIpv4 */);
-    }
-
-    private void verifyClampsToMaxMtu(boolean isIpv4) {
-        assertEquals(
-                0, getMtu(emptyList(), 0 /* maxMtu */, IPV6_MIN_MTU /* underlyingMtu */, isIpv4));
-    }
-
-    @Test
-    public void testClampsToMaxMtuV4() {
-        verifyClampsToMaxMtu(true /* isIpv4 */);
-    }
-
-    @Test
-    public void testClampsToMaxMtuV6() {
-        verifyClampsToMaxMtu(false /* isIpv4 */);
-    }
-
-    private List<ChildSaProposal> buildChildSaProposalsWithNormalModeAlgo() {
-        return Arrays.asList(
-                new ChildSaProposal.Builder()
-                        .addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256)
-                        .addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128)
-                        .build());
-    }
-
-    private void verifyNormalModeAlgorithmLessThanUnderlyingMtu(boolean isIpv4) {
-        final int actualMtu =
-                getMtu(
-                        buildChildSaProposalsWithNormalModeAlgo(),
-                        ETHER_MTU /* maxMtu */,
-                        ETHER_MTU /* underlyingMtu */,
-                        isIpv4);
-        assertTrue(ETHER_MTU > actualMtu);
-    }
-
-    @Test
-    public void testNormalModeAlgorithmLessThanUnderlyingMtuV4() {
-        verifyNormalModeAlgorithmLessThanUnderlyingMtu(true /* isIpv4 */);
-    }
-
-    @Test
-    public void testNormalModeAlgorithmLessThanUnderlyingMtuV6() {
-        verifyNormalModeAlgorithmLessThanUnderlyingMtu(false /* isIpv4 */);
-    }
-
-    @Test
-    public void testMtuIpv4LessThanMtuIpv6() {
-        final int actualMtuV4 =
-                getMtu(
-                        buildChildSaProposalsWithNormalModeAlgo(),
-                        ETHER_MTU /* maxMtu */,
-                        ETHER_MTU /* underlyingMtu */,
-                        true /* isIpv4 */);
-
-        final int actualMtuV6 =
-                getMtu(
-                        buildChildSaProposalsWithNormalModeAlgo(),
-                        ETHER_MTU /* maxMtu */,
-                        ETHER_MTU /* underlyingMtu */,
-                        false /* isIpv4 */);
-
-        assertTrue(actualMtuV4 < actualMtuV6);
-    }
-
-    private void verifyCombinedModeAlgorithmLessThanUnderlyingMtu(boolean isIpv4) {
-        final List<ChildSaProposal> saProposals =
-                Arrays.asList(
-                        new ChildSaProposal.Builder()
-                                .addEncryptionAlgorithm(
-                                        ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256)
-                                .addEncryptionAlgorithm(
-                                        ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256)
-                                .addEncryptionAlgorithm(
-                                        ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256)
-                                .build());
-
-        final int actualMtu =
-                getMtu(saProposals, ETHER_MTU /* maxMtu */, ETHER_MTU /* underlyingMtu */, isIpv4);
-        assertTrue(ETHER_MTU > actualMtu);
-    }
-
-    @Test
-    public void testCombinedModeAlgorithmLessThanUnderlyingMtuV4() {
-        verifyCombinedModeAlgorithmLessThanUnderlyingMtu(true /* isIpv4 */);
-    }
-
-    @Test
-    public void testCombinedModeAlgorithmLessThanUnderlyingMtuV6() {
-        verifyCombinedModeAlgorithmLessThanUnderlyingMtu(false /* isIpv4 */);
-    }
-}
diff --git a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java
deleted file mode 100644
index 9c6d852..0000000
--- a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.vcn.util;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.os.PersistableBundle;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Objects;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class PersistableBundleUtilsTest {
-    private static final String TEST_KEY = "testKey";
-    private static final String TEST_STRING_PREFIX = "testString";
-    private static final int[] TEST_INT_ARRAY = new int[] {0, 1, 2, 3, 4};
-
-    private static final int NUM_COLLECTION_ENTRIES = 10;
-
-    private static class TestKey {
-        private static final String TEST_INTEGER_KEY =
-                "mTestInteger"; // Purposely colliding with keys of test class to ensure namespacing
-        private final int mTestInteger;
-
-        TestKey(int testInteger) {
-            mTestInteger = testInteger;
-        }
-
-        TestKey(PersistableBundle in) {
-            mTestInteger = in.getInt(TEST_INTEGER_KEY);
-        }
-
-        public PersistableBundle toPersistableBundle() {
-            final PersistableBundle result = new PersistableBundle();
-
-            result.putInt(TEST_INTEGER_KEY, mTestInteger);
-
-            return result;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mTestInteger);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof TestKey)) {
-                return false;
-            }
-
-            final TestKey other = (TestKey) o;
-            return mTestInteger == other.mTestInteger;
-        }
-    }
-
-    private static class TestClass {
-        private static final String TEST_INTEGER_KEY = "mTestInteger";
-        private final int mTestInteger;
-
-        private static final String TEST_INT_ARRAY_KEY = "mTestIntArray";
-        private final int[] mTestIntArray;
-
-        private static final String TEST_STRING_KEY = "mTestString";
-        private final String mTestString;
-
-        private static final String TEST_PERSISTABLE_BUNDLE_KEY = "mTestPersistableBundle";
-        private final PersistableBundle mTestPersistableBundle;
-
-        TestClass(
-                int testInteger,
-                int[] testIntArray,
-                String testString,
-                PersistableBundle testPersistableBundle) {
-            mTestInteger = testInteger;
-            mTestIntArray = testIntArray;
-            mTestString = testString;
-            mTestPersistableBundle = testPersistableBundle;
-        }
-
-        TestClass(PersistableBundle in) {
-            mTestInteger = in.getInt(TEST_INTEGER_KEY);
-            mTestIntArray = in.getIntArray(TEST_INT_ARRAY_KEY);
-            mTestString = in.getString(TEST_STRING_KEY);
-            mTestPersistableBundle = in.getPersistableBundle(TEST_PERSISTABLE_BUNDLE_KEY);
-        }
-
-        public PersistableBundle toPersistableBundle() {
-            final PersistableBundle result = new PersistableBundle();
-
-            result.putInt(TEST_INTEGER_KEY, mTestInteger);
-            result.putIntArray(TEST_INT_ARRAY_KEY, mTestIntArray);
-            result.putString(TEST_STRING_KEY, mTestString);
-            result.putPersistableBundle(TEST_PERSISTABLE_BUNDLE_KEY, mTestPersistableBundle);
-
-            return result;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(
-                    mTestInteger,
-                    Arrays.hashCode(mTestIntArray),
-                    mTestString,
-                    mTestPersistableBundle);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof TestClass)) {
-                return false;
-            }
-
-            final TestClass other = (TestClass) o;
-
-            // TODO: Add a proper equals() to PersistableBundle. But in the meantime, force
-            // TODO: unparcelling in order to allow test comparison.
-            if (mTestPersistableBundle.size() != other.mTestPersistableBundle.size()) {
-                return false;
-            }
-
-            return mTestInteger == other.mTestInteger
-                    && Arrays.equals(mTestIntArray, other.mTestIntArray)
-                    && mTestString.equals(other.mTestString)
-                    && mTestPersistableBundle.kindofEquals(other.mTestPersistableBundle);
-        }
-    }
-
-    @Test
-    public void testListConversionLossless() throws Exception {
-        final List<TestClass> sourceList = new ArrayList<>();
-        for (int i = 0; i < NUM_COLLECTION_ENTRIES; i++) {
-            final PersistableBundle innerBundle = new PersistableBundle();
-            innerBundle.putInt(TEST_KEY, i);
-
-            sourceList.add(new TestClass(i, TEST_INT_ARRAY, TEST_STRING_PREFIX + i, innerBundle));
-        }
-
-        final PersistableBundle bundled =
-                PersistableBundleUtils.fromList(sourceList, TestClass::toPersistableBundle);
-        final List<TestClass> resultList = PersistableBundleUtils.toList(bundled, TestClass::new);
-
-        assertEquals(sourceList, resultList);
-    }
-
-    @Test
-    public void testMapConversionLossless() throws Exception {
-        final LinkedHashMap<TestKey, TestClass> sourceMap = new LinkedHashMap<>();
-        for (int i = 0; i < NUM_COLLECTION_ENTRIES; i++) {
-            final TestKey key = new TestKey(i * i);
-
-            final PersistableBundle innerBundle = new PersistableBundle();
-            innerBundle.putInt(TEST_KEY, i);
-            final TestClass value =
-                    new TestClass(i, TEST_INT_ARRAY, TEST_STRING_PREFIX + i, innerBundle);
-
-            sourceMap.put(key, value);
-        }
-
-        final PersistableBundle bundled =
-                PersistableBundleUtils.fromMap(
-                        sourceMap, TestKey::toPersistableBundle, TestClass::toPersistableBundle);
-        final LinkedHashMap<TestKey, TestClass> resultList =
-                PersistableBundleUtils.toMap(bundled, TestKey::new, TestClass::new);
-
-        assertEquals(sourceMap, resultList);
-    }
-
-    @Test
-    public void testByteArrayConversionLossless() {
-        final byte[] byteArray = "testByteArrayConversionLossless".getBytes();
-
-        PersistableBundle bundle = PersistableBundleUtils.fromByteArray(byteArray);
-        byte[] result = PersistableBundleUtils.toByteArray(bundle);
-
-        assertArrayEquals(byteArray, result);
-    }
-
-    @Test
-    public void testIntegerConversionLossless() throws Exception {
-        final int testInt = 1;
-        final PersistableBundle integerBundle =
-                PersistableBundleUtils.INTEGER_SERIALIZER.toPersistableBundle(testInt);
-        final int result =
-                PersistableBundleUtils.INTEGER_DESERIALIZER.fromPersistableBundle(integerBundle);
-
-        assertEquals(testInt, result);
-    }
-
-    private PersistableBundle getTestBundle() {
-        final PersistableBundle bundle = new PersistableBundle();
-
-        bundle.putBoolean(TEST_KEY + "Boolean", true);
-        bundle.putBooleanArray(TEST_KEY + "BooleanArray", new boolean[] {true, false});
-        bundle.putDouble(TEST_KEY + "Double", 0.1);
-        bundle.putDoubleArray(TEST_KEY + "DoubleArray", new double[] {0.1, 0.2, 0.3});
-        bundle.putInt(TEST_KEY + "Int", 1);
-        bundle.putIntArray(TEST_KEY + "IntArray", new int[] {1, 2});
-        bundle.putLong(TEST_KEY + "Long", 5L);
-        bundle.putLongArray(TEST_KEY + "LongArray", new long[] {0L, -1L, -2L});
-        bundle.putString(TEST_KEY + "String", "TEST");
-        bundle.putStringArray(TEST_KEY + "StringArray", new String[] {"foo", "bar", "bas"});
-        bundle.putPersistableBundle(
-                TEST_KEY + "PersistableBundle",
-                new TestClass(1, TEST_INT_ARRAY, TEST_STRING_PREFIX, new PersistableBundle())
-                        .toPersistableBundle());
-
-        return bundle;
-    }
-
-    @Test
-    public void testMinimizeBundle() throws Exception {
-        final String[] minimizedKeys =
-                new String[] {
-                    TEST_KEY + "Boolean",
-                    TEST_KEY + "BooleanArray",
-                    TEST_KEY + "Double",
-                    TEST_KEY + "DoubleArray",
-                    TEST_KEY + "Int",
-                    TEST_KEY + "IntArray",
-                    TEST_KEY + "Long",
-                    TEST_KEY + "LongArray",
-                    TEST_KEY + "String",
-                    TEST_KEY + "StringArray",
-                    TEST_KEY + "PersistableBundle"
-                };
-
-        final PersistableBundle testBundle = getTestBundle();
-        testBundle.putBoolean(TEST_KEY + "Boolean2", true);
-
-        final PersistableBundle minimized =
-                PersistableBundleUtils.minimizeBundle(testBundle, minimizedKeys);
-
-        // Verify that the minimized bundle is NOT the same in size OR values due to the extra
-        // Boolean2 key
-        assertFalse(PersistableBundleUtils.isEqual(testBundle, minimized));
-
-        // Verify that removing the extra key from the source bundle results in equality.
-        testBundle.remove(TEST_KEY + "Boolean2");
-        assertTrue(PersistableBundleUtils.isEqual(testBundle, minimized));
-    }
-
-    @Test
-    public void testToFromDiskStableBytes() throws Exception {
-        final PersistableBundle testBundle = getTestBundle();
-        final PersistableBundle result =
-                PersistableBundleUtils.fromDiskStableBytes(
-                        PersistableBundleUtils.toDiskStableBytes(testBundle));
-        assertTrue(PersistableBundleUtils.isEqual(testBundle, result));
-    }
-
-    @Test
-    public void testEquality_identical() throws Exception {
-        final PersistableBundle left = getTestBundle();
-        final PersistableBundle right = getTestBundle();
-
-        assertTrue(PersistableBundleUtils.isEqual(left, right));
-    }
-
-    @Test
-    public void testEquality_different() throws Exception {
-        final PersistableBundle left = getTestBundle();
-        final PersistableBundle right = getTestBundle();
-
-        left.putBoolean(TEST_KEY + "Boolean2", true);
-        assertFalse(PersistableBundleUtils.isEqual(left, right));
-
-        left.remove(TEST_KEY + "Boolean2");
-        assertTrue(PersistableBundleUtils.isEqual(left, right));
-    }
-
-    @Test
-    public void testEquality_null() throws Exception {
-        assertFalse(PersistableBundleUtils.isEqual(getTestBundle(), null));
-        assertFalse(PersistableBundleUtils.isEqual(null, getTestBundle()));
-        assertTrue(PersistableBundleUtils.isEqual(null, null));
-    }
-}
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 40ff5b6..03da460a 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -111,8 +111,8 @@
 // Retrieves the build fingerprint of aapt2.
 std::string GetToolFingerprint();
 
-template <typename T>
-typename std::enable_if<std::is_arithmetic<T>::value, int>::type compare(const T& a, const T& b) {
+template <std::integral T>
+int compare(T a, T b) {
   if (a < b) {
     return -1;
   } else if (a > b) {
@@ -123,10 +123,7 @@
 
 // Makes a std::unique_ptr<> with the template parameter inferred by the compiler.
 // This will be present in C++14 and can be removed then.
-template <typename T, class... Args>
-std::unique_ptr<T> make_unique(Args&&... args) {
-  return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
-}
+using std::make_unique;
 
 // Writes a set of items to the std::ostream, joining the times with the provided separator.
 template <typename Container>
diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp
index e6d0a3d..2ebede3 100644
--- a/tools/systemfeatures/Android.bp
+++ b/tools/systemfeatures/Android.bp
@@ -13,6 +13,7 @@
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
+        ":framework-metalava-annotations",
     ],
     static_libs: [
         "guava",
@@ -26,6 +27,12 @@
     static_libs: ["systemfeatures-gen-lib"],
 }
 
+java_plugin {
+    name: "systemfeatures-metadata-processor",
+    processor_class: "com.android.systemfeatures.SystemFeaturesMetadataProcessor",
+    static_libs: ["systemfeatures-gen-lib"],
+}
+
 genrule {
     name: "systemfeatures-gen-tests-srcs",
     cmd: "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwNoFeatures --readonly=false > $(location RwNoFeatures.java) && " +
@@ -61,6 +68,7 @@
         "systemfeatures-gen-lib",
         "truth",
     ],
+    plugins: ["systemfeatures-metadata-processor"],
 }
 
 // Rename the goldens as they may be copied into the source tree, and we don't
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesMetadataProcessor.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesMetadataProcessor.kt
new file mode 100644
index 0000000..100d869
--- /dev/null
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesMetadataProcessor.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.systemfeatures
+
+import android.annotation.SdkConstant
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.JavaFile
+import com.squareup.javapoet.TypeSpec
+import java.io.IOException
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.ProcessingEnvironment
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.tools.Diagnostic
+
+/*
+ * Simple Java code generator for computing metadata for system features.
+ *
+ * <p>The output is a single class file, `com.android.internal.pm.SystemFeaturesMetadata`, with
+ * properties computed from feature constant definitions in the PackageManager class. This
+ * class is only produced if the processed environment includes PackageManager; all other
+ * invocations are ignored.
+ */
+class SystemFeaturesMetadataProcessor : AbstractProcessor() {
+
+    private lateinit var packageManagerType: TypeElement
+
+    override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latestSupported()
+
+    override fun getSupportedAnnotationTypes() = setOf(SDK_CONSTANT_ANNOTATION_NAME)
+
+    override fun init(processingEnv: ProcessingEnvironment) {
+        super.init(processingEnv)
+        packageManagerType =
+            processingEnv.elementUtils.getTypeElement("android.content.pm.PackageManager")!!
+    }
+
+    override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
+        if (roundEnv.processingOver()) {
+            return false
+        }
+
+        // We're only interested in feature constants defined in PackageManager.
+        var featureCount = 0
+        roundEnv.getElementsAnnotatedWith(SdkConstant::class.java).forEach {
+            if (
+                it.enclosingElement == packageManagerType &&
+                    it.getAnnotation(SdkConstant::class.java).value ==
+                        SdkConstant.SdkConstantType.FEATURE
+            ) {
+                featureCount++
+            }
+        }
+
+        if (featureCount == 0) {
+            // This is fine, and happens for any environment that doesn't include PackageManager.
+            return false
+        }
+
+        val systemFeatureMetadata =
+            TypeSpec.classBuilder("SystemFeaturesMetadata")
+                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+                .addJavadoc("@hide")
+                .addField(
+                    FieldSpec.builder(Int::class.java, "SDK_FEATURE_COUNT")
+                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
+                        .addJavadoc(
+                            "The number of `@SdkConstant` features defined in PackageManager."
+                        )
+                        .addJavadoc("@hide")
+                        .initializer("\$L", featureCount)
+                        .build()
+                )
+                .build()
+
+        try {
+            JavaFile.builder("com.android.internal.pm", systemFeatureMetadata)
+                .skipJavaLangImports(true)
+                .build()
+                .writeTo(processingEnv.filer)
+        } catch (e: IOException) {
+            processingEnv.messager.printMessage(
+                Diagnostic.Kind.ERROR,
+                "Failed to write file: ${e.message}",
+            )
+        }
+
+        return true
+    }
+
+    companion object {
+        private val SDK_CONSTANT_ANNOTATION_NAME = SdkConstant::class.qualifiedName
+    }
+}
diff --git a/tools/systemfeatures/tests/src/PackageManager.java b/tools/systemfeatures/tests/src/PackageManager.java
index db67048..839a937 100644
--- a/tools/systemfeatures/tests/src/PackageManager.java
+++ b/tools/systemfeatures/tests/src/PackageManager.java
@@ -16,14 +16,33 @@
 
 package android.content.pm;
 
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+
 /** Stub for testing */
 public class PackageManager {
+    @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_AUTO = "automotive";
+
+    @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_PC = "pc";
+
+    @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_VULKAN = "vulkan";
+
+    @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_WATCH = "watch";
+
+    @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_WIFI = "wifi";
 
+    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+    public static final String FEATURE_INTENT_CATEGORY = "intent_category_with_feature_name_prefix";
+
+    public static final String FEATURE_NOT_ANNOTATED = "not_annotated";
+
+    public static final String NOT_FEATURE = "not_feature";
+
     /** @hide */
     public boolean hasSystemFeature(String featureName, int version) {
         return false;
diff --git a/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java b/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java
new file mode 100644
index 0000000..4ffb5b9
--- /dev/null
+++ b/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.systemfeatures;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.internal.pm.SystemFeaturesMetadata;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SystemFeaturesMetadataProcessorTest {
+
+    @Test
+    public void testSdkFeatureCount() {
+        // See the fake PackageManager definition in this directory.
+        // It defines 5 annotated features, and any/all other constants should be ignored.
+        assertThat(SystemFeaturesMetadata.SDK_FEATURE_COUNT).isEqualTo(5);
+    }
+}